Not too long ago, I answered a question on stack overflow where the question was about how to join multiple bézier curves. What he wanted to do was to draw a continuous “Bézier Curve” along several thousand points as though it were one curve (assume that we’re talking about a cubic bézier curves). I answered the question fairly thoroughly, however I decided that I would take a minute to expand some more on the answer.

The part of the question that he was having trouble with was the idea of creating one continuously smooth curve. If you remember way back to Calculus I, for two curves to be “smooth,” the following must be true:

  1. They must end on the same point ( f(x) = g(x) ), and
  2. Their derivatives at that point must be the same ( f’(x) = g’(x) )

In order to do this, we have to look at the equation for a Bézier curve:

Cubic Bezier Equation

Examining this equation, we notice that B(0) = P0 and B(1) = P3. So in order to satisfy the first condition for two curves to be smooth, all we need is for P3 of the first curve is equal to P0 of the second curve.

To satisfy the second condition, we need the derivative:

Bezier Derivative

(Click on the image to see the steps involved in deriving this equation)

Now, by evaluating when t=0 and when t=1, we get the following:

Bezier Derivatives at Beginning and End

What this says is that the slope of the line at the beginning of the curve is P1P0 and the slope at the end of the curve is P3P2.

Now it would be tempting to think that the factor of 3 changes the slope, however remember that when we subtract two points, the result is a vector, thus the 3 is merely changing the magnitude of the vector, not its direction.

So what does this mean? It means that in order for our two Bézier curves to be smooth, the slope between P2 and P3 in the first curve must be the same as the slope between P0 and P1 in the second curve. In other words, they must be co-linear!

To clarify a bit, lets take an example where we have two sets of points (A1, A2, A3, & A4 and B1, B2, B3, & B4). In order for these points to create one smooth Bézier curve, the following two facts must be true:

  • They must end on the same point (A4 == B1)
  • A3, A4, and B2 must be colinear (same as saying A3, B1, and B2 must be colinear)

And that’s it!

So in order to make an arbitrary set of points form a smooth curve, we need to force these two conditions to be true. To do this, lets say that we have the following points:

Bezier_1

To make it so that these points form a smooth curve, lets add some extra points. We’ll place a new point at the midpoint of every other pair as shown:

Bezier_2

We can now draw bézier curves between points 0-3, 3-6, 6-9, etc., and we can be sure that it will form a smooth curve:

Bezier_3

And that is all there is to it. Here’s the same concept written in python (PIL is required):

from PIL import Image
import math

#
#   draws a single point on our image
#
def drawPoint( img, loc, size=5, color=(0,0,0) ):
    px = img.load()
    for x in range(size):
        for y in range(size):
            xloc = loc[0] + x - size/2
            yloc = loc[1] + y - size/2
            px[ xloc, yloc ] = color


#
#   draws a simple bezier curve with 4 points
#            
def drawCurve( img, points ):

    steps = 20
    for i in range(steps):

        t = i / float(steps)

        xloc = math.pow(1-t,3) * points[0][0] \
             + 3*t*math.pow(1-t,2) * points[1][0] \
             + 3*(1-t)*math.pow(t,2) * points[2][0] \
             + math.pow(t,3) * points[3][0]
        yloc = math.pow(1-t,3) * points[0][1] \
             + 3*t*math.pow(1-t,2) * points[1][1] \
             + 3*(1-t)*math.pow(t,2) * points[2][1] \
             + math.pow(t,3) * points[3][1]

        drawPoint( img, (xloc,yloc), size=2 )


#
#   draws a bezier curve with any number of points
#
def drawBezier( img, points ):

    for i in range(0,len(points),3):
        if( i+4 < len(points) ):
            drawCurve( img, points[i:i+4] )


#
#   draws a smooth bezier curve by adding points that
#   force smoothness
#
def drawSmoothBezier( img, points ):

    newpoints = []

    for i in range(len(points)):

        # add the next point (and draw it)
        newpoints.append(points[i])
        drawPoint( img, points[i], color=(255,0,0) )

        if( i%2 == 0 and i>0 and i+1<len(points) ):

            # calculate the midpoint
            xloc = (points[i][0] + points[i+1][0]) / 2.0
            yloc = (points[i][1] + points[i+1][1]) / 2.0

            # add the new point (and draw it)
            newpoints.append( (xloc, yloc) )
            drawPoint( img, (xloc, yloc), color=(0,255,0) )

    drawBezier( img, newpoints )



#   Create the image
myImage = Image.new("RGB",(627,271),(255,255,255))

#   Create the points
points = [  (54,172), 
            (121,60), 
            (220,204), 
            (284,56), 
            (376,159), 
            (444,40), 
            (515,228), 
            (595,72) ]

#   Draw the curve
drawSmoothBezier( myImage, points )

#   Save the image
myImage.save("myfile.png","PNG")