Class: Chromate::Color

Inherits:
Effect
  • Object
show all
Defined in:
lib/chromate/color.rb

Overview

The ‘Color` class is used to represent a terminal color in the range 0..255. Colors are powerful tools, and have a multitude of convenience methods to make working with them easier.

Color Models

All colors have a so-called ‘color model’ (accessible via the ‘color_model` attribute). The color model of a color determines how it will be escaped. The following color models are possible:

- Colors in the range 0..7 have a color model of 8. These colors are
  always escaped as single-integer values in the ranges 30..37 and
  40..47 for foregrounds and backgrounds, respectively.
- Colors in the range 8..15 have a color model of 16. These colors
  can be escaped in two ways, with single-integer values in the 
  ranges 90..97 and 100..107, for foreground and background,
  respectively, or with additional bold sequences. Which route is
  taken depends on the compliancy setting; single-integer values are
  non-compliant, while bold sequences are. The compliancy setting
  may be specified in three ways, each overriding the last: via the
  class attribute on `Color`, via the instance attribute by the same
  name, and in the method call as a second (optional parameter).
  The default at all levels is non-compliancy.
- Colors in the range 16..255 have a color model of 256. These
  colors are always escaped as xterm-256 escape sequences of the
  form 38;5;x for foreground and 48;5;x for background, where x is
  the actual color value.

Converting from Other Formats

The ‘Color` class features a powerful approximation mechanism, allowing the creation of colors from arbitrary RGB values, which may be specified either in RGB using from_rgb or in hex using from_hex. When either of these methods are used, the closest matching terminal color code will be found automatically, using the CIE 1976 delta-E algorithm. This operation is quite a bit slower compared to using exact color values, but it must be done only once, as all values are cached after first use and kept until the end of program execution.

Making New Colors

If you __really__ want to make a new color, you may do so by calling the provided constructor. However, this is wasteful – there is, after all, a finite set of possible colors. Moreover, all of these colors already exist as members of the ‘COLORS` constant. That’s all well and good if you have the raw color code, which can be used as an index for the ‘COLORS` array, but what if you don’t? Well, then [] is your new best friend! This method accepts the full range of color representations, including raw color codes, names, hex strings, and RGB values. The following example showcases all of the various types that [] accepts:

“‘ruby # The following all return the ’black’ color Color Color Color Color Color[[0, 0, 0]] Color[0, 0, 0]

# Malformed inputs Color # => ArgumentError: bad color code: -1 Color # => ArgumentError: bad color code: 256 Color # => nil Color # => ArgumentError: invalid hex string: # Color # => nil “‘

Note that when getting a color from a name, ‘nil` will be returned if no color with that name exists, rather than throwing an error.

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Effect

#+, #==, #to_i, #to_s

Constructor Details

#initialize(value, compliant = Color.compliant) ⇒ Color

Create a new Color object representing the specified color code. The color model will be inferred automatically based on the range of ‘value` (8 if it’s less than 8, 16 if it’s less than 16, and 256 otherwise).

Parameters:

  • the xterm color code of the new color



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/chromate/color.rb', line 101

def initialize(value, compliant = Color.compliant)
  if COLOR_NAMES.key?(value)
    super(COLOR_NAMES[value])
  else
    super(value)
  end

  @compliant = compliant
  @escape = :fg
  @color_model = if @value < 8
    8
  elsif @value < 16
    16
  else
    256
  end
end

Class Attribute Details

.compliantBoolean

Returns the global default compliancy setting.

Returns:

  • the global default compliancy setting



85
86
87
# File 'lib/chromate/color.rb', line 85

def compliant
  @compliant
end

Instance Attribute Details

#color_modelInteger (readonly)

Returns the color model of the color (either 8, 16, or 256).

Returns:

  • the color model of the color (either 8, 16, or 256)



78
79
80
# File 'lib/chromate/color.rb', line 78

def color_model
  @color_model
end

#compliantBoolean

Returns the default compliancy setting for #to_fg and #to_bg.

Returns:



81
82
83
# File 'lib/chromate/color.rb', line 81

def compliant
  @compliant
end

Class Method Details

.[](value, g = nil, b = nil) ⇒ Color

Get an existing Color object representing the specified color code, hex value, color name, or RGB array.

Parameters:

Returns:



125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/chromate/color.rb', line 125

def self.[](value, g = nil, b = nil)
  case value
  when Fixnum
    if value > 255 or value < 0
      raise ArgumentError, "bad color code: #{value}"
    else
      COLORS[value]
    end
  when String
    if value[0] == '#'
      Color.from_hex(value)
    elsif COLOR_NAMES.key?(value.intern)
      COLORS[COLOR_NAMES[value.intern]]
    else
      nil
    end
  when Symbol
    if COLOR_NAMES.key?(value)
      COLORS[COLOR_NAMES[value]]
    else
      nil
    end
  when Array
    Color.from_rgb(*value)
  else
    if g.nil? and b.nil?
      raise TypeError, "don't know what to do with a #{value.class}"
    else
      Color.from_rgb(value, g, b)
    end
  end
end

.from_hex(hex) ⇒ Color

Get a Color object representing the specified hex string.

Parameters:

  • a string of the form #XXXXXX

Returns:



163
164
165
166
167
168
169
# File 'lib/chromate/color.rb', line 163

def self.from_hex(hex)
  if Hex::PATTERN =~ hex
    Color.from_rgb(*hex.scan(Hex::BYTE).map { |b| b.to_i(16) })
  else
    raise ArgumentError, "invalid hex string: #{hex}"
  end
end

.from_rgb(r, g = r, b = r) ⇒ Color

Note:

The ‘g` and `b` arguments default to the value passed in for `r`, so that if you have a monochromatic color (e.g., r, g, and b are the same), you may pass a single value.

Get a Color object representing the specified color in RGB space.

Examples:

Getting a monochromatic color

Color.from_rgb(0) # <=> Color.from_rgb(0, 0, 0)

Parameters:

  • the red component

  • (defaults to: r)

    the green component

  • (defaults to: r)

    the blue component

Returns:



184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
# File 'lib/chromate/color.rb', line 184

def self.from_rgb(r, g = r, b = r)
  ary = [r, g, b]
  if RGB::COLORS.include?(ary)
    COLORS[RGB::COLORS.index(ary)]
  elsif CACHE.include?(ary)
    CACHE[ary]
  else
    lab = RGB.to_lab(ary)
    code = Lab::COLORS.index(Lab::COLORS.sort do |lab1, lab2|
      Lab.difference(lab, lab1) <=> Lab.difference(lab, lab2)
    end.first)
    CACHE[ary] = COLORS[code]
    COLORS[code]
  end
end

Instance Method Details

#as_bgColor

Convert to a Color that escapes to a background by default.

Returns:



279
280
281
282
# File 'lib/chromate/color.rb', line 279

def as_bg
  @escape = :bg
  self
end

#as_fgColor

Convert to a Color that escapes to a foreground by default.

Returns:



241
242
243
244
# File 'lib/chromate/color.rb', line 241

def as_fg
  @escape = :fg
  self
end

#bg?Boolean

Does this color represent a background?

Returns:



288
289
290
# File 'lib/chromate/color.rb', line 288

def bg?
  @escape == :bg
end

#blueInteger Also known as: b

Get the blue component of the color.

Returns:



361
362
363
# File 'lib/chromate/color.rb', line 361

def blue
  RGB::COLORS[@value][2]
end

#escapeString

Escape as an SGR escape sequence.

Returns:

  • an escape sequence of the form “e[1;2;3;…m”



296
297
298
299
300
301
302
303
# File 'lib/chromate/color.rb', line 296

def escape
  case @escape
  when :fg
    to_fg
  when :bg
    to_bg
  end
end

#fg?Boolean

Does this color represent a foreground?

Returns:



250
251
252
# File 'lib/chromate/color.rb', line 250

def fg?
  @escape == :fg
end

#greenInteger Also known as: g

Get the green component of the color.

Returns:



352
353
354
# File 'lib/chromate/color.rb', line 352

def green
  RGB::COLORS[@value][1]
end

#greyscale?Boolean

Is this color greyscale?

Returns:



212
213
214
# File 'lib/chromate/color.rb', line 212

def greyscale?
  GREYSCALE_COLORS.include?(self)
end

#inspectObject



315
316
317
318
319
320
321
# File 'lib/chromate/color.rb', line 315

def inspect
  if name.nil?
    "#<Color: (#{to_rgb.join(',')})>"
  else
    "#<Color: #{name}>"
  end
end

#nameSymbol

Get the name of the color, if it exists.

Returns:



309
310
311
312
313
# File 'lib/chromate/color.rb', line 309

def name
  if COLOR_NAMES.value?(@value)
    COLOR_NAMES.find { |k, v| v == @value }.first
  end
end

#redInteger Also known as: r

Get the red component of the color.

Returns:



343
344
345
# File 'lib/chromate/color.rb', line 343

def red
  RGB::COLORS[@value][0]
end

#supported?Boolean

Is this color supported by the host environment?

Returns:



204
205
206
# File 'lib/chromate/color.rb', line 204

def supported?
  SUPPORTED_COLORS.include?(self)
end

#to_bg(compliant = @compliant) ⇒ String

Escape as a background SGR escape sequence.

Parameters:

  • (defaults to: @compliant)

    whether to use standards-compliant bold sequences or the non-standard 100-107 range

Returns:

  • an escape sequence setting the foreground to the color



260
261
262
263
264
265
266
267
268
269
270
271
272
273
# File 'lib/chromate/color.rb', line 260

def to_bg(compliant = @compliant)
  case @color_model
  when 8
    "\e[#{40 + @value}m"
  when 16
    if compliant
      "\e[#{40 + @value - 8};1m"
    else
      "\e[#{100 + @value - 8}m"
    end
  when 256
    "\e[48;5;#{@value}m"
  end
end

#to_fg(compliant = @compliant) ⇒ String

Escape as a foreground SGR escape sequence.

Parameters:

  • (defaults to: @compliant)

    whether to use standards-compliant bold sequences or the non-standard 90-97 range

Returns:

  • an escape sequence setting the foreground to the color



222
223
224
225
226
227
228
229
230
231
232
233
234
235
# File 'lib/chromate/color.rb', line 222

def to_fg(compliant = @compliant)
  case @color_model
  when 8
    "\e[#{30 + @value}m"
  when 16
    if compliant
      "\e[#{30 + @value - 8};1m"
    else
      "\e[#{90 + @value - 8}m"
    end
  when 256
    "\e[38;5;#{@value}m"
  end
end

#to_hexString

Get the hex value represented by the color.

Returns:

  • a string of the form #XXXXXX



335
336
337
# File 'lib/chromate/color.rb', line 335

def to_hex
  '#' + Hex::COLORS[@value]
end

#to_rgb<Integer>

Get the RGB value represented by the color.

Returns:

  • an array containing r, g, and b values, in that order



327
328
329
# File 'lib/chromate/color.rb', line 327

def to_rgb
  RGB::COLORS[@value]
end