Bezier curves see widespread use across a multitude of applications in computer graphics, data-interpolation and others. For the vast majority of these, curves of the second or third order are sufficient. Higher-order curves remain neglected since they become harder to control and become increasingly expensive to compute.

However, one application in which both of these factors aren't a concern is string art - where programmatically defined Bezier curves can be used to generate beautiful images. As a fan of abstract art, I was naturally drawn to the subject and wanted to see what sort of images could be generated in this fashion.

Table of contents


What are Bezier curves?

Bezier curves are a type of parametric curve defined by a sequence of control points:

$$ P := (p_0, p_1, \ldots p_n) $$

The function \(B(P, t)\) that traces the curve can be defined recursively:

$$ t \in [0, 1] $$ $$ B((p_0), t) = p_0 $$ $$ B((p_0, p_1, \ldots p_n), t) = (1 - t)B((p_0, p_1, \ldots p_{n-1}), t) + tB((p_1, p_2, \ldots p_n), t) $$

The recursive nature of the definition can be visualized by drawing each layer of the recursion. Check out the visualization of the first three orders of Bezier curves below:

Figure 1: The construction of first, second, and third orders of the Bezier curve visualized.

With even more control points and an adequate coloring, these visualizations can become quite mesmerizing. For example take the pointed star pattern as seen in the pentagram - extend the pattern to a higher degree of control points and viola! A brilliant piece of art!

Figure 2: Visualization of a higher-order (12 control points) curve.


We trace the curve by evaluating it at intervals of fixed length \(\ell\) in \(t\), in order to obtain a sequence of points \(C\) that approximates its shape:

$$ \ell = 1 / n $$ $$ C := (c_0, c_1, \ldots c_n) $$ $$ c_i = B(P, i\ell) $$

We use the ordered sequence \(C\) to draw a line strip (connected line segments between subsequent elements) that converges towards the true shape of the curve as \(\ell\) approaches zero.

We evaluate \(B(P, t)\) for some value of \(t\) using the following algorithm:

function B(P, t) {
//    A = P
//    while |A| > 1 {
//        A_new = ()
//        for i in [1, |A|) {
//            A_new[i-1] = t * A[i] + (1 - t) * A[i-1]
//        }
//        A = A_new
//    }
//    return A[0]

At the end of each iteration, we have generated a new sequence \(A_{new}\) with length one less than that of the sequence from the previous iteration: \(|A_{new}| = |A| - 1\)

Within \(|P|\) iterations, the algorithm terminates with a single-item sequence containing the evaluated position of the curve at time t.

Source code

You can view the source code and documentation on GitHub. Additional code used to display the animations on this page can be found in the GitHub Pages branch of the repository.

See also