在 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]
我想用 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]