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:
- They must end on the same point ( f(x) = g(x) ), and
- 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:
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:
(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:
What this says is that the slope of the line at the beginning of the curve is P1 – P0 and the slope at the end of the curve is P3 – P2.
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:
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:
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:
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 + x - size/2 yloc = loc + 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 \ + 3*t*math.pow(1-t,2) * points \ + 3*(1-t)*math.pow(t,2) * points \ + math.pow(t,3) * points yloc = math.pow(1-t,3) * points \ + 3*t*math.pow(1-t,2) * points \ + 3*(1-t)*math.pow(t,2) * points \ + math.pow(t,3) * points 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] + points[i+1]) / 2.0 yloc = (points[i] + points[i+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")
1 Comment »