How can i convert NSBezierPath to CGPath

cocoa - How can i convert NSBezierPath to CGPath - Stack Overflow

How can i convert NSBezierPath to CGPath

Ask Question

Asked 13 years, 3 months ago

Modified 2 years, 1 month ago

Viewed 20k times

46

How can i convert between NSBezierPath to CGPath.

Thanks.

Share

Improve this question

Follow

edited May 25, 2016 at 4:15

Andriy

2,76722 gold badges2020 silver badges2929 bronze badges

asked Nov 29, 2009 at 13:38

vikas

81711 gold badge99 silver badges1919 bronze badges

  • 5

    I don't see how this is a duplicate of that question. That's asking about how to stuff a CGPath into an archive or otherwise serialize it; this is about converting from one kind of path object to another, without necessarily serializing from one and deserializing to the other. 

    – Peter Hosey

     Feb 10, 2013 at 1:34
  • I like this one, a library that adds UIKit functionality to the MacOS. Once included, you can set and read the CGPath for a NSBezierPath github.com/iccir/XUIKit 

    – Wizard of Kneup

     Dec 20, 2013 at 8:14

Add a comment

7 Answers

Sorted by:

                                              Highest score (default)                                                                   Trending (recent votes count more)                                                                   Date modified (newest first)                                                                   Date created (oldest first)                              

42

Right from Apple documentation: Creating a CGPathRef From an NSBezierPath Object

Here is the relevant code.

@implementation NSBezierPath (BezierPathQuartzUtilities)
// This method works only in OS X v10.2 and later.
- (CGPathRef)quartzPath
{
    int i, numElements;

    // Need to begin a path here.
    CGPathRef           immutablePath = NULL;

    // Then draw the path elements.
    numElements = [self elementCount];
    if (numElements > 0)
    {
        CGMutablePathRef    path = CGPathCreateMutable();
        NSPoint             points[3];
        BOOL                didClosePath = YES;

        for (i = 0; i < numElements; i++)
        {
            switch ([self elementAtIndex:i associatedPoints:points])
            {
                case NSMoveToBezierPathElement:
                    CGPathMoveToPoint(path, NULL, points[0].x, points[0].y);
                    break;

                case NSLineToBezierPathElement:
                    CGPathAddLineToPoint(path, NULL, points[0].x, points[0].y);
                    didClosePath = NO;
                    break;

                case NSCurveToBezierPathElement:
                    CGPathAddCurveToPoint(path, NULL, points[0].x, points[0].y,
                                        points[1].x, points[1].y,
                                        points[2].x, points[2].y);
                    didClosePath = NO;
                    break;

                case NSClosePathBezierPathElement:
                    CGPathCloseSubpath(path);
                    didClosePath = YES;
                    break;
            }
        }

        // Be sure the path is closed or Quartz may not do valid hit detection.
        if (!didClosePath)
            CGPathCloseSubpath(path);

        immutablePath = CGPathCreateCopy(path);
        CGPathRelease(path);
    }

    return immutablePath;
}
@end

Bug Reporter

rdar://15758302: NSBezierPath to CGPath.

Share

Improve this answer

Follow

edited Feb 28, 2018 at 9:40

catlan

25k88 gold badges6868 silver badges7777 bronze badges

answered Dec 24, 2009 at 0:03

0xced

24.7k1010 gold badges107107 silver badges252252 bronze badges

  • 39

    I think it's quite amazing that Apple puts that code in the docs instead of just adding a CGPath accessor to NSBezierPath

    – user187676

     Mar 5, 2016 at 10:20

Add a comment

33

The syntax in Xcode 8 GM has been further simplified, code modified from rob-mayoff's answer above. Using this and a helper for addLine(to point: CGPoint) I am sharing drawing code cross platform.

extension NSBezierPath {

    public var cgPath: CGPath {
        let path = CGMutablePath()
        var points = [CGPoint](repeating: .zero, count: 3)

        for i in 0 ..< elementCount {
            let type = element(at: i, associatedPoints: &points)
            switch type {
            case .moveTo:
                path.move(to: points[0])
            case .lineTo:
                path.addLine(to: points[0])
            case .curveTo:
                path.addCurve(to: points[2], control1: points[0], control2: points[1])
            case .closePath:
                path.closeSubpath()
            @unknown default:
                continue
            }
        }

        return path
    }
}

Share

Improve this answer

Follow

edited Feb 4, 2021 at 8:36

CommunityBot

111 silver badge

answered Sep 8, 2016 at 7:35

Henrik

3,8382626 silver badges4848 bronze badges

Add a comment

19

This works in Swift 3.1 and later:

import AppKit

public extension NSBezierPath {

    public var cgPath: CGPath {
        let path = CGMutablePath()
        var points = [CGPoint](repeating: .zero, count: 3)
        for i in 0 ..< self.elementCount {
            let type = self.element(at: i, associatedPoints: &points)
            switch type {
            case .moveToBezierPathElement: path.move(to: points[0])
            case .lineToBezierPathElement: path.addLine(to: points[0])
            case .curveToBezierPathElement: path.addCurve(to: points[2], control1: points[0], control2: points[1])
            case .closePathBezierPathElement: path.closeSubpath()
            }
        }
        return path
    }

}

Share

Improve this answer

Follow

edited Mar 31, 2018 at 21:18

answered Aug 9, 2016 at 21:15

rob mayoff

370k6767 gold badges778778 silver badges840840 bronze badges

  • There are no such things as moveTo or addLineTo in Swift 3, are they? 

    – El Tomato

     Mar 18, 2017 at 23:28
  • This would be nicer as a convenience initializer on CGPath, so one could use the more-standard CGPath(from: bezierPath) syntax 

    – Ky -

     Dec 4, 2017 at 16:27
  • 1

    NSColor has a cgColor property, NSGraphicsContext has a cgContext property, and NSColorSpace has a cgColorSpace property. NSImage has ` cgImage(forProposedRect:context:hints:)` method. 

    – rob mayoff

     Dec 4, 2017 at 17:42 
  • Also, Swift does not support convenience initializers on CGPath. If you try to declare one, you get the error “Convenience initializers are not supported in extensions of CF types”. 

    – rob mayoff

     May 29, 2019 at 21:20

Add a comment

17

For macOS better use - CGMutablePath

But, if you want cgPath for NSBezierPath:

Swift 5.0

extension NSBezierPath {

  var cgPath: CGPath {
    let path = CGMutablePath()
    var points = [CGPoint](repeating: .zero, count: 3)
    for i in 0 ..< self.elementCount {
      let type = self.element(at: i, associatedPoints: &points)

      switch type {
      case .moveTo:
        path.move(to: points[0])

      case .lineTo:
        path.addLine(to: points[0])

      case .curveTo:
        path.addCurve(to: points[2], control1: points[0], control2: points[1])

      case .closePath:
        path.closeSubpath()

      @unknown default:
        break
      }
    }
    return path
  }
}

Share

Improve this answer

Follow

edited May 6, 2019 at 14:27

answered May 6, 2019 at 13:58

Yura Voevodin

41366 silver badges1010 bronze badges

Add a comment

7

Here is a Swift version if anyone else finds a need for it:

extension IXBezierPath {
// Adapted from : https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CocoaDrawingGuide/Paths/Paths.html#//apple_ref/doc/uid/TP40003290-CH206-SW2
// See also: http://www.dreamincode.net/forums/topic/370959-nsbezierpath-to-cgpathref-in-swift/
func CGPath(forceClose forceClose:Bool) -> CGPathRef? {
    var cgPath:CGPathRef? = nil

    let numElements = self.elementCount
    if numElements > 0 {
        let newPath = CGPathCreateMutable()
        let points = NSPointArray.alloc(3)
        var bDidClosePath:Bool = true

        for i in 0 ..< numElements {

            switch elementAtIndex(i, associatedPoints:points) {

            case NSBezierPathElement.MoveToBezierPathElement:
                CGPathMoveToPoint(newPath, nil, points[0].x, points[0].y )

            case NSBezierPathElement.LineToBezierPathElement:
                CGPathAddLineToPoint(newPath, nil, points[0].x, points[0].y )
                bDidClosePath = false

            case NSBezierPathElement.CurveToBezierPathElement:
                CGPathAddCurveToPoint(newPath, nil, points[0].x, points[0].y, points[1].x, points[1].y, points[2].x, points[2].y )
                bDidClosePath = false

            case NSBezierPathElement.ClosePathBezierPathElement:
                CGPathCloseSubpath(newPath)
                bDidClosePath = true
            }

            if forceClose && !bDidClosePath {
                CGPathCloseSubpath(newPath)
            }
        }
        cgPath = CGPathCreateCopy(newPath)
    }
    return cgPath
}

Share

Improve this answer

Follow

answered Jan 6, 2016 at 14:57

Cirec Beback

6931010 silver badges1616 bronze badges

Add a comment

4

I can't figure out why the accepted answer adds some sophisticated close-path logic (maybe it is needed under some circumstances), but for those who just need a pristine conversion of the path, here is cleaned version of that code, implemented as a regular method:

- (CGMutablePathRef)CGPathFromPath:(NSBezierPath *)path
{
    CGMutablePathRef cgPath = CGPathCreateMutable();
    NSInteger n = [path elementCount];

    for (NSInteger i = 0; i < n; i++) {
        NSPoint ps[3];
        switch ([path elementAtIndex:i associatedPoints:ps]) {
            case NSMoveToBezierPathElement: {
                CGPathMoveToPoint(cgPath, NULL, ps[0].x, ps[0].y);
                break;
            }
            case NSLineToBezierPathElement: {
                CGPathAddLineToPoint(cgPath, NULL, ps[0].x, ps[0].y);
                break;
            }
            case NSCurveToBezierPathElement: {
                CGPathAddCurveToPoint(cgPath, NULL, ps[0].x, ps[0].y, ps[1].x, ps[1].y, ps[2].x, ps[2].y);
                break;
            }
            case NSClosePathBezierPathElement: {
                CGPathCloseSubpath(cgPath);
                break;
            }
            default: NSAssert(0, @"Invalid NSBezierPathElement");
        }
    }
    return cgPath;
}

Btw, I needed this to implement "NSBezierPath stroke contains point" method.

I've looked for this conversion to call CGPathCreateCopyByStrokingPath(), that converts NSBezierPath stroke outline to regular path, so you can test hits on strokes too, and here is the solution:

// stroke (0,0) to (10,0) width 5 --> rect (0, -2.5) (10 x 5)
NSBezierPath *path = [[NSBezierPath alloc] init];
[path moveToPoint:NSMakePoint(0.0, 0.0)];
[path lineToPoint:NSMakePoint(10.0, 0.0)];
[path setLineWidth:5.0];

CGMutablePathRef cgPath = [self CGPathFromPath:path];
CGPathRef strokePath = CGPathCreateCopyByStrokingPath(cgPath, NULL, [path lineWidth], [path lineCapStyle],
                                                      [path lineJoinStyle], [path miterLimit]);
CGPathRelease(cgPath);

NSLog(@"%@", NSStringFromRect(NSRectFromCGRect(CGPathGetBoundingBox(strokePath))));
// {
   
   {0, -2.5}, {10, 5}}

CGPoint point = CGPointMake(1.0, 1.0);
BOOL hit = CGPathContainsPoint(strokePath, NULL, point, (bool)[path windingRule]);

NSLog(@"%@: %@", NSStringFromPoint(NSPointFromCGPoint(point)), (hit ? @"yes" : @"no"));
// {1, 1}: yes

CGPathRelease(strokePath);

This is similar to QPainterPathStroker from Qt, but for NSBezierPath.

Share

Improve this answer

Follow

answered Feb 7, 2017 at 15:46

user3125367

2,89011 gold badge1616 silver badges1717 bronze badges

Add a comment

2

c# Xamarin

    private CGPath convertNSBezierPathToCGPath(NSBezierPath sourcePath)
    {
        CGPath destinationPath = new CGPath();

        int i, numElements;

        // Then draw the path elements.
        numElements = (int)Convert.ToInt64(sourcePath.ElementCount);

        if (numElements > 0)
        {

            CGPath path = new CGPath();
            CGPoint[] points;
            bool didClosePath = true;

            for (i = 0; i < numElements; i++)
            {
                switch (sourcePath.ElementAt(i, out points))
                {
                    case NSBezierPathElement.MoveTo:
                        path.MoveToPoint(points[0]);
                        break;


                    case NSBezierPathElement.LineTo:
                        path.MoveToPoint(points[0]);
                        didClosePath = false;
                        break;

                    case NSBezierPathElement.CurveTo:
                        path.AddCurveToPoint(cp1: points[0], cp2: points[1], points[2]);
                        didClosePath = false;
                        break;

                    case NSBezierPathElement.ClosePath:
                        path.CloseSubpath();
                        didClosePath = true;
                        break;
                }
            }

            if (!didClosePath)
                path.CloseSubpath();

            destinationPath = new CGPath(path);

        }

        return destinationPath;

}

Share

Improve this answer

Follow

answered Jun 2, 2020 at 21:44

alexNeutral

2122 bronze badges

Add a comment

猜你喜欢

转载自blog.csdn.net/weixin_42610770/article/details/129604822