2013-09-17 5 views
3

Проще говоря, я ищу эквивалент NSBezierPath's -bezierPathByFlatteningPath, который можно использовать в iOS. Для меня не имеет значения, является ли это функцией, непосредственно связанной с CGPath или методом на UIBezierPath, поскольку они могут быть легко преобразованы взад и вперед. Ни CGPath Reference, ни UIBezierPath Class Reference не указывают на наличие какой-либо такой функции или метода.Сглаживание CGPath

Кроме того: я знаю о функции CGPath CGPathApply, и мне не хватает как времени, так и набора навыков для реализации моего собственного алгоритма сглаживания путем итерации по элементам пути в CGPathApplierFunction. Я ищу существующее решение для этого: функция приложения, категория на UIBezierPath и т. Д. Разумеется, существует.

+1

Использование ' UIBezierPath', устанавливает ли 'path.flatness = 1' то, что вы ищете? –

+0

К сожалению, нет, потому что в качестве свойства чертежа «плоскость» влияет только на то, как визуализируется кривая, а не на то, как она хранится. –

+0

Хм ... В этом случае ваш лучший выбор - это, вероятно, перебрать все точки с помощью 'CGPathApply' и построить новый путь, используя соответствующие функции, чтобы получить плоский эффект. –

ответ

7

Erica Sadun предоставляет набор полезных функций для решения UIBezierPath и CGPathRef.

Этот код используется в this book.

Она не обеспечивает реализацию уплощения CGPathRef, но это можно легко сделать с помощью функций, которые можно найти здесь: https://github.com/erica/iOS-Drawing/blob/master/C05/Quartz%20Book%20Pack/Bezier/BezierFunctions.m

Particulary, эти функции помогут Дискретизируют нелинейные сегменты Безье:

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 точек, которые могут быть использованы для построения аппроксимации пути. Алгоритм наивен, поэтому вам нужно упростить результат в случае, например, когда кубический путь Безье будет линейным (если контрольные точки выровнены); как и раньше, алгоритм Рамера-Дугласа-Пьюкера выполняет эту работу.

Вот как выглядит фактическая дискретизация. Код не является самодостаточным, вам придется использовать все зависимости.

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

Код принадлежит Эрика Садун. Смотрите здесь для полной реализации: https://github.com/erica/iOS-Drawing

Rob Napier также писал о кривых Безье в iOS 6 Pushing the limits, Глава 26 Fancy Text Layout.Он не пытался сгладить полный UIBezierPath, только один кубический маршрут Безье, определенный четырьмя точками, но на самом деле это то же самое (дискретизация пути Безье). . Также вы можете найти эту статью интересной: http://robnapier.net/faster-bezier

+0

Это, хотя и не идеальное (идеальное существо [API, поставляемое Apple] (http://www.openradar.me/15217701)), гораздо лучше отвечает на исходный вопрос, поскольку он решает проблему дискретизации сегменты нелинейного пути, поэтому я переместил галочку здесь. –

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

+0

Это хороший ответ, но вы не указали способ изменить гранулярность аппроксимации, как это может быть сделано с помощью метода 'setDefaultFlatness:' NSBezierPath'. Это также относится ко всем частям кривой таким же образом, когда оно должно ломать более крутые части кривой на большее количество кусков для лучшего приближения. Извините, что я просто отказался от этого вопроса - я сам перешел от проблемы, но только для хорошей меры я подал радар (http://www.openradar.me/15217701) и отметил это как ответ для Теперь. :) –

+0

Вы должны дискретировать квадрические и кубические кривые Безье в последовательности линейных сегментов. Это не функция сглаживания. – alecail