Episode #149

How Bézier Paths Work

12 minutes
Published on December 18, 2014

This video is only available to subscribers. Get access to this video and 584 others.

Have you ever wondered how bezier paths work? What are the control points, and how exactly do they affect the line? In this episode we'll build our own visualization of how a bézier path is constructed to help understand it better.

Episode Links

Drawing a Bezier Curve Between 2 Points

Drawing a bezier curve requires 2 control points, which influence the curve of the line:

CGContextSetStrokeColorWithColor(context, UIColor.blueColor().CGColor)
CGContextSetLineWidth(context, 4)
CGContextMoveToPoint(context, startPoint.x, startPoint.y)
CGContextMoveToPoint(context, startPoint.x, startPoint.y)
CGContextAddCurveToPoint(context,
    control1.x,
    control1.y,
    control2.x,
    control2.y,
    endPoint.x,
    endPoint.y
)
CGContextStrokePath(context)

How does this work? To understand we can draw some lines & points to visualize it.

Some Utility Functions

First we need some utility functions to make drawing a little easier. The first is to draw a point with a specified radius and color:

  func drawPoint(context: CGContextRef, point: Point, color: UIColor, radius: CGFloat = 4, outlineColor: UIColor? = nil) {
        let rect = CGRectMake(point.x - radius, point.y - radius, radius * 2, radius * 2)
        CGContextSetFillColorWithColor(context, color.CGColor)
        CGContextFillEllipseInRect(context, rect)

        if let outline = outlineColor {
            CGContextSetStrokeColorWithColor(context, outline.CGColor)
            CGContextSetLineWidth(context, 1)
            CGContextStrokeEllipseInRect(context, rect)
        }
  }

We also need a way to interpolate a position on a line between two points:

    func interpolatePosition(p1: Point, p2: Point, t: CGFloat) -> Point {
        return Point(
            x: (1 - t) * p1.x + t * p2.x,
            y: (1 - t) * p1.y + t * p2.y
        )
    }

Connecting the Lines

Given 4 points (start, control1, control2, end) you can connect the points with 3 lines like this:

        CGContextMoveToPoint(context, startPoint.x, startPoint.y)
        if control1 != nil {
            CGContextAddLineToPoint(context, control1!.x, control1!.y)
        }

        if control2 != nil {
            CGContextAddLineToPoint(context, control2!.x, control2!.y)
        }
        CGContextAddLineToPoint(context, endPoint.x, endPoint.y)
        CGContextSetLineWidth(context, 1)
        CGContextSetStrokeColorWithColor(context, UIColor.lightGrayColor().CGColor)
        CGContextStrokePath(context)

Interpolating points along the lines

Now we can create interpolated points on each line segment, t1, t2, t3. As we change t these points travel along their respective lines.

            let t1 = interpolatePosition(startPoint, p2: control1!, t: t)
            let t2 = interpolatePosition(control1!, p2: control2!, t: t)
            let t3 = interpolatePosition(control2!, p2: endPoint, t: t)
            drawPoint(context, point: t1, color: UIColor.greenColor(), radius: 4)
            drawPoint(context, point: t2, color: UIColor.greenColor(), radius: 4)
            drawPoint(context, point: t3, color: UIColor.greenColor(), radius: 4)

We can then use these points to connect 2 lines:

            CGContextMoveToPoint(context, t1.x, t1.y)
            CGContextAddLineToPoint(context, t2.x, t2.y)
            CGContextAddLineToPoint(context, t3.x, t3.y)
            CGContextSetStrokeColorWithColor(context, UIColor(white: 0.8, alpha: 1.0).CGColor)
            CGContextStrokePath(context)

This gives us 2 lines, on which we can interpolate 2 more points along time t:

            let u1 = interpolatePosition(t1, p2: t2, t: t)
            let u2 = interpolatePosition(t2, p2: t3, t: t)            
            drawPoint(context, point: u1, color: UIColor.purpleColor(), radius: 4)
            drawPoint(context, point: u2, color: UIColor.purpleColor(), radius: 4)

And with these 2 points we can form another line:

            CGContextMoveToPoint(context, u1.x, u1.y)
            CGContextAddLineToPoint(context, u2.x, u2.y)
            CGContextSetStrokeColorWithColor(context, UIColor(white: 0.7, alpha: 1.0).CGColor)
            CGContextStrokePath(context)

Finally, we can interpolate a point along this line, also across time t. This point, happens to trace the bézier curve exactly.

            let v1 = interpolatePosition(u1, p2: u2, t: t)
            drawPoint(context, point: v1, color: UIColor.orangeColor(), radius: 4)

This episode uses Swift 1.1, Xcode 6.