Direct2D: 1px unpainted seam at opaque component edges with fractional display scale (e.g. 150%)

With the Direct2D renderer and a non-integer display scale (150%), an opaque component whose edge lands on an odd logical coordinate leaves a 1px garbage line along that edge. Odd x 1.5 puts the edge on a fractional device pixel (e.g. x=15 → 22.5).

Cause: The opaque-component culling in Component::paintComponentAndChildren splits paint responsibility at that edge in integer logical coordinates: the parent must not paint under the opaque child, the child covers the area itself. But Direct2DGraphicsContext::clipToRectangle (axis-aligned branch) clips to the exact fractional device rect and pushes it via PushAxisAlignedClip (…, D2D1_ANTIALIAS_MODE_ALIASED). The aliased clip cuts the boundary pixel out of the parent’s render, and the child’s fillAll (edge also at .5) never fully covers it either — so the pixel keeps whatever Clear() left in the swap chain buffer. The software renderer doesn’t have this problem because Rectangle<int>::transformedBy rounds clips outward; note that excludeClipRectangle in the same file already rounds (getLargestIntegerWithin), but the positive clip doesn’t.

Fix: expand the transformed clip to whole device pixels, matching the software renderer:

// Direct2DGraphicsContext::clipToRectangle, axis-aligned branch
auto transformedR = transform.boundsAfterTransform (r.toFloat())
                             .getSmallestIntegerContainer()
                             .toFloat();

and the same in the axis-aligned branch of clipToRectangleList. Verified: the artifacts are gone, with no visible regressions (components can now overpaint up to 1 device px at fractional scales, same as the software renderer, paint order makes that harmless).

That light gray line above the footer shouldn’t be there at all. Nobody paints it. The footer only uses fillAll to create the dark gray background. The rest are child components. The light gray bar is whatever was in the swap buffer and it changes when I switch pages.

1 Like

thanks for spotting this. i hacked around it with some weird awful stuff - i can’t remember what i did but i think i landed on adding an extra 1px transparent border around elements in my filmstrips, i assumed i was doing something wrong on my side. would be great to fix properly.

Thank you for reporting this. A fix has been released on develop

2 Likes

Hmm, still a problem here. I pulled out my workarounds - the issue I have (which might be different to the original one in this report?) still occurs:


Screenshot synced to tag 8.0.15, no hacky workaround - glitch at top of some knobs:


Screenshot synced to 4f5084c (this change) - still busted.

$ git log
commit 4f5084cc2e36189ffa106eca95a8a91b566010b6 (HEAD)
Author: attila attila@juce.com
Date: Fri Aug 7 12:14:46 2026 +0200

Direct2D: Fix unpainted seam at opaque component edges with fractional display scale

Using OpenGL instead of Direct2D, 4f5084c (NO GLITCH):


With the hack, synced to anything - it works. I’ll save the screenshot but it looks like the OpenGL one.

The specific hack is: I call img.duplicateIfShared() on each individual frame of the knob filmstrip, if direct2d. Otherwise the image is just a subimage of a filmstrip like this. Making those duplicates is pretty slow but prevents the fringing shown above. There aren’t any obvious off-by-ones, it works fine on macos, linux and opengl on windows. In direct2d if the img is a subimage of that, i get fringes from surrounding “frames”.

The original report was about the current clip-rectangle not being large enough to fill up the entire pixel, IF a scale factor was involved AND using g.fillAll () AND the component was opaque.

Your bug is about sub-images not being calculated precisely enough, so neighboring pixels of the same source-image are bleeding in.

You need to report this separately, ideally with either the offending code in JUCE and a small fix (like what I submitted) or a small project that reproduces it.

1 Like

thanks, sorry for the noise

absolutely flattened here with tasks so might be a while before i can put together a minimal repro though

I had a similar problem as the OP above. One component exactly overlays another, and on Windows, it would expose a vertical line of pixels on the left side. All my internal computations were exact, but depending on scaling factor, sometimes that left edge of the “behind” image peeked out. I “fixed” it by making the overlay two pixels larger than the image behind it (one extra on the left and one on the right, just for symmetry). That prevented the rounding error from being visible.:man_shrugging:

1 Like