1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-02-05 03:50:07 +00:00

CoreGraphics: Slightly shrink excluded clip regions in order to draw transformed opaque components without border artefacts

Previously, drawing an opaque, scaled component with CoreGraphics could
lead to visible artefacts around the edge of the component.

When drawing the parent of an opaque component, the area covered by the
opaque component is excluded from the clip region. If the clip region is
non-integral when transformed into device space, anti-aliasing will be
applied on the edges of the clip region. Similarly, when drawing the
opaque component itself, anti-aliasing will be applied at the edges of
the component. When the two drawings are superimposed, the foreground
anti-aliased pixels will be blended with the background anti-aliased
pixels, leading to a noticeable border around the component. Ideally,
only the foreground anti-aliasing should be applied, and the background
should not be anti-aliased around its edges.
This commit is contained in:
reuk 2024-05-08 19:50:50 +01:00
parent bd2aea9676
commit e11b8f5ccb
No known key found for this signature in database
GPG key ID: FCB43929F012EE5C
4 changed files with 52 additions and 25 deletions

View file

@ -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<int> 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<int> 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<int>.
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.

View file

@ -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<int>&);
bool clipToRectangleListWithoutTest (const RectangleList<float>&);
void fillCGRect (const CGRect&, bool replaceExistingContents);
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CoreGraphicsContext)

View file

@ -298,7 +298,7 @@ bool CoreGraphicsContext::clipToRectangle (const Rectangle<int>& r)
return ! isClipEmpty();
}
bool CoreGraphicsContext::clipToRectangleListWithoutTest (const RectangleList<int>& clipRegion)
bool CoreGraphicsContext::clipToRectangleListWithoutTest (const RectangleList<float>& clipRegion)
{
if (clipRegion.isEmpty())
{
@ -307,27 +307,48 @@ bool CoreGraphicsContext::clipToRectangleListWithoutTest (const RectangleList<in
return false;
}
auto numRects = (size_t) clipRegion.getNumRectangles();
HeapBlock<CGRect> rects (numRects);
std::vector<CGRect> 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<int>& clipRegion)
{
return clipToRectangleListWithoutTest (clipRegion) && ! isClipEmpty();
RectangleList<float> converted;
for (auto& rect : clipRegion)
converted.add (rect.toFloat());
return clipToRectangleListWithoutTest (converted) && ! isClipEmpty();
}
void CoreGraphicsContext::excludeClipRectangle (const Rectangle<int>& r)
{
RectangleList<int> 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<float> remaining (getClipBounds().toFloat());
remaining.subtract (flip (correctedRect));
clipToRectangleListWithoutTest (remaining);
}

View file

@ -2061,16 +2061,6 @@ public:
return clip != nullptr;
}
static Rectangle<int> getLargestIntegerWithin (Rectangle<float> 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<int> 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
{