Development Tip

부드러운 곡선 그리기-필요한 방법

yourdevel 2021. 1. 5. 19:41
반응형

부드러운 곡선 그리기-필요한 방법


이동 중에 iOS 그리기 앱에서 점 세트를 어떻게 다듬습니까? 나는 UIBezierpaths를 시도했지만 포인트 1,2,3,4-2,3,4,5를 이동할 때 교차하는 곳에서 들쭉날쭉 한 끝을 얻었습니다. 스플라인 커브와 다른 모든 유형에 대해 들어 봤습니다. 나는 iPhone 프로그래밍을 처음 접했고 석영 그리기 앱에서 프로그래밍하는 방법을 이해하지 못합니다. 확실한 예는 대단히 감사하겠습니다. 저는 몇 주 동안 서클에서 달리며이 작업에 대한 iOS 코드를 찾을 수없는 것 같습니다. 대부분의 게시물은 Java 시뮬레이션 또는 위키피디아의 곡선 피팅에 대한 페이지로 연결되며 이는 나에게 아무런 효과가 없습니다. 또한 나는 openGL ES로 전환하고 싶지 않습니다. 누군가가 마침내이 순환되는 질문에 답할 수있는 코드를 제공 할 수 있기를 바랍니다.


이것은 교차로에서 가장자리를 남긴 UIBezierPath에 대한 코드였습니다.

아래 답변으로 업데이트 됨

#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];
}

전화 화면


방금 작업중인 프로젝트에서 비슷한 것을 구현했습니다. 내 해결책은 Bezier 스플라인을 사용하는 대신 Catmull-Rom 스플라인을 사용하는 것이 었습니다. 이것들은 베 지어 스플라인 '주위'포인트가 아닌 세트 포인트를 통해 매우 부드러운 곡선을 제공합니다.

// 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

원래 Catmull-Rom 구현은 Erica Sadun의 책 중 하나에있는 일부 코드를 기반으로했으며, 완전히 매끄러운 곡선을 허용하도록 약간 수정했습니다. 이것은 UIBezierPath의 카테고리로 구현되었으며 저에게 매우 잘 맞았습니다.

원래 경로는 빨간색이고 다듬어 진 경로는 녹색입니다.


@Rakesh는 절대적으로 옳습니다. 곡선을 원하면 Catmull-Rom 알고리즘을 사용할 필요가 없습니다. 그리고 그가 제안한 링크는 바로 그것입니다. 그래서 여기에 그의 대답에 대한 추가 사항이 있습니다.

코드 울부 짖는 소리는 않습니다 하지 캐트 멀 롬 알고리즘 및 단위를 사용하지만 (제어 포인트가 당신을 위해 계산) 쿼드 곡선을 그립니다. 이것은 본질적 으로 Rakesh가 제안한 ios 프리 핸드 드로잉 튜토리얼 에서 수행되는 작업 이지만, 독립 실행 형 방법으로 어디서든 (또는 UIBezierPath 범주) 드롭하고 상자에서 쿼드 커브 스플라인을 얻을 수 있습니다.

당신의 배열이 필요 할 CGPoint'에 싸여 s의 NSValues'을 (를)

+ (UIBezierPath *)quadCurvedPathWithPoints:(NSArray *)points
{
    UIBezierPath *path = [UIBezierPath bezierPath];

    NSValue *value = points[0];
    CGPoint p1 = [value CGPointValue];
    [path moveToPoint:p1];

    if (points.count == 2) {
        value = points[1];
        CGPoint p2 = [value CGPointValue];
        [path addLineToPoint:p2];
        return path;
    }

    for (NSUInteger i = 1; i < points.count; i++) {
        value = points[i];
        CGPoint p2 = [value CGPointValue];

        CGPoint midPoint = midPointForPoints(p1, p2);
        [path addQuadCurveToPoint:midPoint controlPoint:controlPointForPoints(midPoint, p1)];
        [path addQuadCurveToPoint:p2 controlPoint:controlPointForPoints(midPoint, p2)];

        p1 = p2;
    }
    return path;
}

static CGPoint midPointForPoints(CGPoint p1, CGPoint p2) {
    return CGPointMake((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
}

static CGPoint controlPointForPoints(CGPoint p1, CGPoint p2) {
    CGPoint controlPoint = midPointForPoints(p1, p2);
    CGFloat diffY = abs(p2.y - controlPoint.y);

    if (p1.y < p2.y)
        controlPoint.y += diffY;
    else if (p1.y > p2.y)
        controlPoint.y -= diffY;

    return controlPoint;
}

결과는 다음과 같습니다. 여기에 이미지 설명 입력


The key to getting two bezier curves to join smoothly is that the relevant control points and the start/end points on the curves must be collinear. Think of the control point and the endpoint as forming a line that's tangent to the curve at the endpoint. If one curve starts at the same point where another ends, and if they both have the same tangent line at that point, the curve will be smooth. Here's a bit of code to illustrate:

- (void)drawRect:(CGRect)rect
{   
#define commonY 117

    CGPoint point1 = CGPointMake(20, 20);
    CGPoint point2 = CGPointMake(100, commonY);
    CGPoint point3 = CGPointMake(200, 50);
    CGPoint controlPoint1 = CGPointMake(50, 60);
    CGPoint controlPoint2 = CGPointMake(20, commonY);
    CGPoint controlPoint3 = CGPointMake(200, commonY);
    CGPoint controlPoint4 = CGPointMake(250, 75);

    UIBezierPath *path1 = [UIBezierPath bezierPath];
    UIBezierPath *path2 = [UIBezierPath bezierPath];

    [path1 setLineWidth:3.0];
    [path1 moveToPoint:point1];
    [path1 addCurveToPoint:point2 controlPoint1:controlPoint1 controlPoint2:controlPoint2];
    [[UIColor blueColor] set];
    [path1 stroke];

    [path2 setLineWidth:3.0];
    [path2 moveToPoint:point2];
    [path2 addCurveToPoint:point3 controlPoint1:controlPoint3 controlPoint2:controlPoint4];
    [[UIColor orangeColor] set];
    [path2 stroke];
}

path1끝은 point2, path2시작 point2, 및 제어 포인트 (2, 3) 공유 같은 Y 값 commonYpoint2. 원하는대로 코드의 값을 변경할 수 있습니다. 이 세 점이 모두 같은 선에있는 한 두 경로는 원활하게 연결됩니다. (위의 코드에서 선은 y = commonY입니다. 선이 X 축과 평행 할 필요는 없습니다. 점이 그런 식으로 동일 선상에 있다는 것을 쉽게 알 수 있습니다.)

위의 코드가 그리는 이미지는 다음과 같습니다.

부드럽게 연결되는 두 경로

코드를 살펴본 후 곡선이 들쭉날쭉 해지는 이유는 제어점을 곡선의 점으로 생각하기 때문입니다. 베 지어 곡선에서 제어점은 일반적으로 곡선에 없습니다. 당신이 곡선에서 제어점을 취하고 있기 때문에, 제어 포인트와의 교차점은 하지 선상 및 경로가 원활히 참여하지 않습니다.


여기에 좋은 대답이 있지만 (user1244109의 대답은 수평 접선 만 지원하고 일반 곡선에는 유용하지 않음) 지나치게 복잡합니다 (Catmull-Rom 팬 죄송합니다).

쿼드 베 지어 곡선을 사용하여 훨씬 더 간단한 방법으로 구현했습니다. 여기에는 시작점, 끝점 및 제어점이 필요합니다. 할 수있는 자연 것은 수 있습니다 시작 및 끝 지점으로 터치 포인트를 사용 할 수 있습니다. 이러지마! 사용할 적절한 제어점이 없습니다. 대신이 아이디어를 시도해보십시오. 터치 포인트를 제어점으로 사용하고 중간 점 을 시작 / 종료점으로 사용하십시오. 이런 식으로 적절한 접선이 보장되며 코드는 어리석은 단순합니다. 알고리즘은 다음과 같습니다.

  1. 포인트 "터치 다운"는 경로의 시작, 그리고 가게 location에서 prevPoint.
  2. 모든 위치, 계산 끌고 들어 midPoint사이의 포인트 currentPointprevPoint.
    1. 처음으로 드래그 한 위치 인 currentPoint경우 선 세그먼트로 추가 합니다.
    2. 미래의 모든 지점 들어있는 쿼드 곡선 추가 종료 상기 midPoint하고,이를 사용 prevPoint은 AS 제어 지점을 . 이렇게하면 이전 지점에서 현재 지점까지 부드럽게 곡선을 그리는 세그먼트가 생성됩니다.
  3. 에 저장 currentPoint하고 prevPoint드래그가 끝날 때까지 # 2를 반복합니다.
  4. 마지막 점을 또 다른 직선 세그먼트로 추가하여 경로를 마무리합니다.

중간 점을 사용하면 곡선이 끝점에서 부드러운 접선이되도록 보장하기 때문에 매우보기 좋은 곡선이됩니다 (첨부 된 사진 참조).

Swift 코드는 다음과 같습니다.

var bezierPath = UIBezierPath()
var prevPoint: CGPoint?
var isFirst = true

override func touchesBegan(touchesSet: Set<UITouch>, withEvent event: UIEvent?) {
    let location = touchesSet.first!.locationInView(self)
    bezierPath.removeAllPoints()
    bezierPath.moveToPoint(location)
    prevPoint = location
}

override func touchesMoved(touchesSet: Set<UITouch>, withEvent event: UIEvent?) {
    let location = touchesSet.first!.locationInView(self)

    if let prevPoint = prevPoint {
        let midPoint = CGPoint(
            x: (location.x + prevPoint.x) / 2,
            y: (location.y + prevPoint.y) / 2,
        )
        if isFirst {
            bezierPath.addLineToPoint(midPoint)
        else {
            bezierPath.addQuadCurveToPoint(midPoint, controlPoint: prevPoint)
        }
        isFirst = false
    }
    prevPoint = location
}

override func touchesEnded(touchesSet: Set<UITouch>, withEvent event: UIEvent?) {
    let location = touchesSet.first!.locationInView(self)
    bezierPath.addLineToPoint(location)
}

또는 포인트 배열이 UIBezierPath있고 한 번 에 구성하려는 경우 :

var points: [CGPoint] = [...]
var bezierPath = UIBezierPath()
var prevPoint: CGPoint?
var isFirst = true

// obv, there are lots of ways of doing this. let's
// please refrain from yak shaving in the comments
for point in points {
    if let prevPoint = prevPoint {
        let midPoint = CGPoint(
            x: (point.x + prevPoint.x) / 2,
            y: (point.y + prevPoint.y) / 2,
        )
        if isFirst {
            bezierPath.addLineToPoint(midPoint)
        }
        else {
            bezierPath.addQuadCurveToPoint(midPoint, controlPoint: prevPoint)
        }
        isFirst = false
    }
    else { 
        bezierPath.moveToPoint(point)
    }
    prevPoint = point
}
if let prevPoint = prevPoint {
    bezierPath.addLineToPoint(prevPoint)
}

내 메모는 다음과 같습니다.

알고리즘의 예


캡처 된 지점에 알고리즘을 적용하기 전에 몇 가지 사항을 관찰해야합니다.

  1. 일반적으로 UIKit은 동일한 거리에서 포인트를 제공하지 않습니다.
  2. 두 CGPoint 사이의 중간 점을 계산해야합니다 [Touch 이동 방식으로 캡처 한]

이제 부드러운 선을 얻기 위해 많은 방법이 있습니다.

때때로 우리는 2 차 다항식 또는 3 차 다항식 또는 catmullRomSpline 알고리즘을 적용하여 달성 할 수 있습니다.

- (float)findDistance:(CGPoint)point lineA:(CGPoint)lineA lineB:(CGPoint)lineB
{
    CGPoint v1 = CGPointMake(lineB.x - lineA.x, lineB.y - lineA.y);
    CGPoint v2 = CGPointMake(point.x - lineA.x, point.y - lineA.y);
    float lenV1 = sqrt(v1.x * v1.x + v1.y * v1.y);
    float lenV2 = sqrt(v2.x * v2.x + v2.y * v2.y);
    float angle = acos((v1.x * v2.x + v1.y * v2.y) / (lenV1 * lenV2));
    return sin(angle) * lenV2;
}

- (NSArray *)douglasPeucker:(NSArray *)points epsilon:(float)epsilon
{
    int count = [points count];
    if(count < 3) {
        return points;
    }

    //Find the point with the maximum distance
    float dmax = 0;
    int index = 0;
    for(int i = 1; i < count - 1; i++) {
        CGPoint point = [[points objectAtIndex:i] CGPointValue];
        CGPoint lineA = [[points objectAtIndex:0] CGPointValue];
        CGPoint lineB = [[points objectAtIndex:count - 1] CGPointValue];
        float d = [self findDistance:point lineA:lineA lineB:lineB];
        if(d > dmax) {
            index = i;
            dmax = d;
        }
    }

    //If max distance is greater than epsilon, recursively simplify
    NSArray *resultList;
    if(dmax > epsilon) {
        NSArray *recResults1 = [self douglasPeucker:[points subarrayWithRange:NSMakeRange(0, index + 1)] epsilon:epsilon];

        NSArray *recResults2 = [self douglasPeucker:[points subarrayWithRange:NSMakeRange(index, count - index)] epsilon:epsilon];

        NSMutableArray *tmpList = [NSMutableArray arrayWithArray:recResults1];
        [tmpList removeLastObject];
        [tmpList addObjectsFromArray:recResults2];
        resultList = tmpList;
    } else {
        resultList = [NSArray arrayWithObjects:[points objectAtIndex:0], [points objectAtIndex:count - 1],nil];
    }

    return resultList;
}

- (NSArray *)catmullRomSplineAlgorithmOnPoints:(NSArray *)points segments:(int)segments
{
    int count = [points count];
    if(count < 4) {
        return points;
    }

    float b[segments][4];
    {
        // precompute interpolation parameters
        float t = 0.0f;
        float dt = 1.0f/(float)segments;
        for (int i = 0; i < segments; i++, t+=dt) {
            float tt = t*t;
            float ttt = tt * t;
            b[i][0] = 0.5f * (-ttt + 2.0f*tt - t);
            b[i][1] = 0.5f * (3.0f*ttt -5.0f*tt +2.0f);
            b[i][2] = 0.5f * (-3.0f*ttt + 4.0f*tt + t);
            b[i][3] = 0.5f * (ttt - tt);
        }
    }

    NSMutableArray *resultArray = [NSMutableArray array];

    {
        int i = 0; // first control point
        [resultArray addObject:[points objectAtIndex:0]];
        for (int j = 1; j < segments; j++) {
            CGPoint pointI = [[points objectAtIndex:i] CGPointValue];
            CGPoint pointIp1 = [[points objectAtIndex:(i + 1)] CGPointValue];
            CGPoint pointIp2 = [[points objectAtIndex:(i + 2)] CGPointValue];
            float px = (b[j][0]+b[j][1])*pointI.x + b[j][2]*pointIp1.x + b[j][3]*pointIp2.x;
            float py = (b[j][0]+b[j][1])*pointI.y + b[j][2]*pointIp1.y + b[j][3]*pointIp2.y;
            [resultArray addObject:[NSValue valueWithCGPoint:CGPointMake(px, py)]];
        }
    }

    for (int i = 1; i < count-2; i++) {
        // the first interpolated point is always the original control point
        [resultArray addObject:[points objectAtIndex:i]];
        for (int j = 1; j < segments; j++) {
            CGPoint pointIm1 = [[points objectAtIndex:(i - 1)] CGPointValue];
            CGPoint pointI = [[points objectAtIndex:i] CGPointValue];
            CGPoint pointIp1 = [[points objectAtIndex:(i + 1)] CGPointValue];
            CGPoint pointIp2 = [[points objectAtIndex:(i + 2)] CGPointValue];
            float px = b[j][0]*pointIm1.x + b[j][1]*pointI.x + b[j][2]*pointIp1.x + b[j][3]*pointIp2.x;
            float py = b[j][0]*pointIm1.y + b[j][1]*pointI.y + b[j][2]*pointIp1.y + b[j][3]*pointIp2.y;
            [resultArray addObject:[NSValue valueWithCGPoint:CGPointMake(px, py)]];
        }
    }

    {
        int i = count-2; // second to last control point
        [resultArray addObject:[points objectAtIndex:i]];
        for (int j = 1; j < segments; j++) {
            CGPoint pointIm1 = [[points objectAtIndex:(i - 1)] CGPointValue];
            CGPoint pointI = [[points objectAtIndex:i] CGPointValue];
            CGPoint pointIp1 = [[points objectAtIndex:(i + 1)] CGPointValue];
            float px = b[j][0]*pointIm1.x + b[j][1]*pointI.x + (b[j][2]+b[j][3])*pointIp1.x;
            float py = b[j][0]*pointIm1.y + b[j][1]*pointI.y + (b[j][2]+b[j][3])*pointIp1.y;
            [resultArray addObject:[NSValue valueWithCGPoint:CGPointMake(px, py)]];
        }
    }
    // the very last interpolated point is the last control point
    [resultArray addObject:[points objectAtIndex:(count - 1)]]; 

    return resultArray;
}

이를 위해이 방법을 사용해야합니다. BezierSpline 코드는 C #으로 베 지어 스플라인에 대한 제어점 배열을 생성합니다. 나는이 코드를 Objective C로 변환했고 그것은 나를 위해 훌륭하게 작동합니다.

코드를 C #에서 Objective C로 변환하려면 C #을 모르더라도 C # 코드를 한 줄씩 이해해야합니다. C ++ / Java를 알고 있어야합니다.

변환하는 동안 :

  1. 여기에 사용 된 Point 구조체를 CGPoint로 바꿉니다.

  2. Point 배열을 NSMutableArray로 바꾸고 그 안에 CGPoints를 감싸는 NSvalue를 저장합니다.

  3. 모든 이중 배열을 NSMutableArrays로 바꾸고 NSNumber를 이중으로 감싸서 저장하십시오.

  4. 배열 요소에 액세스하기 위해 아래 첨자의 경우 objectAtIndex : 메소드를 사용하십시오.

  5. 특정 인덱스에 객체를 저장하려면 replaceObjectAtIndex : withObject :를 사용하십시오.

    NSMutableArray는 linkedList이고 C #에서 사용하는 것은 동적 배열이므로 이미 기존 인덱스가 있음을 기억하십시오. 귀하의 경우 NSMutableArray가 비어 있으면 C # 코드와 같이 임의의 인덱스에 개체를 저장할 수 없습니다. 이 C # 코드에서 때때로 인덱스 0 앞에 인덱스 1을 채우면 인덱스 1이 존재하므로 그렇게 할 수 있습니다. 여기서 NSMutabelArrays에서 replaceObject를 호출하려면 인덱스 1이 있어야합니다. 그래서 무엇이든 저장하기 전에 NSMutableArray에 n 개의 NSNull 객체를 추가 할 메소드를 만드십시오.

또한 :

이 논리에는 점 배열을 받아들이고 두 개의 배열을 제공하는 정적 메서드가 있습니다.

  1. 첫 번째 제어점의 배열.

  2. 두 번째 제어점의 배열.

이 배열은 첫 번째 배열에서 전달하는 두 점 사이의 각 곡선에 대한 첫 번째 및 두 번째 제어점을 유지합니다.

제 경우에는 이미 모든 포인트를 가지고 있었고 그것들을 통해 곡선을 그릴 수있었습니다.

그리는 동안 부드러운 곡선이 통과하기를 원하는 점 세트를 제공하는 방법이 필요합니다.

setNeedsDisplay를 호출하여 새로 고침하고 첫 번째 배열의 인접한 두 점 사이에 UIBezierPath에 불과한 스플라인을 그립니다. 두 제어점 배열에서 제어점을 인덱스 현명하게 가져옵니다.

귀하의 경우 문제는 모든 중요한 포인트를 이동하면서 이해하기 어렵다는 것입니다.

할 수있는 일은 다음과 같습니다. 단순히 손가락을 움직이는 동안 이전 지점과 현재 지점 사이에 직선을 계속 그립니다. 선이 너무 작아서 확대하지 않으면 육안으로 볼 수없는 작은 직선이됩니다.

최신 정보

위 링크의 Objective C 구현에 관심이있는 사람은 누구나 참조 할 수 있습니다.

GitHub 저장소.

언젠가 다시 썼고 ARC를 지원하지 않지만 쉽게 편집하고 릴리스 및 자동 릴리스 호출을 제거하고 ARC와 함께 작동하도록 할 수 있습니다.

이것은 베 지어 스플라인을 사용하여 결합하려는 점 세트에 대해 두 개의 제어점 배열을 생성합니다.


이 정도의 코드를 작성할 필요가 없습니다.

ios freehand drawing tutorial을 참조하십시오 . 그리기를 정말 매끄럽게하고, 계속해서 그리는 경우에도 성능이 저하되지 않도록 캐시 메커니즘이 있습니다.


나는 가장자리를 아주 멋지게 매끄럽게하는 경향이있는 Bezier 곡선 그리기에 대한 약간의 수정을 설명 하는 꽤 좋은 튜토리얼찾았 습니다 . 그것은 본질적으로 Caleb이 제어점과 동일한 선에 연결하는 끝점을 두는 것에 대해 위에서 언급 한 것입니다. 내가 한동안 읽은 최고의 튜토리얼 중 하나입니다. 그리고 완벽하게 작동하는 Xcode 프로젝트가 함께 제공됩니다.


위의 모든 것을 시도했지만 작동하지 않습니다. 대답 중 하나는 나에게도 깨진 결과를 낳습니다. 더 많이 검색하면 https://github.com/sam-keene/uiBezierPath-hermite-curve를 찾았습니다 . 이 코드를 작성하지는 않았지만 구현했으며 정말 잘 작동합니다. UIBezierPath + Interpolation.m / h 및 CGPointExtension.m / h를 복사하십시오. 그런 다음 다음과 같이 사용합니다.

UIBezierPath *path = [UIBezierPath interpolateCGPointsWithHermite:arrayPoints closed:YES];

전반적으로 강력하고 깔끔한 솔루션입니다.


빠른:

        let point1 = CGPoint(x: 50, y: 100)

        let point2 = CGPoint(x: 50 + 1 * CGFloat(60) * UIScreen.main.bounds.width / 375, y: 200)

        let point3 = CGPoint(x: 50 + 2 * CGFloat(60) * UIScreen.main.bounds.width / 375, y: 250)

        let point4 = CGPoint(x: 50 + 3 * CGFloat(60) * UIScreen.main.bounds.width / 375, y: 50)

        let point5 = CGPoint(x: 50 + 4 * CGFloat(60) * UIScreen.main.bounds.width / 375, y: 100)


        let points = [point1, point2, point3, point4, point5]

        let bezier = UIBezierPath()


        let count = points.count

        var prevDx = CGFloat(0)
        var prevDy = CGFloat(0)

        var prevX = CGFloat(0)
        var prevY = CGFloat(0)

        let div = CGFloat(7)


        for i in 0..<count {
            let x = points[i].x
            let y = points[i].y

            var dx = CGFloat(0)
            var dy = CGFloat(0)

            if (i == 0) {
                bezier.move(to: points[0])
                let nextX = points[i + 1].x
                let nextY = points[i + 1].y

                prevDx = (nextX - x) / div
                prevDy = (nextY - y) / div
                prevX = x
                prevY = y
            } else if (i == count - 1) {
                dx = (x - prevX) / div
                dy = (y - prevY) / div
            } else {

                let nextX = points[i + 1].x
                let nextY = points[i + 1].y
                dx = (nextX - prevX) / div;
                dy = (nextY - prevY) / div;
            }

            bezier.addCurve(to: CGPoint(x: x, y: y), controlPoint1: CGPoint(x: prevX + prevDx, y: prevY + prevDy), controlPoint2: CGPoint(x: x - dx, y: y - dy))

            prevDx = dx;
            prevDy = dy;
            prevX = x;
            prevY = y;
        }

다음은 Swift 4/5의 코드입니다.

func quadCurvedPathWithPoint(points: [CGPoint] ) -> UIBezierPath {
    let path = UIBezierPath()
    if points.count > 1 {
        var prevPoint:CGPoint?
        for (index, point) in points.enumerated() {
            if index == 0 {
                path.move(to: point)
            } else {
                if index == 1 {
                    path.addLine(to: point)
                }
                if prevPoint != nil {
                    let midPoint = self.midPointForPoints(from: prevPoint!, to: point)
                    path.addQuadCurve(to: midPoint, controlPoint: controlPointForPoints(from: midPoint, to: prevPoint!))
                    path.addQuadCurve(to: point, controlPoint: controlPointForPoints(from: midPoint, to: point))
                }
            }
            prevPoint = point
        }
    }
    return path
}

func midPointForPoints(from p1:CGPoint, to p2: CGPoint) -> CGPoint {
    return CGPoint(x: (p1.x + p2.x) / 2, y: (p1.y + p2.y) / 2)
}

func controlPointForPoints(from p1:CGPoint,to p2:CGPoint) -> CGPoint {
    var controlPoint = midPointForPoints(from:p1, to: p2)
    let  diffY = abs(p2.y - controlPoint.y)
    if p1.y < p2.y {
        controlPoint.y = controlPoint.y + diffY
    } else if ( p1.y > p2.y ) {
        controlPoint.y = controlPoint.y - diffY
    }
    return controlPoint
}

참조 URL : https://stackoverflow.com/questions/8702696/drawing-smooth-curves-methods-needed

반응형