sass-embedded 1.79.3-x86-mingw32 → 1.79.5-x86-mingw32

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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/exe/sass +1 -9
  3. data/ext/sass/dart-sass/src/sass.snapshot +0 -0
  4. data/ext/sass/embedded_sass_pb.rb +1 -1
  5. data/lib/sass/compiler/connection.rb +1 -9
  6. data/lib/sass/compiler/host/protofier.rb +17 -55
  7. data/lib/sass/elf.rb +4 -4
  8. data/lib/sass/embedded/version.rb +1 -1
  9. data/lib/sass/value/color/channel.rb +79 -0
  10. data/lib/sass/value/color/conversions.rb +473 -0
  11. data/lib/sass/value/color/gamut_map_method/clip.rb +45 -0
  12. data/lib/sass/value/color/gamut_map_method/local_minde.rb +94 -0
  13. data/lib/sass/value/color/gamut_map_method.rb +45 -0
  14. data/lib/sass/value/color/interpolation_method.rb +51 -0
  15. data/lib/sass/value/color/space/a98_rgb.rb +57 -0
  16. data/lib/sass/value/color/space/display_p3.rb +57 -0
  17. data/lib/sass/value/color/space/hsl.rb +65 -0
  18. data/lib/sass/value/color/space/hwb.rb +70 -0
  19. data/lib/sass/value/color/space/lab.rb +77 -0
  20. data/lib/sass/value/color/space/lch.rb +53 -0
  21. data/lib/sass/value/color/space/lms.rb +129 -0
  22. data/lib/sass/value/color/space/oklab.rb +66 -0
  23. data/lib/sass/value/color/space/oklch.rb +54 -0
  24. data/lib/sass/value/color/space/prophoto_rgb.rb +59 -0
  25. data/lib/sass/value/color/space/rec2020.rb +69 -0
  26. data/lib/sass/value/color/space/rgb.rb +52 -0
  27. data/lib/sass/value/color/space/srgb.rb +140 -0
  28. data/lib/sass/value/color/space/srgb_linear.rb +72 -0
  29. data/lib/sass/value/color/space/utils.rb +86 -0
  30. data/lib/sass/value/color/space/xyz_d50.rb +100 -0
  31. data/lib/sass/value/color/space/xyz_d65.rb +57 -0
  32. data/lib/sass/value/color/space.rb +198 -0
  33. data/lib/sass/value/color.rb +537 -162
  34. data/lib/sass/value/fuzzy_math.rb +23 -19
  35. data/lib/sass/value/number.rb +1 -1
  36. data/lib/sass/value/string.rb +1 -1
  37. metadata +29 -5
@@ -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
- def initialize(red: nil,
23
- green: nil,
24
- blue: nil,
25
- hue: nil,
26
- saturation: nil,
27
- lightness: nil,
28
- whiteness: nil,
29
- blackness: nil,
30
- alpha: 1)
31
- @alpha = alpha.nil? ? 1 : FuzzyMath.assert_between(alpha, 0, 1, 'alpha')
32
- if red && green && blue
33
- @red = FuzzyMath.assert_between(FuzzyMath.round(red), 0, 255, 'red')
34
- @green = FuzzyMath.assert_between(FuzzyMath.round(green), 0, 255, 'green')
35
- @blue = FuzzyMath.assert_between(FuzzyMath.round(blue), 0, 255, 'blue')
36
- elsif hue && saturation && lightness
37
- @hue = hue % 360
38
- @saturation = FuzzyMath.assert_between(saturation, 0, 100, 'saturation')
39
- @lightness = FuzzyMath.assert_between(lightness, 0, 100, 'lightness')
40
- elsif hue && whiteness && blackness
41
- @hue = hue % 360
42
- @whiteness = FuzzyMath.assert_between(whiteness, 0, 100, 'whiteness')
43
- @blackness = FuzzyMath.assert_between(blackness, 0, 100, 'blackness')
44
- hwb_to_rgb
45
- @whiteness = @blackness = nil
46
- else
47
- raise Sass::ScriptError, 'Invalid Color'
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
- # @return [Integer]
52
- def red
53
- hsl_to_rgb unless defined?(@red)
65
+ space = Space.from_name(options[:space])
54
66
 
55
- @red
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 [Integer]
59
- def green
60
- hsl_to_rgb unless defined?(@green)
76
+ # @return [::String]
77
+ def space
78
+ _space.name
79
+ end
61
80
 
62
- @green
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
- # @return [Integer]
66
- def blue
67
- hsl_to_rgb unless defined?(@blue)
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
- @blue
92
+ _in_gamut?
70
93
  end
71
94
 
72
- # @return [Numeric]
73
- def hue
74
- rgb_to_hsl unless defined?(@hue)
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
- @hue
101
+ _to_gamut(GamutMapMethod.from_name(method, 'method'))
77
102
  end
78
103
 
79
- # @return [Numeric]
80
- def saturation
81
- rgb_to_hsl unless defined?(@saturation)
104
+ # @return [Array<Numeric, nil>]
105
+ def channels_or_nil
106
+ [channel0_or_nil, channel1_or_nil, channel2_or_nil].freeze
107
+ end
82
108
 
83
- @saturation
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 lightness
88
- rgb_to_hsl unless defined?(@lightness)
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
- @lightness
126
+ raise Sass::ScriptError.new("Color #{self} doesn't have a channel named \"#{channel}\".", channel)
91
127
  end
92
128
 
93
- # @return [Numeric]
94
- def whiteness
95
- @whiteness ||= Rational([red, green, blue].min, 255) * 100
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
- # @return [Numeric]
99
- def blackness
100
- @blackness ||= 100 - (Rational([red, green, blue].max, 255) * 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
- # @return [Numeric]
104
- attr_reader :alpha
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(red: nil,
117
- green: nil,
118
- blue: nil,
119
- hue: nil,
120
- saturation: nil,
121
- lightness: nil,
122
- whiteness: nil,
123
- blackness: nil,
124
- alpha: nil)
125
- if whiteness || blackness
126
- Sass::Value::Color.new(hue: hue || self.hue,
127
- whiteness: whiteness || self.whiteness,
128
- blackness: blackness || self.blackness,
129
- alpha: alpha || self.alpha)
130
- elsif hue || saturation || lightness
131
- Sass::Value::Color.new(hue: hue || self.hue,
132
- saturation: saturation || self.saturation,
133
- lightness: lightness || self.lightness,
134
- alpha: alpha || self.alpha)
135
- elsif red || green || blue
136
- Sass::Value::Color.new(red: red ? FuzzyMath.round(red) : self.red,
137
- green: green ? FuzzyMath.round(green) : self.green,
138
- blue: blue ? FuzzyMath.round(blue) : self.blue,
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
- other.red == red &&
152
- other.green == green &&
153
- other.blue == blue &&
154
- other.alpha == alpha
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 ||= [red, green, blue, alpha].hash
341
+ @hash ||= [
342
+ _space.name,
343
+ FuzzyMath._hash(channel0_or_nil),
344
+ FuzzyMath._hash(channel1_or_nil),
345
+ FuzzyMath._hash(channel2_or_nil),
346
+ FuzzyMath._hash(alpha_or_nil)
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 rgb_to_hsl
170
- scaled_red = Rational(red, 255)
171
- scaled_green = Rational(green, 255)
172
- scaled_blue = Rational(blue, 255)
173
-
174
- max = [scaled_red, scaled_green, scaled_blue].max
175
- min = [scaled_red, scaled_green, scaled_blue].min
176
- delta = max - min
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
443
+ end
444
+ keys
445
+ end
446
+
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
+ )
467
+ else
468
+ _initialize_for_space(space, channel0, channel1, channel2, alpha)
186
469
  end
470
+ end
471
+
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
187
481
 
188
- lightness = @lightness = (max + min) * 50
189
-
190
- @saturation = if max == min
191
- 0
192
- elsif lightness < 50
193
- delta * 100 / (max + min)
194
- else
195
- delta * 100 / (2 - max - min)
196
- end
197
- end
198
-
199
- def hsl_to_rgb
200
- scaled_hue = Rational(hue, 360)
201
- scaled_saturation = Rational(saturation, 100)
202
- scaled_lightness = Rational(lightness, 100)
203
-
204
- tmp2 = if scaled_lightness <= 0.5
205
- scaled_lightness * (scaled_saturation + 1)
206
- else
207
- scaled_lightness + scaled_saturation - (scaled_lightness * scaled_saturation)
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)
482
+ def _normalize_hue(hue, invert:)
483
+ return hue if hue.nil?
484
+
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)
225
492
  else
226
- tmp1
493
+ true
494
+ end
495
+ end
496
+
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')
227
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)
228
565
  end
229
566
 
230
- def hwb_to_rgb
231
- scaled_hue = Rational(hue, 360)
232
- scaled_whiteness = Rational(whiteness, 100)
233
- scaled_blackness = Rational(blackness, 100)
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
234
581
 
235
- sum = scaled_whiteness + scaled_blackness
236
- if sum > 1
237
- scaled_whiteness /= sum
238
- scaled_blackness /= sum
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
239
602
  end
240
603
 
241
- factor = 1 - scaled_whiteness - scaled_blackness
242
- @red = hwb_hue_to_rgb(factor, scaled_whiteness, scaled_hue + Rational(1, 3))
243
- @green = hwb_hue_to_rgb(factor, scaled_whiteness, scaled_hue)
244
- @blue = hwb_hue_to_rgb(factor, scaled_whiteness, scaled_hue - Rational(1, 3))
604
+ (hue1 * weight) + (hue2 * (1 - weight))
245
605
  end
246
606
 
247
- def hwb_hue_to_rgb(factor, scaled_whiteness, scaled_hue)
248
- channel = (hsl_hue_to_rgb(0, 1, scaled_hue) * factor) + scaled_whiteness
249
- FuzzyMath.round(channel * 255)
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