Skip to content

Commit

Permalink
[blink] Add support for HWB color values.
Browse files Browse the repository at this point in the history
Implement CSSHWB classes and HWB <-> RGB conversion to implement the
standard in https://drafts.csswg.org/css-color-4/#the-hwb-notation#

Fixed: 1288883
I2S: https://groups.google.com/a/chromium.org/g/blink-dev/c/7SKzObJ9IBI/
Change-Id: I343651980244a18af45ac28b446153a93a55721d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3404291
Reviewed-by: Aaron Krajeski <aaronhk@chromium.org>
Reviewed-by: Alex Rudenko <alexrudenko@chromium.org>
Reviewed-by: Anders Hartvoll Ruud <andruud@chromium.org>
Reviewed-by: Stephen Chenney <schenney@chromium.org>
Commit-Queue: Stephen Chenney <schenney@chromium.org>
Cr-Commit-Position: refs/heads/main@{#975498}
  • Loading branch information
jkeitel authored and pull[bot] committed Jun 27, 2023
1 parent 0b0c823 commit 1185781
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 11 deletions.
4 changes: 4 additions & 0 deletions css/css-color/parsing/color-computed.html
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@

test_computed_value("color", "hsl(120 30% 50%)", "rgb(89, 166, 89)");
test_computed_value("color", "hsl(120 30% 50% / 0.5)", "rgba(89, 166, 89, 0.5)");
test_computed_value("color", "hsl(120 30% 50% / 50%)", "rgba(89, 166, 89, 0.5)");
test_computed_value("color", "hsl(none none none)", "rgb(0, 0, 0)");
test_computed_value("color", "hsl(0 0% 0%)", "rgb(0, 0, 0)");
test_computed_value("color", "hsl(none none none / none)", "rgba(0, 0, 0, 0)");
Expand All @@ -73,11 +74,13 @@
test_computed_value("color", "hsl(120 0% 50%)", "rgb(128, 128, 128)");
test_computed_value("color", "hsl(120 100% 50% / none)", "rgba(0, 255, 0, 0)");
test_computed_value("color", "hsl(120 100% 50% / 0)", "rgba(0, 255, 0, 0)");
test_computed_value("color", "hsl(120 100% 50% / 0%)", "rgba(0, 255, 0, 0)");
test_computed_value("color", "hsl(none 100% 50%)", "rgb(255, 0, 0)");
test_computed_value("color", "hsl(0 100% 50%)", "rgb(255, 0, 0)");

test_computed_value("color", "hwb(120 30% 50%)", "rgb(77, 128, 77)");
test_computed_value("color", "hwb(120 30% 50% / 0.5)", "rgba(77, 128, 77, 0.5)");
test_computed_value("color", "hwb(120 30% 50% / 50%)", "rgba(77, 128, 77, 0.5)");
test_computed_value("color", "hwb(none none none)", "rgb(255, 0, 0)");
test_computed_value("color", "hwb(0 0% 0%)", "rgb(255, 0, 0)");
test_computed_value("color", "hwb(none none none / none)", "rgba(255, 0, 0, 0)");
Expand All @@ -90,6 +93,7 @@
test_computed_value("color", "hwb(120 0% 50%)", "rgb(0, 128, 0)");
test_computed_value("color", "hwb(120 30% 50% / none)", "rgba(77, 128, 77, 0)");
test_computed_value("color", "hwb(120 30% 50% / 0)", "rgba(77, 128, 77, 0)");
test_computed_value("color", "hwb(120 30% 50% / 0%)", "rgba(77, 128, 77, 0)");
test_computed_value("color", "hwb(none 100% 50% / none)", "rgba(170, 170, 170, 0)");
test_computed_value("color", "hwb(0 100% 50% / 0)", "rgba(170, 170, 170, 0)");

Expand Down
76 changes: 65 additions & 11 deletions css/css-typed-om/stylevalue-subclasses/cssColorValue.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,44 +9,98 @@
'use strict';

const TEST_COLORS = [
{name: "magenta", rgb: [1, 0, 1], hsl: [300, 1, 0.5]},
{name: "dark cyan", rgb: [0, 0.545, 0.545], hsl: [180, 1, 0.2725]},
{name: "light goldenrod yellow", rgb: [1, 1, 0.82], hsl: [60, 1, 0.91]},
{name: "medium purple", rgb: [0.58, 0.44, 0.86], hsl: [260, 0.5977, 0.649]},
{name: "magenta", rgb: [1, 0, 1], hsl: [300, 1, 0.5], hwb: [300, 0, 0]},
{name: "dark cyan", rgb: [0, 0.545, 0.545], hsl: [180, 1, 0.2725], hwb: [180, 0, 0.455]},
{name: "light goldenrod yellow", rgb: [1, 1, 0.82], hsl: [60, 1, 0.91], hwb: [60, 0.82, 0]},
{name: "medium purple", rgb: [0.58, 0.44, 0.86], hsl: [260, 0.5977, 0.649], hwb: [260, 0.44, 0.14]},
]

for (const color of TEST_COLORS) {
const hsl_color_degrees = new CSSHSL(CSS.deg(color.hsl[0]), color.hsl[1], color.hsl[2])
const hsl_color_radians = new CSSHSL(CSS.rad(color.hsl[0]/360 * 2 * Math.PI), color.hsl[1], color.hsl[2])
const hsl_color_gradiens = new CSSHSL(CSS.grad(color.hsl[0]/360 * 400), color.hsl[1], color.hsl[2])
const hsl_color_gradians = new CSSHSL(CSS.grad(color.hsl[0]/360 * 400), color.hsl[1], color.hsl[2])
const hsl_attributes = ['h', 's', 'l', 'alpha']

const hwb_color_degrees = new CSSHWB(CSS.deg(color.hwb[0]), color.hwb[1], color.hwb[2])
const hwb_color_radians = new CSSHWB(CSS.rad(color.hwb[0]/360 * 2 * Math.PI), color.hwb[1], color.hwb[2])
const hwb_color_gradians = new CSSHWB(CSS.grad(color.hwb[0]/360 * 400), color.hwb[1], color.hwb[2])
const hwb_attributes = ['h', 'w', 'b', 'alpha']

const rgb_color = new CSSRGB(color.rgb[0], color.rgb[1], color.rgb[2])
const rgb_attributes = ['r', 'g', 'b', 'alpha']

// Test conversion from CSSHSL to others.
test(() => {
const hsl_to_rgb = hsl_color_degrees.toRGB();
for (const attr of ['r', 'g', 'b', 'alpha']) {
for (const attr of rgb_attributes) {
assert_color_channel_approx_equals(hsl_to_rgb[attr], rgb_color[attr]);
}
}, `Converting HSL using degrees to RGB works for ${color.name}.`);

test(() => {
const hsl_to_rgb = hsl_color_radians.toRGB();
for (const attr of ['r', 'g', 'b', 'alpha']) {
for (const attr of rgb_attributes) {
assert_color_channel_approx_equals(hsl_to_rgb[attr], rgb_color[attr]);
}
}, `Converting HSL using radians to RGB works for ${color.name}.`);

test(() => {
const hsl_to_rgb = hsl_color_gradiens.toRGB();
for (const attr of ['r', 'g', 'b', 'alpha']) {
const hsl_to_rgb = hsl_color_gradians.toRGB();
for (const attr of rgb_attributes) {
assert_color_channel_approx_equals(hsl_to_rgb[attr], rgb_color[attr]);
}
}, `Converting HSL using gradiens to RGB works for ${color.name}.`);
}, `Converting HSL using gradians to RGB works for ${color.name}.`);

test(() => {
const hsl_to_hwb = hsl_color_degrees.toHWB();
for (const attr of hwb_attributes) {
assert_color_channel_approx_equals(hwb_color_degrees[attr], hsl_to_hwb[attr]);
}
}, `Converting HSL to HWB works for ${color.name}.`);

// Test conversion from CSSHWB to others.
test(() => {
const hwb_to_rgb = hwb_color_degrees.toRGB();
for (const attr of rgb_attributes) {
assert_color_channel_approx_equals(hwb_to_rgb[attr], rgb_color[attr]);
}
}, `Converting HWB using degrees to RGB works for ${color.name}.`);

test(() => {
const hwb_to_rgb = hwb_color_radians.toRGB();
for (const attr of rgb_attributes) {
assert_color_channel_approx_equals(hwb_to_rgb[attr], rgb_color[attr]);
}
}, `Converting HWB using radians to RGB works for ${color.name}.`);

test(() => {
const hwb_to_rgb = hwb_color_gradians.toRGB();
for (const attr of rgb_attributes) {
assert_color_channel_approx_equals(hwb_to_rgb[attr], rgb_color[attr]);
}
}, `Converting HWB using gradians to RGB works for ${color.name}.`);

test(() => {
const hwb_to_hsl = hwb_color_degrees.toHSL();
for (const attr of hsl_attributes) {
assert_color_channel_approx_equals(hsl_color_degrees[attr], hwb_to_hsl[attr]);
}
}, `Converting HWB to HSL works for ${color.name}.`);

// Test conversion from CSSRGB to others
test(() => {
const rgb_to_hsl = rgb_color.toHSL();
for (const attr of ['h', 's', 'l', 'alpha']) {
for (const attr of hsl_attributes) {
assert_color_channel_approx_equals(hsl_color_degrees[attr], rgb_to_hsl[attr]);
}
}, `Converting RGB to HSL works for ${color.name}.`);

test(() => {

const rgb_to_hwb = rgb_color.toHWB();
for (const attr of hwb_attributes) {
assert_color_channel_approx_equals(hwb_color_degrees[attr], rgb_to_hwb[attr]);
}
}, `Converting RGB to HWB works for ${color.name}.`);
}
</script>
82 changes: 82 additions & 0 deletions css/css-typed-om/stylevalue-subclasses/cssHWB.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<!doctype html>
<meta charset="utf-8">
<title>CSSHWB tests</title>
<link rel="help" href="https://drafts.css-houdini.org/css-typed-om-1/#csshwb">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../resources/testhelper.js"></script>
<script>
'use strict';

const gValidHueInputs = [
{hue: CSS.deg(180), desc: 'degrees'},
{hue: CSS.rad(1), desc: 'radians'},
{hue: CSS.grad(81), desc: 'gradians'},
];

const gInvalidHueInputs = [
{hue: 180, desc: "a number"},
{hue: CSS.px(1), desc: "css pixels"},
{hue: undefined, desc: "undefined"},
]

for (const {hue, desc} of gValidHueInputs) {
test(() => {
const result = new CSSHWB(hue, 0.5, 0.5);
assert_color_channel_approx_equals(result.h, hue);
}, `Constructing a CSSHWB with ${desc} for the hue works as intended.`);

test(() => {
const result = new CSSHWB(CSS.deg(0), 0.5, 0.5);
result.h = hue;
assert_color_channel_approx_equals(result.h, hue);
}, `CSSHWB.h can be updated with a ${desc}.`);
}

for (const {hue, desc} of gInvalidHueInputs) {
test(() => {
assert_throws_js(TypeError, () => new CSSHWB(hue, 0, 0));
}, `Constructing a CSSHWB with ${desc} for hue throws a type error.`);
}

test(() => {
const result = new CSSHWB(CSS.deg(27), 0.7, CSS.percent(91));
assert_color_channel_approx_equals(result.h, CSS.deg(27));
assert_color_channel_approx_equals(result.w, CSS.percent(70));
assert_color_channel_approx_equals(result.b, CSS.percent(91));
assert_color_channel_approx_equals(result.alpha, CSS.percent(100));
}, 'CSSHWB can be constructed from three numbers and will get an alpha of 100%.');

test(() => {
const result = new CSSHWB(CSS.grad(101), 0.2, 0.3, CSS.percent(0.4));
assert_color_channel_approx_equals(result.h, CSS.grad(101));
assert_color_channel_approx_equals(result.w, CSS.percent(20));
assert_color_channel_approx_equals(result.b, CSS.percent(30));
assert_color_channel_approx_equals(result.alpha, CSS.percent(0.4));
}, 'CSSHWB can be constructed from four numbers.');

test(() => {
assert_throws_js(TypeError, () => new CSSHWB(CSS.deg(0), CSS.number(1), 0, 0));
assert_throws_js(TypeError, () => new CSSHWB(CSS.deg(0), 0, CSS.number(1), 0));
assert_throws_js(TypeError, () => new CSSHWB(CSS.deg(0), 0, 0, CSS.number(1)));
}, `Constructing a CSSHWB with CSS.number for s, l or alpha throws a TypeError`);

for (const attr of ['w', 'b', 'alpha']) {
test(() => {
const result = new CSSHWB(CSS.deg(0), 0, 0);
assert_throws_js(TypeError, () => result[attr] = CSS.number(1));
}, `Updating a CSSHWB with CSS.number for ${attr} throws a TypeError`);

test(() => {
const result = new CSSHWB(CSS.deg(0), 0, 0);
result[attr] = 0.5;
assert_color_channel_approx_equals(result[attr], CSS.percent(50));
}, 'CSSHWB.' + attr + ' can be updated to a number.');

test(() => {
const result = new CSSHWB(CSS.deg(0), 0, 0);
result[attr] = CSS.percent(50);
assert_color_channel_approx_equals(result[attr], CSS.percent(50));
}, 'CSSHWB.' + attr + ' can be updated with a CSS percent.');
}
</script>

0 comments on commit 1185781

Please sign in to comment.