Class: Abachrome::Converters::OklchToXyz

Inherits:
Base
  • Object
show all
Defined in:
lib/abachrome/converters/oklch_to_xyz.rb

Instance Attribute Summary

Attributes inherited from Base

#from_space, #to_space

Class Method Summary collapse

Methods inherited from Base

#can_convert?, #convert, find_converter, #initialize, raise_unless, register

Constructor Details

This class inherits a constructor from Abachrome::Converters::Base

Class Method Details

.convert(oklch_color) ⇒ Object



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/abachrome/converters/oklch_to_xyz.rb', line 6

def self.convert(oklch_color)
  raise_unless oklch_color, :oklch

  l_oklch, c_oklch, h_oklch = oklch_color.coordinates.map { |coord| AbcDecimal(coord) }
  alpha = oklch_color.alpha

  # Step 1: OKLCH to OKLAB
  # (l_oklab, a_oklab, b_oklab)
  l_oklab = l_oklch
  # h_rad = (h_oklch * Math::PI) / AD(180) # h_oklch is AbcDecimal, Math::PI is Float. Coercion happens.
  # More explicit for Math::PI:
  h_rad = (h_oklch * AD(Math::PI.to_s)) / AD(180)

  # Standard Math.cos/sin expect float. h_rad is AbcDecimal.
  # .to_f is needed for conversion from AbcDecimal/BigDecimal to Float.
  cos_h_rad = AD(Math.cos(h_rad.to_f))
  sin_h_rad = AD(Math.sin(h_rad.to_f))

  a_oklab = c_oklch * cos_h_rad
  b_oklab = c_oklch * sin_h_rad

  # Step 2: OKLAB to L'M'S' (cone responses, non-linear)
  # (l_prime, m_prime, s_prime)
  # These are the M_lms_prime_from_oklab matrix operations.
  # The AbcDecimal() wrapper on the whole sum (as in OklabToLms.rb) is not strictly necessary
  # if l_oklab, a_oklab, b_oklab are already AbcDecimal, as AbcDecimal ops return AbcDecimal.
  l_prime = l_oklab + (AD("0.39633779217376785678") * a_oklab) + (AD("0.21580375806075880339") * b_oklab)
  m_prime = l_oklab - (a_oklab * AD("-0.1055613423236563494")) + (b_oklab * AD("-0.063854174771705903402"))
  s_prime = l_oklab - (a_oklab * AD("-0.089484182094965759684")) + (b_oklab * AD("-1.2914855378640917399"))

  # Step 3: L'M'S' to LMS (cubing)
  # (l_lms, m_lms, s_lms)
  l_lms = l_prime**3
  m_lms = m_prime**3
  s_lms = s_prime**3

  # Step 4: LMS to LRGB
  # (r_lrgb, g_lrgb, b_lrgb)
  # Using matrix M_lrgb_from_lms (OKLAB specific)
  r_lrgb = (l_lms * AD("4.07674166134799")) + (m_lms * AD("-3.307711590408193")) + (s_lms * AD("0.230969928729428"))
  g_lrgb = (l_lms * AD("-1.2684380040921763")) + (m_lms * AD("2.6097574006633715")) + (s_lms * AD("-0.3413193963102197"))
  b_lrgb = (l_lms * AD("-0.004196086541837188")) + (m_lms * AD("-0.7034186144594493")) + (s_lms * AD("1.7076147009309444"))

  # Clamp LRGB values to be non-negative (as done in LmsToLrgb.rb)
  # Using the pattern [AbcDecimal, Integer].max which relies on AbcDecimal's <=> coercion.
  # AD(0) is AbcDecimal zero.
  zero_ad = AD(0)
  r_lrgb_clamped = [r_lrgb, zero_ad].max
  g_lrgb_clamped = [g_lrgb, zero_ad].max
  b_lrgb_clamped = [b_lrgb, zero_ad].max

  # Step 5: LRGB to XYZ
  # (x_xyz, y_xyz, z_xyz)
  # Using matrix M_xyz_from_lrgb (sRGB D65)
  x_xyz = (r_lrgb_clamped * AD("0.4124564")) + (g_lrgb_clamped * AD("0.3575761")) + (b_lrgb_clamped * AD("0.1804375"))
  y_xyz = (r_lrgb_clamped * AD("0.2126729")) + (g_lrgb_clamped * AD("0.7151522")) + (b_lrgb_clamped * AD("0.0721750"))
  z_xyz = (r_lrgb_clamped * AD("0.0193339")) + (g_lrgb_clamped * AD("0.1191920")) + (b_lrgb_clamped * AD("0.9503041"))

  Color.new(ColorSpace.find(:xyz), [x_xyz, y_xyz, z_xyz], alpha)
end