13
votes

I want to do a rounded rectangle outline on an NSImage and I figured that using NSBezierPath would be the best way. However, I ran into a problem: instead of drawing a nice curve, I get this:

enter image description here

For reasons I can't understand, NSBezierPath is drawing the rounded part with a darker color than the rest.

Here's the code I'm using (inside a drawRect: call on a custom view):

NSBezierPath* bp = [NSBezierPath bezierPathWithRoundedRect: self.bounds xRadius: 5 yRadius: 5];
[[[NSColor blackColor] colorWithAlphaComponent: 0.5] setStroke];
[bp stroke];

Any ideas?

Edit:

If I inset the path by 0.5 everything draws just fine. But why is it that I get this when I offset the path by 10 pixels (for example)?

enter image description here

If I understand correctly, it should draw a thin line as well...

2
The key is merely to inset by at least 0.5. That pulls you away from the edges of your clipping rect. You can go further, and get further away from the edge, but it won't have any additional effect.Wade Tregaskis
I know, but it seems that if I inset more than 0.5 I get a thicker line, and I don't understand why. It should draw exactly like when insetting by 0.5.Alex
Same reason as the answer explained - when you inset by 10.0, you're straddling to rows (or columns) of pixels, so you get a two-pixel wide grey line, instead of a one-pixel wide black line. Try insetting by 9.5, or 10.5, to see the difference.Wade Tregaskis

2 Answers

55
votes

Many rendering systems are derived from the PostScript drawing model. Core Graphics is one of these derivative systems. (Here are some others: PDF, SVG, the HTML Canvas 2D Context, Cairo.)

All of these systems have the idea of stroking a path with a line of some fixed width. When you stroke the path, the line straddles the path: half of the line's width is on one side of the path, and half of the line's width is on the other side. Here's a diagram that may make this clearer:

path stroke

Now, what happens when you stroke a path that lies along the boundary of your view? Half of the stroke will fall outside of your view's bounds and be clipped away - not drawn. You will only see the half of the stroke that falls inside the view's bounds.

When you use a rounded corner, that corner pulls away from the view's boundary, toward its center, so more of the stroke around the corner falls inside the view's boundary. So the stroke appears to get thicker around the rounded corner, like this:

path stroke clipped

To fix this, you need to inset your path by half the line width, so that the entire stroke falls inside your view's bounds along the entire path. The default line width is 1.0, so:

NSBezierPath* bp = [NSBezierPath bezierPathWithRoundedRect:
    NSRectInset(self.bounds, 0.5, 0.5) xRadius:5 yRadius:5];
0
votes

In iOS field, just minus the radius of the circle to prevent from being clipped.

UIBezierPath *roundPath = [UIBezierPath bezierPath];
[roundPath addArcWithCenter:
CGPointMake(self.frame.size.width / 2, self.frame.size.height / 2)
 radius:(self.frame.size.width / 2 - 0.5) 
startAngle:M_PI_2 endAngle:M_PI * 3 / 2.f clockwise:YES];