简体   繁体   English

用PyQt5绘制多点曲线

[英]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.使用quadTocubicTo类的函数将不起作用,因为它们使用控制点来创建贝塞尔曲线,而这些点通常不是曲线的一部分。

UPDATE更新

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:为了找到您需要的细分数据:

  1. The previous and next point上一点和下一点
  2. Find the angle bisector between the segments that are created with the previous/current point and the current/next point找到用前一个/当前点和当前/下一个点创建的线段之间的角平分线
  3. Create two segments that are perpendicular to that angle, start from the current point and has lenghts proportional to each segment创建两个垂直于该角度的线段,从当前点开始,长度与每个线段成比例
  4. Use the extremities of those segments as control points使用这些段的末端作为控制点

In the following image you can see all that matters:在下图中,您可以看到所有重要的信息:

曲线数据点

  • red dots: reference points;点:参考点;
  • light gray lines: line segments浅灰色线条:线段
  • azure lines: angle bisectors azure线:角平分线
  • red lines: reference for target lines (from the current point to the next)红线:目标线的参考(从当前点到下一个点)
  • green lines: reference for source lines (from the previous point to the current)绿线:参考源线(从前一点到当前)
  • orange squares: control points橙色方块:控制点

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

Previous answer上一个答案

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM