Title: [287426] trunk
Revision
287426
Author
wei...@apple.com
Date
2021-12-24 08:46:59 -0800 (Fri, 24 Dec 2021)

Log Message

On systems without CG support for alpha premultiplied gradients, the CGGradientRef path should still be used for the subset of gradients that can transformed
https://bugs.webkit.org/show_bug.cgi?id=234653

Reviewed by Simon Fraser.

Source/WebCore:

Test: fast/gradients/alpha-premultiplied-representable-by-unpremultiplied.html

Optimize gradient rendering on systems without a version of CoreGraphics that supports
alpha premultiplied gradients by using the CGGradientRef code path for the subset of
alpha premultiplied gradients that can be represented as alpha non-premultiplied gradients.

Two types of optimizations are possible for two classes of this subset:

  1. Any gradient that uses the same alpha value for all color stops can be used as is
     with the alpha non-premultiplied CGGradientRef.
  2. Any gradient that conforms to the rule that "any two consecutive color stops must
     either have one that is fully transparent or have the same alpha value for both"
     can be transformed into an identical alpha non-premultiplied gradient by transforming
     the fully transparent stops into either one or two stops. A comment in the code
     goes into much more detail about this.

* platform/graphics/cg/GradientRendererCG.cpp:
(WebCore::classifyAlphaType):
(WebCore::analyzeColorStopsForEmulatedAlphaPremuliplicationOppertunity):
(WebCore::alphaTransformStopsToEmulateAlphaPremuliplication):
(WebCore::GradientRendererCG::pickStrategy const):

LayoutTests:

Add tests of gradients that can render identically with both alpha premultiplied and non-premultiplied gradients
either using the same color stop list or a transformed one.

The main test page contains the alpha premultiplied gradients (which is the default now for CSS gradients), while
the -expected.html contains the alpha non-premultiplied gradients and has alpha premultiplied interpolation explicitly
disabled via a CSSGradientPremultipliedAlphaInterpolationEnabled=false comment command.

* fast/gradients/alpha-premultiplied-representable-by-unpremultiplied-expected.html: Added.
* fast/gradients/alpha-premultiplied-representable-by-unpremultiplied.html: Added.
* platform/glib/TestExpectations: Disable on non-CoreGraphics ports.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (287425 => 287426)


--- trunk/LayoutTests/ChangeLog	2021-12-24 10:50:26 UTC (rev 287425)
+++ trunk/LayoutTests/ChangeLog	2021-12-24 16:46:59 UTC (rev 287426)
@@ -1,3 +1,21 @@
+2021-12-24  Sam Weinig  <wei...@apple.com>
+
+        On systems without CG support for alpha premultiplied gradients, the CGGradientRef path should still be used for the subset of gradients that can transformed
+        https://bugs.webkit.org/show_bug.cgi?id=234653
+
+        Reviewed by Simon Fraser.
+
+        Add tests of gradients that can render identically with both alpha premultiplied and non-premultiplied gradients
+        either using the same color stop list or a transformed one.
+
+        The main test page contains the alpha premultiplied gradients (which is the default now for CSS gradients), while
+        the -expected.html contains the alpha non-premultiplied gradients and has alpha premultiplied interpolation explicitly
+        disabled via a CSSGradientPremultipliedAlphaInterpolationEnabled=false comment command.
+
+        * fast/gradients/alpha-premultiplied-representable-by-unpremultiplied-expected.html: Added.
+        * fast/gradients/alpha-premultiplied-representable-by-unpremultiplied.html: Added.
+        * platform/glib/TestExpectations: Disable on non-CoreGraphics ports.
+
 2021-12-23  Alan Bujtas  <za...@apple.com>
 
         REGRESSION(Containment) nullptr deref in RenderBox::styleDidChange

Added: trunk/LayoutTests/fast/gradients/alpha-premultiplied-representable-by-unpremultiplied-expected.html (0 => 287426)


--- trunk/LayoutTests/fast/gradients/alpha-premultiplied-representable-by-unpremultiplied-expected.html	                        (rev 0)
+++ trunk/LayoutTests/fast/gradients/alpha-premultiplied-representable-by-unpremultiplied-expected.html	2021-12-24 16:46:59 UTC (rev 287426)
@@ -0,0 +1,192 @@
+<!DOCTYPE html><!-- webkit-test-runner [ CSSGradientPremultipliedAlphaInterpolationEnabled=false ] -->
+<html>
+<head>
+<style>
+    div { 
+        width: 100px;
+        height: 100px;
+        border: dashed black 1px;
+        display: inline-block;
+    }
+
+    :root {
+        --transparent-1: rgba(0, 0, 255, 0);
+        --transparent-2: rgba(255, 0, 0, 0);
+        --transparent-3: rgba(0, 255, 0, 0);
+        --transparent-4: rgba(255, 255, 0, 0);
+
+        --opaque-1: rgba(0, 0, 255, 1);
+        --opaque-2: rgba(255, 0, 0, 1);
+        --opaque-3: rgba(0, 255, 0, 1);
+        --opaque-4: rgba(255, 255, 0, 1);
+
+        --partial-1: rgba(0, 0, 255, 0.5);
+        --partial-2: rgba(255, 0, 0, 0.5);
+        --partial-3: rgba(0, 255, 0, 0.5);
+        --partial-4: rgba(255, 255, 0, 0.5);
+    }
+
+    /* NOTE: If we ever want to remove the option to disable alpha premultiplied gradients, and alternative way
+             to test this would be using either Canvas or SVG, which both use unpremultiplied interpolation.    */
+
+    #opaque-opaque {
+        /* No transform */
+        background-image: linear-gradient(var(--opaque-1), var(--opaque-2));
+    }
+
+    #partial-partial {
+        /* No transform */
+        background-image: linear-gradient(var(--partial-1), var(--partial-2));
+    }
+
+    #transparent-transparent {
+        /* No transform */
+        background-image: linear-gradient(var(--transparent-1), var(--transparent-2));
+    }
+
+    #transparent-opaque {
+        /* Steal next */
+        background-image: linear-gradient(var(--transparent-2), var(--opaque-2));
+    }
+
+    #transparent-partial {
+        /* Steal next */
+        background-image: linear-gradient(var(--transparent-2), var(--partial-2));
+    }
+
+    #opaque-transparent {
+        /* Steal previous */
+        background-image: linear-gradient(var(--opaque-1), var(--transparent-1));
+    }
+
+    #partial-transparent {
+        /* Steal previous */
+        background-image: linear-gradient(var(--partial-1), var(--transparent-1));
+    }
+
+    #opaque-transparent-opaque {
+        /* Split */
+        background-image: linear-gradient(var(--opaque-1), var(--transparent-1) 50%, var(--transparent-3) 50%, var(--opaque-3));
+    }
+
+    #opaque-transparent-partial {
+        /* Split */
+        background-image: linear-gradient(var(--opaque-1), var(--transparent-1) 50%, var(--transparent-3) 50%, var(--partial-3));
+    }
+
+    #partial-transparent-partial {
+        /* Split */
+        background-image: linear-gradient(var(--partial-1), var(--transparent-1) 50%, var(--transparent-3) 50%, var(--partial-3));
+    }
+
+    #partial-transparent-opaque {
+        /* Split */
+        background-image: linear-gradient(var(--partial-1), var(--transparent-1) 50%, var(--transparent-3) 50%, var(--opaque-3));
+    }
+
+    #opaque-transparent-transparent-opaque {
+        /* Steal previous, steal next */
+        background-image: linear-gradient(var(--opaque-1), var(--transparent-1), var(--transparent-4), var(--opaque-4));
+    }
+
+    #opaque-transparent-transparent-partial {
+        /* Steal previous, steal next */
+        background-image: linear-gradient(var(--opaque-1), var(--transparent-1), var(--transparent-4), var(--partial-4));
+    }
+
+    #partial-transparent-transparent-partial {
+        /* Steal previous, steal next */
+        background-image: linear-gradient(var(--partial-1), var(--transparent-1), var(--transparent-4), var(--partial-4));
+    }
+
+    #partial-transparent-transparent-opaque {
+        /* Steal previous, nothing, steal next */
+        background-image: linear-gradient(var(--partial-1), var(--transparent-1), var(--transparent-4), var(--opaque-4));
+    }
+
+    #opaque-transparent-transparent-transparent-opaque {
+        /* Steal previous, nothing, steal next */
+        background-image: linear-gradient(var(--opaque-1), var(--transparent-1), var(--transparent-3), var(--transparent-2), var(--opaque-2));
+    }
+
+    #opaque-transparent-transparent-transparent-partial {
+        /* Steal previous, nothing, steal next */
+        background-image: linear-gradient(var(--opaque-1), var(--transparent-1), var(--transparent-3), var(--transparent-2), var(--partial-2));
+    }
+
+    #partial-transparent-transparent-transparent-partial {
+        /* Steal previous, nothing, steal next */
+        background-image: linear-gradient(var(--partial-1), var(--transparent-1), var(--transparent-3), var(--transparent-2), var(--partial-2));
+    }
+
+    #partial-transparent-transparent-transparent-opaque {
+        /* Steal previous, nothing, steal next */
+        background-image: linear-gradient(var(--partial-1), var(--transparent-1), var(--transparent-3), var(--transparent-2), var(--opaque-2));
+    }
+
+    #opaque-transparent-transparent {
+        /* Steal previous, nothing */
+        background-image: linear-gradient(var(--opaque-1), var(--transparent-1), var(--transparent-3));
+    }
+
+    #partial-transparent-transparent {
+        /* Steal previous, nothing */
+        background-image: linear-gradient(var(--partial-1), var(--transparent-1), var(--transparent-3));
+    }
+
+    #transparent-transparent-opaque {
+        /* Nothing, steal next */
+        background-image: linear-gradient(var(--transparent-1), var(--transparent-3), var(--opaque-3));
+    }
+
+    #transparent-transparent-partial {
+        /* Nothing, steal next */
+        background-image: linear-gradient(var(--transparent-1), var(--transparent-3), var(--partial-3));
+    }
+
+    #transparent-transparent-transparent {
+        /* No transform */
+        background-image: linear-gradient(var(--transparent-1), var(--transparent-2), var(--transparent-3));
+    }
+
+    #transparent-opaque-transparent {
+        /* Steal next, steal previous */
+        background-image: linear-gradient(var(--transparent-2), var(--opaque-2), var(--transparent-2));
+    }
+
+    #transparent-partial-transparent {
+        /* Steal next, steal previous */
+        background-image: linear-gradient(var(--transparent-2), var(--partial-2), var(--transparent-2));
+    }
+
+</style>
+</head>
+<body>
+<div id="opaque-opaque"></div>
+<div id="partial-partial"></div>
+<div id="transparent-transparent"></div>
+<div id="transparent-opaque"></div>
+<div id="transparent-partial"></div>
+<div id="opaque-transparent"></div>
+<div id="partial-transparent"></div>
+<div id="opaque-transparent-opaque"></div>
+<div id="opaque-transparent-partial"></div>
+<div id="partial-transparent-partial"></div>
+<div id="partial-transparent-opaque"></div>
+<div id="opaque-transparent-transparent-opaque"></div>
+<div id="opaque-transparent-transparent-partial"></div>
+<div id="partial-transparent-transparent-partial"></div>
+<div id="partial-transparent-transparent-opaque"></div>
+<div id="opaque-transparent-transparent-transparent-opaque"></div>
+<div id="opaque-transparent-transparent-transparent-partial"></div>
+<div id="partial-transparent-transparent-transparent-partial"></div>
+<div id="partial-transparent-transparent-transparent-opaque"></div>
+<div id="opaque-transparent-transparent"></div>
+<div id="partial-transparent-transparent"></div>
+<div id="transparent-transparent-opaque"></div>
+<div id="transparent-transparent-partial"></div>
+<div id="transparent-transparent-transparent"></div>
+<div id="transparent-opaque-transparent"></div>
+<div id="transparent-partial-transparent"></div>
+</body>
+</html>

Added: trunk/LayoutTests/fast/gradients/alpha-premultiplied-representable-by-unpremultiplied.html (0 => 287426)


--- trunk/LayoutTests/fast/gradients/alpha-premultiplied-representable-by-unpremultiplied.html	                        (rev 0)
+++ trunk/LayoutTests/fast/gradients/alpha-premultiplied-representable-by-unpremultiplied.html	2021-12-24 16:46:59 UTC (rev 287426)
@@ -0,0 +1,162 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+    div { 
+        width: 100px;
+        height: 100px;
+        border: dashed black 1px;
+        display: inline-block;
+    }
+
+    :root {
+        --transparent-1: rgba(0, 0, 255, 0);
+        --transparent-2: rgba(255, 0, 0, 0);
+        --transparent-3: rgba(0, 255, 0, 0);
+        --transparent-4: rgba(255, 255, 0, 0);
+
+        --opaque-1: rgba(0, 0, 255, 1);
+        --opaque-2: rgba(255, 0, 0, 1);
+        --opaque-3: rgba(0, 255, 0, 1);
+        --opaque-4: rgba(255, 255, 0, 1);
+
+        --partial-1: rgba(0, 0, 255, 0.5);
+        --partial-2: rgba(255, 0, 0, 0.5);
+        --partial-3: rgba(0, 255, 0, 0.5);
+        --partial-4: rgba(255, 255, 0, 0.5);
+    }
+    
+    #opaque-opaque {
+        background-image: linear-gradient(var(--opaque-1), var(--opaque-2));
+    }
+
+    #partial-partial {
+        background-image: linear-gradient(var(--partial-1), var(--partial-2));
+    }
+
+    #transparent-transparent {
+        background-image: linear-gradient(var(--transparent-1), var(--transparent-2));
+    }
+
+    #transparent-opaque {
+        background-image: linear-gradient(var(--transparent-1), var(--opaque-2));
+    }
+
+    #transparent-partial {
+        background-image: linear-gradient(var(--transparent-1), var(--partial-2));
+    }
+
+    #opaque-transparent {
+        background-image: linear-gradient(var(--opaque-1), var(--transparent-2));
+    }
+
+    #partial-transparent {
+        background-image: linear-gradient(var(--partial-1), var(--transparent-2));
+    }
+
+    #opaque-transparent-opaque {
+        background-image: linear-gradient(var(--opaque-1), var(--transparent-2), var(--opaque-3));
+    }
+
+    #opaque-transparent-partial {
+        background-image: linear-gradient(var(--opaque-1), var(--transparent-2), var(--partial-3));
+    }
+
+    #partial-transparent-partial {
+        background-image: linear-gradient(var(--partial-1), var(--transparent-2), var(--partial-3));
+    }
+
+    #partial-transparent-opaque {
+        background-image: linear-gradient(var(--partial-1), var(--transparent-2), var(--opaque-3));
+    }
+
+    #opaque-transparent-transparent-opaque {
+        background-image: linear-gradient(var(--opaque-1), var(--transparent-2), var(--transparent-3), var(--opaque-4));
+    }
+
+    #opaque-transparent-transparent-partial {
+        background-image: linear-gradient(var(--opaque-1), var(--transparent-2), var(--transparent-3), var(--partial-4));
+    }
+
+    #partial-transparent-transparent-partial {
+        background-image: linear-gradient(var(--partial-1), var(--transparent-2), var(--transparent-3), var(--partial-4));
+    }
+
+    #partial-transparent-transparent-opaque {
+        background-image: linear-gradient(var(--partial-1), var(--transparent-2), var(--transparent-3), var(--opaque-4));
+    }
+
+    #opaque-transparent-transparent-transparent-opaque {
+        background-image: linear-gradient(var(--opaque-1), var(--transparent-2), var(--transparent-3), var(--transparent-4), var(--opaque-2));
+    }
+
+    #opaque-transparent-transparent-transparent-partial {
+        background-image: linear-gradient(var(--opaque-1), var(--transparent-2), var(--transparent-3), var(--transparent-4), var(--partial-2));
+    }
+
+    #partial-transparent-transparent-transparent-partial {
+        background-image: linear-gradient(var(--partial-1), var(--transparent-2), var(--transparent-3), var(--transparent-4), var(--partial-2));
+    }
+
+    #partial-transparent-transparent-transparent-opaque {
+        background-image: linear-gradient(var(--partial-1), var(--transparent-2), var(--transparent-3), var(--transparent-4), var(--opaque-2));
+    }
+
+    #opaque-transparent-transparent {
+        background-image: linear-gradient(var(--opaque-1), var(--transparent-2), var(--transparent-3));
+    }
+
+    #partial-transparent-transparent {
+        background-image: linear-gradient(var(--partial-1), var(--transparent-2), var(--transparent-3));
+    }
+
+    #transparent-transparent-opaque {
+        background-image: linear-gradient(var(--transparent-1), var(--transparent-2), var(--opaque-3));
+    }
+
+    #transparent-transparent-partial {
+        background-image: linear-gradient(var(--transparent-1), var(--transparent-2), var(--partial-3));
+    }
+
+    #transparent-transparent-transparent {
+        background-image: linear-gradient(var(--transparent-1), var(--transparent-2), var(--transparent-3));
+    }
+
+    #transparent-opaque-transparent {
+        background-image: linear-gradient(var(--transparent-1), var(--opaque-2), var(--transparent-3));
+    }
+    #transparent-partial-transparent {
+        background-image: linear-gradient(var(--transparent-1), var(--partial-2), var(--transparent-3));
+    }
+
+</style>
+</head>
+<body>
+<div id="opaque-opaque"></div>
+<div id="partial-partial"></div>
+<div id="transparent-transparent"></div>
+<div id="transparent-opaque"></div>
+<div id="transparent-partial"></div>
+<div id="opaque-transparent"></div>
+<div id="partial-transparent"></div>
+<div id="opaque-transparent-opaque"></div>
+<div id="opaque-transparent-partial"></div>
+<div id="partial-transparent-partial"></div>
+<div id="partial-transparent-opaque"></div>
+<div id="opaque-transparent-transparent-opaque"></div>
+<div id="opaque-transparent-transparent-partial"></div>
+<div id="partial-transparent-transparent-partial"></div>
+<div id="partial-transparent-transparent-opaque"></div>
+<div id="opaque-transparent-transparent-transparent-opaque"></div>
+<div id="opaque-transparent-transparent-transparent-partial"></div>
+<div id="partial-transparent-transparent-transparent-partial"></div>
+<div id="partial-transparent-transparent-transparent-opaque"></div>
+<div id="opaque-transparent-transparent"></div>
+<div id="partial-transparent-transparent"></div>
+<div id="transparent-transparent-opaque"></div>
+<div id="transparent-transparent-partial"></div>
+<div id="transparent-transparent-transparent"></div>
+<div id="transparent-opaque-transparent"></div>
+<div id="transparent-partial-transparent"></div>
+</body>
+</html>

Modified: trunk/LayoutTests/platform/glib/TestExpectations (287425 => 287426)


--- trunk/LayoutTests/platform/glib/TestExpectations	2021-12-24 10:50:26 UTC (rev 287425)
+++ trunk/LayoutTests/platform/glib/TestExpectations	2021-12-24 16:46:59 UTC (rev 287426)
@@ -489,9 +489,10 @@
 
 # fast/gradients are Skip in the top level Expectation. When fixing, change this to
 # Pass instead of removing this line.
-webkit.org/b/214259 fast/gradients/conic-gradient-alpha-unpremultiplied.html [ ImageOnlyFailure ]
-webkit.org/b/214259 fast/gradients/conic-gradient-alpha.html [ ImageOnlyFailure ]
-webkit.org/b/234492 fast/gradients/alpha-premultiplied.html [ ImageOnlyFailure ]
+webkit.org/b/234606 fast/gradients/conic-gradient-alpha-unpremultiplied.html [ ImageOnlyFailure ]
+webkit.org/b/234606 fast/gradients/conic-gradient-alpha.html [ ImageOnlyFailure ]
+webkit.org/b/234606 fast/gradients/alpha-premultiplied-representable-by-unpremultiplied.html [ ImageOnlyFailure ]
+webkit.org/b/234606 fast/gradients/alpha-premultiplied.html [ ImageOnlyFailure ]
 
 webkit.org/b/169988 css3/filters/backdrop/backdrop-filter-with-border-radius-and-reflection-add.html [ ImageOnlyFailure ]
 webkit.org/b/169988 css3/filters/backdrop/backdrop-filter-with-border-radius-and-reflection.html [ ImageOnlyFailure ]

Modified: trunk/Source/WebCore/ChangeLog (287425 => 287426)


--- trunk/Source/WebCore/ChangeLog	2021-12-24 10:50:26 UTC (rev 287425)
+++ trunk/Source/WebCore/ChangeLog	2021-12-24 16:46:59 UTC (rev 287426)
@@ -1,3 +1,32 @@
+2021-12-24  Sam Weinig  <wei...@apple.com>
+
+        On systems without CG support for alpha premultiplied gradients, the CGGradientRef path should still be used for the subset of gradients that can transformed
+        https://bugs.webkit.org/show_bug.cgi?id=234653
+
+        Reviewed by Simon Fraser.
+
+        Test: fast/gradients/alpha-premultiplied-representable-by-unpremultiplied.html
+
+        Optimize gradient rendering on systems without a version of CoreGraphics that supports
+        alpha premultiplied gradients by using the CGGradientRef code path for the subset of
+        alpha premultiplied gradients that can be represented as alpha non-premultiplied gradients.
+
+        Two types of optimizations are possible for two classes of this subset:
+
+          1. Any gradient that uses the same alpha value for all color stops can be used as is
+             with the alpha non-premultiplied CGGradientRef.
+          2. Any gradient that conforms to the rule that "any two consecutive color stops must 
+             either have one that is fully transparent or have the same alpha value for both"
+             can be transformed into an identical alpha non-premultiplied gradient by transforming
+             the fully transparent stops into either one or two stops. A comment in the code
+             goes into much more detail about this.
+
+        * platform/graphics/cg/GradientRendererCG.cpp:
+        (WebCore::classifyAlphaType):
+        (WebCore::analyzeColorStopsForEmulatedAlphaPremuliplicationOppertunity):
+        (WebCore::alphaTransformStopsToEmulateAlphaPremuliplication):
+        (WebCore::GradientRendererCG::pickStrategy const):
+
 2021-12-24  Carlos Garcia Campos  <cgar...@igalia.com>
 
         [GTK][a11y] Expose live region attributes with ATSPI

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


--- trunk/Source/WebCore/platform/graphics/cg/GradientRendererCG.cpp	2021-12-24 10:50:26 UTC (rev 287425)
+++ trunk/Source/WebCore/platform/graphics/cg/GradientRendererCG.cpp	2021-12-24 16:46:59 UTC (rev 287426)
@@ -40,6 +40,352 @@
 
 // MARK: - Strategy selection.
 
+#if !HAVE(CORE_GRAPHICS_PREMULTIPLIED_INTERPOLATION_GRADIENT)
+
+enum class EmulatedAlphaPremuliplicationAnalysisResult {
+    UseGradientAsIs,
+    UseGradientWithAlphaTransforms,
+    UseShading
+};
+
+enum class AlphaType {
+    Opaque,
+    PartiallyTransparent,
+    FullyTransparent
+};
+
+static AlphaType classifyAlphaType(float alpha)
+{
+    if (alpha == 1.0f)
+        return AlphaType::Opaque;
+    if (alpha == 0.0f)
+        return AlphaType::FullyTransparent;
+    return AlphaType::PartiallyTransparent;
+}
+
+static EmulatedAlphaPremuliplicationAnalysisResult analyzeColorStopsForEmulatedAlphaPremuliplicationOppertunity(const GradientColorStops& stops)
+{
+    if (stops.size() < 2)
+        return EmulatedAlphaPremuliplicationAnalysisResult::UseGradientAsIs;
+
+    bool uniformAlpha = true;
+
+    auto& initialStop = *stops.begin();
+    auto previousStopAlpha = initialStop.color.alphaAsFloat();
+    auto previousStopAlphaType = classifyAlphaType(previousStopAlpha);
+
+    auto stopIterator = stops.begin();
+    ++stopIterator;
+
+    while (stopIterator != stops.end()) {
+        auto& stop = *stopIterator;
+
+        auto stopAlpha = stop.color.alphaAsFloat();
+        auto stopAlphaType = classifyAlphaType(stopAlpha);
+
+        switch (stopAlphaType) {
+        case AlphaType::Opaque:
+            switch (previousStopAlphaType) {
+            case AlphaType::Opaque:
+                break;
+            case AlphaType::PartiallyTransparent:
+                return EmulatedAlphaPremuliplicationAnalysisResult::UseShading;
+            case AlphaType::FullyTransparent:
+                uniformAlpha = false;
+                break;
+            }
+            break;
+
+        case AlphaType::PartiallyTransparent:
+            switch (previousStopAlphaType) {
+            case AlphaType::Opaque:
+                return EmulatedAlphaPremuliplicationAnalysisResult::UseShading;
+            case AlphaType::PartiallyTransparent:
+                if (previousStopAlpha != stopAlpha)
+                    return EmulatedAlphaPremuliplicationAnalysisResult::UseShading;
+                break;
+            case AlphaType::FullyTransparent:
+                uniformAlpha = false;
+                break;
+            }
+            break;
+
+        case AlphaType::FullyTransparent:
+            switch (previousStopAlphaType) {
+            case AlphaType::Opaque:
+            case AlphaType::PartiallyTransparent:
+                uniformAlpha = false;
+                break;
+
+            case AlphaType::FullyTransparent:
+                break;
+            }
+            break;
+        }
+
+        previousStopAlpha = stopAlpha;
+        previousStopAlphaType = stopAlphaType;
+
+        ++stopIterator;
+    }
+
+    return uniformAlpha ? EmulatedAlphaPremuliplicationAnalysisResult::UseGradientAsIs : EmulatedAlphaPremuliplicationAnalysisResult::UseGradientWithAlphaTransforms;
+}
+
+static GradientColorStops alphaTransformStopsToEmulateAlphaPremuliplication(const GradientColorStops& stops)
+{
+    // The following is the set of transforms that can be performed on a color stop list to transform it from one used with a premuliplied alpha gradient
+    // implmentation to one used by with an un-premultiplied gradient implementation.
+
+    // ... Opaque  -> Transparent -> Opaque  ...                                  ==>    ... Opaque  -> TRANSFORM{Transparent(PreviousChannels)} | ADDITION{Transparent(NextChannels)} -> Opaque  ...
+    // ... Partial -> Transparent -> Partial ...                                  ==>    ... Partial -> TRANSFORM{Transparent(PreviousChannels)} | ADDITION{Transparent(NextChannels)} -> Partial ...
+    // ... Opaque  -> Transparent -> Partial ...                                  ==>    ... Opaque  -> TRANSFORM{Transparent(PreviousChannels)} | ADDITION{Transparent(NextChannels)} -> Partial ...
+    // ... Partial -> Transparent -> Opaque  ...                                  ==>    ... Partial -> TRANSFORM{Transparent(PreviousChannels)} | ADDITION{Transparent(NextChannels)} -> Opaque  ...
+    //
+    // ... Opaque  -> Transparent -> Transparent -> Opaque  ...                   ==>    ... Opaque  -> TRANSFORM{Transparent(PreviousChannels)} -> TRANSFORM{Transparent(NextChannels)} -> Opaque  ...
+    // ... Partial -> Transparent -> Transparent -> Partial ...                   ==>    ... Partial -> TRANSFORM{Transparent(PreviousChannels)} -> TRANSFORM{Transparent(NextChannels)} -> Partial ...
+    // ... Opaque  -> Transparent -> Transparent -> Partial ...                   ==>    ... Opaque  -> TRANSFORM{Transparent(PreviousChannels)} -> TRANSFORM{Transparent(NextChannels)} -> Partial ...
+    // ... Partial -> Transparent -> Transparent -> Opaque  ...                   ==>    ... Partial -> TRANSFORM{Transparent(PreviousChannels)} -> TRANSFORM{Transparent(NextChannels)} -> Opaque  ...
+    //
+    // ... Opaque  -> Transparent -> Transparent -> Transparent -> Opaque  ...    ==>    ... Opaque  -> TRANSFORM{Transparent(PreviousChannels)} -> Transparent -> TRANSFORM{Transparent(NextChannels)} -> Opaque  ...
+    // ... Partial -> Transparent -> Transparent -> Transparent -> Partial ...    ==>    ... Partial -> TRANSFORM{Transparent(PreviousChannels)} -> Transparent -> TRANSFORM{Transparent(NextChannels)} -> Partial ...
+    // ... Opaque  -> Transparent -> Transparent -> Transparent -> Partial ...    ==>    ... Opaque  -> TRANSFORM{Transparent(PreviousChannels)} -> Transparent -> TRANSFORM{Transparent(NextChannels)} -> Partial ...
+    // ... Partial -> Transparent -> Transparent -> Transparent -> Opaque  ...    ==>    ... Partial -> TRANSFORM{Transparent(PreviousChannels)} -> Transparent -> TRANSFORM{Transparent(NextChannels)} -> Opaque  ...
+    //
+    // [ Transparent -> Opaque      ...                                           ==>    [ TRANSFORM{Transparent(NextChannels)} -> Opaque      ...
+    // [ Transparent -> Partial     ...                                           ==>    [ TRANSFORM{Transparent(NextChannels)} -> Partial     ...
+    // [ Transparent -> Transparent ...                                           ==>    [ Transparent                          -> Transparent ...
+    //
+    // ... Opaque       -> Transparent ]                                          ==>    ... Opaque      -> TRANSFORM{Transparent(PreviousChannels)} ]
+    // ... Partial      -> Transparent ]                                          ==>    ... Partial     -> TRANSFORM{Transparent(PreviousChannels)} ]
+    // ... Transparent  -> Transparent ]                                          ==>    ... Transparent -> Transparent                              ]
+
+    // For each stop the following actions are possible:
+    //      - Default           Append self
+    //      - Steal previous    Append previous.colorWithAlpha(0.0f)
+    //      - Steal next        Append next.colorWithAlpha(0.0f)
+    //      - Split             Append previous.colorWithAlpha(0.0f) + next.colorWithAlpha(0.0f)
+
+    ASSERT(stops.size() > 1);
+
+    // Special case when we only have two stops to avoid complicating the cases with more.
+    if (stops.size() == 2) {
+        // Illegal pairs (ruled out in analysis)
+        //   Opaque  -> Partial
+        //   Partial -> Opaque
+        //
+        // Possible pairs
+        //   Opaque      -> Opaque       (default, default)
+        //   Partial     -> Partial      (default, default)
+        //   Transparent -> Transparent  (default, default)
+        //   Opaque      -> Transparent  (default, steal previous)
+        //   Partial     -> Transparent  (default, steal previous)
+        //   Transparent -> Opaque       (steals next, default)
+        //   Transparent -> Partial      (steals next, default)
+
+        GradientColorStops::StopVector result;
+        result.reserveInitialCapacity(2);
+
+        auto& stop1 = stops.stops()[0];
+        auto& stop2 = stops.stops()[1];
+
+        auto stop1AlphaType = classifyAlphaType(stop1.color.alphaAsFloat());
+        auto stop2AlphaType = classifyAlphaType(stop2.color.alphaAsFloat());
+
+        switch (stop1AlphaType) {
+        case AlphaType::Opaque:
+        case AlphaType::PartiallyTransparent:
+            // ACTION (stop1): Default.
+            result.uncheckedAppend(stop1);
+
+            switch (stop2AlphaType) {
+            case AlphaType::Opaque:
+            case AlphaType::PartiallyTransparent:
+                // ACTION (stop2): Default.
+                result.uncheckedAppend(stop2);
+                break;
+            case AlphaType::FullyTransparent:
+                // ACTION (stop2): Steal previous.
+                result.uncheckedAppend({ stop2.offset, stop1.color.colorWithAlpha(0.0f) });
+                break;
+            }
+
+            break;
+
+        case AlphaType::FullyTransparent:
+            switch (stop2AlphaType) {
+            case AlphaType::Opaque:
+            case AlphaType::PartiallyTransparent:
+                // ACTION (stop1): Steal next.
+                result.uncheckedAppend({ stop1.offset, stop2.color.colorWithAlpha(0.0f) });
+                break;
+            case AlphaType::FullyTransparent:
+                // ACTION (stop1): Default.
+                result.uncheckedAppend(stop1);
+                break;
+            }
+            // ACTION (stop2): Default.
+            result.uncheckedAppend(stop2);
+            break;
+        }
+
+        return GradientColorStops::Sorted { WTFMove(result) };
+    }
+
+    GradientColorStops::StopVector result;
+
+    // 1. Handle first stop.
+    //
+    // [first stop matters]
+    //
+    // Illegal pairs (ruled out in analysis)
+    //   Opaque  -> Partial
+    //   Partial -> Opaque
+    //
+    // Possible pairs
+    //   Opaque      -> Opaque       (default)
+    //   Partial     -> Partial      (default)
+    //   Transparent -> Transparent  (default)
+    //   Opaque      -> Transparent  (default)
+    //   Partial     -> Transparent  (default)
+    //   Transparent -> Opaque       (steals next)
+    //   Transparent -> Partial      (steals next)
+
+    auto& firstStop = stops.stops()[0];
+    auto& secondStop = stops.stops()[1];
+
+    auto firstStopAlphaType = classifyAlphaType(firstStop.color.alphaAsFloat());
+    auto secondStopAlphaType = classifyAlphaType(secondStop.color.alphaAsFloat());
+
+    if (firstStopAlphaType == AlphaType::FullyTransparent && secondStopAlphaType != AlphaType::FullyTransparent) {
+        // ACTION: Steal next.
+        result.append({ firstStop.offset, secondStop.color.colorWithAlpha(0.0f) });
+    } else {
+        // ACTION: Default.
+        result.append(firstStop);
+    }
+
+    // 2. Handle middle stops.
+    //
+    // [middle stop matters]
+    //
+    // Illegal triplets (ruled out in analysis)
+    //   Opaque      -> Opaque      -> Partial
+    //   Opaque      -> Partial     -> Partial
+    //   Opaque      -> Partial     -> Opaque
+    //   Partial     -> Opaque      -> Opaque
+    //   Partial     -> Partial     -> Opaque
+    //   Partial     -> Opaque      -> Partial
+    //
+    // Possible triplets
+    //   Opaque      -> Opaque      -> Opaque       (default)
+    //   Partial     -> Partial     -> Partial      (default)
+    //   Opaque      -> Opaque      -> Transparent  (default)
+    //   Opaque      -> Partial     -> Transparent  (default)
+    //   Partial     -> Partial     -> Transparent  (default)
+    //   Partial     -> Opaque      -> Transparent  (default)
+    //   Transparent -> Opaque      -> Transparent  (default)
+    //   Transparent -> Partial     -> Transparent  (default)
+    //   Transparent -> Transparent -> Transparent  (default)
+    //   Opaque      -> Transparent -> Opaque       (splits, steals previous + next)
+    //   Opaque      -> Transparent -> Partial      (splits, steals previous + next)
+    //   Partial     -> Transparent -> Partial      (splits, steals previous + next)
+    //   Partial     -> Transparent -> Opaque       (splits, steals previous + next)
+    //   Transparent -> Transparent -> Opaque       (steals next)
+    //   Transparent -> Transparent -> Partial      (steals next)
+    //   Opaque      -> Transparent -> Transparent  (steals previous)
+    //   Partial     -> Transparent -> Transparent  (steals previous)
+
+    size_t previousStopIndex = 0;
+    size_t stopIndex = 1;
+    size_t nextStopIndex = 2;
+
+    for (; nextStopIndex < stops.size(); ++previousStopIndex, ++stopIndex, ++nextStopIndex) {
+        auto& previousStop = stops.stops()[previousStopIndex];
+        auto& stop = stops.stops()[stopIndex];
+        auto& nextStop = stops.stops()[nextStopIndex];
+
+        auto previousStopAlphaType = classifyAlphaType(previousStop.color.alphaAsFloat());
+        auto stopAlphaType = classifyAlphaType(stop.color.alphaAsFloat());
+        auto nextStopAlphaType = classifyAlphaType(nextStop.color.alphaAsFloat());
+
+        if (stopAlphaType == AlphaType::FullyTransparent) {
+            switch (previousStopAlphaType) {
+            case AlphaType::Opaque:
+            case AlphaType::PartiallyTransparent:
+                switch (nextStopAlphaType) {
+                case AlphaType::Opaque:
+                case AlphaType::PartiallyTransparent:
+                    // ACTION: Split.
+                    result.append({ stop.offset, previousStop.color.colorWithAlpha(0.0f) });
+                    result.append({ stop.offset, nextStop.color.colorWithAlpha(0.0f) });
+                    break;
+
+                case AlphaType::FullyTransparent:
+                    // ACTION: Steal previous.
+                    result.append({ stop.offset, previousStop.color.colorWithAlpha(0.0f) });
+                    break;
+                }
+                break;
+
+            case AlphaType::FullyTransparent:
+                switch (nextStopAlphaType) {
+                case AlphaType::Opaque:
+                case AlphaType::PartiallyTransparent:
+                    // ACTION: Steal next.
+                    result.append({ stop.offset, nextStop.color.colorWithAlpha(0.0f) });
+                    break;
+
+                case AlphaType::FullyTransparent:
+                    // ACTION: Default.
+                    result.append(stop);
+                    break;
+                }
+                break;
+            }
+        } else {
+            // ACTION: Default.
+            result.append(stop);
+        }
+    }
+
+    ASSERT(nextStopIndex == stops.size());
+
+    // 3. Handle last stop.
+    //
+    // [last stop matters]
+    //
+    // Illegal pairs (ruled out in analysis phase)
+    //   Opaque  -> Partial
+    //   Partial -> Opaque
+    //
+    // Possible pairs
+    //   Opaque      -> Opaque       (default)
+    //   Partial     -> Partial      (default)
+    //   Transparent -> Transparent  (default)
+    //   Opaque      -> Transparent  (steals previous)
+    //   Partial     -> Transparent  (steals previous)
+    //   Transparent -> Opaque       (default)
+    //   Transparent -> Partial      (default)
+
+    auto& secondToLastStop = stops.stops()[previousStopIndex];
+    auto& lastStop = stops.stops()[stopIndex];
+
+    auto secondToLastStopAlphaType = classifyAlphaType(secondToLastStop.color.alphaAsFloat());
+    auto lastStopAlphaType = classifyAlphaType(lastStop.color.alphaAsFloat());
+
+    if (lastStopAlphaType == AlphaType::FullyTransparent && secondToLastStopAlphaType != AlphaType::FullyTransparent) {
+        // ACTION: Steal previous.
+        result.append({ lastStop.offset, secondToLastStop.color.colorWithAlpha(0.0f) });
+    } else {
+        // ACTION: Default.
+        result.append(lastStop);
+    }
+
+    return GradientColorStops::Sorted { WTFMove(result) };
+}
+#endif
+
 GradientRendererCG::Strategy GradientRendererCG::pickStrategy(ColorInterpolationMethod colorInterpolationMethod, const GradientColorStops& stops) const
 {
     return WTF::switchOn(colorInterpolationMethod.colorSpace,
@@ -51,8 +397,14 @@
 #if HAVE(CORE_GRAPHICS_PREMULTIPLIED_INTERPOLATION_GRADIENT)
                 return makeGradient(colorInterpolationMethod, stops);
 #else
-                // FIXME: Use gradient strategy if none of the stops have alpha.
-                return makeShading(colorInterpolationMethod, stops);
+                switch (analyzeColorStopsForEmulatedAlphaPremuliplicationOppertunity(stops)) {
+                case EmulatedAlphaPremuliplicationAnalysisResult::UseGradientAsIs:
+                    return makeGradient({ ColorInterpolationMethod::SRGB { }, AlphaPremultiplication::Unpremultiplied }, stops);
+                case EmulatedAlphaPremuliplicationAnalysisResult::UseGradientWithAlphaTransforms:
+                    return makeGradient({ ColorInterpolationMethod::SRGB { }, AlphaPremultiplication::Unpremultiplied }, alphaTransformStopsToEmulateAlphaPremuliplication(stops));
+                case EmulatedAlphaPremuliplicationAnalysisResult::UseShading:
+                    return makeShading(colorInterpolationMethod, stops);
+                }
 #endif
             }
         },
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to