LibWeb: Fix color interpolation by premultiplying alpha

The current Color::interpolate_color method does not follow the specs
properly. Started improving it by handling premultiplied alpha in color
interpolation.

Only one WPT test covers this (color-transition-premultiplied), which we
currently pass due to a different approach in Color.mixed_with.
This commit is contained in:
norbiros 2025-08-11 17:15:26 +02:00 committed by Jelle Raaijmakers
parent d91d28dc2a
commit 783ae44462
4 changed files with 38 additions and 6 deletions

View File

@ -236,6 +236,16 @@ public:
return from_linear_srgb(red, green, blue, alpha);
}
constexpr Oklab to_premultiplied_oklab()
{
auto oklab = to_oklab();
return {
oklab.L * alpha() / 255,
oklab.a * alpha() / 255,
oklab.b * alpha() / 255,
};
}
// https://bottosson.github.io/posts/oklab/
constexpr Oklab to_oklab()
{

View File

@ -890,6 +890,7 @@ Color interpolate_color(Color from, Color to, float delta, ColorSyntax syntax)
// https://drafts.csswg.org/css-color/#interpolation
// FIXME: Handle all interpolation methods.
// FIXME: Handle "analogous", "missing", and "powerless" components, somehow.
// FIXME: Remove duplicated code with Color::mixed_with(Color other, float weight)
// https://drafts.csswg.org/css-color/#interpolation-space
// If the host syntax does not define what color space interpolation should take place in, it defaults to Oklab.
@ -899,21 +900,30 @@ Color interpolate_color(Color from, Color to, float delta, ColorSyntax syntax)
Color result;
if (syntax == ColorSyntax::Modern) {
auto from_oklab = from.to_oklab();
auto to_oklab = to.to_oklab();
// 5. changing the color components to premultiplied form
auto from_oklab = from.to_premultiplied_oklab();
auto to_oklab = to.to_premultiplied_oklab();
// 6. linearly interpolating each component of the computed value of the color separately
// 7. undoing premultiplication
auto from_alpha = from.alpha() / 255.0f;
auto to_alpha = to.alpha() / 255.0f;
auto interpolated_alpha = interpolate_raw(from_alpha, to_alpha, delta);
result = Color::from_oklab(
interpolate_raw(from_oklab.L, to_oklab.L, delta),
interpolate_raw(from_oklab.a, to_oklab.a, delta),
interpolate_raw(from_oklab.b, to_oklab.b, delta));
interpolate_raw(from_oklab.L, to_oklab.L, delta) / interpolated_alpha,
interpolate_raw(from_oklab.a, to_oklab.a, delta) / interpolated_alpha,
interpolate_raw(from_oklab.b, to_oklab.b, delta) / interpolated_alpha,
interpolated_alpha);
} else {
result = Color {
interpolate_raw(from.red(), to.red(), delta),
interpolate_raw(from.green(), to.green(), delta),
interpolate_raw(from.blue(), to.blue(), delta),
interpolate_raw(from.alpha(), to.alpha(), delta)
};
}
result.set_alpha(interpolate_raw(from.alpha(), to.alpha(), delta));
return result;
}

View File

@ -0,0 +1 @@
rgba(255, 0, 0, 0.1)

View File

@ -0,0 +1,11 @@
<!DOCTYPE html>
<script src="include.js"></script>
<script>
test(() => {
const div = document.createElement('div');
div.style.backgroundColor = 'color-mix(in oklab, red 10%, transparent)';
document.body.appendChild(div);
println(getComputedStyle(div).backgroundColor);
});
</script>