如何将NSBezierPath转换为CGPath

47

我该如何在NSBezierPathCGPath之间进行转换?

谢谢。


6
我不认为这与那个问题重复。那个问题是关于如何将CGPath塞入归档或以其他方式进行序列化的;而这个问题是关于将一种路径对象转换为另一种路径对象,而不一定需要从一个对象进行序列化,然后再反序列化到另一个对象。 - Peter Hosey
我喜欢这个库,它可以将UIKit功能添加到MacOS中。一旦包含,您就可以为NSBezierPath设置和读取CGPath。 https://github.com/iccir/XUIKit - Wizard of Kneup
8个回答

43

直接引用自苹果的文档:从NSBezierPath对象创建CGPathRef

以下是相关代码。

@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

错误报告者

rdar://15758302: 将 NSBezierPath 转换为 CGPath。


43
我认为苹果公司将那段代码放入文档中,而不是仅在NSBezierPath中添加一个CGPath访问器,这相当惊人。 - user187676

34

Xcode 8 GM 中的语法已经进一步简化,代码修改自上面 rob-mayoff 的回答。使用此方法和 addLine(to point: CGPoint) 的辅助程序,我可以在多个平台上共享绘图代码。

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
    }
}

19

这适用于 Swift 3.1 及更高版本:

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
    }

}

Swift 3 中没有 moveTo 或 addLineTo 这样的东西,对吧? - El Tomato
这个最好作为 CGPath 的便利初始化器,这样就可以使用更标准的 CGPath(from: bezierPath) 语法。 - Ky -
1
NSColor有一个cgColor属性,NSGraphicsContext有一个cgContext属性,NSColorSpace有一个cgColorSpace属性。NSImage有一个 cgImage(forProposedRect:context:hints:) 方法。 - rob mayoff
另外,Swift不支持在CGPath上使用便利初始化器。如果您尝试声明一个,您会得到错误消息“不支持在CF类型的扩展中使用便利初始化器”。 - rob mayoff

17

对于 macOS 最好使用 - CGMutablePath

但是,如果你想要 NSBezierPathcgPath:

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
  }
}

7
如果有其他人需要,这里提供一份Swift版本的代码:
```Swift // 代码内容 ```
请注意,本文不会对代码进行解释。
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
}

5

我无法理解为什么被接受的答案增加了一些复杂的闭合路径逻辑(也许在某些情况下是必要的),但对于那些只需要一个原始路径转换的人来说,这里提供了清理过的代码版本,实现为常规方法:

- (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;
}

顺便说一下,我需要这个来实现“NSBezierPath stroke contains point”方法。

我查找了这个转换以调用CGPathCreateCopyByStrokingPath(),该方法将NSBezierPath描边轮廓转换为常规路径,因此您可以测试笔画的命中情况,以下是解决方案:

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

这类似于Qt中的QPainterPathStroker,但是针对的是NSBezierPath

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;

}

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接