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.
- checksums.yaml +4 -4
- data/abachrome.gemspec +1 -0
- data/devenv.nix +1 -1
- data/lib/abachrome/abc_decimal.rb +38 -35
- data/lib/abachrome/color.rb +37 -10
- data/lib/abachrome/color_mixins/blend.rb +7 -5
- data/lib/abachrome/color_mixins/lighten.rb +8 -6
- data/lib/abachrome/color_mixins/spectral_mix.rb +70 -0
- data/lib/abachrome/color_mixins/to_colorspace.rb +10 -8
- data/lib/abachrome/color_mixins/to_grayscale.rb +87 -0
- data/lib/abachrome/color_mixins/to_lrgb.rb +14 -12
- data/lib/abachrome/color_mixins/to_oklab.rb +16 -14
- data/lib/abachrome/color_mixins/to_oklch.rb +12 -10
- data/lib/abachrome/color_mixins/to_srgb.rb +17 -15
- data/lib/abachrome/color_models/cmyk.rb +159 -0
- data/lib/abachrome/color_models/hsv.rb +5 -3
- data/lib/abachrome/color_models/lms.rb +3 -1
- data/lib/abachrome/color_models/oklab.rb +3 -1
- data/lib/abachrome/color_models/oklch.rb +6 -4
- data/lib/abachrome/color_models/rgb.rb +4 -2
- data/lib/abachrome/color_models/xyz.rb +3 -1
- data/lib/abachrome/color_models/yiq.rb +37 -0
- data/lib/abachrome/color_space.rb +28 -14
- data/lib/abachrome/converter.rb +10 -8
- data/lib/abachrome/converters/base.rb +13 -11
- data/lib/abachrome/converters/cmyk_to_srgb.rb +42 -0
- data/lib/abachrome/converters/lms_to_lrgb.rb +5 -3
- data/lib/abachrome/converters/lms_to_srgb.rb +6 -4
- data/lib/abachrome/converters/lms_to_xyz.rb +5 -3
- data/lib/abachrome/converters/lrgb_to_lms.rb +3 -1
- data/lib/abachrome/converters/lrgb_to_oklab.rb +5 -3
- data/lib/abachrome/converters/lrgb_to_srgb.rb +6 -4
- data/lib/abachrome/converters/lrgb_to_xyz.rb +5 -3
- data/lib/abachrome/converters/oklab_to_lms.rb +9 -7
- data/lib/abachrome/converters/oklab_to_lrgb.rb +7 -7
- data/lib/abachrome/converters/oklab_to_oklch.rb +4 -2
- data/lib/abachrome/converters/oklab_to_srgb.rb +4 -2
- data/lib/abachrome/converters/oklch_to_lrgb.rb +5 -3
- data/lib/abachrome/converters/oklch_to_oklab.rb +5 -3
- data/lib/abachrome/converters/oklch_to_srgb.rb +6 -4
- data/lib/abachrome/converters/oklch_to_xyz.rb +6 -4
- data/lib/abachrome/converters/srgb_to_cmyk.rb +64 -0
- data/lib/abachrome/converters/srgb_to_lrgb.rb +5 -3
- data/lib/abachrome/converters/srgb_to_oklab.rb +4 -2
- data/lib/abachrome/converters/srgb_to_oklch.rb +5 -3
- data/lib/abachrome/converters/srgb_to_yiq.rb +49 -0
- data/lib/abachrome/converters/xyz_to_lms.rb +5 -3
- data/lib/abachrome/converters/xyz_to_oklab.rb +5 -3
- data/lib/abachrome/converters/yiq_to_srgb.rb +47 -0
- data/lib/abachrome/gamut/base.rb +3 -3
- data/lib/abachrome/gamut/p3.rb +3 -3
- data/lib/abachrome/gamut/rec2020.rb +2 -2
- data/lib/abachrome/gamut/srgb.rb +4 -2
- data/lib/abachrome/illuminants/base.rb +2 -2
- data/lib/abachrome/illuminants/d50.rb +2 -2
- data/lib/abachrome/illuminants/d55.rb +2 -2
- data/lib/abachrome/illuminants/d65.rb +2 -2
- data/lib/abachrome/illuminants/d75.rb +2 -2
- data/lib/abachrome/named/css.rb +149 -149
- data/lib/abachrome/named/tailwind.rb +265 -265
- data/lib/abachrome/outputs/css.rb +2 -2
- data/lib/abachrome/palette.rb +26 -25
- data/lib/abachrome/palette_mixins/interpolate.rb +3 -1
- data/lib/abachrome/palette_mixins/resample.rb +2 -2
- data/lib/abachrome/palette_mixins/stretch_luminance.rb +2 -2
- data/lib/abachrome/parsers/css.rb +86 -71
- data/lib/abachrome/parsers/hex.rb +2 -2
- data/lib/abachrome/parsers/tailwind.rb +8 -8
- data/lib/abachrome/spectral.rb +277 -0
- data/lib/abachrome/to_abcd.rb +4 -4
- data/lib/abachrome/version.rb +2 -2
- data/lib/abachrome.rb +66 -10
- metadata +29 -3
data/lib/abachrome/palette.rb
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Abachrome
|
|
4
4
|
class Palette
|
|
@@ -6,7 +6,7 @@ module Abachrome
|
|
|
6
6
|
|
|
7
7
|
# Initializes a new color palette with the given colors.
|
|
8
8
|
# Automatically converts non-Color objects to Color objects by parsing them as hex values.
|
|
9
|
-
#
|
|
9
|
+
#
|
|
10
10
|
# @param colors [Array] An array of colors to include in the palette. Each element can be
|
|
11
11
|
# either a Color object or a string-convertible object representing a hex color code.
|
|
12
12
|
# @return [Abachrome::Palette] A new palette instance containing the provided colors.
|
|
@@ -17,7 +17,7 @@ module Abachrome
|
|
|
17
17
|
# Adds a color to the palette.
|
|
18
18
|
# Accepts either an Abachrome::Color object or any object that can be
|
|
19
19
|
# converted to a string and parsed as a hex color code.
|
|
20
|
-
#
|
|
20
|
+
#
|
|
21
21
|
# @param color [Abachrome::Color, String, #to_s] The color to add to the palette.
|
|
22
22
|
# If not already an Abachrome::Color object, it will be converted using Color.from_hex
|
|
23
23
|
# @return [Abachrome::Palette] self, enabling method chaining
|
|
@@ -30,7 +30,7 @@ module Abachrome
|
|
|
30
30
|
alias << add
|
|
31
31
|
|
|
32
32
|
# Removes the specified color from the palette.
|
|
33
|
-
#
|
|
33
|
+
#
|
|
34
34
|
# @param color [Abachrome::Color, Object] The color to be removed from the palette
|
|
35
35
|
# @return [Abachrome::Palette] Returns self for method chaining
|
|
36
36
|
def remove(color)
|
|
@@ -39,10 +39,10 @@ module Abachrome
|
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
# Clears all colors from the palette.
|
|
42
|
-
#
|
|
42
|
+
#
|
|
43
43
|
# This method removes all stored colors in the palette. It provides a way to
|
|
44
44
|
# reset the palette to an empty state while maintaining the same palette object.
|
|
45
|
-
#
|
|
45
|
+
#
|
|
46
46
|
# @return [Abachrome::Palette] Returns self for method chaining
|
|
47
47
|
def clear
|
|
48
48
|
@colors.clear
|
|
@@ -50,21 +50,21 @@ module Abachrome
|
|
|
50
50
|
end
|
|
51
51
|
|
|
52
52
|
# Returns the number of colors in the palette.
|
|
53
|
-
#
|
|
53
|
+
#
|
|
54
54
|
# @return [Integer] the number of colors in the palette
|
|
55
55
|
def size
|
|
56
56
|
@colors.size
|
|
57
57
|
end
|
|
58
58
|
|
|
59
59
|
# Returns whether the palette has no colors.
|
|
60
|
-
#
|
|
60
|
+
#
|
|
61
61
|
# @return [Boolean] true if the palette contains no colors, false otherwise
|
|
62
62
|
def empty?
|
|
63
63
|
@colors.empty?
|
|
64
64
|
end
|
|
65
65
|
|
|
66
66
|
# Yields each color in the palette to the given block.
|
|
67
|
-
#
|
|
67
|
+
#
|
|
68
68
|
# @param block [Proc] The block to be executed for each color in the palette.
|
|
69
69
|
# @yield [Abachrome::Color] Each color in the palette.
|
|
70
70
|
# @return [Enumerator] Returns an Enumerator if no block is given.
|
|
@@ -72,11 +72,12 @@ module Abachrome
|
|
|
72
72
|
def each(&block)
|
|
73
73
|
@colors.each(&block)
|
|
74
74
|
end
|
|
75
|
+
|
|
75
76
|
# Calls the given block once for each color in the palette, passing the color and its index as parameters.
|
|
76
|
-
#
|
|
77
|
+
#
|
|
77
78
|
# @example
|
|
78
79
|
# palette.each_with_index { |color, index| puts "Color #{index}: #{color}" }
|
|
79
|
-
#
|
|
80
|
+
#
|
|
80
81
|
# @param block [Proc] The block to be called for each color
|
|
81
82
|
# @yield [color, index] Yields a color and its index
|
|
82
83
|
# @yieldparam color [Abachrome::Color] The color at the current position
|
|
@@ -88,7 +89,7 @@ module Abachrome
|
|
|
88
89
|
end
|
|
89
90
|
|
|
90
91
|
# Maps the palette by applying a block to each color.
|
|
91
|
-
#
|
|
92
|
+
#
|
|
92
93
|
# @param block [Proc] A block that takes a color and returns a new color.
|
|
93
94
|
# @return [Abachrome::Palette] A new palette with the mapped colors.
|
|
94
95
|
# @example
|
|
@@ -99,14 +100,14 @@ module Abachrome
|
|
|
99
100
|
end
|
|
100
101
|
|
|
101
102
|
# Returns a duplicate of the internal colors array.
|
|
102
|
-
#
|
|
103
|
+
#
|
|
103
104
|
# @return [Array<Abachrome::Color>] A duplicate of the palette's color array
|
|
104
105
|
def to_a
|
|
105
106
|
@colors.dup
|
|
106
107
|
end
|
|
107
108
|
|
|
108
109
|
# Access a color in the palette at the specified index.
|
|
109
|
-
#
|
|
110
|
+
#
|
|
110
111
|
# @param index [Integer] The index of the color to retrieve from the palette
|
|
111
112
|
# @return [Abachrome::Color, nil] The color at the specified index, or nil if the index is out of bounds
|
|
112
113
|
def [](index)
|
|
@@ -114,7 +115,7 @@ module Abachrome
|
|
|
114
115
|
end
|
|
115
116
|
|
|
116
117
|
# Slices the palette to create a new palette with a subset of colors.
|
|
117
|
-
#
|
|
118
|
+
#
|
|
118
119
|
# @param start [Integer] The starting index (or range) from which to start the slice.
|
|
119
120
|
# @param length [Integer, nil] The number of colors to include in the slice. If nil and start is an Integer,
|
|
120
121
|
# returns a new palette containing the single color at that index. If start is a Range, length is ignored.
|
|
@@ -125,14 +126,14 @@ module Abachrome
|
|
|
125
126
|
end
|
|
126
127
|
|
|
127
128
|
# Returns the first color in the palette.
|
|
128
|
-
#
|
|
129
|
+
#
|
|
129
130
|
# @return [Abachrome::Color, nil] The first color in the palette, or nil if the palette is empty.
|
|
130
131
|
def first
|
|
131
132
|
@colors.first
|
|
132
133
|
end
|
|
133
134
|
|
|
134
135
|
# Returns the last color in the palette.
|
|
135
|
-
#
|
|
136
|
+
#
|
|
136
137
|
# @return [Abachrome::Color, nil] The last color in the palette or nil if palette is empty.
|
|
137
138
|
def last
|
|
138
139
|
@colors.last
|
|
@@ -141,7 +142,7 @@ module Abachrome
|
|
|
141
142
|
# Returns a new palette with colors sorted by lightness.
|
|
142
143
|
# This method creates a new palette instance containing the same colors as the current
|
|
143
144
|
# palette but sorted in ascending order based on their lightness values.
|
|
144
|
-
#
|
|
145
|
+
#
|
|
145
146
|
# @return [Abachrome::Palette] a new palette with the same colors sorted by lightness
|
|
146
147
|
def sort_by_lightness
|
|
147
148
|
self.class.new(@colors.sort_by(&:lightness))
|
|
@@ -150,7 +151,7 @@ module Abachrome
|
|
|
150
151
|
# Returns a new palette with colors sorted by saturation from lowest to highest.
|
|
151
152
|
# Saturation is determined by the second coordinate (a*) in the OKLAB color space.
|
|
152
153
|
# Lower values represent less saturated colors, higher values represent more saturated colors.
|
|
153
|
-
#
|
|
154
|
+
#
|
|
154
155
|
# @return [Abachrome::Palette] A new palette instance with the same colors sorted by saturation
|
|
155
156
|
def sort_by_saturation
|
|
156
157
|
self.class.new(@colors.sort_by { |c| c.to_oklab.coordinates[1] })
|
|
@@ -160,7 +161,7 @@ module Abachrome
|
|
|
160
161
|
# This method takes each color in the palette and blends it with the accumulated result
|
|
161
162
|
# of previous blends. It starts with the first color and progressively blends each subsequent
|
|
162
163
|
# color at the specified blend amount.
|
|
163
|
-
#
|
|
164
|
+
#
|
|
164
165
|
# @param amount [Float] The blend amount to use between each color pair, between 0.0 and 1.0.
|
|
165
166
|
# Defaults to 0.5 (equal blend).
|
|
166
167
|
# @return [Abachrome::Color, nil] The final blended color result, or nil if the palette is empty.
|
|
@@ -178,7 +179,7 @@ module Abachrome
|
|
|
178
179
|
# This method converts each color in the palette to OKLAB coordinates,
|
|
179
180
|
# calculates the arithmetic mean of these coordinates, and creates a new
|
|
180
181
|
# color from the average values. Alpha values are also averaged.
|
|
181
|
-
#
|
|
182
|
+
#
|
|
182
183
|
# @return [Abachrome::Color, nil] The average color of all colors in the palette,
|
|
183
184
|
# or nil if the palette is empty
|
|
184
185
|
def average
|
|
@@ -198,9 +199,9 @@ module Abachrome
|
|
|
198
199
|
end
|
|
199
200
|
|
|
200
201
|
# Converts the colors in the palette to CSS-formatted strings.
|
|
201
|
-
#
|
|
202
|
+
#
|
|
202
203
|
# The format of the output can be specified with the format parameter.
|
|
203
|
-
#
|
|
204
|
+
#
|
|
204
205
|
# @param format [Symbol] The format to use for the CSS color strings.
|
|
205
206
|
# :hex - Outputs colors in hexadecimal format (e.g., "#RRGGBB")
|
|
206
207
|
# :rgb - Outputs colors in rgb() function format
|
|
@@ -223,7 +224,7 @@ module Abachrome
|
|
|
223
224
|
end
|
|
224
225
|
|
|
225
226
|
# Returns a string representation of the palette for inspection purposes.
|
|
226
|
-
#
|
|
227
|
+
#
|
|
227
228
|
# @return [String] A string containing the class name and a list of colors in the palette
|
|
228
229
|
def inspect
|
|
229
230
|
"#<#{self.class} colors=#{@colors.map(&:to_s)}>"
|
|
@@ -240,4 +241,4 @@ module Abachrome
|
|
|
240
241
|
end
|
|
241
242
|
end
|
|
242
243
|
|
|
243
|
-
# Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
|
|
244
|
+
# Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
# Abachrome::PaletteMixins::Interpolate - Color palette interpolation functionality
|
|
2
4
|
#
|
|
3
5
|
# This mixin provides methods for interpolating between adjacent colors in a palette to create
|
|
@@ -48,4 +50,4 @@ module Abachrome
|
|
|
48
50
|
end
|
|
49
51
|
end
|
|
50
52
|
|
|
51
|
-
# Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
|
|
53
|
+
# 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 PaletteMixins
|
|
@@ -58,4 +58,4 @@ module Abachrome
|
|
|
58
58
|
end
|
|
59
59
|
end
|
|
60
60
|
|
|
61
|
-
# Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
|
|
61
|
+
# 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 PaletteMixins
|
|
@@ -69,4 +69,4 @@ module Abachrome
|
|
|
69
69
|
end
|
|
70
70
|
end
|
|
71
71
|
|
|
72
|
-
# Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
|
|
72
|
+
# Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
#
|
|
2
4
|
# Abachrome::Parsers::CSS - CSS color format parser
|
|
3
5
|
#
|
|
@@ -36,8 +38,6 @@ module Abachrome
|
|
|
36
38
|
parse_functional_color(input)
|
|
37
39
|
end
|
|
38
40
|
|
|
39
|
-
private
|
|
40
|
-
|
|
41
41
|
def self.parse_named_color(input)
|
|
42
42
|
# Check if input matches a named color
|
|
43
43
|
rgb_values = Named::CSS.method(input.to_sym)&.call
|
|
@@ -53,27 +53,25 @@ module Abachrome
|
|
|
53
53
|
def self.parse_functional_color(input)
|
|
54
54
|
case input
|
|
55
55
|
when /^rgb\((.+)\)$/
|
|
56
|
-
parse_rgb(
|
|
56
|
+
parse_rgb(::Regexp.last_match(1))
|
|
57
57
|
when /^rgba\((.+)\)$/
|
|
58
|
-
parse_rgba(
|
|
58
|
+
parse_rgba(::Regexp.last_match(1))
|
|
59
59
|
when /^hsl\((.+)\)$/
|
|
60
|
-
parse_hsl(
|
|
60
|
+
parse_hsl(::Regexp.last_match(1))
|
|
61
61
|
when /^hsla\((.+)\)$/
|
|
62
|
-
parse_hsla(
|
|
62
|
+
parse_hsla(::Regexp.last_match(1))
|
|
63
63
|
when /^hwb\((.+)\)$/
|
|
64
|
-
parse_hwb(
|
|
64
|
+
parse_hwb(::Regexp.last_match(1))
|
|
65
65
|
when /^lab\((.+)\)$/
|
|
66
|
-
parse_lab(
|
|
66
|
+
parse_lab(::Regexp.last_match(1))
|
|
67
67
|
when /^lch\((.+)\)$/
|
|
68
|
-
parse_lch(
|
|
68
|
+
parse_lch(::Regexp.last_match(1))
|
|
69
69
|
when /^oklab\((.+)\)$/
|
|
70
|
-
parse_oklab(
|
|
70
|
+
parse_oklab(::Regexp.last_match(1))
|
|
71
71
|
when /^oklch\((.+)\)$/
|
|
72
|
-
parse_oklch(
|
|
72
|
+
parse_oklch(::Regexp.last_match(1))
|
|
73
73
|
when /^color\((.+)\)$/
|
|
74
|
-
parse_color_function(
|
|
75
|
-
else
|
|
76
|
-
nil
|
|
74
|
+
parse_color_function(::Regexp.last_match(1))
|
|
77
75
|
end
|
|
78
76
|
end
|
|
79
77
|
|
|
@@ -171,39 +169,43 @@ module Abachrome
|
|
|
171
169
|
when "srgb"
|
|
172
170
|
values = parse_color_values(values_str, 3)
|
|
173
171
|
return nil unless values
|
|
172
|
+
|
|
174
173
|
r, g, b = values
|
|
175
174
|
Color.from_rgb(r, g, b)
|
|
176
175
|
when "srgb-linear"
|
|
177
176
|
values = parse_color_values(values_str, 3)
|
|
178
177
|
return nil unless values
|
|
178
|
+
|
|
179
179
|
r, g, b = values
|
|
180
180
|
Color.from_lrgb(r, g, b)
|
|
181
181
|
when "display-p3"
|
|
182
182
|
# For now, approximate as sRGB
|
|
183
183
|
values = parse_color_values(values_str, 3)
|
|
184
184
|
return nil unless values
|
|
185
|
+
|
|
185
186
|
r, g, b = values
|
|
186
187
|
Color.from_rgb(r, g, b)
|
|
187
188
|
when "a98-rgb"
|
|
188
189
|
# For now, approximate as sRGB
|
|
189
190
|
values = parse_color_values(values_str, 3)
|
|
190
191
|
return nil unless values
|
|
192
|
+
|
|
191
193
|
r, g, b = values
|
|
192
194
|
Color.from_rgb(r, g, b)
|
|
193
195
|
when "prophoto-rgb"
|
|
194
196
|
# For now, approximate as sRGB
|
|
195
197
|
values = parse_color_values(values_str, 3)
|
|
196
198
|
return nil unless values
|
|
199
|
+
|
|
197
200
|
r, g, b = values
|
|
198
201
|
Color.from_rgb(r, g, b)
|
|
199
202
|
when "rec2020"
|
|
200
203
|
# For now, approximate as sRGB
|
|
201
204
|
values = parse_color_values(values_str, 3)
|
|
202
205
|
return nil unless values
|
|
206
|
+
|
|
203
207
|
r, g, b = values
|
|
204
208
|
Color.from_rgb(r, g, b)
|
|
205
|
-
else
|
|
206
|
-
nil
|
|
207
209
|
end
|
|
208
210
|
end
|
|
209
211
|
|
|
@@ -224,15 +226,16 @@ module Abachrome
|
|
|
224
226
|
|
|
225
227
|
parsed = []
|
|
226
228
|
values.each_with_index do |v, i|
|
|
227
|
-
if i
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
229
|
+
val = if i.zero? # Hue
|
|
230
|
+
parse_angle_value(v)
|
|
231
|
+
|
|
232
|
+
else # Saturation, Lightness, Alpha
|
|
233
|
+
parse_percentage_or_number(v)
|
|
234
|
+
|
|
235
|
+
end
|
|
236
|
+
return nil unless val
|
|
237
|
+
|
|
238
|
+
parsed << val
|
|
236
239
|
end
|
|
237
240
|
parsed
|
|
238
241
|
end
|
|
@@ -306,66 +309,78 @@ module Abachrome
|
|
|
306
309
|
def self.parse_numeric_value(str)
|
|
307
310
|
return nil unless str
|
|
308
311
|
|
|
309
|
-
if str.end_with?(
|
|
310
|
-
(str.chomp(
|
|
312
|
+
if str.end_with?("%")
|
|
313
|
+
(str.chomp("%").to_f / 100.0)
|
|
311
314
|
else
|
|
312
315
|
str.to_f
|
|
313
316
|
end
|
|
314
|
-
rescue
|
|
317
|
+
rescue StandardError
|
|
315
318
|
nil
|
|
316
319
|
end
|
|
317
320
|
|
|
318
321
|
def self.parse_percentage_or_number(str)
|
|
319
322
|
return nil unless str
|
|
320
323
|
|
|
321
|
-
if str.end_with?(
|
|
322
|
-
str.chomp(
|
|
324
|
+
if str.end_with?("%")
|
|
325
|
+
str.chomp("%").to_f / 100.0
|
|
323
326
|
else
|
|
324
327
|
str.to_f
|
|
325
328
|
end
|
|
326
|
-
rescue
|
|
329
|
+
rescue StandardError
|
|
327
330
|
nil
|
|
328
331
|
end
|
|
329
332
|
|
|
330
333
|
def self.parse_angle_value(str)
|
|
331
334
|
return nil unless str
|
|
332
335
|
|
|
333
|
-
if str.end_with?(
|
|
334
|
-
str.chomp(
|
|
335
|
-
elsif str.end_with?(
|
|
336
|
-
str.chomp(
|
|
337
|
-
elsif str.end_with?(
|
|
338
|
-
str.chomp(
|
|
339
|
-
elsif str.end_with?(
|
|
340
|
-
str.chomp(
|
|
336
|
+
if str.end_with?("deg")
|
|
337
|
+
str.chomp("deg").to_f
|
|
338
|
+
elsif str.end_with?("rad")
|
|
339
|
+
str.chomp("rad").to_f * 180.0 / Math::PI
|
|
340
|
+
elsif str.end_with?("grad")
|
|
341
|
+
str.chomp("grad").to_f * 0.9
|
|
342
|
+
elsif str.end_with?("turn")
|
|
343
|
+
str.chomp("turn").to_f * 360.0
|
|
341
344
|
else
|
|
342
345
|
str.to_f # Assume degrees
|
|
343
346
|
end
|
|
344
|
-
rescue
|
|
347
|
+
rescue StandardError
|
|
345
348
|
nil
|
|
346
349
|
end
|
|
347
350
|
|
|
348
351
|
# Color space conversion functions
|
|
349
352
|
|
|
350
353
|
def self.hsl_to_rgb(h, s, l)
|
|
351
|
-
h
|
|
352
|
-
|
|
353
|
-
c = (1 - (2 * l - 1).abs) * s
|
|
354
|
-
x = c * (1 - ((h * 6) % 2 - 1).abs)
|
|
355
|
-
m = l - c / 2
|
|
356
|
-
|
|
357
|
-
if h < 1.0/6
|
|
358
|
-
r
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
elsif h <
|
|
362
|
-
r
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
elsif h <
|
|
366
|
-
r
|
|
354
|
+
h /= 360.0 # Normalize hue to 0-1
|
|
355
|
+
|
|
356
|
+
c = (1 - ((2 * l) - 1).abs) * s
|
|
357
|
+
x = c * (1 - (((h * 6) % 2) - 1).abs)
|
|
358
|
+
m = l - (c / 2)
|
|
359
|
+
|
|
360
|
+
if h < 1.0 / 6
|
|
361
|
+
r = c
|
|
362
|
+
g = x
|
|
363
|
+
b = 0
|
|
364
|
+
elsif h < 2.0 / 6
|
|
365
|
+
r = x
|
|
366
|
+
g = c
|
|
367
|
+
b = 0
|
|
368
|
+
elsif h < 3.0 / 6
|
|
369
|
+
r = 0
|
|
370
|
+
g = c
|
|
371
|
+
b = x
|
|
372
|
+
elsif h < 4.0 / 6
|
|
373
|
+
r = 0
|
|
374
|
+
g = x
|
|
375
|
+
b = c
|
|
376
|
+
elsif h < 5.0 / 6
|
|
377
|
+
r = x
|
|
378
|
+
g = 0
|
|
379
|
+
b = c
|
|
367
380
|
else
|
|
368
|
-
r
|
|
381
|
+
r = c
|
|
382
|
+
g = 0
|
|
383
|
+
b = x
|
|
369
384
|
end
|
|
370
385
|
|
|
371
386
|
[r + m, g + m, b + m]
|
|
@@ -373,7 +388,7 @@ module Abachrome
|
|
|
373
388
|
|
|
374
389
|
def self.hwb_to_rgb(h, w, b)
|
|
375
390
|
# Normalize values
|
|
376
|
-
h
|
|
391
|
+
h /= 360.0
|
|
377
392
|
|
|
378
393
|
# Calculate RGB from HSL equivalent
|
|
379
394
|
if w + b >= 1
|
|
@@ -384,9 +399,9 @@ module Abachrome
|
|
|
384
399
|
r, g, b_rgb = rgb
|
|
385
400
|
|
|
386
401
|
# Apply whiteness and blackness
|
|
387
|
-
r = r * (1 - w - b) + w
|
|
388
|
-
g = g * (1 - w - b) + w
|
|
389
|
-
b_rgb = b_rgb * (1 - w - b) + w
|
|
402
|
+
r = (r * (1 - w - b)) + w
|
|
403
|
+
g = (g * (1 - w - b)) + w
|
|
404
|
+
b_rgb = (b_rgb * (1 - w - b)) + w
|
|
390
405
|
|
|
391
406
|
[r, g, b_rgb]
|
|
392
407
|
end
|
|
@@ -395,12 +410,12 @@ module Abachrome
|
|
|
395
410
|
def self.lab_to_xyz(l, a, b)
|
|
396
411
|
# CIELAB to XYZ conversion (D65 white point)
|
|
397
412
|
y = (l + 16) / 116
|
|
398
|
-
x = a / 500 + y
|
|
399
|
-
z = y - b / 200
|
|
413
|
+
x = (a / 500) + y
|
|
414
|
+
z = y - (b / 200)
|
|
400
415
|
|
|
401
|
-
x = x**3 > 0.008856 ? x**3 : (x - 16/116) / 7.787
|
|
402
|
-
y = y**3 > 0.008856 ? y**3 : (y - 16/116) / 7.787
|
|
403
|
-
z = z**3 > 0.008856 ? z**3 : (z - 16/116) / 7.787
|
|
416
|
+
x = x**3 > 0.008856 ? x**3 : (x - (16 / 116)) / 7.787
|
|
417
|
+
y = y**3 > 0.008856 ? y**3 : (y - (16 / 116)) / 7.787
|
|
418
|
+
z = z**3 > 0.008856 ? z**3 : (z - (16 / 116)) / 7.787
|
|
404
419
|
|
|
405
420
|
# D65 white point
|
|
406
421
|
x *= 0.95047
|
|
@@ -419,14 +434,14 @@ module Abachrome
|
|
|
419
434
|
|
|
420
435
|
def self.xyz_to_rgb(x, y, z)
|
|
421
436
|
# XYZ to linear RGB
|
|
422
|
-
r = x *
|
|
423
|
-
g = x * -0.9689 + y *
|
|
424
|
-
b = x *
|
|
437
|
+
r = (x * 3.2406) + (y * -1.5372) + (z * -0.4986)
|
|
438
|
+
g = (x * -0.9689) + (y * 1.8758) + (z * 0.0415)
|
|
439
|
+
b = (x * 0.0557) + (y * -0.2040) + (z * 1.0570)
|
|
425
440
|
|
|
426
441
|
# Linear RGB to sRGB
|
|
427
442
|
[r, g, b].map do |v|
|
|
428
443
|
if v > 0.0031308
|
|
429
|
-
1.055 * (v
|
|
444
|
+
(1.055 * (v**(1 / 2.4))) - 0.055
|
|
430
445
|
else
|
|
431
446
|
12.92 * v
|
|
432
447
|
end
|
|
@@ -434,4 +449,4 @@ module Abachrome
|
|
|
434
449
|
end
|
|
435
450
|
end
|
|
436
451
|
end
|
|
437
|
-
end
|
|
452
|
+
end
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Abachrome
|
|
4
4
|
module Parsers
|
|
@@ -49,4 +49,4 @@ module Abachrome
|
|
|
49
49
|
end
|
|
50
50
|
end
|
|
51
51
|
|
|
52
|
-
# Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
|
|
52
|
+
# 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 Parsers
|
|
@@ -7,7 +7,7 @@ module Abachrome
|
|
|
7
7
|
# - gray-400
|
|
8
8
|
# - blue-900/20 (with opacity)
|
|
9
9
|
# - slate-50
|
|
10
|
-
TAILWIND_PATTERN =
|
|
10
|
+
TAILWIND_PATTERN = %r{^([a-z]+)-(\d+)(?:/(\d+(?:\.\d+)?))?$}
|
|
11
11
|
|
|
12
12
|
def self.parse(input)
|
|
13
13
|
match = input.match(TAILWIND_PATTERN)
|
|
@@ -29,12 +29,12 @@ module Abachrome
|
|
|
29
29
|
|
|
30
30
|
# Calculate alpha from opacity percentage if provided
|
|
31
31
|
alpha = if opacity
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
32
|
+
opacity_value = opacity.to_f
|
|
33
|
+
# Opacity in Tailwind is a percentage (0-100)
|
|
34
|
+
opacity_value / 100.0
|
|
35
|
+
else
|
|
36
|
+
1.0
|
|
37
|
+
end
|
|
38
38
|
|
|
39
39
|
Color.from_rgb(r, g, b, alpha)
|
|
40
40
|
end
|