diff --git a/modules/juce_graphics/geometry/juce_Rectangle.h b/modules/juce_graphics/geometry/juce_Rectangle.h index dcd382570c..898359a282 100644 --- a/modules/juce_graphics/geometry/juce_Rectangle.h +++ b/modules/juce_graphics/geometry/juce_Rectangle.h @@ -848,7 +848,7 @@ public: /** Returns the smallest integer-aligned rectangle that completely contains this one. This is only relevant for floating-point rectangles, of course. - @see toFloat(), toNearestInt(), toNearestIntEdges() + @see toFloat(), toNearestInt(), toNearestIntEdges(), getLargestIntegerWithin() */ Rectangle getSmallestIntegerContainer() const noexcept { @@ -858,6 +858,22 @@ public: detail::ceilAsInt (pos.y + h)); } + /** Returns the largest integer-aligned rectangle that is completely contained by this one. + Returns an empty rectangle, outside the original rectangle, if no integer-aligned rectangle + is contained by this one. + This is only relevant for floating-point rectangles, of course. + @see toFloat(), toNearestInt(), toNearestIntEdges(), getSmallestIntegerContainer() + */ + Rectangle getLargestIntegerWithin() const noexcept + { + const auto l = detail::ceilAsInt (pos.x); + const auto t = detail::ceilAsInt (pos.y); + const auto r = detail::floorAsInt (pos.x + w); + const auto b = detail::floorAsInt (pos.y + h); + + return { l, t, jmax (0, r - l), jmax (0, b - t) }; + } + /** Casts this rectangle to a Rectangle. This uses roundToInt to snap x, y, width and height to the nearest integer (losing precision). If the rectangle already uses integers, this will simply return a copy. diff --git a/modules/juce_graphics/native/juce_CoreGraphicsContext_mac.h b/modules/juce_graphics/native/juce_CoreGraphicsContext_mac.h index 31007602af..d613d85bcd 100644 --- a/modules/juce_graphics/native/juce_CoreGraphicsContext_mac.h +++ b/modules/juce_graphics/native/juce_CoreGraphicsContext_mac.h @@ -144,7 +144,7 @@ private: void flip() const; void applyTransform (const AffineTransform&) const; void drawImage (const Image&, const AffineTransform&, bool fillEntireClipAsTiles); - bool clipToRectangleListWithoutTest (const RectangleList&); + bool clipToRectangleListWithoutTest (const RectangleList&); void fillCGRect (const CGRect&, bool replaceExistingContents); JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CoreGraphicsContext) diff --git a/modules/juce_graphics/native/juce_CoreGraphicsContext_mac.mm b/modules/juce_graphics/native/juce_CoreGraphicsContext_mac.mm index 6a6ea8d0a8..d9fb5e90ec 100644 --- a/modules/juce_graphics/native/juce_CoreGraphicsContext_mac.mm +++ b/modules/juce_graphics/native/juce_CoreGraphicsContext_mac.mm @@ -298,7 +298,7 @@ bool CoreGraphicsContext::clipToRectangle (const Rectangle& r) return ! isClipEmpty(); } -bool CoreGraphicsContext::clipToRectangleListWithoutTest (const RectangleList& clipRegion) +bool CoreGraphicsContext::clipToRectangleListWithoutTest (const RectangleList& clipRegion) { if (clipRegion.isEmpty()) { @@ -307,27 +307,48 @@ bool CoreGraphicsContext::clipToRectangleListWithoutTest (const RectangleList rects (numRects); + std::vector rects ((size_t) clipRegion.getNumRectangles()); + std::transform (clipRegion.begin(), clipRegion.end(), rects.begin(), [this] (const auto& r) + { + return CGRectMake (r.getX(), flipHeight - r.getBottom(), r.getWidth(), r.getHeight()); + }); - int i = 0; - for (auto& r : clipRegion) - rects[i++] = CGRectMake (r.getX(), flipHeight - r.getBottom(), r.getWidth(), r.getHeight()); - - CGContextClipToRects (context.get(), rects, numRects); + CGContextClipToRects (context.get(), rects.data(), rects.size()); lastClipRect.reset(); return true; } bool CoreGraphicsContext::clipToRectangleList (const RectangleList& clipRegion) { - return clipToRectangleListWithoutTest (clipRegion) && ! isClipEmpty(); + RectangleList converted; + + for (auto& rect : clipRegion) + converted.add (rect.toFloat()); + + return clipToRectangleListWithoutTest (converted) && ! isClipEmpty(); } void CoreGraphicsContext::excludeClipRectangle (const Rectangle& r) { - RectangleList remaining (getClipBounds()); - remaining.subtract (r); + const auto cgTransform = CGContextGetUserSpaceToDeviceSpaceTransform (context.get()); + const auto transform = AffineTransform { (float) cgTransform.a, + (float) cgTransform.c, + (float) cgTransform.tx, + (float) cgTransform.b, + (float) cgTransform.d, + (float) cgTransform.ty }; + + const auto flip = [this] (auto rect) { return rect.withY ((float) (flipHeight - rect.getBottom())); }; + const auto flipped = flip (r.toFloat()); + + const auto snapped = flipped.toFloat().transformedBy (transform).getLargestIntegerWithin().toFloat(); + + const auto correctedRect = transform.isOnlyTranslationOrScale() + ? snapped.transformedBy (transform.inverted()) + : flipped.toFloat(); + + RectangleList remaining (getClipBounds().toFloat()); + remaining.subtract (flip (correctedRect)); clipToRectangleListWithoutTest (remaining); } diff --git a/modules/juce_graphics/native/juce_RenderingHelpers.h b/modules/juce_graphics/native/juce_RenderingHelpers.h index 3190c5fd66..5fdf236803 100644 --- a/modules/juce_graphics/native/juce_RenderingHelpers.h +++ b/modules/juce_graphics/native/juce_RenderingHelpers.h @@ -2061,16 +2061,6 @@ public: return clip != nullptr; } - static Rectangle getLargestIntegerWithin (Rectangle r) - { - auto x1 = (int) std::ceil (r.getX()); - auto y1 = (int) std::ceil (r.getY()); - auto x2 = (int) std::floor (r.getRight()); - auto y2 = (int) std::floor (r.getBottom()); - - return { x1, y1, x2 - x1, y2 - y1 }; - } - bool excludeClipRectangle (Rectangle r) { if (clip != nullptr) @@ -2079,11 +2069,11 @@ public: if (transform.isOnlyTranslated) { - clip = clip->excludeClipRectangle (getLargestIntegerWithin (transform.translated (r.toFloat()))); + clip = clip->excludeClipRectangle (transform.translated (r.toFloat()).getLargestIntegerWithin()); } else if (! transform.isRotated) { - clip = clip->excludeClipRectangle (getLargestIntegerWithin (transform.boundsAfterTransform (r.toFloat()))); + clip = clip->excludeClipRectangle (transform.boundsAfterTransform (r.toFloat()).getLargestIntegerWithin()); } else {