sass-embedded 1.79.2 → 1.79.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/exe/sass +1 -9
- data/ext/sass/Rakefile +49 -16
- data/ext/sass/package.json +1 -1
- data/lib/sass/compiler/connection.rb +1 -9
- data/lib/sass/compiler/host/protofier.rb +16 -55
- data/lib/sass/elf.rb +4 -4
- data/lib/sass/embedded/version.rb +1 -1
- data/lib/sass/value/color/channel.rb +79 -0
- data/lib/sass/value/color/conversions.rb +464 -0
- data/lib/sass/value/color/gamut_map_method/clip.rb +45 -0
- data/lib/sass/value/color/gamut_map_method/local_minde.rb +94 -0
- data/lib/sass/value/color/gamut_map_method.rb +45 -0
- data/lib/sass/value/color/interpolation_method.rb +51 -0
- data/lib/sass/value/color/space/a98_rgb.rb +57 -0
- data/lib/sass/value/color/space/display_p3.rb +57 -0
- data/lib/sass/value/color/space/hsl.rb +65 -0
- data/lib/sass/value/color/space/hwb.rb +70 -0
- data/lib/sass/value/color/space/lab.rb +77 -0
- data/lib/sass/value/color/space/lch.rb +53 -0
- data/lib/sass/value/color/space/lms.rb +129 -0
- data/lib/sass/value/color/space/oklab.rb +66 -0
- data/lib/sass/value/color/space/oklch.rb +54 -0
- data/lib/sass/value/color/space/prophoto_rgb.rb +59 -0
- data/lib/sass/value/color/space/rec2020.rb +69 -0
- data/lib/sass/value/color/space/rgb.rb +52 -0
- data/lib/sass/value/color/space/srgb.rb +141 -0
- data/lib/sass/value/color/space/srgb_linear.rb +72 -0
- data/lib/sass/value/color/space/utils.rb +86 -0
- data/lib/sass/value/color/space/xyz_d50.rb +100 -0
- data/lib/sass/value/color/space/xyz_d65.rb +57 -0
- data/lib/sass/value/color/space.rb +198 -0
- data/lib/sass/value/color.rb +537 -162
- data/lib/sass/value/fuzzy_math.rb +30 -3
- data/lib/sass/value/string.rb +1 -1
- metadata +29 -5
data/lib/sass/value/color.rb
CHANGED
@@ -1,5 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'color/channel'
|
4
|
+
require_relative 'color/conversions'
|
5
|
+
require_relative 'color/gamut_map_method'
|
6
|
+
require_relative 'color/interpolation_method'
|
7
|
+
require_relative 'color/space'
|
8
|
+
|
3
9
|
module Sass
|
4
10
|
module Value
|
5
11
|
# Sass's color type.
|
@@ -18,90 +24,149 @@ module Sass
|
|
18
24
|
# @param lightness [Numeric]
|
19
25
|
# @param whiteness [Numeric]
|
20
26
|
# @param blackness [Numeric]
|
27
|
+
# @param a [Numeric]
|
28
|
+
# @param b [Numeric]
|
29
|
+
# @param chroma [Numeric]
|
30
|
+
# @param x [Numeric]
|
31
|
+
# @param y [Numeric]
|
32
|
+
# @param z [Numeric]
|
21
33
|
# @param alpha [Numeric]
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
34
|
+
# @param space [::String]
|
35
|
+
# @overload initialize(red: nil, green: nil, blue: nil, alpha: nil, space: 'rgb')
|
36
|
+
# @overload initialize(hue: nil, saturation: nil, lightness: nil, alpha: nil, space: 'hsl')
|
37
|
+
# @overload initialize(hue: nil, whiteness: nil, blackness: nil, alpha: nil, space: 'hwb')
|
38
|
+
# @overload initialize(lightness: nil, a: nil, b: nil, alpha: nil, space: 'lab')
|
39
|
+
# @overload initialize(lightness: nil, a: nil, b: nil, alpha: nil, space: 'oklab')
|
40
|
+
# @overload initialize(lightness: nil, chroma: nil, hue: nil, alpha: nil, space: 'lch')
|
41
|
+
# @overload initialize(lightness: nil, chroma: nil, hue: nil, alpha: nil, space: 'oklch')
|
42
|
+
# @overload initialize(red: nil, green: nil, blue: nil, alpha: nil, space: 'a98-rgb')
|
43
|
+
# @overload initialize(red: nil, green: nil, blue: nil, alpha: nil, space: 'display-p3')
|
44
|
+
# @overload initialize(red: nil, green: nil, blue: nil, alpha: nil, space: 'prophoto-rgb')
|
45
|
+
# @overload initialize(red: nil, green: nil, blue: nil, alpha: nil, space: 'rec2020')
|
46
|
+
# @overload initialize(red: nil, green: nil, blue: nil, alpha: nil, space: 'srgb')
|
47
|
+
# @overload initialize(red: nil, green: nil, blue: nil, alpha: nil, space: 'srgb-linear')
|
48
|
+
# @overload initialize(x: nil, y: nil, z: nil, alpha: nil, space: 'xyz')
|
49
|
+
# @overload initialize(x: nil, y: nil, z: nil, alpha: nil, space: 'xyz-d50')
|
50
|
+
# @overload initialize(x: nil, y: nil, z: nil, alpha: nil, space: 'xyz-d65')
|
51
|
+
def initialize(**options)
|
52
|
+
unless options.key?(:space)
|
53
|
+
options[:space] = case options
|
54
|
+
in {red: _, green: _, blue: _}
|
55
|
+
'rgb'
|
56
|
+
in {hue: _, saturation: _, lightness: _}
|
57
|
+
'hsl'
|
58
|
+
in {hue: _, whiteness: _, blackness: _}
|
59
|
+
'hwb'
|
60
|
+
else
|
61
|
+
raise Sass::ScriptError.new('No color space found', 'space')
|
62
|
+
end
|
48
63
|
end
|
49
|
-
end
|
50
64
|
|
51
|
-
|
52
|
-
def red
|
53
|
-
hsl_to_rgb unless defined?(@red)
|
65
|
+
space = Space.from_name(options[:space])
|
54
66
|
|
55
|
-
|
67
|
+
keys = _assert_options(space, options)
|
68
|
+
|
69
|
+
_initialize_for_space_internal(space,
|
70
|
+
options[keys[0]],
|
71
|
+
options[keys[1]],
|
72
|
+
options[keys[2]],
|
73
|
+
options.fetch(:alpha, 1))
|
56
74
|
end
|
57
75
|
|
58
|
-
# @return [
|
59
|
-
def
|
60
|
-
|
76
|
+
# @return [::String]
|
77
|
+
def space
|
78
|
+
_space.name
|
79
|
+
end
|
61
80
|
|
62
|
-
|
81
|
+
# @param space [::String]
|
82
|
+
# @return [Color]
|
83
|
+
def to_space(space)
|
84
|
+
_to_space(Space.from_name(space))
|
63
85
|
end
|
64
86
|
|
65
|
-
# @
|
66
|
-
|
67
|
-
|
87
|
+
# @param space [::String]
|
88
|
+
# @return [::Boolean]
|
89
|
+
def in_gamut?(space = nil)
|
90
|
+
return to_space(space)._in_gamut? unless space.nil?
|
68
91
|
|
69
|
-
|
92
|
+
_in_gamut?
|
70
93
|
end
|
71
94
|
|
72
|
-
# @
|
73
|
-
|
74
|
-
|
95
|
+
# @param method [::String]
|
96
|
+
# @param space [::String]
|
97
|
+
# @return [Color]
|
98
|
+
def to_gamut(method:, space: nil)
|
99
|
+
return to_space(space).to_gamut(method:)._to_space(_space) unless space.nil?
|
75
100
|
|
76
|
-
|
101
|
+
_to_gamut(GamutMapMethod.from_name(method, 'method'))
|
77
102
|
end
|
78
103
|
|
79
|
-
# @return [Numeric]
|
80
|
-
def
|
81
|
-
|
104
|
+
# @return [Array<Numeric>]
|
105
|
+
def channels_or_nil
|
106
|
+
[channel0_or_nil, channel1_or_nil, channel2_or_nil].freeze
|
107
|
+
end
|
82
108
|
|
83
|
-
|
109
|
+
# @return [Array<Numeric>]
|
110
|
+
def channels
|
111
|
+
[channel0, channel1, channel2].freeze
|
84
112
|
end
|
85
113
|
|
114
|
+
# @param channel [::String]
|
115
|
+
# @param space [::String]
|
86
116
|
# @return [Numeric]
|
87
|
-
def
|
88
|
-
|
117
|
+
def channel(channel, space: nil)
|
118
|
+
return to_space(space).channel(channel) unless space.nil?
|
119
|
+
|
120
|
+
channels = _space.channels
|
121
|
+
return channel0 if channel == channels[0].name
|
122
|
+
return channel1 if channel == channels[1].name
|
123
|
+
return channel2 if channel == channels[2].name
|
124
|
+
return alpha if channel == 'alpha'
|
89
125
|
|
90
|
-
|
126
|
+
raise Sass::ScriptError.new("Color #{self} doesn't have a channel named \"#{channel}\".", channel)
|
91
127
|
end
|
92
128
|
|
93
|
-
# @
|
94
|
-
|
95
|
-
|
129
|
+
# @param channel [::String]
|
130
|
+
# @return [::Boolean]
|
131
|
+
def channel_missing?(channel)
|
132
|
+
channels = _space.channels
|
133
|
+
return channel0_missing? if channel == channels[0].name
|
134
|
+
return channel1_missing? if channel == channels[1].name
|
135
|
+
return channel2_missing? if channel == channels[2].name
|
136
|
+
return alpha_missing? if channel == 'alpha'
|
137
|
+
|
138
|
+
raise Sass::ScriptError.new("Color #{self} doesn't have a channel named \"#{channel}\".", channel)
|
96
139
|
end
|
97
140
|
|
98
|
-
# @
|
99
|
-
|
100
|
-
|
141
|
+
# @param channel [::String]
|
142
|
+
# @param space [::String]
|
143
|
+
# @return [::Boolean]
|
144
|
+
def channel_powerless?(channel, space: nil)
|
145
|
+
return to_space(space).channel_powerless?(channel) unless space.nil?
|
146
|
+
|
147
|
+
channels = _space.channels
|
148
|
+
return channel0_powerless? if channel == channels[0].name
|
149
|
+
return channel1_powerless? if channel == channels[1].name
|
150
|
+
return channel2_powerless? if channel == channels[2].name
|
151
|
+
return false if channel == 'alpha'
|
152
|
+
|
153
|
+
raise Sass::ScriptError.new("Color #{self} doesn't have a channel named \"#{channel}\".", channel)
|
101
154
|
end
|
102
155
|
|
103
|
-
# @
|
104
|
-
|
156
|
+
# @param other [Color]
|
157
|
+
# @param method [::String]
|
158
|
+
# @param weight [Numeric]
|
159
|
+
# @return [Color]
|
160
|
+
def interpolate(other, method: nil, weight: nil)
|
161
|
+
interpolation_method = if !method.nil?
|
162
|
+
InterpolationMethod.new(_space, HueInterpolationMethod.from_name(method))
|
163
|
+
elsif !_space.polar?
|
164
|
+
InterpolationMethod.new(_space)
|
165
|
+
else
|
166
|
+
InterpolationMethod.new(_space, :shorter)
|
167
|
+
end
|
168
|
+
_interpolate(other, interpolation_method, weight:)
|
169
|
+
end
|
105
170
|
|
106
171
|
# @param red [Numeric]
|
107
172
|
# @param green [Numeric]
|
@@ -111,52 +176,175 @@ module Sass
|
|
111
176
|
# @param lightness [Numeric]
|
112
177
|
# @param whiteness [Numeric]
|
113
178
|
# @param blackness [Numeric]
|
179
|
+
# @param a [Numeric]
|
180
|
+
# @param b [Numeric]
|
181
|
+
# @param chroma [Numeric]
|
182
|
+
# @param x [Numeric]
|
183
|
+
# @param y [Numeric]
|
184
|
+
# @param z [Numeric]
|
114
185
|
# @param alpha [Numeric]
|
186
|
+
# @param space [::String]
|
187
|
+
# @overload change(red: nil, green: nil, blue: nil, alpha: nil, space: 'rgb')
|
188
|
+
# @overload change(hue: nil, saturation: nil, lightness: nil, alpha: nil, space: 'hsl')
|
189
|
+
# @overload change(hue: nil, whiteness: nil, blackness: nil, alpha: nil, space: 'hwb')
|
190
|
+
# @overload change(lightness: nil, a: nil, b: nil, alpha: nil, space: 'lab')
|
191
|
+
# @overload change(lightness: nil, a: nil, b: nil, alpha: nil, space: 'oklab')
|
192
|
+
# @overload change(lightness: nil, chroma: nil, hue: nil, alpha: nil, space: 'lch')
|
193
|
+
# @overload change(lightness: nil, chroma: nil, hue: nil, alpha: nil, space: 'oklch')
|
194
|
+
# @overload change(red: nil, green: nil, blue: nil, alpha: nil, space: 'a98-rgb')
|
195
|
+
# @overload change(red: nil, green: nil, blue: nil, alpha: nil, space: 'display-p3')
|
196
|
+
# @overload change(red: nil, green: nil, blue: nil, alpha: nil, space: 'prophoto-rgb')
|
197
|
+
# @overload change(red: nil, green: nil, blue: nil, alpha: nil, space: 'rec2020')
|
198
|
+
# @overload change(red: nil, green: nil, blue: nil, alpha: nil, space: 'srgb')
|
199
|
+
# @overload change(red: nil, green: nil, blue: nil, alpha: nil, space: 'srgb-linear')
|
200
|
+
# @overload change(x: nil, y: nil, z: nil, alpha: nil, space: 'xyz')
|
201
|
+
# @overload change(x: nil, y: nil, z: nil, alpha: nil, space: 'xyz-d50')
|
202
|
+
# @overload change(x: nil, y: nil, z: nil, alpha: nil, space: 'xyz-d65')
|
115
203
|
# @return [Color]
|
116
|
-
def change(
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
alpha: alpha || self.alpha)
|
140
|
-
else
|
141
|
-
dup.instance_eval do
|
142
|
-
@alpha = FuzzyMath.assert_between(alpha, 0, 1, 'alpha')
|
143
|
-
self
|
204
|
+
def change(**options)
|
205
|
+
space_set_explictly = !options[:space].nil?
|
206
|
+
space = space_set_explictly ? Space.from_name(options[:space]) : _space
|
207
|
+
|
208
|
+
if legacy? && !space_set_explictly
|
209
|
+
case options
|
210
|
+
in {whiteness: _} | {blackness: _}
|
211
|
+
space = Space::HWB
|
212
|
+
in {saturation: _} | {lightness: _}
|
213
|
+
space = Space::HSL
|
214
|
+
in {hue: _}
|
215
|
+
space = if _space == Space::HWB
|
216
|
+
Space::HWB
|
217
|
+
else
|
218
|
+
Space::HSL
|
219
|
+
end
|
220
|
+
in {red: _} | {blue: _} | {green: _}
|
221
|
+
space = Space::RGB
|
222
|
+
else
|
223
|
+
end
|
224
|
+
|
225
|
+
if space != _space
|
226
|
+
# deprecated
|
144
227
|
end
|
145
228
|
end
|
229
|
+
|
230
|
+
keys = _assert_options(space, options)
|
231
|
+
|
232
|
+
color = _to_space(space)
|
233
|
+
|
234
|
+
changed_color = if space_set_explictly
|
235
|
+
Color.send(:for_space_internal,
|
236
|
+
space,
|
237
|
+
options.fetch(keys[0], color.channel0_or_nil),
|
238
|
+
options.fetch(keys[1], color.channel1_or_nil),
|
239
|
+
options.fetch(keys[2], color.channel2_or_nil),
|
240
|
+
options.fetch(:alpha, color.alpha_or_nil))
|
241
|
+
else
|
242
|
+
changed_channel0_or_nil = options[keys[0]]
|
243
|
+
changed_channel1_or_nil = options[keys[1]]
|
244
|
+
changed_channel2_or_nil = options[keys[2]]
|
245
|
+
changed_alpha_or_nil = options[:alpha]
|
246
|
+
Color.send(:for_space_internal,
|
247
|
+
space,
|
248
|
+
changed_channel0_or_nil.nil? ? color.channel0_or_nil : changed_channel0_or_nil,
|
249
|
+
changed_channel1_or_nil.nil? ? color.channel1_or_nil : changed_channel1_or_nil,
|
250
|
+
changed_channel2_or_nil.nil? ? color.channel2_or_nil : changed_channel2_or_nil,
|
251
|
+
changed_alpha_or_nil.nil? ? color.alpha_or_nil : changed_alpha_or_nil)
|
252
|
+
end
|
253
|
+
|
254
|
+
changed_color._to_space(_space)
|
255
|
+
end
|
256
|
+
|
257
|
+
# return [Numeric]
|
258
|
+
def alpha
|
259
|
+
@alpha_or_nil.nil? ? 0 : @alpha_or_nil
|
260
|
+
end
|
261
|
+
|
262
|
+
# return [::Boolean]
|
263
|
+
def legacy?
|
264
|
+
_space.legacy?
|
265
|
+
end
|
266
|
+
|
267
|
+
# @deprecated
|
268
|
+
# @return [Numeric]
|
269
|
+
def red
|
270
|
+
_to_space(Space::RGB).channel('red').round
|
271
|
+
end
|
272
|
+
|
273
|
+
# @deprecated
|
274
|
+
# @return [Numeric]
|
275
|
+
def green
|
276
|
+
_to_space(Space::RGB).channel('green').round
|
277
|
+
end
|
278
|
+
|
279
|
+
# @deprecated
|
280
|
+
# @return [Numeric]
|
281
|
+
def blue
|
282
|
+
_to_space(Space::RGB).channel('blue').round
|
283
|
+
end
|
284
|
+
|
285
|
+
# @deprecated
|
286
|
+
# @return [Numeric]
|
287
|
+
def hue
|
288
|
+
_to_space(Space::HSL).channel('hue')
|
289
|
+
end
|
290
|
+
|
291
|
+
# @deprecated
|
292
|
+
# @return [Numeric]
|
293
|
+
def saturation
|
294
|
+
_to_space(Space::HSL).channel('saturation')
|
295
|
+
end
|
296
|
+
|
297
|
+
# @deprecated
|
298
|
+
# @return [Numeric]
|
299
|
+
def lightness
|
300
|
+
_to_space(Space::HSL).channel('lightness')
|
301
|
+
end
|
302
|
+
|
303
|
+
# @deprecated
|
304
|
+
# @return [Numeric]
|
305
|
+
def whiteness
|
306
|
+
_to_space(Space::HWB).channel('whiteness')
|
307
|
+
end
|
308
|
+
|
309
|
+
# @deprecated
|
310
|
+
# @return [Numeric]
|
311
|
+
def blackness
|
312
|
+
_to_space(Space::HWB).channel('blackness')
|
146
313
|
end
|
147
314
|
|
148
315
|
# @return [::Boolean]
|
149
316
|
def ==(other)
|
150
|
-
other.is_a?(Sass::Value::Color)
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
other.
|
317
|
+
return false unless other.is_a?(Sass::Value::Color)
|
318
|
+
|
319
|
+
if legacy?
|
320
|
+
return false unless other.legacy?
|
321
|
+
return false unless FuzzyMath.equals_nilable(other.alpha_or_nil, alpha_or_nil)
|
322
|
+
|
323
|
+
if _space == other._space
|
324
|
+
FuzzyMath.equals_nilable(other.channel0_or_nil, channel0_or_nil) &&
|
325
|
+
FuzzyMath.equals_nilable(other.channel1_or_nil, channel1_or_nil) &&
|
326
|
+
FuzzyMath.equals_nilable(other.channel2_or_nil, channel2_or_nil)
|
327
|
+
else
|
328
|
+
_to_space(Space::RGB) == other._to_space(Space::RGB)
|
329
|
+
end
|
330
|
+
else
|
331
|
+
other._space == _space &&
|
332
|
+
FuzzyMath.equals_nilable(other.channel0_or_nil, channel0_or_nil) &&
|
333
|
+
FuzzyMath.equals_nilable(other.channel1_or_nil, channel1_or_nil) &&
|
334
|
+
FuzzyMath.equals_nilable(other.channel2_or_nil, channel2_or_nil) &&
|
335
|
+
FuzzyMath.equals_nilable(other.alpha_or_nil, alpha_or_nil)
|
336
|
+
end
|
155
337
|
end
|
156
338
|
|
157
339
|
# @return [Integer]
|
158
340
|
def hash
|
159
|
-
@hash ||= [
|
341
|
+
@hash ||= [
|
342
|
+
_space,
|
343
|
+
channel0_or_nil&.finite? ? (channel0_or_nil * FuzzyMath::INVERSE_EPSILON).round : channel0_or_nil, # rubocop:disable Security/CompoundHash
|
344
|
+
channel1_or_nil&.finite? ? (channel1_or_nil * FuzzyMath::INVERSE_EPSILON).round : channel1_or_nil, # rubocop:disable Security/CompoundHash
|
345
|
+
channel2_or_nil&.finite? ? (channel2_or_nil * FuzzyMath::INVERSE_EPSILON).round : channel2_or_nil, # rubocop:disable Security/CompoundHash
|
346
|
+
alpha_or_nil&.finite? ? (alpha_or_nil * FuzzyMath::INVERSE_EPSILON).round : alpha_or_nil # rubocop:disable Security/CompoundHash
|
347
|
+
].hash
|
160
348
|
end
|
161
349
|
|
162
350
|
# @return [Color]
|
@@ -164,89 +352,276 @@ module Sass
|
|
164
352
|
self
|
165
353
|
end
|
166
354
|
|
355
|
+
protected
|
356
|
+
|
357
|
+
attr_reader :channel0_or_nil, :channel1_or_nil, :channel2_or_nil, :alpha_or_nil
|
358
|
+
|
359
|
+
def channel0
|
360
|
+
@channel0_or_nil.nil? ? 0 : @channel0_or_nil
|
361
|
+
end
|
362
|
+
|
363
|
+
def channel0_missing?
|
364
|
+
@channel0_or_nil.nil?
|
365
|
+
end
|
366
|
+
|
367
|
+
def channel0_powerless?
|
368
|
+
case _space
|
369
|
+
when Space::HSL
|
370
|
+
FuzzyMath.equals(channel1, 0)
|
371
|
+
when Space::HWB
|
372
|
+
FuzzyMath.greater_than_or_equals(channel1 + channel2, 100)
|
373
|
+
else
|
374
|
+
false
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
def channel1
|
379
|
+
@channel1_or_nil.nil? ? 0 : @channel1_or_nil
|
380
|
+
end
|
381
|
+
|
382
|
+
def channel1_missing?
|
383
|
+
@channel1_or_nil.nil?
|
384
|
+
end
|
385
|
+
|
386
|
+
def channel1_powerless?
|
387
|
+
false
|
388
|
+
end
|
389
|
+
|
390
|
+
def channel2
|
391
|
+
@channel2_or_nil.nil? ? 0 : @channel2_or_nil
|
392
|
+
end
|
393
|
+
|
394
|
+
def channel2_missing?
|
395
|
+
@channel2_or_nil.nil?
|
396
|
+
end
|
397
|
+
|
398
|
+
def channel2_powerless?
|
399
|
+
case _space
|
400
|
+
when Space::LCH, Space::OKLCH
|
401
|
+
FuzzyMath.equals(channel1, 0)
|
402
|
+
else
|
403
|
+
false
|
404
|
+
end
|
405
|
+
end
|
406
|
+
|
407
|
+
def alpha_missing?
|
408
|
+
@alpha_or_nil.nil?
|
409
|
+
end
|
410
|
+
|
411
|
+
def _space
|
412
|
+
@space
|
413
|
+
end
|
414
|
+
|
415
|
+
def _to_space(space)
|
416
|
+
return self if _space == space
|
417
|
+
|
418
|
+
_space.convert(space, channel0_or_nil, channel1_or_nil, channel2_or_nil, alpha)
|
419
|
+
end
|
420
|
+
|
421
|
+
def _in_gamut?
|
422
|
+
return true unless _space.bounded?
|
423
|
+
|
424
|
+
_is_channel_in_gamut(channel0, _space.channels[0]) &&
|
425
|
+
_is_channel_in_gamut(channel1, _space.channels[1]) &&
|
426
|
+
_is_channel_in_gamut(channel2, _space.channels[2])
|
427
|
+
end
|
428
|
+
|
429
|
+
def _to_gamut(method)
|
430
|
+
_in_gamut? ? self : method.map(self)
|
431
|
+
end
|
432
|
+
|
167
433
|
private
|
168
434
|
|
169
|
-
def
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
if max == min
|
179
|
-
@hue = 0
|
180
|
-
elsif max == scaled_red
|
181
|
-
@hue = ((scaled_green - scaled_blue) * 60 / delta) % 360
|
182
|
-
elsif max == scaled_green
|
183
|
-
@hue = (((scaled_blue - scaled_red) * 60 / delta) + 120) % 360
|
184
|
-
elsif max == scaled_blue
|
185
|
-
@hue = (((scaled_red - scaled_green) * 60 / delta) + 240) % 360
|
435
|
+
def _assert_options(space, options)
|
436
|
+
keys = space.channels.map do |channel|
|
437
|
+
channel.name.to_sym
|
438
|
+
end << :alpha << :space
|
439
|
+
options.each_key do |key|
|
440
|
+
unless keys.include?(key)
|
441
|
+
raise Sass::ScriptError.new("`#{key}` is not a valid channel in `#{space.name}`.", key)
|
442
|
+
end
|
186
443
|
end
|
444
|
+
keys
|
445
|
+
end
|
187
446
|
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
end
|
209
|
-
tmp1 = (scaled_lightness * 2) - tmp2
|
210
|
-
@red = FuzzyMath.round(hsl_hue_to_rgb(tmp1, tmp2, scaled_hue + Rational(1, 3)) * 255)
|
211
|
-
@green = FuzzyMath.round(hsl_hue_to_rgb(tmp1, tmp2, scaled_hue) * 255)
|
212
|
-
@blue = FuzzyMath.round(hsl_hue_to_rgb(tmp1, tmp2, scaled_hue - Rational(1, 3)) * 255)
|
213
|
-
end
|
214
|
-
|
215
|
-
def hsl_hue_to_rgb(tmp1, tmp2, hue)
|
216
|
-
hue += 1 if hue.negative?
|
217
|
-
hue -= 1 if hue > 1
|
218
|
-
|
219
|
-
if hue < Rational(1, 6)
|
220
|
-
tmp1 + ((tmp2 - tmp1) * hue * 6)
|
221
|
-
elsif hue < Rational(1, 2)
|
222
|
-
tmp2
|
223
|
-
elsif hue < Rational(2, 3)
|
224
|
-
tmp1 + ((tmp2 - tmp1) * (Rational(2, 3) - hue) * 6)
|
447
|
+
def _initialize_for_space_internal(space, channel0, channel1, channel2, alpha = 1)
|
448
|
+
case space
|
449
|
+
when Space::HSL
|
450
|
+
_initialize_for_space(
|
451
|
+
space,
|
452
|
+
_normalize_hue(channel0, invert: !channel1.nil? && FuzzyMath.less_than(channel1, 0)),
|
453
|
+
channel1&.abs,
|
454
|
+
channel2,
|
455
|
+
alpha
|
456
|
+
)
|
457
|
+
when Space::HWB
|
458
|
+
_initialize_for_space(space, _normalize_hue(channel0, invert: false), channel1, channel2, alpha)
|
459
|
+
when Space::LCH, Space::OKLCH
|
460
|
+
_initialize_for_space(
|
461
|
+
space,
|
462
|
+
channel0,
|
463
|
+
channel1&.abs,
|
464
|
+
_normalize_hue(channel2, invert: !channel1.nil? && FuzzyMath.less_than(channel1, 0)),
|
465
|
+
alpha
|
466
|
+
)
|
225
467
|
else
|
226
|
-
|
468
|
+
_initialize_for_space(space, channel0, channel1, channel2, alpha)
|
227
469
|
end
|
228
470
|
end
|
229
471
|
|
230
|
-
def
|
231
|
-
|
232
|
-
|
233
|
-
|
472
|
+
def _initialize_for_space(space, channel0_or_nil, channel1_or_nil, channel2_or_nil, alpha)
|
473
|
+
@space = space
|
474
|
+
@channel0_or_nil = channel0_or_nil
|
475
|
+
@channel1_or_nil = channel1_or_nil
|
476
|
+
@channel2_or_nil = channel2_or_nil
|
477
|
+
@alpha_or_nil = alpha
|
478
|
+
|
479
|
+
FuzzyMath.assert_between(@alpha_or_nil, 0, 1, 'alpha') unless @alpha_or_nil.nil?
|
480
|
+
end
|
481
|
+
|
482
|
+
def _normalize_hue(hue, invert:)
|
483
|
+
return hue if hue.nil?
|
234
484
|
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
485
|
+
((hue % 360) + 360 + (invert ? 180 : 0)) % 360
|
486
|
+
end
|
487
|
+
|
488
|
+
def _is_channel_in_gamut(value, channel)
|
489
|
+
case channel
|
490
|
+
when LinearChannel
|
491
|
+
FuzzyMath.less_than_or_equals(value, channel.max) && FuzzyMath.greater_than_or_equals(value, channel.min)
|
492
|
+
else
|
493
|
+
true
|
239
494
|
end
|
495
|
+
end
|
240
496
|
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
497
|
+
def _interpolate(other, method, weight: nil)
|
498
|
+
weight = 0.5 if weight.nil?
|
499
|
+
if weight.negative? || weight > 1
|
500
|
+
raise Sass::ScriptError.new("Expected #{wieght} to be within 0 and 1.", 'weight')
|
501
|
+
end
|
502
|
+
|
503
|
+
return other if FuzzyMath.equals(weight, 0)
|
504
|
+
return self if FuzzyMath.equals(weight, 1)
|
505
|
+
|
506
|
+
color1 = _to_space(method.space)
|
507
|
+
color2 = other._to_space(method.space)
|
508
|
+
|
509
|
+
c1_missing0 = _analogous_channel_missing?(self, color1, 0)
|
510
|
+
c1_missing1 = _analogous_channel_missing?(self, color1, 1)
|
511
|
+
c1_missing2 = _analogous_channel_missing?(self, color1, 2)
|
512
|
+
c2_missing0 = _analogous_channel_missing?(other, color2, 0)
|
513
|
+
c2_missing1 = _analogous_channel_missing?(other, color2, 1)
|
514
|
+
c2_missing2 = _analogous_channel_missing?(other, color2, 2)
|
515
|
+
c1_channel0 = (c1_missing0 ? color2 : color1).channel0
|
516
|
+
c1_channel1 = (c1_missing1 ? color2 : color1).channel1
|
517
|
+
c1_channel2 = (c1_missing2 ? color2 : color1).channel2
|
518
|
+
c2_channel0 = (c2_missing0 ? color1 : color2).channel0
|
519
|
+
c2_channel1 = (c2_missing1 ? color1 : color2).channel1
|
520
|
+
c2_channel2 = (c2_missing2 ? color1 : color2).channel2
|
521
|
+
c1_alpha = alpha_or_nil.nil? ? other.alpha : alpha_or_nil
|
522
|
+
c2_alpha = other.alpha_or_nil.nil? ? alpha : other.alpha_or_nil
|
523
|
+
|
524
|
+
c1_multiplier = (alpha_or_nil.nil? ? 1 : alpha_or_nil) * weight
|
525
|
+
c2_multiplier = (other.alpha_or_nil.nil? ? 1 : other.alpha_or_nil) * (1 - weight)
|
526
|
+
mixed_alpha = alpha_missing? && other.alpha_missing? ? nil : (c1_alpha * weight) + (c2_alpha * (1 - weight))
|
527
|
+
mixed0 = if c1_missing0 && c2_missing0
|
528
|
+
nil
|
529
|
+
else
|
530
|
+
((c1_channel0 * c1_multiplier) + (c2_channel0 * c2_multiplier)) /
|
531
|
+
(mixed_alpha.nil? ? 1 : mixed_alpha)
|
532
|
+
end
|
533
|
+
mixed1 = if c1_missing1 && c2_missing1
|
534
|
+
nil
|
535
|
+
else
|
536
|
+
((c1_channel1 * c1_multiplier) + (c2_channel1 * c2_multiplier)) /
|
537
|
+
(mixed_alpha.nil? ? 1 : mixed_alpha)
|
538
|
+
end
|
539
|
+
mixed2 = if c1_missing2 && c2_missing2
|
540
|
+
nil
|
541
|
+
else
|
542
|
+
((c1_channel2 * c1_multiplier) + (c2_channel2 * c2_multiplier)) /
|
543
|
+
(mixed_alpha.nil? ? 1 : mixed_alpha)
|
544
|
+
end
|
545
|
+
|
546
|
+
case method.space
|
547
|
+
when Space::HSL, Space::HWB
|
548
|
+
Color.send(:for_space_internal,
|
549
|
+
method.space,
|
550
|
+
c1_missing0 && c2_missing0 ? nil : _interpolate_hues(c1_channel0, c2_channel0, method.hue, weight),
|
551
|
+
mixed1,
|
552
|
+
mixed2,
|
553
|
+
mixed_alpha)
|
554
|
+
when Space::LCH, Space::OKLCH
|
555
|
+
Color.send(:for_space_internal,
|
556
|
+
method.space,
|
557
|
+
mixed0,
|
558
|
+
mixed1,
|
559
|
+
c1_missing2 && c2_missing2 ? nil : _interpolate_hues(c1_channel2, c2_channel2, method.hue, weight),
|
560
|
+
mixed_alpha)
|
561
|
+
else
|
562
|
+
Color.send(:_for_space,
|
563
|
+
method.space, mixed0, mixed1, mixed2, mixed_alpha)
|
564
|
+
end._to_space(_space)
|
245
565
|
end
|
246
566
|
|
247
|
-
def
|
248
|
-
|
249
|
-
|
567
|
+
def _analogous_channel_missing?(original, output, output_channel_index)
|
568
|
+
return true if output.channels_or_nil[output_channel_index].nil?
|
569
|
+
|
570
|
+
return false if original.equal?(output)
|
571
|
+
|
572
|
+
output_channel = output.space.channels[output_channel_index]
|
573
|
+
original_channel = original.space.channels.find do |channel|
|
574
|
+
output_channel.analogous?(channel)
|
575
|
+
end
|
576
|
+
|
577
|
+
return false if original_channel.nil?
|
578
|
+
|
579
|
+
original.channel_missing?(original_channel.name)
|
580
|
+
end
|
581
|
+
|
582
|
+
def _interpolate_hues(hue1, hue2, method, weight)
|
583
|
+
case method
|
584
|
+
when :shorter
|
585
|
+
diff = hue2 - hue1
|
586
|
+
if diff > 180
|
587
|
+
hue1 += 360
|
588
|
+
elsif diff < -180
|
589
|
+
hue2 += 360
|
590
|
+
end
|
591
|
+
when :longer
|
592
|
+
diff = hue2 - hue1
|
593
|
+
if diff.positive? && diff < 180
|
594
|
+
hue2 += 360
|
595
|
+
elsif diff > -180 && diff <= 0
|
596
|
+
hue1 += 360
|
597
|
+
end
|
598
|
+
when :increasing
|
599
|
+
hue2 += 360 if hue2 < hue1
|
600
|
+
when :decreasing
|
601
|
+
hue1 += 360 if hue1 < hue2
|
602
|
+
end
|
603
|
+
|
604
|
+
(hue1 * weight) + (hue2 * (1 - weight))
|
605
|
+
end
|
606
|
+
|
607
|
+
class << self
|
608
|
+
private
|
609
|
+
|
610
|
+
def for_space(space, channel0_or_nil, channel1_or_nil, channel2_or_nil, alpha)
|
611
|
+
_for_space(Space.from_name(space), channel0_or_nil, channel1_or_nil, channel2_or_nil, alpha)
|
612
|
+
end
|
613
|
+
|
614
|
+
def for_space_internal(space, channel0, channel1, channel2, alpha)
|
615
|
+
o = allocate
|
616
|
+
o.send(:_initialize_for_space_internal, space, channel0, channel1, channel2, alpha)
|
617
|
+
o
|
618
|
+
end
|
619
|
+
|
620
|
+
def _for_space(space, channel0_or_nil, channel1_or_nil, channel2_or_nil, alpha)
|
621
|
+
o = allocate
|
622
|
+
o.send(:_initialize_for_space, space, channel0_or_nil, channel1_or_nil, channel2_or_nil, alpha)
|
623
|
+
o
|
624
|
+
end
|
250
625
|
end
|
251
626
|
end
|
252
627
|
end
|