macosnsbezierpathcgpathref

Convert CGPathRef to NSBezierPath


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.


Solution

  • 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];