curve-offsetting icon indicating copy to clipboard operation
curve-offsetting copied to clipboard

Generalized offsetting of parameterized Bézier curves

Offsetting parameterised Bezier curves

Simon Cozens

A common problem in type design is the creation of pairs of curves representing the stroke of a pen: an inner curve and an outer curve delimit the contours of a writing implement of fixed or flexible thickness. While it is impossible to precisely offset a Bezier curve at a given width, this paper presents a simple approximation by minimizing the error between desired distance and actual distance. This can also be applied to situations where the thickness varies linearly across the width of the curve.

We use a simplification due to Tunni, who postulates that any curve a, b, c, d with straight handles (i.e. where the control points band c are positioned orthogonally to a and d respectively) can be represented in terms of start and end points aand d and a curve tension τ. To determine curve tension, compute the point T where ab intersects cd:

The curve tension is given by the mean of the ratios ∥∥aabT∥∥ and ∥∥ddTc∥∥. Given the points a, d and a tension τ we can compute the Bezier control points b, c by setting them at the appropriate ratios.

This conceptualization enables us to find similar parameters for an offset Bezier curve. We will approach the problem in small pieces, demonstrating the technique first before solving the general case.

Outer offset of a unit Bezier curve

Consider first the unit Bezier curve BA with a = (0,1), d = (1,0) and c and d chosen as orthogonal control points with a curve tension α. What are the parameters for a Bezier curve BB offsetting this curve on the outside at a fixed distance δ?

Clearly we have a = (0, 1 + δ), d = (1 + δ, 0), so it remains to find the curve tension β.

As a function of time, the distance between the two curves is:

∥BA (t)⋅BB (t)∥
[1]

and at any point on the curve, the expected distance is δ. Knowing it is impossible to achieve a perfect offset, we can treat this as an optimization problem: find the value of β which minimizes the total error function

∫ 1
   (∥BA (t)⋅BB (t)∥ - δ)2dt
 0
[2]

This integral turns out to be tricky to compute due to the presence of the square root, so instead we create an equivalent error function using the square of the distance. We expect the square of the distance to be δ2, and we square the difference of these two values to perform a least squares optimization. This leads to an error function of

                 2 2
(|BA (t)⋅BB (t)|- δ )
[3]

For a unit Bezier, we have:

       (    3         2          2   )
BA (t) =    t2+ 3(1- t)t + 3α3(1 - t) t 2
         3αt((1 - t)+ (1- t) + 3t(1 - t)                                        )
               (Δ + 1)t3 + 3(1- t)t2((1- *β*Δ +1) +*β*Δ + 1))+ 3*β*#x0394; + 1)(1 - t)2t
    BB(t) =  3*β*Δ + 1)t2(1 - t)+ 3t(1- t)2((1- *β*Δ + 1)+ *β*#x0394; +1))+ (Δ + 1)(1 - t)3
                                                (  (Δ + 1)t(3*β*- 1)2 +t(3- 2t))  )
                                              =  (Δ + 1)(- (t- 1))((3*β* 2)t2 + t+ 1)
[4]

leading to a square distance

                2(       2                2           )2
|BA (t)⋅BB (t)| = t 3α (t - 1) - 3*β*#x0394; + 1)(t- 1) +Δt (2t - 3) +
                                          (t- 1)2(Δ + t(Δ + t(- 3α + 3*β*#x0394; + 1) - 2Δ )))2
[5]
and therefore an error function
            ∫ 1                         1  (
E(BA, BB ) =   (|BA (t)⋅BB (t)|- δ2)2dt = 30030 1161(α- *β*4 - 36(129*β* 148)δ(α- *β*3+
         (   02           ) 2      2                                 3
       18  387*β*+ 888*β* 146 δ (α- *β*- 4(9*β**β*3*β* 148)+ 146)- 2138)δ(α - *β*  )
                                          (*β**β*β*29*β* 592)+ 292)- 8552)+ 2916)δ4
[6]

This looks horrific, but it’s only a quartic, and is easily optimizable. Rather than solving the differential equation for the general case, let’s be practical, remember that α and δ will be given and go for a numerical method to minimize the error function.

Beginning with β1 = α and applying the Newton-Raphson optimization method gives us an iterated function

               ′
  *β*1 = *β*- E-(BA,-BB-)
      (      E′′(BA, BB )            (                )                                 )
                                - 36 387*β*+ 888*β* 146 δ2(α - *β*      ||             - 4(9*β*129*β* 3(43*β* 148)) +9(3*β*3*β*148)+ 146))δ3(α - *β*           ||
      ||          +18(774*β*888)δ2(α- *β* - 4644δ(α- *β* + 108(129*β* 148)δ(α- *β*      ||
      (                      - 4644(α- *β* + (9*β*β*29*β* 592)+ 292)+                  )
      --*β**β*58*β*-592)+-9(*β*29*β*-592)+-292))-- 8552)δ4 +-4(9*β**β*3*β*-148)+-146)--2138)δ3
= *β*       (              - 4(2322*β* 18(129*β* 3(43*β*148)))δ3(α - *β*          )
            |         13932δ2(α - *β*2 - 72(774*β* 888)δ2(α- *β*+ 27864δ(α - *β*2      |
            ||                       - 216(129*β* 148)δ(α - *β*                    ||
            ||               13932(α - *β*2 + 36(387*β*+ 888*β* 146)δ2+            ||
            |( (*β*322*β* 18(258*β* 592))+ 2(9*β*258*β* 592)+ 9(*β*29*β* 592) + 292)))δ4+ |)
                        8(9*β*29*β* 3(43*β* 148))+ 9(3*β*43*β* 148)+ 146))δ3
[7]

which quickly converges to the minimum error, giving us the optimal curve tension.

As an example, plugging in α = 0.55, δ = 1:

β1 = 0.55

β2 = 0.550987

β3 = 0.550985

β4 = 0.550985

We can cheat

Thankfully, we find by inspection that the optimal value of β given α and δ, β(α, δ) turns out to be pretty much linear in both α and δ when α >= 0.3. A very pleasing result is:

*β*#x03B1;,1) = 0.275985+ α
                  2
[8]

Note that this gives exactly the answer given by our Newton-Raphson method above. A more general, but less accurate, approximation is:

*β*#x03B1;,δ) = 0.513216α - 0.025407δ+ 0.296638
[9]

Inner offsetting of a unit Bezier

What if we want to go the other way, and find the inner curve at a fixed distance?

A very similar pattern applies, but this time we construct BB as a = (0, 1-δ), d = (1-δ, 0) and the Newton step E′(BA,BB)-
E′′(BA,BB) is

class="multline">
( 18α2 (414*β*#x03B4;- 1)2 + 244(δ - 1)2)+ 8α (1098*β*δ - 1)2 - 108(δ- 1)2)+ )
(  4644*β*δ - 1)4 + 15984*β*δ- 1)4 + 72*β*73δ2 - 718δ + 548) (δ - 1)2 )
- 8(1069δ2 + 3439δ- 4011)(δ- 1)2
-(-----2------2-------------2--------2------4-------------4--)--
7452α (δ- 1) + 8784α((δ-21) + 13932*β*δ- 1)2+ 31968*β*δ - 1)+
72 73δ - 718δ+ 548 (δ- 1)
[10]
Equally, we can invert our approximation href="#x1-2002r9">9 above, giving:
α = 1.9485*β* 0.0495055δ- 0.577999
[11]

Outer offsetting of an arbitrary normalized curve

Real-world curves are not unit curves (0,1)⋅⋅⋅(1, 0). However, we can always use affine transformation to locate the start at a = (1, 0), leaving the end at d = (0,x). The control points for a Bezier curve with tension τ BA

would then be set at b = (1-τ,0), c = (0,x(1-τ)). The problem, again, is to find the offset curve BB which best approximates a fixed distance δ from BA.

Now we have a = (0, 1 + δ), d = (x + δ, 0). Following exactly the procedure above, the square of the distance between the two curves at point t , is

t2 (δ (t(2t- 3)- 3*β*- 1)2) + 3(t- 1)2x(α- *β*2 + (t- 1)2(δ + t(δ +t(- 3α + 3*β*#x03B4; + 1)- 2δ)))2
[12]

and the total error across the curve is

             ∫ 1
 E(B  ,B  ) =   (|B  (t)⋅B  (t)|- δ2)2dt
    A   B     0   A      B
  27(14x4 + 15x2 + 14) (α - *β* - 9δ(x+ 1)(α- *β*(3(56*β* 45)+ x(- 78*β* 3(56*β*45)x + 26))
       +9 δ2(α - *β*(566*β* 9*β*x(33x + 20)+ 33)+ 2*β*283x+ 322)+ 4x(85- 6x)- 24)
                    +2 (9*β*3*β*3*β* 148) +146) - 2138)δ3(x + 1)(α - *β*                      + (*β**β**β*129*β* 592) + 292) - 8552)+ 2916)δ4
= --------------------------------------30030--------------------------------------
[13]

Once again, it’s only a quartic and three of the variables are given. We can apply the Newton-Raphson method again, giving:

               ′
  *β*1 = *β*- E-(BA,-BB-)= *β*
             E′′(BA, BB )     (                           )
                       (   4  1161*β*+)3996*β*+ 1314*β*( 2138 δ4    )
             - 54δ(x+ 1) 28x2 -(13x + 28 (α - *β* -)108 14x4 + 15x2 +14 (α - *β*
                   (      - 18 387*β*+888*β* 146 δ3(x + 1)(α - *β*             )
       - 18δ2(α - *β*566*β* 9*β*x(33x+ 20)+ 33)+ 2*β*283x+ 322)+ 4x(85- 6x)- 24 +
                   9δ2(α - *β*(18*β*(33x+ 20)+ 33)+ 2x(283x+ 322)+ 566)
               +27 δ(x + 1)(α - *β*(3(56*β* 45)+ x(- 78*β* 3(56*β* 45)x+ 26))+
-------------------------2(9*β**β*3*β*-148)+-146)--2138)δ3(x-+1)-------------------------
    18(2(387*β*+888*β* 146)δ4 + 18δ(x+ 1)(28x2 - 13x+ 28)(α - *β* + 18(14x4 + 15x2 + 14)
       (α- *β* - 6(129*β* 148)δ3(x + 1)(α - *β* 9δ2(x (33x + 20)+ 33)(α - *β* - 2δ2(α - *β*         (18*β*(33x+ 20)+ 33)+ 2x(283x+ 322)+ 566)- 3δ(x+ 1)(α - *β*3(56*β* 45)+
x(- 78*β* 3(56*β* 45)x+ 26))+ 2(387*β*+ 888*β* 146) δ3(x + 1) + δ2 (566*β* 9*β*x(33x + 20)+ 33)+
                            2*β*283x+ 322)+ 4x(85- 6x)- 24))
[14]

By iterating this approximation, we can derive the tension for a curve at an offset of a given distance δ from an arbitrary Bezier curve specified by two points and a curve tension parameter.

But wait, it gets more complicated.

Offsetting at a linear-gradiated distance

Strokes in fonts often have a feature called contrast, meaning that the horizontal offset is not the same as the vertical offset:

To model this we will assume that the desired distance between curves is a linear function of curve time t :

δ(t) = δs(1 - t)+ δet
[15]

And now our error function is

                  2 2
(|BA (t)⋅BB (t)|- δ(t)) = x
[16]

The total integrated error across the curve becomes... very complicated, but computable. We can apply a similar Newton-Raphson method as above, leading to the functions given in the associated Python script.