Skip to content

Commit

Permalink
When interpolating colors, analogous components must be carried forward
Browse files Browse the repository at this point in the history
https://bugs.webkit.org/show_bug.cgi?id=273033

Reviewed by Tim Nguyen.

Adds support for missing analogous component forwarding as described by
CSS Color 4 § 12. Color Interpolating. This allows missing components
to survive color space conversion in cases where the input and output
color spaces have analogous components. For example, when converting
`lch(10 20 none)` to HSL, the missing hue in the LCH would be carried
forward to the hue of the HSL.

* LayoutTests/imported/w3c/web-platform-tests/css/css-color/parsing/color-computed-color-mix-function-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/css/css-color/parsing/color-computed-relative-color-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/css/css-color/parsing/color-valid-relative-color-expected.txt:
    - Update results for newly passing tests.

* Source/WebCore/platform/graphics/ColorModels.h:
    - Add analogous component values to the ColorModel component descriptions.

* Source/WebCore/platform/graphics/ColorConversion.h:
(WebCore::analogousComponentIndex):
(WebCore::tryToCarryForwardComponentIfMissing):
(WebCore::convertColorCarryingForwardMissing):
    - Implement support for the carrying using the analogous component
      descriptions now in ColorModels.

(WebCore::computeDeltaEOK):
(WebCore::ColorConversion::handleToFloatConversion):
(WebCore::ColorConversion::handleToByteConversion):
(WebCore::ColorConversion::toLinearEncoded):
(WebCore::ColorConversion::toGammaEncoded):
(WebCore::ColorConversion::toExtended):
(WebCore::ColorConversion::toBounded):
(WebCore::ColorConversion::handleRGBFamilyConversion):
(WebCore::ColorConversion::handleMatrixConversion):
    - Remove some unnecessary "inline" annotations for template functions.

* Source/WebCore/platform/graphics/Color.h:
(WebCore::Color::toColorTypeLossyCarryingForwardMissing const):
    - Add new helper on Color to invoke the new conversion function.

* Source/WebCore/css/parser/CSSPropertyParserConsumer+Color.cpp:
(WebCore::CSSPropertyParserHelpers::consumeAndNormalizeRelativeComponents):
    - Utilize the new conversion function.

* Source/WebCore/css/color/CSSResolvedColorMix.cpp:
(WebCore::mixColorComponentsUsingColorInterpolationMethod):
    - Utilize the new conversion function.

* Source/WebCore/platform/graphics/ColorBlending.cpp:
(WebCore::blend):
    - Utilize the new conversion function.

* Source/WebCore/platform/graphics/ColorInterpolation.cpp:
(WebCore::interpolateColors):
    - Utilize the new conversion function.

* Source/WebCore/platform/graphics/ColorSerialization.cpp:
(WebCore::serializationForCSS):
    - Utilize the new conversion function.

* Source/WebCore/platform/graphics/cg/GradientRendererCG.cpp:
(WebCore::GradientRendererCG::makeShading const):
    - Utilize the new conversion function.

Canonical link: https://commits.webkit.org/278379@main
  • Loading branch information
weinig authored and Sam Weinig committed May 4, 2024
1 parent 7754d9b commit 4223b7f
Show file tree
Hide file tree
Showing 12 changed files with 198 additions and 117 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,7 @@ PASS Property color value 'color-mix(in hsl, hsl(120deg 20% 40%), hsl(30deg 20%
PASS Property color value 'color-mix(in hsl, hsl(none 20% 40%), hsl(30deg none 80%))'
PASS Property color value 'color-mix(in hsl, hsl(120deg 40% 40% / none), hsl(0deg 40% 40%))'
PASS Property color value 'color-mix(in hsl, hsl(120deg 40% 40% / none), hsl(0deg 40% 40% / 0.5))'
FAIL Property color value 'color-mix(in hsl, hsl(120deg 40% 40% / none), hsl(0deg 40% 40% / none))' Colors do not match.
Actual: color(srgb 0.56 0.56 0.24 / 0)
Expected: color(srgb 0.56 0.56 0.24 / none).
Error: assert_array_approx_equals: Numeric parameters are approximately equal. lengths differ, expected 3 got 4
PASS Property color value 'color-mix(in hsl, hsl(120deg 40% 40% / none), hsl(0deg 40% 40% / none))'
PASS Property color value 'color-mix(in hsl, hsl(30deg 40% 80% / 25%) 0%, hsl(90deg none none / none))'
PASS Property color value 'color-mix(in hsl, hsl(30deg 40% 80% / 25%) 0%, hsl(none 50% none / none))'
PASS Property color value 'color-mix(in hsl, hsl(30deg 40% 80% / 25%) 0%, hsl(none none 50% / none))'
Expand Down Expand Up @@ -137,10 +134,7 @@ PASS Property color value 'color-mix(in hwb, hwb(120deg 10% 20%), hwb(30deg 30%
PASS Property color value 'color-mix(in hwb, hwb(none 10% 20%), hwb(30deg none 40%))'
PASS Property color value 'color-mix(in hwb, hwb(120deg 10% 20% / none), hwb(30deg 30% 40%))'
PASS Property color value 'color-mix(in hwb, hwb(120deg 10% 20% / none), hwb(30deg 30% 40% / 0.5))'
FAIL Property color value 'color-mix(in hwb, hwb(120deg 10% 20% / none), hwb(30deg 30% 40% / none))' Colors do not match.
Actual: color(srgb 0.575 0.7 0.2 / 0)
Expected: color(srgb 0.575 0.7 0.2 / none).
Error: assert_array_approx_equals: Numeric parameters are approximately equal. lengths differ, expected 3 got 4
PASS Property color value 'color-mix(in hwb, hwb(120deg 10% 20% / none), hwb(30deg 30% 40% / none))'
PASS Property color value 'color-mix(in hwb, hwb(30deg 30% 40% / 25%) 0%, hwb(90deg none none / none))'
PASS Property color value 'color-mix(in hwb, hwb(30deg 30% 40% / 25%) 0%, hwb(none 50% none / none))'
PASS Property color value 'color-mix(in hwb, hwb(30deg 30% 40% / 25%) 0%, hwb(none none 50% / none))'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,22 +132,13 @@ PASS Property color value 'hsl(from rebeccapurple calc((h / 360) * 360deg) calc(
PASS Property color value 'hsl(from hsl(from rebeccapurple h s l / calc(alpha + 0.5)) h s l / calc(alpha - 0.5))'
PASS Property color value 'hsl(from hsl(from rebeccapurple h s l / calc(alpha - 1.5)) h s l / calc(alpha + 0.5))'
PASS Property color value 'hsl(from rebeccapurple none none none)'
FAIL Property color value 'hsl(from rebeccapurple none none none / none)' Colors do not match.
Actual: color(srgb 0 0 0 / 0)
Expected: color(srgb 0 0 0 / none).
Error: assert_array_approx_equals: Numeric parameters are approximately equal. lengths differ, expected 3 got 4
PASS Property color value 'hsl(from rebeccapurple none none none / none)'
PASS Property color value 'hsl(from rebeccapurple h s none)'
PASS Property color value 'hsl(from rebeccapurple h s none / alpha)'
FAIL Property color value 'hsl(from rebeccapurple h s l / none)' Colors do not match.
Actual: color(srgb 0.4 0.2 0.6 / 0)
Expected: color(srgb 0.4 0.2 0.6 / none).
Error: assert_array_approx_equals: Numeric parameters are approximately equal. lengths differ, expected 3 got 4
PASS Property color value 'hsl(from rebeccapurple h s l / none)'
PASS Property color value 'hsl(from rebeccapurple none s l / alpha)'
PASS Property color value 'hsl(from hsl(120deg 20% 50% / .5) h s none / alpha)'
FAIL Property color value 'hsl(from hsl(120deg 20% 50% / .5) h s l / none)' Colors do not match.
Actual: color(srgb 0.4 0.6 0.4 / 0)
Expected: color(srgb 0.4 0.6 0.4 / none).
Error: assert_array_approx_equals: Numeric parameters are approximately equal. lengths differ, expected 3 got 4
PASS Property color value 'hsl(from hsl(120deg 20% 50% / .5) h s l / none)'
PASS Property color value 'hsl(from hsl(120deg 20% 50% / .5) none s l / alpha)'
PASS Property color value 'hsl(from hsl(none none none) h s l)'
PASS Property color value 'hsl(from hsl(none none none / none) h s l / alpha)'
Expand Down Expand Up @@ -200,22 +191,13 @@ PASS Property color value 'hwb(from rebeccapurple calc((h / 360) * 360deg) calc(
PASS Property color value 'hwb(from hwb(from rebeccapurple h w b / calc(alpha + 0.5)) h w b / calc(alpha - 0.5))'
PASS Property color value 'hwb(from hwb(from rebeccapurple h w b / calc(alpha - 1.5)) h w b / calc(alpha + 0.5))'
PASS Property color value 'hwb(from rebeccapurple none none none)'
FAIL Property color value 'hwb(from rebeccapurple none none none / none)' Colors do not match.
Actual: color(srgb 1 0 0 / 0)
Expected: color(srgb 1 0 0 / none).
Error: assert_array_approx_equals: Numeric parameters are approximately equal. lengths differ, expected 3 got 4
PASS Property color value 'hwb(from rebeccapurple none none none / none)'
PASS Property color value 'hwb(from rebeccapurple h w none)'
PASS Property color value 'hwb(from rebeccapurple h w none / alpha)'
FAIL Property color value 'hwb(from rebeccapurple h w b / none)' Colors do not match.
Actual: color(srgb 0.4 0.2 0.6 / 0)
Expected: color(srgb 0.4 0.2 0.6 / none).
Error: assert_array_approx_equals: Numeric parameters are approximately equal. lengths differ, expected 3 got 4
PASS Property color value 'hwb(from rebeccapurple h w b / none)'
PASS Property color value 'hwb(from rebeccapurple none w b / alpha)'
PASS Property color value 'hwb(from hwb(120deg 20% 50% / .5) h w none / alpha)'
FAIL Property color value 'hwb(from hwb(120deg 20% 50% / .5) h w b / none)' Colors do not match.
Actual: color(srgb 0.2 0.5 0.2 / 0)
Expected: color(srgb 0.2 0.5 0.2 / none).
Error: assert_array_approx_equals: Numeric parameters are approximately equal. lengths differ, expected 3 got 4
PASS Property color value 'hwb(from hwb(120deg 20% 50% / .5) h w b / none)'
PASS Property color value 'hwb(from hwb(120deg 20% 50% / .5) none w b / alpha)'
PASS Property color value 'hwb(from hwb(none none none) h w b)'
PASS Property color value 'hwb(from hwb(none none none / none) h w b / alpha)'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,19 +179,16 @@ PASS e.style['color'] = "hsl(from rgb(20%, 40%, 60%, 80%) h alpha alpha / alpha)
PASS e.style['color'] = "hsl(from rebeccapurple calc(h) calc(s) calc(l))" should set the property value
PASS e.style['color'] = "hsl(from rgb(20%, 40%, 60%, 80%) calc(h) calc(s) calc(l) / calc(alpha))" should set the property value
PASS e.style['color'] = "hsl(from rebeccapurple none none none)" should set the property value
PASS e.style['color'] = "hsl(from rebeccapurple none none none / none)" should set the property value
FAIL e.style['color'] = "hsl(from rebeccapurple none none none / none)" should set the property value Colors do not match.
Actual: color(srgb 0 0 0 / none)
Expected: color(srgb 0 0 0 / 0).
Error: assert_array_approx_equals: Numeric parameters are approximately equal. lengths differ, expected 4 got 3
PASS e.style['color'] = "hsl(from rebeccapurple h s none)" should set the property value
PASS e.style['color'] = "hsl(from rebeccapurple h s none / alpha)" should set the property value
FAIL e.style['color'] = "hsl(from rebeccapurple h s l / none)" should set the property value Colors do not match.
Actual: color(srgb 0.4 0.2 0.6 / 0)
Expected: color(srgb 0.4 0.2 0.6 / none).
Error: assert_array_approx_equals: Numeric parameters are approximately equal. lengths differ, expected 3 got 4
PASS e.style['color'] = "hsl(from rebeccapurple h s l / none)" should set the property value
PASS e.style['color'] = "hsl(from rebeccapurple none s l / alpha)" should set the property value
PASS e.style['color'] = "hsl(from hsl(120deg 20% 50% / .5) h s none / alpha)" should set the property value
FAIL e.style['color'] = "hsl(from hsl(120deg 20% 50% / .5) h s l / none)" should set the property value Colors do not match.
Actual: color(srgb 0.4 0.6 0.4 / 0)
Expected: color(srgb 0.4 0.6 0.4 / none).
Error: assert_array_approx_equals: Numeric parameters are approximately equal. lengths differ, expected 3 got 4
PASS e.style['color'] = "hsl(from hsl(120deg 20% 50% / .5) h s l / none)" should set the property value
PASS e.style['color'] = "hsl(from hsl(120deg 20% 50% / .5) none s l / alpha)" should set the property value
PASS e.style['color'] = "hsl(from hsl(none none none) h s l)" should set the property value
PASS e.style['color'] = "hsl(from hsl(none none none / none) h s l / alpha)" should set the property value
Expand Down Expand Up @@ -240,19 +237,16 @@ PASS e.style['color'] = "hsla(from rgb(20%, 40%, 60%, 80%) h alpha alpha / alpha
PASS e.style['color'] = "hsla(from rebeccapurple calc(h) calc(s) calc(l))" should set the property value
PASS e.style['color'] = "hsla(from rgb(20%, 40%, 60%, 80%) calc(h) calc(s) calc(l) / calc(alpha))" should set the property value
PASS e.style['color'] = "hsla(from rebeccapurple none none none)" should set the property value
PASS e.style['color'] = "hsla(from rebeccapurple none none none / none)" should set the property value
FAIL e.style['color'] = "hsla(from rebeccapurple none none none / none)" should set the property value Colors do not match.
Actual: color(srgb 0 0 0 / none)
Expected: color(srgb 0 0 0 / 0).
Error: assert_array_approx_equals: Numeric parameters are approximately equal. lengths differ, expected 4 got 3
PASS e.style['color'] = "hsla(from rebeccapurple h s none)" should set the property value
PASS e.style['color'] = "hsla(from rebeccapurple h s none / alpha)" should set the property value
FAIL e.style['color'] = "hsla(from rebeccapurple h s l / none)" should set the property value Colors do not match.
Actual: color(srgb 0.4 0.2 0.6 / 0)
Expected: color(srgb 0.4 0.2 0.6 / none).
Error: assert_array_approx_equals: Numeric parameters are approximately equal. lengths differ, expected 3 got 4
PASS e.style['color'] = "hsla(from rebeccapurple h s l / none)" should set the property value
PASS e.style['color'] = "hsla(from rebeccapurple none s l / alpha)" should set the property value
PASS e.style['color'] = "hsla(from hsl(120deg 20% 50% / .5) h s none / alpha)" should set the property value
FAIL e.style['color'] = "hsla(from hsl(120deg 20% 50% / .5) h s l / none)" should set the property value Colors do not match.
Actual: color(srgb 0.4 0.6 0.4 / 0)
Expected: color(srgb 0.4 0.6 0.4 / none).
Error: assert_array_approx_equals: Numeric parameters are approximately equal. lengths differ, expected 3 got 4
PASS e.style['color'] = "hsla(from hsl(120deg 20% 50% / .5) h s l / none)" should set the property value
PASS e.style['color'] = "hsla(from hsl(120deg 20% 50% / .5) none s l / alpha)" should set the property value
PASS e.style['color'] = "hsla(from hsl(none none none) h s l)" should set the property value
PASS e.style['color'] = "hsla(from hsl(none none none / none) h s l / alpha)" should set the property value
Expand Down Expand Up @@ -301,22 +295,13 @@ PASS e.style['color'] = "hwb(from rgb(20%, 40%, 60%, 80%) h alpha alpha / alpha)
PASS e.style['color'] = "hwb(from rebeccapurple calc(h) calc(w) calc(b))" should set the property value
PASS e.style['color'] = "hwb(from rgb(20%, 40%, 60%, 80%) calc(h) calc(w) calc(b) / calc(alpha))" should set the property value
PASS e.style['color'] = "hwb(from rebeccapurple none none none)" should set the property value
FAIL e.style['color'] = "hwb(from rebeccapurple none none none / none)" should set the property value Colors do not match.
Actual: color(srgb 1 0 0 / 0)
Expected: color(srgb 1 0 0 / none).
Error: assert_array_approx_equals: Numeric parameters are approximately equal. lengths differ, expected 3 got 4
PASS e.style['color'] = "hwb(from rebeccapurple none none none / none)" should set the property value
PASS e.style['color'] = "hwb(from rebeccapurple h w none)" should set the property value
PASS e.style['color'] = "hwb(from rebeccapurple h w none / alpha)" should set the property value
FAIL e.style['color'] = "hwb(from rebeccapurple h w b / none)" should set the property value Colors do not match.
Actual: color(srgb 0.4 0.2 0.6 / 0)
Expected: color(srgb 0.4 0.2 0.6 / none).
Error: assert_array_approx_equals: Numeric parameters are approximately equal. lengths differ, expected 3 got 4
PASS e.style['color'] = "hwb(from rebeccapurple h w b / none)" should set the property value
PASS e.style['color'] = "hwb(from rebeccapurple none w b / alpha)" should set the property value
PASS e.style['color'] = "hwb(from hwb(120deg 20% 50% / .5) h w none / alpha)" should set the property value
FAIL e.style['color'] = "hwb(from hwb(120deg 20% 50% / .5) h w b / none)" should set the property value Colors do not match.
Actual: color(srgb 0.2 0.5 0.2 / 0)
Expected: color(srgb 0.2 0.5 0.2 / none).
Error: assert_array_approx_equals: Numeric parameters are approximately equal. lengths differ, expected 3 got 4
PASS e.style['color'] = "hwb(from hwb(120deg 20% 50% / .5) h w b / none)" should set the property value
PASS e.style['color'] = "hwb(from hwb(120deg 20% 50% / .5) none w b / alpha)" should set the property value
PASS e.style['color'] = "hwb(from hwb(none none none) h w b)" should set the property value
PASS e.style['color'] = "hwb(from hwb(none none none / none) h w b / alpha)" should set the property value
Expand Down
4 changes: 2 additions & 2 deletions Source/WebCore/css/color/CSSResolvedColorMix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,8 @@ template<typename InterpolationMethod> static Color mixColorComponentsUsingColor

// 1. Both colors are converted to the specified <color-space>. If the specified color space has a smaller gamut than
// the one in which the color to be adjusted is specified, gamut mapping will occur.
auto convertedColor1 = color1.template toColorTypeLossy<ColorType>();
auto convertedColor2 = color2.template toColorTypeLossy<ColorType>();
auto convertedColor1 = color1.template toColorTypeLossyCarryingForwardMissing<ColorType>();
auto convertedColor2 = color2.template toColorTypeLossyCarryingForwardMissing<ColorType>();

// 2. Colors are then interpolated in the specified color space, as described in CSS Color 4 § 13 Interpolation. [...]
auto mixedColor = interpolateColorComponents<AlphaPremultiplication::Premultiplied>(interpolationMethod, convertedColor1, mixPercentages.p1 / 100.0, convertedColor2, mixPercentages.p2 / 100.0).unresolved();
Expand Down
12 changes: 11 additions & 1 deletion Source/WebCore/css/parser/CSSPropertyParserConsumer+Color.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,17 @@ std::optional<GetColorType<Descriptor>> consumeAndNormalizeAbsoluteComponents(CS
template<typename Descriptor>
std::optional<GetColorType<Descriptor>> consumeAndNormalizeRelativeComponents(CSSParserTokenRange& args, ColorParserState& state, Color originColor)
{
auto originColorAsColorType = originColor.toColorTypeLossy<GetColorType<Descriptor>>();
// Missing components are carried forward for this conversion as specified in
// CSS Color 5 § 4.1 Processing Model for Relative Colors:
//
// "Missing components are handled the same way as with CSS Color 4 § 12.2
// Interpolating with Missing Components: the origin colorspace and the
// relative function colorspace are checked for analogous components which
// are then carried forward as missing."
//
// (https://drafts.csswg.org/css-color-5/#rcs-intro)

auto originColorAsColorType = originColor.toColorTypeLossyCarryingForwardMissing<GetColorType<Descriptor>>();

auto parsed = consumeRelativeComponents<Descriptor>(args, state, originColorAsColorType);
if (!parsed)
Expand Down
11 changes: 11 additions & 0 deletions Source/WebCore/platform/graphics/Color.h
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,10 @@ class Color {
// or precision of ColorType is smaller than the current underlying type.
template<typename ColorType> ColorType toColorTypeLossy() const;

// This acts just like toColorTypeLossy(), but will carry forward missing components
// from the underlying type into any analogous components in ColorType.
template<typename ColorType> ColorType toColorTypeLossyCarryingForwardMissing() const;

ColorComponents<float, 4> toResolvedColorComponentsInColorSpace(ColorSpace) const;
ColorComponents<float, 4> toResolvedColorComponentsInColorSpace(const DestinationColorSpace&) const;

Expand Down Expand Up @@ -428,6 +432,13 @@ template<typename ColorType> ColorType Color::toColorTypeLossy() const
});
}

template<typename ColorType> ColorType Color::toColorTypeLossyCarryingForwardMissing() const
{
return callOnUnderlyingType([] (const auto& underlyingColor) {
return convertColorCarryingForwardMissing<ColorType>(underlyingColor);
});
}

inline Color Color::invertedColorWithAlpha(std::optional<float> alpha) const
{
return alpha ? invertedColorWithAlpha(alpha.value()) : *this;
Expand Down
8 changes: 4 additions & 4 deletions Source/WebCore/platform/graphics/ColorBlending.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,8 @@ Color blend(const Color& from, const Color& to, const BlendingContext& context)
if (requiresLegacyInterpolationRules(from) && requiresLegacyInterpolationRules(to)) {
using InterpolationColorSpace = ColorInterpolationMethod::SRGB;

auto fromComponents = from.toColorTypeLossy<typename InterpolationColorSpace::ColorType>();
auto toComponents = to.toColorTypeLossy<typename InterpolationColorSpace::ColorType>();
auto fromComponents = from.toColorTypeLossyCarryingForwardMissing<typename InterpolationColorSpace::ColorType>();
auto toComponents = to.toColorTypeLossyCarryingForwardMissing<typename InterpolationColorSpace::ColorType>();

switch (context.compositeOperation) {
case CompositeOperation::Replace: {
Expand All @@ -140,8 +140,8 @@ Color blend(const Color& from, const Color& to, const BlendingContext& context)
} else {
using InterpolationColorSpace = ColorInterpolationMethod::OKLab;

auto fromComponents = from.toColorTypeLossy<typename InterpolationColorSpace::ColorType>();
auto toComponents = to.toColorTypeLossy<typename InterpolationColorSpace::ColorType>();
auto fromComponents = from.toColorTypeLossyCarryingForwardMissing<typename InterpolationColorSpace::ColorType>();
auto toComponents = to.toColorTypeLossyCarryingForwardMissing<typename InterpolationColorSpace::ColorType>();

switch (context.compositeOperation) {
case CompositeOperation::Replace:
Expand Down
Loading

0 comments on commit 4223b7f

Please sign in to comment.