How do you smooth a set of points in an iOS drawing app WHILE MOVING? I have tried UIBezierpaths but all I get are jagged ends where they intersect, when I just shift the points 1,2,3,4 - 2,3,4,5. I have heard of spline curves and all the other types. I am quite new to iPhone programming and do not understand how to program it in my quartz drawing app. A solid example would be greatly appreciated, I have spent weeks running in circles and I can never seem to find any iOS code for this task. Most of the posts just link to a java simulation or pages on wikipedia about curve fitting which does nothing for me. Also I do not want to switch to openGL ES. I hope someone can finally provide code to answer this circulating question.
This was my code for the UIBezierPath which left edges at intersection///
UPDATED TO AN ANSWER BELOW
#define VALUE(_INDEX_) [NSValue valueWithCGPoint:points[_INDEX_]] #define POINT(_INDEX_) [(NSValue *)[points objectAtIndex:_INDEX_] CGPointValue] - (UIBezierPath*)smoothedPathWithGranularity:(NSInteger)granularity { NSMutableArray *points = [(NSMutableArray*)[self pointsOrdered] mutableCopy]; if (points.count < 4) return [self bezierPath]; // Add control points to make the math make sense [points insertObject:[points objectAtIndex:0] atIndex:0]; [points addObject:[points lastObject]]; UIBezierPath *smoothedPath = [self bezierPath]; [smoothedPath removeAllPoints]; [smoothedPath moveToPoint:POINT(0)]; for (NSUInteger index = 1; index < points.count - 2; index++) { CGPoint p0 = POINT(index - 1); CGPoint p1 = POINT(index); CGPoint p2 = POINT(index + 1); CGPoint p3 = POINT(index + 2); // now add n points starting at p1 + dx/dy up until p2 using Catmull-Rom splines for (int i = 1; i < granularity; i++) { float t = (float) i * (1.0f / (float) granularity); float tt = t * t; float ttt = tt * t; CGPoint pi; // intermediate point pi.x = 0.5 * (2*p1.x+(p2.x-p0.x)*t + (2*p0.x-5*p1.x+4*p2.x-p3.x)*tt + (3*p1.x-p0.x-3*p2.x+p3.x)*ttt); pi.y = 0.5 * (2*p1.y+(p2.y-p0.y)*t + (2*p0.y-5*p1.y+4*p2.y-p3.y)*tt + (3*p1.y-p0.y-3*p2.y+p3.y)*ttt); [smoothedPath addLineToPoint:pi]; } // Now add p2 [smoothedPath addLineToPoint:p2]; } // finish by adding the last point [smoothedPath addLineToPoint:POINT(points.count - 1)]; return smoothedPath; } - (PVPoint *)pointAppendingCGPoint:(CGPoint)CGPoint { PVPoint *newPoint = [[PVPoint alloc] initInsertingIntoManagedObjectContext:[self managedObjectContext]]; [newPoint setCGPoint:CGPoint]; [newPoint setOrder:[NSNumber numberWithUnsignedInteger:[[self points] count]]]; [[self mutableSetValueForKey:@"points"] addObject:newPoint]; [(NSMutableArray *)[self pointsOrdered] addObject:newPoint]; [[self bezierPath] addLineToPoint:CGPoint]; return [newPoint autorelease]; if ([self bezierPath] && [pointsOrdered count] > 3) { PVPoint *control1 = [pointsOrdered objectAtIndex:[pointsOrdered count] - 2]; PVPoint *control2 = [pointsOrdered objectAtIndex:[pointsOrdered count] - 1]; [bezierPath moveToPoint:[[pointsOrdered objectAtIndex:[pointsOrdered count] - 3] CGPoint]]; [[self bezierPath] addCurveToPoint:CGPoint controlPoint1:[control1 CGPoint] controlPoint2:[control2 CGPoint]]; } } - (BOOL)isComplete { return [[self points] count] > 1; } - (UIBezierPath *)bezierPath { if (!bezierPath) { bezierPath = [UIBezierPath bezierPath]; for (NSUInteger p = 0; p < [[self points] count]; p++) { if (!p) [bezierPath moveToPoint:[(PVPoint *)[[self pointsOrdered] objectAtIndex:p] CGPoint]]; else [bezierPath addLineToPoint:[(PVPoint *)[[self pointsOrdered] objectAtIndex:p] CGPoint]]; } [bezierPath retain]; } return bezierPath; } - (CGPathRef)CGPath { return [[self bezierPath] CGPath]; }
The appropriate answer is option – (c) Pen. Explanation : This Pen Tool icon is similar to a normal pen but it functions in an another way. It is used to draw the precise path, straight lines or smooth flowing curves. We can also make changes in the angles and length of a straight lines, slopes of the curve shapes.
To create an arc, you can specify combinations of center, endpoint, start point, radius, angle, chord length, and direction values. Arcs are drawn in a counterclockwise direction by default. Hold down the Ctrl key as you drag to draw in a clockwise direction. The following prompts are displayed.
I just implemented something similar in a project I am working on. My solution was to use a Catmull-Rom spline instead of using Bezier splines. These provide a very smooth curve THROUGH a set a points rather then a bezier spline 'around' points.
// Based on code from Erica Sadun #import "UIBezierPath+Smoothing.h" void getPointsFromBezier(void *info, const CGPathElement *element); NSArray *pointsFromBezierPath(UIBezierPath *bpath); #define VALUE(_INDEX_) [NSValue valueWithCGPoint:points[_INDEX_]] #define POINT(_INDEX_) [(NSValue *)[points objectAtIndex:_INDEX_] CGPointValue] @implementation UIBezierPath (Smoothing) // Get points from Bezier Curve void getPointsFromBezier(void *info, const CGPathElement *element) { NSMutableArray *bezierPoints = (__bridge NSMutableArray *)info; // Retrieve the path element type and its points CGPathElementType type = element->type; CGPoint *points = element->points; // Add the points if they're available (per type) if (type != kCGPathElementCloseSubpath) { [bezierPoints addObject:VALUE(0)]; if ((type != kCGPathElementAddLineToPoint) && (type != kCGPathElementMoveToPoint)) [bezierPoints addObject:VALUE(1)]; } if (type == kCGPathElementAddCurveToPoint) [bezierPoints addObject:VALUE(2)]; } NSArray *pointsFromBezierPath(UIBezierPath *bpath) { NSMutableArray *points = [NSMutableArray array]; CGPathApply(bpath.CGPath, (__bridge void *)points, getPointsFromBezier); return points; } - (UIBezierPath*)smoothedPathWithGranularity:(NSInteger)granularity; { NSMutableArray *points = [pointsFromBezierPath(self) mutableCopy]; if (points.count < 4) return [self copy]; // Add control points to make the math make sense [points insertObject:[points objectAtIndex:0] atIndex:0]; [points addObject:[points lastObject]]; UIBezierPath *smoothedPath = [self copy]; [smoothedPath removeAllPoints]; [smoothedPath moveToPoint:POINT(0)]; for (NSUInteger index = 1; index < points.count - 2; index++) { CGPoint p0 = POINT(index - 1); CGPoint p1 = POINT(index); CGPoint p2 = POINT(index + 1); CGPoint p3 = POINT(index + 2); // now add n points starting at p1 + dx/dy up until p2 using Catmull-Rom splines for (int i = 1; i < granularity; i++) { float t = (float) i * (1.0f / (float) granularity); float tt = t * t; float ttt = tt * t; CGPoint pi; // intermediate point pi.x = 0.5 * (2*p1.x+(p2.x-p0.x)*t + (2*p0.x-5*p1.x+4*p2.x-p3.x)*tt + (3*p1.x-p0.x-3*p2.x+p3.x)*ttt); pi.y = 0.5 * (2*p1.y+(p2.y-p0.y)*t + (2*p0.y-5*p1.y+4*p2.y-p3.y)*tt + (3*p1.y-p0.y-3*p2.y+p3.y)*ttt); [smoothedPath addLineToPoint:pi]; } // Now add p2 [smoothedPath addLineToPoint:p2]; } // finish by adding the last point [smoothedPath addLineToPoint:POINT(points.count - 1)]; return smoothedPath; } @end
The original Catmull-Rom implementation is based on some code from Erica Sadun in one of her books, I modified it slightly to allow for a full smoothed curve. This is implemented as a category on UIBezierPath and worked out very well for me.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With