class Color::RGB
An RGB
colour object.
Constants
- 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.
Public Class Methods
Source
# 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
Return a colour as identified by the colour name, or by hex.
Source
# 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
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.
Source
# File lib/color/rgb.rb, line 643 def by_name(name, &block) __by_name.fetch(name.to_s.downcase, &block) end
Return a colour as identified by the colour name.
Source
# 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
Extract named or hex colours from the provided text.
Source
# 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
Creates an RGB
colour object from fractional values 0..1.
Color::RGB.from_fraction(.3, .2, .1)
Source
# File lib/color/rgb.rb, line 597 def from_grayscale_fraction(l = 0.0, &block) new(l, l, l, 1.0, &block) end
Creates an RGB
colour object from a grayscale fractional value 0..1.
Source
# 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
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")
Source
# 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
Creates an RGB
colour object from percentages 0..100.
Color::RGB.from_percentage(10, 20, 30)
Source
# 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
Creates an RGB
colour object from the standard range 0..255.
Color::RGB.new(32, 64, 128) Color::RGB.new(0x20, 0x40, 0x80)
Private Class Methods
Source
# File lib/color/rgb.rb, line 680 def __named_color(mod, rgb, *names) used = names - mod.constants.map(&:to_sym) if used.length < names.length raise ArgumentError, "#{names.join(', ')} already defined in #{mod}" end names.each { |n| mod.const_set(n, rgb) } rgb.names = names rgb.names.each { |n| __by_name[n] = rgb } __by_hex[rgb.hex] = rgb rgb.freeze end
Source
# File lib/color/rgb.rb, line 702 def html_hexify(hex) # When we can move to 1.9+ only, this will be \h h = hex.to_s.downcase.scan(/[0-9a-f]/) case h.size when 3 h.map { |v| (v * 2) }.join when 6 h.join else raise ArgumentError, "Not a supported HTML colour type." end end
Source
# File lib/color/rgb/metallic.rb, line 9 def metallic(rgb, *names) __named_color(Metallic, new(*rgb), *names) end
Source
# File lib/color/rgb/colors.rb, line 4 def named(rgb, *names) __named_color(self, new(*rgb), *names) end
Public Instance Methods
Source
# File lib/color/rgb.rb, line 532 def +(other) self.class.from_fraction(r + other.r, g + other.g, b + other.b) end
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.
Source
# File lib/color/rgb.rb, line 542 def -(other) self + (-other) end
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.
Source
# File lib/color/rgb.rb, line 563 def -@ 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
Numerically negate the color. This results in a color that is only usable for subtraction.
Source
# 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
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)
Source
# 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
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)
Source
# 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
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)
Source
# File lib/color/rgb.rb, line 509 def b @b end
Returns the blue component of the colour as a fraction in the range 0.0 .. 1.0.
Source
# File lib/color/rgb.rb, line 522 def b=(bb) @b = Color.normalize(bb) end
Sets the blue component of the colour as a fraction in the range 0.0 .. 1.0.
Source
# File lib/color/rgb.rb, line 500 def blue @b * 255.0 end
Returns the blue component of the colour in the normal 0 .. 255 range.
Source
# File lib/color/rgb.rb, line 513 def blue=(bb) @b = Color.normalize(bb / 255.0) end
Sets the blue component of the colour in the normal 0 .. 255 range.
Source
# File lib/color/rgb.rb, line 504 def blue_p @b * 100.0 end
Returns the blue component of the colour as a percentage.
Source
# File lib/color/rgb.rb, line 517 def blue_p=(bb) @b = Color.normalize(bb / 100.0) end
Sets the blue component of the colour as a percentage.
Source
# File lib/color/rgb.rb, line 286 def brightness to_yiq.y end
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.
Source
# 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
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
.
Source
Source
# File lib/color/rgb/contrast.rb, line 9 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
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
Source
# File lib/color/rgb.rb, line 77 def css_hsl to_hsl.css_hsl end
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.
Source
# File lib/color/rgb.rb, line 84 def css_hsla to_hsl.css_hsla end
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.
Source
Source
# 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
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)'
Source
Source
# 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
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.
Source
# File lib/color/rgb.rb, line 482 def g @g end
Returns the green component of the colour as a fraction in the range 0.0 .. 1.0.
Source
# File lib/color/rgb.rb, line 495 def g=(gg) @g = Color.normalize(gg) end
Sets the green component of the colour as a fraction in the range 0.0 .. 1.0.
Source
# File lib/color/rgb.rb, line 473 def green @g * 255.0 end
Returns the green component of the colour in the normal 0 .. 255 range.
Source
# File lib/color/rgb.rb, line 486 def green=(gg) @g = Color.normalize(gg / 255.0) end
Sets the green component of the colour in the normal 0 .. 255 range.
Source
# File lib/color/rgb.rb, line 477 def green_p @g * 100.0 end
Returns the green component of the colour as a percentage.
Source
# File lib/color/rgb.rb, line 490 def green_p=(gg) @g = Color.normalize(gg / 100.0) end
Sets the green component of the colour as a percentage.
Source
# 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
Present the colour as an RGB
hex triplet.
Source
# File lib/color/rgb.rb, line 51 def html "##{hex}" end
Present the colour as an HTML/CSS colour string.
Source
Source
# File lib/color/rgb.rb, line 548 def max_rgb_as_grayscale Color::GrayScale.from_fraction([@r, @g, @b].max) end
Retrieve the maxmum RGB
value from the current colour as a GrayScale colour
Source
# 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
Mix the mask colour (which must be an RGB
object) with the current colour at the stated opacity percentage (0..100).
Source
# File lib/color/rgb.rb, line 26 def pdf_fill PDF_FORMAT_STR % [ @r, @g, @b, "rg" ] end
Present the colour as a DeviceRGB fill colour string for PDF. This will be removed from the default package in color-tools 2.0.
Source
# File lib/color/rgb.rb, line 32 def pdf_stroke PDF_FORMAT_STR % [ @r, @g, @b, "RG" ] end
Present the colour as a DeviceRGB stroke colour string for PDF. This will be removed from the default package in color-tools 2.0.
Source
# File lib/color/rgb.rb, line 455 def r @r end
Returns the red component of the colour as a fraction in the range 0.0 .. 1.0.
Source
# File lib/color/rgb.rb, line 468 def r=(rr) @r = Color.normalize(rr) end
Sets the red component of the colour as a fraction in the range 0.0 .. 1.0.
Source
# File lib/color/rgb.rb, line 446 def red @r * 255.0 end
Returns the red component of the colour in the normal 0 .. 255 range.
Source
# File lib/color/rgb.rb, line 459 def red=(rr) @r = Color.normalize(rr / 255.0) end
Sets the red component of the colour in the normal 0 .. 255 range.
Source
# File lib/color/rgb.rb, line 450 def red_p @r * 100.0 end
Returns the red component of the colour as a percentage.
Source
# File lib/color/rgb.rb, line 463 def red_p=(rr) @r = Color.normalize(rr / 100.0) end
Sets the red component of the colour as a percentage.
Source
# 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
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:
-
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
-
Compute the minimum amount of black (K) required to smooth the colour in inks.
k = min(c, m, y)
-
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.
Source
# File lib/color/rgb.rb, line 290 def to_grayscale Color::GrayScale.from_fraction(to_hsl.l) end
Convert to grayscale.
Source
# 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
Returns the HSL colour encoding of the RGB
value. The conversions here are based on forumlas from www.easyrgb.com/math.php and elsewhere.
Source
# 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
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.
Source
# 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
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.
Source
# 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
Returns the YIQ (NTSC) colour encoding of the RGB
value.
Private Instance Methods
Source
# File lib/color/rgb/contrast.rb, line 34 def diff_brightness(other) other = other.to_rgb br1 = (299 * other.r + 587 * other.g + 114 * other.b) br2 = (299 * self.r + 587 * self.g + 114 * self.b) (br1 - br2).abs / 1000.0; end
Provides the brightness difference.
Source
# File lib/color/rgb/contrast.rb, line 42 def diff_euclidean(other) other = other.to_rgb ((((other.r - self.r) ** 2) + ((other.g - self.g) ** 2) + ((other.b - self.b) ** 2)) ** 0.5) / 1.7320508075688772 end
Provides the euclidean distance between the two color values
Source
# File lib/color/rgb/contrast.rb, line 50 def diff_hue(other) other = other.to_rgb ((self.r - other.r).abs + (self.g - other.g).abs + (self.b - other.b).abs) / 3 end
Difference in the two colors’ hue
Source
# File lib/color/rgb/contrast.rb, line 20 def diff_luminosity(other) other = coerce(other) l1 = (0.2126 * (other.r) ** 2.2) + (0.7152 * (other.b) ** 2.2) + (0.0722 * (other.g) ** 2.2); l2 = (0.2126 * (self.r) ** 2.2) + (0.7152 * (self.b) ** 2.2) + (0.0722 * (self.g) ** 2.2); ((([l1, l2].max) + 0.05) / ( ([l1, l2].min) + 0.05 ) - 1) / 20.0 end
Provides the luminosity difference between two rbg vals
Source
# File lib/color/rgb.rb, line 572 def normalize_percent(percent) percent /= 100.0 percent += 1.0 percent = [ percent, 2.0 ].min percent = [ 0.0, percent ].max percent end