mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-09-03 08:08:43 +00:00
LibWeb: Implement the color-interpolation
property for SVG gradients
This changes the operating color space for gradient `<linearGradient>` and `<radialGradient>` elements.
This commit is contained in:
parent
040ccc3b42
commit
ad06ac0d58
Notes:
github-actions[bot]
2025-08-17 08:52:13 +00:00
Author: https://github.com/tcl3
Commit: ad06ac0d58
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/5869
Reviewed-by: https://github.com/AtkinsSJ
Reviewed-by: https://github.com/gmta ✅
29 changed files with 192 additions and 13 deletions
16
Libraries/LibGfx/InterpolationColorSpace.h
Normal file
16
Libraries/LibGfx/InterpolationColorSpace.h
Normal file
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Tim Ledbetter <tim.ledbetter@ladybird.org>.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace Gfx {
|
||||
|
||||
enum class InterpolationColorSpace {
|
||||
LinearRGB,
|
||||
SRGB,
|
||||
};
|
||||
|
||||
}
|
|
@ -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<Vector<String> const&> document_supported_schemes) const
|
||||
{
|
||||
|
|
|
@ -78,6 +78,7 @@ public:
|
|||
Optional<LengthPercentage> 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<Vector<String> const&> document_supported_schemes) const;
|
||||
TextAnchor text_anchor() const;
|
||||
TextAlign text_align() const;
|
||||
|
|
|
@ -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<CursorData> 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<Color> 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; }
|
||||
|
|
|
@ -132,6 +132,11 @@
|
|||
"inline-start",
|
||||
"inline-end"
|
||||
],
|
||||
"color-interpolation": [
|
||||
"auto",
|
||||
"linearrgb",
|
||||
"srgb"
|
||||
],
|
||||
"column-span": [
|
||||
"none",
|
||||
"all"
|
||||
|
|
|
@ -301,6 +301,7 @@
|
|||
"lighter",
|
||||
"line-through",
|
||||
"linear",
|
||||
"linearrgb",
|
||||
"lining-nums",
|
||||
"linktext",
|
||||
"list-item",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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<SVGGradientPaintStyle>(paint_style); gradient_paint_style->color_space() == Gfx::InterpolationColorSpace::LinearRGB) {
|
||||
paint.setColorFilter(SkColorFilters::LinearToSRGBGamma());
|
||||
}
|
||||
|
||||
return paint;
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <AK/AtomicRefCounted.h>
|
||||
#include <LibGfx/InterpolationColorSpace.h>
|
||||
#include <LibGfx/PaintStyle.h>
|
||||
|
||||
namespace Web::Painting {
|
||||
|
@ -46,6 +47,9 @@ public:
|
|||
ReadonlySpan<ColorStop> color_stops() const { return m_color_stops; }
|
||||
Optional<float> 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<Gfx::AffineTransform> m_gradient_transform {};
|
||||
SpreadMethod m_spread_method { SpreadMethod::Pad };
|
||||
Gfx::InterpolationColorSpace m_color_space { Gfx::InterpolationColorSpace::SRGB };
|
||||
};
|
||||
|
||||
class SVGLinearGradientPaintStyle final : public SVGGradientPaintStyle {
|
||||
|
|
|
@ -54,6 +54,7 @@ static ReadonlySpan<NamedPropertyID> 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 }),
|
||||
|
|
|
@ -66,6 +66,19 @@ SpreadMethod SVGGradientElement::spread_method_impl(HashTable<SVGGradientElement
|
|||
return SpreadMethod::Pad;
|
||||
}
|
||||
|
||||
Gfx::InterpolationColorSpace SVGGradientElement::color_space() const
|
||||
{
|
||||
switch (computed_properties()->color_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<Gfx::AffineTransform> SVGGradientElement::gradient_transform() const
|
||||
{
|
||||
HashTable<SVGGradientElement const*> seen_gradients;
|
||||
|
|
|
@ -52,6 +52,8 @@ public:
|
|||
|
||||
SpreadMethod spread_method() const;
|
||||
|
||||
Gfx::InterpolationColorSpace color_space() const;
|
||||
|
||||
Optional<Gfx::AffineTransform> gradient_transform() const;
|
||||
|
||||
protected:
|
||||
|
|
|
@ -156,6 +156,7 @@ Optional<Painting::PaintStyle> 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;
|
||||
}
|
||||
|
||||
|
|
|
@ -212,6 +212,7 @@ Optional<Painting::PaintStyle> 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;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
<svg width="100%" height="100%"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:html="http://www.w3.org/1999/xhtml">
|
||||
|
||||
<style>
|
||||
div {
|
||||
border: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
};
|
||||
</style>
|
||||
<defs>
|
||||
<linearGradient id="gradientLinearRGB" gradientUnits="objectBoundingBox" color-interpolation="linearRGB">
|
||||
<stop offset="0" stop-color="white"/>
|
||||
<stop offset=".33" stop-color="blue"/>
|
||||
<stop offset=".66" stop-color="red"/>
|
||||
<stop offset="1" stop-color="yellow"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<foreignObject x="20" y="20" width="200" height="200">
|
||||
<html:div style="height:100%;width:100%;background: linear-gradient(90deg in srgb-linear, white 0%, blue 33%, red 66%, yellow 100%);"/>
|
||||
</foreignObject>
|
||||
</svg>
|
After Width: | Height: | Size: 761 B |
|
@ -0,0 +1,22 @@
|
|||
<svg width="100%" height="100%"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:html="http://www.w3.org/1999/xhtml">
|
||||
<g id="testmeta">
|
||||
<title>Gradient with color-interpolation: linearRGB</title>
|
||||
<html:link rel="help"
|
||||
href="https://www.w3.org/TR/SVG2/pservers.html#LinearGradients"/>
|
||||
<html:link rel="match" href="../../../../../expected/wpt-import/svg/pservers/reftests/reference/gradient-color-interpolation-ref.svg" />
|
||||
<html:meta name="fuzzy" content="maxDifference=0-20;totalPixels=0-29400" />
|
||||
</g>
|
||||
|
||||
<defs>
|
||||
<linearGradient id="gradientLinearRGB" gradientUnits="objectBoundingBox" color-interpolation="linearRGB">
|
||||
<stop offset="0" stop-color="white"/>
|
||||
<stop offset=".33" stop-color="blue"/>
|
||||
<stop offset=".66" stop-color="red"/>
|
||||
<stop offset="1" stop-color="yellow"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<rect x="20" y="20" width="200" height="200" style="fill:url(#gradientLinearRGB)" />
|
||||
</svg>
|
After Width: | Height: | Size: 953 B |
|
@ -8,6 +8,7 @@ All properties associated with getComputedStyle(document.body):
|
|||
"caret-color",
|
||||
"clip-rule",
|
||||
"color",
|
||||
"color-interpolation",
|
||||
"color-scheme",
|
||||
"cursor",
|
||||
"direction",
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:h="http://www.w3.org/1999/xhtml"
|
||||
width="800px" height="800px">
|
||||
<title>SVG Painting: parsing color-interpolation with invalid values</title>
|
||||
<metadata>
|
||||
<h:link rel="help" href="https://svgwg.org/svg2-draft/painting.html#ColorInterpolationProperty"/>
|
||||
<h:meta name="assert" content="color-interpolation supports only the grammar 'auto | sRGB | linearRGB'."/>
|
||||
</metadata>
|
||||
<g id="target"></g>
|
||||
<h:script src="../../../resources/testharness.js"/>
|
||||
<h:script src="../../../resources/testharnessreport.js"/>
|
||||
<h:script src="../../../css/support/parsing-testcommon.js"/>
|
||||
<script><![CDATA[
|
||||
|
||||
test_invalid_value("color-interpolation", "none");
|
||||
test_invalid_value("color-interpolation", "auto srgb");
|
||||
|
||||
]]></script>
|
||||
</svg>
|
After Width: | Height: | Size: 827 B |
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:h="http://www.w3.org/1999/xhtml"
|
||||
width="800px" height="800px">
|
||||
<title>SVG Painting: parsing color-interpolation with valid values</title>
|
||||
<metadata>
|
||||
<h:link rel="help" href="https://svgwg.org/svg2-draft/painting.html#ColorInterpolationProperty"/>
|
||||
<h:meta name="assert" content="color-interpolation supports the full grammar 'auto | sRGB | linearRGB'."/>
|
||||
</metadata>
|
||||
<g id="target"></g>
|
||||
<h:script src="../../../resources/testharness.js"/>
|
||||
<h:script src="../../../resources/testharnessreport.js"/>
|
||||
<h:script src="../../../css/support/parsing-testcommon.js"/>
|
||||
<script><![CDATA[
|
||||
|
||||
test_valid_value("color-interpolation", "auto");
|
||||
test_valid_value("color-interpolation", "srgb");
|
||||
test_valid_value("color-interpolation", "linearrgb");
|
||||
|
||||
]]></script>
|
||||
</svg>
|
After Width: | Height: | Size: 870 B |
Loading…
Add table
Add a link
Reference in a new issue