Drawing Smooth Curves - Methods Needed

前端 未结 12 2261
长情又很酷
长情又很酷 2020-11-28 17:13

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 po

12条回答
  •  感动是毒
    2020-11-28 17:58

    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.

    The original path is in red, the smoothed path is in green.

提交回复
热议问题