14

I am using UIBezierPath, but this question concerns control points for the paths, not the drawing. Given a set of points, I can render a path. However, I have not been able to figure out how to calculate the control points to have a smooth line like in a photo curves editor ( How to implement a Photoshop Curves editor in UIKit ).

The closest answer I've seen is here: how can i trace the finger movement on touch for drawing smooth curves?

However, I still cannot grasp the proper calculation. To sum it up in code:

for (int i = 0; i< points; i++) 
{
     ...

     [path addQuadCurveToPoint:nextPoint controlPoint:WTF];
}
Community
  • 1
  • 1
akaru
  • 6,299
  • 9
  • 63
  • 102

2 Answers2

13

The image you linked to is an example that doesn't use quadratic curves, so I'm going to run with the image and not the code.

A bezier path on ios (and os x) underneath is basically a list of drawing commands and points. eg:

[path moveTo:CGMakePoint(1,1)];
[path curveToPoint:(10,10) controPoint1:(3,7) controlPoint2:(4,1)];
[path curveToPoint:(10,10) controPoint1:(15,17) controlPoint2:(21,11)];
[path closePath];

Results in:

moveto (1,1)      
curveto (10,10) (3,7) (4,1) 
curveto (20,0) (15,17) (21,11)    
closepath 

Control points on a bezier path control the direction and rate of curve out of a point. The first control point(cp) controls the direction and rate of curve exiting the previous point and the second cp controls the same for the point you're curving to. For a quadratic curve (what you get using addQuadCurveToPoint:controlPoint: ), both of these points are the same, as you can see in the docs for the method here.

Getting a smooth curve along a set of points involves making cp1 and cp2 be colinear with each other and that line be parallel to the points at either end of that segment.

Annotated curve

This would look something like:

[path moveTo:2];
[path curveTo:3 controlPoint1:cp1 controlPoint2:cp2];

cp1 and cp2 can be computed by choosing some constant line length and doing some geometry (i forget all my line equations right now but they're easily googleable )

Going to use #-># to designate a segment and #->#(cp#) to designate a control point for that segment's curveto call.

The next issue is making the curve smooth from the 2->3 segment going into 3->4 segment. At this point in your code, you should have a control point calculated for 2->3(cp2). Given your constant line length from before (this will control how sharp of a curve you get), you can calculate a cp1 for 3->4 by getting a point colinear with 2->3(cp2) and point 3 in the diagram. Then calculate a 3->4(cp2) that's colinear with 3->4(cp1) and parallel to the line that point 3 and point 4 form. Rinse and repeat through your points array.

waltflanagan
  • 1,432
  • 11
  • 9
  • 1
    Thanks for the response. Unfortunately, after reading through the links I am still unable to determine the right method. I blame my brain. – akaru Nov 15 '11 at 12:31
  • There is no way you write code for computing the control points. – Abdul Yasin Aug 22 '16 at 12:42
4

I'm not sure how much this will help, but I had to do something similar to implement a curved path for notes to follow in this app, (www.app.net/hereboy). Essentially, it is a path with three curves.

To do this I created 4 points per curve, a starting point, an ending point, and two control points at the 25% mark and the 75% mark.

Here is the code that I wrote to do this:

//create points along the keypath for curve.
CGMutablePathRef curvedPath = CGPathCreateMutable();
const int TOTAL_POINTS = 3;
int horizontalWiggle = 15;

int stepChangeX = (endPoint.x - viewOrigin.x) / TOTAL_POINTS;
int stepChangeY = (endPoint.y - viewOrigin.y) / TOTAL_POINTS;

for(int i = 0; i < TOTAL_POINTS; i++) {
    int startX = (int)(viewOrigin.x + i * stepChangeX);
    int startY = (int)(viewOrigin.y + i * stepChangeY);

    int endX = (int)(viewOrigin.x + (i+1) * stepChangeX);
    int endY = (int)(viewOrigin.y + (i+1) * stepChangeY);

    int cpX1 = (int)(viewOrigin.x + (i+0.25) * stepChangeX);
    if((i+1)%2) {
        cpX1 -= horizontalWiggle;
    } else {
        cpX1 += horizontalWiggle;
    }
    int cpY1 = (int)(viewOrigin.y + (i+0.25) * stepChangeY);

    int cpX2 = (int)(viewOrigin.x + (i+0.75) * stepChangeX);
    if((i+1)%2) {
        cpX2 -= horizontalWiggle;
    } else {
        cpX2 += horizontalWiggle;
    }
    int cpY2 = (int)(viewOrigin.y + (i+0.75) * stepChangeY);

    CGPathMoveToPoint(curvedPath, NULL, startX, startY);
    CGPathAddCurveToPoint(curvedPath, NULL, cpX1, cpY1, cpX2, cpY2, endX, endY);
}

Good luck!

Andrew Zimmer
  • 3,183
  • 1
  • 19
  • 18