[英]Drawing multi-point curve with PyQt5
How to join multiple points with a flowing curve, using PyQt5?如何使用 PyQt5 将多个点与流动曲线连接起来? For example, I attempted to do this for 8 points using quadTo(), using the alternate points as control points, but the arcs dont touch the control points (see code and graph below).
例如,我尝试使用 quadTo() 对 8 个点执行此操作,使用交替点作为控制点,但弧线不接触控制点(参见下面的代码和图表)。 I also tried using cubicTo(), but that also resulted in a weird curve.
我也尝试使用cubicTo(),但这也导致了一条奇怪的曲线。 use Is there any other function call that I should use, or a custom way to do this?
是否还有其他我应该使用的 function 调用,或自定义方法来执行此操作?
from PyQt5 import QtGui, QtCore
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import sys
class Window(QMainWindow):
def __init__(self):
super().__init__()
self.title = "PyQt5 Drawing Tutorial"
self.top= 150
self.left= 150
self.width = 500
self.height = 500
self.InitWindow()
def InitWindow(self):
self.setWindowTitle(self.title)
self.setGeometry(self.top, self.left, self.width, self.height)
self.show()
def paintEvent(self, event):
painter = QPainter(self)
path = QPainterPath()
points = [
QPoint(20,40),
QPoint(60,10),
QPoint(100,50),
QPoint(80,200),
QPoint(200,300),
QPoint(150,400),
QPoint(350,450),
QPoint(400,350),
]
# draw small red dots on each point
painter.setPen(QtCore.Qt.red)
painter.setBrush(QBrush(Qt.red))
for i in range(len(points)):
painter.drawEllipse(points[i], 3, 3)
painter.setPen(QtCore.Qt.blue)
painter.setBrush(QBrush(Qt.red, Qt.NoBrush)) #reset the brush
path.moveTo(points[0])
# connect the points with blue straight lines
#for i in range(len(points)-1): # 1 less than length
# path.lineTo(points[i+1])
# connect points with curve
for i in range(0,len(points),2):
path.quadTo(points[i], points[i+1])
painter.drawPath(path)
App = QApplication(sys.argv)
window = Window()
sys.exit(App.exec())
Using functions like quadTo
or cubicTo
won't work, as they use control points to create bezier curves, and those points are usually not part of the curve.使用
quadTo
或cubicTo
类的函数将不起作用,因为它们使用控制点来创建贝塞尔曲线,而这些点通常不是曲线的一部分。
I realized that my previous answer was not only inaccurate, but also wrong.我意识到我之前的回答不仅不准确,而且是错误的。 I'm leaving it at the bottom of this answer, for documentation/historical purposes.
出于文档/历史目的,我将其留在此答案的底部。
An accurate "spline" interpolation has to use a segment that is tangent to the possible curve;精确的“样条”插值必须使用与可能曲线相切的线段; in order to find the segment data you need:
为了找到您需要的细分数据:
In the following image you can see all that matters:在下图中,您可以看到所有重要的信息:
Note that the first and last curve are only quadratic (not cubic), as there's only one control point: the target line reference for the first point, the source line reference for the last.请注意,第一条和最后一条曲线只是二次曲线(不是三次曲线),因为只有一个控制点:第一个点的目标线参考,最后一个点的源线参考。
The code uses a for loop that cycles from the second to the second-to-last point, and also uses a control point set from the previous cycle.该代码使用从第二个到倒数第二个点循环的 for 循环,并且还使用前一个循环中设置的控制点。
I suggest you to use a factor =.25
, which should create a path smooth enough.我建议你使用一个
factor =.25
,它应该创建一个足够平滑的路径。 Lower values results in "smaller" curves, while higher values will give you more "rounded" paths.较低的值会导致“更小”的曲线,而较高的值将为您提供更多“圆润”的路径。
class Window(QWidget):
# ...
def buildPath(self):
factor =
self.path = QtGui.QPainterPath(points[0])
for p, current in enumerate(points[1:-1], 1):
# previous segment
source = QtCore.QLineF(points[p - 1], current)
# next segment
target = QtCore.QLineF(current, points[p + 1])
targetAngle = target.angleTo(source)
if targetAngle > 180:
angle = (source.angle() + source.angleTo(target) / 2) % 360
else:
angle = (target.angle() + target.angleTo(source) / 2) % 360
revTarget = QtCore.QLineF.fromPolar(source.length() * factor, angle + 180).translated(current)
cp2 = revTarget.p2()
if p == 1:
self.path.quadTo(cp2, current)
else:
# use the control point "cp1" set in the *previous* cycle
self.path.cubicTo(cp1, cp2, current)
revSource = QtCore.QLineF.fromPolar(target.length() * factor, angle).translated(current)
cp1 = revSource.p2()
# the final curve, that joins to the last point
self.path.quadTo(cp1, points[-1])
There are some algorithms that allow to build "splines" for interpolation, but you'd need some mathematical skills to understand them and create a good system that creates a smooth curve.有一些算法允许为插值构建“样条曲线”,但您需要一些数学技能来理解它们并创建一个创建平滑曲线的良好系统。 In the meantime, a possible (but not perfect) solution is to create control points that are computed from the extension of the existing segments (which is similar to what vector graphics editor do):
同时,一个可能的(但不是完美的)解决方案是创建从现有段的扩展计算的控制点(这类似于矢量图形编辑器所做的):
The extremities of each extension is used as a control point for the bezier curves: for the first and last segment I'm using a quadratic (one control point), while all the others are cubic (two control points);每个扩展的末端用作贝塞尔曲线的控制点:对于第一段和最后一段,我使用二次(一个控制点),而所有其他都是三次(两个控制点); this results in a acceptable result:
这会产生可接受的结果:
Unfortunately, it is far from perfect, especially for certain combinations of angles and lengths:不幸的是,它远非完美,尤其是对于角度和长度的某些组合:
I recommend you to build the path only if required (for example, the points change), not in the paintEvent.我建议您仅在需要时(例如,点更改)而不是在paintEvent 中构建路径。
class Window(QWidget):
# ...
def buildPath(self):
self.path = QtGui.QPainterPath()
self.path.moveTo(points[0])
factor = .1412
for p in range(len(points) - 2):
p2 = points[p + 1]
target = QtCore.QLineF(p2, points[p + 2])
reverseTarget = QtCore.QLineF.fromPolar(
target.length() * factor, target.angle() + 180).translated(p2)
if not p:
self.path.quadTo(reverseTarget.p2(), p2)
else:
p0 = points[p - 1]
p1 = points[p]
source = QtCore.QLineF(p0, p1)
current = QtCore.QLineF(p1, p2)
targetAngle = target.angleTo(current)
if 90 < targetAngle < 270:
ratio = abs(sin(radians(targetAngle)))
reverseTarget.setLength(reverseTarget.length() * ratio)
reverseSource = QtCore.QLineF.fromPolar(
source.length() * factor, source.angle()).translated(p1)
sourceAngle = current.angleTo(source)
if 90 < sourceAngle < 270:
ratio = abs(sin(radians(sourceAngle)))
reverseSource.setLength(reverseSource.length() * ratio)
self.path.cubicTo(reverseSource.p2(), reverseTarget.p2(), p2)
final = QtCore.QLineF(points[-3], points[-2])
reverseFinal = QtCore.QLineF.fromPolar(
final.length() * factor, final.angle()).translated(final.p2())
self.path.quadTo(reverseFinal.p2(), points[-1])
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.