Unwanted borders when scaling UI with opaque components

I have added a component with the same background colour like the plugin background and used setOpaque(true) because the component changes it’s colours and i want to suppress the re-rendering of the whole UI. This works if i don’t scale the UI. If i scale the UI, i can see a small border around the component:

image

I understand that this happens because the overlaying component does not blend colours with setOpaque(true) enabled and because the scaling does not hit exactly the pixels.

Is there a way to avoid this issue. Maybe something like snap to pixels or a totally different solution?

Just a shot in the dark, your issue might be something else, but is this on osx with the openGL renderer?
Does the component use an image, or are you painting it?
With opengl and images, you might need that to avoid glitches :
#define JUCE_OPENGL_ALLOW_NON_POWER_OF_TWO_TEXTURES (1)

Thanks for the fast reply. I do not use OpenGL and there are no images involved. I’m using fillAll() to paint the background of the plugin and the component. It happens only if i scale the UI with an affine transform.

perhaps it could be related to that : Subtle drawing bug(?) -> fillRect (integer version) scaled

Thanks for pointing to that. It looks that this isn’t related. I get the issue also when i draw a very bold border. It seems that it is something that can’t be changed within the paint method. Something on the component borders. And it looks like empty opaque components always are white.
The problem could be solved if there is a way how i can change the default opaque color to the background color.

I would dig here:

My guess is, the clip rectangle uses truncated integers, clipping too much for the child rect.

For a test, I would rewrite this into:

            if (child.affineTransform != nullptr)
            {
                g.saveState();

                if ((child.flags.dontClipGraphicsFlag && ! g.isClipEmpty()) ||
                    g.reduceClipRegion (child.getBounds()
                                        .toFloat()
                                        .transformedBy (*child.affineTransform)
                                        .getSmallestIntegerContainer()))
                {
                    g.addTransform (*child.affineTransform);
                    child.paintWithinParentContext (g);
                }

                g.restoreState();
            }

but I might shooting in the wrong direction here, I rarely (never so far) use affineTransform on the whole component…

The changes make a lot of sense for me, but i had the same borders. I will make some more tests… any idea is welcome.

I think it’s some kind of unwanted anti-aliasing with the white background colour of the opaque component that creates this artefacts on the border:

This makes it almost impossible to use opaque components if you want to have a scalable plugin. Is there something i can do to avoid this? Any help is welcome! @juceteam

Instead of fillAll use

fillRect (getLocalBounds ().toFloat () );

Does that work for you?

No, that didn’t help. I believe @daniel pointed to the right direction. It seems that the graphic context does not fit the component bounds when scaling with affine transform. I think it’s a float / int issue.

Yeah, no surprises.

The reason is actually quite complicated and subtle though… Yes, partly float/int rounding, but even if all coordinates and clipping was completely floating point, then the way that graphics engines perform sub-pixel anti-aliasing will still cause artefacts if you draw two adjacent rectangles - if you imagine splitting a pixel halfway, so each half is drawn with an alpha of 0.5, and then you alpha-composite two 0.5-level pixels, then the result is actually 0.75 rather than 1.0, so is lighter. It’s just a side-effect of the maths, and the only way to avoid it is for an engine to render using no anti-aliasing at a much higher resolution, and then down-sample the result.

You’ll get the same visual artifacts if you’re rendering a bitmap that is within a scaled component.

We routinely have the problem that bitmaps have these extra “garbage” pixels on the right and bottom and ONLY if we attach an OpenGL context to speed up the UI rendering. If we don’t attach the context, the problem disappears.

Another work-around we found is making the source bitmap about 1.5x larger than the target resolution. At least then it’s less visible with our bitmaps and colors.

Could it be that in this particular case the Component is bitmap-cached?

The opposite works:

            g.saveState();

            if ((child.flags.dontClipGraphicsFlag && ! g.isClipEmpty()) ||
                g.reduceClipRegion (child.getBounds()
                                    .toFloat()
                                    .transformedBy (*child.affineTransform)
                                    .getBiggestIntegerContainer()))
            {
                g.addTransform (*child.affineTransform);
                child.paintWithinParentContext (g);
            }

            g.restoreState();

edit: it didn’t work.

@reFX no cache involved.

Then I can only suggest to not use setOpaque (true) and live with performance degradation.

I’m having a problem with it, because the opaque background color is always white. If i could choose a color while set opaque to true, i could remove the artefacts in my case.

Otherwise it works only if you have a white background. I will have another look at it. In the worst case i have to patch if no one have a solution. Any input or ideas are welcome!

A simple fix would be to just not make the child opaque, and let the parent fill the background. If the parent’s paint method is slow, then add logic to it so it avoids wasting time calculating or trying to paint the occluded areas.

But really, like I said above, unless the rasteriser uses super-sampling (and I’m not sure whether many of those are even available - even engines like Skia don’t do it) then it’s just not practical to render adjacent blocks without sub-pixel artefacts. Tom and I were mulling over how difficult it’d be to write a juce renderer that does tiling + supersampling rather than normal anti-aliasing, and may experiment with it if we have time next year, but no promises.

2 Likes

Thanks for the answer. I will do most of the things without opaque and optimise the paint methods now. Rendering shadows and other complex drawings into bitmap overlays already helped a lot.

It is possible to get rid of the opaque artefacts on the border with a little hack if you have a solid background colour in the parent component like in the example above:

setPaintingIsUnclipped( true );
setOpaque( true );

// in the paint method
...
g.setColour(findColour(parentBackgroundColourId));
g.fillRect(-1, -1, getWidth()+2, getHeight()+2);
...

setPaintingIsUnclipped makes it possible to draw behind the component bounds.

I’m pretty happy with the anti aliasing at the moment. There are only a few edge cases that show some visible artefacts.

Interesting read. Just ran into the same problem.
About your workaround: Just be aware of this: if a component is setPaintingIsUnclipped(), it will always get repainted whenever its child is repainted!
So you might see worse UI performance.
See also this thread:

1 Like

Thanks for the additional information. I only use it for child components that haven’t any children. How did you solve the issue?

Well, I didn’t.
As you, I am only using this on components, which have no children.

1 Like