Module: Color

Included in:
CIELAB, CMYK, Grayscale, HSL, RGB, XYZ, YIQ
Defined in:
lib/color.rb,
lib/color/version.rb

Overview

# Color – Color Math in Ruby

Color is a Ruby library to provide RGB, CMYK, HSL, and other color space manipulation support to applications that require it. It provides optional named RGB colors that are commonly supported in HTML, SVG, and X11 applications.

The Color library performs purely mathematical manipulation of the colors based on color theory without reference to device color profiles (such as sRGB or Adobe RGB). For most purposes, when working with RGB and HSL color spaces, this won’t matter. Absolute color spaces (like CIE LAB and CIE XYZ) cannot be reliably converted to relative color spaces (like RGB) without color profiles. When necessary for conversions, Color provides D65 and D50 reference white values in Color::XYZ.

Color 2.1 fixes a Color::XYZ bug where the values were improperly clamped and adds more Color::XYZ white points for standard illuminants. It builds on the Color 2.0 major release, dropping support for all versions of Ruby prior to 3.2 as well as removing or renaming a number of features. The main breaking changes are:

  • Color classes are immutable Data objects; they are no longer mutable.

  • RGB named colors are no longer loaded on gem startup, but must be required explicitly (this is not done via ‘autoload` because there are more than 100 named colors with spelling variations) with `require “color/rgb/colors”`.

  • Color palettes have been removed.

  • ‘Color::CSS` and `Color::CSS#[]` have been removed.

Defined Under Namespace

Classes: CIELAB, CMYK, Grayscale, HSL, RGB, XYZ, YIQ

Constant Summary collapse

EPSILON =

The maximum “resolution” for color math; if any value is less than or equal to this value, it is treated as zero.

1e-5
TOLERANCE =

The tolerance for comparing the components of two colors. In general, colors are considered equal if all of their components are within this tolerance value of each other.

1e-4
VERSION =

:nodoc:

"2.1.1"

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.normalize(value, range = 0.0..1.0) ⇒ Object

:nodoc:



188
189
190
191
192
193
194
195
196
197
# File 'lib/color.rb', line 188

module_function def normalize(value, range = 0.0..1.0) # :nodoc:
  value = value.clamp(range)
  if near?(value, range.begin)
    range.begin
  elsif near?(value, range.end)
    range.end
  else
    value
  end
end

.translate_range(x, to:, from: 0.0..1.0) ⇒ Object

:nodoc:



211
212
213
214
215
216
# File 'lib/color.rb', line 211

module_function def translate_range(x, to:, from: 0.0..1.0) # :nodoc:
  a, b = [from.begin, from.end]
  c, d = [to.begin, to.end]
  y = (((x - a) * (d - c)) / (b - a)) + c
  y.clamp(to)
end

Instance Method Details

#==(other) ⇒ Object

Compares the ‘other` color to this one. The `other` color will be coerced to the same type as the current color. Such converted color comparisons will always be more approximate than non-converted comparisons.

All values are compared as floating-point values, so two colors will be reported equivalent if all component values are within TOLERANCE of each other.



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

def ==(other)
  other.is_a?(Color) && to_internal.zip(coerce(other).to_internal).all? { near?(_1, _2) }
end

#componentsObject

It is useful to know the number of components in some cases. Since most colors are defined with three components, we define a constant value here. Color classes that require more or less should override this.

We could define this as ‘members.count`, but this would require a special case for Color::RGB regardless because there’s an additional member for RGB colors (names).



61
# File 'lib/color.rb', line 61

def components = 3 # :nodoc:

#css_value(value, format = nil) ⇒ Object

:nodoc:



122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/color.rb', line 122

def css_value(value, format = nil) # :nodoc:
  if value.nil?
    "none"
  elsif near_zero?(value)
    "0"
  else
    suffix =
      case format
      in :percent
        "%"
      in :degrees
        "deg"
      else
        ""
      end

    "%3.2f%s" % [value, suffix]
  end
end

#map(&block) ⇒ Object

Apply the provided block to each color component in turn, returning a new color instance.



77
# File 'lib/color.rb', line 77

def map(&block) = self.class.from_internal(*to_internal.map(&block))

#map_with(other, &block) ⇒ Object

Apply the provided block to the color component pairs in turn, returning a new color instance.



82
# File 'lib/color.rb', line 82

def map_with(other, &block) = self.class.from_internal(*zip(other).map(&block))

#scale(*factors) ⇒ Object

Multiplies each component value by the scaling factor or factors, returning a new color object with the scaled values.

If a single scaling factor is provided, it is applied to all components:

“‘ruby rgb = Color::RGB::Wheat # => RGB [#f5deb3] rgb.scale(0.75) # => RGB [#b8a786] “`

If more than one scaling factor is provided, there must be exactly one factor for each color component of the color object or an ‘ArgumentError` will be raised.

“‘ruby rgb = Color::RGB::Wheat # => RGB [#f5deb3] # 0xf5 * 0 == 0x00, 0xde * 0.5 == 0x6f, 0xb3 * 2 == 0x166 (clamped to 0xff) rgb.scale(0, 0.5, 2) # => RGB [#006fff]

rgb.scale(1, 2) # => Invalid scaling factors [1, 2] for Color::RGB (ArgumentError) “‘



109
110
111
112
113
114
115
116
117
118
119
# File 'lib/color.rb', line 109

def scale(*factors)
  if factors.size == 1
    factor = factors.first
    map { _1 * factor }
  elsif factors.size != components
    raise ArgumentError, "Invalid scaling factors #{factors.inspect} for #{self.class}"
  else
    new_components = to_internal.zip(factors).map { _1 * _2 }
    self.class.from_internal(*new_components)
  end
end

#zip(other) ⇒ Object

Zip the color component pairs together.



86
# File 'lib/color.rb', line 86

def zip(other) = to_internal.zip(coerce(other).to_internal)