White beats black

I have code for a basic component that uses a path to accomplish rounded corners. I fill the path and then stroke the path for a border. If I fill the path with white and stroke the path with black, the black appears to get consumed by the white. Zooming in on an image of it shows that the border turns light grey, except for the corners which remain black. Is this an anti-aliasing effect or a defect of some sort? Is there a specific way to stroke a colour that will not be affected by the colour next to it?

Path Code:

[code] Path sp;
int pathWidth = getWidth();
int pathHeight = getHeight();

sp.startNewSubPath (topLeftCornerSize, 0); 
sp.lineTo (pathWidth-topRightCornerSize, 0);

if (topRightCornerSize > 0) {
	sp.addArc(pathWidth-topRightCornerSize, 0, topRightCornerSize, topRightCornerSize, 2*double_Pi, 2.5*double_Pi, false);
}

sp.lineTo(pathWidth,pathHeight-bottomRightCornerSize);

if (bottomRightCornerSize > 0) {
	sp.addArc(pathWidth-bottomRightCornerSize, pathHeight-bottomRightCornerSize, bottomRightCornerSize, bottomRightCornerSize, 2.5*double_Pi, 3*double_Pi, false);
}	

sp.lineTo(bottomLeftCornerSize,pathHeight);

if (bottomLeftCornerSize > 0) {
	sp.addArc(0, pathHeight-bottomLeftCornerSize, bottomLeftCornerSize, bottomLeftCornerSize, 3*double_Pi, 3.5*double_Pi, false);
}		

sp.lineTo(0, topLeftCornerSize);

if (topLeftCornerSize > 0) {
	sp.addArc(0, 0, topLeftCornerSize, topLeftCornerSize, 3.5*double_Pi, 4*double_Pi, false);
}		

sp.closeSubPath();
g.fillPath(sp);

	Colour borderColour = Colour(0,0,0);
	g.setColour(borderColour);
	g.strokePath(sp, PathStrokeType::curved);

[/code]

Keep in mind that a stroke is centered on the coordinates. A 1 pixel stroke of a rectangle with a corner at (0,0) will cover the area from (-0.5,-0.5) to (0.5,0.5). So yes, this is anti-aliasing at work.

A screenshot of the zoomed in pixels would be handy.

hmmm, yeah I remember reading about that .5 business. Here’s a shot of it.

[attachment=0]cornerwhitezoom.png[/attachment]

Oh hmm…if you want your component to have a 1 pixel stroked path, then your path needs to have this bounding box:

topLeft = (0.5, 0.5) in Component coordinates
width = getWidth() - 1
height = getHeight() - 1

It seems that what is happening is that half of your stroke lies outside the Component coordinates and gets clipped out. If you want a 1 pixel stroke, then half of that pixel is going to be to the left of your coordinate (0.5 minus 0.5) and the other half to the right (0.5 plus 0.5) which will fill the column from x=0 to x=1. Right now it looks like you are filling from x=-0.5 to x=0.5, which is half way outside the Component bounds.

I’m also a little confused about why you are making a rounded rectangle yourself, why not just let Juce do it?

Thanks for the insight Vin. I use a custom path so I can easily specify distinct corner sizes for a component.
e.g.

//set the comp's top corners to 10, and bottom to 5
comp->setRoundedCorners(10,10,5,5);

I changed the code to use .5 and it appears to fix the issue (top of white), although it seems I have some funkiness to sort out on the side. Probably just need to fix my path adjustments.

[attachment=0]newcornerwhitezoom.png[/attachment]

A different way to do it would be to use the even/odd versus non-zero winding rule (I forget which is which but I’m guessing you want even/odd). Make a path that has a clockwise outer rounded rect created by Juce with a specified corner radius, then a counter-clockwise inner rounded rect with another specified corner radius. You might have to make a copy of the Juce code to produce a counter-clockwise rounded rect.

Then, set the winding rule on the path to “subtract out” the inner rectangle.

Now that you have done this, instead of stroking the path you can just fill the path to produce the border and then inset the path by an exact number of pixels, then fill it again to produce the interior.

I’m not certain we’re talking about the same thing. I mean distinct corner sizes like so:

//round the top left corner
comp->setRoundedCorners(10,0,0,0);

[attachment=0]singlecorner.png[/attachment]

It sounds like what you are referring to is drawing a border with a certain thickness so the corners of the border are made smaller on the inner side of it? If so that is handy information anyway as I’m planning to improve my border drawing to do that sort of thing.

Yes, this is what I thought you meant, and what I was referring to. Still, the technique of using the even/odd winding rule holds. You will just need to resort to drawing your own rectangle, as in your code example.

Yeah cool, thanks. I wasn’t familiar with winding rules so I read up on the even/odd winding business and I’ve put together the following code that draws a border using fills. The border has an outer edge, a body and an inner edge. I didn’t seem to need to use counter clockwise drawing, but flipping the non zero winding was essential.

    //I should do better checking here for border size.
	if (borderSize > 0) {

		sp.setUsingNonZeroWinding(false);
		
		Path outerEdge(sp);
		Path borderBody(sp);
		Path innerEdge(sp);
		
		int adjustment = 1;
		outerEdge.scaleToFit(adjustment, adjustment, getWidth()-adjustment*2, getHeight()-adjustment*2, false);
		
		adjustment = borderSize-1;
		borderBody.scaleToFit(adjustment, adjustment, getWidth()-adjustment*2, getHeight()-adjustment*2, false);
		
		adjustment = borderSize;
		innerEdge.scaleToFit(adjustment, adjustment, getWidth()-adjustment*2, getHeight()-adjustment*2, false);
		
		sp.addPath(outerEdge);
		outerEdge.addPath(borderBody);
		borderBody.addPath(innerEdge);
		
           //Ideally the paths would be setup so the fillPath commands would be stated more logically, i.e. sp would be outerEdge, outerEdge would be borderBody..etc  but whatever..
		g.setColour(Colours::black);
		g.fillPath(sp);

		g.setColour(this->borderColour);
		g.fillPath(outerEdge);
		
		g.setColour(Colours::black);
		g.fillPath(borderBody);
		
	}

A couple examples of borders (just the outer black, light grey, black - 3 pixels). The corners when viewed at regular size look like they have extra black fuzz on them. It would be nice to have them be smooth. Not sure if that’s fixable but otherwise the borders seem to be on the right track.

[attachment=0]borders.png[/attachment]

Ah yeah okay, the direction of drawing is irrelevant, just the “winding” number determined by counting crossings. Hey, its been 18 years since I fooled around with these things.

But, back to the point, your image looks great! I don’t see the problem…

This is how a corner looks when not zoomed in. It’s definitely fairly minor and it’s probably just the reality of anti-aliasing but it is noticeably fuzzy. My app is designed to run full screen and has a darker theme going so the “defect” isn’t as noticeable when viewing here. You may have to look closely to see it.

[attachment=0]fuzzycorner.png[/attachment]

Darnit, you had to go and show me that. Well, it does look like something is wrong there. Its the anti aliasing I believe. Try adding or subtracting 0.5 from the corner radius.

If I saw that in my app it would bug the shit out of me.

I think this will fix the presentation of your component:

[attachment=0]subpixels.gif[/attachment]

At higher resolutions (i.e. greater thickness) it would look fine but when you start trying to stroke paths with a 1 pixel thickness that’s when a little bit of hand-tuning is required.

haha, yeah sorry about that. But I agree it’s gotta look right. I moved the left corner inward .5 as noted and that helped, so that’s cool, but I brightened up the colour on the inside to better reveal the inner edge and both sides are a mess. I’m guessing I’ll have to either somehow tweak the scaling of the path, or not use the scale method and just manually redraw. I’ll post something if I get it looking right.

The left corner is 19.5 px (outer edge), the right side is 20. I should probably post these without the speckled background but I gotta get some sleep now. I’ll post something up tomorrow maybe with some further tweaks and bright and smooth backgrounds. Thanks again for your insight on things.

[attachment=1]leftcornerpoint5.png[/attachment]

[attachment=0]leftcornerpoint5zoom.png[/attachment]

Try using just the path for the white frame. Stroke it in black, 3 px thickness, then stroke it again in white, 1 px thickness.