I have the following code:
- (void)drawRect:(CGRect)rect {
CGContextRef c = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(c, [UIColor blackColor].CGColor);
CGContextFillRect(c, rect);
CGContextSetLineJoin(c, kCGLineJoinRound);
CGContextSetLineCap(c, kCGLineCapRound);
CGContextSetLineWidth(c, 50.0);
CGContextSetStrokeColorWithColor(c, [UIColor redColor].CGColor);
CGContextBeginPath(c);
CGContextMoveToPoint(c, 60, 60);
CGContextAddLineToPoint(c, 60, 250);
CGContextAddLineToPoint(c, 60, 249);
CGContextStrokePath(c);
CGContextSetStrokeColorWithColor(c, [UIColor blueColor].CGColor);
CGContextBeginPath(c);
CGContextMoveToPoint(c, 160, 60);
CGContextAddLineToPoint(c, 160, 250);
CGContextAddLineToPoint(c, 160.01, 249);
CGContextStrokePath(c);
}
This generates the following output:
Is there a good reason that the red shape's bottom edge is not rounded? Or is it a bug in Core Graphics when the line exactly doubles back on itself?
It's definitely a bug. If you try adding another line to the path, you can see how Core Graphics is unable to handle it.
CGContextMoveToPoint(c, 60.0, 60.0);
CGContextAddLineToPoint(c, 60.0, 250.0);
CGContextAddLineToPoint(c, 60.0, 249.0);
CGContextAddLineToPoint(c, 60.0, 250.0);
It's as if the masking that creates the rounded caps and joins gets inverted when it's doubled.
mortenfast proved this is a bug. But I'll post this answer to offer my workaround.
A workaround is to detect this case and add a very short line segment perpendicular to the existing line, something like this:
- (void)addPtToPath:(CGPoint)newPt {
// CoreGraphics seems to have a bug if a path doubles back on itself.
// Detect that and apply a workaround.
CGPoint curPt = CGPathGetCurrentPoint(self.currentPath);
if (!CGPointEqualToPoint(newPt, curPt)) {
CGFloat slope1 = (curPt.y - prevPt.y) / (curPt.x - prevPt.x);
CGFloat slope2 = (curPt.y - newPt.y) / (curPt.x - newPt.x);
CGFloat diff;
BOOL between;
if (isinf(slope1) && isinf(slope2)) {
// Special-case vertical lines
diff = 0;
between = ((prevPt.y < curPt.y) != (curPt.y < newPt.y));
} else {
diff = slope1 - slope2;
between = ((prevPt.x < curPt.x) != (curPt.x < newPt.x));
}
if (between && diff > -0.1 && diff < 0.1) {
//NSLog(@"Hack alert! (%g,%g) (%g,%g) (%g,%g) => %g %g => %g", prevPt.x, prevPt.y, curPt.x, curPt.y, newPt.x, newPt.y, slope1, slope2, diff);
if (isinf(slope1)) {
curPt.x += 0.1;
} else if (slope1 == 0) {
curPt.y += 0.1;
} else if (slope1 < -1 || slope1 > 1) {
curPt.x += 0.1; curPt.y -= 0.1 / slope1;
} else {
curPt.x -= 0.1 * slope1; curPt.y += 0.1;
}
CGPathAddLineToPoint(self.currentPath, NULL, curPt.x, curPt.y);
}
prevPt = curPt;
}
CGPathAddLineToPoint(self.currentPath, NULL, newPt.x, newPt.y);
}
This needs one ivar named prevPt
, and operates on the path in the ivar currentPath
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With