diff --git a/Userland/Libraries/LibGfx/Color.cpp b/Userland/Libraries/LibGfx/Color.cpp index b63904c0d92..b2653162d35 100644 --- a/Userland/Libraries/LibGfx/Color.cpp +++ b/Userland/Libraries/LibGfx/Color.cpp @@ -1,6 +1,7 @@ /* * Copyright (c) 2018-2020, Andreas Kling * Copyright (c) 2019-2023, Shannon Booth + * Copyright (c) 2024, Lucas Chollet * * SPDX-License-Identifier: BSD-2-Clause */ @@ -395,6 +396,55 @@ Vector Color::tints(u32 steps, float max) const return tints; } +Color Color::from_xyz50(float x, float y, float z, float alpha) +{ + // See commit description for these values + float red = 3.13397926 * x - 1.61689519 * y - 0.49070587 * z; + float green = -0.97840009 * x + 1.91589112 * y + 0.03339256 * z; + float blue = 0.07200357 * x - 0.22897505 * y + 1.40517398 * z; + + auto linear_to_srgb = [](float c) { + return c >= 0.0031308f ? 1.055f * pow(c, 0.4166666f) - 0.055f : 12.92f * c; + }; + + red = linear_to_srgb(red) * 255.f; + green = linear_to_srgb(green) * 255.f; + blue = linear_to_srgb(blue) * 255.f; + + return Color( + clamp(lroundf(red), 0, 255), + clamp(lroundf(green), 0, 255), + clamp(lroundf(blue), 0, 255), + clamp(lroundf(alpha * 255.f), 0, 255)); +} + +Color Color::from_lab(float L, float a, float b, float alpha) +{ + // Third edition of "Colorimetry" by the CIE + // 8.2.1 CIE 1976 (L*a*b*) colour space; CIELAB colour space + float y = (L + 16) / 116; + float x = y + a / 500; + float z = y - b / 200; + + auto f_inv = [](float t) -> float { + constexpr auto delta = 24. / 116; + if (t > delta) + return t * t * t; + return (108. / 841) * (t - 116. / 16); + }; + + // D50 + constexpr float x_n = 0.96422; + constexpr float y_n = 1; + constexpr float z_n = 0.82521; + + x = x_n * f_inv(x); + y = y_n * f_inv(y); + z = z_n * f_inv(z); + + return from_xyz50(x, y, z, alpha); +} + } template<> diff --git a/Userland/Libraries/LibGfx/Color.h b/Userland/Libraries/LibGfx/Color.h index f00a48b71b9..f02aa8c2d4e 100644 --- a/Userland/Libraries/LibGfx/Color.h +++ b/Userland/Libraries/LibGfx/Color.h @@ -182,6 +182,9 @@ public: return Color(r_u8, g_u8, b_u8, a_u8); } + static Color from_lab(float L, float a, float b, float alpha = 1.0f); + static Color from_xyz50(float x, float y, float z, float alpha = 1.0f); + // https://bottosson.github.io/posts/oklab/ static constexpr Color from_oklab(float L, float a, float b, float alpha = 1.0f) { diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp index 91909abc57d..c8d104df621 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -3296,6 +3296,26 @@ Optional, 4>> Parser::parse_lab_like_color_value(Tok return Array { move(l), move(a), move(b), move(alpha) }; } +// https://www.w3.org/TR/css-color-4/#funcdef-lab +RefPtr Parser::parse_lab_color_value(TokenStream& outer_tokens) +{ + // lab() = lab( [ | | none] + // [ | | none] + // [ | | none] + // [ / [ | none] ]? ) + + auto maybe_color_values = parse_lab_like_color_value(outer_tokens, "lab"sv); + if (!maybe_color_values.has_value()) + return {}; + + auto& color_values = *maybe_color_values; + + return CSSLab::create(color_values[0].release_nonnull(), + color_values[1].release_nonnull(), + color_values[2].release_nonnull(), + color_values[3].release_nonnull()); +} + // https://www.w3.org/TR/css-color-4/#funcdef-oklab RefPtr Parser::parse_oklab_color_value(TokenStream& outer_tokens) { @@ -3387,6 +3407,8 @@ RefPtr Parser::parse_color_value(TokenStream& tok return hsl; if (auto hwb = parse_hwb_color_value(tokens)) return hwb; + if (auto lab = parse_lab_color_value(tokens)) + return lab; if (auto oklab = parse_oklab_color_value(tokens)) return oklab; if (auto oklch = parse_oklch_color_value(tokens)) diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h index ad4b8cabac6..2cb4dd4b4d7 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h @@ -251,6 +251,7 @@ private: RefPtr parse_hsl_color_value(TokenStream&); RefPtr parse_hwb_color_value(TokenStream&); Optional, 4>> parse_lab_like_color_value(TokenStream&, StringView); + RefPtr parse_lab_color_value(TokenStream&); RefPtr parse_oklab_color_value(TokenStream&); RefPtr parse_oklch_color_value(TokenStream&); RefPtr parse_color_value(TokenStream&); diff --git a/Userland/Libraries/LibWeb/CSS/StyleValues/CSSColorValue.h b/Userland/Libraries/LibWeb/CSS/StyleValues/CSSColorValue.h index 677bcd71df7..32ab45a1881 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleValues/CSSColorValue.h +++ b/Userland/Libraries/LibWeb/CSS/StyleValues/CSSColorValue.h @@ -26,6 +26,7 @@ public: RGB, HSL, HWB, + Lab, OKLab, OKLCH, }; diff --git a/Userland/Libraries/LibWeb/CSS/StyleValues/CSSLabLike.cpp b/Userland/Libraries/LibWeb/CSS/StyleValues/CSSLabLike.cpp index ccdcbf2e767..f5210e0eece 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleValues/CSSLabLike.cpp +++ b/Userland/Libraries/LibWeb/CSS/StyleValues/CSSLabLike.cpp @@ -41,4 +41,21 @@ String CSSOKLab::to_string() const return serialize_a_srgb_value(to_color({})); } +Color CSSLab::to_color(Optional) const +{ + auto const l_val = clamp(resolve_with_reference_value(m_properties.l, 100).value_or(0), 0, 100); + auto const a_val = resolve_with_reference_value(m_properties.a, 125).value_or(0); + auto const b_val = resolve_with_reference_value(m_properties.b, 125).value_or(0); + auto const alpha_val = resolve_alpha(m_properties.alpha).value_or(1); + + return Color::from_lab(l_val, a_val, b_val, alpha_val); +} + +// https://www.w3.org/TR/css-color-4/#serializing-lab-lch +String CSSLab::to_string() const +{ + // FIXME: Do this properly, taking unresolved calculated values into account. + return serialize_a_srgb_value(to_color({})); +} + } diff --git a/Userland/Libraries/LibWeb/CSS/StyleValues/CSSLabLike.h b/Userland/Libraries/LibWeb/CSS/StyleValues/CSSLabLike.h index 783a03cfa9d..e82070e483f 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleValues/CSSLabLike.h +++ b/Userland/Libraries/LibWeb/CSS/StyleValues/CSSLabLike.h @@ -60,4 +60,26 @@ private: } }; +// https://drafts.css-houdini.org/css-typed-om-1/#csslab +class CSSLab final : public CSSLabLike { +public: + static ValueComparingNonnullRefPtr create(ValueComparingNonnullRefPtr l, ValueComparingNonnullRefPtr a, ValueComparingNonnullRefPtr b, ValueComparingRefPtr alpha = {}) + { + // alpha defaults to 1 + if (!alpha) + return adopt_ref(*new (nothrow) CSSLab(move(l), move(a), move(b), NumberStyleValue::create(1))); + + return adopt_ref(*new (nothrow) CSSLab(move(l), move(a), move(b), alpha.release_nonnull())); + } + + virtual Color to_color(Optional) const override; + virtual String to_string() const override; + +private: + CSSLab(ValueComparingNonnullRefPtr l, ValueComparingNonnullRefPtr a, ValueComparingNonnullRefPtr b, ValueComparingNonnullRefPtr alpha) + : CSSLabLike(ColorType::Lab, move(l), move(a), move(b), move(alpha)) + { + } +}; + }