4

简而言之,我正在寻找-bezierPathByFlatteningPath可以在 iOS 上使用的与 NSBezierPath 等效的产品。这是直接处理 CGPath 的函数还是 UIBezierPath 上的方法对我来说并不重要,因为这两者可以很容易地来回转换。CGPath参考UIBezierPath 类参考都没有表明任何此类函数或方法的存在。

另外:我知道 CGPath 的CGPathApply功能,并且我缺乏时间和技能集来通过迭代路径中的元素来实现我自己的展平算法CGPathApplierFunction。我正在寻找一个现有的解决方案——应用程序函数、UIBezierPath 上的类别等。当然存在。

4

2 回答 2

7

Erica Sadun 提供了一组有用的函数来处理 UIBezierPath 和 CGPathRef。

本书使用此代码。

她没有提供 CGPathRef 展平的实现,但可以使用以下函数轻松完成: https ://github.com/erica/iOS-Drawing/blob/master/C05/Quartz%20Book%20Pack /Bezier/BezierFunctions.m

特别是,这些函数将有助于离散非线性贝塞尔线段:

float CubicBezier(float t, float start, float c1, float c2, float end)
float QuadBezier(float t, float start, float c1, float end)
CGPoint CubicBezierPoint(CGFloat t, CGPoint start, CGPoint c1, CGPoint c2, CGPoint end);
CGPoint QuadBezierPoint(CGFloat t, CGPoint start, CGPoint c1, CGPoint end);

所以基本上,初始化一个空的 CGMutablePathRef,对于原始路径中的每个 CGPath 元素,如果它是线性的,要么复制它,要么根据 Bezier 段的度数对其进行离散化。

您可能还想应用Ramer–Douglas–Peucker 算法来删除不必要的点。

您也可以直接使用:- (NSArray *) interpolatedPathPoints它返回一个 NSArray 点,可用于构建路径的近似值。该算法是幼稚的,因此您必须简化这种情况下的结果,例如,三次贝塞尔路径将是线性的(如果控制点对齐);和以前一样,Ramer-Douglas-Peucker 算法完成了这项工作。

这是实际离散化的样子。代码不是自包含的,您必须使用所有依赖项。

- (NSArray *) interpolatedPathPoints
{
    NSMutableArray *points = [NSMutableArray array];
    BezierElement *current = nil;
    int overkill = 3;
    for (BezierElement *element in self.elements)
    {
        switch (element.elementType)
        {
            case kCGPathElementMoveToPoint:
            case kCGPathElementAddLineToPoint:
                [points addObject:[NSValue valueWithCGPoint:element.point]];
                current = element;
                break;
            case kCGPathElementCloseSubpath:
                current = nil;
                break;
            case kCGPathElementAddCurveToPoint:
            {
                for (int i = 1; i < NUMBER_OF_BEZIER_SAMPLES * overkill; i++)
                {
                    CGFloat percent = (CGFloat) i / (CGFloat) (NUMBER_OF_BEZIER_SAMPLES * overkill);
                    CGPoint p = CubicBezierPoint(percent, current.point, element.controlPoint1, element.controlPoint2, element.point);
                    [points addObject:[NSValue valueWithCGPoint:p]];
                }
                [points addObject:[NSValue valueWithCGPoint:element.point]];
                current = element;
                break;
            }
            case kCGPathElementAddQuadCurveToPoint:
            {
                for (int i = 1; i < NUMBER_OF_BEZIER_SAMPLES * overkill; i++)
                {
                    CGFloat percent = (CGFloat) i / (CGFloat) (NUMBER_OF_BEZIER_SAMPLES * overkill);
                    CGPoint p = QuadBezierPoint(percent, current.point, element.controlPoint1, element.point);
                    [points addObject:[NSValue valueWithCGPoint:p]];
                }
                [points addObject:[NSValue valueWithCGPoint:element.point]];
                current = element;
                break;
            }
        }
    }
    return points;
}

代码属于 Erica Sadun。完整的实现见这里:https ://github.com/erica/iOS-Drawing

Rob Napier 还在iOS 6 Pushing the limits , Chapter 26 Fancy Text Layout 中写了关于 Bezier 曲线的文章。他并没有试图展平完整的 UIBezierPath,只有一个由四个点定义的三次 Bezier 路径,但实际上这完全一样(离散化 Bezier 路径)另外,您可能会发现这篇文章很有趣:http ://robnapier.net/更快的贝塞尔曲线

于 2014-03-28T16:49:56.233 回答
1

您基本上要做的是转换路径以将所有曲线和四边形曲线替换为线。你可以很轻松地做到这一点,CGPathApply并将其包装在一个类别中UIBezierPath

#import <CoreGraphics/CoreGraphics.h>

@interface UIBezierPath (Flatten)
- (UIBezierPath *)bezierPathByFlatteningPath;
@end

void __flattenBezierPath(void *target, const CGPathElement *element) {
    UIBezierPath *newPath = (__bridge UIBezierPath *)(target);

    switch (element->type) {
        case kCGPathElementMoveToPoint:
            [newPath moveToPoint:element->points[0]];
            break;

        case kCGPathElementAddLineToPoint:
            [newPath addLineToPoint:element->points[0]];
            break;

        case kCGPathElementCloseSubpath:
            [newPath closePath];
            break;

        case kCGPathElementAddCurveToPoint:
            [newPath addLineToPoint:element->points[2]];
            break;

        case kCGPathElementAddQuadCurveToPoint:
            [newPath addLineToPoint:element->points[1]];
            break;
    }

}

@implementation UIBezierPath (Flatten)

- (UIBezierPath *)bezierPathByFlatteningPath
{
    UIBezierPath *newPath = [UIBezierPath bezierPath];
    CGPathApply(self.CGPath, (__bridge void *)(newPath), __flattenBezierPath);
    return newPath;
}

@end

效果如下图,其中绿色为原始路径,红色为该方法生成的路径,黄色为使用的路径setFlatness:MAXFLOAT

贝塞尔路径的不同渲染

于 2013-09-18T05:53:59.367 回答