@@ -20,6 +20,13 @@ interface HslColor {
20
20
alpha : number ;
21
21
}
22
22
23
+ interface HwbColor {
24
+ hue : number ;
25
+ whiteness : number ;
26
+ blackness : number ;
27
+ alpha : number ;
28
+ }
29
+
23
30
/** A SassScript color. */
24
31
export class SassColor extends Value {
25
32
private redInternal ?: number ;
@@ -30,102 +37,62 @@ export class SassColor extends Value {
30
37
private lightnessInternal ?: number ;
31
38
private readonly alphaInternal : number ;
32
39
33
- private constructor ( color : RgbColor | HslColor ) {
40
+ constructor ( color : RgbColor | HslColor | HwbColor ) {
34
41
super ( ) ;
35
42
36
43
if ( 'red' in color ) {
37
- this . redInternal = color . red ;
38
- this . greenInternal = color . green ;
39
- this . blueInternal = color . blue ;
44
+ this . redInternal = fuzzyAssertInRange ( color . red , 0 , 255 , 'red' ) ;
45
+ this . greenInternal = fuzzyAssertInRange ( color . green , 0 , 255 , 'green' ) ;
46
+ this . blueInternal = fuzzyAssertInRange ( color . blue , 0 , 255 , 'blue' ) ;
47
+ } else if ( 'saturation' in color ) {
48
+ this . hueInternal = color . hue % 360 ;
49
+ this . saturationInternal = fuzzyAssertInRange (
50
+ color . saturation ,
51
+ 0 ,
52
+ 100 ,
53
+ 'saturation'
54
+ ) ;
55
+ this . lightnessInternal = fuzzyAssertInRange (
56
+ color . lightness ,
57
+ 0 ,
58
+ 100 ,
59
+ 'lightness'
60
+ ) ;
40
61
} else {
41
- this . hueInternal = color . hue ;
42
- this . saturationInternal = color . saturation ;
43
- this . lightnessInternal = color . lightness ;
62
+ // From https://www.w3.org/TR/css-color-4/#hwb-to-rgb
63
+ const scaledHue = ( color . hue % 360 ) / 360 ;
64
+ let scaledWhiteness =
65
+ fuzzyAssertInRange ( color . whiteness , 0 , 100 , 'whiteness' ) / 100 ;
66
+ let scaledBlackness =
67
+ fuzzyAssertInRange ( color . blackness , 0 , 100 , 'blackness' ) / 100 ;
68
+
69
+ const sum = scaledWhiteness + scaledBlackness ;
70
+ if ( sum > 1 ) {
71
+ scaledWhiteness /= sum ;
72
+ scaledBlackness /= sum ;
73
+ }
74
+
75
+ // Because HWB is (currently) used much less frequently than HSL or RGB, we
76
+ // don't cache its values because we expect the memory overhead of doing so
77
+ // to outweigh the cost of recalculating it on access. Instead, we eagerly
78
+ // convert it to RGB and then convert back if necessary.
79
+ this . redInternal = toRgb (
80
+ scaledHue + 1 / 3 ,
81
+ scaledWhiteness ,
82
+ scaledBlackness
83
+ ) ;
84
+ this . greenInternal = toRgb ( scaledHue , scaledWhiteness , scaledBlackness ) ;
85
+ this . blueInternal = toRgb (
86
+ scaledHue - 1 / 3 ,
87
+ scaledWhiteness ,
88
+ scaledBlackness
89
+ ) ;
44
90
}
45
- this . alphaInternal = color . alpha ;
46
- }
47
91
48
- /**
49
- * Creates an RGB color.
50
- *
51
- * Throws an error if `red`, `green`, and `blue` aren't between `0` and `255`,
52
- * or if `alpha` isn't between `0` and `1`.
53
- */
54
- static rgb (
55
- red : number ,
56
- green : number ,
57
- blue : number ,
58
- alpha ?: number
59
- ) : SassColor {
60
- return new SassColor ( {
61
- red : fuzzyAssertInRange ( red , 0 , 255 , 'red' ) ,
62
- green : fuzzyAssertInRange ( green , 0 , 255 , 'green' ) ,
63
- blue : fuzzyAssertInRange ( blue , 0 , 255 , 'blue' ) ,
64
- alpha : alpha === undefined ? 1 : fuzzyAssertInRange ( alpha , 0 , 1 , 'alpha' ) ,
65
- } ) ;
66
- }
67
-
68
- /**
69
- * Creates an HSL color.
70
- *
71
- * Throws an error if `saturation` or `lightness` aren't between `0` and
72
- * `100`, or if `alpha` isn't between `0` and `1`.
73
- */
74
- static hsl (
75
- hue : number ,
76
- saturation : number ,
77
- lightness : number ,
78
- alpha ?: number
79
- ) {
80
- return new SassColor ( {
81
- hue : hue % 360 ,
82
- saturation : fuzzyAssertInRange ( saturation , 0 , 100 , 'saturation' ) ,
83
- lightness : fuzzyAssertInRange ( lightness , 0 , 100 , 'lightness' ) ,
84
- alpha : alpha === undefined ? 1 : fuzzyAssertInRange ( alpha , 0 , 1 , 'alpha' ) ,
85
- } ) ;
86
- }
87
-
88
- /**
89
- * Creates an HWB color.
90
- *
91
- * Throws an error if `whiteness` or `blackness` aren't between `0` and `100`,
92
- * or if `alpha` isn't between `0` and `1`.
93
- */
94
- static hwb (
95
- hue : number ,
96
- whiteness : number ,
97
- blackness : number ,
98
- alpha ?: number
99
- ) {
100
- // From https://www.w3.org/TR/css-color-4/#hwb-to-rgb
101
- const scaledHue = ( hue % 360 ) / 360 ;
102
- let scaledWhiteness =
103
- fuzzyAssertInRange ( whiteness , 0 , 100 , 'whiteness' ) / 100 ;
104
- let scaledBlackness =
105
- fuzzyAssertInRange ( blackness , 0 , 100 , 'blackness' ) / 100 ;
106
-
107
- const sum = scaledWhiteness + scaledBlackness ;
108
- if ( sum > 1 ) {
109
- scaledWhiteness /= sum ;
110
- scaledBlackness /= sum ;
111
- }
112
-
113
- const factor = 1 - scaledWhiteness - scaledBlackness ;
114
- function toRgb ( hue : number ) {
115
- const channel = hueToRgb ( 0 , 1 , hue ) * factor + scaledWhiteness ;
116
- return fuzzyRound ( channel * 255 ) ;
117
- }
118
-
119
- // Because HWB is (currently) used much less frequently than HSL or RGB, we
120
- // don't cache its values because we expect the memory overhead of doing so
121
- // to outweigh the cost of recalculating it on access. Instead, we eagerly
122
- // convert it to RGB and then convert back if necessary.
123
- return SassColor . rgb (
124
- toRgb ( scaledHue + 1 / 3 ) ,
125
- toRgb ( scaledHue ) ,
126
- toRgb ( scaledHue - 1 / 3 ) ,
127
- alpha
128
- ) ;
92
+ this . alphaInternal =
93
+ color . alpha === undefined
94
+ ? 1
95
+ : fuzzyAssertInRange ( color . alpha , 0 , 1 , 'alpha' ) ;
129
96
}
130
97
131
98
/** `this`'s red channel. */
@@ -202,67 +169,51 @@ export class SassColor extends Value {
202
169
}
203
170
204
171
/**
205
- * Returns a copy of `this` with its RGB channels changed to `red`, `green`,
206
- * `blue`, and/or `alpha`.
207
- */
208
- changeRgb ( options : {
209
- red ?: number ;
210
- green ?: number ;
211
- blue ?: number ;
212
- alpha ?: number ;
213
- } ) : SassColor {
214
- return SassColor . rgb (
215
- options . red ?? this . red ,
216
- options . green ?? this . green ,
217
- options . blue ?? this . blue ,
218
- options . alpha ?? this . alpha
219
- ) ;
220
- }
221
-
222
- /**
223
- * Returns a copy of `this` with its HSL values changed to `hue`,
224
- * `saturation`, `lightness`, and/or `alpha`.
225
- */
226
- changeHsl ( options : {
227
- hue ?: number ;
228
- saturation ?: number ;
229
- lightness ?: number ;
230
- alpha ?: number ;
231
- } ) : SassColor {
232
- return SassColor . hsl (
233
- options . hue ?? this . hue ,
234
- options . saturation ?? this . saturation ,
235
- options . lightness ?? this . lightness ,
236
- options . alpha ?? this . alpha
237
- ) ;
238
- }
239
-
240
- /**
241
- * Returns a copy of `this` with its HWB values changed to `hue`, `whiteness`,
242
- * `blackness`, and/or `alpha`.
172
+ * Returns a copy of `this` with its channels changed to match `color`.
243
173
*/
244
- changeHwb ( options : {
245
- hue ?: number ;
246
- whiteness ?: number ;
247
- blackness ?: number ;
248
- alpha ?: number ;
249
- } ) : SassColor {
250
- return SassColor . hwb (
251
- options . hue ?? this . hue ,
252
- options . whiteness ?? this . whiteness ,
253
- options . blackness ?? this . blackness ,
254
- options . alpha ?? this . alpha
255
- ) ;
256
- }
257
-
258
- /** Returns a copy of `this` with its alpha channel changed to `alpha`. */
259
- changeAlpha ( alpha : number ) : SassColor {
260
- return new SassColor ( {
261
- red : this . red ,
262
- green : this . green ,
263
- blue : this . blue ,
264
- alpha : fuzzyAssertInRange ( alpha , 0 , 1 , 'alpha' ) ,
265
- } ) ;
174
+ change (
175
+ color : Partial < RgbColor > | Partial < HslColor > | Partial < HwbColor >
176
+ ) : SassColor {
177
+ if ( 'whiteness' in color || 'blackness' in color ) {
178
+ return new SassColor ( {
179
+ hue : color . hue ?? this . hue ,
180
+ whiteness : color . whiteness ?? this . whiteness ,
181
+ blackness : color . blackness ?? this . blackness ,
182
+ alpha : color . alpha ?? this . alpha ,
183
+ } ) ;
184
+ } else if (
185
+ 'hue' in color ||
186
+ 'saturation' in color ||
187
+ 'lightness' in color
188
+ ) {
189
+ // Tell TypeScript this isn't a Partial<HwbColor>.
190
+ const hsl = color as Partial < HslColor > ;
191
+ return new SassColor ( {
192
+ hue : hsl . hue ?? this . hue ,
193
+ saturation : hsl . saturation ?? this . saturation ,
194
+ lightness : hsl . lightness ?? this . lightness ,
195
+ alpha : hsl . alpha ?? this . alpha ,
196
+ } ) ;
197
+ } else if (
198
+ 'red' in color ||
199
+ 'green' in color ||
200
+ 'blue' in color ||
201
+ this . redInternal
202
+ ) {
203
+ return new SassColor ( {
204
+ red : color . red ?? this . red ,
205
+ green : color . green ?? this . green ,
206
+ blue : color . blue ?? this . blue ,
207
+ alpha : color . alpha ?? this . alpha ,
208
+ } ) ;
209
+ } else {
210
+ return new SassColor ( {
211
+ hue : this . hue ,
212
+ saturation : this . saturation ,
213
+ lightness : this . lightness ,
214
+ alpha : color . alpha ?? this . alpha ,
215
+ } ) ;
216
+ }
266
217
}
267
218
268
219
equals ( other : Value ) : boolean {
@@ -344,6 +295,13 @@ export class SassColor extends Value {
344
295
}
345
296
}
346
297
298
+ // A helper for converting HWB colors to RGB.
299
+ function toRgb ( hue : number , scaledWhiteness : number , scaledBlackness : number ) {
300
+ const factor = 1 - scaledWhiteness - scaledBlackness ;
301
+ const channel = hueToRgb ( 0 , 1 , hue ) * factor + scaledWhiteness ;
302
+ return fuzzyRound ( channel * 255 ) ;
303
+ }
304
+
347
305
// An algorithm from the CSS3 spec: http://www.w3.org/TR/css3-color/#hsl-color.
348
306
function hueToRgb ( m1 : number , m2 : number , hue : number ) : number {
349
307
if ( hue < 0 ) hue += 1 ;
0 commit comments