abachrome 0.1.5 → 0.2.0

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 (85) 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 +42 -35
  5. data/lib/abachrome/color.rb +61 -10
  6. data/lib/abachrome/color_mixins/blend.rb +7 -5
  7. data/lib/abachrome/color_mixins/harmonies.rb +187 -0
  8. data/lib/abachrome/color_mixins/lighten.rb +8 -6
  9. data/lib/abachrome/color_mixins/spectral_mix.rb +70 -0
  10. data/lib/abachrome/color_mixins/to_colorspace.rb +10 -8
  11. data/lib/abachrome/color_mixins/to_grayscale.rb +87 -0
  12. data/lib/abachrome/color_mixins/to_lms.rb +106 -0
  13. data/lib/abachrome/color_mixins/to_lrgb.rb +14 -12
  14. data/lib/abachrome/color_mixins/to_oklab.rb +16 -14
  15. data/lib/abachrome/color_mixins/to_oklch.rb +12 -10
  16. data/lib/abachrome/color_mixins/to_srgb.rb +17 -15
  17. data/lib/abachrome/color_mixins/to_xyz.rb +106 -0
  18. data/lib/abachrome/color_mixins/wcag.rb +126 -0
  19. data/lib/abachrome/color_models/cmyk.rb +160 -0
  20. data/lib/abachrome/color_models/hsv.rb +5 -3
  21. data/lib/abachrome/color_models/lms.rb +3 -1
  22. data/lib/abachrome/color_models/oklab.rb +3 -1
  23. data/lib/abachrome/color_models/oklch.rb +6 -4
  24. data/lib/abachrome/color_models/rgb.rb +4 -2
  25. data/lib/abachrome/color_models/xyz.rb +11 -1
  26. data/lib/abachrome/color_models/yiq.rb +37 -0
  27. data/lib/abachrome/color_space.rb +28 -14
  28. data/lib/abachrome/converter.rb +10 -8
  29. data/lib/abachrome/converters/base.rb +13 -11
  30. data/lib/abachrome/converters/cmyk_to_srgb.rb +42 -0
  31. data/lib/abachrome/converters/lms_to_lrgb.rb +5 -3
  32. data/lib/abachrome/converters/lms_to_oklab.rb +28 -0
  33. data/lib/abachrome/converters/lms_to_srgb.rb +6 -4
  34. data/lib/abachrome/converters/lms_to_xyz.rb +5 -3
  35. data/lib/abachrome/converters/lrgb_to_lms.rb +29 -1
  36. data/lib/abachrome/converters/lrgb_to_oklab.rb +5 -3
  37. data/lib/abachrome/converters/lrgb_to_srgb.rb +6 -4
  38. data/lib/abachrome/converters/lrgb_to_xyz.rb +5 -3
  39. data/lib/abachrome/converters/oklab_to_lms.rb +9 -7
  40. data/lib/abachrome/converters/oklab_to_lrgb.rb +7 -7
  41. data/lib/abachrome/converters/oklab_to_oklch.rb +4 -2
  42. data/lib/abachrome/converters/oklab_to_srgb.rb +4 -2
  43. data/lib/abachrome/converters/oklab_to_xyz.rb +29 -0
  44. data/lib/abachrome/converters/oklch_to_lms.rb +28 -0
  45. data/lib/abachrome/converters/oklch_to_lrgb.rb +5 -3
  46. data/lib/abachrome/converters/oklch_to_oklab.rb +5 -3
  47. data/lib/abachrome/converters/oklch_to_srgb.rb +6 -4
  48. data/lib/abachrome/converters/oklch_to_xyz.rb +6 -4
  49. data/lib/abachrome/converters/srgb_to_cmyk.rb +64 -0
  50. data/lib/abachrome/converters/srgb_to_lms.rb +29 -0
  51. data/lib/abachrome/converters/srgb_to_lrgb.rb +5 -3
  52. data/lib/abachrome/converters/srgb_to_oklab.rb +4 -2
  53. data/lib/abachrome/converters/srgb_to_oklch.rb +5 -3
  54. data/lib/abachrome/converters/srgb_to_xyz.rb +30 -0
  55. data/lib/abachrome/converters/srgb_to_yiq.rb +49 -0
  56. data/lib/abachrome/converters/xyz_to_lms.rb +5 -3
  57. data/lib/abachrome/converters/xyz_to_lrgb.rb +33 -0
  58. data/lib/abachrome/converters/xyz_to_oklab.rb +5 -3
  59. data/lib/abachrome/converters/xyz_to_srgb.rb +30 -0
  60. data/lib/abachrome/converters/yiq_to_srgb.rb +47 -0
  61. data/lib/abachrome/floatify.rb +282 -0
  62. data/lib/abachrome/gamut/base.rb +3 -3
  63. data/lib/abachrome/gamut/p3.rb +3 -3
  64. data/lib/abachrome/gamut/rec2020.rb +2 -2
  65. data/lib/abachrome/gamut/srgb.rb +4 -2
  66. data/lib/abachrome/illuminants/base.rb +2 -2
  67. data/lib/abachrome/illuminants/d50.rb +2 -2
  68. data/lib/abachrome/illuminants/d55.rb +2 -2
  69. data/lib/abachrome/illuminants/d65.rb +2 -2
  70. data/lib/abachrome/illuminants/d75.rb +2 -2
  71. data/lib/abachrome/named/css.rb +149 -149
  72. data/lib/abachrome/named/tailwind.rb +265 -265
  73. data/lib/abachrome/outputs/css.rb +2 -2
  74. data/lib/abachrome/palette.rb +26 -25
  75. data/lib/abachrome/palette_mixins/interpolate.rb +3 -1
  76. data/lib/abachrome/palette_mixins/resample.rb +2 -2
  77. data/lib/abachrome/palette_mixins/stretch_luminance.rb +2 -2
  78. data/lib/abachrome/parsers/css.rb +86 -71
  79. data/lib/abachrome/parsers/hex.rb +2 -2
  80. data/lib/abachrome/parsers/tailwind.rb +8 -8
  81. data/lib/abachrome/spectral.rb +277 -0
  82. data/lib/abachrome/to_abcd.rb +4 -4
  83. data/lib/abachrome/version.rb +2 -2
  84. data/lib/abachrome.rb +66 -10
  85. metadata +41 -3
@@ -0,0 +1,282 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Abachrome::Floatify - Float-based arithmetic for color calculations
4
+ #
5
+ # This module monkey-patches AbcDecimal to use Float instead of BigDecimal.
6
+ # Require this file to make AbcDecimal use floats throughout the codebase.
7
+ #
8
+ # Usage:
9
+ # require 'abachrome/floatify'
10
+ #
11
+ # This makes AbcDecimal a drop-in replacement that uses Float arithmetic
12
+ # for performance-critical applications where BigDecimal precision is not required.
13
+
14
+ require_relative "abc_decimal"
15
+
16
+ module Abachrome
17
+ class AbcDecimal
18
+ # Remove the precision-related constant and attribute
19
+ remove_const(:DEFAULT_PRECISION) if defined?(DEFAULT_PRECISION)
20
+
21
+ attr_accessor :value
22
+
23
+ # Initializes a new AbcDecimal object with the specified value.
24
+ # Precision parameter is ignored when floatify is loaded.
25
+ #
26
+ # @param value [AbcDecimal, Rational, #to_f] The numeric value to represent.
27
+ # If an AbcDecimal is provided, its internal value is used.
28
+ # Otherwise, the value is converted to a float.
29
+ # @param _precision [Integer] Ignored - included for API compatibility
30
+ # @return [AbcDecimal] A new AbcDecimal instance.
31
+ def initialize(value, _precision = nil)
32
+ @value = case value
33
+ when AbcDecimal
34
+ value.value
35
+ else
36
+ value.to_f
37
+ end
38
+ end
39
+
40
+ # Returns a string representation of the float value.
41
+ #
42
+ # @return [String] The float value as a string
43
+ def to_s
44
+ @value.to_s
45
+ end
46
+
47
+ # Converts the value to a floating-point number.
48
+ #
49
+ # @return [Float] the floating-point representation of the AbcDecimal value
50
+ def to_f
51
+ @value
52
+ end
53
+
54
+ # Creates a new AbcDecimal from a string representation of a number.
55
+ #
56
+ # @param str [String] The string representation of a number to convert to an AbcDecimal
57
+ # @param _precision [Integer] Ignored - included for API compatibility
58
+ # @return [AbcDecimal] A new AbcDecimal instance initialized with the given string value
59
+ def self.from_string(str, _precision = nil)
60
+ new(str)
61
+ end
62
+
63
+ # Creates a new AbcDecimal from a Rational number.
64
+ #
65
+ # @param rational [Rational] The rational number to convert to an AbcDecimal
66
+ # @param _precision [Integer] Ignored - included for API compatibility
67
+ # @return [AbcDecimal] A new AbcDecimal instance with the value of the given rational number
68
+ def self.from_rational(rational, _precision = nil)
69
+ new(rational)
70
+ end
71
+
72
+ # Creates a new AbcDecimal instance from a float value.
73
+ #
74
+ # @param float [Float] The floating point number to convert to an AbcDecimal
75
+ # @param _precision [Integer] Ignored - included for API compatibility
76
+ # @return [AbcDecimal] A new AbcDecimal instance representing the given float value
77
+ def self.from_float(float, _precision = nil)
78
+ new(float)
79
+ end
80
+
81
+ # Creates a new AbcDecimal from an integer value.
82
+ #
83
+ # @param integer [Integer] The integer value to convert to an AbcDecimal
84
+ # @param _precision [Integer] Ignored - included for API compatibility
85
+ # @return [AbcDecimal] A new AbcDecimal instance with the specified integer value
86
+ def self.from_integer(integer, _precision = nil)
87
+ new(integer)
88
+ end
89
+
90
+ # Addition operation
91
+ #
92
+ # Adds another value to this float.
93
+ #
94
+ # @param other [AbcDecimal, Numeric] The value to add. If not an AbcDecimal,
95
+ # it will be converted to one.
96
+ # @return [AbcDecimal] A new AbcDecimal instance with the sum of the two values
97
+ def +(other)
98
+ other_value = other.is_a?(AbcDecimal) ? other.value : AbcDecimal(other).value
99
+ self.class.new(@value + other_value)
100
+ end
101
+
102
+ # Subtracts another numeric value from this AbcDecimal.
103
+ #
104
+ # @param other [AbcDecimal, Numeric] The value to subtract from this AbcDecimal.
105
+ # @return [AbcDecimal] A new AbcDecimal representing the result of the subtraction.
106
+ def -(other)
107
+ other_value = other.is_a?(AbcDecimal) ? other.value : AbcDecimal(other).value
108
+ self.class.new(@value - other_value)
109
+ end
110
+
111
+ # Multiplies this AbcDecimal by another value.
112
+ #
113
+ # @param other [Object] The value to multiply by. If not an AbcDecimal, it will be converted to one.
114
+ # @return [AbcDecimal] A new AbcDecimal instance representing the product of this float and the other value.
115
+ def *(other)
116
+ other_value = other.is_a?(AbcDecimal) ? other.value : AbcDecimal(other).value
117
+ self.class.new(@value * other_value)
118
+ end
119
+
120
+ # Divides this float by another value.
121
+ #
122
+ # @param other [Numeric, AbcDecimal] The divisor, which can be an AbcDecimal instance or any numeric value
123
+ # @return [AbcDecimal] A new AbcDecimal representing the result of the division
124
+ def /(other)
125
+ other_value = other.is_a?(AbcDecimal) ? other.value : AbcDecimal(other).value
126
+ self.class.new(@value / other_value)
127
+ end
128
+
129
+ # Performs modulo operation with another value.
130
+ #
131
+ # @param other [Numeric, AbcDecimal] The divisor for the modulo operation
132
+ # @return [AbcDecimal] A new AbcDecimal containing the remainder after division
133
+ def %(other)
134
+ other_value = other.is_a?(AbcDecimal) ? other.value : AbcDecimal(other).value
135
+ self.class.new(@value % other_value)
136
+ end
137
+
138
+ # Constrains the value to be between the specified minimum and maximum values.
139
+ #
140
+ # @param min [Numeric, AbcDecimal] The minimum value to clamp to
141
+ # @param max [Numeric, AbcDecimal] The maximum value to clamp to
142
+ # @return [Float] A float within the specified range
143
+ def clamp(min, max)
144
+ @value.clamp(AbcDecimal(min).value, AbcDecimal(max).value)
145
+ end
146
+
147
+ # Raises self to the power of another value.
148
+ #
149
+ # @param other [Numeric, AbcDecimal] The exponent to raise this value to
150
+ # @return [AbcDecimal] A new AbcDecimal representing self raised to the power of other
151
+ def **(other)
152
+ other_value = other.is_a?(AbcDecimal) ? other.value : AbcDecimal(other).value
153
+ self.class.new(@value**other_value)
154
+ end
155
+
156
+ # Allows for mixed arithmetic operations between AbcDecimal and other numeric types.
157
+ #
158
+ # @param other [Numeric] The other number to be coerced into an AbcDecimal object
159
+ # @return [Array<AbcDecimal>] A two-element array containing the coerced value and self,
160
+ # allowing Ruby to perform arithmetic operations with mixed types
161
+ def coerce(other)
162
+ [self.class.new(other), self]
163
+ end
164
+
165
+ # Returns a string representation of the float value for inspection purposes.
166
+ #
167
+ # @return [String] A string in the format "ClassName('value')"
168
+ def inspect
169
+ "#{self.class}('#{self}')"
170
+ end
171
+
172
+ # Compares this float value with another value for equality.
173
+ # Attempts to convert the other value to an AbcDecimal if it isn't one already.
174
+ #
175
+ # @param other [Object] The value to compare against this AbcDecimal
176
+ # @return [Boolean] True if the values are equal, false otherwise
177
+ def ==(other)
178
+ @value == (other.is_a?(AbcDecimal) ? other.value : AbcDecimal(other).value)
179
+ end
180
+
181
+ # Compares this AbcDecimal instance with another AbcDecimal or a value that can be
182
+ # converted to an AbcDecimal.
183
+ #
184
+ # @param other [Object] The value to compare with this AbcDecimal.
185
+ # If not an AbcDecimal, it will be converted using AbcDecimal().
186
+ # @return [Integer, nil] Returns -1 if self is less than other,
187
+ # 0 if they are equal,
188
+ # 1 if self is greater than other,
189
+ # or nil if the comparison is not possible.
190
+ def <=>(other)
191
+ @value <=> (other.is_a?(AbcDecimal) ? other.value : AbcDecimal(other).value)
192
+ end
193
+
194
+ # Compares this float with another value.
195
+ #
196
+ # @param other [Object] The value to compare with. Can be an AbcDecimal or any value
197
+ # convertible to AbcDecimal
198
+ # @return [Boolean] true if this float is greater than the other value, false otherwise
199
+ def >(other)
200
+ @value > (other.is_a?(AbcDecimal) ? other.value : AbcDecimal(other).value)
201
+ end
202
+
203
+ # Compares this float value with another value.
204
+ #
205
+ # @param other [Object] The value to compare against. If not an AbcDecimal,
206
+ # it will be converted to one.
207
+ # @return [Boolean] true if this float is greater than or equal to the other value,
208
+ # false otherwise.
209
+ def >=(other)
210
+ @value >= (other.is_a?(AbcDecimal) ? other.value : AbcDecimal(other).value)
211
+ end
212
+
213
+ # Compares this float with another value.
214
+ #
215
+ # @param other [Object] The value to compare with. Will be coerced to AbcDecimal if not already an instance.
216
+ # @return [Boolean] true if this float is less than the other value, false otherwise.
217
+ def <(other)
218
+ @value < (other.is_a?(AbcDecimal) ? other.value : AbcDecimal(other).value)
219
+ end
220
+
221
+ # Compares this AbcDecimal with another value.
222
+ #
223
+ # @param other [AbcDecimal, Numeric] The value to compare with. If not an AbcDecimal,
224
+ # it will be converted to one.
225
+ # @return [Boolean] true if this AbcDecimal is less than or equal to the other value,
226
+ # false otherwise.
227
+ def <=(other)
228
+ @value <= (other.is_a?(AbcDecimal) ? other.value : AbcDecimal(other).value)
229
+ end
230
+
231
+ # Rounds this float to a specified precision.
232
+ #
233
+ # @param args [Array] Arguments to be passed to Float#round. Can include
234
+ # the number of decimal places to round to.
235
+ # @return [AbcDecimal] A new AbcDecimal instance with the rounded value
236
+ def round(*args)
237
+ AbcDecimal(@value.round(*args))
238
+ end
239
+
240
+ # Returns the absolute value (magnitude) of the float number.
241
+ #
242
+ # @param _args [Array] Ignored - included for API compatibility
243
+ # @return [AbcDecimal] The absolute value of the float number
244
+ def abs(*_args)
245
+ AbcDecimal(@value.abs)
246
+ end
247
+
248
+ # Returns the square root of the AbcDecimal value.
249
+ #
250
+ # @return [AbcDecimal] A new AbcDecimal representing the square root of the value
251
+ def sqrt
252
+ AbcDecimal(Math.sqrt(@value))
253
+ end
254
+
255
+ # Returns true if the internal value is negative, false otherwise.
256
+ #
257
+ # @return [Boolean] true if the value is negative, false otherwise
258
+ def negative?
259
+ @value.negative?
260
+ end
261
+
262
+ # Returns true if the internal value is positive, false otherwise.
263
+ #
264
+ # @return [Boolean] true if the value is positive, false otherwise
265
+ def positive?
266
+ @value > 0
267
+ end
268
+
269
+ # Calculates the arctangent of y/x using the signs of the arguments to determine the quadrant.
270
+ #
271
+ # @param y [AbcDecimal, Numeric] The y coordinate
272
+ # @param x [AbcDecimal, Numeric] The x coordinate
273
+ # @return [AbcDecimal] The angle in radians between the positive x-axis and the ray to the point (x,y)
274
+ def self.atan2(y, x)
275
+ y_value = y.is_a?(AbcDecimal) ? y.value : AbcDecimal(y).value
276
+ x_value = x.is_a?(AbcDecimal) ? x.value : AbcDecimal(x).value
277
+ new(Math.atan2(y_value, x_value))
278
+ end
279
+ end
280
+ end
281
+
282
+ # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
@@ -1,4 +1,4 @@
1
- #
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Abachrome
4
4
  module Gamut
@@ -28,7 +28,7 @@ module Abachrome
28
28
  # TODO: - make this work properly
29
29
  def contains?(coordinates)
30
30
  x, y, z = coordinates
31
- x >= 0 && x <= 1 &&
31
+ x.between?(0, 1) &&
32
32
  y >= 0 && y <= 1 &&
33
33
  z >= 0 && z <= 1
34
34
  end
@@ -71,4 +71,4 @@ module Abachrome
71
71
  end
72
72
  end
73
73
 
74
- # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
74
+ # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
@@ -1,4 +1,4 @@
1
- #
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Abachrome
4
4
  module Gamut
@@ -14,7 +14,7 @@ module Abachrome
14
14
 
15
15
  def contains?(coordinates)
16
16
  r, g, b = coordinates
17
- r >= 0 && r <= 1 &&
17
+ r.between?(0, 1) &&
18
18
  g >= 0 && g <= 1 &&
19
19
  b >= 0 && b <= 1
20
20
  end
@@ -24,4 +24,4 @@ module Abachrome
24
24
  end
25
25
  end
26
26
 
27
- # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
27
+ # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
@@ -1,4 +1,4 @@
1
- #
1
+ # frozen_string_literal: true
2
2
 
3
3
  lib
4
4
  abachrome
@@ -22,4 +22,4 @@ module Abachrome
22
22
  end
23
23
  end
24
24
 
25
- # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
25
+ # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Abachrome::Gamut::SRGB - sRGB color gamut definition and validation
2
4
  #
3
5
  # This module defines the sRGB color gamut within the Abachrome color manipulation library.
@@ -34,7 +36,7 @@ module Abachrome
34
36
 
35
37
  def contains?(coordinates)
36
38
  r, g, b = coordinates
37
- r >= 0 && r <= 1 &&
39
+ r.between?(0, 1) &&
38
40
  g >= 0 && g <= 1 &&
39
41
  b >= 0 && b <= 1
40
42
  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,4 +1,4 @@
1
- #
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Abachrome
4
4
  module Illuminants
@@ -32,4 +32,4 @@ module Abachrome
32
32
  end
33
33
  end
34
34
 
35
- # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
35
+ # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
@@ -1,4 +1,4 @@
1
- #
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Abachrome
4
4
  module Illuminants
@@ -30,4 +30,4 @@ module Abachrome
30
30
  end
31
31
  end
32
32
 
33
- # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
33
+ # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
@@ -1,4 +1,4 @@
1
- #
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Abachrome
4
4
  module Illuminants
@@ -26,4 +26,4 @@ module Abachrome
26
26
  end
27
27
  end
28
28
 
29
- # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
29
+ # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
@@ -1,4 +1,4 @@
1
- #
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Abachrome
4
4
  module Illuminants
@@ -34,4 +34,4 @@ module Abachrome
34
34
  end
35
35
  end
36
36
 
37
- # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
37
+ # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
@@ -1,4 +1,4 @@
1
- #
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Abachrome
4
4
  module Illuminants
@@ -26,4 +26,4 @@ module Abachrome
26
26
  end
27
27
  end
28
28
 
29
- # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
29
+ # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.