LibWeb: Route lch()/oklch() through unified ColorFunctionStyleValue

This commit is contained in:
Tim Ledbetter
2026-04-17 12:04:56 +01:00
committed by Sam Atkins
parent 43ba21a45b
commit 8d4f8a2d7f
Notes: github-actions[bot] 2026-04-22 10:54:12 +00:00
6 changed files with 16 additions and 228 deletions

View File

@@ -10,7 +10,6 @@
#include <LibWeb/CSS/Interpolation.h>
#include <LibWeb/CSS/StyleValues/ColorFunctionStyleValue.h>
#include <LibWeb/CSS/StyleValues/KeywordStyleValue.h>
#include <LibWeb/CSS/StyleValues/LCHLikeColorStyleValue.h>
#include <LibWeb/CSS/StyleValues/NumberStyleValue.h>
namespace Web::CSS {
@@ -138,12 +137,12 @@ static MissingComponents extract_missing_components(StyleValue const& style_valu
return { is_component_none(oklab.channel(0)), is_component_none(oklab.channel(1)), is_component_none(oklab.channel(2)), is_component_none(oklab.alpha()) };
}
case ColorStyleValue::ColorType::LCH: {
auto const& lch = as<LCHColorStyleValue>(color);
return { is_component_none(lch.l()), is_component_none(lch.c()), is_component_none(lch.h()), is_component_none(lch.alpha()) };
auto const& lch = as<ColorFunctionStyleValue>(color);
return { is_component_none(lch.channel(0)), is_component_none(lch.channel(1)), is_component_none(lch.channel(2)), is_component_none(lch.alpha()) };
}
case ColorStyleValue::ColorType::OKLCH: {
auto const& oklch = as<OKLCHColorStyleValue>(color);
return { is_component_none(oklch.l()), is_component_none(oklch.c()), is_component_none(oklch.h()), is_component_none(oklch.alpha()) };
auto const& oklch = as<ColorFunctionStyleValue>(color);
return { is_component_none(oklch.channel(0)), is_component_none(oklch.channel(1)), is_component_none(oklch.channel(2)), is_component_none(oklch.alpha()) };
}
case ColorStyleValue::ColorType::RGB: {
auto const& rgb = as<ColorFunctionStyleValue>(color);
@@ -363,13 +362,13 @@ static ValueComparingNonnullRefPtr<StyleValue const> style_value_from_polar_colo
alpha);
}
case PolarColorSpace::Lch:
return LCHLikeColorStyleValue::create<LCHColorStyleValue>(
return ColorFunctionStyleValue::create(ColorStyleValue::ColorType::LCH,
number_or_none(components[0], missing.component(0)),
number_or_none(components[1], missing.component(1)),
number_or_none(components[2], missing.component(2)),
alpha);
case PolarColorSpace::Oklch:
return LCHLikeColorStyleValue::create<OKLCHColorStyleValue>(
return ColorFunctionStyleValue::create(ColorStyleValue::ColorType::OKLCH,
number_or_none(components[0], missing.component(0)),
number_or_none(components[1], missing.component(1)),
number_or_none(components[2], missing.component(2)),
@@ -437,20 +436,20 @@ static Optional<Gfx::ColorComponents> style_value_to_color_components(StyleValue
return Gfx::ColorComponents { static_cast<float>(l.value()), static_cast<float>(a_comp.value()), static_cast<float>(b_comp.value()), a.value() };
}
case ColorStyleValue::ColorType::LCH: {
auto const& lch = as<LCHColorStyleValue>(color);
auto l = ColorStyleValue::resolve_with_reference_value(lch.l(), 100.0f, context);
auto c = ColorStyleValue::resolve_with_reference_value(lch.c(), 150.0f, context);
auto h = ColorStyleValue::resolve_hue(lch.h(), context);
auto const& lch = as<ColorFunctionStyleValue>(color);
auto l = ColorStyleValue::resolve_with_reference_value(lch.channel(0), 100.0f, context);
auto c = ColorStyleValue::resolve_with_reference_value(lch.channel(1), 150.0f, context);
auto h = ColorStyleValue::resolve_hue(lch.channel(2), context);
auto a = resolve_alpha(lch.alpha());
if (!l.has_value() || !c.has_value() || !h.has_value() || !a.has_value())
return {};
return Gfx::ColorComponents { static_cast<float>(l.value()), static_cast<float>(c.value()), static_cast<float>(h.value()), a.value() };
}
case ColorStyleValue::ColorType::OKLCH: {
auto const& oklch = as<OKLCHColorStyleValue>(color);
auto l = ColorStyleValue::resolve_with_reference_value(oklch.l(), 1.0f, context);
auto c = ColorStyleValue::resolve_with_reference_value(oklch.c(), 0.4f, context);
auto h = ColorStyleValue::resolve_hue(oklch.h(), context);
auto const& oklch = as<ColorFunctionStyleValue>(color);
auto l = ColorStyleValue::resolve_with_reference_value(oklch.channel(0), 1.0f, context);
auto c = ColorStyleValue::resolve_with_reference_value(oklch.channel(1), 0.4f, context);
auto h = ColorStyleValue::resolve_hue(oklch.channel(2), context);
auto a = resolve_alpha(oklch.alpha());
if (!l.has_value() || !c.has_value() || !h.has_value() || !a.has_value())
return {};

View File

@@ -56,7 +56,6 @@
#include <LibWeb/CSS/StyleValues/ImageStyleValue.h>
#include <LibWeb/CSS/StyleValues/IntegerStyleValue.h>
#include <LibWeb/CSS/StyleValues/KeywordStyleValue.h>
#include <LibWeb/CSS/StyleValues/LCHLikeColorStyleValue.h>
#include <LibWeb/CSS/StyleValues/LengthStyleValue.h>
#include <LibWeb/CSS/StyleValues/LightDarkStyleValue.h>
#include <LibWeb/CSS/StyleValues/LinearGradientStyleValue.h>
@@ -1893,7 +1892,7 @@ RefPtr<StyleValue const> Parser::parse_lch_color_value(TokenStream<ComponentValu
auto& color_values = *maybe_color_values;
return LCHLikeColorStyleValue::create<LCHColorStyleValue>(color_values[0].release_nonnull(),
return ColorFunctionStyleValue::create(ColorStyleValue::ColorType::LCH, color_values[0].release_nonnull(),
color_values[1].release_nonnull(),
color_values[2].release_nonnull(),
color_values[3].release_nonnull());
@@ -1913,7 +1912,7 @@ RefPtr<StyleValue const> Parser::parse_oklch_color_value(TokenStream<ComponentVa
auto& color_values = *maybe_color_values;
return LCHLikeColorStyleValue::create<OKLCHColorStyleValue>(color_values[0].release_nonnull(),
return ColorFunctionStyleValue::create(ColorStyleValue::ColorType::OKLCH, color_values[0].release_nonnull(),
color_values[1].release_nonnull(),
color_values[2].release_nonnull(),
color_values[3].release_nonnull());

View File

@@ -1,121 +0,0 @@
/*
* Copyright (c) 2024-2026, Sam Atkins <sam@ladybird.org>
* Copyright (c) 2025, Tim Ledbetter <tim.ledbetter@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "LCHLikeColorStyleValue.h"
#include <AK/Math.h>
#include <AK/TypeCasts.h>
#include <LibWeb/CSS/Serialize.h>
#include <LibWeb/CSS/StyleValues/CalculatedStyleValue.h>
#include <LibWeb/CSS/StyleValues/KeywordStyleValue.h>
#include <LibWeb/CSS/StyleValues/NumberStyleValue.h>
#include <LibWeb/CSS/StyleValues/PercentageStyleValue.h>
namespace Web::CSS {
bool LCHLikeColorStyleValue::equals(StyleValue const& other) const
{
if (type() != other.type())
return false;
auto const& other_color = other.as_color();
if (color_type() != other_color.color_type())
return false;
auto const& other_oklch_like = as<LCHLikeColorStyleValue>(other_color);
return m_properties == other_oklch_like.m_properties;
}
Optional<Color> LCHColorStyleValue::to_color(ColorResolutionContext color_resolution_context) const
{
auto raw_l_val = resolve_with_reference_value(m_properties.l, 100, color_resolution_context.calculation_resolution_context);
auto c_val = resolve_with_reference_value(m_properties.c, 150, color_resolution_context.calculation_resolution_context);
auto raw_h_val = resolve_hue(m_properties.h, color_resolution_context.calculation_resolution_context);
auto alpha_val = resolve_alpha(m_properties.alpha, color_resolution_context.calculation_resolution_context);
if (!raw_l_val.has_value() || !c_val.has_value() || !raw_h_val.has_value() || !alpha_val.has_value())
return {};
auto l_val = clamp(raw_l_val.value(), 0, 100);
auto h_val = AK::to_radians(raw_h_val.value());
return Color::from_lab(l_val, c_val.value() * cos(h_val), c_val.value() * sin(h_val), alpha_val.value());
}
ValueComparingNonnullRefPtr<StyleValue const> LCHColorStyleValue::absolutized(ComputationContext const& context) const
{
auto l = m_properties.l->absolutized(context);
auto c = m_properties.c->absolutized(context);
auto h = m_properties.h->absolutized(context);
auto alpha = m_properties.alpha->absolutized(context);
if (l == m_properties.l && c == m_properties.c && h == m_properties.h && alpha == m_properties.alpha)
return *this;
return LCHLikeColorStyleValue::create<LCHColorStyleValue>(move(l), move(c), move(h), move(alpha));
}
// https://www.w3.org/TR/css-color-4/#serializing-lab-lch
void LCHColorStyleValue::serialize(StringBuilder& builder, SerializationMode mode) const
{
builder.append("lch("sv);
serialize_color_component(builder, mode, m_properties.l, 100, 0, 100);
builder.append(' ');
serialize_color_component(builder, mode, m_properties.c, 150, 0);
builder.append(' ');
serialize_hue_component(builder, mode, m_properties.h);
if ((!m_properties.alpha->is_number() || m_properties.alpha->as_number().number() < 1)
&& (!m_properties.alpha->is_percentage() || m_properties.alpha->as_percentage().percentage().as_fraction() < 1)) {
builder.append(" / "sv);
serialize_alpha_component(builder, mode, m_properties.alpha);
}
builder.append(')');
}
Optional<Color> OKLCHColorStyleValue::to_color(ColorResolutionContext color_resolution_context) const
{
auto raw_l_val = resolve_with_reference_value(m_properties.l, 1.0, color_resolution_context.calculation_resolution_context);
auto raw_c_val = resolve_with_reference_value(m_properties.c, 0.4, color_resolution_context.calculation_resolution_context);
auto raw_h_val = resolve_hue(m_properties.h, color_resolution_context.calculation_resolution_context);
auto alpha_val = resolve_alpha(m_properties.alpha, color_resolution_context.calculation_resolution_context);
if (!raw_l_val.has_value() || !raw_c_val.has_value() || !raw_h_val.has_value() || !alpha_val.has_value())
return {};
auto l_val = clamp(raw_l_val.value(), 0, 1);
auto c_val = max(raw_c_val.value(), 0);
auto h_val = AK::to_radians(raw_h_val.value());
return Color::from_oklab(l_val, c_val * cos(h_val), c_val * sin(h_val), alpha_val.value());
}
ValueComparingNonnullRefPtr<StyleValue const> OKLCHColorStyleValue::absolutized(ComputationContext const& context) const
{
auto l = m_properties.l->absolutized(context);
auto c = m_properties.c->absolutized(context);
auto h = m_properties.h->absolutized(context);
auto alpha = m_properties.alpha->absolutized(context);
if (l == m_properties.l && c == m_properties.c && h == m_properties.h && alpha == m_properties.alpha)
return *this;
return LCHLikeColorStyleValue::create<OKLCHColorStyleValue>(l, c, h, alpha);
}
// https://www.w3.org/TR/css-color-4/#serializing-oklab-oklch
void OKLCHColorStyleValue::serialize(StringBuilder& builder, SerializationMode mode) const
{
builder.append("oklch("sv);
serialize_color_component(builder, mode, m_properties.l, 1.0f, 0, 1);
builder.append(' ');
serialize_color_component(builder, mode, m_properties.c, 0.4f, 0);
builder.append(' ');
serialize_hue_component(builder, mode, m_properties.h);
if ((!m_properties.alpha->is_number() || m_properties.alpha->as_number().number() < 1)
&& (!m_properties.alpha->is_percentage() || m_properties.alpha->as_percentage().percentage().as_fraction() < 1)) {
builder.append(" / "sv);
serialize_alpha_component(builder, mode, m_properties.alpha);
}
builder.append(')');
}
}

View File

@@ -1,87 +0,0 @@
/*
* Copyright (c) 2024-2026, Sam Atkins <sam@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibWeb/CSS/StyleValues/ColorStyleValue.h>
#include <LibWeb/CSS/StyleValues/ComputationContext.h>
#include <LibWeb/CSS/StyleValues/NumberStyleValue.h>
namespace Web::CSS {
class LCHLikeColorStyleValue : public ColorStyleValue {
public:
template<DerivedFrom<LCHLikeColorStyleValue> T>
static ValueComparingNonnullRefPtr<T const> create(ValueComparingNonnullRefPtr<StyleValue const> l, ValueComparingNonnullRefPtr<StyleValue const> c, ValueComparingNonnullRefPtr<StyleValue const> h, ValueComparingRefPtr<StyleValue const> alpha = {})
{
// alpha defaults to 1
if (!alpha)
alpha = NumberStyleValue::create(1);
return adopt_ref(*new (nothrow) T({}, move(l), move(c), move(h), alpha.release_nonnull()));
}
virtual ~LCHLikeColorStyleValue() override = default;
StyleValue const& l() const { return *m_properties.l; }
StyleValue const& c() const { return *m_properties.c; }
StyleValue const& h() const { return *m_properties.h; }
StyleValue const& alpha() const { return *m_properties.alpha; }
virtual bool equals(StyleValue const& other) const override;
virtual bool is_computationally_independent() const override
{
return m_properties.l->is_computationally_independent()
&& m_properties.c->is_computationally_independent()
&& m_properties.h->is_computationally_independent()
&& m_properties.alpha->is_computationally_independent();
}
protected:
LCHLikeColorStyleValue(ColorType color_type, ValueComparingNonnullRefPtr<StyleValue const> l, ValueComparingNonnullRefPtr<StyleValue const> c, ValueComparingNonnullRefPtr<StyleValue const> h, ValueComparingNonnullRefPtr<StyleValue const> alpha)
: ColorStyleValue(color_type, ColorSyntax::Modern)
, m_properties { .l = move(l), .c = move(c), .h = move(h), .alpha = move(alpha) }
{
}
struct Properties {
ValueComparingNonnullRefPtr<StyleValue const> l;
ValueComparingNonnullRefPtr<StyleValue const> c;
ValueComparingNonnullRefPtr<StyleValue const> h;
ValueComparingNonnullRefPtr<StyleValue const> alpha;
bool operator==(Properties const&) const = default;
} m_properties;
};
class LCHColorStyleValue final : public LCHLikeColorStyleValue {
public:
LCHColorStyleValue(Badge<LCHLikeColorStyleValue>, ValueComparingNonnullRefPtr<StyleValue const> l, ValueComparingNonnullRefPtr<StyleValue const> c, ValueComparingNonnullRefPtr<StyleValue const> h, ValueComparingNonnullRefPtr<StyleValue const> alpha)
: LCHLikeColorStyleValue(ColorType::LCH, move(l), move(c), move(h), move(alpha))
{
}
virtual ~LCHColorStyleValue() override = default;
virtual Optional<Color> to_color(ColorResolutionContext) const override;
virtual ValueComparingNonnullRefPtr<StyleValue const> absolutized(ComputationContext const&) const override;
virtual void serialize(StringBuilder&, SerializationMode) const override;
};
class OKLCHColorStyleValue final : public LCHLikeColorStyleValue {
public:
OKLCHColorStyleValue(Badge<LCHLikeColorStyleValue>, ValueComparingNonnullRefPtr<StyleValue const> l, ValueComparingNonnullRefPtr<StyleValue const> c, ValueComparingNonnullRefPtr<StyleValue const> h, ValueComparingNonnullRefPtr<StyleValue const> alpha)
: LCHLikeColorStyleValue(ColorType::OKLCH, move(l), move(c), move(h), move(alpha))
{
}
virtual ~OKLCHColorStyleValue() override = default;
virtual Optional<Color> to_color(ColorResolutionContext) const override;
virtual ValueComparingNonnullRefPtr<StyleValue const> absolutized(ComputationContext const&) const override;
virtual void serialize(StringBuilder&, SerializationMode) const override;
};
}