Bezier Curves from the Ground Up
This post is also available in Japanese: 一から学ぶベジェ曲線.
How do you describe a straight line segment? We might think about a line segment in terms of its endpoints. Let’s call those endpoints \( P_0 \) and \( P_1 \).
To define the line segment rigorously, we might say “the set of all points along the line through \( P_0 \) and \( P_1 \) which lie between \( P_0 \) and \( P_1 \)“, or perhaps this:
$$ L(t) = (1 - t) P_0 + t P_1, 0 \le t \le 1 $$
Conveniently, this definition let’s us easily find the coordinate of the point any portion of the way along that line segment. The midpoint, for instance, lies at \( L(0.5) \).
We can, in fact, linearly interpolate to any value we want between the two points, with arbitrary precision. This allows us to do fancier things, like trace the line by having the \( t \) in \( L(t) \) be a function of time.
If you got this far, you might now be wondering, “What does this have to do with curves?“. Well, it seems quite intuitive that you can precisely describe a line segment with only two points. How might you go about precisely describing this?
It turns out that this particular kind of curve can be described by only 3 points!
This is called a Quadratic Bezier Curve. A line segment, donning a fancier hat, might be called a Linear Bezier Curve. Let’s investigate why.
First, let’s consider what it looks like when we interpolate between \( P_0 \) and \( P_1 \) while simultaneously interpolating between \( P_1 \) and \( P_2 \).
Now let’s linearly interpolate between \( B_{0, 1}(t) \) and \( B_{1, 2}(t) \)…
Notice that the equation for \( B_{0, 1, 2}(t) \) looks remarkably similar to the equations for \( B_{0, 1} \) and \( B_{1, 2} \). Let’s see what happens when we trace the path of \( B_{0, 1, 2}(t) \).
We get our curve!
Higher Order Bezier Curves
Just as we get a quadratic bezier by interpolating between two linear bezier curves, we get a cubic bezier curve by interpolating between two quadratic bezier curves:
You may have a sneaking suspicion at this point that there’s a nice recursive definition lurking here. And indeed there is:
Or, expressed (concisely but inefficiently) in TypeScript, it might look like this:
type Point = [number, number];
function B(P: Point[], t: number): Point {
if (P.length === 1) return P[0];
const left: Point = B(P.slice(0, P.length - 1), t);
const right: Point = B(P.slice(1, P.length), t);
return [(1 - t) * left[0] + t * right[0],
(1 - t) * left[1] + t * right[1]];
}
// Evaluate a cubic spline at t=0.7
B([[0.0, 0.0], [0.0, 0.42], [0.58, 1.0], [1.0, 1.0]], 0.7)
Cubic Bezier Curves in Vector Images
As it happens, cubic bezier curves seem to be the right balance between simplicity and accuracy for many purposes. These are the kind of curves you’ll most often see in vector editing tools like Figma.
You can think of the two filled in circles ● as \( P_0 \) and \( P_3 \), and the two diamonds ◇ as \( P_1 \) and \( P_2 \). These are the fundamental building blocks of more complex curved vector constructions.
Font glyphs are specified in terms of bezier curves in TrueType (.ttf) fonts.
The Scalable Vector Graphics (.svg) file format uses bezier curves as one of its two curve primitives, which are used extensively in this:
Cubic Bezier Curves in Animation
While bezier curves have their most obvious uses in representing spacial curves, there’s no reason why they can’t be used to represented curved relationships between other quantities. For instance, rather than relating \( x \) and \(y \), CSS transition timing functions relate a time ratio with an output value ratio.
Cubic bezier curves are one of two ways of expressing timing functions in CSS
(steps()
being the other). The cubic-bezier(x1, y1, x2, y2)
notation
for CSS timing functions specifies the coordinates of \( P_1 \) and \( P_2
\) of a cubic bezier curve.
Let’s pretend we’re trying to animate an orange ball moving. In all of these diagrams, the red lines representing time move at a constant speed.
Why Bezier Curves?
Bezier curves are a beautiful abstraction for describing curves. The most commonly used form, cubic bezier curves, reduce the problem of describing and storing a curve down to storing 4 coordinates.
Beyond the efficiency benefits, the effect of moving the 4 control points on the curve shape is intuitive, making them suitable for direct manipulation editors.
Since 2 of the points specify the endpoints of the curve, composing many bezier curves into more complex structures with precision becomes easy. The exact specification of endpoints is always what makes it so convenient in the animation case: the only sensible value of the easing function at \( t = 0\% \) is the initial value, and the only sensible value at \( t = 100\% \) is the final value.
A less obvious benefit is that the line from \( P_0 \) to \( P_1 \) specifies the tangent of the curve leaving \( P_0 \). This means if you have two joint curves with mirrored control points, the slope at the join point is guaranteed to be the same on either side of the join.
A major benefit of mathematical construct like bezier curves is the ability to leverage decades of mathematical research to solve most problems you might run into, completely agnostic to the rest of your problem domain.
For instance, to make this post, I had to learn how to split a bezier curve at a given value of \( t \) in order to animate the curves above. I was quickly able to find a well written article on the matter: A Primer on Bézier Curves: Splitting Curves.
Resources and Further Reading
- A Primer on Bézier Curves in addition to having a description of using deCasteljau’s algorithm to draw and split curves, this free online book seems to be a pretty comprehensive intro.
- Bézier curve on Wikipedia shows many different mathematical formulas of bezier curves beyond the recursive definition shown here. It also contains the original animations that made bezier curves seem so evidently elegant to me.
Also a shoutout to Dudley Storey for his article Make SVG Responsive, which allowed all of the inline SVG in this article to work nicely on mobile.