diff --git a/Libraries/LibGfx/InterpolationColorSpace.h b/Libraries/LibGfx/InterpolationColorSpace.h new file mode 100644 index 00000000000..8e7c00bcd18 --- /dev/null +++ b/Libraries/LibGfx/InterpolationColorSpace.h @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2025, Tim Ledbetter . + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +namespace Gfx { + +enum class InterpolationColorSpace { + LinearRGB, + SRGB, +}; + +} diff --git a/Libraries/LibWeb/CSS/ComputedProperties.cpp b/Libraries/LibWeb/CSS/ComputedProperties.cpp index 184da404027..1cdec337bbf 100644 --- a/Libraries/LibWeb/CSS/ComputedProperties.cpp +++ b/Libraries/LibWeb/CSS/ComputedProperties.cpp @@ -232,6 +232,12 @@ Color ComputedProperties::color_or_fallback(PropertyID id, ColorResolutionContex return value.to_color(color_resolution_context).value(); } +ColorInterpolation ComputedProperties::color_interpolation() const +{ + auto const& value = property(PropertyID::ColorInterpolation); + return keyword_to_color_interpolation(value.to_keyword()).value_or(CSS::ColorInterpolation::Auto); +} + // https://drafts.csswg.org/css-color-adjust-1/#determine-the-used-color-scheme PreferredColorScheme ComputedProperties::color_scheme(PreferredColorScheme preferred_scheme, Optional const&> document_supported_schemes) const { diff --git a/Libraries/LibWeb/CSS/ComputedProperties.h b/Libraries/LibWeb/CSS/ComputedProperties.h index 3bf965f3a94..18ac7eae49a 100644 --- a/Libraries/LibWeb/CSS/ComputedProperties.h +++ b/Libraries/LibWeb/CSS/ComputedProperties.h @@ -78,6 +78,7 @@ public: Optional length_percentage(PropertyID) const; LengthBox length_box(PropertyID left_id, PropertyID top_id, PropertyID right_id, PropertyID bottom_id, Length const& default_value) const; Color color_or_fallback(PropertyID, ColorResolutionContext, Color fallback) const; + ColorInterpolation color_interpolation() const; PreferredColorScheme color_scheme(PreferredColorScheme, Optional const&> document_supported_schemes) const; TextAnchor text_anchor() const; TextAlign text_align() const; diff --git a/Libraries/LibWeb/CSS/ComputedValues.h b/Libraries/LibWeb/CSS/ComputedValues.h index aa8c04a2a48..5dff89bd4e6 100644 --- a/Libraries/LibWeb/CSS/ComputedValues.h +++ b/Libraries/LibWeb/CSS/ComputedValues.h @@ -104,6 +104,7 @@ public: static Color caret_color() { return Color::Black; } static CSS::Clear clear() { return CSS::Clear::None; } static CSS::Clip clip() { return CSS::Clip::make_auto(); } + static CSS::ColorInterpolation color_interpolation() { return CSS::ColorInterpolation::Auto; } static CSS::PreferredColorScheme color_scheme() { return CSS::PreferredColorScheme::Auto; } static CSS::ContentVisibility content_visibility() { return CSS::ContentVisibility::Visible; } static CursorData cursor() { return { CSS::CursorPredefined::Auto }; } @@ -428,6 +429,7 @@ public: Color caret_color() const { return m_inherited.caret_color; } CSS::Clear clear() const { return m_noninherited.clear; } CSS::Clip clip() const { return m_noninherited.clip; } + CSS::ColorInterpolation color_interpolation() const { return m_inherited.color_interpolation; } CSS::PreferredColorScheme color_scheme() const { return m_inherited.color_scheme; } CSS::ContentVisibility content_visibility() const { return m_inherited.content_visibility; } Vector const& cursor() const { return m_inherited.cursor; } @@ -638,6 +640,7 @@ protected: CSS::Length border_spacing_vertical { InitialValues::border_spacing() }; CSS::CaptionSide caption_side { InitialValues::caption_side() }; Color color { InitialValues::color() }; + CSS::ColorInterpolation color_interpolation { InitialValues::color_interpolation() }; CSS::PreferredColorScheme color_scheme { InitialValues::color_scheme() }; Optional accent_color {}; Color webkit_text_fill_color { InitialValues::color() }; @@ -837,6 +840,7 @@ public: void set_border_spacing_vertical(CSS::Length border_spacing_vertical) { m_inherited.border_spacing_vertical = border_spacing_vertical; } void set_caption_side(CSS::CaptionSide caption_side) { m_inherited.caption_side = caption_side; } void set_color(Color color) { m_inherited.color = color; } + void set_color_interpolation(CSS::ColorInterpolation color_interpolation) { m_inherited.color_interpolation = color_interpolation; } void set_color_scheme(CSS::PreferredColorScheme color_scheme) { m_inherited.color_scheme = color_scheme; } void set_clip(CSS::Clip const& clip) { m_noninherited.clip = clip; } void set_content(ContentData const& content) { m_noninherited.content = content; } diff --git a/Libraries/LibWeb/CSS/Enums.json b/Libraries/LibWeb/CSS/Enums.json index d8ffedd9b3b..9aa65c89aa5 100644 --- a/Libraries/LibWeb/CSS/Enums.json +++ b/Libraries/LibWeb/CSS/Enums.json @@ -132,6 +132,11 @@ "inline-start", "inline-end" ], + "color-interpolation": [ + "auto", + "linearrgb", + "srgb" + ], "column-span": [ "none", "all" diff --git a/Libraries/LibWeb/CSS/Keywords.json b/Libraries/LibWeb/CSS/Keywords.json index 60b125573a5..25a2cdb5f79 100644 --- a/Libraries/LibWeb/CSS/Keywords.json +++ b/Libraries/LibWeb/CSS/Keywords.json @@ -301,6 +301,7 @@ "lighter", "line-through", "linear", + "linearrgb", "lining-nums", "linktext", "list-item", diff --git a/Libraries/LibWeb/CSS/Properties.json b/Libraries/LibWeb/CSS/Properties.json index d1130b81590..32e55844fd0 100644 --- a/Libraries/LibWeb/CSS/Properties.json +++ b/Libraries/LibWeb/CSS/Properties.json @@ -1229,6 +1229,15 @@ "hashless-hex-color" ] }, + "color-interpolation": { + "affects-layout": false, + "animation-type": "discrete", + "inherited": true, + "initial": "srgb", + "valid-types": [ + "color-interpolation" + ] + }, "color-scheme": { "affects-layout": false, "animation-type": "discrete", diff --git a/Libraries/LibWeb/Layout/Node.cpp b/Libraries/LibWeb/Layout/Node.cpp index 5c042d48e16..8b1b804999b 100644 --- a/Libraries/LibWeb/Layout/Node.cpp +++ b/Libraries/LibWeb/Layout/Node.cpp @@ -1017,6 +1017,7 @@ void NodeWithStyle::apply_style(CSS::ComputedProperties const& computed_style) computed_values.set_contain(computed_style.contain()); computed_values.set_caret_color(computed_style.caret_color(*this)); + computed_values.set_color_interpolation(computed_style.color_interpolation()); propagate_style_to_anonymous_wrappers(); diff --git a/Libraries/LibWeb/Painting/DisplayListPlayerSkia.cpp b/Libraries/LibWeb/Painting/DisplayListPlayerSkia.cpp index c01a9c5f88c..0a28f7d6cfb 100644 --- a/Libraries/LibWeb/Painting/DisplayListPlayerSkia.cpp +++ b/Libraries/LibWeb/Painting/DisplayListPlayerSkia.cpp @@ -633,6 +633,9 @@ static SkPaint paint_style_to_skia_paint(Painting::SVGGradientPaintStyle const& shader = SkGradientShader::MakeTwoPointConical(start_center, start_radius, end_center, end_radius, colors.data(), positions.data(), color_stops.size(), tile_mode, 0, &matrix); } paint.setShader(shader); + if (auto* gradient_paint_style = as_if(paint_style); gradient_paint_style->color_space() == Gfx::InterpolationColorSpace::LinearRGB) { + paint.setColorFilter(SkColorFilters::LinearToSRGBGamma()); + } return paint; } diff --git a/Libraries/LibWeb/Painting/PaintStyle.h b/Libraries/LibWeb/Painting/PaintStyle.h index 0f94a16820f..3e1c96378e6 100644 --- a/Libraries/LibWeb/Painting/PaintStyle.h +++ b/Libraries/LibWeb/Painting/PaintStyle.h @@ -7,6 +7,7 @@ #pragma once #include +#include #include namespace Web::Painting { @@ -46,6 +47,9 @@ public: ReadonlySpan color_stops() const { return m_color_stops; } Optional repeat_length() const { return m_repeat_length; } + Gfx::InterpolationColorSpace color_space() const { return m_color_space; } + void set_color_space(Gfx::InterpolationColorSpace color_space) { m_color_space = color_space; } + virtual ~SVGGradientPaintStyle() { } protected: @@ -54,6 +58,7 @@ protected: Optional m_gradient_transform {}; SpreadMethod m_spread_method { SpreadMethod::Pad }; + Gfx::InterpolationColorSpace m_color_space { Gfx::InterpolationColorSpace::SRGB }; }; class SVGLinearGradientPaintStyle final : public SVGGradientPaintStyle { diff --git a/Libraries/LibWeb/SVG/SVGElement.cpp b/Libraries/LibWeb/SVG/SVGElement.cpp index f2e8a47c8d9..6de3b6bf811 100644 --- a/Libraries/LibWeb/SVG/SVGElement.cpp +++ b/Libraries/LibWeb/SVG/SVGElement.cpp @@ -54,6 +54,7 @@ static ReadonlySpan attribute_style_properties() NamedPropertyID(CSS::PropertyID::ClipPath), NamedPropertyID(CSS::PropertyID::ClipRule), NamedPropertyID(CSS::PropertyID::Color), + NamedPropertyID(CSS::PropertyID::ColorInterpolation), NamedPropertyID(CSS::PropertyID::Cursor), NamedPropertyID(CSS::PropertyID::Cx, { SVG::TagNames::circle, SVG::TagNames::ellipse }), NamedPropertyID(CSS::PropertyID::Cy, { SVG::TagNames::circle, SVG::TagNames::ellipse }), diff --git a/Libraries/LibWeb/SVG/SVGGradientElement.cpp b/Libraries/LibWeb/SVG/SVGGradientElement.cpp index 699f6b9ee38..a9007d3af94 100644 --- a/Libraries/LibWeb/SVG/SVGGradientElement.cpp +++ b/Libraries/LibWeb/SVG/SVGGradientElement.cpp @@ -66,6 +66,19 @@ SpreadMethod SVGGradientElement::spread_method_impl(HashTablecolor_interpolation()) { + case CSS::ColorInterpolation::Linearrgb: + return Gfx::InterpolationColorSpace::LinearRGB; + case CSS::ColorInterpolation::Auto: + case CSS::ColorInterpolation::Srgb: + return Gfx::InterpolationColorSpace::SRGB; + } + + VERIFY_NOT_REACHED(); +} + Optional SVGGradientElement::gradient_transform() const { HashTable seen_gradients; diff --git a/Libraries/LibWeb/SVG/SVGGradientElement.h b/Libraries/LibWeb/SVG/SVGGradientElement.h index 81d6f9f5b01..6d6b4b3ed3a 100644 --- a/Libraries/LibWeb/SVG/SVGGradientElement.h +++ b/Libraries/LibWeb/SVG/SVGGradientElement.h @@ -52,6 +52,8 @@ public: SpreadMethod spread_method() const; + Gfx::InterpolationColorSpace color_space() const; + Optional gradient_transform() const; protected: diff --git a/Libraries/LibWeb/SVG/SVGLinearGradientElement.cpp b/Libraries/LibWeb/SVG/SVGLinearGradientElement.cpp index 92845f22d8b..faa2e5c46c2 100644 --- a/Libraries/LibWeb/SVG/SVGLinearGradientElement.cpp +++ b/Libraries/LibWeb/SVG/SVGLinearGradientElement.cpp @@ -156,6 +156,7 @@ Optional SVGLinearGradientElement::to_gfx_paint_style(SVGP m_paint_style->set_gradient_transform(gradient_paint_transform(paint_context)); m_paint_style->set_spread_method(to_painting_spread_method(spread_method())); + m_paint_style->set_color_space(color_space()); return *m_paint_style; } diff --git a/Libraries/LibWeb/SVG/SVGRadialGradientElement.cpp b/Libraries/LibWeb/SVG/SVGRadialGradientElement.cpp index df55dd4ee62..412ee052ad3 100644 --- a/Libraries/LibWeb/SVG/SVGRadialGradientElement.cpp +++ b/Libraries/LibWeb/SVG/SVGRadialGradientElement.cpp @@ -212,6 +212,7 @@ Optional SVGRadialGradientElement::to_gfx_paint_style(SVGP } m_paint_style->set_gradient_transform(gradient_paint_transform(paint_context)); m_paint_style->set_spread_method(to_painting_spread_method(spread_method())); + m_paint_style->set_color_space(color_space()); return *m_paint_style; } diff --git a/Tests/LibWeb/Ref/expected/wpt-import/svg/pservers/reftests/reference/gradient-color-interpolation-ref.svg b/Tests/LibWeb/Ref/expected/wpt-import/svg/pservers/reftests/reference/gradient-color-interpolation-ref.svg new file mode 100644 index 00000000000..e7e475eeb16 --- /dev/null +++ b/Tests/LibWeb/Ref/expected/wpt-import/svg/pservers/reftests/reference/gradient-color-interpolation-ref.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + diff --git a/Tests/LibWeb/Ref/input/wpt-import/svg/pservers/reftests/gradient-color-interpolation.svg b/Tests/LibWeb/Ref/input/wpt-import/svg/pservers/reftests/gradient-color-interpolation.svg new file mode 100644 index 00000000000..49c77ce1f25 --- /dev/null +++ b/Tests/LibWeb/Ref/input/wpt-import/svg/pservers/reftests/gradient-color-interpolation.svg @@ -0,0 +1,22 @@ + + + Gradient with color-interpolation: linearRGB + + + + + + + + + + + + + + + + diff --git a/Tests/LibWeb/Text/expected/css/CSSStyleDeclaration-has-indexed-property-getter.txt b/Tests/LibWeb/Text/expected/css/CSSStyleDeclaration-has-indexed-property-getter.txt index 3b7a53d2457..e5758f60978 100644 --- a/Tests/LibWeb/Text/expected/css/CSSStyleDeclaration-has-indexed-property-getter.txt +++ b/Tests/LibWeb/Text/expected/css/CSSStyleDeclaration-has-indexed-property-getter.txt @@ -8,6 +8,7 @@ All properties associated with getComputedStyle(document.body): "caret-color", "clip-rule", "color", + "color-interpolation", "color-scheme", "cursor", "direction", diff --git a/Tests/LibWeb/Text/expected/css/CSSStyleProperties-all-supported-properties-and-default-values.txt b/Tests/LibWeb/Text/expected/css/CSSStyleProperties-all-supported-properties-and-default-values.txt index 61aae1df5d8..b31f0fad875 100644 --- a/Tests/LibWeb/Text/expected/css/CSSStyleProperties-all-supported-properties-and-default-values.txt +++ b/Tests/LibWeb/Text/expected/css/CSSStyleProperties-all-supported-properties-and-default-values.txt @@ -340,6 +340,8 @@ All supported properties and their default values exposed from CSSStylePropertie 'clipRule': 'nonzero' 'clip-rule': 'nonzero' 'color': 'rgb(0, 0, 0)' +'colorInterpolation': 'srgb' +'color-interpolation': 'srgb' 'colorScheme': 'normal' 'color-scheme': 'normal' 'columnCount': 'auto' diff --git a/Tests/LibWeb/Text/expected/css/getComputedStyle-print-all.txt b/Tests/LibWeb/Text/expected/css/getComputedStyle-print-all.txt index d2a8575308c..a599e7f9e7a 100644 --- a/Tests/LibWeb/Text/expected/css/getComputedStyle-print-all.txt +++ b/Tests/LibWeb/Text/expected/css/getComputedStyle-print-all.txt @@ -6,6 +6,7 @@ caption-side: top caret-color: rgb(0, 0, 0) clip-rule: nonzero color: rgb(0, 0, 0) +color-interpolation: srgb color-scheme: normal cursor: auto direction: ltr @@ -89,7 +90,7 @@ background-position-x: 0% background-position-y: 0% background-repeat: repeat background-size: auto -block-size: 1365px +block-size: 1380px border-block-end-color: rgb(0, 0, 0) border-block-end-style: none border-block-end-width: 0px @@ -165,7 +166,7 @@ grid-row-start: auto grid-template-areas: none grid-template-columns: none grid-template-rows: none -height: 2505px +height: 2520px inline-size: 784px inset-block-end: auto inset-block-start: auto diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-cascade/all-prop-revert-layer.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-cascade/all-prop-revert-layer.txt index db501826fb8..d8c9d530982 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/css-cascade/all-prop-revert-layer.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-cascade/all-prop-revert-layer.txt @@ -1,8 +1,8 @@ Harness status: OK -Found 254 tests +Found 255 tests -247 Pass +248 Pass 7 Fail Pass accent-color Pass border-collapse @@ -11,6 +11,7 @@ Pass caption-side Pass caret-color Pass clip-rule Pass color +Pass color-interpolation Pass color-scheme Pass cursor Pass direction diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/cssom/shorthand-values.txt b/Tests/LibWeb/Text/expected/wpt-import/css/cssom/shorthand-values.txt index 1f0465c77f7..0ccfa5c34a2 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/cssom/shorthand-values.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/cssom/shorthand-values.txt @@ -2,15 +2,15 @@ Harness status: OK Found 20 tests -15 Pass -5 Fail +14 Pass +6 Fail Pass The serialization of border: 1px; border-top: 1px; should be canonical. Pass The serialization of border: 1px solid red; should be canonical. Pass The serialization of border: 1px red; should be canonical. Pass The serialization of border: red; should be canonical. Fail The serialization of border-top: 1px; border-right: 1px; border-bottom: 1px; border-left: 1px; border-image: none; should be canonical. Fail The serialization of border-top: 1px; border-right: 1px; border-bottom: 1px; border-left: 1px; should be canonical. -Pass The serialization of border-top: 1px; border-right: 2px; border-bottom: 3px; border-left: 4px; should be canonical. +Fail The serialization of border-top: 1px; border-right: 2px; border-bottom: 3px; border-left: 4px; should be canonical. Fail The serialization of border: 1px; border-top: 2px; should be canonical. Fail The serialization of border: 1px; border-top: 1px !important; should be canonical. Fail The serialization of border: 1px; border-top-color: red; should be canonical. diff --git a/Tests/LibWeb/Text/expected/wpt-import/svg/painting/parsing/color-interpolation-invalid.txt b/Tests/LibWeb/Text/expected/wpt-import/svg/painting/parsing/color-interpolation-invalid.txt new file mode 100644 index 00000000000..719158a74b8 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/svg/painting/parsing/color-interpolation-invalid.txt @@ -0,0 +1,7 @@ +Harness status: OK + +Found 2 tests + +2 Pass +Pass e.style['color-interpolation'] = "none" should not set the property value +Pass e.style['color-interpolation'] = "auto srgb" should not set the property value \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/svg/painting/parsing/color-interpolation-valid.txt b/Tests/LibWeb/Text/expected/wpt-import/svg/painting/parsing/color-interpolation-valid.txt new file mode 100644 index 00000000000..4dd3665d5ed --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/svg/painting/parsing/color-interpolation-valid.txt @@ -0,0 +1,8 @@ +Harness status: OK + +Found 3 tests + +3 Pass +Pass e.style['color-interpolation'] = "auto" should set the property value +Pass e.style['color-interpolation'] = "srgb" should set the property value +Pass e.style['color-interpolation'] = "linearrgb" should set the property value \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/svg/styling/presentation-attributes-irrelevant.txt b/Tests/LibWeb/Text/expected/wpt-import/svg/styling/presentation-attributes-irrelevant.txt index 7dc65880b26..c88e2d4a92d 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/svg/styling/presentation-attributes-irrelevant.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/svg/styling/presentation-attributes-irrelevant.txt @@ -1,12 +1,13 @@ Harness status: OK -Found 45 tests +Found 46 tests -42 Pass +43 Pass 3 Fail Pass clip-path presentation attribute supported on an irrelevant element Pass clip-rule presentation attribute supported on an irrelevant element Pass color presentation attribute supported on an irrelevant element +Pass color-interpolation presentation attribute supported on an irrelevant element Pass cursor presentation attribute supported on an irrelevant element Pass direction presentation attribute supported on an irrelevant element Pass display presentation attribute supported on an irrelevant element diff --git a/Tests/LibWeb/Text/expected/wpt-import/svg/styling/presentation-attributes-relevant.txt b/Tests/LibWeb/Text/expected/wpt-import/svg/styling/presentation-attributes-relevant.txt index fddc6f55837..f677f3653df 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/svg/styling/presentation-attributes-relevant.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/svg/styling/presentation-attributes-relevant.txt @@ -1,12 +1,13 @@ Harness status: OK -Found 55 tests +Found 56 tests -51 Pass +52 Pass 4 Fail Pass clip-path presentation attribute supported on a relevant element Pass clip-rule presentation attribute supported on a relevant element Pass color presentation attribute supported on a relevant element +Pass color-interpolation presentation attribute supported on a relevant element Pass cursor presentation attribute supported on a relevant element Pass cx presentation attribute supported on a relevant element Pass cy presentation attribute supported on a relevant element diff --git a/Tests/LibWeb/Text/expected/wpt-import/svg/styling/presentation-attributes-unknown.txt b/Tests/LibWeb/Text/expected/wpt-import/svg/styling/presentation-attributes-unknown.txt index 69d33a73197..e85f90447a6 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/svg/styling/presentation-attributes-unknown.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/svg/styling/presentation-attributes-unknown.txt @@ -1,12 +1,13 @@ Harness status: OK -Found 45 tests +Found 46 tests -42 Pass +43 Pass 3 Fail Pass clip-path presentation attribute supported on an unknown SVG element Pass clip-rule presentation attribute supported on an unknown SVG element Pass color presentation attribute supported on an unknown SVG element +Pass color-interpolation presentation attribute supported on an unknown SVG element Pass cursor presentation attribute supported on an unknown SVG element Pass direction presentation attribute supported on an unknown SVG element Pass display presentation attribute supported on an unknown SVG element diff --git a/Tests/LibWeb/Text/input/wpt-import/svg/painting/parsing/color-interpolation-invalid.svg b/Tests/LibWeb/Text/input/wpt-import/svg/painting/parsing/color-interpolation-invalid.svg new file mode 100644 index 00000000000..919e0586369 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/svg/painting/parsing/color-interpolation-invalid.svg @@ -0,0 +1,20 @@ + + + SVG Painting: parsing color-interpolation with invalid values + + + + + + + + + + diff --git a/Tests/LibWeb/Text/input/wpt-import/svg/painting/parsing/color-interpolation-valid.svg b/Tests/LibWeb/Text/input/wpt-import/svg/painting/parsing/color-interpolation-valid.svg new file mode 100644 index 00000000000..ea323f9330c --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/svg/painting/parsing/color-interpolation-valid.svg @@ -0,0 +1,21 @@ + + + SVG Painting: parsing color-interpolation with valid values + + + + + + + + + +