From 8ad4de99f50dcc2bf9a0a7365d5895e1c7545a47 Mon Sep 17 00:00:00 2001
From: reuk <reuk@users.noreply.github.com>
Date: Wed, 3 May 2023 15:32:51 +0100
Subject: [PATCH] CoreGraphicsContext: Fix issue where clipping a gradient on a
 layer-backed view could cause rendering glitches

---
 .../native/juce_CoreGraphicsContext_mac.h     |  5 +-
 .../native/juce_CoreGraphicsContext_mac.mm    | 87 ++++++-------------
 2 files changed, 29 insertions(+), 63 deletions(-)

diff --git a/modules/juce_graphics/native/juce_CoreGraphicsContext_mac.h b/modules/juce_graphics/native/juce_CoreGraphicsContext_mac.h
index 6230c99c1c..07ee759aca 100644
--- a/modules/juce_graphics/native/juce_CoreGraphicsContext_mac.h
+++ b/modules/juce_graphics/native/juce_CoreGraphicsContext_mac.h
@@ -120,8 +120,7 @@ private:
     detail::ContextPtr context;
     const CGFloat flipHeight;
     detail::ColorSpacePtr rgbColourSpace, greyColourSpace;
-    mutable Rectangle<int> lastClipRect;
-    mutable bool lastClipRectIsValid = false;
+    mutable std::optional<Rectangle<int>> lastClipRect;
 
     struct SavedState
     {
@@ -142,8 +141,8 @@ private:
     std::unique_ptr<SavedState> state;
     OwnedArray<SavedState> stateStack;
 
+    void setContextClipToPath (const Path&, const AffineTransform&);
     void drawGradient();
-    void createPath (const Path&) const;
     void createPath (const Path&, const AffineTransform&) const;
     void flip() const;
     void applyTransform (const AffineTransform&) const;
diff --git a/modules/juce_graphics/native/juce_CoreGraphicsContext_mac.mm b/modules/juce_graphics/native/juce_CoreGraphicsContext_mac.mm
index 8496147b0a..fb67176cf2 100644
--- a/modules/juce_graphics/native/juce_CoreGraphicsContext_mac.mm
+++ b/modules/juce_graphics/native/juce_CoreGraphicsContext_mac.mm
@@ -221,8 +221,8 @@ static CGBitmapInfo getCGImageFlags (const Image::PixelFormat& format)
 {
     CGContextTranslateCTM (context.get(), o.x, -o.y);
 
-    if (lastClipRectIsValid)
-        lastClipRect.translate (-o.x, -o.y);
+    if (lastClipRect.has_value())
+        lastClipRect->translate (-o.x, -o.y);
 }
 
 void CoreGraphicsContext::addTransform (const AffineTransform& transform)
@@ -231,7 +231,7 @@ static CGBitmapInfo getCGImageFlags (const Image::PixelFormat& format)
                                     .followedBy (transform)
                                     .translated (0, (float) -flipHeight)
                                     .scaled (1.0f, -1.0f));
-    lastClipRectIsValid = false;
+    lastClipRect.reset();
 
     jassert (getPhysicalPixelScaleFactor() > 0.0f);
 }
@@ -249,14 +249,14 @@ static CGBitmapInfo getCGImageFlags (const Image::PixelFormat& format)
     CGContextClipToRect (context.get(), CGRectMake (r.getX(), flipHeight - r.getBottom(),
                                                     r.getWidth(), r.getHeight()));
 
-    if (lastClipRectIsValid)
+    if (lastClipRect.has_value())
     {
         // This is actually incorrect, because the actual clip region may be complex, and
         // clipping its bounds to a rect may not be right... But, removing this shortcut
         // doesn't actually fix anything because CoreGraphics also ignores complex regions
         // when calculating the resultant clip bounds, and makes the same mistake!
-        lastClipRect = lastClipRect.getIntersection (r);
-        return ! lastClipRect.isEmpty();
+        lastClipRect = lastClipRect->getIntersection (r);
+        return ! lastClipRect->isEmpty();
     }
 
     return ! isClipEmpty();
@@ -267,7 +267,6 @@ static CGBitmapInfo getCGImageFlags (const Image::PixelFormat& format)
     if (clipRegion.isEmpty())
     {
         CGContextClipToRect (context.get(), CGRectZero);
-        lastClipRectIsValid = true;
         lastClipRect = Rectangle<int>();
         return false;
     }
@@ -280,7 +279,7 @@ static CGBitmapInfo getCGImageFlags (const Image::PixelFormat& format)
         rects[i++] = CGRectMake (r.getX(), flipHeight - r.getBottom(), r.getWidth(), r.getHeight());
 
     CGContextClipToRects (context.get(), rects, numRects);
-    lastClipRectIsValid = false;
+    lastClipRect.reset();
     return true;
 }
 
@@ -296,7 +295,7 @@ static CGBitmapInfo getCGImageFlags (const Image::PixelFormat& format)
     clipToRectangleListWithoutTest (remaining);
 }
 
-void CoreGraphicsContext::clipToPath (const Path& path, const AffineTransform& transform)
+void CoreGraphicsContext::setContextClipToPath (const Path& path, const AffineTransform& transform)
 {
     createPath (path, transform);
 
@@ -304,8 +303,12 @@ static CGBitmapInfo getCGImageFlags (const Image::PixelFormat& format)
         CGContextClip (context.get());
     else
         CGContextEOClip (context.get());
+}
 
-    lastClipRectIsValid = false;
+void CoreGraphicsContext::clipToPath (const Path& path, const AffineTransform& transform)
+{
+    setContextClipToPath (path, transform);
+    lastClipRect.reset();
 }
 
 void CoreGraphicsContext::clipToImageAlpha (const Image& sourceImage, const AffineTransform& transform)
@@ -329,7 +332,7 @@ static CGBitmapInfo getCGImageFlags (const Image::PixelFormat& format)
         applyTransform (t.inverted());
         flip();
 
-        lastClipRectIsValid = false;
+        lastClipRect.reset();
     }
 }
 
@@ -340,18 +343,17 @@ static CGBitmapInfo getCGImageFlags (const Image::PixelFormat& format)
 
 Rectangle<int> CoreGraphicsContext::getClipBounds() const
 {
-    if (! lastClipRectIsValid)
+    if (! lastClipRect.has_value())
     {
         auto bounds = CGRectIntegral (CGContextGetClipBoundingBox (context.get()));
 
-        lastClipRectIsValid = true;
-        lastClipRect.setBounds (roundToInt (bounds.origin.x),
-                                roundToInt (flipHeight - (bounds.origin.y + bounds.size.height)),
-                                roundToInt (bounds.size.width),
-                                roundToInt (bounds.size.height));
+        lastClipRect = Rectangle<int> (roundToInt (bounds.origin.x),
+                                       roundToInt (flipHeight - (bounds.origin.y + bounds.size.height)),
+                                       roundToInt (bounds.size.width),
+                                       roundToInt (bounds.size.height));
     }
 
-    return lastClipRect;
+    return *lastClipRect;
 }
 
 bool CoreGraphicsContext::isClipEmpty() const
@@ -376,7 +378,7 @@ static CGBitmapInfo getCGImageFlags (const Image::PixelFormat& format)
         CGContextSetTextMatrix (context.get(), state->textMatrix);
 
         stateStack.removeLast (1, false);
-        lastClipRectIsValid = false;
+        lastClipRect.reset();
     }
     else
     {
@@ -495,31 +497,14 @@ static CGBitmapInfo getCGImageFlags (const Image::PixelFormat& format)
 {
     ScopedCGContextState scopedState (context.get());
 
-    if (state->fillType.isColour())
-    {
-        flip();
-        applyTransform (transform);
-        createPath (path);
+    setContextClipToPath (path, transform);
 
-        if (path.isUsingNonZeroWinding())
-            CGContextFillPath (context.get());
-        else
-            CGContextEOFillPath (context.get());
-    }
+    if (state->fillType.isColour())
+        CGContextFillRect (context.get(), CGContextGetClipBoundingBox (context.get()));
+    else if (state->fillType.isGradient())
+        drawGradient();
     else
-    {
-        createPath (path, transform);
-
-        if (path.isUsingNonZeroWinding())
-            CGContextClip (context.get());
-        else
-            CGContextEOClip (context.get());
-
-        if (state->fillType.isGradient())
-            drawGradient();
-        else
-            drawImage (state->fillType.image, state->fillType.transform, true);
-    }
+        drawImage (state->fillType.image, state->fillType.transform, true);
 }
 
 void CoreGraphicsContext::drawImage (const Image& sourceImage, const AffineTransform& transform)
@@ -773,24 +758,6 @@ static CGGradientRef createGradient (const ColourGradient& g, CGColorSpaceRef co
                                      kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
 }
 
-void CoreGraphicsContext::createPath (const Path& path) const
-{
-    CGContextBeginPath (context.get());
-
-    for (Path::Iterator i (path); i.next();)
-    {
-        switch (i.elementType)
-        {
-            case Path::Iterator::startNewSubPath:  CGContextMoveToPoint (context.get(), i.x1, i.y1); break;
-            case Path::Iterator::lineTo:           CGContextAddLineToPoint (context.get(), i.x1, i.y1); break;
-            case Path::Iterator::quadraticTo:      CGContextAddQuadCurveToPoint (context.get(), i.x1, i.y1, i.x2, i.y2); break;
-            case Path::Iterator::cubicTo:          CGContextAddCurveToPoint (context.get(), i.x1, i.y1, i.x2, i.y2, i.x3, i.y3); break;
-            case Path::Iterator::closePath:        CGContextClosePath (context.get()); break;
-            default:                               jassertfalse; break;
-        }
-    }
-}
-
 void CoreGraphicsContext::createPath (const Path& path, const AffineTransform& transform) const
 {
     CGContextBeginPath (context.get());
-- 
2.39.2 (Apple Git-143)

