Class: Color::XYZ

Inherits:
Data
  • Object
show all
Includes:
Color
Defined in:
lib/color/xyz.rb,
lib/color.rb

Overview

A Color object for the CIE 1931 XYZ color space derived from the original CIE RGB color space as linear transformation functions x̅(λ), y̅(λ), and z̅(λ) that describe the device-independent CIE standard observer. It underpins most other CIE color systems (such as CIELAB), but is directly used mostly for color instrument readings and color space transformations particularly in color profiles.

The XYZ color space ranges describe the mixture of wavelengths of light required to stimulate cone cells in the human eye, as well as the luminance (brightness) required. The ‘Y` component describes the luminance while the `X` and `Z` components describe two axes of chromaticity. Definitionally, the minimum value for any XYZ color component is 0.

As XYZ describes imaginary colors, the color gamut is usually expressed in relation to a reference white of an illuminant (frequently often D65 or D50) and expressed as the ‘xyY` color space, computed as:

“‘ x = X / (X + Y + Z) y = Y / (X + Y + Z) Y = Y “`

The range of ‘Y` values is conventionally clamped to 0..100, whereas the `X` and `Z` values must be no lower than 0 and on the same scale.

For more details, see [CIE XYZ color space].

[ciexyz]: en.wikipedia.org/wiki/CIE_1931_color_space#CIE_XYZ_color_space

XYZ colors are immutable Data class instances. Array deconstruction is ‘[x * 100, y * 100, z * 100]` and hash deconstruction is `y:, z:` (see #x, #y, #z).

Constant Summary collapse

E =

:stopdoc: NOTE: This should be using Rational instead of floating point values, otherwise there will be discontinuities. www.brucelindbloom.com/LContinuity.html :startdoc:

K =

:nodoc:

EK =

:nodoc:

E * K
WP2 =

White points for standard illuminants at 2° (CIE 1931).

{
  A: new(1.09849161234507, 1.0, 0.355798257454902),
  B: new(0.9909274480248, 1.0, 0.853132732288615),
  C: new(0.980705971659919, 1.0, 1.18224949392713),
  D50: new(0.964211994421199, 1.0, 0.825188284518828),
  D55: new(0.956797052643698, 1.0, 0.921480586017327),
  D65: new(0.950430051970945, 1.0, 1.08880649180926),
  D75: new(0.949722089884072, 1.0, 1.22639352072415),
  D93: new(0.953014035205816, 1.0, 1.41274275520851),
  E: new(1, 1.0, 1.0000300003),
  F1: new(0.92833634773327, 1.0, 1.03664719660806),
  F2: new(0.991446614618029, 1.0, 0.673159423379253),
  F3: new(1.03753487192493, 1.0, 0.49860512300279),
  F4: new(1.0914726375561, 1.0, 0.388132609288601),
  F5: new(0.908719701138108, 1.0, 0.987228866815325),
  F6: new(0.973091283635895, 1.0, 0.601905497618128),
  F7: new(0.950171560440895, 1.0, 1.08629642000425),
  F8: new(0.96412543554007, 1.0, 0.823331010452962),
  F9: new(1.00364797081623, 1.0, 0.678683511708377),
  F10: new(0.961735119213027, 1.0, 0.817123325737787),
  F11: new(1.00898894280487, 1.0, 0.642616604353936),
  F12: new(1.08046289656537, 1.0, 0.392275166291635),
  "FL3.0": new(1.09273493677163, 1.0, 0.3868088271758),
  "FL3.1": new(1.01981788966256, 1.0, 0.658275307980718),
  "FL3.2": new(0.916836289619075, 1.0, 0.990985751671998),
  "FL3.3": new(1.09547365817462, 1.0, 0.377937175364828),
  "FL3.4": new(1.02096949891068, 1.0, 0.702342047930283),
  "FL3.5": new(0.968888888888889, 1.0, 0.808888888888889),
  "FL3.6": new(1.08380716934487, 1.0, 0.388380716934487),
  "FL3.7": new(0.996868475991649, 1.0, 0.612734864300626),
  "FL3.8": new(0.974380395433027, 1.0, 0.810359231411863),
  "FL3.9": new(0.970505617977528, 1.0, 0.838483146067416),
  "FL3.10": new(0.944962143273151, 1.0, 0.967093768200349),
  "FL3.11": new(1.08422095615556, 1.0, 0.392865989596235),
  "FL3.12": new(1.02846401718582, 1.0, 0.656820622986037),
  "FL3.13": new(0.955112219451372, 1.0, 0.815738431698531),
  "FL3.14": new(0.951034063260341, 1.0, 1.09032846715328),
  HP1: new(1.28433734939759, 1.0, 0.125301204819277),
  HP2: new(1.14911014911015, 1.0, 0.255892255892256),
  HP3: new(1.05570552147239, 1.0, 0.398282208588957),
  HP4: new(1.00395048722676, 1.0, 0.62970766394522),
  HP5: new(1.01696741179639, 1.0, 0.676272555884729),
  "LED-B1": new(1.11819519372241, 1.0, 0.3339872486513),
  "LED-B2": new(1.08599202392822, 1.0, 0.406530408773679),
  "LED-B3": new(1.0088638195004, 1.0, 0.677142089712597),
  "LED-B4": new(0.977155910908053, 1.0, 0.87835522558538),
  "LED-B5": new(0.963535228677379, 1.0, 1.12669962917182),
  "LED-BH1": new(1.10034431874078, 1.0, 0.359075258239056),
  "LED-RGB1": new(1.08216575635241, 1.0, 0.292567086202802),
  "LED-V1": new(1.12462908011869, 1.0, 0.348170128585559),
  "LED-V2": new(1.00158940397351, 1.0, 0.647417218543046),
  ID50: new(0.952803997779012, 1.0, 0.823431426985008),
  ID65: new(0.939522225582099, 1.0, 1.08436649531297)
}.freeze
D50 =

The D50 standard illuminant white point at 2° (CIE 1931).

D65 =

The D65 standard illuminant white point at 2° (CIE 1931).

Constants included from Color

EPSILON, TOLERANCE, VERSION

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Color

#==, #components, #css_value, #map, #map_with, normalize, #scale, translate_range, #zip

Constructor Details

#initialize(x:, y:, z:) ⇒ XYZ

Creates a XYZ color representation from native values. The ‘y` value must be between 0 and 1 and `x` and `z` must be fractional valiues greater than or equal to 0.

“‘ruby Color::XYZ.from_fraction(0.95047, 1.0, 1.0883) Color::XYZ.new(0.95047, 1.0, 1.08883) Color::XYZ[x: 0.95047, y: 1.0, z: 1.08883] “`



90
91
92
93
94
95
96
97
98
99
# File 'lib/color/xyz.rb', line 90

def initialize(x:, y:, z:)
  # The X and Z values in the XYZ color model are technically unbounded. With Y scaled
  # to 1.0, we will clamp X to 0.0..2.2 and Z to 0.0..2.8.

  super(
    x: normalize(x, 0.0..2.2),
    y: normalize(y),
    z: normalize(z, 0.0..2.8)
  )
end

Instance Attribute Details

#xObject (readonly)

Returns the value of attribute x

Returns:

  • (Object)

    the current value of x



49
50
51
# File 'lib/color.rb', line 49

def x
  @x
end

#yObject (readonly)

Returns the value of attribute y

Returns:

  • (Object)

    the current value of y



49
50
51
# File 'lib/color.rb', line 49

def y
  @y
end

#zObject (readonly)

Returns the value of attribute z

Returns:

  • (Object)

    the current value of z



49
50
51
# File 'lib/color.rb', line 49

def z
  @z
end

Class Method Details

.from_values(*args, **kwargs) ⇒ Object

Creates a XYZ color representation from native values. ‘y` must be between 0 and 100 and `x` and `z` values must be scaled to `y`.

“‘ruby Color::XYZ.from_values(95.047, 100.00, 108.883) Color::XYZ.from_values(x: 95.047, y: 100.00, z: 108.883) “`

call-seq:

Color::XYZ.from_values(x, y, z)
Color::XYZ.from_values(x:, y:, z:)


62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/color/xyz.rb', line 62

def self.from_values(*args, **kwargs)
  x, y, z =
    case [args, kwargs]
    in [[_, _, _], {}]
      args
    in [[], {x:, y:, z:}]
      [x, y, z]
    else
      new(*args, **kwargs)
    end

  new(x: x / 100.0, y: y / 100.0, z: z / 100.0)
end

Instance Method Details

#coerce(other) ⇒ Object

Coerces the other Color object into XYZ.



178
# File 'lib/color/xyz.rb', line 178

def coerce(other) = other.to_xyz

#deconstructObject Also known as: to_a

:nodoc:



258
# File 'lib/color/xyz.rb', line 258

def deconstruct = [x * 100.0, y * 100.0, z * 100.0] # :nodoc:

#inspectObject

:nodoc:



263
# File 'lib/color/xyz.rb', line 263

def inspect = "XYZ [#{x} #{y} #{z}]" # :nodoc:

#pretty_print(q) ⇒ Object

:nodoc:



265
266
267
268
269
270
271
272
273
274
275
# File 'lib/color/xyz.rb', line 265

def pretty_print(q) # :nodoc:
  q.text "XYZ"
  q.breakable
  q.group 2, "[", "]" do
    q.text "%.4f" % x
    q.fill_breakable
    q.text "%.4f" % y
    q.fill_breakable
    q.text "%.4f" % z
  end
end

#to_cmykObject

Converts XYZ to Color::CMYK via Color::RGB.

See #to_rgb and Color::RGB#to_cmyk.



187
# File 'lib/color/xyz.rb', line 187

def to_cmyk(...) = to_rgb(...).to_cmyk(...)

#to_grayscaleObject

Converts XYZ to Color::Grayscale using the #y value



191
# File 'lib/color/xyz.rb', line 191

def to_grayscale(...) = Color::Grayscale.from_fraction(y)

#to_hslObject

Converts XYZ to Color::HSL via Color::RGB.

See #to_rgb and Color::RGB#to_hsl.



197
# File 'lib/color/xyz.rb', line 197

def to_hsl(...) = to_rgb(...).to_hsl(...)

#to_internalObject

:nodoc:



261
# File 'lib/color/xyz.rb', line 261

def to_internal = [x, y, z] # :nodoc:

#to_lab(*args, **kwargs) ⇒ Object

Converts XYZ to Color::CIELAB.

:call-seq:

to_lab(white: Color::XYZ::D65)


210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
# File 'lib/color/xyz.rb', line 210

def to_lab(*args, **kwargs)
  ref = kwargs[:white] || args.first || Color::XYZ::D65
  # Calculate the ratio of the XYZ values to the reference white.
  # http://www.brucelindbloom.com/index.html?Equations.html
  rel = scale(1.0 / ref.x, 1.0 / ref.y, 1.0 / ref.z)

  # 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.
  f = rel.map { |t|
    if t > E
      t**(1.0 / 3)
    else # t <= E
      ((K * 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
  }
  Color::CIELAB.from_values(
    (116 * f.y) - 16,
    500 * (f.x - f.y),
    200 * (f.y - f.z)
  )
end

#to_rgbObject

Converts XYZ to Color::RGB.

This always assumes an sRGB target color space and a D65 white point.



241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/color/xyz.rb', line 241

def to_rgb(...)
  # sRGB companding from linear values
  linear = [
    x * 3.2406255 + y * -1.5372080 + z * -0.4986286,
    x * -0.9689307 + y * 1.8757561 + z * 0.0415175,
    x * 0.0557101 + y * -0.2040211 + z * 1.0569959
  ].map {
    if _1.abs <= 0.0031308
      _1 * 12.92
    else
      1.055 * (_1**(1 / 2.4)) - 0.055
    end
  }

  Color::RGB.from_fraction(*linear)
end

#to_xyzObject



181
# File 'lib/color/xyz.rb', line 181

def to_xyz(...) = self

#to_yiqObject

Converts XYZ to Color::YIQ via Color::RGB.

See #to_rgb and Color::RGB#to_yiq.



203
# File 'lib/color/xyz.rb', line 203

def to_yiq(...) = to_rgb(...).to_yiq(...)