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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9600763490f935450197f31388435f33c7d82c6dbcf854a8f2d01d8c5a301367
4
- data.tar.gz: 0e5fb3f804f9dd1332e37311f1e63e17b50aa6e204b67944c5338e18879b0ee8
3
+ metadata.gz: 584251d0988a5611a22766c4665f4be880d636c70af0a612d7eb39994e32e1cc
4
+ data.tar.gz: 0c1b82ff4874f2c74d494cdc79b5b2b4c8259c7e387441477a3a6dcb2046faad
5
5
  SHA512:
6
- metadata.gz: 660d4f26b871b165eeb5bbf17c380ebe1ec8ba950746b409d6e44a26dab04c3ae95e3e8952ff9e60dccaef6d7cc5d2386bd8e24730214cabbbab2593b1bbe128
7
- data.tar.gz: 101a8a42a9509aedd238b96e5327441b4d4f79efc33715d857b88f8614f4d72ec1fe0c6af41f629f0c53b8db888ec7cc448ec603e6a0c4d379ff7a0b609af23f
6
+ metadata.gz: 10cd1fd7eb789c3e3fb0d10a1753a139ef83c388d5d22b850397995872784cf8cc2a134d7f423eb8365e609919d2814c1decd16abf9ea7941e2718f587352a46
7
+ data.tar.gz: a0dac04fc6b12902df2650b50794f044467c8521f352123239f862ec547970601a623d04306ab4c124af96e64cd317a77cf1781302d31d54e54d9a005c4dcce9
data/abachrome.gemspec CHANGED
@@ -30,6 +30,7 @@ Gem::Specification.new do |spec|
30
30
  spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
31
31
 
32
32
  # Runtime dependencies
33
+ spec.add_dependency "bigdecimal", "~> 3.1"
33
34
  spec.add_dependency "dry-inflector", "~> 1.0"
34
35
 
35
36
  end
data/devenv.nix CHANGED
@@ -5,7 +5,7 @@
5
5
  env.GREET = "devenv";
6
6
 
7
7
  # https://devenv.sh/packages/
8
- packages = [ pkgs.git];
8
+ packages = [ pkgs.git pkgs.ncurses];
9
9
 
10
10
  languages.ruby.enable = true;
11
11
  languages.ruby.version = "3.3.2";
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Abachrome::AbcDecimal - High-precision decimal arithmetic for color calculations
2
4
  #
3
5
  # This class provides a wrapper around Ruby's BigDecimal to ensure consistent precision
@@ -19,6 +21,7 @@ require "forwardable"
19
21
  module Abachrome
20
22
  class AbcDecimal
21
23
  extend Forwardable
24
+
22
25
  DEFAULT_PRECISION = (ENV["ABC_DECIMAL_PRECISION"] || "24").to_i
23
26
 
24
27
  attr_accessor :value, :precision
@@ -26,7 +29,7 @@ module Abachrome
26
29
  def_delegators :@value, :to_i, :zero?, :nonzero?, :finite?
27
30
 
28
31
  # Initializes a new AbcDecimal object with the specified value and precision.
29
- #
32
+ #
30
33
  # @param value [AbcDecimal, BigDecimal, Rational, #to_s] The numeric value to represent.
31
34
  # If an AbcDecimal is provided, its internal value is used.
32
35
  # If a BigDecimal or Rational is provided, it's used directly.
@@ -49,11 +52,11 @@ module Abachrome
49
52
  end
50
53
 
51
54
  # Returns a string representation of the decimal value.
52
- #
55
+ #
53
56
  # This method converts the internal value to a String, using a fixed-point
54
57
  # notation format. If the internal value is a Rational, it's first converted
55
58
  # to a BigDecimal with the configured precision before string conversion.
56
- #
59
+ #
57
60
  # @return [String] The decimal value as a string in fixed-point notation
58
61
  def to_s
59
62
  if @value.is_a?(Rational)
@@ -64,14 +67,14 @@ module Abachrome
64
67
  end
65
68
 
66
69
  # Converts the decimal value to a floating-point number.
67
- #
70
+ #
68
71
  # @return [Float] the floating-point representation of the AbcDecimal value
69
72
  def to_f
70
73
  @value.to_f
71
74
  end
72
75
 
73
76
  # Creates a new AbcDecimal from a string representation of a number.
74
- #
77
+ #
75
78
  # @param str [String] The string representation of a number to convert to an AbcDecimal
76
79
  # @param precision [Integer] The precision to use for the decimal value (number of significant digits after the decimal point). Defaults to DEFAULT_PRECISION
77
80
  # @return [AbcDecimal] A new AbcDecimal instance initialized with the given string value and precision
@@ -80,7 +83,7 @@ module Abachrome
80
83
  end
81
84
 
82
85
  # Creates a new AbcDecimal from a Rational number.
83
- #
86
+ #
84
87
  # @param rational [Rational] The rational number to convert to an AbcDecimal
85
88
  # @param precision [Integer] The precision to use for the decimal representation, defaults to DEFAULT_PRECISION
86
89
  # @return [AbcDecimal] A new AbcDecimal instance with the value of the given rational number
@@ -89,7 +92,7 @@ module Abachrome
89
92
  end
90
93
 
91
94
  # Creates a new AbcDecimal instance from a float value.
92
- #
95
+ #
93
96
  # @param float [Float] The floating point number to convert to an AbcDecimal
94
97
  # @param precision [Integer] The precision to use for the decimal representation (default: DEFAULT_PRECISION)
95
98
  # @return [AbcDecimal] A new AbcDecimal instance representing the given float value
@@ -98,7 +101,7 @@ module Abachrome
98
101
  end
99
102
 
100
103
  # Creates a new AbcDecimal from an integer value.
101
- #
104
+ #
102
105
  # @param integer [Integer] The integer value to convert to an AbcDecimal
103
106
  # @param precision [Integer] The precision to use for the decimal, defaults to DEFAULT_PRECISION
104
107
  # @return [AbcDecimal] A new AbcDecimal instance with the specified integer value and precision
@@ -119,7 +122,7 @@ module Abachrome
119
122
  end
120
123
 
121
124
  # Subtracts another numeric value from this AbcDecimal.
122
- #
125
+ #
123
126
  # @param other [AbcDecimal, Numeric] The value to subtract from this AbcDecimal.
124
127
  # @return [AbcDecimal] A new AbcDecimal representing the result of the subtraction.
125
128
  def -(other)
@@ -128,14 +131,14 @@ module Abachrome
128
131
  end
129
132
 
130
133
  # Multiplies this AbcDecimal by another value.
131
- #
134
+ #
132
135
  # @param other [Object] The value to multiply by. If not an AbcDecimal, it will be converted to one.
133
136
  # @return [AbcDecimal] A new AbcDecimal instance representing the product of this decimal and the other value.
134
137
  # @example
135
138
  # dec1 = AbcDecimal.new(5)
136
139
  # dec2 = AbcDecimal.new(2)
137
140
  # result = dec1 * dec2 # => AbcDecimal representing 10
138
- #
141
+ #
139
142
  # # With a non-AbcDecimal value
140
143
  # result = dec1 * 3 # => AbcDecimal representing 15
141
144
  def *(other)
@@ -144,7 +147,7 @@ module Abachrome
144
147
  end
145
148
 
146
149
  # Divides this decimal by another value.
147
- #
150
+ #
148
151
  # @param other [Numeric, AbcDecimal] The divisor, which can be an AbcDecimal instance or any numeric value
149
152
  # @return [AbcDecimal] A new AbcDecimal representing the result of the division
150
153
  # @example
@@ -157,7 +160,7 @@ module Abachrome
157
160
  end
158
161
 
159
162
  # Performs modulo operation with another value.
160
- #
163
+ #
161
164
  # @param other [Numeric, AbcDecimal] The divisor for the modulo operation
162
165
  # @return [AbcDecimal] A new AbcDecimal containing the remainder after division
163
166
  def %(other)
@@ -166,7 +169,7 @@ module Abachrome
166
169
  end
167
170
 
168
171
  # Constrains the value to be between the specified minimum and maximum values.
169
- #
172
+ #
170
173
  # @param min [Numeric, AbcDecimal] The minimum value to clamp to
171
174
  # @param max [Numeric, AbcDecimal] The maximum value to clamp to
172
175
  # @return [AbcDecimal] A new AbcDecimal within the specified range
@@ -174,14 +177,14 @@ module Abachrome
174
177
  # AbcDecimal(5).clamp(0, 10) # => 5
175
178
  # AbcDecimal(15).clamp(0, 10) # => 10
176
179
  # AbcDecimal(-5).clamp(0, 10) # => 0
177
- def clamp(min,max)
178
- @value.clamp(AbcDecimal(min),AbcDecimal(max))
180
+ def clamp(min, max)
181
+ @value.clamp(AbcDecimal(min), AbcDecimal(max))
179
182
  end
180
183
 
181
184
  # Raises self to the power of another value.
182
185
  # This method handles different input types, including Rational values and
183
186
  # other AbcDecimal instances.
184
- #
187
+ #
185
188
  # @param other [Numeric, Rational, AbcDecimal] The exponent to raise this value to
186
189
  # @return [AbcDecimal] A new AbcDecimal representing self raised to the power of other
187
190
  def **(other)
@@ -194,7 +197,7 @@ module Abachrome
194
197
  end
195
198
 
196
199
  # Allows for mixed arithmetic operations between AbcDecimal and other numeric types.
197
- #
200
+ #
198
201
  # @param other [Numeric] The other number to be coerced into an AbcDecimal object
199
202
  # @return [Array<AbcDecimal>] A two-element array containing the coerced value and self,
200
203
  # allowing Ruby to perform arithmetic operations with mixed types
@@ -205,7 +208,7 @@ module Abachrome
205
208
  # Returns a string representation of the decimal value for inspection purposes.
206
209
  # This method returns a formatted string that includes the class name and
207
210
  # the string representation of the decimal value itself.
208
- #
211
+ #
209
212
  # @return [String] A string in the format "ClassName('value')"
210
213
  def inspect
211
214
  "#{self.class}('#{self}')"
@@ -213,7 +216,7 @@ module Abachrome
213
216
 
214
217
  # Compares this decimal value with another value for equality.
215
218
  # Attempts to convert the other value to an AbcDecimal if it isn't one already.
216
- #
219
+ #
217
220
  # @param other [Object] The value to compare against this AbcDecimal
218
221
  # @return [Boolean] True if the values are equal, false otherwise
219
222
  def ==(other)
@@ -222,7 +225,7 @@ module Abachrome
222
225
 
223
226
  # Compares this AbcDecimal instance with another AbcDecimal or a value that can be
224
227
  # converted to an AbcDecimal.
225
- #
228
+ #
226
229
  # @param other [Object] The value to compare with this AbcDecimal.
227
230
  # If not an AbcDecimal, it will be converted using AbcDecimal().
228
231
  # @return [Integer, nil] Returns -1 if self is less than other,
@@ -234,7 +237,7 @@ module Abachrome
234
237
  end
235
238
 
236
239
  # Compares this decimal with another value.
237
- #
240
+ #
238
241
  # @param other [Object] The value to compare with. Can be an AbcDecimal or any value
239
242
  # convertible to AbcDecimal
240
243
  # @return [Boolean] true if this decimal is greater than the other value, false otherwise
@@ -243,7 +246,7 @@ module Abachrome
243
246
  end
244
247
 
245
248
  # Compares this decimal value with another value.
246
- #
249
+ #
247
250
  # @param other [Object] The value to compare against. If not an AbcDecimal,
248
251
  # it will be converted to one.
249
252
  # @return [Boolean] true if this decimal is greater than or equal to the other value,
@@ -253,7 +256,7 @@ module Abachrome
253
256
  end
254
257
 
255
258
  # Compares this decimal with another value.
256
- #
259
+ #
257
260
  # @param other [Object] The value to compare with. Will be coerced to AbcDecimal if not already an instance.
258
261
  # @return [Boolean] true if this decimal is less than the other value, false otherwise.
259
262
  # @example
@@ -266,7 +269,7 @@ module Abachrome
266
269
  end
267
270
 
268
271
  # Compares this AbcDecimal with another value.
269
- #
272
+ #
270
273
  # @param other [AbcDecimal, Numeric] The value to compare with. If not an AbcDecimal,
271
274
  # it will be converted to one.
272
275
  # @return [Boolean] true if this AbcDecimal is less than or equal to the other value,
@@ -277,7 +280,7 @@ module Abachrome
277
280
 
278
281
  # @overload round(*args)
279
282
  # Rounds this decimal to a specified precision.
280
- #
283
+ #
281
284
  # @param args [Array] Arguments to be passed to BigDecimal#round. Can include
282
285
  # the number of decimal places to round to and the rounding mode.
283
286
  # @return [AbcDecimal] A new AbcDecimal instance with the rounded value
@@ -292,9 +295,9 @@ module Abachrome
292
295
  end
293
296
 
294
297
  # Returns the absolute value (magnitude) of the decimal number.
295
- #
298
+ #
296
299
  # Wraps BigDecimal#abs to ensure return values are properly converted to AbcDecimal.
297
- #
300
+ #
298
301
  # @param args [Array] Optional arguments to pass to BigDecimal#abs
299
302
  # @return [AbcDecimal] The absolute value of the decimal number
300
303
  def abs(*args)
@@ -304,14 +307,14 @@ module Abachrome
304
307
  # Returns the square root of the AbcDecimal value.
305
308
  # Calculates the square root by using Ruby's built-in Math.sqrt function
306
309
  # and converting the result back to an AbcDecimal.
307
- #
310
+ #
308
311
  # @return [AbcDecimal] A new AbcDecimal representing the square root of the value
309
312
  def sqrt
310
313
  AbcDecimal(Math.sqrt(@value))
311
314
  end
312
315
 
313
316
  # Returns true if the internal value is negative, false otherwise.
314
- #
317
+ #
315
318
  # @return [Boolean] true if the value is negative, false otherwise
316
319
  def negative?
317
320
  @value.negative?
@@ -320,7 +323,7 @@ module Abachrome
320
323
  # Calculates the arctangent of y/x using the signs of the arguments to determine the quadrant.
321
324
  # Unlike the standard Math.atan2, this method accepts AbcDecimal objects or any values
322
325
  # that can be converted to AbcDecimal.
323
- #
326
+ #
324
327
  # @param y [AbcDecimal, Numeric] The y coordinate
325
328
  # @param x [AbcDecimal, Numeric] The x coordinate
326
329
  # @return [AbcDecimal] The angle in radians between the positive x-axis and the ray to the point (x,y)
@@ -333,10 +336,10 @@ module Abachrome
333
336
  end
334
337
 
335
338
  # Creates a new AbcDecimal instance.
336
- #
339
+ #
337
340
  # This is a convenience method that allows creating AbcDecimal objects
338
341
  # without explicitly referencing the Abachrome namespace.
339
- #
342
+ #
340
343
  # @param args [Array] Arguments to pass to the AbcDecimal constructor
341
344
  # @return [Abachrome::AbcDecimal] A new AbcDecimal instance
342
345
  def AbcDecimal(*args)
@@ -344,7 +347,7 @@ def AbcDecimal(*args)
344
347
  end
345
348
 
346
349
  # Creates a new AbcDecimal instance.
347
- #
350
+ #
348
351
  # @param args [Array] Arguments to pass to AbcDecimal.new
349
352
  # @return [Abachrome::AbcDecimal] A new AbcDecimal instance
350
353
  # @example
@@ -354,4 +357,4 @@ def AD(*args)
354
357
  Abachrome::AbcDecimal.new(*args)
355
358
  end
356
359
 
357
- # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
360
+ # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Abachrome::Color - Core color representation class
2
4
  #
3
5
  # This is the central color class that represents colors across multiple color spaces
@@ -27,7 +29,7 @@ module Abachrome
27
29
  attr_reader :color_space, :coordinates, :alpha
28
30
 
29
31
  # Initializes a new Color object with the specified color space, coordinates, and alpha value.
30
- #
32
+ #
31
33
  # @param color_space [ColorSpace] The color space for this color instance
32
34
  # @param coordinates [Array<Numeric, String>] The color coordinates in the specified color space
33
35
  # @param alpha [Numeric, String] The alpha (opacity) value, between 0.0 and 1.0 (default: 1.0)
@@ -51,7 +53,7 @@ module Abachrome
51
53
  end
52
54
 
53
55
  # Creates a new Color instance from RGB values
54
- #
56
+ #
55
57
  # @param r [Numeric] The red component value (typically 0-1)
56
58
  # @param g [Numeric] The green component value (typically 0-1)
57
59
  # @param b [Numeric] The blue component value (typically 0-1)
@@ -63,7 +65,7 @@ module Abachrome
63
65
  end
64
66
 
65
67
  # Creates a new Color instance from LRGB values
66
- #
68
+ #
67
69
  # @param r [Numeric] The red component value (typically 0-1)
68
70
  # @param g [Numeric] The green component value (typically 0-1)
69
71
  # @param b [Numeric] The blue component value (typically 0-1)
@@ -75,7 +77,7 @@ module Abachrome
75
77
  end
76
78
 
77
79
  # Creates a new Color object with OKLAB values.
78
- #
80
+ #
79
81
  # @param l [Float] The lightness component (L) of the OKLAB color space
80
82
  # @param a [Float] The green-red component (a) of the OKLAB color space
81
83
  # @param b [Float] The blue-yellow component (b) of the OKLAB color space
@@ -87,7 +89,7 @@ module Abachrome
87
89
  end
88
90
 
89
91
  # Creates a new color instance in the OKLCH color space.
90
- #
92
+ #
91
93
  # @param l [Numeric] The lightness component (L), typically in range 0..1
92
94
  # @param c [Numeric] The chroma component (C), typically starting from 0 with no upper bound
93
95
  # @param h [Numeric] The hue component (H) in degrees, typically in range 0..360
@@ -98,11 +100,36 @@ module Abachrome
98
100
  new(space, [l, c, h], alpha)
99
101
  end
100
102
 
103
+ # Creates a new Color instance from YIQ values
104
+ #
105
+ # @param y [Numeric] The luma (brightness) component, typically in range 0 to 1
106
+ # @param i [Numeric] The in-phase component (orange-blue), typically in range -0.5957 to 0.5957
107
+ # @param q [Numeric] The quadrature component (purple-green), typically in range -0.5226 to 0.5226
108
+ # @param alpha [Numeric] The alpha (opacity) component value (0-1), defaults to 1.0 (fully opaque)
109
+ # @return [Abachrome::Color] A new Color instance in the YIQ color space
110
+ def self.from_yiq(y, i, q, alpha = 1.0)
111
+ space = ColorSpace.find(:yiq)
112
+ new(space, [y, i, q], alpha)
113
+ end
114
+
115
+ # Creates a new Color instance from CMYK values
116
+ #
117
+ # @param c [Numeric] The cyan component, typically in range 0 to 1
118
+ # @param m [Numeric] The magenta component, typically in range 0 to 1
119
+ # @param y [Numeric] The yellow component, typically in range 0 to 1
120
+ # @param k [Numeric] The key/black component, typically in range 0 to 1
121
+ # @param alpha [Numeric] The alpha (opacity) component value (0-1), defaults to 1.0 (fully opaque)
122
+ # @return [Abachrome::Color] A new Color instance in the CMYK color space
123
+ def self.from_cmyk(c, m, y, k, alpha = 1.0)
124
+ space = ColorSpace.find(:cmyk)
125
+ new(space, [c, m, y, k], alpha)
126
+ end
127
+
101
128
  # Compares this color instance with another for equality.
102
- #
129
+ #
103
130
  # Two colors are considered equal if they have the same color space,
104
131
  # coordinates, and alpha value.
105
- #
132
+ #
106
133
  # @param other [Object] The object to compare with
107
134
  # @return [Boolean] true if the colors are equal, false otherwise
108
135
  def ==(other)
@@ -114,7 +141,7 @@ module Abachrome
114
141
  end
115
142
 
116
143
  # Checks if this color is equal to another color object.
117
- #
144
+ #
118
145
  # @param other [Object] The object to compare with
119
146
  # @return [Boolean] true if the two colors are equal, false otherwise
120
147
  # @see ==
@@ -126,7 +153,7 @@ module Abachrome
126
153
  # based on its color space, coordinates, and alpha value.
127
154
  # The method first converts these components to strings,
128
155
  # then computes a hash of the resulting array.
129
- #
156
+ #
130
157
  # @return [Integer] a hash code that can be used for equality comparison
131
158
  # and as a hash key in Hash objects
132
159
  def hash
@@ -168,4 +195,4 @@ module Abachrome
168
195
  end
169
196
  end
170
197
 
171
- # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
198
+ # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Abachrome::ColorMixins::Blend - Color blending and mixing functionality
2
4
  #
3
5
  # This mixin provides methods for blending and mixing colors together in various color spaces.
@@ -58,7 +60,7 @@ module Abachrome
58
60
  # Blends this color with another color by the specified amount.
59
61
  # This is a destructive version of the blend method, modifying the current
60
62
  # color in place.
61
- #
63
+ #
62
64
  # @param other [Abachrome::Color] The color to blend with
63
65
  # @param amount [Float] The blend amount, between 0.0 and 1.0, where 0.0 is
64
66
  # this color and 1.0 is the other color (default: 0.5)
@@ -72,7 +74,7 @@ module Abachrome
72
74
  end
73
75
 
74
76
  # Alias for the blend method that mixes two colors together.
75
- #
77
+ #
76
78
  # @param other [Abachrome::Color] The color to mix with
77
79
  # @param amount [Float] The amount to mix, between 0.0 and 1.0, where 0.0 returns the original color and 1.0 returns the other color (default: 0.5)
78
80
  # @return [Abachrome::Color] A new color resulting from the mix of the two colors
@@ -81,10 +83,10 @@ module Abachrome
81
83
  end
82
84
 
83
85
  # Mix the current color with another color.
84
- #
86
+ #
85
87
  # This method is an alias for blend!. It combines the current color with
86
88
  # the provided color at the specified amount.
87
- #
89
+ #
88
90
  # @param other [Abachrome::Color] The color to mix with the current color
89
91
  # @param amount [Numeric] The amount of the other color to mix in, from 0 to 1 (default: 0.5)
90
92
  # @return [self] Returns the modified color object
@@ -95,4 +97,4 @@ module Abachrome
95
97
  end
96
98
  end
97
99
 
98
- # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
100
+ # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Abachrome::ColorMixins::Lighten - Color lightness adjustment functionality
2
4
  #
3
5
  # This mixin provides methods for adjusting the lightness of colors by manipulating
@@ -23,7 +25,7 @@ module Abachrome
23
25
  # This method works by extracting the L (lightness) component from the OKLab
24
26
  # representation of the color and increasing it by the given amount, ensuring
25
27
  # the result stays within the valid range of [0, 1].
26
- #
28
+ #
27
29
  # @param amount [Numeric] The amount to increase the lightness by, as a decimal
28
30
  # value between 0 and 1. Defaults to 0.1 (10% increase).
29
31
  # @return [Abachrome::Color] A new Color instance with increased lightness.
@@ -46,7 +48,7 @@ module Abachrome
46
48
  # Increases the lightness of the color by the specified amount and modifies the current color object.
47
49
  # This method changes the color in-place, mutating the current object. The color
48
50
  # is converted to a lightness-based color space if needed to perform the operation.
49
- #
51
+ #
50
52
  # @param amount [Float] The amount to increase the lightness by, as a decimal value
51
53
  # between 0 and 1. Default is 0.1 (10% increase).
52
54
  # @return [Abachrome::Color] Returns self for method chaining.
@@ -59,10 +61,10 @@ module Abachrome
59
61
  end
60
62
 
61
63
  # Darkens a color by decreasing its lightness value.
62
- #
64
+ #
63
65
  # This method is effectively a convenience wrapper around the {#lighten} method,
64
66
  # passing a negative amount value to decrease the lightness instead of increasing it.
65
- #
67
+ #
66
68
  # @param amount [Float] The amount to darken the color by, between 0 and 1.
67
69
  # Defaults to 0.1 (10% darker).
68
70
  # @return [Color] A new color instance with decreased lightness.
@@ -73,7 +75,7 @@ module Abachrome
73
75
 
74
76
  # Decreases the lightness value of the color by the specified amount.
75
77
  # Modifies the color in place.
76
- #
78
+ #
77
79
  # @param amount [Float] The amount to darken the color by, as a value between 0 and 1.
78
80
  # Defaults to 0.1 (10% darker).
79
81
  # @return [Abachrome::Color] Returns self with the modified lightness value.
@@ -85,4 +87,4 @@ module Abachrome
85
87
  end
86
88
  end
87
89
 
88
- # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
90
+ # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Abachrome::ColorMixins::SpectralMix - Kubelka-Munk spectral color mixing
4
+ #
5
+ # This mixin adds spectral mixing capabilities to Color objects using the
6
+ # Kubelka-Munk theory for realistic paint-like color mixing.
7
+ #
8
+ # Unlike simple RGB blending or interpolation in perceptual color spaces,
9
+ # spectral mixing simulates how real pigments interact with light through
10
+ # absorption and scattering. This produces more realistic results, especially
11
+ # when mixing complementary colors.
12
+ #
13
+ # Key features:
14
+ # - Physics-based color mixing using Kubelka-Munk theory
15
+ # - Avoids muddy browns when mixing complementary colors
16
+ # - Supports tinting strength for different pigment concentrations
17
+ # - More realistic than linear RGB or LAB interpolation
18
+
19
+ module Abachrome
20
+ module ColorMixins
21
+ module SpectralMix
22
+ # Mix this color with another color using Kubelka-Munk spectral mixing.
23
+ #
24
+ # This method produces more realistic color mixing than simple RGB or LAB
25
+ # interpolation by simulating how real pigments absorb and scatter light.
26
+ #
27
+ # @param other [Abachrome::Color] The color to mix with
28
+ # @param amount [Float] The mix ratio, between 0 and 1. 0.5 means equal mixing.
29
+ # Values closer to 0 favor this color, values closer to 1 favor the other color.
30
+ # @param tinting_strength_self [Float] Tinting strength of this color (default: 1.0)
31
+ # Higher values mean stronger pigment concentration
32
+ # @param tinting_strength_other [Float] Tinting strength of other color (default: 1.0)
33
+ # @return [Abachrome::Color] A new color representing the spectral mix
34
+ #
35
+ # @example Mix red and blue equally
36
+ # red = Abachrome.from_rgb(1, 0, 0)
37
+ # blue = Abachrome.from_rgb(0, 0, 1)
38
+ # purple = red.spectral_mix(blue, 0.5)
39
+ #
40
+ # @example Mix with 25% of blue
41
+ # mostly_red = red.spectral_mix(blue, 0.25)
42
+ #
43
+ # @example Mix with different tinting strengths
44
+ # # Stronger blue pigment
45
+ # purple = red.spectral_mix(blue, 0.5, tinting_strength_other: 2.0)
46
+ def spectral_mix(other, amount = 0.5, tinting_strength_self: 1.0, tinting_strength_other: 1.0)
47
+ require_relative "../spectral"
48
+
49
+ # Convert amount to weights
50
+ # amount = 0 means 100% self, 0% other
51
+ # amount = 0.5 means 50% self, 50% other
52
+ # amount = 1 means 0% self, 100% other
53
+ weight_self = 1.0 - amount.to_f
54
+ weight_other = amount.to_f
55
+
56
+ colors = [
57
+ { color: self, weight: weight_self },
58
+ { color: other, weight: weight_other }
59
+ ]
60
+
61
+ tinting_strengths = {
62
+ self => tinting_strength_self.to_f,
63
+ other => tinting_strength_other.to_f
64
+ }
65
+
66
+ Spectral.mix(colors, tinting_strengths: tinting_strengths)
67
+ end
68
+ end
69
+ end
70
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Abachrome::ColorMixins::ToColorspace - Color space conversion functionality
2
4
  #
3
5
  # This mixin provides methods for converting colors between different color spaces within
@@ -20,11 +22,11 @@ module Abachrome
20
22
  module ColorMixins
21
23
  module ToColorspace
22
24
  # Converts the current color to the specified target color space.
23
- #
25
+ #
24
26
  # This method transforms the current color into an equivalent color in a different
25
27
  # color space. If the target space is the same as the current color space, no
26
28
  # conversion is performed and the current color is returned.
27
- #
29
+ #
28
30
  # @param target_space [Abachrome::ColorSpace] The target color space to convert to
29
31
  # @return [Abachrome::Color] A new color object in the target color space, or self
30
32
  # if the target space is the same as the current color space
@@ -37,7 +39,7 @@ module Abachrome
37
39
  # Converts the color object to the specified target color space in-place.
38
40
  # This method modifies the current object by changing its color space and
39
41
  # coordinates to match the target color space.
40
- #
42
+ #
41
43
  # @param target_space [Abachrome::ColorSpace] The color space to convert to
42
44
  # @return [Abachrome::Color] Returns self with modified color space and coordinates
43
45
  # @see #to_color_space The non-destructive version that returns a new color object
@@ -51,7 +53,7 @@ module Abachrome
51
53
  end
52
54
 
53
55
  # Convert this color to a different color space.
54
- #
56
+ #
55
57
  # @param space_name [String, Symbol] The name of the target color space to convert to.
56
58
  # @return [Abachrome::Color] A new Color object in the specified color space.
57
59
  # @example
@@ -64,7 +66,7 @@ module Abachrome
64
66
  end
65
67
 
66
68
  # Converts this color to the specified color space in place.
67
- #
69
+ #
68
70
  # @param space_name [String, Symbol] The name or identifier of the target color space to convert to.
69
71
  # @return [Abachrome::Color] Returns self with its values converted to the specified color space.
70
72
  # @raise [Abachrome::ColorSpaceNotFoundError] If the specified color space is not registered.
@@ -77,7 +79,7 @@ module Abachrome
77
79
  end
78
80
 
79
81
  # Convert a color to a specified color space.
80
- #
82
+ #
81
83
  # @param space_name [Symbol, String] The name of the color space to convert to.
82
84
  # @return [Abachrome::Color] A new Color instance in the specified color space.
83
85
  # @example
@@ -88,7 +90,7 @@ module Abachrome
88
90
  end
89
91
 
90
92
  # Converts the color to the specified color space and mutates the current object.
91
- #
93
+ #
92
94
  # @param space_name [Symbol, String] The target color space to convert to (e.g., :oklch, :rgb, :lab)
93
95
  # @return [Abachrome::Color] Returns self after conversion
94
96
  # @see #convert_to!
@@ -102,4 +104,4 @@ module Abachrome
102
104
  end
103
105
  end
104
106
 
105
- # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
107
+ # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.