123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471 |
- //
- // LOTBezierPath.m
- // Lottie
- //
- // Created by brandon_withrow on 7/20/17.
- // Copyright © 2017 Airbnb. All rights reserved.
- //
- #import "LOTBezierPath.h"
- #import "CGGeometry+LOTAdditions.h"
- typedef struct LOT_Subpath LOT_Subpath;
- typedef void(^LOTBezierPathEnumerationHandler)(const CGPathElement *element);
- struct LOT_Subpath {
- CGPathElementType type;
- CGFloat length;
- CGPoint endPoint;
- CGPoint controlPoint1;
- CGPoint controlPoint2;
- LOT_Subpath *nextSubpath;
- };
- @interface LOTBezierPath ()
- @property (nonatomic, readonly) LOT_Subpath *headSubpath;
- @end
- @implementation LOTBezierPath {
- LOT_Subpath *headSubpath_;
- LOT_Subpath *tailSubpath_;
- CGPoint subpathStartPoint_;
- CGFloat *_lineDashPattern;
- NSInteger _lineDashCount;
- CGFloat _lineDashPhase;
- CGMutablePathRef _path;
- }
- // MARK - Lifecycle
- + (instancetype)pathWithCGPath:(CGPathRef)path {
- LOTBezierPath *returnPath = [LOTBezierPath newPath];
- [returnPath setWithCGPath:path];
- return returnPath;
- }
- + (instancetype)newPath {
- return [[LOTBezierPath alloc] init];
- }
- - (instancetype)init
- {
- self = [super init];
- if (self) {
- _length = 0;
- headSubpath_ = NULL;
- tailSubpath_ = NULL;
- _path = CGPathCreateMutable();
- _lineWidth = 1;
- _lineCapStyle = kCGLineCapButt;
- _lineJoinStyle = kCGLineJoinMiter;
- _miterLimit = 10;
- _flatness = 0.6;
- _usesEvenOddFillRule = NO;
- _lineDashPattern = NULL;
- _lineDashCount = 0;
- _lineDashPhase = 0;
- _cacheLengths = NO;
- }
- return self;
- }
- - (void)dealloc {
- [self removeAllSubpaths];
- if (_path) CGPathRelease(_path);
- }
- - (id)copyWithZone:(NSZone *)zone {
- LOTBezierPath *copy = [[self class] new];
-
- copy.cacheLengths = self.cacheLengths;
- copy.lineWidth = self.lineWidth;
- copy.lineCapStyle = self.lineCapStyle;
- copy.lineJoinStyle = self.lineJoinStyle;
- copy.miterLimit = self.miterLimit;
- copy.flatness = self.flatness;
- copy.usesEvenOddFillRule = self.usesEvenOddFillRule;
-
- [copy LOT_appendPath:self];
-
- return copy;
- }
- // MARK - Subpaths List
- - (void)removeAllSubpaths {
- LOT_Subpath *node = headSubpath_;
- while (node) {
- LOT_Subpath *nextNode = node->nextSubpath;
- node->nextSubpath = NULL;
- free(node);
- node = nextNode;
- }
- headSubpath_ = NULL;
- tailSubpath_ = NULL;
- }
- - (void)addSubpathWithType:(CGPathElementType)type
- length:(CGFloat)length
- endPoint:(CGPoint)endPoint
- controlPoint1:(CGPoint)controlPoint1
- controlPoint1:(CGPoint)controlPoint2 {
- LOT_Subpath *subPath = (LOT_Subpath *)malloc(sizeof(LOT_Subpath));
- subPath->type = type;
- subPath->length = length;
- subPath->endPoint = endPoint;
- subPath->controlPoint1 = controlPoint1;
- subPath->controlPoint2 = controlPoint2;
- subPath->nextSubpath = NULL;
- if (tailSubpath_ == NULL) {
- headSubpath_ = subPath;
- tailSubpath_ = subPath;
- } else {
- tailSubpath_->nextSubpath = subPath;
- tailSubpath_ = subPath;
- }
- }
- // MARK Getters Setters
- - (CGPoint)currentPoint {
- CGPoint previousPoint = tailSubpath_ ? tailSubpath_->endPoint : CGPointZero;
- return previousPoint;
- }
- - (CGPathRef)CGPath {
- return _path;
- }
- - (LOT_Subpath *)headSubpath {
- return headSubpath_;
- }
- // MARK - External
- - (void)LOT_moveToPoint:(CGPoint)point {
- subpathStartPoint_ = point;
- [self addSubpathWithType:kCGPathElementMoveToPoint length:0 endPoint:point controlPoint1:CGPointZero controlPoint1:CGPointZero];
- CGPathMoveToPoint(_path, NULL, point.x, point.y);
- }
- - (void)LOT_addLineToPoint:(CGPoint)point {
- CGFloat length = 0;
- if (_cacheLengths) {
- length = LOT_PointDistanceFromPoint(self.currentPoint, point);
- _length = _length + length;
- }
- [self addSubpathWithType:kCGPathElementAddLineToPoint length:length endPoint:point controlPoint1:CGPointZero controlPoint1:CGPointZero];
- CGPathAddLineToPoint(_path, NULL, point.x, point.y);
- }
- - (void)LOT_addCurveToPoint:(CGPoint)point
- controlPoint1:(CGPoint)cp1
- controlPoint2:(CGPoint)cp2 {
- CGFloat length = 0;
- if (_cacheLengths) {
- length = LOT_CubicLengthWithPrecision(self.currentPoint, point, cp1, cp2, 5);
- _length = _length + length;
- }
- [self addSubpathWithType:kCGPathElementAddCurveToPoint length:length endPoint:point controlPoint1:cp1 controlPoint1:cp2];
- CGPathAddCurveToPoint(_path, NULL, cp1.x, cp1.y, cp2.x, cp2.y, point.x, point.y);
- }
- - (void)LOT_closePath {
- CGFloat length = 0;
- if (_cacheLengths) {
- length = LOT_PointDistanceFromPoint(self.currentPoint, subpathStartPoint_);
- _length = _length + length;
- }
- [self addSubpathWithType:kCGPathElementCloseSubpath length:length endPoint:subpathStartPoint_ controlPoint1:CGPointZero controlPoint1:CGPointZero];
- CGPathCloseSubpath(_path);
- }
- - (void)_clearPathData {
- _length = 0;
- subpathStartPoint_ = CGPointZero;
- CGPathRelease(_path);
- _path = CGPathCreateMutable();
- }
- - (void)LOT_removeAllPoints {
- [self removeAllSubpaths];
- [self _clearPathData];
- }
- - (BOOL)containsPoint:(CGPoint)point {
- return CGPathContainsPoint(_path, NULL, point, _usesEvenOddFillRule);
- }
- - (BOOL)isEmpty {
- return CGPathIsEmpty(_path);
- }
- - (CGRect)bounds {
- return CGPathGetBoundingBox(_path);
- }
- - (void)LOT_applyTransform:(CGAffineTransform)transform {
- CGMutablePathRef mutablePath = CGPathCreateMutable();
- CGPathAddPath(mutablePath, &transform, _path);
- CGPathRelease(_path);
- _path = mutablePath;
- }
- - (void)LOT_appendPath:(LOTBezierPath *)bezierPath {
- CGPathAddPath(_path, NULL, bezierPath.CGPath);
-
- LOT_Subpath *nextSubpath = bezierPath.headSubpath;
- while (nextSubpath) {
- CGFloat length = 0;
- if (self.cacheLengths) {
- if (bezierPath.cacheLengths) {
- length = nextSubpath->length;
- } else {
- // No previous length data, measure.
- if (nextSubpath->type == kCGPathElementAddLineToPoint) {
- length = LOT_PointDistanceFromPoint(self.currentPoint, nextSubpath->endPoint);
- } else if (nextSubpath->type == kCGPathElementAddCurveToPoint) {
- length = LOT_CubicLengthWithPrecision(self.currentPoint, nextSubpath->endPoint, nextSubpath->controlPoint1, nextSubpath->controlPoint2, 5);
- } else if (nextSubpath->type == kCGPathElementCloseSubpath) {
- length = LOT_PointDistanceFromPoint(self.currentPoint, nextSubpath->endPoint);
- }
- }
- }
- _length = _length + length;
- [self addSubpathWithType:nextSubpath->type
- length:length
- endPoint:nextSubpath->endPoint
- controlPoint1:nextSubpath->controlPoint1
- controlPoint1:nextSubpath->controlPoint2];
-
- nextSubpath = nextSubpath->nextSubpath;
- }
- }
- - (void)trimPathFromT:(CGFloat)fromT toT:(CGFloat)toT offset:(CGFloat)offset {
- fromT = MIN(MAX(0, fromT), 1);
- toT = MIN(MAX(0, toT), 1);
- if (fromT > toT) {
- CGFloat to = fromT;
- fromT = toT;
- toT = to;
- }
-
- offset = offset - floor(offset);
- CGFloat fromLength = fromT + offset;
- CGFloat toLength = toT + offset;
-
- if (toT - fromT == 1) {
- // Do Nothing, Full Path returned.
- return;
- }
-
- if (fromLength == toLength) {
- // Empty Path
- [self LOT_removeAllPoints];
- return;
- }
-
- if (fromLength >= 1) {
- fromLength = fromLength - floor(fromLength);
- }
- if (toLength > 1) {
- toLength = toLength - floor(toLength);
- }
-
- if (fromLength == 0 &&
- toLength == 1) {
- // Do Nothing. Full Path returned.
- return;
- }
-
- if (fromLength == toLength) {
- // Empty Path
- [self LOT_removeAllPoints];
- return;
- }
-
- CGFloat totalLength = _length;
-
- [self _clearPathData];
- LOT_Subpath *subpath = headSubpath_;
- headSubpath_ = NULL;
- tailSubpath_ = NULL;
-
- fromLength = fromLength * totalLength;
- toLength = toLength * totalLength;
-
- CGFloat currentStartLength = fromLength < toLength ? fromLength : 0;
- CGFloat currentEndLength = toLength;
- CGFloat subpathBeginningLength = 0;
- CGPoint currentPoint = CGPointZero;
- while (subpath) {
-
- CGFloat pathLength = subpath->length;
- if (!_cacheLengths) {
- if (subpath->type == kCGPathElementAddLineToPoint) {
- pathLength = LOT_PointDistanceFromPoint(currentPoint, subpath->endPoint);
- } else if (subpath->type == kCGPathElementAddCurveToPoint) {
- pathLength = LOT_CubicLengthWithPrecision(currentPoint, subpath->endPoint, subpath->controlPoint1, subpath->controlPoint2, 5);
- } else if (subpath->type == kCGPathElementCloseSubpath) {
- pathLength = LOT_PointDistanceFromPoint(currentPoint, subpath->endPoint);
- }
- }
- CGFloat subpathEndLength = subpathBeginningLength + pathLength;
- if (subpath->type != kCGPathElementMoveToPoint &&
- subpathEndLength > currentStartLength) {
- // The end of this path overlaps the current drawing region
-
- // x x x x
- // ---------------ooooooooooooooooooooooooooooooooooooooooooooooooo-------------------
- // Start |currentStartLength currentEndLength| End
-
- CGFloat currentSpanStartT = LOT_RemapValue(currentStartLength, subpathBeginningLength, subpathEndLength, 0, 1);
- CGFloat currentSpanEndT = LOT_RemapValue(currentEndLength, subpathBeginningLength, subpathEndLength, 0, 1);
-
- // At this point currentSpan start and end T can be less than 0 or greater than 1
-
- if (subpath->type == kCGPathElementAddLineToPoint) {
-
- if (currentSpanStartT >= 0) {
- // The current drawable span either starts with this subpath or along this subpath.
- // If this is the middle of a segment then currentSpanStartT would be less than 0
- if (currentSpanStartT > 0) {
- currentPoint = LOT_PointInLine(currentPoint, subpath->endPoint, currentSpanStartT);
- }
- [self LOT_moveToPoint:currentPoint];
- // Now we are ready to draw a line
- }
-
- CGPoint toPoint = subpath->endPoint;
- if (currentSpanEndT < 1) {
- // The end of the span is inside of the current subpath. Find it.
- toPoint = LOT_PointInLine(currentPoint, subpath->endPoint, currentSpanEndT);
- }
- [self LOT_addLineToPoint:toPoint];
- currentPoint = toPoint;
- } else if (subpath->type == kCGPathElementAddCurveToPoint) {
- CGPoint cp1, cp2, end;
- cp1 = subpath->controlPoint1;
- cp2 = subpath->controlPoint2;
- end = subpath->endPoint;
-
- if (currentSpanStartT >= 0) {
- // The current drawable span either starts with this subpath or along this subpath.
- // If this is the middle of a segment then currentSpanStartT would be less than 0
- // Beginning of a segment Move start point and calculate cp1 and 2 is necessary
- if (currentSpanStartT > 0) {
- CGPoint A = LOT_PointInLine(currentPoint, cp1, currentSpanStartT);
- CGPoint B = LOT_PointInLine(cp1, cp2, currentSpanStartT);
- CGPoint C = LOT_PointInLine(cp2, end, currentSpanStartT);
- CGPoint D = LOT_PointInLine(A, B, currentSpanStartT);
- CGPoint E = LOT_PointInLine(B, C, currentSpanStartT);
- CGPoint F = LOT_PointInLine(D, E, currentSpanStartT);
- currentPoint = F;
- cp1 = E;
- cp2 = C;
- currentSpanEndT = LOT_RemapValue(currentSpanEndT, currentSpanStartT, 1, 0, 1);
- }
- [self LOT_moveToPoint:currentPoint];
- }
-
- if (currentSpanEndT < 1) {
- CGPoint A = LOT_PointInLine(currentPoint, cp1, currentSpanEndT);
- CGPoint B = LOT_PointInLine(cp1, cp2, currentSpanEndT);
- CGPoint C = LOT_PointInLine(cp2, end, currentSpanEndT);
- CGPoint D = LOT_PointInLine(A, B, currentSpanEndT);
- CGPoint E = LOT_PointInLine(B, C, currentSpanEndT);
- CGPoint F = LOT_PointInLine(D, E, currentSpanEndT);
- cp1 = A;
- cp2 = D;
- end = F;
- }
- [self LOT_addCurveToPoint:end controlPoint1:cp1 controlPoint2:cp2];
- }
- if (currentSpanEndT <= 1) {
- // We have possibly reached the end.
- // Current From and To will possibly need to be reset.
- if (fromLength < toLength) {
- while (subpath) {
- LOT_Subpath *nextNode = subpath->nextSubpath;
- subpath->nextSubpath = NULL;
- free(subpath);
- subpath = nextNode;
- }
- break;
- } else {
- currentStartLength = fromLength;
- currentEndLength = totalLength;
- if (fromLength < (subpathBeginningLength + pathLength) &&
- fromLength > subpathBeginningLength &&
- currentSpanEndT < 1) {
- // Loop over this subpath one more time.
- // In this case the path start and end trim fall within this subpath bounds
- continue;
- }
- }
- }
- }
- currentPoint = subpath->endPoint;
- subpathBeginningLength = subpathEndLength;
-
- LOT_Subpath *nextNode = subpath->nextSubpath;
- subpath->nextSubpath = NULL;
- free(subpath);
- subpath = nextNode;
- }
- }
- #pragma mark - From CGPath
- - (void)setWithCGPath:(CGPathRef)path {
- [self lot_enumeratePath:path elementsUsingBlock:^(const CGPathElement *element) {
- switch (element->type) {
- case kCGPathElementMoveToPoint: {
- CGPoint point = element ->points[0];
- [self LOT_moveToPoint:point];
- break;
- }
- case kCGPathElementAddLineToPoint: {
- CGPoint point = element ->points[0];
- [self LOT_addLineToPoint:point];
- break;
- }
- case kCGPathElementAddQuadCurveToPoint: {
- break;
- }
- case kCGPathElementAddCurveToPoint: {
- CGPoint point1 = element->points[0];
- CGPoint point2 = element->points[1];
- CGPoint point3 = element->points[2];
- [self LOT_addCurveToPoint:point3 controlPoint1:point1 controlPoint2:point2];
- break;
- }
- case kCGPathElementCloseSubpath: {
- [self LOT_closePath];
- break;
- }
- }
- }];
- }
- - (void)lot_enumeratePath:(CGPathRef)cgPath elementsUsingBlock:(LOTBezierPathEnumerationHandler)handler {
- void CGPathEnumerationCallback(void *info, const CGPathElement *element);
- CGPathApply(cgPath, (__bridge void * _Nullable)(handler), CGPathEnumerationCallback);
- }
- @end
- void CGPathEnumerationCallback(void *info, const CGPathElement *element)
- {
- LOTBezierPathEnumerationHandler handler = (__bridge LOTBezierPathEnumerationHandler)(info);
- if (handler) {
- handler(element);
- }
- }
|