class RGB
Constants
- VERSION
redgreenblue version.
Public Class Methods
Creates a new RGB
object from a 24-bit integer in the range 0..16777215.
# File lib/redgreenblue/int.rb, line 9 def self.at(number) n = number.to_i if (0..16777215) === n rgb( ( n & 0xff0000 ) >> 16, ( n & 0x00ff00 ) >> 8, ( n & 0x0000ff ) ) else raise ArgumentError, "Argument '#{number}' not in range 0..16777215" end end
Returns the base inspect style, dependent on the COLORTERM environment variable.
# File lib/redgreenblue/inspect.rb, line 44 def self.base_style if styles.include? ENV['REDGREENBLUE_STYLE'] ENV['REDGREENBLUE_STYLE'] else if ENV['COLORTERM'] == 'truecolor' 'simple' else 'default' end end end
Creates a new RGB
object from BGR24 data (a 3-byte string).
# File lib/redgreenblue/bgr24bit.rb, line 14 def self.bgr24(bgr) c = self.new c.bgr24 = bgr c end
Creates a black RGB
object.
# File lib/redgreenblue/lazy.rb, line 15 def black new(0,0,0) end
Creates a pure blue RGB
object.
# File lib/redgreenblue/lazy.rb, line 40 def blue new(0,0,1) end
Returns the centre of the RGB
cube.
# File lib/redgreenblue/lazy.rb, line 65 def centre grey end
Returns the 8 corners of the RGB
cube.
# File lib/redgreenblue/lazy.rb, line 60 def corners [ black, red, yellow, green, cyan, blue, magenta, white ] end
Returns CSS named colors, as per CSS Color Module Level 4.
Optional selector argument can be:
-
String or Regexp (to select by name)
-
RGB
color (to select by color) -
Integer (to select by index).
Selection by name (string or regular expression) is case-insensitive by default.
@example No Selector
# All colors RGB.css # Pastels RGB.css.select { |c| c.ostwald_cwk[1] > 0.6 }
@example Select by Name
# Exact name RGB.css 'pink' # Regular expression RGB.css /pink|rose/
@example Select by RGB
color
RGB.css RGB.hex('0ff')
# File lib/redgreenblue/web.rb, line 32 def css(selector=nil) @@css ||= load_gpl file: ( File.join File.dirname(__FILE__), 'palettes', 'css.gpl' ), freeze: true case selector when NilClass @@css when String n = selector.downcase @@css.select { |c| c.name == n }.first when Regexp r = Regexp.new selector.source, Regexp::IGNORECASE @@css.select { |c| c.name =~ r } when Integer @@css[selector] when self @@css.select { |c| c == selector } else raise ArgumentError, 'Unsupported selector' end end
Creates a cyan RGB
object.
# File lib/redgreenblue/lazy.rb, line 50 def cyan new(0,1,1) end
Calls the given block for each 24-bit RGB
color (from black to white), passing the color as an RGB
object.
Returns the number of iterations.
# File lib/redgreenblue/24bit.rb, line 68 def self.each_24bit_color range = 0..255 range.each do |r| range.each do |g| range.each do |b| yield self.rgb(r,g,b) end end end range.size ** 3 end
Creates a new RGB
object from a line of gpl (Gimp color palette) input. Returns nil if not successful.
@example
RGB.gpl "255 153 204\tpink"
# File lib/redgreenblue/gpl.rb, line 13 def gpl(line) if line.chomp.match( /^\s*(?<r>\d{1,3})\s+(?<g>\d{1,3})\s+(?<b>\d{1,3})(\s+(?<name>.*))?/ ) color = RGB.rgb $~[:r].to_i, $~[:g].to_i, $~[:b].to_i color.name = $~[:name] if $~[:name] color else nil end end
Returns a header for a .gpl file (Gimp color palette). Includes an optional name and number of columns.
@example
RGB.gpl_header('Spring')
Reverse-engineered from:
# File lib/redgreenblue/gpl.rb, line 74 def gpl_header(name=nil, columns: nil) "GIMP Palette\n" + ( name ? "Name: #{name}\n" : '' ) + ( columns ? "Columns: #{columns}\n" : '' ) end
Creates a pure green RGB
object.
# File lib/redgreenblue/lazy.rb, line 35 def green new(0,1,0) end
Creates a grey RGB
object. Defaults to lightness 0.5, a middle grey. Black equals grey(0), white equals grey(1).
::gray
is an alias for ::grey
.
# File lib/redgreenblue/lazy.rb, line 22 def grey(lightness=0.5) new(lightness, lightness, lightness) end
Creates a new object from a 6- or 3-digit hexadecimal string representing red, green, and blue.
The string may include a '#' prefix.
# File lib/redgreenblue/hex.rb, line 32 def self.hex(hex_string) if rgb = RGB.hex_to_rgb(hex_string) self.rgb rgb else raise ArgumentError, "'#{hex_string}' is not a usable hexadecimal string" end end
Returns a 3-digit shorthand version of a 6-digit hexadecimal string.
If a shorthand version is not possible, returns the original string.
# File lib/redgreenblue/hex.rb, line 43 def self.hex_shorthand(hex_string) hex_string.sub( /^(#?)(\h)\2(\h)\3(\h)\4$/, '\1\2\3\4' ) end
Parses a 6- or 3-digit hexadecimal string. Returns three integers in the range 0..255, or nil if not successful.
The string may include a '#' prefix.
# File lib/redgreenblue/hex.rb, line 51 def self.hex_to_rgb(hex_string) if hex_string =~ /^(#?)(\h\h)(\h\h)(\h\h)$/ [$2, $3, $4].map { |h| h.to_i(16) } elsif hex_string =~ /^(#?)(\h)(\h)(\h)$/ [$2, $3, $4].map { |h| (h*2).to_i(16) } else nil end end
Creates a new RGB
object from HSB values: hue (0..360), saturation (0..1), and brightness (0..1).
Creates a new RGB
object from HSL values: hue (0..360), saturation (0..1), and lightness (0..1).
# File lib/redgreenblue/hsl.rb, line 13 def hsl(*a) new hsl_to_values(*a) end
Calculates RGB
values from HSL. Given hue (0..360), saturation (0..1), and lightness (0..1), returns red, green, and blue as three values between 0 and 1.
# File lib/redgreenblue/hsl.rb, line 20 def hsl_to_values(*a) hsm_to_values(:hsl, a) end
Creates a new RGB
object from HSV values: hue (0..360), saturation (0..1), and value (0..1).
# File lib/redgreenblue/hsv.rb, line 13 def hsv(*a) new hsv_to_values(*a) end
Calculates RGB
values from HSV. Given hue (0..360), saturation (0..1), and value (0..1), returns red, green, and blue as three values between 0 and 1.
# File lib/redgreenblue/hsv.rb, line 20 def hsv_to_values(*a) hsm_to_values(:hsv, a) end
Loads a gpl (Gimp color palette) source and returns an array of RGB
objects.
Options:
-
file: Path to a .gpl file to be loaded.
-
url: URL for a .gpl source to be loaded.
-
compact: Defaults to true. If set to false, returns nil for each line that can not be parsed to an
RGB
color. -
freeze: Defaults to false. If set to true, returns a frozen array of frozen objects.
@example String
RGB.load_gpl "255 0 0\tred\n255 153 204\tpink\n"
@example File
RGB.load_gpl file: '/path/to/palette.gpl' RGB.load_gpl file: '/path/to/palette.gpl', compact: false
@example URL
RGB.load_gpl url: 'https://lospec.com/palette-list/yuko-tomita-time.gpl'
# File lib/redgreenblue/gpl.rb, line 38 def load_gpl(source=nil, file: nil, url: nil, compact: true, freeze: false) if ! source if file source = File.open file elsif url require 'open-uri' source = URI.open url end end if source.respond_to? :each_line list = source.each_line.map { |line| RGB.gpl(line) } if compact list.compact! end if freeze list.freeze list.each &:freeze end list else raise ArgumentError, 'Not a valid source' end end
Creates a magenta RGB
object.
# File lib/redgreenblue/lazy.rb, line 55 def magenta new(1,0,1) end
# File lib/redgreenblue/base.rb, line 3 def initialize(*a) self.values = a.any? ? a : [ 0.5, 0.5, 0.5 ] end
On Mac OS, shows the color picker and creates an RGB
object with the chosen color. Not available on other platforms.
If no default color is specified, the picker defaults to a middle grey.
# File lib/redgreenblue/os/mac.rb, line 16 def self.pick(default_color=RGB.new) result = RGB.mac_choose(default_color) if result RGB.rrggbb result else nil end end
Creates a new RGB
object with random red, green, and blue values.
# File lib/redgreenblue/random.rb, line 21 def self.rand new(Kernel::rand, Kernel::rand, Kernel::rand) end
Creates a pure red RGB
object.
# File lib/redgreenblue/lazy.rb, line 30 def red new(1,0,0) end
Creates a new object from red, green, and blue components as integers in the range 0..255 (three 8-bit values).
# File lib/redgreenblue/24bit.rb, line 48 def self.rgb(*rgb) c = self.new c.rgb = rgb c end
Creates a new RGB
color from 16-bit RGB565 data.
# File lib/redgreenblue/rgb565.rb, line 19 def self.rgb565(rgb565_string) c = self.new c.rgb565 = rgb565_string c end
Creates a new object from red, green, and blue components as integers in the range 0..65535 (three 16-bit values).
# File lib/redgreenblue/48bit.rb, line 48 def self.rrggbb(*rrggbb) c = self.new c.rrggbb = rrggbb c end
Returns the current inspect style.
# File lib/redgreenblue/inspect.rb, line 57 def self.style @@style ||= base_style end
Selects an inspect style.
Only the first few characters of your preferred style are required.
# File lib/redgreenblue/inspect.rb, line 69 def self.style=(s) @@style = styles.grep( /^#{s.to_s.downcase}/ ).first || style end
Returns a list of all available inspect styles.
# File lib/redgreenblue/inspect.rb, line 62 def self.styles ( self.instance_methods + self.private_instance_methods ).grep( /^_inspect_(.*)/ ) { $1 }.sort end
Returns RGB::VERSION
.
# File lib/redgreenblue/version.rb, line 7 def self.version VERSION end
Creates a white RGB
object.
# File lib/redgreenblue/lazy.rb, line 10 def white new(1,1,1) end
Creates a yellow RGB
object.
# File lib/redgreenblue/lazy.rb, line 45 def yellow new(1,1,0) end
Private Class Methods
Calculates RGB
values from HSL or HSV. With help from:
# File lib/redgreenblue/hsx_shared.rb, line 14 def hsm_to_values(type, *a) raise NotImplementedError unless [:hsl, :hsv].include? type hue, saturation, magnitude = a.flatten if ( saturation == 0 ) or ( ! hue ) return Array.new(3) { magnitude } end hue = hue.modulo 360 chroma = case type when :hsl ( 1 - ( 2 * magnitude - 1 ).abs ) * saturation when :hsv magnitude * saturation end values = [ chroma, ( 1 - ( ( hue / 60.0 ).modulo(2) - 1 ).abs ) * chroma, 0 ] values = case type when :hsl values.map { |v| ( v + magnitude - chroma / 2.0 ).round(9) } when :hsv values.map { |v| ( v + magnitude - chroma ).round(9) } end # order values according to hue sextant [ [0,1,2], [1,0,2], [2,0,1], [2,1,0], [1,2,0], [0,2,1] ][hue.div 60].map { |i| values[i]} end
Uses Applescript to call the color picker on Mac OS.
-
requires a 48-bit
RGB
triplet [rr, gg, bb] for default choice. -
Returns nil when <cancel> is clicked or <esc> key hit.
-
Otherwise returns 48-bit
RGB
triplet [rr, gg, bb].
Applescript command documented here: Standard Additions -> User Interaction -> choose color
# File lib/redgreenblue/os/mac.rb, line 35 def self.mac_choose(default_color) app = case ENV['TERM_PROGRAM'] when /iTerm\.app/ 'iTerm' else 'Terminal' end script = <<~ENDSCRIPT tell application "#{app}" try return choose color default color #{default_color.applescript} on error return "" end try end tell ENDSCRIPT result = `osascript -e '#{script}'`.chomp if result.empty? nil else (result.split( /,\w*/ )).map { |s| s.to_i } end end
Public Instance Methods
Returns true if this object and another object represent exactly the same color. Otherwise returns false.
# File lib/redgreenblue/base.rb, line 65 def ==(other) ( self.class == other.class ) && ( self.values == other.values ) end
Returns true when this is an achromatic color: red, green, and blue have equal values. Otherwise false.
# File lib/redgreenblue/misc.rb, line 16 def achromatic? values.min == values.max end
Returns the color in the format used by AppleScript (a 48-bit RGB
triplet).
# File lib/redgreenblue/mac.rb, line 4 def applescript "{%i, %i, %i}" % rrggbb end
Returns the blue component as an integer in the range 0..255 (an 8-bit value).
# File lib/redgreenblue/24bit.rb, line 16 def b (blue * 255).round end
Sets the blue component using an integer in the range 0..255 (an 8-bit value).
# File lib/redgreenblue/24bit.rb, line 31 def b=(n) self.blue = n / 255.0 end
Returns the blue component as an integer in the range 0..65535 (a 16-bit value).
# File lib/redgreenblue/48bit.rb, line 16 def bb (blue * 65535).round end
Sets the blue component using an integer in the range 0..65535 (a 16-bit value).
# File lib/redgreenblue/48bit.rb, line 31 def bb=(n) self.blue = n / 65535.0 end
Returns a 3-byte string containing the object's color in BGR24 format.
# File lib/redgreenblue/bgr24bit.rb, line 4 def bgr24 [b, g, r].pack('C3') end
Sets red, green, and blue using BGR24 data (a 3-byte string).
# File lib/redgreenblue/bgr24bit.rb, line 9 def bgr24=(bgr_string) self.b, self.g, self.r = bgr_string.unpack('C3') end
Creates one or more new RGB
objects by mixing this object's color with a portion of black.
# File lib/redgreenblue/mix.rb, line 34 def blacken(portion=0.5, *portions) if (portion.class != Array) and portions.none? mix(RGB.black, portion) else ( [portion].flatten + portions ).map { |p| mix(RGB.black, p) } end end
Changes the object's color by mixing it with a portion of black.
# File lib/redgreenblue/mix.rb, line 29 def blacken!(portion=0.5) mix!(RGB.black, portion) end
Returns the blue component as a value between 0 and 1.
# File lib/redgreenblue/base.rb, line 25 def blue @blue end
Sets the blue component to a value between 0 and 1.
Values outside the range 0..1 will be clipped.
# File lib/redgreenblue/base.rb, line 46 def blue=(value) @blue = limit(value) end
Returns CIE 1976 L*a*b* (CIELAB) values for the RGB
object.
# File lib/redgreenblue/cie_1976.rb, line 4 def cie_lab(round: true) cie_lab_luv(round: round, type: :lab) end
Returns CIE 1976 LCHab values for the RGB
object, derived from L*a*b* (CIELAB).
When C is 0, H is nil.
# File lib/redgreenblue/cie_1976.rb, line 13 def cie_lch_ab cie_lch_ab_uv(type: :lab) end
Returns CIE 1976 LCHuv values for the RGB
object, derived from L*u*v* (CIELUV).
When C is 0, H is nil.
# File lib/redgreenblue/cie_1976.rb, line 27 def cie_lch_uv cie_lch_ab_uv(type: :luv) end
Returns CIE 1976 L*u*v* (CIELUV) values for the RGB
object.
# File lib/redgreenblue/cie_1976.rb, line 18 def cie_luv(round: true) cie_lab_luv(round: round, type: :luv) end
Returns CIE 1931 xy values for the RGB
object.
# File lib/redgreenblue/cie_1931.rb, line 45 def cie_xy cie_xyy[0..1] end
Returns CIE 1931 xyY values for the RGB
object.
Based on:
# File lib/redgreenblue/cie_1931.rb, line 30 def cie_xyy x, y, z = cie_xyz(round: false) [ x / ( x + y + z ), y / ( x + y + z ), y ].map { |v| v.round(8) } end
Returns CIE 1931 XYZ values for the RGB
object.
Based on:
sRGB to XYZ matrix for D65 reference white calculated with Javascript by Bruce Lindbloom:
# File lib/redgreenblue/cie_1931.rb, line 10 def cie_xyz(round: true) r, g, b = linear_values [ r * 0.4124_5643_9090 + g * 0.3575_7607_7644 + b * 0.1804_3748_3266, r * 0.2126_7285_1406 + g * 0.7151_5215_5288 + b * 0.0721_7499_3307, r * 0.0193_3389_5582 + g * 0.1191_9202_5881 + b * 0.9503_0407_8536 ].map { |v| round ? v.round(8) : v } end
Returns the color space.
Currently always 'sRGB'.
# File lib/redgreenblue/base.rb, line 10 def color_space 'sRGB' end
Returns an array of three RGB
objects, for the red, green, and blue components of this object.
# File lib/redgreenblue/misc.rb, line 29 def components [ RGB.new(red,0,0), RGB.new(0,green,0), RGB.new(0,0,blue) ] end
Returns the object's RGB
value in hexadecimal notation as used in CSS.
Shortens to 3 digits when possible.
# File lib/redgreenblue/web.rb, line 61 def css_hex "##{hex true}" end
Returns the names of CSS named colors identical to this color. @example
RGB.hex('f0f').css_names
# File lib/redgreenblue/web.rb, line 68 def css_names RGB.css(self).map &:name end
Returns the difference between this color and another color, according to the CIE 1976 delta E formula.
Based on:
# File lib/redgreenblue/cie_1976.rb, line 36 def delta_e_cie_1976(another) l , a , b = cie_lab(round: false) l2, a2, b2 = another.cie_lab(round: false) Math.sqrt( (l - l2) ** 2 + (a - a2) ** 2 + (b - b2) ** 2 ).round(8) end
Returns the euclidean distance between this color and another color.
When you imagine a color as a point in a 3-dimensional space, the dimensions being red, green, and blue, this is the distance between two colors.
# File lib/redgreenblue/misc.rb, line 44 def distance(another) ( ( (another.red - red ) ** 2) + ( (another.green - green) ** 2) + ( (another.blue - blue ) ** 2) ) ** (1/2.0) end
Returns the green component as an integer in the range 0..255 (an 8-bit value).
# File lib/redgreenblue/24bit.rb, line 11 def g (green * 255).round end
Sets the green component using an integer in the range 0..255 (an 8-bit value).
# File lib/redgreenblue/24bit.rb, line 26 def g=(n) self.green = n / 255.0 end
Returns the green component as an integer in the range 0..65535 (a 16-bit value).
# File lib/redgreenblue/48bit.rb, line 11 def gg (green * 65535).round end
Sets the green component using an integer in the range 0..65535 (a 16-bit value).
# File lib/redgreenblue/48bit.rb, line 26 def gg=(n) self.green = n / 65535.0 end
Returns a 1-pixel GIF image set to the color.
With help from:
# File lib/redgreenblue/gif.rb, line 7 def gif_pixel "GIF89a\1\0\1\0\x90\0\0".b + rgb.pack('C3') + "\0\0\0,\0\0\0\0\1\0\1\0\0\x02\x02\x04\1\0;".b end
Writes a 1-pixel GIF image to a file.
# File lib/redgreenblue/gif.rb, line 14 def gif_pixel_write(file_path) File.binwrite(file_path, gif_pixel) end
Returns the color in the format used in .gpl files (Gimp color palettes), including its name (if present).
You can optionally supply a name as argument.
# File lib/redgreenblue/gpl.rb, line 89 def gpl(gpl_name=name) ( "%3d %3d %3d" % rgb ) + ( gpl_name.to_s.size != 0 ? "\t#{gpl_name}" : '' ) end
Returns the green component as a value between 0 and 1.
# File lib/redgreenblue/base.rb, line 20 def green @green end
Sets the green component to a value between 0 and 1.
Values outside the range 0..1 will be clipped.
# File lib/redgreenblue/base.rb, line 39 def green=(value) @green = limit(value) end
Returns a 6-digit hexadecimal string representing the object's red, green, and blue components as 8-bit values.
If shorthand is true, returns 3-digit shorthand if possible.
# File lib/redgreenblue/hex.rb, line 6 def hex(shorthand=false) if shorthand RGB.hex_shorthand hex6 else hex6 end end
Sets red, green, and blue using a 6- or 3-digit hexadecimal string.
The string may include a '#' prefix.
# File lib/redgreenblue/hex.rb, line 17 def hex=(hex_string) if rgb = RGB.hex_to_rgb(hex_string) self.rgb = rgb else raise ArgumentError, "'#{hex_string}' is not a usable hexadecimal string" end end
Returns color as HSB: hue (0..360), saturation (0..1), brightness (0..1). When saturation is 0, hue is nil.
Sets red, green, and blue using HSB values: hue (0..360), saturation (0..1), and brightness (0..1).
Sets HSB-brightness to a number between 0 and 1.
Adjusts red, green, and blue, leaving HSB-hue and -saturation unchanged. When brightness is 0, hue will be nil, and saturation will be 0.
Sets HSB-hue to a number of degrees (0..360) or nil.
Adjusts red, green, and blue, leaving HSB-saturation and -brightness unchanged. When hue is nil, saturation will be 0.
Creates one or more new RGB
objects by rotating this object's HSB-hue a number of degrees.
Sets red, green, and blue by rotating the object's HSB-hue a number of degrees.
Sets HSB-saturation to a number between 0 and 1.
Adjusts red, green, and blue, leaving HSB-hue and -brightness unchanged. When saturation is 0, hue will be nil.
Returns color as HSL: hue (0..360), saturation (0..1), lightness (0..1). When saturation is 0, hue is nil.
# File lib/redgreenblue/hsl.rb, line 33 def hsl hsl_hsv_c[0] end
Sets red, green, and blue using HSL values: hue (0..360), saturation (0..1), and lightness (0..1).
# File lib/redgreenblue/hsl.rb, line 53 def hsl=(*a) self.values = RGB.hsl_to_values(*a) end
Returns the object's HSL-hue (0..360).
# File lib/redgreenblue/hsl.rb, line 38 def hsl_h hsl[0] end
Sets HSL-hue to a number of degrees (0..360) or nil.
Adjusts red, green, and blue, leaving saturation and lightness unchanged. When hue is nil, saturation will be 0.
# File lib/redgreenblue/hsl.rb, line 61 def hsl_h=(degrees) self.hsl = hsl.fill(degrees,0,1) end
Returns the object's HSL-lightness (0..1).
# File lib/redgreenblue/hsl.rb, line 48 def hsl_l hsl[2] end
Sets HSL-lightness to a value between 0 and 1.
Adjusts red, green, and blue, leaving hue and saturation unchanged. When lightness is 0 or 1, hue will be nil, and saturation will be 0.
# File lib/redgreenblue/hsl.rb, line 77 def hsl_l=(value) self.hsl = hsl.fill(value ,2,1) end
Creates one or more new RGB
objects by rotating this object's HSL-hue a number of degrees.
# File lib/redgreenblue/hsl.rb, line 88 def hsl_rotate(a_degrees, *b_degrees) if a_degrees.class != Array and b_degrees.none? RGB.hsl zip_add(hsl, [a_degrees, 0, 0]) else ( [a_degrees] + b_degrees ).flatten.map { |degrees| RGB.hsl zip_add(hsl, [degrees, 0, 0]) } end end
Sets red, green, and blue by rotating the object's HSL-hue a number of degrees.
# File lib/redgreenblue/hsl.rb, line 82 def hsl_rotate!(degrees) self.hsl = zip_add(hsl, [degrees, 0, 0]) self end
Returns the object's HSL-saturation (0..1).
# File lib/redgreenblue/hsl.rb, line 43 def hsl_s hsl[1] end
Sets HSL-saturation to a value between 0 and 1.
Adjusts red, green, and blue, leaving hue and lightness unchanged. When saturation is 0, hue will be nil.
# File lib/redgreenblue/hsl.rb, line 69 def hsl_s=(value) self.hsl = hsl.fill(value ,1,1) end
Returns color as HSV: hue (0..360), saturation (0..1), value (0..1). When saturation is 0, hue is nil.
# File lib/redgreenblue/hsv.rb, line 33 def hsv hsl_hsv_c[1] end
Sets red, green, and blue using HSV values: hue (0..360), saturation (0..1), and value (0..1).
# File lib/redgreenblue/hsv.rb, line 53 def hsv=(*a) self.values = RGB.hsv_to_values(*a) end
Returns the object's HSV-hue (0..360).
# File lib/redgreenblue/hsv.rb, line 38 def hsv_h hsv[0] end
Sets HSV-hue to a number of degrees (0..360) or nil.
Adjusts red, green, and blue, leaving HSV-saturation and -value unchanged. When hue is nil, saturation will be 0.
# File lib/redgreenblue/hsv.rb, line 61 def hsv_h=(degrees) self.hsv = hsv.fill(degrees,0,1) end
Creates one or more new RGB
objects by rotating this object's HSV-hue a number of degrees.
# File lib/redgreenblue/hsv.rb, line 88 def hsv_rotate(a_degrees, *b_degrees) if a_degrees.class != Array and b_degrees.none? RGB.hsv zip_add(hsv, [a_degrees, 0, 0]) else ( [a_degrees] + b_degrees ).flatten.map { |degrees| RGB.hsv zip_add(hsv, [degrees, 0, 0]) } end end
Sets red, green, and blue by rotating the object's HSV-hue a number of degrees.
# File lib/redgreenblue/hsv.rb, line 82 def hsv_rotate!(degrees) self.hsv = zip_add(hsv, [degrees, 0, 0]) self end
Returns the object's HSV-saturation (0..1).
# File lib/redgreenblue/hsv.rb, line 43 def hsv_s hsv[1] end
Sets HSV-saturation to a number between 0 and 1.
Adjusts red, green, and blue, leaving HSV-hue and -value unchanged. When saturation is 0, hue will be nil.
# File lib/redgreenblue/hsv.rb, line 69 def hsv_s=(value) self.hsv = hsv.fill(value ,1,1) end
Returns the object's HSV-value (0..1).
# File lib/redgreenblue/hsv.rb, line 48 def hsv_v hsv[2] end
Sets HSV-value to a number between 0 and 1.
Adjusts red, green, and blue, leaving HSV-hue and -saturation unchanged. When value is 0, hue will be nil, and saturation will be 0.
# File lib/redgreenblue/hsv.rb, line 77 def hsv_v=(value) self.hsv = hsv.fill(value ,2,1) end
Returns the color's hue (0..360), whiteness (0..1), and blackness (0..1), as defined by the HWB color model.
For achromatic colors, hue is nil.
Based on:
# File lib/redgreenblue/hwb.rb, line 10 def hwb [ hsv_h, cwk[1,2] ].flatten end
Returns a programmer-friendly representation of the object.
You can choose among several inspect styles. See the styles, style, and style= class methods.
# File lib/redgreenblue/inspect.rb, line 34 def inspect send "_inspect_#{self.class.style}" end
Inverts the object's color.
# File lib/redgreenblue/misc.rb, line 4 def invert! self.values = values.map { |v| 1 - v } self end
Returns gamma-expanded (inverse-companded) RGB
values for the object (three values between 0 and 1).
Based on:
# File lib/redgreenblue/gamma.rb, line 9 def linear_values if color_space == 'sRGB' values.map { |v| if v <= 0.04045 v / 12.92 else ( ( v + 0.055 ) / 1.055 ) ** 2.4 end } else raise "can not compute gamma for color space '#{color_space}'" end end
Sets the object's RGB
values using three linear RGB
values, each between 0 and 1. Linear values will be converted to the object's gamma (gamma-companded).
Based on:
# File lib/redgreenblue/gamma.rb, line 30 def linear_values=(*a) if color_space == 'sRGB' self.values = a.flatten.map { |v| if v <= 0.0031308 v * 12.92 else 1.055 * ( v ** ( 1/2.4 ) ) - 0.055 end }.map { |v| v.round(9) } else raise "can not compute gamma for color space '#{color_space}'" end end
Matches this color to a set of colors using the CIE 1976 delta E formula.
Returns the given set of colors with their difference from this color, sorted by difference (nearest color first). @example Find the 3 nearest CSS named colors
RGB.hex('f9c').match_de76(RGB.css).first(3)
# File lib/redgreenblue/match.rb, line 17 def match_de76(others) others.map { |c| [ c, de76(c) ] }.sort_by { |a| a[1] } end
Matches this color to a set of colors by calculating their euclidean distance.
Returns the given set of colors with their distance from this color, sorted by distance (nearest color first). @example What is nearer: red, grey or white?
RGB.hex('f9c').match_distance [RGB.red, RGB.grey, RGB.white]
# File lib/redgreenblue/match.rb, line 8 def match_distance(others) others.map { |c| [ c, distance(c) ] }.sort_by { |a| a[1] } end
Changes the object's color by mixing it with a portion of another RGB
color.
# File lib/redgreenblue/mix.rb, line 4 def mix!(another,portion=0.5) self.values = mix_values(another.values, portion) self end
Returns the name.
# File lib/redgreenblue/name.rb, line 4 def name defined?(@name) ? @name : nil end
Sets the name (a string or nil).
# File lib/redgreenblue/name.rb, line 9 def name=(text) @name = ( text.to_s.size != 0 ) ? text.to_s : nil end
Returns a new RGB
object with this color's Ostwald full-color, or nil for achromatic colors (white, greys, and black).
The resulting color contains no white or black, i.e. it has at least one RGB
component set to 0, and at least one set to maximum.
This is identical (barring very small rounding errors) to setting HSL-saturation to 1, and -lightness to 0.5
Based on:
-
Color for the Sciences, pp. 575–591
# File lib/redgreenblue/ostwald.rb, line 15 def ostwald_color white_portion = values.min color_portion = values.max - white_portion if color_portion == 0 nil else RGB.new( values.map { |v| ( ( v - white_portion ) / color_portion ).round(6) } ) end end
Returns the portions of Ostwald full-color, white, and black, which constitute this color.
The sum of these three numbers equals 1.
cwk
is an alias for ostwald_cwk
.
Based on:
-
Color for the Sciences, pp. 575–591
# File lib/redgreenblue/ostwald.rb, line 35 def ostwald_cwk(round: true) [ color_portion = values.max - values.min, white_portion = values.min, 1 - color_portion - white_portion ].map { |v| round ? v.round(8) : v } end
Returns an array of RGB
objects for all possible ways in which the red, green, and blue values of this object can be exchanged.
Example: RGB.red
.permutation returns [ RGB.red
, RGB.green
, RGB.blue
]. See also: shuffle.
# File lib/redgreenblue/misc.rb, line 24 def permutation values.permutation.to_a.uniq.map { |v| RGB.new v } end
On Mac OS, shows the color picker to choose a color for the RGB
object. Not available on other platforms.
# File lib/redgreenblue/os/mac.rb, line 5 def pick result = RGB.mac_choose(self) if result self.rrggbb = result end end
Returns the red component as an integer in the range 0..255 (an 8-bit value).
# File lib/redgreenblue/24bit.rb, line 6 def r (red * 255).round end
Sets the red component using an integer in the range 0..255 (an 8-bit value).
# File lib/redgreenblue/24bit.rb, line 21 def r=(n) self.red = n / 255.0 end
Assigns random values to red, green, and blue.
# File lib/redgreenblue/random.rb, line 15 def randomize! self.values = Kernel::rand, Kernel::rand, Kernel::rand self end
Returns the red component as a value between 0 and 1.
# File lib/redgreenblue/base.rb, line 15 def red @red end
Sets the red component to a value between 0 and 1.
Values outside the range 0..1 will be clipped.
# File lib/redgreenblue/base.rb, line 32 def red=(value) @red = limit(value) end
Returns the red, green, and blue components as integers in the range 0..255 (three 8-bit values).
# File lib/redgreenblue/24bit.rb, line 38 def rgb [r,g,b] end
Returns the color in 16-bit RGB565 format.
# File lib/redgreenblue/rgb565.rb, line 4 def rgb565 [((r >> 3) << 11) + ((g >> 2) << 5) + (b >> 3)].pack 'S<' end
Sets the color from 16-bit RGB565 data. With help from:
# File lib/redgreenblue/rgb565.rb, line 11 def rgb565=(rgb565_string) v = ( rgb565_string.unpack "S<" )[0] self.r = ( ( v & 0xf800 ) >> 11 ) << 3 self.g = ( ( v & 0x07e0 ) >> 5 ) << 2 self.b = ( ( v & 0x001f ) ) << 3 end
Sets the red, green, and blue components using three integers in the range 0..255 (three 8-bit values).
# File lib/redgreenblue/24bit.rb, line 43 def rgb=(*rgb) self.r, self.g, self.b = rgb.flatten end
Returns the red component as an integer in the range 0..65535 (a 16-bit value).
# File lib/redgreenblue/48bit.rb, line 6 def rr (red * 65535).round end
Sets the red component using an integer in the range 0..65535 (a 16-bit value).
# File lib/redgreenblue/48bit.rb, line 21 def rr=(n) self.red = n / 65535.0 end
Returns the red, green, and blue components as integers in the range 0..65535 (three 16-bit values).
# File lib/redgreenblue/48bit.rb, line 38 def rrggbb [rr,gg,bb] end
Sets the red, green, and blue components using three integers in the range 0..65535 (three 16-bit values).
# File lib/redgreenblue/48bit.rb, line 43 def rrggbb=(*rrggbb) self.rr, self.gg, self.bb = rrggbb.flatten end
Creates a new RGB
object with this object's red, green, and blue values shuffled.
# File lib/redgreenblue/random.rb, line 10 def shuffle RGB.new values.shuffle end
Shuffles the object's red, green, and blue values.
# File lib/redgreenblue/random.rb, line 4 def shuffle! self.values = values.shuffle self end
Creates a new RGB
object containing the nearest 24-bit color.
# File lib/redgreenblue/24bit.rb, line 61 def snap RGB.rgb rgb end
Sets the red, green, and blue values to those of the nearest 24-bit color.
# File lib/redgreenblue/24bit.rb, line 55 def snap! self.rgb = rgb self end
Returns a set of colors between this color and another. That other color is included.
The resulting colors are spaced evenly in the RGB
color space using a straightforward calculation. You will likely experience these colors as not exactly evenly spaced.
# File lib/redgreenblue/mix.rb, line 46 def steps(another,step_count=1,include_begin=false) # origin (self, optional) ( include_begin ? [self.dup] : [] ) + # ...plus intermediate colors (1..step_count-1).map { |c| mix(another, c.to_f/step_count) } + # ...plus destination color [another.dup] end
Returns the ANSI escape sequence required to set the background color on a terminal to this color. Only works on terminals that support 24-bit colors, so-called “true color”.
# File lib/redgreenblue/terminal.rb, line 15 def terminal_background "\e[48;2;%d;%d;%dm" % rgb end
Returns the ANSI escape sequence required to set the foreground color on a terminal to this color. Only works on terminals that support 24-bit colors, so-called “true color”.
# File lib/redgreenblue/terminal.rb, line 9 def terminal_foreground "\e[38;2;%d;%d;%dm" % rgb end
Returns a sorted hash of 3 key/value pairs for red, green, and blue, sorted in order of decreasing value.
# File lib/redgreenblue/base.rb, line 71 def to_h ([:red, :green, :blue].zip values).sort_by { |k,v| [-v,[:red, :green, :blue].index(k)] }.to_h end
Returns the color as a 24-bit integer in the range 0..16777215.
# File lib/redgreenblue/int.rb, line 4 def to_i ( r << 16 ) + ( g << 8 ) + b end
Only available when optional support for Philips Hue lights is loaded.
Returns a hash with the arguments required by the Philips Hue API, to set a light to this RGB
object's hue, saturation and brightness.
Formatted as JSON, this hash can be sent to a bridge to set a light's state.
See (regrettably requires registration):
@example Load
require 'redgreenblue/opt/philipshue'
@example Use
RGB.magenta.to_philips_hue_api_hsb_arguments => {"on"=>true, "bri"=>254, "hue"=>54613, "sat"=>254}
@return [Hash] API arguments
# File lib/redgreenblue/opt/philipshue.rb, line 26 def to_philips_hue_api_hsb_arguments(black_off=true) my_hsb = hsb # Black means 'off' if black_off and ( my_hsb[2] == 0 ) { 'on' => false } else { # On/Off state of the light 'on' => true, # Brightness 1..254 # Note: a brightness of 1 will not switch the light off 'bri' => ( my_hsb[2] * 253 + 1 ).round, # Hue 0..65535 'hue' => (( my_hsb[0] || 0 ) * 65535 / 360 ).round, # Saturation 0..254 'sat' => ( my_hsb[1] * 254 ).round } end end
Returns a string representation of the object.
# File lib/redgreenblue/inspect.rb, line 39 def to_s _inspect_default end
Returns the red, green, and blue components as three values between 0 and 1.
# File lib/redgreenblue/base.rb, line 51 def values [ red, green, blue ] end
Sets the red, green, and blue components using three values between 0 and 1.
Values outside the range 0..1 will be clipped.
# File lib/redgreenblue/base.rb, line 60 def values=(*a) self.red, self.green, self.blue = a.flatten end
Prints details for the RGB
object, using multiple lines.
# File lib/redgreenblue/view.rb, line 4 def view puts _inspect_view end
Returns the contrast ratio for this color and another color, as defined by the Web Content Accessibility Guidelines (WCAG) 2.0.
Based on:
# File lib/redgreenblue/web.rb, line 98 def wcag20_contrast_ratio(another, round: true) luminances = [ wcag20_luminance(round: false), another.wcag20_luminance(round: false) ].sort.reverse contrast_ratio = ( luminances[0] + 0.05 ) / ( luminances[1] + 0.05 ) round ? contrast_ratio.round(8) : contrast_ratio end
Returns the color's relative luminance, as defined by the Web Content Accessibility Guidelines (WCAG) 2.0.
This is different from the Y component of CIE 1931 XYZ.
Based on:
# File lib/redgreenblue/web.rb, line 78 def wcag20_luminance(round: true) if color_space == 'sRGB' values.map { |v| if v <= 0.03928 v / 12.92 else ( ( v + 0.055 ) / 1.055 ) ** 2.4 end }.zip( [0.2126, 0.7152, 0.0722] ).map { |c,f| c * f }.inject(:+).instance_eval { |v| round ? v.round(8) : v } else raise "can not calculate luminance for color space '#{color_space}'" end end
Creates one or more new RGB
objects by mixing this object's color with a portion of white.
# File lib/redgreenblue/mix.rb, line 20 def whiten(portion=0.5, *portions) if (portion.class != Array) and portions.none? mix(RGB.white, portion) else ( [portion].flatten + portions ).map { |p| mix(RGB.white, p) } end end
Changes the object's color by mixing it with a portion of white.
# File lib/redgreenblue/mix.rb, line 15 def whiten!(portion=0.5) mix!(RGB.white, portion) end
Private Instance Methods
# File lib/redgreenblue/inspect.rb, line 5 def _inspect_default(prefix='RGB') "#{prefix} #{_inspect_hex} (red=%1.5f green=%1.5f blue=%1.5f)" % [red, green, blue] + ( name ? ' ' + name : '' ) end
# File lib/redgreenblue/inspect.rb, line 9 def _inspect_hex (self == snap ? '#' : '~') + hex end
# File lib/redgreenblue/inspect.rb, line 25 def _inspect_name _inspect_swatch + ( name ? ' ' + name : '' ) end
# File lib/redgreenblue/inspect.rb, line 17 def _inspect_short "#{_inspect_swatch} #{_inspect_hex}" end
# File lib/redgreenblue/inspect.rb, line 21 def _inspect_simple _inspect_default _inspect_swatch end
# File lib/redgreenblue/inspect.rb, line 13 def _inspect_swatch terminal_background + " \e[0m" end
# File lib/redgreenblue/view.rb, line 12 def _inspect_view o = [] o << 3.times.map { terminal_background + " \e[0m "} o << [ "#{_inspect_hex} ", '%-7.7s ' % color_space, ' ' ] o << components.map { |c| c.terminal_background + " \e[0m " } o << %w(R: G: B:) o << values.map { |v| '%1.5f ' % v } o << rgb.map { |v| '%3d ' % v } o << %w(H: S: L:) o << hsl.map { |v| v ? ('%7.3f ' % v) : ' - ' } o << %w(H: S: B:) o << hsb.map { |v| v ? ('%7.3f ' % v) : ' - ' } o << 3.times.map { (ostwald_color ? ostwald_color.terminal_background + " \e[0m " : ' ') } o << %w(C: W: K:) o << ostwald_cwk.map { |v| '%1.3f ' % v } o.transpose.map(&:join).join("\n") end
Returns either CIE 1976 L*a*b* (CIELAB) or CIE 1976 L*u*v* (CIELUV) values for the RGB
object.
L*a*b* formula based on:
With peeking at:
L*u*v* formula based on:
# File lib/redgreenblue/cie_1976.rb, line 58 def cie_lab_luv(round: true, type: :lab) x , y , z = cie_xyz(round: false) xr, yr, zr = RGB.white.cie_xyz(round: false) f = [ x / xr, y / yr, z / zr ].map { |v| if v > ( 216.0 / 24389 ) Math.cbrt v else ( 24389.0 / 27 * v + 16 ) / 116.0 end } if type == :luv [ l = 116 * f[1] - 16, 13 * l * ( ( 4 * x ) / ( x + 15 * y + 3 * z ) - ( 4 * xr ) / ( xr + 15 * yr + 3 * zr ) ), 13 * l * ( ( 9 * y ) / ( x + 15 * y + 3 * z ) - ( 9 * yr ) / ( xr + 15 * yr + 3 * zr ) ) ] else [ 116 * f[1] - 16, 500 * (f[0] - f[1]), 200 * (f[1] - f[2]) ] end.map { |v| v.nan? ? 0.0 : ( round ? v.round(8) : v ) } end
Returns either CIE 1976 LCHab or CIE 1976 LCHuv values for the RGB
object.
Based on:
# File lib/redgreenblue/cie_1976.rb, line 91 def cie_lch_ab_uv(type: :lab) if type == :luv l, v1, v2 = cie_luv(round: false) else l, v1, v2 = cie_lab(round: false) end [ l.round(8), c = Math.hypot(v1, v2).round(8), c == 0 ? nil : ( Math.atan2(v2, v1) * 180.0 / Math::PI ).modulo(360).round(8) ] end
# File lib/redgreenblue/hex.rb, line 63 def hex6 '%02x%02x%02x' % [ r, g, b ] end
Compute HSL, HSV, and chroma. With help from:
# File lib/redgreenblue/hsx_shared.rb, line 60 def hsl_hsv_c sorted_hash = to_h min, max = sorted_hash.values.values_at(2,0) chroma = max - min hue = if chroma == 0 nil else ( case sorted_hash.keys.first when :red 60 * ( ( ( green - blue ) / chroma ).modulo 6 ) when :green 60 * ( ( blue - red ) / chroma + 2 ) when :blue 60 * ( ( red - green ) / chroma + 4 ) end ).round(6) end lightness = ( min + max ) / 2.0 saturation_hsl = chroma == 0 ? 0.0 : ( chroma / ( 1 - (2 * lightness - 1).abs ) ).round(9) value = max saturation_hsv = value == 0 ? 0.0 : chroma / value [ [hue, saturation_hsl, lightness], [hue, saturation_hsv, value], chroma ] end
limits to range 0..1
# File lib/redgreenblue/base.rb, line 80 def limit(n) n <= 0 ? 0.0 : n >= 1 ? 1.0 : n end
# File lib/redgreenblue/mix.rb, line 57 def mix_values(some_values, portion) raise(ArgumentError, "Portion '#{portion}' not in range (0..1)") unless (0..1).include? portion [ ( red * (1 - portion) ) + ( some_values[0] * portion ), ( green * (1 - portion) ) + ( some_values[1] * portion ), ( blue * (1 - portion) ) + ( some_values[2] * portion ) ] end
# File lib/redgreenblue/math.rb, line 5 def zip_add(a,b) a.zip(b).map { |ab| ( ab[0] || 0 ) + ab[1] } end