abachrome 0.1.5 → 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 (73) hide show
  1. checksums.yaml +4 -4
  2. data/abachrome.gemspec +1 -0
  3. data/devenv.nix +1 -1
  4. data/lib/abachrome/abc_decimal.rb +38 -35
  5. data/lib/abachrome/color.rb +37 -10
  6. data/lib/abachrome/color_mixins/blend.rb +7 -5
  7. data/lib/abachrome/color_mixins/lighten.rb +8 -6
  8. data/lib/abachrome/color_mixins/spectral_mix.rb +70 -0
  9. data/lib/abachrome/color_mixins/to_colorspace.rb +10 -8
  10. data/lib/abachrome/color_mixins/to_grayscale.rb +87 -0
  11. data/lib/abachrome/color_mixins/to_lrgb.rb +14 -12
  12. data/lib/abachrome/color_mixins/to_oklab.rb +16 -14
  13. data/lib/abachrome/color_mixins/to_oklch.rb +12 -10
  14. data/lib/abachrome/color_mixins/to_srgb.rb +17 -15
  15. data/lib/abachrome/color_models/cmyk.rb +159 -0
  16. data/lib/abachrome/color_models/hsv.rb +5 -3
  17. data/lib/abachrome/color_models/lms.rb +3 -1
  18. data/lib/abachrome/color_models/oklab.rb +3 -1
  19. data/lib/abachrome/color_models/oklch.rb +6 -4
  20. data/lib/abachrome/color_models/rgb.rb +4 -2
  21. data/lib/abachrome/color_models/xyz.rb +3 -1
  22. data/lib/abachrome/color_models/yiq.rb +37 -0
  23. data/lib/abachrome/color_space.rb +28 -14
  24. data/lib/abachrome/converter.rb +10 -8
  25. data/lib/abachrome/converters/base.rb +13 -11
  26. data/lib/abachrome/converters/cmyk_to_srgb.rb +42 -0
  27. data/lib/abachrome/converters/lms_to_lrgb.rb +5 -3
  28. data/lib/abachrome/converters/lms_to_srgb.rb +6 -4
  29. data/lib/abachrome/converters/lms_to_xyz.rb +5 -3
  30. data/lib/abachrome/converters/lrgb_to_lms.rb +3 -1
  31. data/lib/abachrome/converters/lrgb_to_oklab.rb +5 -3
  32. data/lib/abachrome/converters/lrgb_to_srgb.rb +6 -4
  33. data/lib/abachrome/converters/lrgb_to_xyz.rb +5 -3
  34. data/lib/abachrome/converters/oklab_to_lms.rb +9 -7
  35. data/lib/abachrome/converters/oklab_to_lrgb.rb +7 -7
  36. data/lib/abachrome/converters/oklab_to_oklch.rb +4 -2
  37. data/lib/abachrome/converters/oklab_to_srgb.rb +4 -2
  38. data/lib/abachrome/converters/oklch_to_lrgb.rb +5 -3
  39. data/lib/abachrome/converters/oklch_to_oklab.rb +5 -3
  40. data/lib/abachrome/converters/oklch_to_srgb.rb +6 -4
  41. data/lib/abachrome/converters/oklch_to_xyz.rb +6 -4
  42. data/lib/abachrome/converters/srgb_to_cmyk.rb +64 -0
  43. data/lib/abachrome/converters/srgb_to_lrgb.rb +5 -3
  44. data/lib/abachrome/converters/srgb_to_oklab.rb +4 -2
  45. data/lib/abachrome/converters/srgb_to_oklch.rb +5 -3
  46. data/lib/abachrome/converters/srgb_to_yiq.rb +49 -0
  47. data/lib/abachrome/converters/xyz_to_lms.rb +5 -3
  48. data/lib/abachrome/converters/xyz_to_oklab.rb +5 -3
  49. data/lib/abachrome/converters/yiq_to_srgb.rb +47 -0
  50. data/lib/abachrome/gamut/base.rb +3 -3
  51. data/lib/abachrome/gamut/p3.rb +3 -3
  52. data/lib/abachrome/gamut/rec2020.rb +2 -2
  53. data/lib/abachrome/gamut/srgb.rb +4 -2
  54. data/lib/abachrome/illuminants/base.rb +2 -2
  55. data/lib/abachrome/illuminants/d50.rb +2 -2
  56. data/lib/abachrome/illuminants/d55.rb +2 -2
  57. data/lib/abachrome/illuminants/d65.rb +2 -2
  58. data/lib/abachrome/illuminants/d75.rb +2 -2
  59. data/lib/abachrome/named/css.rb +149 -149
  60. data/lib/abachrome/named/tailwind.rb +265 -265
  61. data/lib/abachrome/outputs/css.rb +2 -2
  62. data/lib/abachrome/palette.rb +26 -25
  63. data/lib/abachrome/palette_mixins/interpolate.rb +3 -1
  64. data/lib/abachrome/palette_mixins/resample.rb +2 -2
  65. data/lib/abachrome/palette_mixins/stretch_luminance.rb +2 -2
  66. data/lib/abachrome/parsers/css.rb +86 -71
  67. data/lib/abachrome/parsers/hex.rb +2 -2
  68. data/lib/abachrome/parsers/tailwind.rb +8 -8
  69. data/lib/abachrome/spectral.rb +277 -0
  70. data/lib/abachrome/to_abcd.rb +4 -4
  71. data/lib/abachrome/version.rb +2 -2
  72. data/lib/abachrome.rb +66 -10
  73. metadata +29 -3
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Abachrome::ColorMixins::ToGrayscale - Grayscale conversion mixin
4
+ #
5
+ # This mixin provides methods for converting colors to grayscale using various
6
+ # standard luma calculations. It supports both legacy Rec. 601 (SDTV) and modern
7
+ # Rec. 709 (HDTV) coefficients for accurate grayscale conversion based on human
8
+ # eye sensitivity.
9
+ #
10
+ # Key features:
11
+ # - Rec. 601 luma: Y = 0.299R + 0.587G + 0.114B (NTSC/SDTV standard)
12
+ # - Rec. 709 luma: Y = 0.2126R + 0.7152G + 0.0722B (HDTV standard)
13
+ # - Maintains alpha channel during conversion
14
+ # - Uses BigDecimal for precise calculations
15
+ #
16
+ # This is essential for image processing, accessibility checks, and any application
17
+ # that needs perceptually accurate grayscale conversion rather than simple averaging.
18
+
19
+ module Abachrome
20
+ module ColorMixins
21
+ module ToGrayscale
22
+ # Converts the color to grayscale using Rec. 601 luma coefficients (legacy NTSC standard).
23
+ # This is the same calculation used in the YIQ color space's Y component.
24
+ #
25
+ # @return [Abachrome::Color] A grayscale version of the color in sRGB space
26
+ def to_grayscale_601
27
+ rgb_color = to_srgb
28
+ r, g, b = rgb_color.coordinates
29
+
30
+ # Rec. 601 luma: Y = 0.299R + 0.587G + 0.114B
31
+ luma = (AD("0.299") * r) + (AD("0.587") * g) + (AD("0.114") * b)
32
+
33
+ Color.from_rgb(luma, luma, luma, alpha)
34
+ end
35
+
36
+ # Converts the color to grayscale using Rec. 709 luma coefficients (HDTV standard).
37
+ #
38
+ # @return [Abachrome::Color] A grayscale version of the color in sRGB space
39
+ def to_grayscale_709
40
+ rgb_color = to_srgb
41
+ r, g, b = rgb_color.coordinates
42
+
43
+ # Rec. 709 luma: Y = 0.2126R + 0.7152G + 0.0722B
44
+ luma = (AD("0.2126") * r) + (AD("0.7152") * g) + (AD("0.0722") * b)
45
+
46
+ Color.from_rgb(luma, luma, luma, alpha)
47
+ end
48
+
49
+ # Converts the color to grayscale using the default Rec. 601 standard.
50
+ # This is an alias for to_grayscale_601 and matches the legacy behavior
51
+ # expected by most image processing applications.
52
+ #
53
+ # @return [Abachrome::Color] A grayscale version of the color in sRGB space
54
+ def to_grayscale
55
+ to_grayscale_601
56
+ end
57
+
58
+ # Calculates the relative luminance (luma) value using Rec. 601 coefficients.
59
+ # This returns just the Y component without creating a new color.
60
+ #
61
+ # @return [AbcDecimal] The luma value in range [0, 1]
62
+ def luma_601
63
+ rgb_color = to_srgb
64
+ r, g, b = rgb_color.coordinates
65
+ (AD("0.299") * r) + (AD("0.587") * g) + (AD("0.114") * b)
66
+ end
67
+
68
+ # Calculates the relative luminance (luma) value using Rec. 709 coefficients.
69
+ # This returns just the Y component without creating a new color.
70
+ #
71
+ # @return [AbcDecimal] The luma value in range [0, 1]
72
+ def luma_709
73
+ rgb_color = to_srgb
74
+ r, g, b = rgb_color.coordinates
75
+ (AD("0.2126") * r) + (AD("0.7152") * g) + (AD("0.0722") * b)
76
+ end
77
+
78
+ # Calculates the relative luminance using the default Rec. 601 standard.
79
+ # Alias for luma_601.
80
+ #
81
+ # @return [AbcDecimal] The luma value in range [0, 1]
82
+ def luma
83
+ luma_601
84
+ end
85
+ end
86
+ end
87
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Abachrome::ColorMixins::ToLrgb - Linear RGB color space conversion functionality
2
4
  #
3
5
  # This mixin provides methods for converting colors to the linear RGB (LRGB) color space,
@@ -28,7 +30,7 @@ module Abachrome
28
30
  # which uses a linear relationship between the stored numeric value and
29
31
  # the actual light intensity. If the color is already in the LRGB space,
30
32
  # it returns the current object without conversion.
31
- #
33
+ #
32
34
  # @return [Abachrome::Color] A new color object in the LRGB color space,
33
35
  # or the original object if already in LRGB space
34
36
  def to_lrgb
@@ -39,10 +41,10 @@ module Abachrome
39
41
 
40
42
  # Converts the current color to the linear RGB (LRGB) color space and updates
41
43
  # the receiver's state. If the color is already in LRGB space, this is a no-op.
42
- #
44
+ #
43
45
  # Unlike #to_lrgb which returns a new color instance, this method modifies the
44
46
  # current object by changing its color space and coordinates to the LRGB equivalent.
45
- #
47
+ #
46
48
  # @return [Abachrome::Color] the receiver itself, now in LRGB color space
47
49
  def to_lrgb!
48
50
  unless color_space.name == :lrgb
@@ -54,11 +56,11 @@ module Abachrome
54
56
  end
55
57
 
56
58
  # Returns the linear red component value of the color.
57
- #
59
+ #
58
60
  # This method accesses the first coordinate from the color in linear RGB space.
59
61
  # Linear RGB values differ from standard RGB by using a non-gamma-corrected
60
62
  # linear representation of luminance.
61
- #
63
+ #
62
64
  # @return [AbcDecimal] The linear red component value, typically in range [0, 1]
63
65
  def lred
64
66
  to_lrgb.coordinates[0]
@@ -68,7 +70,7 @@ module Abachrome
68
70
  # linear RGB color space first. Linear RGB uses a different scale than standard
69
71
  # sRGB, with values representing linear light energy rather than gamma-corrected
70
72
  # values.
71
- #
73
+ #
72
74
  # @return [AbcDecimal] The linear green component value from the color's linear
73
75
  # RGB representation
74
76
  def lgreen
@@ -76,17 +78,17 @@ module Abachrome
76
78
  end
77
79
 
78
80
  # Returns the linear blue channel value of this color after conversion to linear RGB color space.
79
- #
81
+ #
80
82
  # This method converts the current color to the linear RGB color space and extracts the blue
81
83
  # component (the third coordinate).
82
- #
84
+ #
83
85
  # @return [AbcDecimal] The linear blue component value, typically in the range [0, 1]
84
86
  def lblue
85
87
  to_lrgb.coordinates[2]
86
88
  end
87
89
 
88
90
  # Returns the coordinates of the color in the linear RGB color space.
89
- #
91
+ #
90
92
  # @return [Array<AbcDecimal>] An array of three AbcDecimal values representing
91
93
  # the red, green, and blue components in linear RGB color space.
92
94
  def lrgb_values
@@ -96,14 +98,14 @@ module Abachrome
96
98
  # Returns an array of sRGB values as integers in the 0-255 range.
97
99
  # This method converts the color to RGB, scales the values to the 0-255 range,
98
100
  # rounds to integers, and ensures they are clamped within the valid range.
99
- #
101
+ #
100
102
  # @return [Array<Integer>] An array of three RGB integer values between 0-255
101
103
  def rgb_array
102
104
  to_rgb.coordinates.map { |c| (c * 255).round.clamp(0, 255) }
103
105
  end
104
106
 
105
107
  # Returns a hexadecimal string representation of the color in RGB format.
106
- #
108
+ #
107
109
  # @return [String] A hexadecimal color code in the format '#RRGGBB' where RR, GG, and BB
108
110
  # are two-digit hexadecimal values for the red, green, and blue components respectively.
109
111
  # @example
@@ -116,4 +118,4 @@ module Abachrome
116
118
  end
117
119
  end
118
120
 
119
- # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
121
+ # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Abachrome::ColorMixins::ToOklab - OKLAB color space conversion functionality
2
4
  #
3
5
  # This mixin provides methods for converting colors to the OKLAB color space, which is a
@@ -23,10 +25,10 @@ module Abachrome
23
25
  module ColorMixins
24
26
  module ToOklab
25
27
  # Converts the current color to the OKLAB color space.
26
- #
28
+ #
27
29
  # If the color is already in OKLAB, it returns the color unchanged.
28
30
  # Otherwise, it uses the Converter to transform the color to OKLAB.
29
- #
31
+ #
30
32
  # @return [Abachrome::Color] A new Color object in the OKLAB color space
31
33
  def to_oklab
32
34
  return self if color_space.name == :oklab
@@ -38,11 +40,11 @@ module Abachrome
38
40
  # This method transforms the current color into OKLAB space,
39
41
  # modifying the original object by updating its color space
40
42
  # and coordinates if not already in OKLAB.
41
- #
43
+ #
42
44
  # @example
43
45
  # color = Abachrome::Color.from_hex("#ff5500")
44
46
  # color.to_oklab! # Color now uses OKLAB color space
45
- #
47
+ #
46
48
  # @return [Abachrome::Color] self, with updated color space and coordinates
47
49
  def to_oklab!
48
50
  unless color_space.name == :oklab
@@ -56,27 +58,27 @@ module Abachrome
56
58
  # Returns the lightness component (L) of the color in the OKLAB color space.
57
59
  # The lightness value ranges from 0 (black) to 1 (white) and represents
58
60
  # the perceived lightness of the color.
59
- #
61
+ #
60
62
  # @return [AbcDecimal] The lightness (L) value from the OKLAB color space
61
63
  def lightness
62
64
  to_oklab.coordinates[0]
63
65
  end
64
66
 
65
67
  # Returns the L (Lightness) component from the OKLAB color space.
66
- #
68
+ #
67
69
  # The L value represents perceptual lightness in the OKLAB color space,
68
70
  # typically ranging from 0 (black) to 1 (white).
69
- #
71
+ #
70
72
  # @return [AbcDecimal] The L (Lightness) component from the OKLAB color space
71
73
  def l
72
74
  to_oklab.coordinates[0]
73
75
  end
74
76
 
75
77
  # Returns the 'a' component from the OKLAB color space (green-red axis).
76
- #
78
+ #
77
79
  # The 'a' component in OKLAB represents the position on the green-red axis,
78
80
  # with negative values being more green and positive values being more red.
79
- #
81
+ #
80
82
  # @return [AbcDecimal] The 'a' component value from the OKLAB color space.
81
83
  # @see #to_oklab For the full conversion to OKLAB color space
82
84
  def a
@@ -84,25 +86,25 @@ module Abachrome
84
86
  end
85
87
 
86
88
  # Returns the B value of the color in OKLAB color space.
87
- #
89
+ #
88
90
  # This method first converts the color to OKLAB color space if needed,
89
91
  # then extracts the B component (blue-yellow axis), which is the third
90
92
  # coordinate in the OKLAB model.
91
- #
93
+ #
92
94
  # @return [AbcDecimal] The B component value in OKLAB color space
93
95
  def b
94
96
  to_oklab.coordinates[2]
95
97
  end
96
98
 
97
99
  # Returns the OKLAB color space coordinates for this color.
98
- #
100
+ #
99
101
  # @return [Array] An array of OKLAB coordinates [L, a, b] representing the color in OKLAB color space
100
102
  def oklab_values
101
103
  to_oklab.coordinates
102
104
  end
103
105
 
104
106
  # Returns an array representation of the color's coordinates in the OKLAB color space.
105
- #
107
+ #
106
108
  # @return [Array<AbcDecimal>] An array containing the coordinates of the color
107
109
  # in the OKLAB color space in the order [L, a, b]
108
110
  def oklab_array
@@ -112,4 +114,4 @@ module Abachrome
112
114
  end
113
115
  end
114
116
 
115
- # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
117
+ # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Abachrome::ColorMixins::ToOklch - OKLCH color space conversion functionality
2
4
  #
3
5
  # This mixin provides methods for converting colors to the OKLCH color space, which is a
@@ -21,12 +23,12 @@ module Abachrome
21
23
  module ColorMixins
22
24
  module ToOklch
23
25
  # Converts the current color to the OKLCH color space.
24
- #
26
+ #
25
27
  # This method transforms the color into the perceptually uniform OKLCH color space.
26
28
  # If the color is already in OKLCH, it returns itself unchanged. If the color is in
27
29
  # OKLAB, it directly converts from OKLAB to OKLCH. For all other color spaces, it
28
30
  # first converts to OKLAB as an intermediate step, then converts to OKLCH.
29
- #
31
+ #
30
32
  # @return [Abachrome::Color] A new Color object in the OKLCH color space
31
33
  def to_oklch
32
34
  return self if color_space.name == :oklch
@@ -44,7 +46,7 @@ module Abachrome
44
46
  # This method transforms the current color to OKLCH color space, modifying
45
47
  # the original object instead of creating a new one. If the color is already
46
48
  # in OKLCH space, no conversion is performed.
47
- #
49
+ #
48
50
  # @return [Abachrome::Color] self, allowing for method chaining
49
51
  def to_oklch!
50
52
  unless color_space.name == :oklch
@@ -58,7 +60,7 @@ module Abachrome
58
60
  # Returns the lightness component of the color in the OKLCH color space.
59
61
  # This method provides direct access to the first coordinate of the OKLCH
60
62
  # representation of the color, which represents perceptual lightness.
61
- #
63
+ #
62
64
  # @return [AbcDecimal] the lightness value in the OKLCH color space,
63
65
  # typically in the range of 0.0 to 1.0, where 0.0 is black and 1.0 is white
64
66
  def lightness
@@ -67,14 +69,14 @@ module Abachrome
67
69
 
68
70
  # Returns the chroma value of the color by converting it to the OKLCH color space.
69
71
  # Chroma represents color intensity or saturation in the OKLCH color space.
70
- #
72
+ #
71
73
  # @return [AbcDecimal] The chroma value (second coordinate) from the OKLCH color space
72
74
  def chroma
73
75
  to_oklch.coordinates[1]
74
76
  end
75
77
 
76
78
  # Returns the hue value of the color in the OKLCH color space.
77
- #
79
+ #
78
80
  # @return [AbcDecimal] The hue component of the color in degrees (0-360)
79
81
  # from the OKLCH color space representation.
80
82
  def hue
@@ -82,7 +84,7 @@ module Abachrome
82
84
  end
83
85
 
84
86
  # Returns the OKLCH coordinates of the color.
85
- #
87
+ #
86
88
  # @return [Array<AbcDecimal>] Array of OKLCH coordinates [lightness, chroma, hue] where:
87
89
  # - lightness: perceptual lightness component (0-1)
88
90
  # - chroma: colorfulness/saturation component
@@ -92,11 +94,11 @@ module Abachrome
92
94
  end
93
95
 
94
96
  # Returns the OKLCH coordinates of the color as an array.
95
- #
97
+ #
96
98
  # Converts the current color to OKLCH color space and returns its coordinates
97
99
  # as an array. The OKLCH color space represents colors using Lightness,
98
100
  # Chroma, and Hue components in a perceptually uniform way.
99
- #
101
+ #
100
102
  # @return [Array<Numeric>] An array containing the OKLCH coordinates [lightness, chroma, hue]
101
103
  def oklch_array
102
104
  to_oklch.coordinates
@@ -105,4 +107,4 @@ module Abachrome
105
107
  end
106
108
  end
107
109
 
108
- # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
110
+ # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Abachrome::ColorMixins::ToSrgb - sRGB color space conversion functionality
2
4
  #
3
5
  # This mixin provides methods for converting colors to the sRGB color space, which is the
@@ -23,11 +25,11 @@ module Abachrome
23
25
  module ColorMixins
24
26
  module ToSrgb
25
27
  # Converts the current color to the sRGB color space.
26
- #
28
+ #
27
29
  # If the color is already in the sRGB color space, returns the color instance
28
30
  # unchanged. Otherwise, performs a color space conversion from the current
29
31
  # color space to sRGB.
30
- #
32
+ #
31
33
  # @return [Abachrome::Color] A new Color instance in the sRGB color space,
32
34
  # or self if already in sRGB
33
35
  def to_srgb
@@ -37,7 +39,7 @@ module Abachrome
37
39
  end
38
40
 
39
41
  # Alias for #to_srgb method.
40
- #
42
+ #
41
43
  # @return [Abachrome::Color] The color converted to sRGB color space
42
44
  def to_rgb
43
45
  # assume they mean srgb
@@ -59,7 +61,7 @@ module Abachrome
59
61
  # Converts the current color to sRGB color space in place.
60
62
  # This is an alias for {#to_srgb!} as RGB commonly refers to sRGB
61
63
  # in web and design contexts.
62
- #
64
+ #
63
65
  # @return [self] Returns self after converting to sRGB
64
66
  def to_rgb!
65
67
  # assume they mean srgb
@@ -67,7 +69,7 @@ module Abachrome
67
69
  end
68
70
 
69
71
  # Returns the red component of the color in the sRGB color space.
70
- #
72
+ #
71
73
  # @return [AbcDecimal] The red component value in the sRGB color space,
72
74
  # normalized between 0 and 1.
73
75
  def red
@@ -75,27 +77,27 @@ module Abachrome
75
77
  end
76
78
 
77
79
  # Returns the green component of the color in sRGB space.
78
- #
80
+ #
79
81
  # This method converts the current color to sRGB color space if needed,
80
82
  # then extracts the green component (second coordinate).
81
- #
83
+ #
82
84
  # @return [AbcDecimal] The green component value in the sRGB color space, typically in the range 0-1
83
85
  def green
84
86
  to_srgb.coordinates[1]
85
87
  end
86
88
 
87
89
  # Returns the blue component of the color in sRGB color space.
88
- #
90
+ #
89
91
  # This method converts the current color to sRGB if needed and
90
92
  # extracts the third coordinate value (blue).
91
- #
93
+ #
92
94
  # @return [AbcDecimal] The blue component value in sRGB space, typically in range 0-1
93
95
  def blue
94
96
  to_srgb.coordinates[2]
95
97
  end
96
98
 
97
99
  # Returns the RGB color values in the sRGB color space.
98
- #
100
+ #
99
101
  # @return [Array<AbcDecimal>] An array of three AbcDecimal values representing
100
102
  # the red, green, and blue color components in the sRGB color space.
101
103
  def srgb_values
@@ -103,18 +105,18 @@ module Abachrome
103
105
  end
104
106
 
105
107
  # Returns the RGB values of the color as coordinates in the sRGB color space.
106
- #
108
+ #
107
109
  # @return [Array<Abachrome::AbcDecimal>] The RGB coordinates (red, green, blue) in sRGB color space
108
110
  def rgb_values
109
111
  to_srgb.coordinates
110
112
  end
111
113
 
112
114
  # Returns an array of RGB values (0-255) for this color.
113
- #
115
+ #
114
116
  # This method converts the color to sRGB, then scales the component values
115
117
  # from the 0-1 range to the 0-255 range commonly used in RGB color codes.
116
118
  # Values are rounded to the nearest integer and clamped between 0 and 255.
117
- #
119
+ #
118
120
  # @return [Array<Integer>] An array of three integers representing the [R, G, B]
119
121
  # values in the 0-255 range
120
122
  def rgb_array
@@ -123,7 +125,7 @@ module Abachrome
123
125
 
124
126
  # Returns a hexadecimal representation of this color in sRGB color space.
125
127
  # Converts the color to sRGB, then formats it as a hexadecimal string.
126
- #
128
+ #
127
129
  # @return [String] A string in the format "#RRGGBB" where RR, GG, and BB are
128
130
  # the hexadecimal representations of the red, green, and blue components,
129
131
  # each ranging from 00 to FF.
@@ -137,4 +139,4 @@ module Abachrome
137
139
  end
138
140
  end
139
141
 
140
- # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
142
+ # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
@@ -0,0 +1,159 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Abachrome::ColorModels::CMYK - CMYK color space model utilities
4
+ #
5
+ # This module provides utility methods for the CMYK (Cyan, Magenta, Yellow, Key/Black)
6
+ # color model within the Abachrome color manipulation library. CMYK represents colors
7
+ # using the subtractive color synthesis model used in printing and physical media.
8
+ #
9
+ # Key features:
10
+ # - Subtractive color model (ink on paper, not light)
11
+ # - Four channels: Cyan, Magenta, Yellow, and Key (Black)
12
+ # - Supports Undercolor Removal (UCR) for ink savings
13
+ # - Supports Gray Component Replacement (GCR) for richer blacks
14
+ # - High-precision BigDecimal arithmetic for exact ink percentages
15
+ # - Essential for print media, PDF generation, and pre-press workflows
16
+ #
17
+ # The CMYK model is fundamental for professional printing, where the fourth channel
18
+ # (Black/Key) is added to achieve crisp text and deep shadows that cannot be produced
19
+ # by combining cyan, magenta, and yellow inks alone.
20
+
21
+ require_relative "../abc_decimal"
22
+
23
+ module Abachrome
24
+ module ColorModels
25
+ class CMYK
26
+ include Abachrome::ToAbcd
27
+
28
+ class << self
29
+ # Normalizes CMYK color component values to the [0,1] range.
30
+ #
31
+ # @param c [Numeric] Cyan component (0-1 or 0-100 for percentages)
32
+ # @param m [Numeric] Magenta component (0-1 or 0-100 for percentages)
33
+ # @param y [Numeric] Yellow component (0-1 or 0-100 for percentages)
34
+ # @param k [Numeric] Key/Black component (0-1 or 0-100 for percentages)
35
+ # @return [Array<AbcDecimal>] Array of four normalized components as AbcDecimal objects
36
+ def normalize(c, m, y, k)
37
+ [c, m, y, k].map do |value|
38
+ case value
39
+ when String
40
+ if value.end_with?("%")
41
+ AbcDecimal(value.chomp("%")) / AbcDecimal(100)
42
+ else
43
+ AbcDecimal(value)
44
+ end
45
+ when Numeric
46
+ if value > 1
47
+ AbcDecimal(value) / AbcDecimal(100)
48
+ else
49
+ AbcDecimal(value)
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ # Converts RGB values to CMYK using the standard naive conversion.
56
+ # This produces high ink coverage and is generally not recommended for production.
57
+ #
58
+ # @param r [Numeric] Red component (0-1)
59
+ # @param g [Numeric] Green component (0-1)
60
+ # @param b [Numeric] Blue component (0-1)
61
+ # @return [Array<AbcDecimal>] Array of [c, m, y, k] values
62
+ def from_rgb_naive(r, g, b)
63
+ r = AbcDecimal(r)
64
+ g = AbcDecimal(g)
65
+ b = AbcDecimal(b)
66
+
67
+ # Simple complementary conversion
68
+ c = AbcDecimal(1) - r
69
+ m = AbcDecimal(1) - g
70
+ y = AbcDecimal(1) - b
71
+ k = AbcDecimal(0)
72
+
73
+ [c, m, y, k]
74
+ end
75
+
76
+ # Converts RGB values to CMYK using Undercolor Removal (UCR).
77
+ # This extracts the gray component to the black (K) channel, reducing ink usage.
78
+ #
79
+ # @param r [Numeric] Red component (0-1)
80
+ # @param g [Numeric] Green component (0-1)
81
+ # @param b [Numeric] Blue component (0-1)
82
+ # @param gcr_amount [Numeric] Gray Component Replacement amount (0-1), default 1.0 for full UCR
83
+ # @return [Array<AbcDecimal>] Array of [c, m, y, k] values
84
+ def from_rgb_ucr(r, g, b, gcr_amount = 1.0)
85
+ r = AbcDecimal(r)
86
+ g = AbcDecimal(g)
87
+ b = AbcDecimal(b)
88
+ gcr = AbcDecimal(gcr_amount)
89
+
90
+ # Calculate complementary CMY values
91
+ c = AbcDecimal(1) - r
92
+ m = AbcDecimal(1) - g
93
+ y = AbcDecimal(1) - b
94
+
95
+ # Find the minimum (gray component)
96
+ k = [c, m, y].min * gcr
97
+
98
+ # Remove the gray component from CMY if k > 0
99
+ if k.positive?
100
+ c -= k
101
+ m -= k
102
+ y -= k
103
+ end
104
+
105
+ [c, m, y, k]
106
+ end
107
+
108
+ # Alias for from_rgb_ucr with full GCR (the standard approach).
109
+ # GCR is essentially the same as UCR but allows control over how much
110
+ # of the gray component to extract.
111
+ #
112
+ # @param r [Numeric] Red component (0-1)
113
+ # @param g [Numeric] Green component (0-1)
114
+ # @param b [Numeric] Blue component (0-1)
115
+ # @param amount [Numeric] Amount of GCR to apply (0-1), default 1.0
116
+ # @return [Array<AbcDecimal>] Array of [c, m, y, k] values
117
+ def from_rgb_gcr(r, g, b, amount = 1.0)
118
+ from_rgb_ucr(r, g, b, amount)
119
+ end
120
+
121
+ # Converts CMYK values back to RGB.
122
+ # This is a straightforward inverse of the naive RGB→CMY conversion
123
+ # plus the addition of the black channel.
124
+ #
125
+ # @param c [Numeric] Cyan component (0-1)
126
+ # @param m [Numeric] Magenta component (0-1)
127
+ # @param y [Numeric] Yellow component (0-1)
128
+ # @param k [Numeric] Key/Black component (0-1)
129
+ # @return [Array<AbcDecimal>] Array of [r, g, b] values
130
+ def to_rgb(c, m, y, k)
131
+ c = AbcDecimal(c)
132
+ m = AbcDecimal(m)
133
+ y = AbcDecimal(y)
134
+ k = AbcDecimal(k)
135
+
136
+ # Standard CMYK to RGB conversion
137
+ r = (AbcDecimal(1) - c) * (AbcDecimal(1) - k)
138
+ g = (AbcDecimal(1) - m) * (AbcDecimal(1) - k)
139
+ b = (AbcDecimal(1) - y) * (AbcDecimal(1) - k)
140
+
141
+ [r, g, b]
142
+ end
143
+
144
+ # Calculates the Total Area Coverage (TAC) for CMYK values.
145
+ # TAC is the sum of all four ink percentages and is critical in print workflows
146
+ # to prevent excessive ink that can cause smearing or paper damage.
147
+ #
148
+ # @param c [Numeric] Cyan component (0-1)
149
+ # @param m [Numeric] Magenta component (0-1)
150
+ # @param y [Numeric] Yellow component (0-1)
151
+ # @param k [Numeric] Key/Black component (0-1)
152
+ # @return [AbcDecimal] Total ink coverage as a decimal (0-4, often expressed as 0-400%)
153
+ def total_area_coverage(c, m, y, k)
154
+ AbcDecimal(c) + AbcDecimal(m) + AbcDecimal(y) + AbcDecimal(k)
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Abachrome::ColorModels::HSV - HSV color space model definition
2
4
  #
3
5
  # This module defines the HSV (Hue, Saturation, Value) color model within the Abachrome
@@ -30,13 +32,13 @@ module Abachrome
30
32
 
31
33
  # Validates whether the coordinates are valid for the HSV color model.
32
34
  # Each component (hue, saturation, value) must be in the range [0, 1].
33
- #
35
+ #
34
36
  # @param coordinates [Array<Numeric>] An array of three values representing
35
37
  # hue (h), saturation (s), and value (v) in the range [0, 1]
36
38
  # @return [Boolean] true if all coordinates are within valid ranges, false otherwise
37
39
  def valid_coordinates?(coordinates)
38
40
  h, s, v = coordinates
39
- h >= 0 && h <= 1.0 &&
41
+ h.between?(0, 1.0) &&
40
42
  s >= 0 && s <= 1.0 &&
41
43
  v >= 0 && v <= 1.0
42
44
  end
@@ -44,4 +46,4 @@ module Abachrome
44
46
  end
45
47
  end
46
48
 
47
- # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
49
+ # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Abachrome::ColorModels::Lms - LMS color space model definition
2
4
  #
3
5
  # This module defines the LMS color model within the Abachrome color manipulation library.
@@ -33,4 +35,4 @@ ColorSpace.register(
33
35
  []
34
36
  )
35
37
 
36
- # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
38
+ # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Abachrome::ColorModels::Oklab - OKLAB color space model definition
2
4
  #
3
5
  # This module defines the OKLAB color model within the Abachrome color manipulation library.
@@ -32,4 +34,4 @@ ColorSpace.register(
32
34
  ["ok-lab"]
33
35
  )
34
36
 
35
- # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
37
+ # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.