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,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Abachrome::Converters::SrgbToCmyk - sRGB to CMYK color space converter
4
+ #
5
+ # This converter transforms colors from the standard RGB (sRGB) color space to the CMYK
6
+ # (Cyan, Magenta, Yellow, Key/Black) color space using Gray Component Replacement (GCR)
7
+ # for optimal ink usage in print production.
8
+ #
9
+ # Key features:
10
+ # - Implements GCR/UCR for efficient ink usage and better print quality
11
+ # - Converts from additive (light) to subtractive (ink) color model
12
+ # - Configurable GCR amount for different printing requirements
13
+ # - Uses BigDecimal for precise ink percentage calculations
14
+ # - Maintains alpha channel transparency values during conversion
15
+ # - Produces professional-quality CMYK separations
16
+ #
17
+ # The conversion uses Gray Component Replacement by default, which extracts the neutral
18
+ # gray component from CMY inks and assigns it to the cheaper and more stable Black (K) ink.
19
+ # This reduces Total Area Coverage (TAC) and improves print quality.
20
+
21
+ module Abachrome
22
+ module Converters
23
+ class SrgbToCmyk
24
+ # Converts a color from sRGB color space to CMYK color space.
25
+ # Uses full GCR (Gray Component Replacement) by default for optimal ink usage.
26
+ #
27
+ # @param srgb_color [Abachrome::Color] A color object in the sRGB color space
28
+ # @param gcr_amount [Numeric] The amount of GCR to apply (0.0 to 1.0), default 1.0 for full GCR
29
+ # @return [Abachrome::Color] A new color object in the CMYK color space
30
+ # with the same alpha value as the input color
31
+ def self.convert(srgb_color, gcr_amount: 1.0)
32
+ r, g, b = srgb_color.coordinates.map { |c| c.to_f }
33
+
34
+ # Use GCR conversion from the CMYK color model
35
+ c, m, y, k = ColorModels::CMYK.from_rgb_gcr(r, g, b, gcr_amount)
36
+
37
+ Color.new(
38
+ ColorSpace.find(:cmyk),
39
+ [c, m, y, k],
40
+ srgb_color.alpha
41
+ )
42
+ end
43
+
44
+ # Converts a color from sRGB to CMYK using the naive method (no UCR/GCR).
45
+ # This produces higher ink coverage but simpler conversion.
46
+ # Generally not recommended for production printing.
47
+ #
48
+ # @param srgb_color [Abachrome::Color] A color object in the sRGB color space
49
+ # @return [Abachrome::Color] A new color object in the CMYK color space
50
+ def self.convert_naive(srgb_color)
51
+ r, g, b = srgb_color.coordinates.map { |c| c.to_f }
52
+
53
+ # Use naive conversion from the CMYK color model
54
+ c, m, y, k = ColorModels::CMYK.from_rgb_naive(r, g, b)
55
+
56
+ Color.new(
57
+ ColorSpace.find(:cmyk),
58
+ [c, m, y, k],
59
+ srgb_color.alpha
60
+ )
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Abachrome::Converters::SrgbToLrgb - sRGB to Linear RGB color space converter
4
+ #
5
+ # 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.
6
+ #
7
+ # Key features:
8
+ # - Implements the standard sRGB to linear RGB conversion algorithm with precise threshold handling
9
+ # - Converts gamma-corrected sRGB values to linear RGB values for accurate color calculations
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 sRGB source data
14
+ #
15
+ # 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.
16
+
17
+ module Abachrome
18
+ module Converters
19
+ class SrgbToLrgb
20
+ # Converts a color from sRGB color space to linear RGB color space.
21
+ # This method performs gamma correction by linearizing each sRGB coordinate.
22
+ #
23
+ # @param srgb_color [Abachrome::Color] A color object in the sRGB color space
24
+ # @return [Abachrome::Color] A new color object in the linear RGB (LRGB) color space
25
+ # with the same alpha value as the input color
26
+ def self.convert(srgb_color)
27
+ r, g, b = srgb_color.coordinates.map { |c| to_linear(c.to_f) }
28
+
29
+ Color.new(
30
+ ColorSpace.find(:lrgb),
31
+ [r, g, b],
32
+ srgb_color.alpha
33
+ )
34
+ end
35
+
36
+ # Converts a sRGB component to its linear RGB equivalent.
37
+ # This conversion applies the appropriate gamma correction to transform an sRGB value
38
+ # into a linear RGB value.
39
+ #
40
+ # @param v [AbcDecimal, Numeric] The sRGB component value to convert (typically in range 0-1)
41
+ # @return [AbcDecimal] The corresponding linear RGB component value
42
+ def self.to_linear(v)
43
+ v_abs = v.abs
44
+ v_sign = v.negative? ? -1 : 1
45
+ if v_abs <= AD("0.04045")
46
+ v / AD("12.92")
47
+ else
48
+ v_sign * (((v_abs + AD("0.055")) / AD("1.055"))**AD("2.4"))
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Abachrome::Converters::SrgbToOklab - sRGB to OKLAB color space converter
4
+ #
5
+ # This converter transforms colors from the standard RGB (sRGB) color space to the OKLAB color space
6
+ # through a two-step conversion process. The transformation first converts sRGB gamma-corrected
7
+ # values to linear RGB as an intermediate step, then applies the standard OKLAB transformation
8
+ # matrices to produce the final perceptually uniform OKLAB coordinates.
9
+ #
10
+ # Key features:
11
+ # - Two-stage conversion pipeline: sRGB → Linear RGB → OKLAB
12
+ # - Leverages existing SrgbToLrgb and LrgbToOklab converters for modular transformation
13
+ # - Removes gamma correction and applies perceptual uniformity transformations
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 sRGB 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. This converter enables
21
+ # seamless transformation from display-ready sRGB values to the scientifically accurate OKLAB
22
+ # representation for advanced color processing workflows.
23
+
24
+ module Abachrome
25
+ module Converters
26
+ class SrgbToOklab
27
+ # Converts a color from sRGB color space to Oklab color space.
28
+ # The conversion happens in two steps:
29
+ # 1. sRGB is first converted to linear RGB
30
+ # 2. Linear RGB is then converted to Oklab
31
+ #
32
+ # @param srgb_color [Abachrome::Color] The color in sRGB color space to convert
33
+ # @return [Abachrome::Color] The converted color in Oklab color space
34
+ def self.convert(srgb_color)
35
+ # First convert sRGB to linear RGB
36
+ lrgb_color = SrgbToLrgb.convert(srgb_color)
37
+
38
+ # Then convert linear RGB to Oklab
39
+ LrgbToOklab.convert(lrgb_color)
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Abachrome::Converters::SrgbToOklch - sRGB to OKLCH color space converter
4
+ #
5
+ # This converter transforms colors from the standard RGB (sRGB) color space to the OKLCH color space
6
+ # through a two-step conversion process. The transformation first converts sRGB gamma-corrected
7
+ # values to OKLAB rectangular coordinates as an intermediate step, then applies cylindrical coordinate
8
+ # conversion to produce the final OKLCH values with lightness, chroma, and hue components.
9
+ #
10
+ # Key features:
11
+ # - Two-stage conversion pipeline: sRGB → OKLAB → OKLCH
12
+ # - Leverages existing SrgbToOklab and OklabToOklch converters for modular transformation
13
+ # - Converts display-ready RGB values to perceptually uniform cylindrical coordinates
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 sRGB source data
17
+ #
18
+ # The OKLCH color space provides an intuitive interface for color manipulation through its
19
+ # cylindrical coordinate system, making it ideal for hue adjustments, saturation modifications,
20
+ # and other color operations that benefit from polar coordinates while maintaining perceptual
21
+ # uniformity for natural-looking color transformations.
22
+
23
+ require_relative "srgb_to_oklab"
24
+ require_relative "oklab_to_oklch"
25
+
26
+ module Abachrome
27
+ module Converters
28
+ class SrgbToOklch < Abachrome::Converters::Base
29
+ # Converts an sRGB color to OKLCH color space
30
+ #
31
+ # @param srgb_color [Abachrome::Color] The color in sRGB color space to convert
32
+ # @return [Abachrome::Color] The converted color in OKLCH color space
33
+ # @note This is a two-step conversion process: first from sRGB to OKLab, then from OKLab to OKLCH
34
+ # @see SrgbToOklab
35
+ # @see OklabToOklch
36
+ def self.convert(srgb_color)
37
+ # First convert sRGB to OKLab
38
+ oklab_color = SrgbToOklab.convert(srgb_color)
39
+
40
+ # Then convert OKLab to OKLCh
41
+ OklabToOklch.convert(oklab_color)
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Abachrome::Converters::SrgbToYiq - sRGB to YIQ color space converter
4
+ #
5
+ # This converter transforms colors from the standard RGB (sRGB) color space to the YIQ
6
+ # color space using the NTSC standard transformation matrix. The conversion applies the
7
+ # Rec. 601 luma coefficients to separate luminance (Y) from chrominance (I and Q).
8
+ #
9
+ # Key features:
10
+ # - Implements the standard NTSC RGB to YIQ conversion using matrix transformation
11
+ # - Separates brightness (Y) from color information (I, Q)
12
+ # - Uses Rec. 601 luma coefficients: Y = 0.299R + 0.587G + 0.114B
13
+ # - I axis represents orange-to-blue chrominance
14
+ # - Q axis represents purple-to-green chrominance
15
+ # - Maintains alpha channel transparency values during conversion
16
+ # - Uses AbcDecimal arithmetic for precise color science calculations
17
+ #
18
+ # The YIQ color space is historically significant for broadcast television and remains
19
+ # relevant for image processing tasks that require luminance/chrominance separation.
20
+
21
+ module Abachrome
22
+ module Converters
23
+ class SrgbToYiq
24
+ # Converts a color from sRGB color space to YIQ color space.
25
+ # This method applies the NTSC standard transformation matrix.
26
+ #
27
+ # @param srgb_color [Abachrome::Color] A color object in the sRGB color space
28
+ # @return [Abachrome::Color] A new color object in the YIQ color space
29
+ # with the same alpha value as the input color
30
+ def self.convert(srgb_color)
31
+ r, g, b = srgb_color.coordinates.map { |c| c.to_f }
32
+
33
+ # NTSC RGB to YIQ transformation matrix
34
+ # Y = 0.299R + 0.587G + 0.114B (Rec. 601 luma)
35
+ # I = 0.596R - 0.275G - 0.321B (In-phase: orange-blue)
36
+ # Q = 0.212R - 0.523G + 0.311B (Quadrature: purple-green)
37
+ y = (AD("0.299") * r) + (AD("0.587") * g) + (AD("0.114") * b)
38
+ i = (AD("0.5959") * r) - (AD("0.2746") * g) - (AD("0.3213") * b)
39
+ q = (AD("0.2115") * r) - (AD("0.5227") * g) + (AD("0.3112") * b)
40
+
41
+ Color.new(
42
+ ColorSpace.find(:yiq),
43
+ [y, i, q],
44
+ srgb_color.alpha
45
+ )
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abachrome
4
+ module Converters
5
+ class XyzToLms < Abachrome::Converters::Base
6
+ # Converts a color from XYZ color space to LMS color space.
7
+ #
8
+ # This method implements the XYZ to LMS transformation using the standard
9
+ # transformation matrix. The LMS color space represents the response of
10
+ # the three types of cone cells in the human eye (Long, Medium, Short),
11
+ # while XYZ is the CIE 1931 color space that forms the basis for most
12
+ # other color space definitions.
13
+ #
14
+ # @param xyz_color [Abachrome::Color] The color in XYZ color space
15
+ # @raise [ArgumentError] If the input color is not in XYZ color space
16
+ # @return [Abachrome::Color] The resulting color in LMS color space with
17
+ # the same alpha as the input color
18
+ def self.convert(xyz_color)
19
+ raise_unless xyz_color, :xyz
20
+
21
+ x, y, z = xyz_color.coordinates.map { |_| _.to_f }
22
+
23
+ # XYZ to LMS transformation matrix
24
+ l = (x * AD("0.8189330101")) + (y * AD("0.3618667424")) - (z * AD("0.1288597137"))
25
+ m = (x * AD("0.0329845436")) + (y * AD("0.9293118715")) + (z * AD("0.0361456387"))
26
+ s = (x * AD("0.0482003018")) + (y * AD("0.2643662691")) + (z * AD("0.6338517070"))
27
+
28
+ Color.new(ColorSpace.find(:lms), [l, m, s], xyz_color.alpha)
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abachrome
4
+ module Converters
5
+ class XyzToOklab < Abachrome::Converters::Base
6
+ # Converts a color from XYZ color space to OKLAB color space.
7
+ #
8
+ # This method implements the XYZ to OKLAB transformation by first
9
+ # converting XYZ coordinates to the intermediate LMS (Long, Medium, Short)
10
+ # color space, then applying the LMS to OKLAB transformation matrix.
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 OKLAB 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 { |_| _.to_f }
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
+ # Apply cube root transformation
27
+ l_ = l.to_f**Rational(1, 3)
28
+ m_ = m.to_f**Rational(1, 3)
29
+ s_ = s.to_f**Rational(1, 3)
30
+
31
+ # LMS to OKLAB transformation matrix
32
+ lightness = (AD("0.2104542553") * l_) + (AD("0.793617785") * m_) - (AD("0.0040720468") * s_)
33
+ a = (AD("1.9779984951") * l_) - (AD("2.4285922050") * m_) + (AD("0.4505937099") * s_)
34
+ b = (AD("0.0259040371") * l_) + (AD("0.7827717662") * m_) - (AD("0.8086757660") * s_)
35
+
36
+ Color.new(ColorSpace.find(:oklab), [lightness, a, b], xyz_color.alpha)
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Abachrome::Converters::YiqToSrgb - YIQ to sRGB color space converter
4
+ #
5
+ # This converter transforms colors from the YIQ color space back to the standard RGB
6
+ # (sRGB) color space using the inverse NTSC transformation matrix. The conversion
7
+ # recombines luminance (Y) and chrominance (I, Q) into RGB components.
8
+ #
9
+ # Key features:
10
+ # - Implements the inverse NTSC YIQ to RGB conversion using matrix transformation
11
+ # - Recombines brightness (Y) and color information (I, Q) into RGB
12
+ # - Maintains alpha channel transparency values during conversion
13
+ # - Uses AbcDecimal arithmetic for precise color science calculations
14
+ # - May produce RGB values outside [0,1] range for out-of-gamut YIQ values
15
+ #
16
+ # The inverse transformation allows colors manipulated in YIQ space (e.g., brightness
17
+ # adjustments on the Y channel) to be converted back to RGB for display.
18
+
19
+ module Abachrome
20
+ module Converters
21
+ class YiqToSrgb
22
+ # Converts a color from YIQ color space to sRGB color space.
23
+ # This method applies the inverse NTSC transformation matrix.
24
+ #
25
+ # @param yiq_color [Abachrome::Color] A color object in the YIQ color space
26
+ # @return [Abachrome::Color] A new color object in the sRGB color space
27
+ # with the same alpha value as the input color
28
+ def self.convert(yiq_color)
29
+ y, i, q = yiq_color.coordinates.map { |c| c.to_f }
30
+
31
+ # Inverse NTSC YIQ to RGB transformation matrix
32
+ # R = Y + 0.956I + 0.619Q
33
+ # G = Y - 0.272I - 0.647Q
34
+ # B = Y - 1.106I + 1.703Q
35
+ r = y + (AD("0.9563") * i) + (AD("0.6210") * q)
36
+ g = y - (AD("0.2721") * i) - (AD("0.6474") * q)
37
+ b = y - (AD("1.1070") * i) + (AD("1.7046") * q)
38
+
39
+ Color.new(
40
+ ColorSpace.find(:srgb),
41
+ [r, g, b],
42
+ yiq_color.alpha
43
+ )
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abachrome
4
+ module Gamut
5
+ def self.register(gamut)
6
+ @gamuts ||= {}
7
+ @gamuts[gamut.name] = gamut
8
+ end
9
+
10
+ def self.find(name)
11
+ @gamuts ||= {}
12
+ @gamuts[name.to_sym] or raise ArgumentError, "Unknown gamut: #{name}"
13
+ end
14
+
15
+ def self.gamuts
16
+ @gamuts ||= {}
17
+ end
18
+
19
+ class Base
20
+ attr_reader :name, :primaries, :white_point
21
+
22
+ def initialize(name, primaries, white_point)
23
+ @name = name
24
+ @primaries = primaries
25
+ @white_point = white_point
26
+ end
27
+
28
+ # TODO: - make this work properly
29
+ def contains?(coordinates)
30
+ x, y, z = coordinates
31
+ x.between?(0, 1) &&
32
+ y >= 0 && y <= 1 &&
33
+ z >= 0 && z <= 1
34
+ end
35
+
36
+ def map(coordinates, method: :clip)
37
+ case method
38
+ when :clip
39
+ clip(coordinates)
40
+ when :scale
41
+ scale(coordinates)
42
+ else
43
+ raise ArgumentError, "Unknown mapping method: #{method}"
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def clip(coordinates)
50
+ coordinates.map do |c|
51
+ c.clamp(0, 1)
52
+ end
53
+ end
54
+
55
+ def scale(coordinates, channels = nil, min = nil, max = nil)
56
+ min ||= coordinates.min
57
+ max ||= coordinates.max
58
+ channels ||= (0..(coordinates.length - 1)).to_a
59
+
60
+ scale_factor = if max > 1
61
+ 1.0 / max
62
+ elsif min.negative?
63
+ 1.0 / (1 - min)
64
+ else
65
+ 1.0
66
+ end
67
+
68
+ coordinates.each_with_index.map { |c, channel_index| channels.include?(channel_index) ? c * scale_factor : c }
69
+ end
70
+ end
71
+ end
72
+ end
73
+
74
+ # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abachrome
4
+ module Gamut
5
+ class P3 < Base
6
+ def initialize
7
+ primaries = {
8
+ red: [0.680, 0.320],
9
+ green: [0.265, 0.690],
10
+ blue: [0.150, 0.060]
11
+ }
12
+ super(:p3, primaries, :D65)
13
+ end
14
+
15
+ def contains?(coordinates)
16
+ r, g, b = coordinates
17
+ r.between?(0, 1) &&
18
+ g >= 0 && g <= 1 &&
19
+ b >= 0 && b <= 1
20
+ end
21
+ end
22
+
23
+ register(P3.new)
24
+ end
25
+ end
26
+
27
+ # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib
4
+ abachrome
5
+ gamut
6
+ rec2020.rb
7
+
8
+ module Abachrome
9
+ module Gamut
10
+ class Rec2020 < Base
11
+ def initialize
12
+ primaries = {
13
+ red: [0.708, 0.292],
14
+ green: [0.170, 0.797],
15
+ blue: [0.131, 0.046]
16
+ }
17
+ super(:rec2020, primaries, :D65)
18
+ end
19
+ end
20
+
21
+ register(Rec2020.new)
22
+ end
23
+ end
24
+
25
+ # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Abachrome::Gamut::SRGB - sRGB color gamut definition and validation
4
+ #
5
+ # This module defines the sRGB color gamut within the Abachrome color manipulation library.
6
+ # The sRGB gamut represents the range of colors that can be displayed on standard monitors
7
+ # and is the default color space for web content and most consumer displays. It uses the
8
+ # D65 white point and specific primary color coordinates that define the boundaries of
9
+ # reproducible colors in the sRGB color space.
10
+ #
11
+ # Key features:
12
+ # - Defines sRGB primary color coordinates for red, green, and blue
13
+ # - Uses D65 illuminant as the white point reference for proper color reproduction
14
+ # - Provides gamut boundary validation to ensure colors fall within displayable ranges
15
+ # - Implements color containment checking for RGB coordinate validation
16
+ # - Automatically registers with the global gamut registry for system-wide access
17
+ # - Serves as the foundation for sRGB color space conversions and display output
18
+ #
19
+ # The sRGB gamut is essential for ensuring colors are properly represented on standard
20
+ # displays and provides the color boundary definitions needed for gamut mapping operations
21
+ # when converting between different color spaces or preparing colors for web and print output.
22
+
23
+ require_relative "base"
24
+
25
+ module Abachrome
26
+ module Gamut
27
+ class SRGB < Base
28
+ def initialize
29
+ primaries = {
30
+ red: [0.6400, 0.3300],
31
+ green: [0.3000, 0.6000],
32
+ blue: [0.1500, 0.0600]
33
+ }
34
+ super(:srgb, primaries, :D65)
35
+ end
36
+
37
+ def contains?(coordinates)
38
+ r, g, b = coordinates
39
+ r.between?(0, 1) &&
40
+ g >= 0 && g <= 1 &&
41
+ b >= 0 && b <= 1
42
+ end
43
+ end
44
+
45
+ register(SRGB.new)
46
+ end
47
+ end
48
+
49
+ # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abachrome
4
+ module Illuminants
5
+ class Base
6
+ class << self
7
+ def whitepoint
8
+ raise NotImplementedError, "#{name}#whitepoint must be implemented"
9
+ end
10
+
11
+ def x
12
+ whitepoint[0]
13
+ end
14
+
15
+ def y
16
+ whitepoint[1]
17
+ end
18
+
19
+ def z
20
+ whitepoint[2]
21
+ end
22
+
23
+ def xyz
24
+ whitepoint
25
+ end
26
+
27
+ def to_s
28
+ name.split("::").last
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abachrome
4
+ module Illuminants
5
+ class D50 < Base
6
+ def self.x
7
+ 0.96422
8
+ end
9
+
10
+ def self.y
11
+ 1.0
12
+ end
13
+
14
+ def self.z
15
+ 0.82521
16
+ end
17
+
18
+ def self.temperature
19
+ 5003
20
+ end
21
+
22
+ def self.name
23
+ "D50"
24
+ end
25
+
26
+ def self.description
27
+ "CIE Standard Illuminant D50 (Horizon Light)"
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abachrome
4
+ module Illuminants
5
+ class D55 < Base
6
+ def x
7
+ 0.33243
8
+ end
9
+
10
+ def y
11
+ 0.34744
12
+ end
13
+
14
+ def z
15
+ 0.32013
16
+ end
17
+
18
+ def temperature
19
+ 5503
20
+ end
21
+
22
+ def description
23
+ "D55 (mid-morning/mid-afternoon daylight)"
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.