45

What is the algorithm to convert a quadratic bezier (with 3 points) to a cubic one (with 4 points)?

David Jones
  • 4,766
  • 3
  • 32
  • 45
jmasterx
  • 52,639
  • 96
  • 311
  • 557

3 Answers3

71

From https://fontforge.org/docs/techref/bezier.html#converting-truetype-to-postscript:

Any quadratic spline can be expressed as a cubic (where the cubic term is zero). The end points of the cubic will be the same as the quadratic's.

CP0 = QP0
CP3 = QP2

The two control points for the cubic are:

CP1 = QP0 + 2/3 *(QP1-QP0)
CP2 = QP2 + 2/3 *(QP1-QP2)

...There is a slight error introduced due to rounding, but it is unlikely to be noticeable.

Fredrick Brennan
  • 7,079
  • 2
  • 30
  • 61
Owen S.
  • 7,665
  • 1
  • 28
  • 44
  • 3
    Flavius has proposed `CP2 = CP1 + 1/3*(QP1-QP2)` instead. But from my math, that seems to give a different result. (Take points `QP0=(0,0)`, `QP1=(1,2)`, and `QP2=(3,0)`; I get `CP2=(5/3, 4/3)` for my formula and `CP2=(0,2)` for Flavius's.) I verified my formulas by setting the cubic coefficient to 0 and solving for the rest. Flavius, where did your formula come from? – Owen S. Jul 01 '13 at 23:41
  • Is QP2 the handle/anchor of the quadratic or is QP1 the handle/anchor of the quadratic ? People change the order of these every where i read up about bezier's it's a pain to keep track when people don't specify. – WDUK Dec 13 '18 at 04:53
  • 1
    QP1 is the control point in the middle and QP2 is the end point. – Robin Stewart Mar 07 '19 at 00:08
20

Just giving a proof for the accepted answer.

A quadratic Bezier is expressed as:

Q(t) = Q0 (1-t)² + 2 Q1 (1-t) t + Q2

A cubic Bezier is expressed as:

C(t) = C0 (1-t)³ + 3 C1 (1-t)² t + 3 C2 (1-t) t² + C3

For those two polynomials to be equals, all their polynomial coefficients must be equal. The polynomial coefficents are obtained by developing the expressions (example: (1-t)² = 1 - 2t + t²), then factorizing all terms in 1, t, t², and t³:

Q(t) = Q0 + (-2Q0 + 2Q1) t + (Q0 - 2Q1 + Q2) t²

C(t) = C0 + (-3C0 + 3C1) t + (3C0 - 6C1 + 3C2) t² + (-C0 + 3C1 -3C2 + C3) t³

Therefore, we get the following 4 equations:

C0 = Q0

-3C0 + 3C1 = -2Q0 + 2Q1

3C0 - 6C1 + 3C2 = Q0 - 2Q1 + Q2

-C0 + 3C1 -3C2 + C3 = 0

We can solve for C1 by simply substituting C0 by Q0 in the 2nd row, which gives:

C1 = Q0 + (2/3) (Q1 - Q0)

Then, we can either continue to substitute to solve for C2 then C3, or more elegantly notice the symmetry in the original equations under the change of variable t' = 1-t, and conclude:

C0 = Q0

C1 = Q0 + (2/3) (Q1 - Q0)

C2 = Q2 + (2/3) (Q1 - Q2)

C3 = Q2

Boris Dalstein
  • 7,015
  • 4
  • 30
  • 59
5

For reference, I implemented addQuadCurve for NSBezierPath (macOS Swift 4) based on Owen's answer above.

extension NSBezierPath {
    public func addQuadCurve(to qp2: CGPoint, controlPoint qp1: CGPoint) {
        let qp0 = self.currentPoint
        self.curve(to: qp2,
            controlPoint1: qp0 + (2.0/3.0)*(qp1 - qp0),
            controlPoint2: qp2 + (2.0/3.0)*(qp1 - qp2))
    }
}

extension CGPoint {
    // Vector math
    public static func +(left: CGPoint, right: CGPoint) -> CGPoint {
        return CGPoint(x: left.x + right.x, y: left.y + right.y)
    }
    public static func -(left: CGPoint, right: CGPoint) -> CGPoint {
        return CGPoint(x: left.x - right.x, y: left.y - right.y)
    }
    public static func *(left: CGFloat, right: CGPoint) -> CGPoint {
        return CGPoint(x: left * right.x, y: left * right.y)
    }
}
Robin Stewart
  • 3,147
  • 20
  • 29