LibGfx: Preserve out-of-range components in HSL to sRGB conversion

This commit is contained in:
Tim Ledbetter 2026-04-15 23:35:11 +01:00 committed by Andreas Kling
parent 176e1a5a7c
commit 4575d23b1e
4 changed files with 70 additions and 3 deletions

View file

@ -171,7 +171,7 @@ public:
static constexpr Color from_hsl(float h_degrees, float s, float l) { return from_hsla(h_degrees, s, l, 1.0); }
static constexpr Color from_hsla(float h_degrees, float s, float l, float a)
{
auto srgb = hsl_to_srgb({ h_degrees, s, l, a });
auto srgb = hsl_to_srgb({ h_degrees, clamp(s, 0.0f, 1.0f), clamp(l, 0.0f, 1.0f), a });
u8 r_u8 = clamp(lroundf(srgb[0] * 255.0f), 0, 255);
u8 g_u8 = clamp(lroundf(srgb[1] * 255.0f), 0, 255);
u8 b_u8 = clamp(lroundf(srgb[2] * 255.0f), 0, 255);

View file

@ -43,8 +43,11 @@ constexpr ColorComponents hsl_to_srgb(ColorComponents const& hsl)
if (h < 0.0f)
h += 360.0f;
float s = clamp(hsl[1], 0.0f, 1.0f);
float l = clamp(hsl[2], 0.0f, 1.0f);
// NB: Saturation and lightness are intentionally left unclamped. Color interpolation in HSL can produce
// out-of-range s/l when representing colors outside the sRGB gamut, and those values should be able to
// round-trip back to sRGB correctly.
float s = hsl[1];
float l = hsl[2];
float a = clamp(hsl.alpha(), 0.0f, 1.0f);
// Algorithm from https://drafts.csswg.org/css-color-3/#hsl-color

View file

@ -0,0 +1,23 @@
Harness status: OK
Found 18 tests
18 Pass
Pass Property color value 'color-mix(in hsl, color(display-p3 0 1 0) 100%, rgb(0, 0, 0) 0%)'
Pass Property color value 'color-mix(in hsl, lab(100 104.3 -50.9) 100%, rgb(0, 0, 0) 0%)'
Pass Property color value 'color-mix(in hsl, lab(0 104.3 -50.9) 100%, rgb(0, 0, 0) 0%)'
Pass Property color value 'color-mix(in hsl, lch(100 116 334) 100%, rgb(0, 0, 0) 0%)'
Pass Property color value 'color-mix(in hsl, lch(0 116 334) 100%, rgb(0, 0, 0) 0%)'
Pass Property color value 'color-mix(in hsl, oklab(1 0.365 -0.16) 100%, rgb(0, 0, 0) 0%)'
Pass Property color value 'color-mix(in hsl, oklab(0 0.365 -0.16) 100%, rgb(0, 0, 0) 0%)'
Pass Property color value 'color-mix(in hsl, oklch(1 0.399 336.3) 100%, rgb(0, 0, 0) 0%)'
Pass Property color value 'color-mix(in hsl, oklch(0 0.399 336.3) 100%, rgb(0, 0, 0) 0%)'
Pass Property color value 'color-mix(in hwb, color(display-p3 0 1 0) 100%, rgb(0, 0, 0) 0%)'
Pass Property color value 'color-mix(in hwb, lab(100 104.3 -50.9) 100%, rgb(0, 0, 0) 0%)'
Pass Property color value 'color-mix(in hwb, lab(0 104.3 -50.9) 100%, rgb(0, 0, 0) 0%)'
Pass Property color value 'color-mix(in hwb, lch(100 116 334) 100%, rgb(0, 0, 0) 0%)'
Pass Property color value 'color-mix(in hwb, lch(0 116 334) 100%, rgb(0, 0, 0) 0%)'
Pass Property color value 'color-mix(in hwb, oklab(1 0.365 -0.16) 100%, rgb(0, 0, 0) 0%)'
Pass Property color value 'color-mix(in hwb, oklab(0 0.365 -0.16) 100%, rgb(0, 0, 0) 0%)'
Pass Property color value 'color-mix(in hwb, oklch(1 0.399 336.3) 100%, rgb(0, 0, 0) 0%)'
Pass Property color value 'color-mix(in hwb, oklch(0 0.399 336.3) 100%, rgb(0, 0, 0) 0%)'

View file

@ -0,0 +1,41 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>CSS Color Level 4: Computation of colors using color-mix() function syntax that result in out-of-gamut sRGB colors</title>
<link rel="author" title="Sam Weinig" href="mailto:weinig@apple.com">
<link rel="author" title="Aaron Krajeski" href="mailto:aaronhk@chromium.org">
<link rel="help" href="https://drafts.csswg.org/css-color-5/#color-mix">
<link rel="help" href="https://drafts.csswg.org/css-color-5/#resolving-mix">
<link rel="help" href="https://drafts.csswg.org/css-color-5/#serial-color-mix">
<meta name="assert" content="gamut mapping works for computed value of color-mix()">
<script src="../../../resources/testharness.js"></script>
<script src="../../../resources/testharnessreport.js"></script>
<script src="../../../css/support/computed-testcommon.js"></script>
<script src="../../../css/support/color-testcommon.js"></script>
</head>
<body>
<div id="target"></div>
<script>
fuzzy_test_computed_color(`color-mix(in hsl, color(display-p3 0 1 0) 100%, rgb(0, 0, 0) 0%)`, `color(srgb -0.511814 1.01832 -0.310726)`);
fuzzy_test_computed_color(`color-mix(in hsl, lab(100 104.3 -50.9) 100%, rgb(0, 0, 0) 0%)`, `color(srgb 1.59343 0.58802 1.40564)`);
fuzzy_test_computed_color(`color-mix(in hsl, lab(0 104.3 -50.9) 100%, rgb(0, 0, 0) 0%)`, `color(srgb 0.351376 -0.213938 0.299501)`);
fuzzy_test_computed_color(`color-mix(in hsl, lch(100 116 334) 100%, rgb(0, 0, 0) 0%)`, `color(srgb 1.59328 0.588284 1.40527)`);
fuzzy_test_computed_color(`color-mix(in hsl, lch(0 116 334) 100%, rgb(0, 0, 0) 0%)`, `color(srgb 0.351307 -0.213865 0.299236)`);
fuzzy_test_computed_color(`color-mix(in hsl, oklab(1 0.365 -0.16) 100%, rgb(0, 0, 0) 0%)`, `color(srgb 1.59295 0.360371 1.38571)`);
fuzzy_test_computed_color(`color-mix(in hsl, oklab(0 0.365 -0.16) 100%, rgb(0, 0, 0) 0%)`, `color(srgb 0.0763893 -0.0456266 0.0932598)`);
fuzzy_test_computed_color(`color-mix(in hsl, oklch(1 0.399 336.3) 100%, rgb(0, 0, 0) 0%)`, `color(srgb 1.59328 0.358734 1.38664)`);
fuzzy_test_computed_color(`color-mix(in hsl, oklch(0 0.399 336.3) 100%, rgb(0, 0, 0) 0%)`, `color(srgb 0.076536 -0.045825 0.0937443)`);
fuzzy_test_computed_color(`color-mix(in hwb, color(display-p3 0 1 0) 100%, rgb(0, 0, 0) 0%)`, `color(srgb -0.511814 1.01832 -0.310726)`);
fuzzy_test_computed_color(`color-mix(in hwb, lab(100 104.3 -50.9) 100%, rgb(0, 0, 0) 0%)`, `color(srgb 1.59343 0.58802 1.40564)`);
fuzzy_test_computed_color(`color-mix(in hwb, lab(0 104.3 -50.9) 100%, rgb(0, 0, 0) 0%)`, `color(srgb 0.351376 -0.213938 0.299501)`);
fuzzy_test_computed_color(`color-mix(in hwb, lch(100 116 334) 100%, rgb(0, 0, 0) 0%)`, `color(srgb 1.59328 0.588284 1.40527)`);
fuzzy_test_computed_color(`color-mix(in hwb, lch(0 116 334) 100%, rgb(0, 0, 0) 0%)`, `color(srgb 0.351307 -0.213865 0.299236)`);
fuzzy_test_computed_color(`color-mix(in hwb, oklab(1 0.365 -0.16) 100%, rgb(0, 0, 0) 0%)`, `color(srgb 1.59295 0.360371 1.38571)`);
fuzzy_test_computed_color(`color-mix(in hwb, oklab(0 0.365 -0.16) 100%, rgb(0, 0, 0) 0%)`, `color(srgb 0.0763893 -0.0456266 0.0932598)`);
fuzzy_test_computed_color(`color-mix(in hwb, oklch(1 0.399 336.3) 100%, rgb(0, 0, 0) 0%)`, `color(srgb 1.59328 0.358736 1.38664)`);
fuzzy_test_computed_color(`color-mix(in hwb, oklch(0 0.399 336.3) 100%, rgb(0, 0, 0) 0%)`, `color(srgb 0.0765361 -0.045825 0.0937443)`);
</script>
</body>
</html>