将二次贝塞尔曲线转换为三次贝塞尔曲线

46

如何将具有3个点的二次贝塞尔曲线转换为具有4个点的三次贝塞尔曲线的算法?

3个回答

72

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

任何二次样条曲线都可以表示为三次曲线(其中三次项为零),而且这个三次曲线的端点与二次曲线的端点相同。

CP0 = QP0
CP3 = QP2

该三次曲线的两个控制点为:

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

由于舍入引入了轻微误差,但不太可能引起注意。


3
Flavius提出了CP2 = CP1 + 1/3*(QP1-QP2)的公式。但是根据我的数学计算,这似乎得出了不同的结果。(以点QP0=(0,0)QP1=(1,2)QP2=(3,0)为例;我的公式得出CP2=(5/3,4/3),而Flavius的公式得出CP2=(0,2))。我通过将三次项系数设置为0并解出其余部分来验证了我的公式。Flavius,你的公式从哪里来? - Owen S.
QP2是二次曲线的句柄/锚点还是QP1是二次曲线的句柄/锚点?我在阅读关于贝塞尔曲线的资料时,人们总是改变这些顺序,当人们没有明确说明时,这很难跟踪。 - WDUK
1
QP1是中间的控制点,而QP2则是终点。 - Robin Stewart

20

仅为接受答案提供证明。

二次贝塞尔曲线表达式如下:

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

三次贝塞尔曲线表达式如下:

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

为了使这两个多项式相等,它们的所有多项式系数必须相等。这些多项式系数是通过展开表达式(例如:(1-t)²=1-2t+t²),然后将所有项因式分解为1、t、t²和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³

因此,我们得到以下4个方程:

C0 = Q0

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

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

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

我们可以通过在第二行中将C0替换为Q0来简单地解决C1,得到:

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

然后,我们可以继续替换并解决C2和C3,或者更加优雅地注意到在变量t' = 1-t的变化下,原方程式具有对称性,从而得出以下结论:

C0 = Q0

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

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

C3 = Q2


5

供参考,我基于上面Owen的答案实现了NSBezierPath的addQuadCurve函数(macOS Swift 4版本)。

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)
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接