展平CGPath

时间:2013-09-17 19:12:04

标签: ios uikit core-graphics

简单地说,我正在寻找可以在iOS上使用的NSBezierPath -bezierPathByFlatteningPath的等价物。对我来说,这是一个直接处理UGPezierPath上的CGPath或方法的函数并不重要,因为这两个函数可以很容易地来回转换。 CGPath ReferenceUIBezierPath Class Reference都没有表明存在任何此类功能或方法。

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

2 个答案:

答案 0 :(得分:7)

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

此代码用于this book

她没有提供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元素,如果它是线性的,则复制它,或者根据贝塞尔曲线段的程度对其进行离散化。

您可能还想应用Ramer–Douglas–Peucker algorithm删除不必要的积分。

您也可以直接使用:- (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;
}

Code属于Erica Sadun。请参阅此处了解完整实施:https://github.com/erica/iOS-Drawing

Rob Napier还在iOS 6 Pushing the limits第26章中写了关于Bezier曲线的文章 花哨的文字布局。他并没有试图压平一个完整的UIBezierPath,只有一个用四个点定义的三次Bezier路径,但实际上这是完全相同的事情(离散Bezier路径) 此外,您可能会发现这篇文章很有趣:http://robnapier.net/faster-bezier

答案 1 :(得分:1)

您基本上要做的是转换路径以将所有曲线和quadCurves替换为线。您可以使用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的路径

Different renderings of the bezier path