在 PyQt 中绘制具有多个曲线外点的贝塞尔曲线

Drawing Bezier curve with multiple off curve points in PyQt

我想用 PyQt5 QPainterPath 绘制一个 TrueType 字体字形。字形片段示例:(来自 Fonttools ttx 的数据)

<pt x="115" y="255" on="1"/>
<pt x="71" y="255" on="0"/>
<pt x="64" y="244" on="0"/>
<pt x="53" y="213" on="0"/>
<pt x="44" y="180" on="0"/>
<pt x="39" y="166" on="1"/> 

on=0 表示控制点 on=1 表示 start/end 点 我假设这不会使用 (QPainterPath) quadTo 或 cubicTo,因为它是高阶曲线。

True type 字体实际上只使用二次贝塞尔曲线。这是有道理的,它们是非常简单的曲线,不需要大量计算,当您可能需要为一个简单的段落绘制数百或数千条曲线时,这对性能有好处。

意识到这一点后,我发现奇怪的是你的曲线有4个控制点,但后来我做了一些研究,发现了这个interesting answer

实际上,TrueType 格式允许对始终在每个控制点的中间共享每个起点或终点的二次曲线进行分组。

所以,从您的列表开始:

Start  <pt x="115" y="255" on="1"/>
C1     <pt x="71" y="255" on="0"/>
C2     <pt x="64" y="244" on="0"/>
C3     <pt x="53" y="213" on="0"/>
C4     <pt x="44" y="180" on="0"/>
End    <pt x="39" y="166" on="1"/> 

我们有 6 个点,但是有 4 条曲线,4 个控制点之间的中间点是 曲线上剩余的 start/end 个点:

start control end
Start C1 (C2-C1)/2
(C2-C1)/2 C2 (C3-C2)/2
(C3-C2)/2 C3 (C4-C3)/2
(C4-C3)/2 C4 End

为了计算所有这些,我们可以循环遍历这些点并存储对前一个点的引用,每当我们在它们之后有一个控制点或一个曲线上的点时,我们就向路径添加一条新的二次曲线。

start control end
115 x 255 71 x 255 67.5 x 249.5
67.5 x 249.5 64 x 244 58.5 x 228.5
58.5 x 228.5 53 x 213 48.5 x 106.5
48.5 x 106.5 44 x 180 39 x 166

以下代码将创建一个对应于每个 <contour> 组的 QPainterPath。

path = QtGui.QPainterPath()
currentCurve = []
started = False
for x, y, onCurve in contour:
    point = QtCore.QPointF(x, y)
    if onCurve:
        if not currentCurve:
            # start of curve
            currentCurve.append(point)
        else:
            # end of curve
            start, cp = currentCurve
            path.quadTo(cp, point)
            currentCurve = []
            started = False
    else:
        if len(currentCurve) == 1:
            # control point
            currentCurve.append(point)
        else:
            start, cp = currentCurve
            # find the midpoint
            end = QtCore.QLineF(cp, point).pointAt(.5)
            if not started:
                # first curve of many
                path.moveTo(start)
                started = True
            path.quadTo(cp, end)
            currentCurve = [end, point]