abachrome-float 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. checksums.yaml +7 -0
  2. data/.envrc +3 -0
  3. data/.rubocop.yml +10 -0
  4. data/CHANGELOG.md +21 -0
  5. data/CLA.md +45 -0
  6. data/CODE-OF-CONDUCT.md +9 -0
  7. data/LICENSE +19 -0
  8. data/README.md +315 -0
  9. data/Rakefile +15 -0
  10. data/SECURITY.md +94 -0
  11. data/abachrome-float.gemspec +36 -0
  12. data/demos/ncurses/plasma.rb +124 -0
  13. data/devenv.lock +171 -0
  14. data/devenv.nix +52 -0
  15. data/devenv.yaml +8 -0
  16. data/lib/abachrome/color.rb +197 -0
  17. data/lib/abachrome/color_mixins/blend.rb +100 -0
  18. data/lib/abachrome/color_mixins/lighten.rb +90 -0
  19. data/lib/abachrome/color_mixins/spectral_mix.rb +70 -0
  20. data/lib/abachrome/color_mixins/to_colorspace.rb +107 -0
  21. data/lib/abachrome/color_mixins/to_grayscale.rb +87 -0
  22. data/lib/abachrome/color_mixins/to_lrgb.rb +121 -0
  23. data/lib/abachrome/color_mixins/to_oklab.rb +117 -0
  24. data/lib/abachrome/color_mixins/to_oklch.rb +110 -0
  25. data/lib/abachrome/color_mixins/to_srgb.rb +142 -0
  26. data/lib/abachrome/color_models/cmyk.rb +159 -0
  27. data/lib/abachrome/color_models/hsv.rb +49 -0
  28. data/lib/abachrome/color_models/lms.rb +38 -0
  29. data/lib/abachrome/color_models/oklab.rb +37 -0
  30. data/lib/abachrome/color_models/oklch.rb +91 -0
  31. data/lib/abachrome/color_models/rgb.rb +58 -0
  32. data/lib/abachrome/color_models/xyz.rb +31 -0
  33. data/lib/abachrome/color_models/yiq.rb +37 -0
  34. data/lib/abachrome/color_space.rb +199 -0
  35. data/lib/abachrome/converter.rb +117 -0
  36. data/lib/abachrome/converters/base.rb +128 -0
  37. data/lib/abachrome/converters/cmyk_to_srgb.rb +42 -0
  38. data/lib/abachrome/converters/lms_to_lrgb.rb +40 -0
  39. data/lib/abachrome/converters/lms_to_srgb.rb +27 -0
  40. data/lib/abachrome/converters/lms_to_xyz.rb +34 -0
  41. data/lib/abachrome/converters/lrgb_to_lms.rb +3 -0
  42. data/lib/abachrome/converters/lrgb_to_oklab.rb +57 -0
  43. data/lib/abachrome/converters/lrgb_to_srgb.rb +59 -0
  44. data/lib/abachrome/converters/lrgb_to_xyz.rb +33 -0
  45. data/lib/abachrome/converters/oklab_to_lms.rb +44 -0
  46. data/lib/abachrome/converters/oklab_to_lrgb.rb +71 -0
  47. data/lib/abachrome/converters/oklab_to_oklch.rb +56 -0
  48. data/lib/abachrome/converters/oklab_to_srgb.rb +46 -0
  49. data/lib/abachrome/converters/oklch_to_lrgb.rb +79 -0
  50. data/lib/abachrome/converters/oklch_to_oklab.rb +52 -0
  51. data/lib/abachrome/converters/oklch_to_srgb.rb +46 -0
  52. data/lib/abachrome/converters/oklch_to_xyz.rb +70 -0
  53. data/lib/abachrome/converters/srgb_to_cmyk.rb +64 -0
  54. data/lib/abachrome/converters/srgb_to_lrgb.rb +55 -0
  55. data/lib/abachrome/converters/srgb_to_oklab.rb +45 -0
  56. data/lib/abachrome/converters/srgb_to_oklch.rb +47 -0
  57. data/lib/abachrome/converters/srgb_to_yiq.rb +49 -0
  58. data/lib/abachrome/converters/xyz_to_lms.rb +34 -0
  59. data/lib/abachrome/converters/xyz_to_oklab.rb +42 -0
  60. data/lib/abachrome/converters/yiq_to_srgb.rb +47 -0
  61. data/lib/abachrome/gamut/base.rb +74 -0
  62. data/lib/abachrome/gamut/p3.rb +27 -0
  63. data/lib/abachrome/gamut/rec2020.rb +25 -0
  64. data/lib/abachrome/gamut/srgb.rb +49 -0
  65. data/lib/abachrome/illuminants/base.rb +35 -0
  66. data/lib/abachrome/illuminants/d50.rb +33 -0
  67. data/lib/abachrome/illuminants/d55.rb +29 -0
  68. data/lib/abachrome/illuminants/d65.rb +37 -0
  69. data/lib/abachrome/illuminants/d75.rb +29 -0
  70. data/lib/abachrome/named/css.rb +157 -0
  71. data/lib/abachrome/named/tailwind.rb +301 -0
  72. data/lib/abachrome/outputs/css.rb +119 -0
  73. data/lib/abachrome/palette.rb +244 -0
  74. data/lib/abachrome/palette_mixins/interpolate.rb +53 -0
  75. data/lib/abachrome/palette_mixins/resample.rb +61 -0
  76. data/lib/abachrome/palette_mixins/stretch_luminance.rb +72 -0
  77. data/lib/abachrome/parsers/css.rb +452 -0
  78. data/lib/abachrome/parsers/hex.rb +52 -0
  79. data/lib/abachrome/parsers/tailwind.rb +45 -0
  80. data/lib/abachrome/spectral.rb +276 -0
  81. data/lib/abachrome/to_abcd.rb +23 -0
  82. data/lib/abachrome/version.rb +7 -0
  83. data/lib/abachrome.rb +242 -0
  84. data/logo.png +0 -0
  85. data/logo.webp +0 -0
  86. data/security/assesments/2025-10-12-SECURITY_ASSESSMENT.md +53 -0
  87. data/security/vex.json +21 -0
  88. metadata +146 -0
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Abachrome::Converters::LrgbToOklab - Linear RGB to OKLAB color space converter
4
+ #
5
+ # This converter transforms colors from the linear RGB (LRGB) color space to the OKLAB color space
6
+ # using the standard OKLAB transformation matrices. The conversion process applies a series of
7
+ # matrix transformations and non-linear operations to accurately map linear RGB coordinates to
8
+ # the perceptually uniform OKLAB color space.
9
+ #
10
+ # Key features:
11
+ # - Implements the official OKLAB transformation algorithm with high-precision matrices
12
+ # - Converts linear RGB values through intermediate LMS color space representation
13
+ # - Applies cube root transformation for perceptual uniformity in the OKLAB space
14
+ # - Maintains alpha channel transparency values during conversion
15
+ # - Uses AbcDecimal arithmetic for precise color science calculations
16
+ # - Validates input color space to ensure proper linear RGB source data
17
+ #
18
+ # The OKLAB color space provides better perceptual uniformity compared to traditional RGB spaces,
19
+ # making it ideal for color manipulation operations like blending, lightness adjustments, and
20
+ # gamut mapping where human visual perception accuracy is important.
21
+
22
+ module Abachrome
23
+ module Converters
24
+ class LrgbToOklab < Abachrome::Converters::Base
25
+ # Converts a color from linear RGB (LRGB) color space to OKLAB color space.
26
+ #
27
+ # This conversion applies a matrix transformation to the linear RGB values,
28
+ # followed by a non-linear transformation, then another matrix transformation
29
+ # to produce OKLAB coordinates.
30
+ #
31
+ # @param rgb_color [Abachrome::Color] A color in linear RGB (LRGB) color space
32
+ # @raise [ArgumentError] If the provided color is not in LRGB color space
33
+ # @return [Abachrome::Color] The converted color in OKLAB color space with the same alpha value as the input
34
+ def self.convert(rgb_color)
35
+ raise_unless rgb_color, :lrgb
36
+
37
+ r, g, b = rgb_color.coordinates.map { |_| _.to_f }
38
+
39
+ l = (AD("0.41222147079999993") * r) + (AD("0.5363325363") * g) + (AD("0.0514459929") * b)
40
+ m = (AD("0.2119034981999999") * r) + (AD("0.680699545099999") * g) + (AD("0.1073969566") * b)
41
+ s = (AD("0.08830246189999998") * r) + (AD("0.2817188376") * g) + (AD("0.6299787005000002") * b)
42
+
43
+ l_ = l.to_f**Rational(1, 3)
44
+ m_ = m.to_f**Rational(1, 3)
45
+ s_ = s.to_f**Rational(1, 3)
46
+
47
+ lightness = (AD("0.2104542553") * l_) + (AD("0.793617785") * m_) - (AD("0.0040720468") * s_)
48
+ a = (AD("1.9779984951") * l_) - (AD("2.4285922050") * m_) + (AD("0.4505937099") * s_)
49
+ b = (AD("0.0259040371") * l_) + (AD("0.7827717662") * m_) - (AD("0.8086757660") * s_)
50
+
51
+ Color.new(ColorSpace.find(:oklab), [lightness, a, b], rgb_color.alpha)
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Abachrome::Converters::LrgbToSrgb - Linear RGB to sRGB color space converter
4
+ #
5
+ # This converter transforms colors from the linear RGB (LRGB) color space to the standard RGB (sRGB) color space by applying gamma correction. The conversion process applies the sRGB transfer function which uses different formulas for small and large values to match the non-linear response characteristics of typical display devices.
6
+ #
7
+ # Key features:
8
+ # - Implements the standard sRGB gamma correction algorithm with precise threshold handling
9
+ # - Converts linear RGB values to gamma-corrected sRGB values for proper display representation
10
+ # - Applies different transformation functions based on value magnitude (linear vs power function)
11
+ # - Maintains alpha channel transparency values during conversion
12
+ # - Uses AbcDecimal arithmetic for precise color science calculations
13
+ # - Validates input color space to ensure proper linear RGB source data
14
+ #
15
+ # The sRGB color space is the standard RGB color space for web content and most consumer displays, providing gamma correction that better matches human visual perception and display device characteristics compared to linear RGB values.
16
+
17
+ module Abachrome
18
+ module Converters
19
+ class LrgbToSrgb < Abachrome::Converters::Base
20
+ # Converts a color from linear RGB to sRGB color space.
21
+ #
22
+ # @param lrgb_color [Abachrome::Color] The color in linear RGB color space to convert
23
+ # @return [Abachrome::Color] A new Color object in sRGB color space with the converted coordinates
24
+ # @raise [TypeError] If the provided color is not in linear RGB color space
25
+ def self.convert(lrgb_color)
26
+ raise_unless lrgb_color, :lrgb
27
+ r, g, b = lrgb_color.coordinates.map { |c| to_srgb(c.to_f) }
28
+
29
+ output_coords = [r, g, b]
30
+
31
+ Color.new(
32
+ ColorSpace.find(:srgb),
33
+ output_coords,
34
+ lrgb_color.alpha
35
+ )
36
+ end
37
+
38
+ # Converts a linear RGB value to standard RGB color space (sRGB) value.
39
+ #
40
+ # This method implements the standard linearization function used in the sRGB color space.
41
+ # For small values (≤ 0.0031308), a simple linear transformation is applied.
42
+ # For larger values, a power function with gamma correction is used.
43
+ #
44
+ # @param v [AbcDecimal] The linear RGB value to convert
45
+ # @return [AbcDecimal] The corresponding sRGB value, preserving the sign of the input
46
+ def self.to_srgb(v)
47
+ v_abs = v.abs
48
+ v_sign = v.negative? ? -1 : 1
49
+ if v_abs <= AD("0.0031308")
50
+ v * AD("12.92")
51
+ else
52
+ v_sign * ((AD("1.055") * (v_abs**Rational(1.0, 2.4))) - AD("0.055"))
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abachrome
4
+ module Converters
5
+ class LrgbToXyz < Abachrome::Converters::Base
6
+ # Converts a color from linear RGB color space to XYZ color space.
7
+ #
8
+ # This method implements the linear RGB to XYZ transformation using the standard
9
+ # transformation matrix for the sRGB color space with D65 white point. The XYZ
10
+ # color space is the CIE 1931 color space that forms the basis for most other
11
+ # color space definitions and serves as a device-independent reference.
12
+ #
13
+ # @param lrgb_color [Abachrome::Color] The color in linear RGB color space
14
+ # @raise [ArgumentError] If the input color is not in linear RGB color space
15
+ # @return [Abachrome::Color] The resulting color in XYZ color space with
16
+ # the same alpha as the input color
17
+ def self.convert(lrgb_color)
18
+ raise_unless lrgb_color, :lrgb
19
+
20
+ r, g, b = lrgb_color.coordinates.map { |_| _.to_f }
21
+
22
+ # Linear RGB to XYZ transformation matrix (sRGB/D65)
23
+ x = (r * AD("0.4124564")) + (g * AD("0.3575761")) + (b * AD("0.1804375"))
24
+ y = (r * AD("0.2126729")) + (g * AD("0.7151522")) + (b * AD("0.0721750"))
25
+ z = (r * AD("0.0193339")) + (g * AD("0.1191920")) + (b * AD("0.9503041"))
26
+
27
+ Color.new(ColorSpace.find(:xyz), [x, y, z], lrgb_color.alpha)
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abachrome
4
+ module Converters
5
+ class OklabToLms < Abachrome::Converters::Base
6
+ # Converts a color from OKLAB color space to LMS color space.
7
+ #
8
+ # This method implements the first part of the OKLAB to linear RGB transformation,
9
+ # converting OKLAB coordinates to the intermediate LMS (Long, Medium, Short) color space
10
+ # which represents the response of the three types of cone cells in the human eye.
11
+ #
12
+ # @param oklab_color [Abachrome::Color] The color in OKLAB color space
13
+ # @raise [ArgumentError] If the input color is not in OKLAB color space
14
+ # @return [Abachrome::Color] The resulting color in LMS color space with
15
+ # the same alpha as the input color
16
+ def self.convert(oklab_color)
17
+ raise_unless oklab_color, :oklab
18
+
19
+ l, a, b = oklab_color.coordinates.map { |_| _.to_f }
20
+
21
+ l_ = AbcDecimal(l +
22
+ (AD("0.39633779217376785678") * a) +
23
+ (AD("0.21580375806075880339") * b))
24
+
25
+ m_ = AbcDecimal(l -
26
+ (a * AD("-0.1055613423236563494")) +
27
+ (b * AD("-0.063854174771705903402")))
28
+
29
+ s_ = AbcDecimal(l -
30
+ (a * AD("-0.089484182094965759684")) +
31
+ (b * AD("-1.2914855378640917399")))
32
+
33
+ # Apply cubic operation to convert from L'M'S' to LMS
34
+ l_lms = l_.to_f**3
35
+ m_lms = m_.to_f**3
36
+ s_lms = s_.to_f**3
37
+
38
+ Color.new(ColorSpace.find(:lms), [l_lms, m_lms, s_lms], oklab_color.alpha)
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Abachrome::Converters::OklabToLrgb - OKLAB to Linear RGB color space converter
4
+ #
5
+ # This converter transforms colors from the OKLAB color space to the linear RGB (LRGB) color space
6
+ # using the standard OKLAB transformation matrices. The conversion process first transforms
7
+ # OKLAB coordinates to the intermediate LMS (Long, Medium, Short) color space, then applies
8
+ # another matrix transformation to convert LMS coordinates to linear RGB coordinates.
9
+ #
10
+ # Key features:
11
+ # - Implements the official OKLAB inverse transformation algorithm with high-precision matrices
12
+ # - Converts OKLAB coordinates through intermediate LMS color space representation
13
+ # - Applies cubic transformation for perceptual uniformity in the OKLAB space
14
+ # - Maintains alpha channel transparency values during conversion
15
+ # - Uses AbcDecimal arithmetic for precise color science calculations
16
+ # - Validates input color space to ensure proper OKLAB source data
17
+ #
18
+ # The linear RGB color space provides a linear relationship between stored numeric values and
19
+ # actual light intensity, making it essential for accurate color calculations and serving as
20
+ # an intermediate color space for many color transformations, particularly when converting
21
+ # between different color models or preparing colors for display on standard monitors.
22
+
23
+ module Abachrome
24
+ module Converters
25
+ class OklabToLrgb < Abachrome::Converters::Base
26
+ # Converts a color from OKLAB color space to linear RGB (LRGB) color space.
27
+ #
28
+ # This method performs a two-step conversion:
29
+ # 1. OKLAB to LMS (cone response space)
30
+ # 2. LMS to LRGB (linear RGB)
31
+ #
32
+ # @param oklab_color [Abachrome::Color] The color in OKLAB color space
33
+ # @raise [ArgumentError] If the input color is not in OKLAB color space
34
+ # @return [Abachrome::Color] The resulting color in linear RGB color space with
35
+ # the same alpha as the input color
36
+ def self.convert(oklab_color)
37
+ raise_unless oklab_color, :oklab
38
+
39
+ l_ok, a_ok, b_ok = oklab_color.coordinates.map { |_| _.to_f }
40
+
41
+ # Step 1: OKLAB to L'M'S' (cone responses, non-linear)
42
+ # These are the M_lms_prime_from_oklab matrix operations.
43
+ l_prime = l_ok + (AD("0.39633779217376785678".to_f * a_ok) + (AD("0.21580375806075880339") * b_ok))
44
+ m_prime = l_ok - (a_ok * AD("0.1055613423236563494".to_f) - (b_ok * AD("0.063854174771705903402"))) # NOTE: original OklabToLms had + (b * AD("-0.063..."))
45
+ s_prime = l_ok - (a_ok * AD("0.089484182094965759684".to_f) - (b_ok * AD("1.2914855378640917399"))) # NOTE: original OklabToLms had + (b * AD("-1.291..."))
46
+
47
+ # Step 2: L'M'S' to LMS (cubing)
48
+ l_lms = l_prime**3
49
+ m_lms = m_prime**3
50
+ s_lms = s_prime**3
51
+
52
+ # Step 3: LMS to LRGB
53
+ # Using matrix M_lrgb_from_lms (OKLAB specific)
54
+ r_lrgb = (l_lms * AD("4.07674166134799")) + (m_lms * AD("-3.307711590408193")) + (s_lms * AD("0.230969928729428"))
55
+ g_lrgb = (l_lms * AD("-1.2684380040921763")) + (m_lms * AD("2.6097574006633715")) + (s_lms * AD("-0.3413193963102197"))
56
+ b_lrgb = (l_lms * AD("-0.004196086541837188")) + (m_lms * AD("-0.7034186144594493")) + (s_lms * AD("1.7076147009309444"))
57
+
58
+ # Clamp LRGB values to be non-negative (as done in LmsToLrgb.rb)
59
+ # It's also common to clamp to [0, 1] range after conversion from a wider gamut space
60
+ # For LRGB, often just ensuring non-negative is done, and further clamping happens
61
+ # when converting to sRGB or other display spaces.
62
+ # Here, we'll ensure non-negative as per LmsToLrgb.
63
+ output_coords = [r_lrgb, g_lrgb, b_lrgb].map { |it| [it, AD(0)].max }
64
+
65
+ Color.new(ColorSpace.find(:lrgb), output_coords, oklab_color.alpha)
66
+ end
67
+ end
68
+ end
69
+ end
70
+
71
+ # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Abachrome::Converters::OklabToOklch - OKLAB to OKLCH color space converter
4
+ #
5
+ # This converter transforms colors from the OKLAB color space to the OKLCH color space
6
+ # using cylindrical coordinate conversion. The transformation converts the rectangular
7
+ # coordinates (L, a, b) to cylindrical coordinates (L, C, h) where lightness remains
8
+ # unchanged, chroma is calculated as the Euclidean distance in the a-b plane, and hue
9
+ # is calculated as the angle in the a-b plane expressed in degrees.
10
+ #
11
+ # Key features:
12
+ # - Converts OKLAB rectangular coordinates to OKLCH cylindrical coordinates
13
+ # - Preserves lightness component unchanged during conversion
14
+ # - Calculates chroma as sqrt(a² + b²) for colorfulness representation
15
+ # - Computes hue angle using atan2 function and normalizes to 0-360 degree range
16
+ # - Maintains alpha channel transparency values during conversion
17
+ # - Uses AbcDecimal arithmetic for precise color science calculations
18
+ # - Validates input color space to ensure proper OKLAB source data
19
+ #
20
+ # The OKLCH color space provides an intuitive interface for color manipulation through
21
+ # its cylindrical coordinate system, making it ideal for hue adjustments, saturation
22
+ # modifications, and other color operations that benefit from polar coordinates.
23
+
24
+ module Abachrome
25
+ module Converters
26
+ class OklabToOklch < Abachrome::Converters::Base
27
+ # Converts a color from OKLAB color space to OKLCH color space.
28
+ # The method performs a mathematical transformation from the rectangular
29
+ # coordinates (L, a, b) to cylindrical coordinates (L, C, h), where:
30
+ # - L (lightness) remains the same
31
+ # - C (chroma) is calculated as the Euclidean distance from the origin in the a-b plane
32
+ # - h (hue) is calculated as the angle in the a-b plane
33
+ #
34
+ # @param oklab_color [Abachrome::Color] A color in the OKLAB color space
35
+ # @raise [ArgumentError] If the provided color is not in OKLAB color space
36
+ # @return [Abachrome::Color] The equivalent color in OKLCH color space with the same alpha value
37
+ def self.convert(oklab_color)
38
+ raise_unless oklab_color, :oklab
39
+
40
+ l, a, b = oklab_color.coordinates.map { |_| _.to_f }
41
+
42
+ c = ((a * a) + (b * b)).sqrt
43
+ h = (AbcDecimal.atan2(b, a) * 180) / Math::PI
44
+ h += 360 if h.negative?
45
+
46
+ Color.new(
47
+ ColorSpace.find(:oklch),
48
+ [l, c, h],
49
+ oklab_color.alpha
50
+ )
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Abachrome::Converters::OklabToSrgb - OKLAB to sRGB color space converter
4
+ #
5
+ # This converter transforms colors from the OKLAB color space to the standard RGB (sRGB) color space
6
+ # through a two-step conversion process. The transformation first converts OKLAB coordinates to
7
+ # linear RGB as an intermediate step, then applies gamma correction to produce the final sRGB
8
+ # values suitable for display on standard monitors and web applications.
9
+ #
10
+ # Key features:
11
+ # - Two-stage conversion pipeline: OKLAB → Linear RGB → sRGB
12
+ # - Leverages existing OklabToLrgb and LrgbToSrgb converters for modular transformation
13
+ # - Maintains alpha channel transparency values during conversion
14
+ # - Applies proper gamma correction for display-ready color values
15
+ # - Uses AbcDecimal arithmetic for precise color science calculations
16
+ # - Validates input color space to ensure proper OKLAB source data
17
+ #
18
+ # The sRGB color space is the standard RGB color space for web content and most consumer
19
+ # displays, providing gamma-corrected values that properly represent colors on typical
20
+ # display devices while maintaining compatibility with web standards and digital media formats.
21
+
22
+ module Abachrome
23
+ module Converters
24
+ class OklabToSrgb < Abachrome::Converters::Base
25
+ # Converts a color from the Oklab color space to the sRGB color space.
26
+ # This conversion is performed in two steps:
27
+ # 1. First converts from Oklab to linear RGB
28
+ # 2. Then converts from linear RGB to sRGB
29
+ #
30
+ # @param oklab_color [Color] A color in the Oklab color space
31
+ # @raise [ArgumentError] If the provided color is not in the Oklab color space
32
+ # @return [Color] The converted color in the sRGB color space
33
+ def self.convert(oklab_color)
34
+ raise_unless oklab_color, :oklab
35
+
36
+ # First convert Oklab to linear RGB
37
+ lrgb_color = OklabToLrgb.convert(oklab_color)
38
+
39
+ # Then use the LrgbToSrgb converter to go from linear RGB to sRGB
40
+ LrgbToSrgb.convert(lrgb_color)
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Abachrome::Converters::OklchToLrgb - OKLCH to Linear RGB color space converter
4
+ #
5
+ # This converter transforms colors from the OKLCH color space to the linear RGB (LRGB) color space.
6
+ # The conversion is performed by first transforming OKLCH's cylindrical coordinates (Lightness, Chroma, Hue)
7
+ # into OKLAB's rectangular coordinates (L, a, b).
8
+ # Then, these OKLAB coordinates are converted to LRGB. This second part involves transforming
9
+ # OKLAB to an intermediate non-linear cone response space (L'M'S'), then to a linear
10
+ # cone response space (LMS), and finally from LMS to LRGB using appropriate matrices.
11
+ # All these steps are combined into a single direct conversion method.
12
+ #
13
+ # Key features:
14
+ # - Direct conversion from OKLCH to LRGB.
15
+ # - Combines cylindrical to rectangular conversion (OKLCH to OKLAB)
16
+ # with the OKLAB to LRGB transformation pipeline (OKLAB -> L'M'S' -> LMS -> LRGB).
17
+ # - Uses AbcDecimal arithmetic for precise color science calculations.
18
+ # - Maintains alpha channel transparency values during conversion.
19
+ # - Validates input color space to ensure proper OKLCH source data.
20
+
21
+ module Abachrome
22
+ module Converters
23
+ class OklchToLrgb < Abachrome::Converters::Base
24
+ def self.convert(oklch_color)
25
+ raise_unless oklch_color, :oklch
26
+
27
+ l_oklch, c_oklch, h_oklch = oklch_color.coordinates.map { |_| _.to_f }
28
+ alpha = oklch_color.alpha
29
+
30
+ # Step 1: OKLCH to OKLAB
31
+ # l_oklab is the same as l_oklch
32
+ l_oklab = l_oklch
33
+
34
+ # Convert hue from degrees to radians
35
+ # h_oklch is AbcDecimal, Math::PI is Float. AD(Math::PI) makes it AbcDecimal.
36
+ # Division by AD("180") ensures AbcDecimal arithmetic.
37
+ h_rad = (h_oklch * AD(Math::PI)) / AD("180")
38
+
39
+ # Calculate a_oklab and b_oklab
40
+ # Math.cos/sin take a float; .value of AbcDecimal is BigDecimal.
41
+ # AD(Math.cos/sin(big_decimal_value)) wraps the result back to AbcDecimal.
42
+ a_oklab = c_oklch * AD(Math.cos(h_rad.value))
43
+ b_oklab = c_oklch * AD(Math.sin(h_rad.value))
44
+
45
+ # Step 2: OKLAB to L'M'S' (cone responses, non-linear)
46
+ # Constants from the inverse of M2 matrix (OKLAB to L'M'S')
47
+ # l_oklab, a_oklab, b_oklab are already AbcDecimal.
48
+ l_prime = l_oklab + (AD("0.39633779217376785678") * a_oklab) + (AD("0.21580375806075880339") * b_oklab)
49
+ m_prime = l_oklab - (AD("0.1055613423236563494") * a_oklab) - (AD("0.063854174771705903402") * b_oklab)
50
+ s_prime = l_oklab - (AD("0.089484182094965759684") * a_oklab) - (AD("1.2914855378640917399") * b_oklab)
51
+
52
+ # Step 3: L'M'S' to LMS (cubing to linearize cone responses)
53
+ l_lms = l_prime**3
54
+ m_lms = m_prime**3
55
+ s_lms = s_prime**3
56
+
57
+ # Step 4: LMS to LRGB
58
+ # Using matrix M_lrgb_from_lms (OKLAB specific)
59
+ r_lrgb = (l_lms * AD("4.07674166134799")) + (m_lms * AD("-3.307711590408193")) + (s_lms * AD("0.230969928729428"))
60
+ g_lrgb = (l_lms * AD("-1.2684380040921763")) + (m_lms * AD("2.6097574006633715")) + (s_lms * AD("-0.3413193963102197"))
61
+ b_lrgb = (l_lms * AD("-0.004196086541837188")) + (m_lms * AD("-0.7034186144594493")) + (s_lms * AD("1.7076147009309444"))
62
+
63
+ # Clamp LRGB values to be non-negative.
64
+ # LRGB values can be outside [0,1] but should be >= 0.
65
+ # Further clamping to [0,1] typically occurs when converting to display-referred spaces like sRGB.
66
+ zero_ad = AD("0")
67
+ output_coords = [
68
+ [r_lrgb, zero_ad].max,
69
+ [g_lrgb, zero_ad].max,
70
+ [b_lrgb, zero_ad].max
71
+ ]
72
+
73
+ Color.new(ColorSpace.find(:lrgb), output_coords, alpha)
74
+ end
75
+ end
76
+ end
77
+ end
78
+
79
+ # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Abachrome::Converters::OklchToOklab - OKLCH to OKLAB color space converter
4
+ #
5
+ # This converter transforms colors from the OKLCH color space to the OKLAB color space
6
+ # using cylindrical to rectangular coordinate conversion. The transformation converts the
7
+ # cylindrical coordinates (L, C, h) to rectangular coordinates (L, a, b) where lightness
8
+ # remains unchanged, and the a and b components are calculated from chroma and hue using
9
+ # trigonometric functions (cosine and sine respectively).
10
+ #
11
+ # Key features:
12
+ # - Converts OKLCH cylindrical coordinates to OKLAB rectangular coordinates
13
+ # - Preserves lightness component unchanged during conversion
14
+ # - Calculates a component as chroma × cos(hue) for green-red axis positioning
15
+ # - Calculates b component as chroma × sin(hue) for blue-yellow axis positioning
16
+ # - Converts hue angle from degrees to radians for trigonometric calculations
17
+ # - Maintains alpha channel transparency values during conversion
18
+ # - Uses AbcDecimal arithmetic for precise color science calculations
19
+ # - Validates input color space to ensure proper OKLCH source data
20
+ #
21
+ # The OKLAB color space provides the foundation for further conversions to other color
22
+ # spaces and serves as an intermediate step in the color transformation pipeline when
23
+ # working with OKLCH color manipulations that need to be converted to display-ready formats.
24
+
25
+ module Abachrome
26
+ module Converters
27
+ class OklchToOklab < Abachrome::Converters::Base
28
+ # Converts a color from OKLCH color space to OKLAB color space.
29
+ #
30
+ # @param oklch_color [Abachrome::Color] The color in OKLCH format to convert
31
+ # @return [Abachrome::Color] The converted color in OKLAB format
32
+ # @raise [StandardError] If the provided color is not in OKLCH color space
33
+ def self.convert(oklch_color)
34
+ raise_unless oklch_color, :oklch
35
+
36
+ l, c, h = oklch_color.coordinates.map { |_| _.to_f }
37
+
38
+ h_rad = (h * Math::PI) / AD(180)
39
+ a = c * AD(Math.cos(h_rad.value))
40
+ b = c * AD(Math.sin(h_rad.value))
41
+
42
+ Color.new(
43
+ ColorSpace.find(:oklab),
44
+ [l, a, b],
45
+ oklch_color.alpha
46
+ )
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Abachrome::Converters::OklchToSrgb - OKLCH to sRGB color space converter
4
+ #
5
+ # This converter transforms colors from the OKLCH color space to the standard RGB (sRGB) color space
6
+ # through a two-step conversion process. The transformation first converts OKLCH cylindrical
7
+ # coordinates to OKLAB rectangular coordinates, then applies the standard OKLAB to sRGB
8
+ # transformation pipeline to produce the final gamma-corrected sRGB values.
9
+ #
10
+ # Key features:
11
+ # - Two-stage conversion pipeline: OKLCH → OKLAB → sRGB
12
+ # - Leverages existing OklchToOklab and OklabToSrgb converters for modular transformation
13
+ # - Converts cylindrical coordinates (lightness, chroma, hue) to display-ready RGB values
14
+ # - Maintains alpha channel transparency values during conversion
15
+ # - Applies proper gamma correction for display on standard monitors and web applications
16
+ # - Uses AbcDecimal arithmetic for precise color science calculations
17
+ # - Validates input color space to ensure proper OKLCH source data
18
+ #
19
+ # The sRGB color space is the standard RGB color space for web content and most consumer
20
+ # displays, providing gamma-corrected values that properly represent colors on typical
21
+ # display devices while maintaining compatibility with web standards and digital media formats.
22
+
23
+ require_relative "oklch_to_oklab"
24
+ require_relative "oklab_to_srgb"
25
+
26
+ module Abachrome
27
+ module Converters
28
+ class OklchToSrgb < Abachrome::Converters::Base
29
+ # Converts a color from OKLCH color space to sRGB color space.
30
+ # This is done by first converting from OKLCH to OKLAB,
31
+ # then from OKLAB to sRGB.
32
+ #
33
+ # @param oklch_color [Abachrome::Color] Color in OKLCH color space
34
+ # @return [Abachrome::Color] The converted color in sRGB color space
35
+ def self.convert(oklch_color)
36
+ # Convert OKLCh to OKLab first
37
+ oklab_color = OklchToOklab.convert(oklch_color)
38
+
39
+ # Then convert OKLab to sRGB
40
+ OklabToSrgb.convert(oklab_color)
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abachrome
4
+ module Converters
5
+ class OklchToXyz < Abachrome::Converters::Base
6
+ def self.convert(oklch_color)
7
+ raise_unless oklch_color, :oklch
8
+
9
+ l_oklch, c_oklch, h_oklch = oklch_color.coordinates.map { |coord| coord.to_f }
10
+ alpha = oklch_color.alpha
11
+
12
+ # Step 1: OKLCH to OKLAB
13
+ # (l_oklab, a_oklab, b_oklab)
14
+ l_oklab = l_oklch
15
+ # h_rad = (h_oklch * Math::PI) / AD(180) # h_oklch is AbcDecimal, Math::PI is Float. Coercion happens.
16
+ # More explicit for Math::PI:
17
+ h_rad = (h_oklch * AD(Math::PI.to_s)) / AD(180)
18
+
19
+ # Standard Math.cos/sin expect float. h_rad is AbcDecimal.
20
+ # .to_f is needed for conversion from AbcDecimal/BigDecimal to Float.
21
+ cos_h_rad = AD(Math.cos(h_rad.to_f))
22
+ sin_h_rad = AD(Math.sin(h_rad.to_f))
23
+
24
+ a_oklab = c_oklch * cos_h_rad
25
+ b_oklab = c_oklch * sin_h_rad
26
+
27
+ # Step 2: OKLAB to L'M'S' (cone responses, non-linear)
28
+ # (l_prime, m_prime, s_prime)
29
+ # These are the M_lms_prime_from_oklab matrix operations.
30
+ # The .to_f wrapper on the whole sum (as in OklabToLms.rb) is not strictly necessary
31
+ # if l_oklab, a_oklab, b_oklab are already AbcDecimal, as AbcDecimal ops return AbcDecimal.
32
+ l_prime = l_oklab + (AD("0.39633779217376785678") * a_oklab) + (AD("0.21580375806075880339") * b_oklab)
33
+ m_prime = l_oklab - (a_oklab * AD("-0.1055613423236563494")) + (b_oklab * AD("-0.063854174771705903402"))
34
+ s_prime = l_oklab - (a_oklab * AD("-0.089484182094965759684")) + (b_oklab * AD("-1.2914855378640917399"))
35
+
36
+ # Step 3: L'M'S' to LMS (cubing)
37
+ # (l_lms, m_lms, s_lms)
38
+ l_lms = l_prime**3
39
+ m_lms = m_prime**3
40
+ s_lms = s_prime**3
41
+
42
+ # Step 4: LMS to LRGB
43
+ # (r_lrgb, g_lrgb, b_lrgb)
44
+ # Using matrix M_lrgb_from_lms (OKLAB specific)
45
+ r_lrgb = (l_lms * AD("4.07674166134799")) + (m_lms * AD("-3.307711590408193")) + (s_lms * AD("0.230969928729428"))
46
+ g_lrgb = (l_lms * AD("-1.2684380040921763")) + (m_lms * AD("2.6097574006633715")) + (s_lms * AD("-0.3413193963102197"))
47
+ b_lrgb = (l_lms * AD("-0.004196086541837188")) + (m_lms * AD("-0.7034186144594493")) + (s_lms * AD("1.7076147009309444"))
48
+
49
+ # Clamp LRGB values to be non-negative (as done in LmsToLrgb.rb)
50
+ # Using the pattern [AbcDecimal, Integer].max which relies on AbcDecimal's <=> coercion.
51
+ # AD(0) is AbcDecimal zero.
52
+ zero_ad = AD(0)
53
+ r_lrgb_clamped = [r_lrgb, zero_ad].max
54
+ g_lrgb_clamped = [g_lrgb, zero_ad].max
55
+ b_lrgb_clamped = [b_lrgb, zero_ad].max
56
+
57
+ # Step 5: LRGB to XYZ
58
+ # (x_xyz, y_xyz, z_xyz)
59
+ # Using matrix M_xyz_from_lrgb (sRGB D65)
60
+ x_xyz = (r_lrgb_clamped * AD("0.4124564")) + (g_lrgb_clamped * AD("0.3575761")) + (b_lrgb_clamped * AD("0.1804375"))
61
+ y_xyz = (r_lrgb_clamped * AD("0.2126729")) + (g_lrgb_clamped * AD("0.7151522")) + (b_lrgb_clamped * AD("0.0721750"))
62
+ z_xyz = (r_lrgb_clamped * AD("0.0193339")) + (g_lrgb_clamped * AD("0.1191920")) + (b_lrgb_clamped * AD("0.9503041"))
63
+
64
+ Color.new(ColorSpace.find(:xyz), [x_xyz, y_xyz, z_xyz], alpha)
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.