LOTBezierPath.m 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  1. //
  2. // LOTBezierPath.m
  3. // Lottie
  4. //
  5. // Created by brandon_withrow on 7/20/17.
  6. // Copyright © 2017 Airbnb. All rights reserved.
  7. //
  8. #import "LOTBezierPath.h"
  9. #import "CGGeometry+LOTAdditions.h"
  10. typedef struct LOT_Subpath LOT_Subpath;
  11. typedef void(^LOTBezierPathEnumerationHandler)(const CGPathElement *element);
  12. struct LOT_Subpath {
  13. CGPathElementType type;
  14. CGFloat length;
  15. CGPoint endPoint;
  16. CGPoint controlPoint1;
  17. CGPoint controlPoint2;
  18. LOT_Subpath *nextSubpath;
  19. };
  20. @interface LOTBezierPath ()
  21. @property (nonatomic, readonly) LOT_Subpath *headSubpath;
  22. @end
  23. @implementation LOTBezierPath {
  24. LOT_Subpath *headSubpath_;
  25. LOT_Subpath *tailSubpath_;
  26. CGPoint subpathStartPoint_;
  27. CGFloat *_lineDashPattern;
  28. NSInteger _lineDashCount;
  29. CGFloat _lineDashPhase;
  30. CGMutablePathRef _path;
  31. }
  32. // MARK - Lifecycle
  33. + (instancetype)pathWithCGPath:(CGPathRef)path {
  34. LOTBezierPath *returnPath = [LOTBezierPath newPath];
  35. [returnPath setWithCGPath:path];
  36. return returnPath;
  37. }
  38. + (instancetype)newPath {
  39. return [[LOTBezierPath alloc] init];
  40. }
  41. - (instancetype)init
  42. {
  43. self = [super init];
  44. if (self) {
  45. _length = 0;
  46. headSubpath_ = NULL;
  47. tailSubpath_ = NULL;
  48. _path = CGPathCreateMutable();
  49. _lineWidth = 1;
  50. _lineCapStyle = kCGLineCapButt;
  51. _lineJoinStyle = kCGLineJoinMiter;
  52. _miterLimit = 10;
  53. _flatness = 0.6;
  54. _usesEvenOddFillRule = NO;
  55. _lineDashPattern = NULL;
  56. _lineDashCount = 0;
  57. _lineDashPhase = 0;
  58. _cacheLengths = NO;
  59. }
  60. return self;
  61. }
  62. - (void)dealloc {
  63. [self removeAllSubpaths];
  64. if (_path) CGPathRelease(_path);
  65. }
  66. - (id)copyWithZone:(NSZone *)zone {
  67. LOTBezierPath *copy = [[self class] new];
  68. copy.cacheLengths = self.cacheLengths;
  69. copy.lineWidth = self.lineWidth;
  70. copy.lineCapStyle = self.lineCapStyle;
  71. copy.lineJoinStyle = self.lineJoinStyle;
  72. copy.miterLimit = self.miterLimit;
  73. copy.flatness = self.flatness;
  74. copy.usesEvenOddFillRule = self.usesEvenOddFillRule;
  75. [copy LOT_appendPath:self];
  76. return copy;
  77. }
  78. // MARK - Subpaths List
  79. - (void)removeAllSubpaths {
  80. LOT_Subpath *node = headSubpath_;
  81. while (node) {
  82. LOT_Subpath *nextNode = node->nextSubpath;
  83. node->nextSubpath = NULL;
  84. free(node);
  85. node = nextNode;
  86. }
  87. headSubpath_ = NULL;
  88. tailSubpath_ = NULL;
  89. }
  90. - (void)addSubpathWithType:(CGPathElementType)type
  91. length:(CGFloat)length
  92. endPoint:(CGPoint)endPoint
  93. controlPoint1:(CGPoint)controlPoint1
  94. controlPoint1:(CGPoint)controlPoint2 {
  95. LOT_Subpath *subPath = (LOT_Subpath *)malloc(sizeof(LOT_Subpath));
  96. subPath->type = type;
  97. subPath->length = length;
  98. subPath->endPoint = endPoint;
  99. subPath->controlPoint1 = controlPoint1;
  100. subPath->controlPoint2 = controlPoint2;
  101. subPath->nextSubpath = NULL;
  102. if (tailSubpath_ == NULL) {
  103. headSubpath_ = subPath;
  104. tailSubpath_ = subPath;
  105. } else {
  106. tailSubpath_->nextSubpath = subPath;
  107. tailSubpath_ = subPath;
  108. }
  109. }
  110. // MARK Getters Setters
  111. - (CGPoint)currentPoint {
  112. CGPoint previousPoint = tailSubpath_ ? tailSubpath_->endPoint : CGPointZero;
  113. return previousPoint;
  114. }
  115. - (CGPathRef)CGPath {
  116. return _path;
  117. }
  118. - (LOT_Subpath *)headSubpath {
  119. return headSubpath_;
  120. }
  121. // MARK - External
  122. - (void)LOT_moveToPoint:(CGPoint)point {
  123. subpathStartPoint_ = point;
  124. [self addSubpathWithType:kCGPathElementMoveToPoint length:0 endPoint:point controlPoint1:CGPointZero controlPoint1:CGPointZero];
  125. CGPathMoveToPoint(_path, NULL, point.x, point.y);
  126. }
  127. - (void)LOT_addLineToPoint:(CGPoint)point {
  128. CGFloat length = 0;
  129. if (_cacheLengths) {
  130. length = LOT_PointDistanceFromPoint(self.currentPoint, point);
  131. _length = _length + length;
  132. }
  133. [self addSubpathWithType:kCGPathElementAddLineToPoint length:length endPoint:point controlPoint1:CGPointZero controlPoint1:CGPointZero];
  134. CGPathAddLineToPoint(_path, NULL, point.x, point.y);
  135. }
  136. - (void)LOT_addCurveToPoint:(CGPoint)point
  137. controlPoint1:(CGPoint)cp1
  138. controlPoint2:(CGPoint)cp2 {
  139. CGFloat length = 0;
  140. if (_cacheLengths) {
  141. length = LOT_CubicLengthWithPrecision(self.currentPoint, point, cp1, cp2, 5);
  142. _length = _length + length;
  143. }
  144. [self addSubpathWithType:kCGPathElementAddCurveToPoint length:length endPoint:point controlPoint1:cp1 controlPoint1:cp2];
  145. CGPathAddCurveToPoint(_path, NULL, cp1.x, cp1.y, cp2.x, cp2.y, point.x, point.y);
  146. }
  147. - (void)LOT_closePath {
  148. CGFloat length = 0;
  149. if (_cacheLengths) {
  150. length = LOT_PointDistanceFromPoint(self.currentPoint, subpathStartPoint_);
  151. _length = _length + length;
  152. }
  153. [self addSubpathWithType:kCGPathElementCloseSubpath length:length endPoint:subpathStartPoint_ controlPoint1:CGPointZero controlPoint1:CGPointZero];
  154. CGPathCloseSubpath(_path);
  155. }
  156. - (void)_clearPathData {
  157. _length = 0;
  158. subpathStartPoint_ = CGPointZero;
  159. CGPathRelease(_path);
  160. _path = CGPathCreateMutable();
  161. }
  162. - (void)LOT_removeAllPoints {
  163. [self removeAllSubpaths];
  164. [self _clearPathData];
  165. }
  166. - (BOOL)containsPoint:(CGPoint)point {
  167. return CGPathContainsPoint(_path, NULL, point, _usesEvenOddFillRule);
  168. }
  169. - (BOOL)isEmpty {
  170. return CGPathIsEmpty(_path);
  171. }
  172. - (CGRect)bounds {
  173. return CGPathGetBoundingBox(_path);
  174. }
  175. - (void)LOT_applyTransform:(CGAffineTransform)transform {
  176. CGMutablePathRef mutablePath = CGPathCreateMutable();
  177. CGPathAddPath(mutablePath, &transform, _path);
  178. CGPathRelease(_path);
  179. _path = mutablePath;
  180. }
  181. - (void)LOT_appendPath:(LOTBezierPath *)bezierPath {
  182. CGPathAddPath(_path, NULL, bezierPath.CGPath);
  183. LOT_Subpath *nextSubpath = bezierPath.headSubpath;
  184. while (nextSubpath) {
  185. CGFloat length = 0;
  186. if (self.cacheLengths) {
  187. if (bezierPath.cacheLengths) {
  188. length = nextSubpath->length;
  189. } else {
  190. // No previous length data, measure.
  191. if (nextSubpath->type == kCGPathElementAddLineToPoint) {
  192. length = LOT_PointDistanceFromPoint(self.currentPoint, nextSubpath->endPoint);
  193. } else if (nextSubpath->type == kCGPathElementAddCurveToPoint) {
  194. length = LOT_CubicLengthWithPrecision(self.currentPoint, nextSubpath->endPoint, nextSubpath->controlPoint1, nextSubpath->controlPoint2, 5);
  195. } else if (nextSubpath->type == kCGPathElementCloseSubpath) {
  196. length = LOT_PointDistanceFromPoint(self.currentPoint, nextSubpath->endPoint);
  197. }
  198. }
  199. }
  200. _length = _length + length;
  201. [self addSubpathWithType:nextSubpath->type
  202. length:length
  203. endPoint:nextSubpath->endPoint
  204. controlPoint1:nextSubpath->controlPoint1
  205. controlPoint1:nextSubpath->controlPoint2];
  206. nextSubpath = nextSubpath->nextSubpath;
  207. }
  208. }
  209. - (void)trimPathFromT:(CGFloat)fromT toT:(CGFloat)toT offset:(CGFloat)offset {
  210. fromT = MIN(MAX(0, fromT), 1);
  211. toT = MIN(MAX(0, toT), 1);
  212. if (fromT > toT) {
  213. CGFloat to = fromT;
  214. fromT = toT;
  215. toT = to;
  216. }
  217. offset = offset - floor(offset);
  218. CGFloat fromLength = fromT + offset;
  219. CGFloat toLength = toT + offset;
  220. if (toT - fromT == 1) {
  221. // Do Nothing, Full Path returned.
  222. return;
  223. }
  224. if (fromLength == toLength) {
  225. // Empty Path
  226. [self LOT_removeAllPoints];
  227. return;
  228. }
  229. if (fromLength >= 1) {
  230. fromLength = fromLength - floor(fromLength);
  231. }
  232. if (toLength > 1) {
  233. toLength = toLength - floor(toLength);
  234. }
  235. if (fromLength == 0 &&
  236. toLength == 1) {
  237. // Do Nothing. Full Path returned.
  238. return;
  239. }
  240. if (fromLength == toLength) {
  241. // Empty Path
  242. [self LOT_removeAllPoints];
  243. return;
  244. }
  245. CGFloat totalLength = _length;
  246. [self _clearPathData];
  247. LOT_Subpath *subpath = headSubpath_;
  248. headSubpath_ = NULL;
  249. tailSubpath_ = NULL;
  250. fromLength = fromLength * totalLength;
  251. toLength = toLength * totalLength;
  252. CGFloat currentStartLength = fromLength < toLength ? fromLength : 0;
  253. CGFloat currentEndLength = toLength;
  254. CGFloat subpathBeginningLength = 0;
  255. CGPoint currentPoint = CGPointZero;
  256. while (subpath) {
  257. CGFloat pathLength = subpath->length;
  258. if (!_cacheLengths) {
  259. if (subpath->type == kCGPathElementAddLineToPoint) {
  260. pathLength = LOT_PointDistanceFromPoint(currentPoint, subpath->endPoint);
  261. } else if (subpath->type == kCGPathElementAddCurveToPoint) {
  262. pathLength = LOT_CubicLengthWithPrecision(currentPoint, subpath->endPoint, subpath->controlPoint1, subpath->controlPoint2, 5);
  263. } else if (subpath->type == kCGPathElementCloseSubpath) {
  264. pathLength = LOT_PointDistanceFromPoint(currentPoint, subpath->endPoint);
  265. }
  266. }
  267. CGFloat subpathEndLength = subpathBeginningLength + pathLength;
  268. if (subpath->type != kCGPathElementMoveToPoint &&
  269. subpathEndLength > currentStartLength) {
  270. // The end of this path overlaps the current drawing region
  271. // x x x x
  272. // ---------------ooooooooooooooooooooooooooooooooooooooooooooooooo-------------------
  273. // Start |currentStartLength currentEndLength| End
  274. CGFloat currentSpanStartT = LOT_RemapValue(currentStartLength, subpathBeginningLength, subpathEndLength, 0, 1);
  275. CGFloat currentSpanEndT = LOT_RemapValue(currentEndLength, subpathBeginningLength, subpathEndLength, 0, 1);
  276. // At this point currentSpan start and end T can be less than 0 or greater than 1
  277. if (subpath->type == kCGPathElementAddLineToPoint) {
  278. if (currentSpanStartT >= 0) {
  279. // The current drawable span either starts with this subpath or along this subpath.
  280. // If this is the middle of a segment then currentSpanStartT would be less than 0
  281. if (currentSpanStartT > 0) {
  282. currentPoint = LOT_PointInLine(currentPoint, subpath->endPoint, currentSpanStartT);
  283. }
  284. [self LOT_moveToPoint:currentPoint];
  285. // Now we are ready to draw a line
  286. }
  287. CGPoint toPoint = subpath->endPoint;
  288. if (currentSpanEndT < 1) {
  289. // The end of the span is inside of the current subpath. Find it.
  290. toPoint = LOT_PointInLine(currentPoint, subpath->endPoint, currentSpanEndT);
  291. }
  292. [self LOT_addLineToPoint:toPoint];
  293. currentPoint = toPoint;
  294. } else if (subpath->type == kCGPathElementAddCurveToPoint) {
  295. CGPoint cp1, cp2, end;
  296. cp1 = subpath->controlPoint1;
  297. cp2 = subpath->controlPoint2;
  298. end = subpath->endPoint;
  299. if (currentSpanStartT >= 0) {
  300. // The current drawable span either starts with this subpath or along this subpath.
  301. // If this is the middle of a segment then currentSpanStartT would be less than 0
  302. // Beginning of a segment Move start point and calculate cp1 and 2 is necessary
  303. if (currentSpanStartT > 0) {
  304. CGPoint A = LOT_PointInLine(currentPoint, cp1, currentSpanStartT);
  305. CGPoint B = LOT_PointInLine(cp1, cp2, currentSpanStartT);
  306. CGPoint C = LOT_PointInLine(cp2, end, currentSpanStartT);
  307. CGPoint D = LOT_PointInLine(A, B, currentSpanStartT);
  308. CGPoint E = LOT_PointInLine(B, C, currentSpanStartT);
  309. CGPoint F = LOT_PointInLine(D, E, currentSpanStartT);
  310. currentPoint = F;
  311. cp1 = E;
  312. cp2 = C;
  313. currentSpanEndT = LOT_RemapValue(currentSpanEndT, currentSpanStartT, 1, 0, 1);
  314. }
  315. [self LOT_moveToPoint:currentPoint];
  316. }
  317. if (currentSpanEndT < 1) {
  318. CGPoint A = LOT_PointInLine(currentPoint, cp1, currentSpanEndT);
  319. CGPoint B = LOT_PointInLine(cp1, cp2, currentSpanEndT);
  320. CGPoint C = LOT_PointInLine(cp2, end, currentSpanEndT);
  321. CGPoint D = LOT_PointInLine(A, B, currentSpanEndT);
  322. CGPoint E = LOT_PointInLine(B, C, currentSpanEndT);
  323. CGPoint F = LOT_PointInLine(D, E, currentSpanEndT);
  324. cp1 = A;
  325. cp2 = D;
  326. end = F;
  327. }
  328. [self LOT_addCurveToPoint:end controlPoint1:cp1 controlPoint2:cp2];
  329. }
  330. if (currentSpanEndT <= 1) {
  331. // We have possibly reached the end.
  332. // Current From and To will possibly need to be reset.
  333. if (fromLength < toLength) {
  334. while (subpath) {
  335. LOT_Subpath *nextNode = subpath->nextSubpath;
  336. subpath->nextSubpath = NULL;
  337. free(subpath);
  338. subpath = nextNode;
  339. }
  340. break;
  341. } else {
  342. currentStartLength = fromLength;
  343. currentEndLength = totalLength;
  344. if (fromLength < (subpathBeginningLength + pathLength) &&
  345. fromLength > subpathBeginningLength &&
  346. currentSpanEndT < 1) {
  347. // Loop over this subpath one more time.
  348. // In this case the path start and end trim fall within this subpath bounds
  349. continue;
  350. }
  351. }
  352. }
  353. }
  354. currentPoint = subpath->endPoint;
  355. subpathBeginningLength = subpathEndLength;
  356. LOT_Subpath *nextNode = subpath->nextSubpath;
  357. subpath->nextSubpath = NULL;
  358. free(subpath);
  359. subpath = nextNode;
  360. }
  361. }
  362. #pragma mark - From CGPath
  363. - (void)setWithCGPath:(CGPathRef)path {
  364. [self lot_enumeratePath:path elementsUsingBlock:^(const CGPathElement *element) {
  365. switch (element->type) {
  366. case kCGPathElementMoveToPoint: {
  367. CGPoint point = element ->points[0];
  368. [self LOT_moveToPoint:point];
  369. break;
  370. }
  371. case kCGPathElementAddLineToPoint: {
  372. CGPoint point = element ->points[0];
  373. [self LOT_addLineToPoint:point];
  374. break;
  375. }
  376. case kCGPathElementAddQuadCurveToPoint: {
  377. break;
  378. }
  379. case kCGPathElementAddCurveToPoint: {
  380. CGPoint point1 = element->points[0];
  381. CGPoint point2 = element->points[1];
  382. CGPoint point3 = element->points[2];
  383. [self LOT_addCurveToPoint:point3 controlPoint1:point1 controlPoint2:point2];
  384. break;
  385. }
  386. case kCGPathElementCloseSubpath: {
  387. [self LOT_closePath];
  388. break;
  389. }
  390. }
  391. }];
  392. }
  393. - (void)lot_enumeratePath:(CGPathRef)cgPath elementsUsingBlock:(LOTBezierPathEnumerationHandler)handler {
  394. void CGPathEnumerationCallback(void *info, const CGPathElement *element);
  395. CGPathApply(cgPath, (__bridge void * _Nullable)(handler), CGPathEnumerationCallback);
  396. }
  397. @end
  398. void CGPathEnumerationCallback(void *info, const CGPathElement *element)
  399. {
  400. LOTBezierPathEnumerationHandler handler = (__bridge LOTBezierPathEnumerationHandler)(info);
  401. if (handler) {
  402. handler(element);
  403. }
  404. }