How to get the CGPoint(s) of a CGPath

前端 未结 4 1163
天命终不由人
天命终不由人 2020-11-27 04:22

How can I get an array with all the CGPoint(s) contained in a given CGPath (CGMutablePathRef)?

相关标签:
4条回答
  • 2020-11-27 04:47

    You can use CGPathApply() to iterate over every segment in the path and run a custom function with that segment. That will give you all the information the path has.

    However, if by "all the CGPoint(s)", you meant every point that has a pixel rendered to it, that's an infinitely-sized set. Although you could certainly use the apply function to get each segment, and then for every non-move segment, evaluate your own math with the segment's control points to get a list of points at whatever density you want.

    0 讨论(0)
  • 2020-11-27 04:53

    Using Swift 2.x (for Swift 3.x, Swift 4.x and Swift 5.x read here below..) , i've found this fantastic article about C Callbacks in Swift.

    Trying to obtain "all the CGPoint(s)", as explained by Lily Ballard, can be a bad idea as she said.

    So, I think maybe the best way is to get the path elements points used to create a particular CGPath:

    //MARK: - CGPath extensions
    extension CGPath {
        func forEach(@noescape body: @convention(block) (CGPathElement) -> Void) {
            typealias Body = @convention(block) (CGPathElement) -> Void
            func callback(info: UnsafeMutablePointer<Void>, element: UnsafePointer<CGPathElement>) {
                let body = unsafeBitCast(info, Body.self)
                body(element.memory)
            }
            print(sizeofValue(body))
            let unsafeBody = unsafeBitCast(body, UnsafeMutablePointer<Void>.self)
            CGPathApply(self, unsafeBody, callback)
        }
    
        func getPathElementsPoints() -> [CGPoint] {
            var arrayPoints : [CGPoint]! = [CGPoint]()
            self.forEach { element in
                switch (element.type) {
                case CGPathElementType.MoveToPoint:
                    arrayPoints.append(element.points[0])
                case .AddLineToPoint:
                    arrayPoints.append(element.points[0])
                case .AddQuadCurveToPoint:
                    arrayPoints.append(element.points[0])
                    arrayPoints.append(element.points[1])
                case .AddCurveToPoint:
                    arrayPoints.append(element.points[0])
                    arrayPoints.append(element.points[1])
                    arrayPoints.append(element.points[2])
                default: break
                }
            }
            return arrayPoints
        }
    }
    

    With this extension you can do for example:

    var bezier = UIBezierPath(ovalInRect: CGRectMake(0, 0, 400, 300))
    let myOval = bezier.CGPath
    let junctionPoints = myOval.getPathElementsPoints()
    print("junction points are: \(junctionPoints)")
    

    Swift 3.x and Swift 4.1 (look below for Swift 4.2 or major..)

    (there are some corrections due to syntax re-introduction of @convention(c)):

    extension CGPath {
    
        func forEach( body: @convention(block) (CGPathElement) -> Void) {
            typealias Body = @convention(block) (CGPathElement) -> Void
            let callback: @convention(c) (UnsafeMutableRawPointer, UnsafePointer<CGPathElement>) -> Void = { (info, element) in
                let body = unsafeBitCast(info, to: Body.self)
                body(element.pointee)
            }
            print(MemoryLayout.size(ofValue: body))
            let unsafeBody = unsafeBitCast(body, to: UnsafeMutableRawPointer.self)
            self.apply(info: unsafeBody, function: unsafeBitCast(callback, to: CGPathApplierFunction.self))
        }
    
    
        func getPathElementsPoints() -> [CGPoint] {
            var arrayPoints : [CGPoint]! = [CGPoint]()
            self.forEach { element in
                switch (element.type) {
                case CGPathElementType.moveToPoint:
                    arrayPoints.append(element.points[0])
                case .addLineToPoint:
                    arrayPoints.append(element.points[0])
                case .addQuadCurveToPoint:
                    arrayPoints.append(element.points[0])
                    arrayPoints.append(element.points[1])
                case .addCurveToPoint:
                    arrayPoints.append(element.points[0])
                    arrayPoints.append(element.points[1])
                    arrayPoints.append(element.points[2])
                default: break
                }
            }
            return arrayPoints
        }
    
        func getPathElementsPointsAndTypes() -> ([CGPoint],[CGPathElementType]) {
            var arrayPoints : [CGPoint]! = [CGPoint]()
            var arrayTypes : [CGPathElementType]! = [CGPathElementType]()
            self.forEach { element in
                switch (element.type) {
                case CGPathElementType.moveToPoint:
                    arrayPoints.append(element.points[0])
                    arrayTypes.append(element.type)
                case .addLineToPoint:
                    arrayPoints.append(element.points[0])
                    arrayTypes.append(element.type)
                case .addQuadCurveToPoint:
                    arrayPoints.append(element.points[0])
                    arrayPoints.append(element.points[1])
                    arrayTypes.append(element.type)
                    arrayTypes.append(element.type)
                case .addCurveToPoint:
                    arrayPoints.append(element.points[0])
                    arrayPoints.append(element.points[1])
                    arrayPoints.append(element.points[2])
                    arrayTypes.append(element.type)
                    arrayTypes.append(element.type)
                    arrayTypes.append(element.type)
                default: break
                }
            }
            return (arrayPoints,arrayTypes)
        }
    }
    

    Swift > 4.1 (also Swift 5.x) and iOS 9.x and > compatible

    extension CGPath {
        func forEach( body: @escaping @convention(block) (CGPathElement) -> Void) {
            typealias Body = @convention(block) (CGPathElement) -> Void
            let callback: @convention(c) (UnsafeMutableRawPointer, UnsafePointer<CGPathElement>) -> Void = { (info, element) in
                let body = unsafeBitCast(info, to: Body.self)
                body(element.pointee)
            }
            //print(MemoryLayout.size(ofValue: body))
            let unsafeBody = unsafeBitCast(body, to: UnsafeMutableRawPointer.self)
            self.apply(info: unsafeBody, function: unsafeBitCast(callback, to: CGPathApplierFunction.self))
        }
        func getPathElementsPoints() -> [CGPoint] {
            var arrayPoints : [CGPoint]! = [CGPoint]()
            self.forEach { element in
                switch (element.type) {
                case CGPathElementType.moveToPoint:
                    arrayPoints.append(element.points[0])
                case .addLineToPoint:
                    arrayPoints.append(element.points[0])
                case .addQuadCurveToPoint:
                    arrayPoints.append(element.points[0])
                    arrayPoints.append(element.points[1])
                case .addCurveToPoint:
                    arrayPoints.append(element.points[0])
                    arrayPoints.append(element.points[1])
                    arrayPoints.append(element.points[2])
                default: break
                }
            }
            return arrayPoints
        }
        func getPathElementsPointsAndTypes() -> ([CGPoint],[CGPathElementType]) {
            var arrayPoints : [CGPoint]! = [CGPoint]()
            var arrayTypes : [CGPathElementType]! = [CGPathElementType]()
            self.forEach { element in
                switch (element.type) {
                case CGPathElementType.moveToPoint:
                    arrayPoints.append(element.points[0])
                    arrayTypes.append(element.type)
                case .addLineToPoint:
                    arrayPoints.append(element.points[0])
                    arrayTypes.append(element.type)
                case .addQuadCurveToPoint:
                    arrayPoints.append(element.points[0])
                    arrayPoints.append(element.points[1])
                    arrayTypes.append(element.type)
                    arrayTypes.append(element.type)
                case .addCurveToPoint:
                    arrayPoints.append(element.points[0])
                    arrayPoints.append(element.points[1])
                    arrayPoints.append(element.points[2])
                    arrayTypes.append(element.type)
                    arrayTypes.append(element.type)
                    arrayTypes.append(element.type)
                default: break
                }
            }
            return (arrayPoints,arrayTypes)
        }
    }
    
    0 讨论(0)
  • 2020-11-27 04:56

    Apple added instance method CGPath.applyWithBlock, available for iOS11.0+ and macOS10.13+

    You still can examine each element of a path with CGPath.apply as explained in previous answers. But if you want to avoid using C style pointers and unsafeBitCast, you should use the much more convenient instance method applyWithBlock. For example, if you want to get an array of CGPoints of a CGPath you can add an extension to CGPath, in this example a computed property(points) that collects the CGPoints of the CGPath:

    /// Extension to collect CGPath points
    extension CGPath {
    
      /// this is a computed property, it will hold the points we want to extract
      var points: [CGPoint] {
    
         /// this is a local transient container where we will store our CGPoints
         var arrPoints: [CGPoint] = []
    
         // applyWithBlock lets us examine each element of the CGPath, and decide what to do
         self.applyWithBlock { element in
    
            switch element.pointee.type
            {
            case .moveToPoint, .addLineToPoint:
              arrPoints.append(element.pointee.points.pointee)
    
            case .addQuadCurveToPoint:
              arrPoints.append(element.pointee.points.pointee)
              arrPoints.append(element.pointee.points.advanced(by: 1).pointee)
    
            case .addCurveToPoint:
              arrPoints.append(element.pointee.points.pointee)
              arrPoints.append(element.pointee.points.advanced(by: 1).pointee)
              arrPoints.append(element.pointee.points.advanced(by: 2).pointee)
    
            default:
              break
            }
         }
    
        // We are now done collecting our CGPoints and so we can return the result
        return arrPoints
    
      }
    }
    
    0 讨论(0)
  • 2020-11-27 05:02

    A CGPath is an opaque data type and does not necessarily store all the points used. In addition to this, a path may not actually draw all the points used as input (for example, consider Bézier control points).

    The only two documented ways of getting information out of a path is to use CGPathGetBoundingBox to get the bounding box, or the more complicated method of using CGPathApply to call a callback function that will give you a sequence if CGPathElement types.

    0 讨论(0)
提交回复
热议问题