Support HWB colors in SassColor (#1085)

See sass/sass#2835
This commit is contained in:
Natalie Weizenbaum 2020-09-17 16:18:12 -07:00 committed by GitHub
parent 6ec78f975b
commit 1918674295
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 180 additions and 12 deletions

View File

@ -1,3 +1,12 @@
## 1.27.0
### Dart API
* Add [HWB] support to the `SassColor` class, including a `SassColor.hwb()`
constructor, `whiteness` and `blackness` getters, and a `changeHwb()` method.
[HWB]: https://en.wikipedia.org/wiki/HWB_color_model
## 1.26.11
* **Potentially breaking bug fix:** `selector.nest()` now throws an error

View File

@ -55,6 +55,20 @@ class SassColor extends Value implements ext.SassColor {
num _lightness;
num get whiteness {
// Because HWB is (currently) used much less frequently than HSL or RGB, we
// don't cache its values because we expect the memory overhead of doing so
// to outweigh the cost of recalculating it on access.
return math.min(math.min(red, green), blue) / 255 * 100;
}
num get blackness {
// Because HWB is (currently) used much less frequently than HSL or RGB, we
// don't cache its values because we expect the memory overhead of doing so
// to outweigh the cost of recalculating it on access.
return 100 - math.max(math.max(red, green), blue) / 255 * 100;
}
final num alpha;
/// The original string representation of this color, or `null` if one is
@ -81,6 +95,35 @@ class SassColor extends Value implements ext.SassColor {
alpha = alpha == null ? 1 : fuzzyAssertRange(alpha, 0, 1, "alpha"),
originalSpan = null;
factory SassColor.hwb(num hue, num whiteness, num blackness, [num alpha]) {
// From https://www.w3.org/TR/css-color-4/#hwb-to-rgb
var scaledHue = hue % 360 / 360;
var scaledWhiteness = fuzzyAssertRange(whiteness, 0, 100, "whiteness") / 100;
var scaledBlackness = fuzzyAssertRange(blackness, 0, 100, "blackness") / 100;
var sum = scaledWhiteness + scaledBlackness;
if (sum > 1) {
scaledWhiteness /= sum;
scaledBlackness /= sum;
}
var factor = 1 - scaledWhiteness - scaledBlackness;
int toRgb(num hue) {
var channel = _hueToRgb(0, 1, hue) * factor + scaledWhiteness;
return fuzzyRound(channel * 255);
}
// Because HWB is (currently) used much less frequently than HSL or RGB, we
// don't cache its values because we expect the memory overhead of doing so
// to outweigh the cost of recalculating it on access. Instead, we eagerly
// convert it to RGB and then convert back if necessary.
return SassColor.rgb(
toRgb(scaledHue + 1/3),
toRgb(scaledHue),
toRgb(scaledHue - 1/3),
alpha);
}
SassColor._(this._red, this._green, this._blue, this._hue, this._saturation,
this._lightness, this.alpha)
: originalSpan = null;
@ -97,6 +140,10 @@ class SassColor extends Value implements ext.SassColor {
SassColor.hsl(hue ?? this.hue, saturation ?? this.saturation,
lightness ?? this.lightness, alpha ?? this.alpha);
SassColor changeHwb({num hue, num whiteness, num blackness, num alpha}) =>
SassColor.hwb(hue ?? this.hue, whiteness ?? this.whiteness,
blackness ?? this.blackness, alpha ?? this.alpha);
SassColor changeAlpha(num alpha) => SassColor._(_red, _green, _blue, _hue,
_saturation, _lightness, fuzzyAssertRange(alpha, 0, 1, "alpha"));
@ -177,29 +224,26 @@ class SassColor extends Value implements ext.SassColor {
scaledSaturation -
scaledLightness * scaledSaturation;
var m1 = scaledLightness * 2 - m2;
_red = _hueToRgb(m1, m2, scaledHue + 1 / 3);
_green = _hueToRgb(m1, m2, scaledHue);
_blue = _hueToRgb(m1, m2, scaledHue - 1 / 3);
_red = fuzzyRound(_hueToRgb(m1, m2, scaledHue + 1 / 3) * 255);
_green = fuzzyRound(_hueToRgb(m1, m2, scaledHue) * 255);
_blue = fuzzyRound(_hueToRgb(m1, m2, scaledHue - 1 / 3) * 255);
}
/// An algorithm from the CSS3 spec:
/// http://www.w3.org/TR/css3-color/#hsl-color.
int _hueToRgb(num m1, num m2, num hue) {
static num _hueToRgb(num m1, num m2, num hue) {
if (hue < 0) hue += 1;
if (hue > 1) hue -= 1;
num result;
if (hue < 1 / 6) {
result = m1 + (m2 - m1) * hue * 6;
return m1 + (m2 - m1) * hue * 6;
} else if (hue < 1 / 2) {
result = m2;
return m2;
} else if (hue < 2 / 3) {
result = m1 + (m2 - m1) * (2 / 3 - hue) * 6;
return m1 + (m2 - m1) * (2 / 3 - hue) * 6;
} else {
result = m1;
return m1;
}
return fuzzyRound(result * 255);
}
/// Returns an `rgb()` or `rgba()` function call that will evaluate to this

View File

@ -28,6 +28,12 @@ abstract class SassColor extends Value {
/// This color's lightness, a percentage between `0` and `100`.
num get lightness;
/// This color's whiteness, a percentage between `0` and `100`.
num get whiteness;
/// This color's blackness, a percentage between `0` and `100`.
num get blackness;
/// This color's alpha channel, between `0` and `1`.
num get alpha;
@ -45,12 +51,22 @@ abstract class SassColor extends Value {
factory SassColor.hsl(num hue, num saturation, num lightness, [num alpha]) =
internal.SassColor.hsl;
/// Creates an HWB color.
///
/// Throws a [RangeError] if [whiteness] or [blackness] aren't between `0` and
/// `100`, or if [alpha] isn't between `0` and `1`.
factory SassColor.hwb(num hue, num whiteness, num blackness, [num alpha]) =
internal.SassColor.hwb;
/// Changes one or more of this color's RGB channels and returns the result.
SassColor changeRgb({int red, int green, int blue, num alpha});
/// Changes one or more of this color's HSL channels and returns the result.
SassColor changeHsl({num hue, num saturation, num lightness, num alpha});
/// Changes one or more of this color's HWB channels and returns the result.
SassColor changeHwb({num hue, num whiteness, num blackness, num alpha});
/// Returns a new copy of this color with the alpha channel set to [alpha].
SassColor changeAlpha(num alpha);
}

View File

@ -1,5 +1,5 @@
name: sass
version: 1.26.11-dev
version: 1.27.0-dev
description: A Sass implementation in Dart.
author: Sass Team
homepage: https://github.com/sass/dart-sass

View File

@ -28,6 +28,12 @@ void main() {
expect(value.lightness, equals(20.392156862745097));
});
test("has HWB channels", () {
expect(value.hue, equals(210));
expect(value.whiteness, equals(7.0588235294117645));
expect(value.blackness, equals(66.27450980392157));
});
test("has an alpha channel", () {
expect(value.alpha, equals(1));
});
@ -114,6 +120,46 @@ void main() {
});
});
group("changeHwb()", () {
test("changes HWB values", () {
expect(value.changeHwb(hue: 120),
equals(SassColor.hwb(120, 7.0588235294117645, 66.27450980392157)));
expect(value.changeHwb(whiteness: 20),
equals(SassColor.hwb(210, 20, 66.27450980392157)));
expect(value.changeHwb(blackness: 42),
equals(SassColor.hwb(210, 7.0588235294117645, 42)));
expect(
value.changeHwb(alpha: 0.5),
equals(
SassColor.hwb(210, 7.0588235294117645, 66.27450980392157, 0.5)));
expect(
value.changeHwb(
hue: 120, whiteness: 42, blackness: 42, alpha: 0.5),
equals(SassColor.hwb(120, 42, 42, 0.5)));
expect(
value.changeHwb(whiteness: 50),
equals(SassColor.hwb(210, 43, 57)));
});
test("allows valid values", () {
expect(value.changeHwb(whiteness: 0).whiteness, equals(0));
expect(value.changeHwb(whiteness: 100).whiteness, equals(60.0));
expect(value.changeHwb(blackness: 0).blackness, equals(0));
expect(value.changeHwb(blackness: 100).blackness, equals(93.33333333333333));
expect(value.changeHwb(alpha: 0).alpha, equals(0));
expect(value.changeHwb(alpha: 1).alpha, equals(1));
});
test("disallows invalid values", () {
expect(() => value.changeHwb(whiteness: -0.1), throwsRangeError);
expect(() => value.changeHwb(whiteness: 100.1), throwsRangeError);
expect(() => value.changeHwb(blackness: -0.1), throwsRangeError);
expect(() => value.changeHwb(blackness: 100.1), throwsRangeError);
expect(() => value.changeHwb(alpha: -0.1), throwsRangeError);
expect(() => value.changeHwb(alpha: 1.1), throwsRangeError);
});
});
group("changeAlpha()", () {
test("changes the alpha value", () {
expect(value.changeAlpha(0.5),
@ -160,6 +206,11 @@ void main() {
expect(value.lightness, equals(42));
});
test("has HWB channels", () {
expect(value.whiteness, equals(24.313725490196077));
expect(value.blackness, equals(40.3921568627451));
});
test("has an alpha channel", () {
expect(value.alpha, equals(1));
});
@ -167,6 +218,7 @@ void main() {
test("equals the same color", () {
expect(value, equalsWithHash(SassColor.rgb(0x3E, 0x98, 0x3E)));
expect(value, equalsWithHash(SassColor.hsl(120, 42, 42)));
expect(value, equalsWithHash(SassColor.hwb(120, 24.313725490196077, 40.3921568627451)));
});
});
@ -209,4 +261,51 @@ void main() {
expect(() => SassColor.hsl(0, 0, 0, 1.1), throwsRangeError);
});
});
group("new SassColor.hwb()", () {
SassColor value;
setUp(() => value = SassColor.hwb(120, 42, 42));
test("has RGB channels", () {
expect(value.red, equals(0x6B));
expect(value.green, equals(0x94));
expect(value.blue, equals(0x6B));
});
test("has HSL channels", () {
expect(value.hue, equals(120));
expect(value.saturation, equals(16.078431372549026));
expect(value.lightness, equals(50));
});
test("has HWB channels", () {
expect(value.whiteness, equals(41.96078431372549));
expect(value.blackness, equals(41.96078431372548));
});
test("has an alpha channel", () {
expect(value.alpha, equals(1));
});
test("equals the same color", () {
expect(value, equalsWithHash(SassColor.rgb(0x6B, 0x94, 0x6B)));
expect(value, equalsWithHash(SassColor.hsl(120, 16, 50)));
expect(value, equalsWithHash(SassColor.hwb(120, 42, 42)));
});
test("allows valid values", () {
expect(SassColor.hwb(0, 0, 0, 0), equals(parseValue("rgba(255, 0, 0, 0)")));
expect(SassColor.hwb(4320, 100, 100, 1),
equals(parseValue("grey")));
});
test("disallows invalid values", () {
expect(() => SassColor.hwb(0, -0.1, 0, 0), throwsRangeError);
expect(() => SassColor.hwb(0, 0, -0.1, 0), throwsRangeError);
expect(() => SassColor.hwb(0, 0, 0, -0.1), throwsRangeError);
expect(() => SassColor.hwb(0, 100.1, 0, 0), throwsRangeError);
expect(() => SassColor.hwb(0, 0, 100.1, 0), throwsRangeError);
expect(() => SassColor.hwb(0, 0, 0, 1.1), throwsRangeError);
});
});
}