Class: Color::RGB

Inherits:
Object
  • Object
show all
Includes:
Color
Defined in:
lib/color/rgb.rb,
lib/color.rb,
lib/color/rgb/colors.rb,
lib/color/rgb/contrast.rb,
lib/color/rgb/metallic.rb

Overview

An RGB colour object.

Defined Under Namespace

Modules: Metallic

Constant Summary collapse

PDF_FORMAT_STR =

The format of a DeviceRGB colour for PDF. In color-tools 2.0 this will be removed from this package and added back as a modification by the PDF::Writer package.

"%.3f %.3f %.3f %s"

Constants included from Color

COLOR_EPSILON, COLOR_TOLERANCE, COLOR_VERSION, GreyScale

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Color

#==, coerce, const_missing, equivalent?, #name, #names, #names=, near?, near_one?, near_one_or_more?, near_zero?, near_zero_or_less?, new, normalize, normalize_byte, normalize_to_range, normalize_word

Constructor Details

#initialize(r = 0, g = 0, b = 0, radix = 255.0, &block) ⇒ RGB

Creates an RGB colour object from the standard range 0..255.

Color::RGB.new(32, 64, 128)
Color::RGB.new(0x20, 0x40, 0x80)

19
20
21
22
# File 'lib/color/rgb.rb', line 19

def initialize(r = 0, g = 0, b = 0, radix = 255.0, &block) # :yields self:
  @r, @g, @b = [ r, g, b ].map { |v| Color.normalize(v / radix) }
  block.call(self) if block
end

Class Method Details

.by_css(name_or_hex, &block) ⇒ Object

Return a colour as identified by the colour name, or by hex.


648
649
650
# File 'lib/color/rgb.rb', line 648

def by_css(name_or_hex, &block)
  by_name(name_or_hex) { by_hex(name_or_hex, &block) }
end

.by_hex(hex, &block) ⇒ Object

Find or create a colour by an HTML hex code. This differs from the #from_html method in that if the colour code matches a named colour, the existing colour will be returned.

Color::RGB.by_hex('ff0000').name # => 'red'
Color::RGB.by_hex('ff0001').name # => nil

If a block is provided, the value that is returned by the block will be returned instead of the exception caused by an error in providing a correct hex format.


632
633
634
635
636
637
638
639
640
# File 'lib/color/rgb.rb', line 632

def by_hex(hex, &block)
  __by_hex.fetch(html_hexify(hex)) { from_html(hex) }
rescue
  if block
    block.call
  else
    raise
  end
end

.by_name(name, &block) ⇒ Object

Return a colour as identified by the colour name.


643
644
645
# File 'lib/color/rgb.rb', line 643

def by_name(name, &block)
  __by_name.fetch(name.to_s.downcase, &block)
end

.extract_colors(text, mode = :both) ⇒ Object

Extract named or hex colours from the provided text.


653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
# File 'lib/color/rgb.rb', line 653

def extract_colors(text, mode = :both)
  text  = text.downcase
  regex = case mode
          when :name
            Regexp.union(__by_name.keys)
          when :hex
            Regexp.union(__by_hex.keys)
          when :both
            Regexp.union(__by_hex.keys + __by_name.keys)
          end

  text.scan(regex).map { |match|
    case mode
    when :name
      by_name(match)
    when :hex
      by_hex(match)
    when :both
      by_css(match)
    end
  }
end

.from_fraction(r = 0.0, g = 0.0, b = 0.0, &block) ⇒ Object

Creates an RGB colour object from fractional values 0..1.

Color::RGB.from_fraction(.3, .2, .1)

592
593
594
# File 'lib/color/rgb.rb', line 592

def from_fraction(r = 0.0, g = 0.0, b = 0.0, &block)
  new(r, g, b, 1.0, &block)
end

.from_grayscale_fraction(l = 0.0, &block) ⇒ Object Also known as: from_greyscale_fraction

Creates an RGB colour object from a grayscale fractional value 0..1.


597
598
599
# File 'lib/color/rgb.rb', line 597

def from_grayscale_fraction(l = 0.0, &block)
  new(l, l, l, 1.0, &block)
end

.from_html(html_colour, &block) ⇒ Object

Creates an RGB colour object from an HTML colour descriptor (e.g., "fed" or "#cabbed;".

Color::RGB.from_html("fed")
Color::RGB.from_html("#fed")
Color::RGB.from_html("#cabbed")
Color::RGB.from_html("cabbed")

609
610
611
612
613
614
615
616
617
618
619
620
# File 'lib/color/rgb.rb', line 609

def from_html(html_colour, &block)
  # When we can move to 1.9+ only, this will be \h
  h = html_colour.scan(/[0-9a-f]/i)
  case h.size
  when 3
    new(*h.map { |v| (v * 2).to_i(16) }, &block)
  when 6
    new(*h.each_slice(2).map { |v| v.join.to_i(16) }, &block)
  else
    raise ArgumentError, "Not a supported HTML colour type."
  end
end

.from_percentage(r = 0, g = 0, b = 0, &block) ⇒ Object

Creates an RGB colour object from percentages 0..100.

Color::RGB.from_percentage(10, 20, 30)

585
586
587
# File 'lib/color/rgb.rb', line 585

def from_percentage(r = 0, g = 0, b = 0, &block)
  new(r, g, b, 100.0, &block)
end

Instance Method Details

#+(other) ⇒ Object

Adds another colour to the current colour. The other colour will be converted to RGB before addition. This conversion depends upon a #to_rgb method on the other colour.

The addition is done using the RGB Accessor methods to ensure a valid colour in the result.


532
533
534
# File 'lib/color/rgb.rb', line 532

def +(other)
  self.class.from_fraction(r + other.r, g + other.g, b + other.b)
end

#-(other) ⇒ Object

Subtracts another colour to the current colour. The other colour will be converted to RGB before subtraction. This conversion depends upon a #to_rgb method on the other colour.

The subtraction is done using the RGB Accessor methods to ensure a valid colour in the result.


542
543
544
# File 'lib/color/rgb.rb', line 542

def -(other)
  self + (-other)
end

#[email protected]Object

Numerically negate the color. This results in a color that is only usable for subtraction.


563
564
565
566
567
568
569
# File 'lib/color/rgb.rb', line 563

def [email protected]
  rgb = self.dup
  rgb.instance_variable_set(:@r, -rgb.r)
  rgb.instance_variable_set(:@g, -rgb.g)
  rgb.instance_variable_set(:@b, -rgb.b)
  rgb
end

#adjust_brightness(percent) ⇒ Object

Returns a new colour with the brightness adjusted by the specified percentage. Negative percentages will darken the colour; positive percentages will brighten the colour.

Color::RGB::DarkBlue.adjust_brightness(10)
Color::RGB::DarkBlue.adjust_brightness(-10)

301
302
303
304
305
306
# File 'lib/color/rgb.rb', line 301

def adjust_brightness(percent)
  percent = normalize_percent(percent)
  hsl      = to_hsl
  hsl.l   *= percent
  hsl.to_rgb
end

#adjust_hue(percent) ⇒ Object

Returns a new colour with the hue adjusted by the specified percentage. Negative percentages will reduce the hue; positive percentages will increase the hue.

Color::RGB::DarkBlue.adjust_hue(10)
Color::RGB::DarkBlue.adjust_hue(-10)

327
328
329
330
331
332
# File 'lib/color/rgb.rb', line 327

def adjust_hue(percent)
  percent = normalize_percent(percent)
  hsl      = to_hsl
  hsl.h   *= percent
  hsl.to_rgb
end

#adjust_saturation(percent) ⇒ Object

Returns a new colour with the saturation adjusted by the specified percentage. Negative percentages will reduce the saturation; positive percentages will increase the saturation.

Color::RGB::DarkBlue.adjust_saturation(10)
Color::RGB::DarkBlue.adjust_saturation(-10)

314
315
316
317
318
319
# File 'lib/color/rgb.rb', line 314

def adjust_saturation(percent)
  percent = normalize_percent(percent)
  hsl      = to_hsl
  hsl.s   *= percent
  hsl.to_rgb
end

#bObject

Returns the blue component of the colour as a fraction in the range 0.0 .. 1.0.


509
510
511
# File 'lib/color/rgb.rb', line 509

def b
  @b
end

#b=(bb) ⇒ Object

Sets the blue component of the colour as a fraction in the range 0.0 .. 1.0.


522
523
524
# File 'lib/color/rgb.rb', line 522

def b=(bb)
  @b = Color.normalize(bb)
end

#blueObject

Returns the blue component of the colour in the normal 0 .. 255 range.


500
501
502
# File 'lib/color/rgb.rb', line 500

def blue
  @b * 255.0
end

#blue=(bb) ⇒ Object

Sets the blue component of the colour in the normal 0 .. 255 range.


513
514
515
# File 'lib/color/rgb.rb', line 513

def blue=(bb)
  @b = Color.normalize(bb / 255.0)
end

#blue_pObject

Returns the blue component of the colour as a percentage.


504
505
506
# File 'lib/color/rgb.rb', line 504

def blue_p
  @b * 100.0
end

#blue_p=(bb) ⇒ Object

Sets the blue component of the colour as a percentage.


517
518
519
# File 'lib/color/rgb.rb', line 517

def blue_p=(bb)
  @b = Color.normalize(bb / 100.0)
end

#brightnessObject

Returns the brightness value for a colour, a number between 0..1. Based on the Y value of YIQ encoding, representing luminosity, or perceived brightness.

This may be modified in a future version of color-tools to use the luminosity value of HSL.


286
287
288
# File 'lib/color/rgb.rb', line 286

def brightness
  to_yiq.y
end

#closest_match(color_list, threshold_distance = 1000.0) ⇒ Object

Calculates and returns the closest match to this colour from a list of provided colours. Returns nil if color_list is empty or if there is no colour within the threshold_distance.

threshold_distance is used to determine the minimum colour distance permitted. Uses the CIE Delta E 1994 algorithm (CIE94) to find near matches based on perceived visual colour. The default value (1000.0) is an arbitrarily large number. The values :jnd and :just_noticeable may be passed as the threshold_distance to use the value 2.3.


347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
# File 'lib/color/rgb.rb', line 347

def closest_match(color_list, threshold_distance = 1000.0)
  color_list = [ color_list ].flatten(1)
  return nil if color_list.empty?

  threshold_distance = case threshold_distance
                       when :jnd, :just_noticeable
                         2.3
                       else
                         threshold_distance.to_f
                       end
  lab = to_lab
  closest_distance = threshold_distance
  best_match = nil

  color_list.each do |c|
    distance = delta_e94(lab, c.to_lab)
    if (distance < closest_distance)
      closest_distance = distance
      best_match = c
    end
  end
  best_match
end

#coerce(other) ⇒ Object

Coerces the other Color object into RGB.


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

def coerce(other)
  other.to_rgb
end

#contrast(other) ⇒ Object

Outputs how much contrast this color has with another RGB color. Computes the same regardless of which one is considered foreground. If the other color does not have a #to_rgb method, this will throw an exception. Anything over about 0.22 should have a high likelihood of being legible, but the larger the difference, the more contrast. Otherwise, to be safe go with something > 0.3


10
11
12
13
14
15
16
17
# File 'lib/color/rgb/contrast.rb', line 10

def contrast(other)
  other = coerce(other)

  # The following numbers have been set with some care.
  ((diff_brightness(other) * 0.65) +
   (diff_hue(other)        * 0.20) +
   (diff_luminosity(other) * 0.15))
end

#css_hslObject

Present the colour as an HSL HTML/CSS colour string (e.g., “hsl(180, 25%, 35%)”). Note that this will perform a #to_hsl operation using the default conversion formula.


77
78
79
# File 'lib/color/rgb.rb', line 77

def css_hsl
  to_hsl.css_hsl
end

#css_hslaObject

Present the colour as an HSLA (with alpha) HTML/CSS colour string (e.g., “hsla(180, 25%, 35%, 1)”). Note that this will perform a #to_hsl operation using the default conversion formula.


84
85
86
# File 'lib/color/rgb.rb', line 84

def css_hsla
  to_hsl.css_hsla
end

#css_rgbObject

Present the colour as an RGB HTML/CSS colour string (e.g., “rgb(0%, 50%, 100%)”). Note that this will perform a #to_rgb operation using the default conversion formula.


58
59
60
# File 'lib/color/rgb.rb', line 58

def css_rgb
  "rgb(%3.2f%%, %3.2f%%, %3.2f%%)" % [ red_p, green_p, blue_p ]
end

#css_rgba(alpha = 1) ⇒ Object

Present the colour as an RGBA (with an optional alpha that defaults to 1) HTML/CSS colour string (e.g.,“rgb(0%, 50%, 100%, 1)”). Note that this will perform a #to_rgb operation using the default conversion formula.

Color::RGB.by_hex('ff0000').css_rgba
=> 'rgba(100.00%, 0.00%, 0.00%, 1.00)'
Color::RGB.by_hex('ff0000').css_rgba(0.2)
=> 'rgba(100.00%, 0.00%, 0.00%, 0.20)'

70
71
72
# File 'lib/color/rgb.rb', line 70

def css_rgba(alpha = 1)
  "rgba(%3.2f%%, %3.2f%%, %3.2f%%, %3.2f)" % [ red_p, green_p, blue_p, alpha ]
end

#darken_by(percent) ⇒ Object

Mix the RGB hue with Black so that the RGB hue is the specified percentage of the resulting colour. Strictly speaking, this isn't a darken_by operation.


263
264
265
# File 'lib/color/rgb.rb', line 263

def darken_by(percent)
  mix_with(Black, percent)
end

#delta_e94(color_1, color_2, weighting_type = :graphic_arts) ⇒ Object

The Delta E (CIE94) algorithm en.wikipedia.org/wiki/Color_difference#CIE94

There is a newer version, CIEDE2000, that uses slightly more complicated math, but addresses “the perceptual uniformity issue” left lingering by the CIE94 algorithm. color_1 and color_2 are both L*a*b* hashes, rendered by #to_lab.

Since our source is treated as sRGB, we use the “graphic arts” presets for k_L, k_1, and k_2

The calculations go through LCH(ab). (?)

See also www.brucelindbloom.com/index.html?Eqn_DeltaE_CIE94.html

NOTE: This should be moved to Color::Lab.


387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
# File 'lib/color/rgb.rb', line 387

def delta_e94(color_1, color_2, weighting_type = :graphic_arts)
  case weighting_type
  when :graphic_arts
    k_1 = 0.045
    k_2 = 0.015
    k_L = 1
  when :textiles
    k_1 = 0.048
    k_2 = 0.014
    k_L = 2
  else
    raise ArgumentError, "Unsupported weighting type #{weighting_type}."
  end

  # delta_E = Math.sqrt(
  #   ((delta_L / (k_L * s_L)) ** 2) +
  #   ((delta_C / (k_C * s_C)) ** 2) +
  #   ((delta_H / (k_H * s_H)) ** 2)
  # )
  #
  # Under some circumstances in real computers, delta_H could be an
  # imaginary number (it's a square root value), so we're going to treat
  # this as:
  #
  # delta_E = Math.sqrt(
  #   ((delta_L / (k_L * s_L)) ** 2) +
  #   ((delta_C / (k_C * s_C)) ** 2) +
  #   (delta_H2 / ((k_H * s_H) ** 2)))
  # )
  #
  # And not perform the square root when calculating delta_H2.

  k_C = k_H = 1

  l_1, a_1, b_1 = color_1.values_at(:L, :a, :b)
  l_2, a_2, b_2 = color_2.values_at(:L, :a, :b)

  delta_a = a_1 - a_2
  delta_b = b_1 - b_2

  c_1 = Math.sqrt((a_1 ** 2) + (b_1 ** 2))
  c_2 = Math.sqrt((a_2 ** 2) + (b_2 ** 2))

  delta_L = color_1[:L] - color_2[:L]
  delta_C = c_1 - c_2

  delta_H2 = (delta_a ** 2) + (delta_b ** 2) - (delta_C ** 2)

  s_L = 1
  s_C = 1 + k_1 * c_1
  s_H = 1 + k_2 * c_1

  composite_L = (delta_L / (k_L * s_L)) ** 2
  composite_C = (delta_C / (k_C * s_C)) ** 2
  composite_H = delta_H2 / ((k_H * s_H) ** 2)
  Math.sqrt(composite_L + composite_C + composite_H)
end

#gObject

Returns the green component of the colour as a fraction in the range 0.0 .. 1.0.


482
483
484
# File 'lib/color/rgb.rb', line 482

def g
  @g
end

#g=(gg) ⇒ Object

Sets the green component of the colour as a fraction in the range 0.0 .. 1.0.


495
496
497
# File 'lib/color/rgb.rb', line 495

def g=(gg)
  @g = Color.normalize(gg)
end

#greenObject

Returns the green component of the colour in the normal 0 .. 255 range.


473
474
475
# File 'lib/color/rgb.rb', line 473

def green
  @g * 255.0
end

#green=(gg) ⇒ Object

Sets the green component of the colour in the normal 0 .. 255 range.


486
487
488
# File 'lib/color/rgb.rb', line 486

def green=(gg)
  @g = Color.normalize(gg / 255.0)
end

#green_pObject

Returns the green component of the colour as a percentage.


477
478
479
# File 'lib/color/rgb.rb', line 477

def green_p
  @g * 100.0
end

#green_p=(gg) ⇒ Object

Sets the green component of the colour as a percentage.


490
491
492
# File 'lib/color/rgb.rb', line 490

def green_p=(gg)
  @g = Color.normalize(gg / 100.0)
end

#hexObject

Present the colour as an RGB hex triplet.


37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/color/rgb.rb', line 37

def hex
  r = (@r * 255).round
  r = 255 if r > 255

  g = (@g * 255).round
  g = 255 if g > 255

  b = (@b * 255).round
  b = 255 if b > 255

  "%02x%02x%02x" % [ r, g, b ]
end

#htmlObject

Present the colour as an HTML/CSS colour string.


51
52
53
# File 'lib/color/rgb.rb', line 51

def html
  "##{hex}"
end

#inspectObject


553
554
555
# File 'lib/color/rgb.rb', line 553

def inspect
  "RGB [#{html}]"
end

#lighten_by(percent) ⇒ Object

Mix the RGB hue with White so that the RGB hue is the specified percentage of the resulting colour. Strictly speaking, this isn't a darken_by operation.


256
257
258
# File 'lib/color/rgb.rb', line 256

def lighten_by(percent)
  mix_with(White, percent)
end

#max_rgb_as_grayscaleObject Also known as: max_rgb_as_greyscale

Retrieve the maxmum RGB value from the current colour as a GrayScale colour


548
549
550
# File 'lib/color/rgb.rb', line 548

def max_rgb_as_grayscale
  Color::GrayScale.from_fraction([@r, @g, @b].max)
end

#mix_with(mask, opacity) ⇒ Object

Mix the mask colour (which must be an RGB object) with the current colour at the stated opacity percentage (0..100).


269
270
271
272
273
274
275
276
277
278
# File 'lib/color/rgb.rb', line 269

def mix_with(mask, opacity)
  opacity /= 100.0
  rgb = self.dup

  rgb.r = (@r * opacity) + (mask.r * (1 - opacity))
  rgb.g = (@g * opacity) + (mask.g * (1 - opacity))
  rgb.b = (@b * opacity) + (mask.b * (1 - opacity))

  rgb
end

#pdf_fillObject

Present the colour as a DeviceRGB fill colour string for PDF. This will be removed from the default package in color-tools 2.0.


26
27
28
# File 'lib/color/rgb.rb', line 26

def pdf_fill
  PDF_FORMAT_STR % [ @r, @g, @b, "rg" ]
end

#pdf_strokeObject

Present the colour as a DeviceRGB stroke colour string for PDF. This will be removed from the default package in color-tools 2.0.


32
33
34
# File 'lib/color/rgb.rb', line 32

def pdf_stroke
  PDF_FORMAT_STR % [ @r, @g, @b, "RG" ]
end

#rObject

Returns the red component of the colour as a fraction in the range 0.0 .. 1.0.


455
456
457
# File 'lib/color/rgb.rb', line 455

def r
  @r
end

#r=(rr) ⇒ Object

Sets the red component of the colour as a fraction in the range 0.0 .. 1.0.


468
469
470
# File 'lib/color/rgb.rb', line 468

def r=(rr)
  @r = Color.normalize(rr)
end

#redObject

Returns the red component of the colour in the normal 0 .. 255 range.


446
447
448
# File 'lib/color/rgb.rb', line 446

def red
  @r * 255.0
end

#red=(rr) ⇒ Object

Sets the red component of the colour in the normal 0 .. 255 range.


459
460
461
# File 'lib/color/rgb.rb', line 459

def red=(rr)
  @r = Color.normalize(rr / 255.0)
end

#red_pObject

Returns the red component of the colour as a percentage.


450
451
452
# File 'lib/color/rgb.rb', line 450

def red_p
  @r * 100.0
end

#red_p=(rr) ⇒ Object

Sets the red component of the colour as a percentage.


463
464
465
# File 'lib/color/rgb.rb', line 463

def red_p=(rr)
  @r = Color.normalize(rr / 100.0)
end

#to_aObject


557
558
559
# File 'lib/color/rgb.rb', line 557

def to_a
  [ r, g, b ]
end

#to_cmykObject

Converts the RGB colour to CMYK. Most colour experts strongly suggest that this is not a good idea (some even suggesting that it's a very bad idea). CMYK represents additive percentages of inks on white paper, whereas RGB represents mixed colour intensities on a black screen.

However, the colour conversion can be done. The basic method is multi-step:

  1. Convert the R, G, and B components to C, M, and Y components.

    c = 1.0 - r
    m = 1.0 - g
    y = 1.0 - b
    
  2. Compute the minimum amount of black (K) required to smooth the colour in inks.

    k = min(c, m, y)
    
  3. Perform undercolour removal on the C, M, and Y components of the colours because less of each colour is needed for each bit of black. Also, regenerate the black (K) based on the undercolour removal so that the colour is more accurately represented in ink.

    c = min(1.0, max(0.0, c - UCR(k)))
    m = min(1.0, max(0.0, m - UCR(k)))
    y = min(1.0, max(0.0, y - UCR(k)))
    k = min(1.0, max(0.0, BG(k)))
    

The undercolour removal function and the black generation functions return a value based on the brightness of the RGB colour.


114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/color/rgb.rb', line 114

def to_cmyk
  c = 1.0 - @r.to_f
  m = 1.0 - @g.to_f
  y = 1.0 - @b.to_f

  k = [c, m, y].min
  k = k - (k * brightness)

  c = [1.0, [0.0, c - k].max].min
  m = [1.0, [0.0, m - k].max].min
  y = [1.0, [0.0, y - k].max].min
  k = [1.0, [0.0, k].max].min

  Color::CMYK.from_fraction(c, m, y, k)
end

#to_grayscaleObject Also known as: to_greyscale

Convert to grayscale.


290
291
292
# File 'lib/color/rgb.rb', line 290

def to_grayscale
  Color::GrayScale.from_fraction(to_hsl.l)
end

#to_hslObject

Returns the HSL colour encoding of the RGB value. The conversions here are based on forumlas from www.easyrgb.com/math.php and elsewhere.


145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/color/rgb.rb', line 145

def to_hsl
  min   = [ @r, @g, @b ].min
  max   = [ @r, @g, @b ].max
  delta = (max - min).to_f

  lum   = (max + min) / 2.0

  if Color.near_zero?(delta) # close to 0.0, so it's a grey
    hue = 0
    sat = 0
  else
    if Color.near_zero_or_less?(lum - 0.5)
      sat = delta / (max + min).to_f
    else
      sat = delta / (2 - max - min).to_f
    end

    # This is based on the conversion algorithm from
    # http://en.wikipedia.org/wiki/HSV_color_space#Conversion_from_RGB_to_HSL_or_HSV
    # Contributed by Adam Johnson
    sixth = 1 / 6.0
    if @r == max # Color.near_zero_or_less?(@r - max)
      hue = (sixth * ((@g - @b) / delta))
      hue += 1.0 if @g < @b
    elsif @g == max # Color.near_zero_or_less(@g - max)
      hue = (sixth * ((@b - @r) / delta)) + (1.0 / 3.0)
    elsif @b == max # Color.near_zero_or_less?(@b - max)
      hue = (sixth * ((@r - @g) / delta)) + (2.0 / 3.0)
    end

    hue += 1 if hue < 0
    hue -= 1 if hue > 1
  end
  Color::HSL.from_fraction(hue, sat, lum)
end

#to_lab(color_space = :sRGB, reference_white = [ 95.047, 100.00, 108.883 ]) ⇒ Object

Returns the L*a*b* colour encoding of the value via the XYZ colour encoding. Based on the XYZ to Lab formula presented by Bruce Lindbloom.

Currently only the sRGB colour space is supported and defaults to using a D65 reference white.


217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
# File 'lib/color/rgb.rb', line 217

def to_lab(color_space = :sRGB, reference_white = [ 95.047, 100.00, 108.883 ])
  xyz = to_xyz

  # Calculate the ratio of the XYZ values to the reference white.
  # http://www.brucelindbloom.com/index.html?Equations.html
  xr = xyz[:x] / reference_white[0]
  yr = xyz[:y] / reference_white[1]
  zr = xyz[:z] / reference_white[2]

  # NOTE: This should be using Rational instead of floating point values,
  # otherwise there will be discontinuities.
  # http://www.brucelindbloom.com/LContinuity.html
  epsilon = (216 / 24389.0)
  kappa   = (24389 / 27.0)

  # And now transform
  # http://en.wikipedia.org/wiki/Lab_color_space#Forward_transformation
  # There is a brief explanation there as far as the nature of the calculations,
  # as well as a much nicer looking modeling of the algebra.
  fx, fy, fz = [ xr, yr, zr ].map { |t|
    if (t > (epsilon))
      t ** (1.0 / 3)
    else # t <= epsilon
      ((kappa * t) + 16) / 116.0
      # The 4/29 here is for when t = 0 (black). 4/29 * 116 = 16, and 16 -
      # 16 = 0, which is the correct value for L* with black.
#       ((1.0/3)*((29.0/6)**2) * t) + (4.0/29)
    end
  }
  {
    :L => ((116 * fy) - 16),
    :a => (500 * (fx - fy)),
    :b => (200 * (fy - fz))
  }
end

#to_rgb(ignored = nil) ⇒ Object


130
131
132
# File 'lib/color/rgb.rb', line 130

def to_rgb(ignored = nil)
  self
end

#to_xyz(color_space = :sRGB) ⇒ Object

Returns the XYZ colour encoding of the value. Based on the RGB to XYZ formula presented by Bruce Lindbloom.

Currently only the sRGB colour space is supported.


186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'lib/color/rgb.rb', line 186

def to_xyz(color_space = :sRGB)
  unless color_space.to_s.downcase == 'srgb'
    raise ArgumentError, "Unsupported colour space #{color_space}."
  end

  # Inverse sRGB companding. Linearizes RGB channels with respect to
  # energy.
  r, g, b = [ @r, @g, @b ].map { |v|
    if (v > 0.04045)
      (((v + 0.055) / 1.055) ** 2.4) * 100
    else
      (v / 12.92) * 100
    end
  }

  # Convert using the RGB/XYZ matrix at:
  # http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html#WSMatrices
  {
    :x => (r * 0.4124564 + g * 0.3575761 + b * 0.1804375),
    :y => (r * 0.2126729 + g * 0.7151522 + b * 0.0721750),
    :z => (r * 0.0193339 + g * 0.1191920 + b * 0.9503041)
  }
end

#to_yiqObject

Returns the YIQ (NTSC) colour encoding of the RGB value.


135
136
137
138
139
140
# File 'lib/color/rgb.rb', line 135

def to_yiq
  y = (@r * 0.299) + (@g *  0.587) + (@b *  0.114)
  i = (@r * 0.596) + (@g * -0.275) + (@b * -0.321)
  q = (@r * 0.212) + (@g * -0.523) + (@b *  0.311)
  Color::YIQ.from_fraction(y, i, q)
end