Convert CGPathRef to NSBezierPath

1.1k views Asked by At

In Apple docs they give you code of how to convert NSBezierPath to CGPathRef. I need to convert the other way around, from CGPathRef to NSBezierPath. UIBezierPath has a property called cgPath so if I was working on iPhone that would not be a problem, but I'm working on MacOS.

This must be an old question, and I was sure to find an answer on Internet but no luck. Could be I'm missing something. Any help appreciated.

1

There are 1 answers

0
Jon On BEST ANSWER

Old question but I'm sure this will still be helpful for others. (You didn't specify Objective-C or Swift; this is an Objective-C answer.)

You can convert a CGPathRef to an NSBezierPath using CGPathApply() with an applier function callback that translates the CGPathRef points to NSBezierPath points. The only tricky part is the conversion from CGPathRef's quadratic curves to NSBezierPath's cubic curves but there's an equation for that:

Any quadratic spline can be expressed as a cubic (where the cubic term is zero). The end points of the cubic will be the same as the quadratic's.

 CP0 = QP0
 CP3 = QP2 

The two control points for the cubic are:

 CP1 = QP0 + 2/3 * (QP1-QP0)
 CP2 = QP2 + 2/3 * (QP1-QP2)

... There is a slight error introduced due to rounding, but it is usually not noticeable.

Using the equation above, here's an NSBezierPath category for converting from CGPathRef:

NSBezierPath+BezierPathWithCGPath.h

@interface NSBezierPath (BezierPathWithCGPath)
+ (NSBezierPath *)JNS_bezierPathWithCGPath:(CGPathRef)cgPath; //prefixed as Apple may add bezierPathWithCGPath: method someday
@end

NSBezierPath+BezierPathWithCGPath.m

static void CGPathToBezierPathApplierFunction(void *info, const CGPathElement *element) {
    NSBezierPath *bezierPath = (__bridge NSBezierPath *)info;
    CGPoint *points = element->points;
    switch(element->type) {
        case kCGPathElementMoveToPoint: [bezierPath moveToPoint:points[0]]; break;
        case kCGPathElementAddLineToPoint: [bezierPath lineToPoint:points[0]]; break;
        case kCGPathElementAddQuadCurveToPoint: {
            NSPoint qp0 = bezierPath.currentPoint, qp1 = points[0], qp2 = points[1], cp1, cp2;
            CGFloat m = (2.0 / 3.0);
            cp1.x = (qp0.x + ((qp1.x - qp0.x) * m));
            cp1.y = (qp0.y + ((qp1.y - qp0.y) * m));
            cp2.x = (qp2.x + ((qp1.x - qp2.x) * m));
            cp2.y = (qp2.y + ((qp1.y - qp2.y) * m));
            [bezierPath curveToPoint:qp2 controlPoint1:cp1 controlPoint2:cp2];
            break;
        }
        case kCGPathElementAddCurveToPoint: [bezierPath curveToPoint:points[2] controlPoint1:points[0] controlPoint2:points[1]]; break;
        case kCGPathElementCloseSubpath: [bezierPath closePath]; break;
    }
}

@implementation NSBezierPath (BezierPathWithCGPath)
+ (NSBezierPath *)JNS_bezierPathWithCGPath:(CGPathRef)cgPath {
    NSBezierPath *bezierPath = [NSBezierPath bezierPath];
    CGPathApply(cgPath, (__bridge void *)bezierPath, CGPathToBezierPathApplierFunction);
    return bezierPath;
}
@end

Called like so:

//...get cgPath (CGPathRef) from somewhere
NSBezierPath *bezierPath = [NSBezierPath JNS_bezierPathWithCGPath:cgPath];