Title: [289122] trunk
Revision
289122
Author
wei...@apple.com
Date
2022-02-04 09:26:30 -0800 (Fri, 04 Feb 2022)

Log Message

Gradients don't correctly interpolate missing/none color components correctly
https://bugs.webkit.org/show_bug.cgi?id=236025

Reviewed by Simon Fraser.

Source/WebCore:

Test: fast/gradients/gradient-with-missing-components.html

* platform/graphics/Color.cpp:
(WebCore::Color::anyComponentIsNone const):
* platform/graphics/Color.h:
Add helper to check if any component is 'none'.

* platform/graphics/ColorComponents.h:
(WebCore::operator==):
Update operator== for ColorComponents to be none-aware and treat
two components that are both none as equal.

* platform/graphics/ColorTypes.h:
(WebCore::assertInRange):
(WebCore::constexprIsNaN): Deleted.
Adopt shared version of the constexpr isnan.

* platform/graphics/cg/GradientRendererCG.cpp:
(WebCore::anyComponentIsNone):
(WebCore::GradientRendererCG::pickStrategy const):
(WebCore::GradientRendererCG::makeShading const):
Use the CGShaderRef strategy if any component of any stop is none, as it
is the only one that currently supports it correctly. Ensure none is preserved
by utilizing the unresolved component values. This can be optimized in the
future by preprocessing the color stops to pre-resolve the none components,
even allowing the CGGradientRef path to be used for supported cases.

Source/WTF:

Move isNaNConstExpr to MathExtras.h from WebCore so it can used in multiple places.

* wtf/MathExtras.h:
(WTF::isNaNConstExpr):

LayoutTests:

Add tests of gradients with explicit 'none' components in some of the colors.

* fast/gradients/gradient-with-missing-components-expected.html: Added.
* fast/gradients/gradient-with-missing-components.html: Added.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (289121 => 289122)


--- trunk/LayoutTests/ChangeLog	2022-02-04 17:06:32 UTC (rev 289121)
+++ trunk/LayoutTests/ChangeLog	2022-02-04 17:26:30 UTC (rev 289122)
@@ -1,3 +1,15 @@
+2022-02-04  Sam Weinig  <wei...@apple.com>
+
+        Gradients don't correctly interpolate missing/none color components correctly
+        https://bugs.webkit.org/show_bug.cgi?id=236025
+
+        Reviewed by Simon Fraser.
+
+        Add tests of gradients with explicit 'none' components in some of the colors.
+
+        * fast/gradients/gradient-with-missing-components-expected.html: Added.
+        * fast/gradients/gradient-with-missing-components.html: Added.
+
 2022-02-04  Kate Cheney  <katherine_che...@apple.com>
 
         Fix App Privacy Report redirect attribution

Added: trunk/LayoutTests/fast/gradients/gradient-with-missing-components-expected.html (0 => 289122)


--- trunk/LayoutTests/fast/gradients/gradient-with-missing-components-expected.html	                        (rev 0)
+++ trunk/LayoutTests/fast/gradients/gradient-with-missing-components-expected.html	2022-02-04 17:26:30 UTC (rev 289122)
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+    div { 
+        width: 100px;
+        height: 100px;
+        border: dashed black 1px;
+        display: inline-block;
+    }
+
+    #blue-white-none {
+        background-image: linear-gradient(in oklch, oklch(45% 0.3 265) 0%, oklch(from white l c 265) 100%);
+    }
+    #white-none-blue {
+        background-image: linear-gradient(in oklch, oklch(from white l c 265) 0%, oklch(45% 0.3 265) 100%);
+    }
+    #blue-white-none-red {
+        background-image: linear-gradient(in oklch, oklch(45% 0.3 265) 0%, oklch(from white l c 265) 50%, oklch(from white l c 30) 50%, oklch(60% 0.25 30) 100%);
+    }
+    #white-none-red-white-none {
+        background-image: linear-gradient(in oklch, oklch(from white l c none) 0%, oklch(60% 0.25 30) 50%, oklch(from white l c none) 100%);
+    }
+
+    #blue-black-none {
+        background-image: linear-gradient(in oklch, oklch(45% 0.3 265) 0%, oklch(from black l c 265) 100%);
+    }
+    #black-none-blue {
+        background-image: linear-gradient(in oklch, oklch(from black l c 265) 0%, oklch(45% 0.3 265) 100%);
+    }
+    #blue-black-none-red {
+        background-image: linear-gradient(in oklch, oklch(45% 0.3 265) 0%, oklch(from black l c 265) 50%, oklch(from black l c 30) 50%, oklch(60% 0.25 30) 100%);
+    }
+    #black-none-red-black-none {
+        background-image: linear-gradient(in oklch, oklch(from black l c none) 0%, oklch(60% 0.25 30) 50%, oklch(from black l c none) 100%);
+    }
+</style>
+</head>
+<body>
+<div id="blue-white-none"></div>
+<div id="white-none-blue"></div>
+<div id="blue-white-none-red"></div>
+<div id="white-none-red-white-none"></div>
+
+<div id="blue-black-none"></div>
+<div id="black-none-blue"></div>
+<div id="blue-black-none-red"></div>
+<div id="black-none-red-black-none"></div>
+</body>
+</html>

Added: trunk/LayoutTests/fast/gradients/gradient-with-missing-components.html (0 => 289122)


--- trunk/LayoutTests/fast/gradients/gradient-with-missing-components.html	                        (rev 0)
+++ trunk/LayoutTests/fast/gradients/gradient-with-missing-components.html	2022-02-04 17:26:30 UTC (rev 289122)
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+    div { 
+        width: 100px;
+        height: 100px;
+        border: dashed black 1px;
+        display: inline-block;
+    }
+
+    #blue-white-none {
+        background-image: linear-gradient(in oklch, oklch(45% 0.3 265) 0%, oklch(from white l c none) 100%);
+    }
+    #white-none-blue {
+        background-image: linear-gradient(in oklch, oklch(from white l c none) 0%, oklch(45% 0.3 265) 100%);
+    }
+    #blue-white-none-red {
+        background-image: linear-gradient(in oklch, oklch(45% 0.3 265) 0%, oklch(from white l c none) 50%, oklch(60% 0.25 30) 100%);
+    }
+    #white-none-red-white-none {
+        background-image: linear-gradient(in oklch, oklch(from white l c none) 0%, oklch(60% 0.25 30) 50%, oklch(from white l c none) 100%);
+    }
+
+    #blue-black-none {
+        background-image: linear-gradient(in oklch, oklch(45% 0.3 265) 0%, oklch(from black l c none) 100%);
+    }
+    #black-none-blue {
+        background-image: linear-gradient(in oklch, oklch(from black l c none) 0%, oklch(45% 0.3 265) 100%);
+    }
+    #blue-black-none-red {
+        background-image: linear-gradient(in oklch, oklch(45% 0.3 265) 0%, oklch(from black l c none) 50%, oklch(60% 0.25 30) 100%);
+    }
+    #black-none-red-black-none {
+        background-image: linear-gradient(in oklch, oklch(from black l c none) 0%, oklch(60% 0.25 30) 50%, oklch(from black l c none) 100%);
+    }
+</style>
+</head>
+<body>
+<div id="blue-white-none"></div>
+<div id="white-none-blue"></div>
+<div id="blue-white-none-red"></div>
+<div id="white-none-red-white-none"></div>
+
+<div id="blue-black-none"></div>
+<div id="black-none-blue"></div>
+<div id="blue-black-none-red"></div>
+<div id="black-none-red-black-none"></div>
+</body>
+</html>

Modified: trunk/Source/WTF/ChangeLog (289121 => 289122)


--- trunk/Source/WTF/ChangeLog	2022-02-04 17:06:32 UTC (rev 289121)
+++ trunk/Source/WTF/ChangeLog	2022-02-04 17:26:30 UTC (rev 289122)
@@ -1,3 +1,15 @@
+2022-02-04  Sam Weinig  <wei...@apple.com>
+
+        Gradients don't correctly interpolate missing/none color components correctly
+        https://bugs.webkit.org/show_bug.cgi?id=236025
+
+        Reviewed by Simon Fraser.
+
+        Move isNaNConstExpr to MathExtras.h from WebCore so it can used in multiple places.
+
+        * wtf/MathExtras.h:
+        (WTF::isNaNConstExpr):
+
 2022-02-03  Ryosuke Niwa  <rn...@webkit.org>
 
         Delete SelectionAcrossShadowBoundariesEnabled

Modified: trunk/Source/WTF/wtf/MathExtras.h (289121 => 289122)


--- trunk/Source/WTF/wtf/MathExtras.h	2022-02-04 17:06:32 UTC (rev 289121)
+++ trunk/Source/WTF/wtf/MathExtras.h	2022-02-04 17:26:30 UTC (rev 289122)
@@ -770,6 +770,21 @@
 #endif
 }
 
+// FIXME: Replace with std::isnan() once std::isnan() is constexpr.
+template<typename T> typename std::enable_if_t<std::is_floating_point_v<T>, bool> isNaNConstExpr(T value)
+{
+#if COMPILER_HAS_CLANG_BUILTIN(__builtin_isnan)
+    return __builtin_isnan(value);
+#else
+    return value != value;
+#endif
+}
+
+template<typename T> typename std::enable_if_t<std::is_integral_v<T>, bool> isNaNConstExpr(T)
+{
+    return false;
+}
+
 } // namespace WTF
 
 using WTF::shuffleVector;
@@ -777,4 +792,5 @@
 using WTF::ctz;
 using WTF::getLSBSet;
 using WTF::getMSBSet;
+using WTF::isNaNConstExpr;
 using WTF::reverseBits32;

Modified: trunk/Source/WebCore/ChangeLog (289121 => 289122)


--- trunk/Source/WebCore/ChangeLog	2022-02-04 17:06:32 UTC (rev 289121)
+++ trunk/Source/WebCore/ChangeLog	2022-02-04 17:26:30 UTC (rev 289122)
@@ -1,3 +1,37 @@
+2022-02-04  Sam Weinig  <wei...@apple.com>
+
+        Gradients don't correctly interpolate missing/none color components correctly
+        https://bugs.webkit.org/show_bug.cgi?id=236025
+
+        Reviewed by Simon Fraser.
+
+        Test: fast/gradients/gradient-with-missing-components.html
+
+        * platform/graphics/Color.cpp:
+        (WebCore::Color::anyComponentIsNone const):
+        * platform/graphics/Color.h:
+        Add helper to check if any component is 'none'.
+
+        * platform/graphics/ColorComponents.h:
+        (WebCore::operator==):
+        Update operator== for ColorComponents to be none-aware and treat
+        two components that are both none as equal.
+
+        * platform/graphics/ColorTypes.h:
+        (WebCore::assertInRange):
+        (WebCore::constexprIsNaN): Deleted.
+        Adopt shared version of the constexpr isnan.
+
+        * platform/graphics/cg/GradientRendererCG.cpp:
+        (WebCore::anyComponentIsNone):
+        (WebCore::GradientRendererCG::pickStrategy const):
+        (WebCore::GradientRendererCG::makeShading const):
+        Use the CGShaderRef strategy if any component of any stop is none, as it
+        is the only one that currently supports it correctly. Ensure none is preserved
+        by utilizing the unresolved component values. This can be optimized in the
+        future by preprocessing the color stops to pre-resolve the none components,
+        even allowing the CGGradientRef path to be used for supported cases.
+
 2022-02-04  Kate Cheney  <katherine_che...@apple.com>
 
         Fix App Privacy Report redirect attribution

Modified: trunk/Source/WebCore/platform/graphics/Color.cpp (289121 => 289122)


--- trunk/Source/WebCore/platform/graphics/Color.cpp	2022-02-04 17:06:32 UTC (rev 289121)
+++ trunk/Source/WebCore/platform/graphics/Color.cpp	2022-02-04 17:26:30 UTC (rev 289122)
@@ -28,6 +28,7 @@
 
 #include "ColorLuminance.h"
 #include "ColorSerialization.h"
+#include <cmath>
 #include <wtf/Assertions.h>
 #include <wtf/text/TextStream.h>
 
@@ -124,6 +125,20 @@
     });
 }
 
+bool Color::anyComponentIsNone() const
+{
+    return callOnUnderlyingType([&] (const auto& underlyingColor) {
+        using ColorType = std::decay_t<decltype(underlyingColor)>;
+
+        if constexpr (std::is_same_v<ColorType, SRGBA<uint8_t>>) {
+            return false;
+        } else {
+            auto [c1, c2, c3, alpha] = underlyingColor.unresolved();
+            return std::isnan(c1) || std::isnan(c2) || std::isnan(c3) || std::isnan(alpha);
+        }
+    });
+}
+
 Color Color::colorWithAlpha(float alpha) const
 {
     return callOnUnderlyingType([&] (const auto& underlyingColor) -> Color {

Modified: trunk/Source/WebCore/platform/graphics/Color.h (289121 => 289122)


--- trunk/Source/WebCore/platform/graphics/Color.h	2022-02-04 17:06:32 UTC (rev 289121)
+++ trunk/Source/WebCore/platform/graphics/Color.h	2022-02-04 17:26:30 UTC (rev 289122)
@@ -100,6 +100,8 @@
     WEBCORE_EXPORT double luminance() const;
     WEBCORE_EXPORT double lightness() const; // FIXME: Replace remaining uses with luminance.
 
+    bool anyComponentIsNone() const;
+
     template<typename Functor> decltype(auto) callOnUnderlyingType(Functor&&) const;
 
     // This will convert the underlying color into ColorType, potentially lossily if the gamut

Modified: trunk/Source/WebCore/platform/graphics/ColorComponents.h (289121 => 289122)


--- trunk/Source/WebCore/platform/graphics/ColorComponents.h	2022-02-04 17:06:32 UTC (rev 289121)
+++ trunk/Source/WebCore/platform/graphics/ColorComponents.h	2022-02-04 17:26:30 UTC (rev 289122)
@@ -29,6 +29,7 @@
 #include <array>
 #include <cmath>
 #include <tuple>
+#include <wtf/MathExtras.h>
 
 namespace WebCore {
 
@@ -165,7 +166,15 @@
 template<typename T, size_t N>
 constexpr bool operator==(const ColorComponents<T, N>& a, const ColorComponents<T, N>& b)
 {
-    return a.components == b.components;
+    for (size_t i = 0; i < N; ++i) {
+        if (a[i] == b[i])
+            continue;
+        if (isNaNConstExpr(a[i]) && isNaNConstExpr(b[i]))
+            continue;
+        return false;
+    }
+
+    return true;
 }
 
 template<typename T, size_t N>

Modified: trunk/Source/WebCore/platform/graphics/ColorTypes.h (289121 => 289122)


--- trunk/Source/WebCore/platform/graphics/ColorTypes.h	2022-02-04 17:06:32 UTC (rev 289121)
+++ trunk/Source/WebCore/platform/graphics/ColorTypes.h	2022-02-04 17:26:30 UTC (rev 289122)
@@ -132,24 +132,17 @@
 
 #if ASSERT_ENABLED
 
-template<typename ComponentType>
-constexpr bool constexprIsNaN(ComponentType value)
-{
-    // FIXME: Replace with std::isnan() once std::isnan() is constexpr.
-    return value != value;
-}
-
 template<typename ColorType, typename std::enable_if_t<std::is_same_v<typename ColorType::ComponentType, float>>* = nullptr>
 constexpr void assertInRange(ColorType color)
 {
     auto components = asColorComponents(color.unresolved());
     for (unsigned i = 0; i < 3; ++i) {
-        if (constexprIsNaN(components[i]))
+        if (isNaNConstExpr(components[i]))
             continue;
         ASSERT_WITH_MESSAGE(components[i] >= ColorType::Model::componentInfo[i].min, "Component at index %d is %f and is less than the allowed minimum %f", i,  components[i], ColorType::Model::componentInfo[i].min);
         ASSERT_WITH_MESSAGE(components[i] <= ColorType::Model::componentInfo[i].max, "Component at index %d is %f and is greater than the allowed maximum %f", i,  components[i], ColorType::Model::componentInfo[i].max);
     }
-    if (!constexprIsNaN(components[3])) {
+    if (!isNaNConstExpr(components[3])) {
         ASSERT_WITH_MESSAGE(components[3] >= AlphaTraits<typename ColorType::ComponentType>::transparent, "Alpha is %f and is less than the allowed minimum (transparent) %f", components[3], AlphaTraits<typename ColorType::ComponentType>::transparent);
         ASSERT_WITH_MESSAGE(components[3] <= AlphaTraits<typename ColorType::ComponentType>::opaque, "Alpha is %f and is greater than the allowed maximum (opaque) %f", components[3], AlphaTraits<typename ColorType::ComponentType>::opaque);
     }

Modified: trunk/Source/WebCore/platform/graphics/cg/GradientRendererCG.cpp (289121 => 289122)


--- trunk/Source/WebCore/platform/graphics/cg/GradientRendererCG.cpp	2022-02-04 17:06:32 UTC (rev 289121)
+++ trunk/Source/WebCore/platform/graphics/cg/GradientRendererCG.cpp	2022-02-04 17:26:30 UTC (rev 289122)
@@ -386,10 +386,24 @@
 }
 #endif
 
+static bool anyComponentIsNone(const GradientColorStops& stops)
+{
+    for (auto& stop : stops) {
+        if (stop.color.anyComponentIsNone())
+            return true;
+    }
+    
+    return false;
+}
+
 GradientRendererCG::Strategy GradientRendererCG::pickStrategy(ColorInterpolationMethod colorInterpolationMethod, const GradientColorStops& stops) const
 {
     return WTF::switchOn(colorInterpolationMethod.colorSpace,
         [&] (const ColorInterpolationMethod::SRGB&) -> Strategy {
+            // FIXME: As an optimization we can precompute 'none' replacements and create a transformed stop list rather than falling back on CGShadingRef.
+            if (anyComponentIsNone(stops))
+                return makeShading(colorInterpolationMethod, stops);
+
             switch (colorInterpolationMethod.alphaPremultiplication) {
             case AlphaPremultiplication::Unpremultiplied:
                 return makeGradient(colorInterpolationMethod, stops);
@@ -552,7 +566,7 @@
             return WTF::switchOn(colorInterpolationMethod.colorSpace,
                 [&] (auto& colorSpace) -> ColorComponents<float, 4> {
                     using ColorType = typename std::remove_reference_t<decltype(colorSpace)>::ColorType;
-                    return asColorComponents(color.template toColorTypeLossy<ColorType>().resolved());
+                    return asColorComponents(color.template toColorTypeLossy<ColorType>().unresolved());
                 }
             );
         };
@@ -577,6 +591,10 @@
         if (!hasOne)
             totalNumberOfStops++;
 
+        // FIXME: To avoid duplicate work in the shader function, we could precompute a few things:
+        //   - If we have a polar coordinate color space, we can pre-fixup the hues, inserting an extra stop at the same offset if both the fixup on the left and right require different results.
+        //   - If we have 'none' components, we can precompute 'none' replacements, inserting an extra stop at the same offset if the replacements on the left and right are different.
+
         Vector<ColorConvertedToInterpolationColorSpaceStop> convertedStops;
         convertedStops.reserveInitialCapacity(totalNumberOfStops);
 
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to