PyQt中在拟合直线为曲线绘图时,二次贝塞尔曲线QPainterPath().quadTo()及三次贝塞尔曲线QPainterPath().cubicTo()的应用

文章描述:

在Qt开发的软件中,我们经常需要进行绘图操作;当然采用PyQtChart模块可以直接创建坐标进行曲线的绘制,然而本文不讲PyQtChart进行绘图的功能,而且针对我们QtWidget.QWidget等控件中重写paintEvent函数进行绘图来讲解。

那么就有人要问了,既然PyQtChart模块都已经很完善了,为什么我还要来讲解重绘paintEvent方法进行绘图呢?

这就是我要讲的原因了,PyQtChart进行绘图基本都是静态绘图,对于动态绘图大多数情况下都是通过信号发送进行页面的更新;然而对于数据量很大的时候并且每个数据是实时生成的情况下,处理信号的发送非常耗PC内存并且绘制的图形的线条有时会存在延时更新的情况,对于中低端的PC来说,这可以说是一大痛点。

举一个我之前的一个项目来说,如果你要实时显示你的鼠标的移动轨迹路线,并在窗口中显示出来。如果是采用PyQtChart模块来开发,首先你要安装,然后在窗体中设置QChart图标为背景图并把QChartView背景透明化等等一系列操作,很显然对于一个鼠标的轨迹路线显示,其实并没有那么麻烦,只需通过重写paintEvent()方法并添加逻辑即可

如果一个鼠标移动时,一定时间内上报的两个点间来绘制线条,采用QPainterPath().drawLine()方法来画线必然是一条直线,所以才会引出我想要讲的直线拟合曲线的两个方式。

注:本文主要是采用简易的方式来讲解这两个方法,当然不可能将我的项目放上来讲。

二次贝塞尔曲线和三次贝塞尔曲线的区别

(1)二次贝塞尔曲线:

结合QPainterPath().quadTo()方法来看,二次贝塞尔曲线需要一个控制点坐标及一个终点坐标;然后并不止如此,其实还需要一个起始点的坐标,然后通过起始点的坐标和终点坐标来计算控制点的坐标

def quadTo(self,
           ctrlPt: QPointF | QPoint,
           endPt: QPointF | QPoint) -> None

如下图,是一个采用二次贝塞尔曲线将两个点由直线拟合成曲线的图形

下面是代码案例:

from PyQt5.QtWidgets import QWidget, QApplication
from PyQt5.QtGui import QPainter, QPainterPath
from PyQt5.QtCore import Qt

class BezierFittingWidget(QWidget):
    def __init__(self):
        super().__init__()
        self.setFixedSize(400, 400)

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setRenderHint(QPainter.Antialiasing)  # 启用抗锯齿

        start_point = (50, 50)
        end_point = (300, 300)

        # 计算控制点
        control_point = ((start_point[0] + end_point[0]) / 2, start_point[1])

        path = QPainterPath()
        path.moveTo(start_point[0], start_point[1])
        path.quadTo(control_point[0], control_point[1], end_point[0], end_point[1])

        painter.drawPath(path)

if __name__ == '__main__':
    app = QApplication([])
    widget = BezierFittingWidget()
    widget.show()
    app.exec_()

下面是一个计算二次贝塞尔曲线中控制点的方法:



import math

def calculate_control_point(point1, point2):
    # 计算中点
    mid_x = (point1[0] + point2[0]) / 2
    mid_y = (point1[1] + point2[1]) / 2

    # 计算垂直偏移量
    offset = math.sqrt((point2[0] - point1[0])**2 + (point2[1] - point1[1])**2) / 2

    # 根据两点的斜率决定偏移方向
    slope = (point2[1] - point1[1]) / (point2[0] - point1[0])
    if slope == 0:
        control_point = (mid_x, mid_y + offset)
    else:
        angle = math.atan(slope)
        control_point = (mid_x + offset * math.cos(angle), mid_y + offset * math.sin(angle))

    return control_point

(2)三次贝塞尔曲线

结合QPainterPath().cubicTo()方法来看,三次贝塞尔曲线需要两个控制点坐标及一个终点坐标;同样他也是需要一个起始点的坐标,然后通过起始点的坐标和终点坐标来计算两个控制点的坐标

def cubicTo(self,
            ctrlPt1: QPointF | QPoint,
            ctrlPt2: QPointF | QPoint,
            endPt: QPointF | QPoint) -> None

如下图,是一个采用三次贝塞尔曲线将四个点由直线拟合成曲线的图形(共有三条贝塞尔曲线)

下面是代码案例:

from PyQt5.QtWidgets import QWidget, QApplication
from PyQt5.QtGui import QPainter, QPainterPath
from PyQt5.QtCore import Qt
import math

def calculate_cubic_bezier_control_points(start_point, end_point):
    # 假设另外两个控制点为 control_point1 和 control_point2

    # 计算中点
    mid_x = (start_point[0] + end_point[0]) / 2
    mid_y = (start_point[1] + end_point[1]) / 2

    # 计算偏移量(可根据需求调整这个值来控制曲线的形状)
    offset = math.sqrt((end_point[0] - start_point[0])**2 + (end_point[1] - start_point[1])**2) / 4

    # 计算角度
    angle = math.atan2(end_point[1] - start_point[1], end_point[0] - start_point[0])

    # 计算控制点
    control_point1 = (mid_x + offset * math.cos(angle + math.pi / 2), mid_y + offset * math.sin(angle + math.pi / 2))
    control_point2 = (mid_x + offset * math.cos(angle - math.pi / 2), mid_y + offset * math.sin(angle - math.pi / 2))

    return control_point1, control_point2


class FitCurveWidget(QWidget):
    def __init__(self):
        super().__init__()
        self.setFixedSize(400, 400)

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setRenderHint(QPainter.Antialiasing)  # 启用抗锯齿

        # 方形的顶点坐标
        square_points = [(100, 200), (200, 300), (300, 200), (200, 200)]

        path = QPainterPath()
        path.moveTo(square_points[0][0], square_points[0][1])   # 50,50

        for i in range(1, len(square_points)):
            # 计算控制点
            result = calculate_cubic_bezier_control_points(square_points[i - 1], square_points[i])
            print(result)
            # 使用 cubicTo 绘制贝塞尔曲线拟合角
            path.cubicTo(result[0][0], result[0][1], result[1][0], result[1][1], square_points[i][0], square_points[i][1])

        painter.drawPath(path)

if __name__ == '__main__':
    app = QApplication([])
    widget = FitCurveWidget()
    widget.show()
    app.exec_()

下面是一个计算三次贝塞尔曲线中两个控制点的方法:

def calculate_cubic_bezier_control_points(start_point, end_point):
    # 假设另外两个控制点为 control_point1 和 control_point2

    # 计算中点
    mid_x = (start_point[0] + end_point[0]) / 2
    mid_y = (start_point[1] + end_point[1]) / 2

    # 计算偏移量(可根据需求调整这个值来控制曲线的形状)
    offset = math.sqrt((end_point[0] - start_point[0])**2 + (end_point[1] - start_point[1])**2) / 4

    # 计算角度
    angle = math.atan2(end_point[1] - start_point[1], end_point[0] - start_point[0])

    # 计算控制点
    control_point1 = (mid_x + offset * math.cos(angle + math.pi / 2), mid_y + offset * math.sin(angle + math.pi / 2))
    control_point2 = (mid_x + offset * math.cos(angle - math.pi / 2), mid_y + offset * math.sin(angle - math.pi / 2))

    return control_point1, control_point2

好了,以上就是我想要记录的相关知识了

  • 9
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值