压扁CGPath

简单地说,我正在寻找一个相当于NSBezierPath的-bezierPathByFlatteningPath ,可以在iOS上使用。 这对我来说无论是直接处理CGPath还是UIBezierPath上的方法都是无关紧要的,因为这两者可以很容易地来回转换。 CGPath Reference和UIBezierPath Class Reference均不表示存在任何此类function或方法。

此外:我知道CGPath的CGPathApply函数,我缺乏时间和技能集来实现自己的展平algorithm,通过迭代CGPathApplierFunction的path元素。 我正在寻找一个现有的解决scheme,这是一个应用函数,UIBezierPath上的一个类,等等。

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

本书中使用了这个代码。

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

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

 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,并且对于原始path中的每个CGPath元素,如果它是线性的,则复制它,或者根据Bezier段的程度将其离散化。

你也可以使用Ramer-Douglas-Peuckeralgorithm去除不必要的点。

你也可以直接使用: - (NSArray *) interpolatedPathPoints返回一个NSArray的点,可以用来build立一个近似的path。 该algorithm是天真的,所以你必须在这种情况下简化结果,例如,三次贝塞尔path是线性的(如果控制点是alignment的)。 和以前一样,Ramer-Douglas-Peuckeralgorithm可以完成这项工作。

这是实际离散化的样子。 代码不是自包含的,你将不得不使用所有的依赖关系。

 - (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中推出了 Bezier曲线推动极限 ,第26章花式文本布局。 他并没有试图压扁一个完整的UIBezierpath,只有一个三angular形的Bezierpath定义了四个点,但实际上这完全一样(离散化一个Bezierpath)。另外,你可能会觉得这篇文章很有意思: http : //robnapier.net/快贝塞尔

你基本上想要做的是改变path,以取代所有的曲线和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 

效果可以在下面看到,其中绿色是原始path,红色是由此方法生成的path,黄色是使用setFlatness:MAXFLOAT的pathsetFlatness:MAXFLOAT

贝塞尔路径的不同渲染