abachrome 0.1.0 → 0.1.2
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.
- checksums.yaml +4 -4
- data/.envrc +3 -0
- data/README.md +10 -0
- data/Rakefile +15 -0
- data/devenv.lock +88 -17
- data/devenv.nix +2 -1
- data/devenv.yaml +5 -12
- data/lib/abachrome/abc_decimal.rb +201 -7
- data/lib/abachrome/color.rb +88 -1
- data/lib/abachrome/color_mixins/blend.rb +53 -2
- data/lib/abachrome/color_mixins/lighten.rb +49 -2
- data/lib/abachrome/color_mixins/to_colorspace.rb +67 -2
- data/lib/abachrome/color_mixins/to_lrgb.rb +70 -2
- data/lib/abachrome/color_mixins/to_oklab.rb +67 -2
- data/lib/abachrome/color_mixins/to_oklch.rb +60 -2
- data/lib/abachrome/color_mixins/to_srgb.rb +77 -2
- data/lib/abachrome/color_models/hsv.rb +25 -2
- data/lib/abachrome/color_models/lms.rb +34 -0
- data/lib/abachrome/color_models/oklab.rb +19 -2
- data/lib/abachrome/color_models/oklch.rb +42 -2
- data/lib/abachrome/color_models/rgb.rb +28 -2
- data/lib/abachrome/color_models/xyz.rb +28 -0
- data/lib/abachrome/color_space.rb +88 -2
- data/lib/abachrome/converter.rb +56 -2
- data/lib/abachrome/converters/base.rb +69 -2
- data/lib/abachrome/converters/lms_to_lrgb.rb +36 -0
- data/lib/abachrome/converters/lms_to_srgb.rb +23 -0
- data/lib/abachrome/converters/lms_to_xyz.rb +30 -0
- data/lib/abachrome/converters/lrgb_to_lms.rb +0 -0
- data/lib/abachrome/converters/lrgb_to_oklab.rb +28 -2
- data/lib/abachrome/converters/lrgb_to_srgb.rb +27 -2
- data/lib/abachrome/converters/lrgb_to_xyz.rb +29 -0
- data/lib/abachrome/converters/oklab_to_lms.rb +41 -0
- data/lib/abachrome/converters/oklab_to_lrgb.rb +56 -29
- data/lib/abachrome/converters/oklab_to_oklch.rb +31 -2
- data/lib/abachrome/converters/oklab_to_srgb.rb +27 -2
- data/lib/abachrome/converters/oklch_to_lrgb.rb +66 -6
- data/lib/abachrome/converters/oklch_to_oklab.rb +29 -4
- data/lib/abachrome/converters/oklch_to_srgb.rb +26 -2
- data/lib/abachrome/converters/oklch_to_xyz.rb +66 -0
- data/lib/abachrome/converters/srgb_to_lrgb.rb +26 -2
- data/lib/abachrome/converters/srgb_to_oklab.rb +28 -2
- data/lib/abachrome/converters/srgb_to_oklch.rb +27 -2
- data/lib/abachrome/converters/xyz_to_lms.rb +30 -0
- data/lib/abachrome/converters/xyz_to_oklab.rb +38 -0
- data/lib/abachrome/gamut/base.rb +2 -2
- data/lib/abachrome/gamut/p3.rb +2 -2
- data/lib/abachrome/gamut/rec2020.rb +2 -2
- data/lib/abachrome/gamut/srgb.rb +20 -2
- data/lib/abachrome/illuminants/base.rb +2 -2
- data/lib/abachrome/illuminants/d50.rb +2 -2
- data/lib/abachrome/illuminants/d55.rb +2 -2
- data/lib/abachrome/illuminants/d65.rb +2 -2
- data/lib/abachrome/illuminants/d75.rb +2 -2
- data/lib/abachrome/named/css.rb +149 -158
- data/lib/abachrome/outputs/css.rb +2 -2
- data/lib/abachrome/palette.rb +112 -2
- data/lib/abachrome/palette_mixins/interpolate.rb +20 -2
- data/lib/abachrome/palette_mixins/resample.rb +2 -2
- data/lib/abachrome/palette_mixins/stretch_luminance.rb +2 -2
- data/lib/abachrome/parsers/hex.rb +2 -2
- data/lib/abachrome/to_abcd.rb +10 -2
- data/lib/abachrome/version.rb +2 -2
- data/lib/abachrome.rb +78 -2
- data/logo.png +0 -0
- data/logo.webp +0 -0
- metadata +26 -67
@@ -1,14 +1,74 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
# Abachrome::Converters::OklchToLrgb - OKLCH to Linear RGB color space converter
|
2
|
+
#
|
3
|
+
# This converter transforms colors from the OKLCH color space to the linear RGB (LRGB) color space.
|
4
|
+
# The conversion is performed by first transforming OKLCH's cylindrical coordinates (Lightness, Chroma, Hue)
|
5
|
+
# into OKLAB's rectangular coordinates (L, a, b).
|
6
|
+
# Then, these OKLAB coordinates are converted to LRGB. This second part involves transforming
|
7
|
+
# OKLAB to an intermediate non-linear cone response space (L'M'S'), then to a linear
|
8
|
+
# cone response space (LMS), and finally from LMS to LRGB using appropriate matrices.
|
9
|
+
# All these steps are combined into a single direct conversion method.
|
10
|
+
#
|
11
|
+
# Key features:
|
12
|
+
# - Direct conversion from OKLCH to LRGB.
|
13
|
+
# - Combines cylindrical to rectangular conversion (OKLCH to OKLAB)
|
14
|
+
# with the OKLAB to LRGB transformation pipeline (OKLAB -> L'M'S' -> LMS -> LRGB).
|
15
|
+
# - Uses AbcDecimal arithmetic for precise color science calculations.
|
16
|
+
# - Maintains alpha channel transparency values during conversion.
|
17
|
+
# - Validates input color space to ensure proper OKLCH source data.
|
5
18
|
|
6
19
|
module Abachrome
|
7
20
|
module Converters
|
8
21
|
class OklchToLrgb < Abachrome::Converters::Base
|
9
22
|
def self.convert(oklch_color)
|
10
|
-
|
11
|
-
|
23
|
+
raise_unless oklch_color, :oklch
|
24
|
+
|
25
|
+
l_oklch, c_oklch, h_oklch = oklch_color.coordinates.map { |_| AbcDecimal(_) }
|
26
|
+
alpha = oklch_color.alpha
|
27
|
+
|
28
|
+
# Step 1: OKLCH to OKLAB
|
29
|
+
# l_oklab is the same as l_oklch
|
30
|
+
l_oklab = l_oklch
|
31
|
+
|
32
|
+
# Convert hue from degrees to radians
|
33
|
+
# h_oklch is AbcDecimal, Math::PI is Float. AD(Math::PI) makes it AbcDecimal.
|
34
|
+
# Division by AD("180") ensures AbcDecimal arithmetic.
|
35
|
+
h_rad = (h_oklch * AD(Math::PI)) / AD("180")
|
36
|
+
|
37
|
+
# Calculate a_oklab and b_oklab
|
38
|
+
# Math.cos/sin take a float; .value of AbcDecimal is BigDecimal.
|
39
|
+
# AD(Math.cos/sin(big_decimal_value)) wraps the result back to AbcDecimal.
|
40
|
+
a_oklab = c_oklch * AD(Math.cos(h_rad.value))
|
41
|
+
b_oklab = c_oklch * AD(Math.sin(h_rad.value))
|
42
|
+
|
43
|
+
# Step 2: OKLAB to L'M'S' (cone responses, non-linear)
|
44
|
+
# Constants from the inverse of M2 matrix (OKLAB to L'M'S')
|
45
|
+
# l_oklab, a_oklab, b_oklab are already AbcDecimal.
|
46
|
+
l_prime = l_oklab + (AD("0.39633779217376785678") * a_oklab) + (AD("0.21580375806075880339") * b_oklab)
|
47
|
+
m_prime = l_oklab - (AD("0.1055613423236563494") * a_oklab) - (AD("0.063854174771705903402") * b_oklab)
|
48
|
+
s_prime = l_oklab - (AD("0.089484182094965759684") * a_oklab) - (AD("1.2914855378640917399") * b_oklab)
|
49
|
+
|
50
|
+
# Step 3: L'M'S' to LMS (cubing to linearize cone responses)
|
51
|
+
l_lms = l_prime**3
|
52
|
+
m_lms = m_prime**3
|
53
|
+
s_lms = s_prime**3
|
54
|
+
|
55
|
+
# Step 4: LMS to LRGB
|
56
|
+
# Using matrix M_lrgb_from_lms (OKLAB specific)
|
57
|
+
r_lrgb = (l_lms * AD("4.07674166134799")) + (m_lms * AD("-3.307711590408193")) + (s_lms * AD("0.230969928729428"))
|
58
|
+
g_lrgb = (l_lms * AD("-1.2684380040921763")) + (m_lms * AD("2.6097574006633715")) + (s_lms * AD("-0.3413193963102197"))
|
59
|
+
b_lrgb = (l_lms * AD("-0.004196086541837188"))+ (m_lms * AD("-0.7034186144594493")) + (s_lms * AD("1.7076147009309444"))
|
60
|
+
|
61
|
+
# Clamp LRGB values to be non-negative.
|
62
|
+
# LRGB values can be outside [0,1] but should be >= 0.
|
63
|
+
# Further clamping to [0,1] typically occurs when converting to display-referred spaces like sRGB.
|
64
|
+
zero_ad = AD("0")
|
65
|
+
output_coords = [
|
66
|
+
[r_lrgb, zero_ad].max,
|
67
|
+
[g_lrgb, zero_ad].max,
|
68
|
+
[b_lrgb, zero_ad].max
|
69
|
+
]
|
70
|
+
|
71
|
+
Color.new(ColorSpace.find(:lrgb), output_coords, alpha)
|
12
72
|
end
|
13
73
|
end
|
14
74
|
end
|
@@ -1,16 +1,41 @@
|
|
1
|
-
#
|
1
|
+
# Abachrome::Converters::OklchToOklab - OKLCH to OKLAB color space converter
|
2
|
+
#
|
3
|
+
# This converter transforms colors from the OKLCH color space to the OKLAB color space
|
4
|
+
# using cylindrical to rectangular coordinate conversion. The transformation converts the
|
5
|
+
# cylindrical coordinates (L, C, h) to rectangular coordinates (L, a, b) where lightness
|
6
|
+
# remains unchanged, and the a and b components are calculated from chroma and hue using
|
7
|
+
# trigonometric functions (cosine and sine respectively).
|
8
|
+
#
|
9
|
+
# Key features:
|
10
|
+
# - Converts OKLCH cylindrical coordinates to OKLAB rectangular coordinates
|
11
|
+
# - Preserves lightness component unchanged during conversion
|
12
|
+
# - Calculates a component as chroma × cos(hue) for green-red axis positioning
|
13
|
+
# - Calculates b component as chroma × sin(hue) for blue-yellow axis positioning
|
14
|
+
# - Converts hue angle from degrees to radians for trigonometric calculations
|
15
|
+
# - Maintains alpha channel transparency values during conversion
|
16
|
+
# - Uses AbcDecimal arithmetic for precise color science calculations
|
17
|
+
# - Validates input color space to ensure proper OKLCH source data
|
18
|
+
#
|
19
|
+
# The OKLAB color space provides the foundation for further conversions to other color
|
20
|
+
# spaces and serves as an intermediate step in the color transformation pipeline when
|
21
|
+
# working with OKLCH color manipulations that need to be converted to display-ready formats.
|
2
22
|
|
3
23
|
module Abachrome
|
4
24
|
module Converters
|
5
25
|
class OklchToOklab < Abachrome::Converters::Base
|
26
|
+
# Converts a color from OKLCH color space to OKLAB color space.
|
27
|
+
#
|
28
|
+
# @param oklch_color [Abachrome::Color] The color in OKLCH format to convert
|
29
|
+
# @return [Abachrome::Color] The converted color in OKLAB format
|
30
|
+
# @raise [StandardError] If the provided color is not in OKLCH color space
|
6
31
|
def self.convert(oklch_color)
|
7
32
|
raise_unless oklch_color, :oklch
|
8
33
|
|
9
34
|
l, c, h = oklch_color.coordinates.map { |_| AbcDecimal(_) }
|
10
35
|
|
11
|
-
h_rad = h *
|
12
|
-
a = c * Math.cos(h_rad.value)
|
13
|
-
b = c * Math.sin(h_rad.value)
|
36
|
+
h_rad = (h * Math::PI)/ AD(180)
|
37
|
+
a = c * AD(Math.cos(h_rad.value))
|
38
|
+
b = c * AD(Math.sin(h_rad.value))
|
14
39
|
|
15
40
|
Color.new(
|
16
41
|
ColorSpace.find(:oklab),
|
@@ -1,4 +1,22 @@
|
|
1
|
-
#
|
1
|
+
# Abachrome::Converters::OklchToSrgb - OKLCH to sRGB color space converter
|
2
|
+
#
|
3
|
+
# This converter transforms colors from the OKLCH color space to the standard RGB (sRGB) color space
|
4
|
+
# through a two-step conversion process. The transformation first converts OKLCH cylindrical
|
5
|
+
# coordinates to OKLAB rectangular coordinates, then applies the standard OKLAB to sRGB
|
6
|
+
# transformation pipeline to produce the final gamma-corrected sRGB values.
|
7
|
+
#
|
8
|
+
# Key features:
|
9
|
+
# - Two-stage conversion pipeline: OKLCH → OKLAB → sRGB
|
10
|
+
# - Leverages existing OklchToOklab and OklabToSrgb converters for modular transformation
|
11
|
+
# - Converts cylindrical coordinates (lightness, chroma, hue) to display-ready RGB values
|
12
|
+
# - Maintains alpha channel transparency values during conversion
|
13
|
+
# - Applies proper gamma correction for display on standard monitors and web applications
|
14
|
+
# - Uses AbcDecimal arithmetic for precise color science calculations
|
15
|
+
# - Validates input color space to ensure proper OKLCH source data
|
16
|
+
#
|
17
|
+
# The sRGB color space is the standard RGB color space for web content and most consumer
|
18
|
+
# displays, providing gamma-corrected values that properly represent colors on typical
|
19
|
+
# display devices while maintaining compatibility with web standards and digital media formats.
|
2
20
|
|
3
21
|
require_relative "oklch_to_oklab"
|
4
22
|
require_relative "oklab_to_srgb"
|
@@ -6,6 +24,12 @@ require_relative "oklab_to_srgb"
|
|
6
24
|
module Abachrome
|
7
25
|
module Converters
|
8
26
|
class OklchToSrgb < Abachrome::Converters::Base
|
27
|
+
# Converts a color from OKLCH color space to sRGB color space.
|
28
|
+
# This is done by first converting from OKLCH to OKLAB,
|
29
|
+
# then from OKLAB to sRGB.
|
30
|
+
#
|
31
|
+
# @param oklch_color [Abachrome::Color] Color in OKLCH color space
|
32
|
+
# @return [Abachrome::Color] The converted color in sRGB color space
|
9
33
|
def self.convert(oklch_color)
|
10
34
|
# Convert OKLCh to OKLab first
|
11
35
|
oklab_color = OklchToOklab.convert(oklch_color)
|
@@ -15,4 +39,4 @@ module Abachrome
|
|
15
39
|
end
|
16
40
|
end
|
17
41
|
end
|
18
|
-
end
|
42
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Abachrome
|
2
|
+
module Converters
|
3
|
+
class OklchToXyz < Abachrome::Converters::Base
|
4
|
+
def self.convert(oklch_color)
|
5
|
+
raise_unless oklch_color, :oklch
|
6
|
+
|
7
|
+
l_oklch, c_oklch, h_oklch = oklch_color.coordinates.map { |coord| AbcDecimal(coord) }
|
8
|
+
alpha = oklch_color.alpha
|
9
|
+
|
10
|
+
# Step 1: OKLCH to OKLAB
|
11
|
+
# (l_oklab, a_oklab, b_oklab)
|
12
|
+
l_oklab = l_oklch
|
13
|
+
# h_rad = (h_oklch * Math::PI) / AD(180) # h_oklch is AbcDecimal, Math::PI is Float. Coercion happens.
|
14
|
+
# More explicit for Math::PI:
|
15
|
+
h_rad = (h_oklch * AD(Math::PI.to_s)) / AD(180)
|
16
|
+
|
17
|
+
# Standard Math.cos/sin expect float. h_rad is AbcDecimal.
|
18
|
+
# .to_f is needed for conversion from AbcDecimal/BigDecimal to Float.
|
19
|
+
cos_h_rad = AD(Math.cos(h_rad.to_f))
|
20
|
+
sin_h_rad = AD(Math.sin(h_rad.to_f))
|
21
|
+
|
22
|
+
a_oklab = c_oklch * cos_h_rad
|
23
|
+
b_oklab = c_oklch * sin_h_rad
|
24
|
+
|
25
|
+
# Step 2: OKLAB to L'M'S' (cone responses, non-linear)
|
26
|
+
# (l_prime, m_prime, s_prime)
|
27
|
+
# These are the M_lms_prime_from_oklab matrix operations.
|
28
|
+
# The AbcDecimal() wrapper on the whole sum (as in OklabToLms.rb) is not strictly necessary
|
29
|
+
# if l_oklab, a_oklab, b_oklab are already AbcDecimal, as AbcDecimal ops return AbcDecimal.
|
30
|
+
l_prime = l_oklab + (AD("0.39633779217376785678") * a_oklab) + (AD("0.21580375806075880339") * b_oklab)
|
31
|
+
m_prime = l_oklab - (a_oklab * AD("-0.1055613423236563494")) + (b_oklab * AD("-0.063854174771705903402"))
|
32
|
+
s_prime = l_oklab - (a_oklab * AD("-0.089484182094965759684")) + (b_oklab * AD("-1.2914855378640917399"))
|
33
|
+
|
34
|
+
# Step 3: L'M'S' to LMS (cubing)
|
35
|
+
# (l_lms, m_lms, s_lms)
|
36
|
+
l_lms = l_prime**3
|
37
|
+
m_lms = m_prime**3
|
38
|
+
s_lms = s_prime**3
|
39
|
+
|
40
|
+
# Step 4: LMS to LRGB
|
41
|
+
# (r_lrgb, g_lrgb, b_lrgb)
|
42
|
+
# Using matrix M_lrgb_from_lms (OKLAB specific)
|
43
|
+
r_lrgb = (l_lms * AD("4.07674166134799")) + (m_lms * AD("-3.307711590408193")) + (s_lms * AD("0.230969928729428"))
|
44
|
+
g_lrgb = (l_lms * AD("-1.2684380040921763")) + (m_lms * AD("2.6097574006633715")) + (s_lms * AD("-0.3413193963102197"))
|
45
|
+
b_lrgb = (l_lms * AD("-0.004196086541837188"))+ (m_lms * AD("-0.7034186144594493")) + (s_lms * AD("1.7076147009309444"))
|
46
|
+
|
47
|
+
# Clamp LRGB values to be non-negative (as done in LmsToLrgb.rb)
|
48
|
+
# Using the pattern [AbcDecimal, Integer].max which relies on AbcDecimal's <=> coercion.
|
49
|
+
# AD(0) is AbcDecimal zero.
|
50
|
+
zero_ad = AD(0)
|
51
|
+
r_lrgb_clamped = [r_lrgb, zero_ad].max
|
52
|
+
g_lrgb_clamped = [g_lrgb, zero_ad].max
|
53
|
+
b_lrgb_clamped = [b_lrgb, zero_ad].max
|
54
|
+
|
55
|
+
# Step 5: LRGB to XYZ
|
56
|
+
# (x_xyz, y_xyz, z_xyz)
|
57
|
+
# Using matrix M_xyz_from_lrgb (sRGB D65)
|
58
|
+
x_xyz = (r_lrgb_clamped * AD("0.4124564")) + (g_lrgb_clamped * AD("0.3575761")) + (b_lrgb_clamped * AD("0.1804375"))
|
59
|
+
y_xyz = (r_lrgb_clamped * AD("0.2126729")) + (g_lrgb_clamped * AD("0.7151522")) + (b_lrgb_clamped * AD("0.0721750"))
|
60
|
+
z_xyz = (r_lrgb_clamped * AD("0.0193339")) + (g_lrgb_clamped * AD("0.1191920")) + (b_lrgb_clamped * AD("0.9503041"))
|
61
|
+
|
62
|
+
Color.new(ColorSpace.find(:xyz), [x_xyz, y_xyz, z_xyz], alpha)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -1,8 +1,26 @@
|
|
1
|
-
#
|
1
|
+
# Abachrome::Converters::SrgbToLrgb - sRGB to Linear RGB color space converter
|
2
|
+
#
|
3
|
+
# This converter transforms colors from the standard RGB (sRGB) color space to the linear RGB (LRGB) color space by removing gamma correction. The conversion process applies the inverse sRGB transfer function which uses different formulas for small and large values to convert from the gamma-corrected sRGB representation to linear light intensity values.
|
4
|
+
#
|
5
|
+
# Key features:
|
6
|
+
# - Implements the standard sRGB to linear RGB conversion algorithm with precise threshold handling
|
7
|
+
# - Converts gamma-corrected sRGB values to linear RGB values for accurate color calculations
|
8
|
+
# - Applies different transformation functions based on value magnitude (linear vs power function)
|
9
|
+
# - Maintains alpha channel transparency values during conversion
|
10
|
+
# - Uses AbcDecimal arithmetic for precise color science calculations
|
11
|
+
# - Validates input color space to ensure proper sRGB source data
|
12
|
+
#
|
13
|
+
# The linear RGB color space provides a linear relationship between stored numeric values and actual light intensity, making it essential for accurate color calculations and serving as an intermediate color space for many color transformations, particularly when converting between different color models.
|
2
14
|
|
3
15
|
module Abachrome
|
4
16
|
module Converters
|
5
17
|
class SrgbToLrgb
|
18
|
+
# Converts a color from sRGB color space to linear RGB color space.
|
19
|
+
# This method performs gamma correction by linearizing each sRGB coordinate.
|
20
|
+
#
|
21
|
+
# @param srgb_color [Abachrome::Color] A color object in the sRGB color space
|
22
|
+
# @return [Abachrome::Color] A new color object in the linear RGB (LRGB) color space
|
23
|
+
# with the same alpha value as the input color
|
6
24
|
def self.convert(srgb_color)
|
7
25
|
r, g, b = srgb_color.coordinates.map { |c| to_linear(AbcDecimal(c)) }
|
8
26
|
|
@@ -13,6 +31,12 @@ module Abachrome
|
|
13
31
|
)
|
14
32
|
end
|
15
33
|
|
34
|
+
# Converts a sRGB component to its linear RGB equivalent.
|
35
|
+
# This conversion applies the appropriate gamma correction to transform an sRGB value
|
36
|
+
# into a linear RGB value.
|
37
|
+
#
|
38
|
+
# @param v [AbcDecimal, Numeric] The sRGB component value to convert (typically in range 0-1)
|
39
|
+
# @return [AbcDecimal] The corresponding linear RGB component value
|
16
40
|
def self.to_linear(v)
|
17
41
|
v_abs = v.abs
|
18
42
|
v_sign = v.negative? ? -1 : 1
|
@@ -24,4 +48,4 @@ module Abachrome
|
|
24
48
|
end
|
25
49
|
end
|
26
50
|
end
|
27
|
-
end
|
51
|
+
end
|
@@ -1,8 +1,34 @@
|
|
1
|
-
#
|
1
|
+
# Abachrome::Converters::SrgbToOklab - sRGB to OKLAB color space converter
|
2
|
+
#
|
3
|
+
# This converter transforms colors from the standard RGB (sRGB) color space to the OKLAB color space
|
4
|
+
# through a two-step conversion process. The transformation first converts sRGB gamma-corrected
|
5
|
+
# values to linear RGB as an intermediate step, then applies the standard OKLAB transformation
|
6
|
+
# matrices to produce the final perceptually uniform OKLAB coordinates.
|
7
|
+
#
|
8
|
+
# Key features:
|
9
|
+
# - Two-stage conversion pipeline: sRGB → Linear RGB → OKLAB
|
10
|
+
# - Leverages existing SrgbToLrgb and LrgbToOklab converters for modular transformation
|
11
|
+
# - Removes gamma correction and applies perceptual uniformity transformations
|
12
|
+
# - Maintains alpha channel transparency values during conversion
|
13
|
+
# - Uses AbcDecimal arithmetic for precise color science calculations
|
14
|
+
# - Validates input color space to ensure proper sRGB source data
|
15
|
+
#
|
16
|
+
# The OKLAB color space provides better perceptual uniformity compared to traditional RGB spaces,
|
17
|
+
# making it ideal for color manipulation operations like blending, lightness adjustments, and
|
18
|
+
# gamut mapping where human visual perception accuracy is important. This converter enables
|
19
|
+
# seamless transformation from display-ready sRGB values to the scientifically accurate OKLAB
|
20
|
+
# representation for advanced color processing workflows.
|
2
21
|
|
3
22
|
module Abachrome
|
4
23
|
module Converters
|
5
24
|
class SrgbToOklab
|
25
|
+
# Converts a color from sRGB color space to Oklab color space.
|
26
|
+
# The conversion happens in two steps:
|
27
|
+
# 1. sRGB is first converted to linear RGB
|
28
|
+
# 2. Linear RGB is then converted to Oklab
|
29
|
+
#
|
30
|
+
# @param srgb_color [Abachrome::Color] The color in sRGB color space to convert
|
31
|
+
# @return [Abachrome::Color] The converted color in Oklab color space
|
6
32
|
def self.convert(srgb_color)
|
7
33
|
# First convert sRGB to linear RGB
|
8
34
|
lrgb_color = SrgbToLrgb.convert(srgb_color)
|
@@ -12,4 +38,4 @@ module Abachrome
|
|
12
38
|
end
|
13
39
|
end
|
14
40
|
end
|
15
|
-
end
|
41
|
+
end
|
@@ -1,4 +1,22 @@
|
|
1
|
-
#
|
1
|
+
# Abachrome::Converters::SrgbToOklch - sRGB to OKLCH color space converter
|
2
|
+
#
|
3
|
+
# This converter transforms colors from the standard RGB (sRGB) color space to the OKLCH color space
|
4
|
+
# through a two-step conversion process. The transformation first converts sRGB gamma-corrected
|
5
|
+
# values to OKLAB rectangular coordinates as an intermediate step, then applies cylindrical coordinate
|
6
|
+
# conversion to produce the final OKLCH values with lightness, chroma, and hue components.
|
7
|
+
#
|
8
|
+
# Key features:
|
9
|
+
# - Two-stage conversion pipeline: sRGB → OKLAB → OKLCH
|
10
|
+
# - Leverages existing SrgbToOklab and OklabToOklch converters for modular transformation
|
11
|
+
# - Converts display-ready RGB values to perceptually uniform cylindrical coordinates
|
12
|
+
# - Maintains alpha channel transparency values during conversion
|
13
|
+
# - Uses AbcDecimal arithmetic for precise color science calculations
|
14
|
+
# - Validates input color space to ensure proper sRGB source data
|
15
|
+
#
|
16
|
+
# The OKLCH color space provides an intuitive interface for color manipulation through its
|
17
|
+
# cylindrical coordinate system, making it ideal for hue adjustments, saturation modifications,
|
18
|
+
# and other color operations that benefit from polar coordinates while maintaining perceptual
|
19
|
+
# uniformity for natural-looking color transformations.
|
2
20
|
|
3
21
|
require_relative "srgb_to_oklab"
|
4
22
|
require_relative "oklab_to_oklch"
|
@@ -6,6 +24,13 @@ require_relative "oklab_to_oklch"
|
|
6
24
|
module Abachrome
|
7
25
|
module Converters
|
8
26
|
class SrgbToOklch < Abachrome::Converters::Base
|
27
|
+
# Converts an sRGB color to OKLCH color space
|
28
|
+
#
|
29
|
+
# @param srgb_color [Abachrome::Color] The color in sRGB color space to convert
|
30
|
+
# @return [Abachrome::Color] The converted color in OKLCH color space
|
31
|
+
# @note This is a two-step conversion process: first from sRGB to OKLab, then from OKLab to OKLCH
|
32
|
+
# @see SrgbToOklab
|
33
|
+
# @see OklabToOklch
|
9
34
|
def self.convert(srgb_color)
|
10
35
|
# First convert sRGB to OKLab
|
11
36
|
oklab_color = SrgbToOklab.convert(srgb_color)
|
@@ -15,4 +40,4 @@ module Abachrome
|
|
15
40
|
end
|
16
41
|
end
|
17
42
|
end
|
18
|
-
end
|
43
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Abachrome
|
2
|
+
module Converters
|
3
|
+
class XyzToLms < Abachrome::Converters::Base
|
4
|
+
# Converts a color from XYZ color space to LMS color space.
|
5
|
+
#
|
6
|
+
# This method implements the XYZ to LMS transformation using the standard
|
7
|
+
# transformation matrix. The LMS color space represents the response of
|
8
|
+
# the three types of cone cells in the human eye (Long, Medium, Short),
|
9
|
+
# while XYZ is the CIE 1931 color space that forms the basis for most
|
10
|
+
# other color space definitions.
|
11
|
+
#
|
12
|
+
# @param xyz_color [Abachrome::Color] The color in XYZ color space
|
13
|
+
# @raise [ArgumentError] If the input color is not in XYZ 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(xyz_color)
|
17
|
+
raise_unless xyz_color, :xyz
|
18
|
+
|
19
|
+
x, y, z = xyz_color.coordinates.map { |_| AbcDecimal(_) }
|
20
|
+
|
21
|
+
# XYZ to LMS transformation matrix
|
22
|
+
l = (x * AD("0.8189330101")) + (y * AD("0.3618667424")) - (z * AD("0.1288597137"))
|
23
|
+
m = (x * AD("0.0329845436")) + (y * AD("0.9293118715")) + (z * AD("0.0361456387"))
|
24
|
+
s = (x * AD("0.0482003018")) + (y * AD("0.2643662691")) + (z * AD("0.6338517070"))
|
25
|
+
|
26
|
+
Color.new(ColorSpace.find(:lms), [l, m, s], xyz_color.alpha)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Abachrome
|
2
|
+
module Converters
|
3
|
+
class XyzToOklab < Abachrome::Converters::Base
|
4
|
+
# Converts a color from XYZ color space to OKLAB color space.
|
5
|
+
#
|
6
|
+
# This method implements the XYZ to OKLAB transformation by first
|
7
|
+
# converting XYZ coordinates to the intermediate LMS (Long, Medium, Short)
|
8
|
+
# color space, then applying the LMS to OKLAB transformation matrix.
|
9
|
+
#
|
10
|
+
# @param xyz_color [Abachrome::Color] The color in XYZ color space
|
11
|
+
# @raise [ArgumentError] If the input color is not in XYZ color space
|
12
|
+
# @return [Abachrome::Color] The resulting color in OKLAB color space with
|
13
|
+
# the same alpha as the input color
|
14
|
+
def self.convert(xyz_color)
|
15
|
+
raise_unless xyz_color, :xyz
|
16
|
+
|
17
|
+
x, y, z = xyz_color.coordinates.map { |_| AbcDecimal(_) }
|
18
|
+
|
19
|
+
# XYZ to LMS transformation matrix
|
20
|
+
l = (x * AD("0.8189330101")) + (y * AD("0.3618667424")) - (z * AD("0.1288597137"))
|
21
|
+
m = (x * AD("0.0329845436")) + (y * AD("0.9293118715")) + (z * AD("0.0361456387"))
|
22
|
+
s = (x * AD("0.0482003018")) + (y * AD("0.2643662691")) + (z * AD("0.6338517070"))
|
23
|
+
|
24
|
+
# Apply cube root transformation
|
25
|
+
l_ = AbcDecimal(l)**Rational(1, 3)
|
26
|
+
m_ = AbcDecimal(m)**Rational(1, 3)
|
27
|
+
s_ = AbcDecimal(s)**Rational(1, 3)
|
28
|
+
|
29
|
+
# LMS to OKLAB transformation matrix
|
30
|
+
lightness = (AD("0.2104542553") * l_) + (AD("0.793617785") * m_) - (AD("0.0040720468") * s_)
|
31
|
+
a = (AD("1.9779984951") * l_) - (AD("2.4285922050") * m_) + (AD("0.4505937099") * s_)
|
32
|
+
b = (AD("0.0259040371") * l_) + (AD("0.7827717662") * m_) - (AD("0.8086757660") * s_)
|
33
|
+
|
34
|
+
Color.new(ColorSpace.find(:oklab), [lightness, a, b], xyz_color.alpha)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/abachrome/gamut/base.rb
CHANGED
data/lib/abachrome/gamut/p3.rb
CHANGED
data/lib/abachrome/gamut/srgb.rb
CHANGED
@@ -1,4 +1,22 @@
|
|
1
|
-
#
|
1
|
+
# Abachrome::Gamut::SRGB - sRGB color gamut definition and validation
|
2
|
+
#
|
3
|
+
# This module defines the sRGB color gamut within the Abachrome color manipulation library.
|
4
|
+
# The sRGB gamut represents the range of colors that can be displayed on standard monitors
|
5
|
+
# and is the default color space for web content and most consumer displays. It uses the
|
6
|
+
# D65 white point and specific primary color coordinates that define the boundaries of
|
7
|
+
# reproducible colors in the sRGB color space.
|
8
|
+
#
|
9
|
+
# Key features:
|
10
|
+
# - Defines sRGB primary color coordinates for red, green, and blue
|
11
|
+
# - Uses D65 illuminant as the white point reference for proper color reproduction
|
12
|
+
# - Provides gamut boundary validation to ensure colors fall within displayable ranges
|
13
|
+
# - Implements color containment checking for RGB coordinate validation
|
14
|
+
# - Automatically registers with the global gamut registry for system-wide access
|
15
|
+
# - Serves as the foundation for sRGB color space conversions and display output
|
16
|
+
#
|
17
|
+
# The sRGB gamut is essential for ensuring colors are properly represented on standard
|
18
|
+
# displays and provides the color boundary definitions needed for gamut mapping operations
|
19
|
+
# when converting between different color spaces or preparing colors for web and print output.
|
2
20
|
|
3
21
|
require_relative "base"
|
4
22
|
|
@@ -24,4 +42,4 @@ module Abachrome
|
|
24
42
|
|
25
43
|
register(SRGB.new)
|
26
44
|
end
|
27
|
-
end
|
45
|
+
end
|