开发者

Why is the join not rounded when the line doubles back on itself?

开发者 https://www.devze.com 2023-03-03 01:48 出处:网络
I have the following code: - (void)drawRect:(CGRect)rect { CGContextRef c = UIGraphicsGetCurrentContext();

I have the following code:

- (void)drawRect:(CGRect)rect {
    CGContextRef c = UIGraphicsGetCurrentContext();

    CGContextSe开发者_Go百科tFillColorWithColor(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:

Why is the join not rounded when the line doubles back on itself?

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);

Why is the join not rounded when the line doubles back on itself?

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.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号