Graphics drawing artifacts

Hello, I have a weird behaviour using the Graphics class that I had not before.

When I use this code :

class BoxComponent : public Component {
public:
	void paint(Graphics& g) override {
		g.setColour(Colours::red);
		g.fillRect(getLocalBounds());
		g.setColour(Colours::green);
		g.drawRect(getLocalBounds());
	}
};
//Parent resize
void MainComponent::resized() {
	const int boxW = 100;
	const int boxH = 30;
	const auto centre = getLocalBounds().getCentre();
	Rectangle<int> bounds(boxW * _boxs.size(), boxH);
	bounds.setCentre(centre);

	for (const auto& box : _boxs) {
		const auto bBox = bounds.removeFromLeft(boxW);
		box->setBounds(bBox);
	}
}

I expect to have this result (well aligned rectangles):

But I actually get this :


The green isn’t the same, but it’s not the point. The point is the drawRect seems to draw with float coordinates when I give it int ones

Sorry, I just discovered (because I never use it) that drawRect behaves differently from the rest of the stroke functions… I’m not sure what’s happening there, but my guess is that there’s some scaling being applied, so the 1 px stroke ends up not being 1 px in the end. This is still equivalent and faster:

g.setColour (Colours::green);
g.fillRect (getLocalBounds());
g.setColour (Colours::red);
g.fillRect (getLocalBounds().reduced (1));

a post about it:

Thank you for your answers !

@Kamedin : The use case I show here is a simplification of what I actually need to draw. In my original code I don’t use drawRect, I use drawVerticalLine at the end of my object, and I have the same issue of incorrect positions. And my line color use transparancy so, it stacks up constraints. So I would like to understand what’s going on here. Especially since that’s a new behaviour. I remember doing similar things with no problems before.

@lalala
Based on your link, a work around could have been :

Path p;
p.addRectangle(getLocalBounds());
g.setColour(Colours::red);
g.fillPath(p);
g.setColour(Colours::green);
g.strokePath(p, PathStrokeType(1.0));

But I get this :


It’s a bit better, but there is still some artifacts

Actually, for strokePath what I had written before revising applies: you need to use

p.addRectangle (getLocalBounds().toFloat().reduced (0.5f));

because the coordinates correspond to the middle of the stroke. That’s not the case for drawRect: “The lines are drawn inside the given rectangle, and greater line thicknesses extend inwards”. It’s not the case for drawVerticalLine either: “The x position is an integer, but the top and bottom of the line can be sub-pixel positions, and these will be anti-aliased if necessary”. So with integer positions and sizes, there shouldn’t be antialiasing. That’s why I suspect there’s scaling involved.

I tried it but it’s worst :sweat_smile:

Path p;
p.addRectangle(getLocalBounds().toFloat().reduced(0.5f));
g.setColour(Colours::red);
g.fillPath(p);
g.setColour(Colours::green);
g.strokePath(p, PathStrokeType(1.0));

is getCentre of the main component returning a float point? You might want to have a wrapper container that is an integer mutiple of your boxes.

Yes getLocalBounds() return a Rectangle<int>, getCentre() returns a Point<int> and in the end, setBounds() only have one version taking a Rectangle<int> so no way to offset a component by a float amount

That’s expected: the reduced path should behave like drawRect. Again, this must be scaling. If it’s Windows, there’s a system-wide scaling which is often active by default in laptops. The interface from JUCE is different for plugins and applications. I don’t know the details for MacOS. One of the links before showed the results for retina and non-retina displays.

Some tests :

==============================
Test 1

g.fillAll(Colours::red);

image
Seems normal

==============================
Test2

g.setColour(Colours::red);
g.fillRect(getLocalBounds());

image
Seems normal

==============================
Test3

Path p;
p.addRectangle(getLocalBounds().toFloat());
g.setColour(Colours::red);
g.fillPath(p);

image
Not what I expect

==============================
Test4

Path p;
p.addRectangle(getLocalBounds().toFloat().reduced(0.5f));
g.setColour(Colours::red);
g.fillPath(p);

image
From this it seems that a rectangle in a path is already reduced, so it stacks to 1 pixel difference.

==============================
Test5

Path p;
p.addRectangle(getLocalBounds().toFloat().expanded(0.5f)); 
g.setColour(Colours::red);
g.fillPath(p);

image
So if we add 0.5 it display wath I want. At least for the fillPath

==============================
Test6

Path p;
p.addRectangle(getLocalBounds().toFloat().expanded(0.5f));
g.setColour(Colours::red);
g.fillPath(p);

Path p2;
p2.addRectangle(getLocalBounds().toFloat());
g.setColour(Colours::green);
g.strokePath(p2, PathStrokeType(1.0));

image
This one seems pretty good, but actually if you look at pixel levels there is a dark rectangle. Something I would expect with this formula :

auto bounds = getLocalBounds();
bounds = bounds.removeFromLeft(1);
bounds = bounds.removeFromRight(1);
g.setColour(Colours::black.withAlpha(0.1f));
g.drawRect(bounds);

==============================
Test7
I tried

Path p;
p.addRectangle(getLocalBounds().toFloat().expanded(0.5f)); 
g.setColour(Colours::red);
g.fillPath(p);

Path p2;
p2.addRectangle(getLocalBounds().toFloat().reduced(0.5f));
g.setColour(Colours::green);
g.strokePath(p2, PathStrokeType(1.0));

image
This one has the blurred lines for the outline that teach us that for drawing rect it should have the 0.5 offset, wich is seems normal to me.

==============================
Conclusion, it seems that g.drawRect behaviour is the same as the one using g.strokePath. It’s consistant, so it’s fine.
Filling a rect from a path should expand it from 0.5f to actually fill the full component which seems strange to me.
But stroking a path does not need the bounds to be expanded wich seems normal, but unfortunately there is artifacts (the dark rectangle that exclude the side of the component)…

1 Like

Ok, I tried.

Maybe @t0m can explain or give a workaround :wink:

The integer-version of fillrect on windows behaves inconsistent to all other functions/platforms.
I discovered this a long time ago.
Maybe all integer drawing functions should be simply marked as deprecated.

3 Likes

Good call, I ran into this on windows and it drove me crazy for a bit until I dove around the forum, found your post and added a toFloat() to my drawRect() that I cared about being “logical pixel perfect.”

Anyway what’s being reported here is different. I don’t know which platform and renderer is the op on, but here on Windows, both on the software and opengl renderers, a 1 px integer drawRect gives the same result as a strokePath with 0.5 px reduction, which is solid, non-antialiased lines, of course only when there’s no scaling. What’s being reported here is visible antialiasing in those cases, so unless there’s a bug in another platform’s implementation, this is just scaling doing its normal thing, as would happen on retina displays. From the images I assume a 150% scale or close, as the 2 px logical boundaries appear as 3.

I’m on Windows, and I used a brand new project to confirm what I noticed on my app. I’m not using OpenGl unless juce is using it by default

On Windows using the software renderer, I run your code on a 400x400 main component with your resize function and these three versions of BoxComponent::paint:

g.setColour (juce::Colours::red);
g.fillRect (getLocalBounds());
g.setColour (juce::Colours::green);
g.drawRect (getLocalBounds());
g.setColour (juce::Colours::red);
g.fillRect (getLocalBounds());
g.setColour (juce::Colours::green);
juce::Path p;
p.addRectangle (getLocalBounds().toFloat().reduced (0.5f));
g.strokePath (p, juce::PathStrokeType (1.0f));
g.setColour (juce::Colours::green);
g.fillRect (getLocalBounds());
g.setColour (juce::Colours::red);
g.fillRect (getLocalBounds().reduced (1));

This is what I get in all cases:
test
You can open that image and see the whole rectangle is 300x30. Your image, otoh, has a 1504x152 rectangle, where each of the apparent pixels is actually 4x4:
test1
Even reducing by 4 leaves us with a 376x38 rectangle, which is (almost) x1.25 with respect to the code. So the image you uploaded has a 125% scale, and then 400% on top of that. I’ve been mentioning scaling since the first answer and you’ve consistently ignored it, so that’s it from me.

I didnt understood what you were talking about mentionning scaling. From the source code I do not apply any scaling (unless there is one from juce I’m not aware of). The only one is when I paste my screenshot in windows paint app to zoom to better see at pixel level.

So with this code :

g.setColour(Colours::red);
g.fillRect(getLocalBounds());

Path p2;
p2.addRectangle(getLocalBounds().toFloat());
g.setColour(Colours::green);
g.strokePath(p2, PathStrokeType(1.0));

A raw screenshot is :

One on paint, with a pixel level zoom x400 :

And a larger one x800 to make it even more obvious :

The issue is this dark rectangle that should not appear, and the size of the green rectangle seems to overlap the start of the next object since it’s only 1px large instead of 2 when there’s two objects.

I get the same thing using different screenshot applications, or replacing Paint app by Gimp

============================================
We were talking about drawRect, but there’s artifacts to draw a line too

g.setColour(Colours::red);
g.fillRect(getLocalBounds());

g.setColour(Colours::green);
g.drawVerticalLine(getWidth()-1, 0, getHeight()); //without -1 the line does not appear at all

I get this (I took a screenshot, then paste it onto paint, then zoom x800 to confirm pixel level):

If I replace the drawVerticalLine by a path :

Path p2;
p2.addRectangle(getLocalBounds().toFloat().removeFromRight(1));
g.setColour(Colours::green);
g.strokePath(p2, PathStrokeType(1.0));

With a 1px large rectangle instead I get a 2 pixel wide drawing + a dark line (??)

I tried the 0.5 pixel fix we discussed previously :

Path p2;
p2.addRectangle(getLocalBounds().toFloat().removeFromRight(0.5));
g.setColour(Colours::green);
g.strokePath(p2, PathStrokeType(1.0));

But I still have a 2 line rectangle. It drives me crazy :sweat_smile:

Just make it a habit to force your pixels coordinates either on (0.0f) or between (0.5f) pixels, depending on what graphic methods you use, before you call them. Simply make sure your code bypasses these “issues”.

You can safely assume that the mapping by those methods will never be changed, given all the existing Juce-based plugins using them.

Be pragmatic - some things are not worth the effort trying to understand them :upside_down_face:

I understand that, so what would you do exactly ? Because none of the try was artifacts free