Class: Philiprehberger::ColorConvert::Color

Inherits:
Object
  • Object
show all
Defined in:
lib/philiprehberger/color_convert/color.rb

Overview

Represents a color with conversion, manipulation, and comparison methods.

Constant Summary collapse

COLOR_BLINDNESS_MATRICES =

Color blindness simulation matrices (Brettel/Vienot method).

{
  protanopia: [
    [0.152286, 1.052583, -0.204868],
    [0.114503, 0.786281, 0.099216],
    [-0.003882, -0.048116, 1.051998]
  ],
  deuteranopia: [
    [0.367322, 0.860646, -0.227968],
    [0.280085, 0.672501, 0.047413],
    [-0.011820, 0.042940, 0.968881]
  ],
  tritanopia: [
    [1.255528, -0.076749, -0.178779],
    [-0.078411, 0.930809, 0.147602],
    [0.004733, 0.691367, 0.303900]
  ]
}.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(r, g, b, alpha: 1.0) ⇒ Color

Returns a new instance of Color.

Parameters:

  • r (Integer)

    red component (0-255)

  • g (Integer)

    green component (0-255)

  • b (Integer)

    blue component (0-255)

  • alpha (Float) (defaults to: 1.0)

    alpha component (0.0-1.0, default 1.0)



23
24
25
26
27
28
# File 'lib/philiprehberger/color_convert/color.rb', line 23

def initialize(r, g, b, alpha: 1.0)
  @r = clamp(r.round, 0, 255)
  @g = clamp(g.round, 0, 255)
  @b = clamp(b.round, 0, 255)
  @alpha = clamp(alpha.to_f, 0.0, 1.0)
end

Instance Attribute Details

#alphaFloat (readonly)

Returns alpha component (0.0-1.0).

Returns:

  • (Float)

    alpha component (0.0-1.0)



17
18
19
# File 'lib/philiprehberger/color_convert/color.rb', line 17

def alpha
  @alpha
end

#bInteger (readonly)

Returns blue component (0-255).

Returns:

  • (Integer)

    blue component (0-255)



14
15
16
# File 'lib/philiprehberger/color_convert/color.rb', line 14

def b
  @b
end

#gInteger (readonly)

Returns green component (0-255).

Returns:

  • (Integer)

    green component (0-255)



11
12
13
# File 'lib/philiprehberger/color_convert/color.rb', line 11

def g
  @g
end

#rInteger (readonly)

Returns red component (0-255).

Returns:

  • (Integer)

    red component (0-255)



8
9
10
# File 'lib/philiprehberger/color_convert/color.rb', line 8

def r
  @r
end

Class Method Details

.delinearize_srgb_class(c) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



528
529
530
531
532
533
534
# File 'lib/philiprehberger/color_convert/color.rb', line 528

def self.delinearize_srgb_class(c)
  if c <= 0.0031308
    12.92 * c
  else
    (1.055 * (c**(1.0 / 2.4))) - 0.055
  end
end

.from_cmyk(c, m, y, k) ⇒ Color

Create a Color from CMYK values.

Parameters:

  • c (Numeric)

    cyan (0-100)

  • m (Numeric)

    magenta (0-100)

  • y (Numeric)

    yellow (0-100)

  • k (Numeric)

    key/black (0-100)

Returns:



449
450
451
452
453
454
455
456
457
458
459
460
# File 'lib/philiprehberger/color_convert/color.rb', line 449

def self.from_cmyk(c, m, y, k)
  c /= 100.0
  m /= 100.0
  y /= 100.0
  k /= 100.0

  r = 255 * (1 - c) * (1 - k)
  g = 255 * (1 - m) * (1 - k)
  b = 255 * (1 - y) * (1 - k)

  new(r.round, g.round, b.round)
end

.from_hsl(h, s, l, alpha: 1.0) ⇒ Color

Create a Color from HSL values.

Parameters:

  • h (Numeric)

    hue (0-360)

  • s (Numeric)

    saturation (0-100)

  • l (Numeric)

    lightness (0-100)

  • alpha (Float) (defaults to: 1.0)

    alpha component (0.0-1.0, default 1.0)

Returns:



422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
# File 'lib/philiprehberger/color_convert/color.rb', line 422

def self.from_hsl(h, s, l, alpha: 1.0)
  h /= 360.0
  s /= 100.0
  l /= 100.0

  if s.zero?
    val = (l * 255).round
    return new(val, val, val, alpha: alpha)
  end

  q = l < 0.5 ? l * (1 + s) : l + s - (l * s)
  p = (2 * l) - q

  r = hue_to_rgb(p, q, h + (1.0 / 3))
  g = hue_to_rgb(p, q, h)
  b = hue_to_rgb(p, q, h - (1.0 / 3))

  new((r * 255).round, (g * 255).round, (b * 255).round, alpha: alpha)
end

.from_lab(l, a, b) ⇒ Color

Create a Color from CIELAB values (D65 illuminant).

Parameters:

  • l (Numeric)

    lightness (0-100)

  • a (Numeric)

    green-red component (approx -128 to 127)

  • b (Numeric)

    blue-yellow component (approx -128 to 127)

Returns:



468
469
470
471
472
473
474
475
476
477
478
479
# File 'lib/philiprehberger/color_convert/color.rb', line 468

def self.from_lab(l, a, b)
  # LAB to XYZ
  fy = (l + 16.0) / 116.0
  fx = (a / 500.0) + fy
  fz = fy - (b / 200.0)

  x = lab_f_inv(fx) * 95.047
  y = lab_f_inv(fy) * 100.0
  z = lab_f_inv(fz) * 108.883

  from_xyz(x, y, z)
end

.from_xyz(x, y, z) ⇒ Color

Create a Color from CIE XYZ values.

Parameters:

  • x (Numeric)

    X component

  • y (Numeric)

    Y component

  • z (Numeric)

    Z component

Returns:



487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
# File 'lib/philiprehberger/color_convert/color.rb', line 487

def self.from_xyz(x, y, z)
  x /= 100.0
  y /= 100.0
  z /= 100.0

  r = (x * 3.2404542) + (y * -1.5371385) + (z * -0.4985314)
  g = (x * -0.9692660) + (y * 1.8760108) + (z * 0.0415560)
  b = (x * 0.0556434) + (y * -0.2040259) + (z * 1.0572252)

  r = delinearize_srgb_class(r)
  g = delinearize_srgb_class(g)
  b = delinearize_srgb_class(b)

  new(
    [[r * 255, 0].max, 255].min.round,
    [[g * 255, 0].max, 255].min.round,
    [[b * 255, 0].max, 255].min.round
  )
end

.hue_to_rgb(p, q, t) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



508
509
510
511
512
513
514
515
516
# File 'lib/philiprehberger/color_convert/color.rb', line 508

def self.hue_to_rgb(p, q, t)
  t += 1 if t.negative?
  t -= 1 if t > 1
  return p + ((q - p) * 6 * t) if t < 1.0 / 6
  return q if t < 1.0 / 2
  return p + ((q - p) * ((2.0 / 3) - t) * 6) if t < 2.0 / 3

  p
end

.lab_f_inv(t) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



519
520
521
522
523
524
525
# File 'lib/philiprehberger/color_convert/color.rb', line 519

def self.lab_f_inv(t)
  if t > 6.0 / 29
    t**3
  else
    3.0 * ((6.0 / 29)**2) * (t - (4.0 / 29))
  end
end

Instance Method Details

#==(other) ⇒ Boolean

Returns:

  • (Boolean)


411
412
413
# File 'lib/philiprehberger/color_convert/color.rb', line 411

def ==(other)
  other.is_a?(Color) && @r == other.r && @g == other.g && @b == other.b && @alpha == other.alpha
end

#analogousArray<Color>

Generate analogous colors (30 degrees apart on the color wheel).

Returns:

  • (Array<Color>)

    array of 3 colors: -30deg, self, +30deg



252
253
254
255
256
257
258
259
# File 'lib/philiprehberger/color_convert/color.rb', line 252

def analogous
  hsl = to_hsl
  [
    self.class.from_hsl((hsl[:h] - 30) % 360, hsl[:s], hsl[:l]),
    self.class.new(@r, @g, @b),
    self.class.from_hsl((hsl[:h] + 30) % 360, hsl[:s], hsl[:l])
  ]
end

#blend(other, weight: 0.5) ⇒ Color

Blend this color with another color.

Parameters:

  • other (Color)

    the other color to blend with

  • weight (Float) (defaults to: 0.5)

    blend weight (0.0 = all self, 1.0 = all other, 0.5 = equal mix)

Returns:

  • (Color)

    the blended color



241
242
243
244
245
246
247
# File 'lib/philiprehberger/color_convert/color.rb', line 241

def blend(other, weight: 0.5)
  w = clamp(weight.to_f, 0.0, 1.0)
  new_r = (@r * (1.0 - w)) + (other.r * w)
  new_g = (@g * (1.0 - w)) + (other.g * w)
  new_b = (@b * (1.0 - w)) + (other.b * w)
  self.class.new(new_r.round, new_g.round, new_b.round)
end

#complementColor

Return the complementary color (180 degrees on the color wheel).

Returns:

  • (Color)

    the complement color



230
231
232
233
234
# File 'lib/philiprehberger/color_convert/color.rb', line 230

def complement
  hsl = to_hsl
  new_h = (hsl[:h] + 180) % 360
  self.class.from_hsl(new_h, hsl[:s], hsl[:l])
end

#contrast_ratio(other) ⇒ Float

Calculate the WCAG contrast ratio between this color and another.

Parameters:

  • other (Color)

    the other color

Returns:

  • (Float)

    the contrast ratio (1.0 to 21.0)



360
361
362
363
364
365
366
# File 'lib/philiprehberger/color_convert/color.rb', line 360

def contrast_ratio(other)
  l1 = relative_luminance
  l2 = other.relative_luminance
  lighter = [l1, l2].max
  darker = [l1, l2].min
  ((lighter + 0.05) / (darker + 0.05)).round(2)
end

#cool?Boolean

Returns true if the color temperature is cool.

Returns:

  • (Boolean)

    true if the color temperature is cool



399
400
401
# File 'lib/philiprehberger/color_convert/color.rb', line 399

def cool?
  temperature == :cool
end

#darken(amount) ⇒ Color

Darken the color by a percentage.

Parameters:

  • amount (Numeric)

    percentage to darken (0-100)

Returns:

  • (Color)

    a new darkened color



205
206
207
# File 'lib/philiprehberger/color_convert/color.rb', line 205

def darken(amount)
  lighten(-amount)
end

#desaturate(amount) ⇒ Color

Decrease saturation by a percentage.

Parameters:

  • amount (Numeric)

    percentage to decrease saturation (0-100)

Returns:

  • (Color)

    a new desaturated color



223
224
225
# File 'lib/philiprehberger/color_convert/color.rb', line 223

def desaturate(amount)
  saturate(-amount)
end

#gradient(other, steps: 5) ⇒ Array<Color>

Generate a gradient palette between this color and another.

Parameters:

  • other (Color)

    the target color

  • steps (Integer) (defaults to: 5)

    number of colors in the gradient (minimum 2)

Returns:

  • (Array<Color>)

    array of colors forming a gradient



334
335
336
337
338
339
340
# File 'lib/philiprehberger/color_convert/color.rb', line 334

def gradient(other, steps: 5)
  steps = [steps, 2].max
  (0...steps).map do |i|
    weight = i / (steps - 1).to_f
    blend(other, weight: weight)
  end
end

#lighten(amount) ⇒ Color

Lighten the color by a percentage.

Parameters:

  • amount (Numeric)

    percentage to lighten (0-100)

Returns:

  • (Color)

    a new lightened color



195
196
197
198
199
# File 'lib/philiprehberger/color_convert/color.rb', line 195

def lighten(amount)
  hsl = to_hsl
  new_l = clamp(hsl[:l] + amount, 0, 100)
  self.class.from_hsl(hsl[:h], hsl[:s], new_l)
end

#monochromatic(steps: 5) ⇒ Array<Color>

Generate a monochromatic palette by varying lightness.

Parameters:

  • steps (Integer) (defaults to: 5)

    number of shades to generate (minimum 2)

Returns:

  • (Array<Color>)

    array of colors from dark to light



346
347
348
349
350
351
352
353
354
# File 'lib/philiprehberger/color_convert/color.rb', line 346

def monochromatic(steps: 5)
  steps = [steps, 2].max
  hsl = to_hsl
  step_size = 100.0 / (steps + 1)

  (1..steps).map do |i|
    self.class.from_hsl(hsl[:h], hsl[:s], step_size * i)
  end
end

#opacityFloat

Return the opacity (alpha) value.

Returns:

  • (Float)

    alpha value (0.0-1.0)



54
55
56
# File 'lib/philiprehberger/color_convert/color.rb', line 54

def opacity
  @alpha
end

#opaque?Boolean

Returns true if the color is fully opaque (alpha == 1.0).

Returns:

  • (Boolean)

    true if the color is fully opaque (alpha == 1.0)



67
68
69
# File 'lib/philiprehberger/color_convert/color.rb', line 67

def opaque?
  (@alpha - 1.0).abs < Float::EPSILON
end

#relative_luminanceFloat

Calculate relative luminance per WCAG 2.0.

Returns:

  • (Float)

    luminance value (0.0 to 1.0)



371
372
373
374
375
376
# File 'lib/philiprehberger/color_convert/color.rb', line 371

def relative_luminance
  rs = linearize(@r / 255.0)
  gs = linearize(@g / 255.0)
  bs = linearize(@b / 255.0)
  (0.2126 * rs) + (0.7152 * gs) + (0.0722 * bs)
end

#saturate(amount) ⇒ Color

Increase saturation by a percentage.

Parameters:

  • amount (Numeric)

    percentage to increase saturation (0-100)

Returns:

  • (Color)

    a new saturated color



213
214
215
216
217
# File 'lib/philiprehberger/color_convert/color.rb', line 213

def saturate(amount)
  hsl = to_hsl
  new_s = clamp(hsl[:s] + amount, 0, 100)
  self.class.from_hsl(hsl[:h], new_s, hsl[:l])
end

#simulate_color_blindness(type) ⇒ Color

Simulate color blindness.

Parameters:

  • type (Symbol)

    one of :protanopia, :deuteranopia, :tritanopia

Returns:

  • (Color)

    the simulated color

Raises:

  • (ArgumentError)

    if type is not recognized



303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
# File 'lib/philiprehberger/color_convert/color.rb', line 303

def simulate_color_blindness(type)
  matrix = COLOR_BLINDNESS_MATRICES[type]
  raise ArgumentError, "Unknown color blindness type: #{type}" unless matrix

  rf = @r / 255.0
  gf = @g / 255.0
  bf = @b / 255.0

  # Convert to linear RGB
  rl = linearize_srgb(rf)
  gl = linearize_srgb(gf)
  bl = linearize_srgb(bf)

  # Apply simulation matrix
  new_r = (matrix[0][0] * rl) + (matrix[0][1] * gl) + (matrix[0][2] * bl)
  new_g = (matrix[1][0] * rl) + (matrix[1][1] * gl) + (matrix[1][2] * bl)
  new_b = (matrix[2][0] * rl) + (matrix[2][1] * gl) + (matrix[2][2] * bl)

  # Convert back to sRGB
  new_r = delinearize_srgb(clamp(new_r, 0.0, 1.0))
  new_g = delinearize_srgb(clamp(new_g, 0.0, 1.0))
  new_b = delinearize_srgb(clamp(new_b, 0.0, 1.0))

  self.class.new((new_r * 255).round, (new_g * 255).round, (new_b * 255).round)
end

#split_complementaryArray<Color>

Generate split-complementary colors (150 and 210 degrees from base).

Returns:

  • (Array<Color>)

    array of 3 colors



289
290
291
292
293
294
295
296
# File 'lib/philiprehberger/color_convert/color.rb', line 289

def split_complementary
  hsl = to_hsl
  [
    self.class.new(@r, @g, @b),
    self.class.from_hsl((hsl[:h] + 150) % 360, hsl[:s], hsl[:l]),
    self.class.from_hsl((hsl[:h] + 210) % 360, hsl[:s], hsl[:l])
  ]
end

#temperatureSymbol

Classify the color temperature based on HSL hue.

Returns:

  • (Symbol)

    :warm, :cool, or :neutral



381
382
383
384
385
386
387
388
389
390
391
# File 'lib/philiprehberger/color_convert/color.rb', line 381

def temperature
  hue = to_hsl[:h]

  if hue <= 60 || hue >= 300
    :warm
  elsif hue.between?(120, 240)
    :cool
  else
    :neutral
  end
end

#tetradicArray<Color>

Generate tetradic (rectangular) colors (90 degrees apart).

Returns:

  • (Array<Color>)

    array of 4 colors



276
277
278
279
280
281
282
283
284
# File 'lib/philiprehberger/color_convert/color.rb', line 276

def tetradic
  hsl = to_hsl
  [
    self.class.new(@r, @g, @b),
    self.class.from_hsl((hsl[:h] + 90) % 360, hsl[:s], hsl[:l]),
    self.class.from_hsl((hsl[:h] + 180) % 360, hsl[:s], hsl[:l]),
    self.class.from_hsl((hsl[:h] + 270) % 360, hsl[:s], hsl[:l])
  ]
end

#to_cmykHash

Convert to CMYK hash.

Returns:

  • (Hash)

    with :c, :m, :y, :k keys (0-100)



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/philiprehberger/color_convert/color.rb', line 138

def to_cmyk
  rf = @r / 255.0
  gf = @g / 255.0
  bf = @b / 255.0

  k = 1.0 - [rf, gf, bf].max

  if k >= 1.0
    return { c: 0.0, m: 0.0, y: 0.0, k: 100.0 }
  end

  c = (1.0 - rf - k) / (1.0 - k)
  m = (1.0 - gf - k) / (1.0 - k)
  y = (1.0 - bf - k) / (1.0 - k)

  { c: (c * 100).round(1), m: (m * 100).round(1), y: (y * 100).round(1), k: (k * 100).round(1) }
end

#to_hexString

Convert to hex string.

Returns:

  • (String)

    hex color string (e.g., “#ff0000”)



33
34
35
# File 'lib/philiprehberger/color_convert/color.rb', line 33

def to_hex
  format('#%<r>02x%<g>02x%<b>02x', r: @r, g: @g, b: @b)
end

#to_hslHash

Convert to HSL hash.

Returns:

  • (Hash)

    with :h (0-360), :s (0-100), :l (0-100) keys



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/philiprehberger/color_convert/color.rb', line 79

def to_hsl
  rf = @r / 255.0
  gf = @g / 255.0
  bf = @b / 255.0

  max = [rf, gf, bf].max
  min = [rf, gf, bf].min
  l = (max + min) / 2.0

  if max == min
    h = 0.0
    s = 0.0
  else
    d = max - min
    s = l > 0.5 ? d / (2.0 - max - min) : d / (max + min)
    h = case max
        when rf then ((gf - bf) / d) + (gf < bf ? 6 : 0)
        when gf then ((bf - rf) / d) + 2
        else ((rf - gf) / d) + 4
        end
    h /= 6.0
  end

  { h: (h * 360).round(1), s: (s * 100).round(1), l: (l * 100).round(1) }
end

#to_hsvHash

Convert to HSV hash.

Returns:

  • (Hash)

    with :h (0-360), :s (0-100), :v (0-100) keys



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/philiprehberger/color_convert/color.rb', line 108

def to_hsv
  rf = @r / 255.0
  gf = @g / 255.0
  bf = @b / 255.0

  max = [rf, gf, bf].max
  min = [rf, gf, bf].min
  d = max - min

  v = max

  s = max.zero? ? 0.0 : d / max

  if max == min
    h = 0.0
  else
    h = case max
        when rf then ((gf - bf) / d) + (gf < bf ? 6 : 0)
        when gf then ((bf - rf) / d) + 2
        else ((rf - gf) / d) + 4
        end
    h /= 6.0
  end

  { h: (h * 360).round(1), s: (s * 100).round(1), v: (v * 100).round(1) }
end

#to_labHash

Convert to CIELAB hash via XYZ (D65 illuminant).

Returns:

  • (Hash)

    with :l (0-100), :a (approx -128 to 127), :b (approx -128 to 127) keys



159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/philiprehberger/color_convert/color.rb', line 159

def to_lab
  xyz = to_xyz
  x = xyz[:x] / 95.047
  y = xyz[:y] / 100.0
  z = xyz[:z] / 108.883

  x = lab_f(x)
  y = lab_f(y)
  z = lab_f(z)

  l = (116.0 * y) - 16.0
  a = 500.0 * (x - y)
  b = 200.0 * (y - z)

  { l: l.round(2), a: a.round(2), b: b.round(2) }
end

#to_rgbHash

Convert to RGB hash.

Returns:

  • (Hash)

    with :r, :g, :b keys (0-255)



40
41
42
# File 'lib/philiprehberger/color_convert/color.rb', line 40

def to_rgb
  { r: @r, g: @g, b: @b }
end

#to_rgbaHash

Convert to RGBA hash.

Returns:

  • (Hash)

    with :r, :g, :b (0-255) and :a (0.0-1.0) keys



47
48
49
# File 'lib/philiprehberger/color_convert/color.rb', line 47

def to_rgba
  { r: @r, g: @g, b: @b, a: @alpha }
end

#to_sString

Returns:

  • (String)


404
405
406
407
408
# File 'lib/philiprehberger/color_convert/color.rb', line 404

def to_s
  return to_hex if opaque?

  format('rgba(%d, %d, %d, %s)', @r, @g, @b, @alpha)
end

#to_xyzHash

Convert to CIE XYZ color space (D65 illuminant).

Returns:

  • (Hash)

    with :x, :y, :z keys



179
180
181
182
183
184
185
186
187
188
189
# File 'lib/philiprehberger/color_convert/color.rb', line 179

def to_xyz
  rf = linearize_srgb(@r / 255.0) * 100.0
  gf = linearize_srgb(@g / 255.0) * 100.0
  bf = linearize_srgb(@b / 255.0) * 100.0

  x = (rf * 0.4124564) + (gf * 0.3575761) + (bf * 0.1804375)
  y = (rf * 0.2126729) + (gf * 0.7151522) + (bf * 0.0721750)
  z = (rf * 0.0193339) + (gf * 0.1191920) + (bf * 0.9503041)

  { x: x.round(4), y: y.round(4), z: z.round(4) }
end

#transparent?Boolean

Returns true if the color has any transparency (alpha < 1.0).

Returns:

  • (Boolean)

    true if the color has any transparency (alpha < 1.0)



72
73
74
# File 'lib/philiprehberger/color_convert/color.rb', line 72

def transparent?
  !opaque?
end

#triadicArray<Color>

Generate triadic colors (120 degrees apart on the color wheel).

Returns:

  • (Array<Color>)

    array of 3 colors



264
265
266
267
268
269
270
271
# File 'lib/philiprehberger/color_convert/color.rb', line 264

def triadic
  hsl = to_hsl
  [
    self.class.new(@r, @g, @b),
    self.class.from_hsl((hsl[:h] + 120) % 360, hsl[:s], hsl[:l]),
    self.class.from_hsl((hsl[:h] + 240) % 360, hsl[:s], hsl[:l])
  ]
end

#warm?Boolean

Returns true if the color temperature is warm.

Returns:

  • (Boolean)

    true if the color temperature is warm



394
395
396
# File 'lib/philiprehberger/color_convert/color.rb', line 394

def warm?
  temperature == :warm
end

#with_opacity(val) ⇒ Color

Return a new Color with the given opacity.

Parameters:

  • val (Float)

    new alpha value (0.0-1.0)

Returns:

  • (Color)

    a new Color with the updated alpha



62
63
64
# File 'lib/philiprehberger/color_convert/color.rb', line 62

def with_opacity(val)
  self.class.new(@r, @g, @b, alpha: val)
end