PyQt6 基础操作:绘图 QPainter

QPainter绘图

概述:QPainter类是绘图工具,可以绘制直线以及弧线、椭圆等复杂图形,还可以添加文本和图片。QPainter类在QWidget上执行绘图操作,QWidget是所有界面控件的基类,有一个paintEvent事件,在此事件里,创建一个QPainter对象获取绘图设备的接口,就可以用QPainter对象绘图了。

操作要点:

  • 重写paintEvent,使用QPainter绘图函数绘制图形

       下面简单列举一些QPainter绘图函数:

drawLine(起点QPoint,终点QPoint)画直线
drawArc(矩形区域QRect,起始弧度,跨越弧度)画弧线,注意弧度是角度*16
drawRect(矩形区域QRect)画矩形
drawText(矩形区域QRect,对齐方式,文本内容)单行文本
drawImage(矩形区域QRect,图片QImage)图片
  • 重写resizeEvent,根据窗口大小调整线段长度等;

(1) 在窗口空白处(没有选中控件的地方)单击右键,点击布局,选择水平布局或者垂直布局,所有的控件和窗口实现了统一布局,就可以达到控件大小自适应的效果,当窗口最大化时,控件也等比例放大。

(2) 不过控件放大,控件内部的线条不会自动延长,因而需要在resizeEvent中,计算各个线条长度,更新绘图结果,从而实现调整窗口大小时,控件和控件内部图形同步放大。

  • 创建saveImage函数,可将图片保存下来

代码分享

1. 定义QWidget子类 <MyDraw.py>

import datetime
import math
import os.path
import sys
from PyQt6.QtCore import Qt, QPoint, QRect
from PyQt6.QtGui import QPainter, QPen, QPixmap, QImage, QFont
from PyQt6.QtWidgets import QApplication, QWidget


class DrawB(QWidget):
    def __init__(self, parent=None):
        super().__init__()
        self.phi = 30
        self.fig_height = 0

        self.true_d_list = [100, 200, 300, 400, 500]
        self.true_dh_list = []
        self.fake_d_list = []
        self.fake_dh_list = []

        self.margin_w = self.width()//10
        self.margin_h = self.height()//10
        self.max_avail_w, self.max_avail_h, self.rectp = 0, 0, 0

        self.data_standardization()
        # self.show()  # 单独调试此文件时需去掉注释;将这部分用于QMainWindow时,此处保持注释,在QMainWindow使用show方法即可

    def cal_height(self, theta, x):
        y = x * math.tan(math.radians(theta))
        return y

    def paintEvent(self, event):
        painter = QPainter()
        painter.begin(self)

        pen_balck_solid = QPen()
        pen_balck_solid.setColor(Qt.GlobalColor.black)

        pen_balck_dash = QPen()
        pen_balck_dash.setStyle(Qt.PenStyle.DashLine)
        pen_balck_dash.setColor(Qt.GlobalColor.black)

        pen_green_dash = QPen()
        pen_green_dash.setStyle(Qt.PenStyle.DashLine)
        pen_green_dash.setColor(Qt.GlobalColor.green)

        # 绘制图片
        rect = QRect(self.margin_w // 2,
                     self.height() - self.margin_h - self.fig_height - self.fig_height // 4,
                     self.fig_height, self.fig_height)
        site_fig_path = os.path.join(os.path.dirname(__file__), "fig_pea.PNG")
        image = QImage(site_fig_path)
        painter.drawImage(rect, image)

        # 绘制实线
        painter.setPen(pen_balck_solid)
        start_w = self.margin_w // 2 + self.fig_height + self.rectp // 2
        p_site = QPoint(start_w, self.height() - self.margin_h - self.fig_height)
        p1 = QPoint(start_w, self.height() - self.margin_h)
        p2 = QPoint(start_w + self.max_avail_w + self.margin_w // 2, self.height() - self.margin_h - self.fig_height)
        p3 = QPoint(start_w, self.height() - self.margin_h - self.max_avail_h)
        p5 = QPoint(start_w + self.max_avail_w,
                    self.height() - self.margin_h - self.fig_height - int(self.fake_dh_list[-1][1]))
        p6 = QPoint(start_w + self.max_avail_w,
                    self.height() - self.margin_h - self.fig_height + int(self.fake_dh_list[-1][1]))
        painter.drawLine(p_site, p2)
        painter.drawLine(p1, p3)
        painter.drawLine(p_site, p5)
        painter.drawLine(p_site, p6)

        text_angel = "BiuBiuBiu"
        rect = QRect(p_site.x() + 2 * self.rectp, p_site.y() - self.rectp, self.rectp*max(2, len(text_angel)-3), self.rectp*2)
        painter.drawText(rect, Qt.AlignmentFlag.AlignTop, text_angel)

        rect = QRect(p_site.x() + 2 * self.rectp, p_site.y() + self.rectp, self.rectp * max(2, len(text_angel) - 3),
                     self.rectp * 2)
        painter.drawText(rect, Qt.AlignmentFlag.AlignTop, text_angel)

        def draw_line(s_id, distance):
            painter.setPen(pen_balck_dash)
            cur_height = int(self.fake_dh_list[s_id][1])
            cur_hyp = math.sqrt(cur_height**2 + distance**2)
            painter.setPen(pen_green_dash)
            rect4 = QRect(p_site.x() - cur_hyp, p_site.y() - cur_hyp, cur_hyp*2, cur_hyp*2)
            painter.drawArc(rect4, 0, 360*16)

        for s_id, item in enumerate(self.fake_d_list):
            if s_id == len(self.fake_d_list) - 1:
                break
            draw_line(s_id, item)

        painter.setPen(pen_balck_solid)
        painter.setFont(QFont('Times New Roman', 16))
        rect_x = QRect(p2.x() - self.rectp//2, p2.y() + self.rectp//2, 2*self.rectp, 2*self.rectp)
        painter.drawText(rect_x, Qt.AlignmentFlag.AlignTop, "X")
        rect_y = QRect(p3.x() - self.rectp//2*3, p3.y(), 2*self.rectp, 2*self.rectp)
        painter.drawText(rect_y, Qt.AlignmentFlag.AlignTop, "Y")

    def resizeEvent(self, event):
        self.data_standardization()
        self.update()

    def data_standardization(self):
        max_avail_h = self.height() - self.margin_h*2
        max_avail_w = self.width() - self.margin_w*2 - max_avail_h//2
        self.fig_height = max_avail_h // 2
        self.rectp = self.height() // 40

        self.true_dh_list = [(item, int(self.cal_height(self.phi, item))) for item in self.true_d_list]

        if self.true_d_list:
            data_max_w = max(self.true_d_list) + self.rectp * 2
            data_max_h = max([item[1] for item in self.true_dh_list]) + self.rectp * 2
        else:
            data_max_w = self.width()
            data_max_h = self.height()

        w_coef = max_avail_w/data_max_w
        h_coef = max_avail_h/(data_max_h*2)
        w_coef = h_coef = min(w_coef, h_coef)

        d_list = [int(item*w_coef) for item in self.true_d_list]
        dh_list = [(int(item[0]*w_coef), int(item[1]*h_coef)) for item in self.true_dh_list]
        d_list.append(max_avail_w)
        dh_list.append((max_avail_w, h_coef*self.cal_height(self.phi, max_avail_w/w_coef)))

        self.max_avail_w = max_avail_w
        self.max_avail_h = max_avail_h
        self.fake_d_list = d_list
        self.fake_dh_list = dh_list
        return

    def saveImage(self, name='Image'):
        now = datetime.datetime.now()
        curren_time = now.strftime('%m-%d_%H-%M-%S')
        pixmap = QPixmap(self.size())
        self.render(pixmap)
        pixmap.save(f'{name}_{curren_time}.png', 'png')


if __name__ == '__main__':
    app = QApplication(sys.argv)
    u = DrawB()
    sys.exit(app.exec())

2. 程序界面 <qpainter_study.py>

在程序界面中,有一个DrawB类型的控件,用于展示绘图内容

关键行:self.widget_figure = DrawB(parent=self.centralwidget)

# Form implementation generated from reading ui file 'qpainter_study.ui'
#
# Created by: PyQt6 UI code generator 6.6.1
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again.  Do not edit this file unless you know what you are doing.


from PyQt6 import QtCore, QtGui, QtWidgets
from MyDraw import DrawB


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(1001, 546)
        MainWindow.setStyleSheet("QPushButton{\n"
                                 "background-color:rgb(0, 85, 127);\n"
                                 "color:rgb(255, 255, 255);\n"
                                 "font:bold;\n"
                                 "font-size:15px\n"
                                 "}\n"
                                 "QPushButton#pushButton_update{\n"
                                 "background-color:rgb(170, 0, 0);\n"
                                 "color:rgb(255, 255, 255);\n"
                                 "font:bold;\n"
                                 "font-size:15px\n"
                                 "}")
        self.centralwidget = QtWidgets.QWidget(parent=MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.centralwidget)
        self.verticalLayout_2.setObjectName("verticalLayout_2")
        self.verticalLayout = QtWidgets.QVBoxLayout()
        self.verticalLayout.setObjectName("verticalLayout")
        self.widget_figure = DrawB(parent=self.centralwidget)
        self.widget_figure.setObjectName("widget_figure")
        self.verticalLayout.addWidget(self.widget_figure)
        self.horizontalLayout = QtWidgets.QHBoxLayout()
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.label = QtWidgets.QLabel(parent=self.centralwidget)
        self.label.setObjectName("label")
        self.horizontalLayout.addWidget(self.label)
        self.lineEdit_d = QtWidgets.QLineEdit(parent=self.centralwidget)
        self.lineEdit_d.setObjectName("lineEdit_d")
        self.horizontalLayout.addWidget(self.lineEdit_d)
        self.verticalLayout.addLayout(self.horizontalLayout)
        self.pushButton_update = QtWidgets.QPushButton(parent=self.centralwidget)
        self.pushButton_update.setObjectName("pushButton_update")
        self.verticalLayout.addWidget(self.pushButton_update)
        self.pushButton_save = QtWidgets.QPushButton(parent=self.centralwidget)
        self.pushButton_save.setObjectName("pushButton_save")
        self.verticalLayout.addWidget(self.pushButton_save)
        self.verticalLayout.setStretch(0, 20)
        self.verticalLayout.setStretch(1, 2)
        self.verticalLayout.setStretch(2, 2)
        self.verticalLayout.setStretch(3, 2)
        self.verticalLayout_2.addLayout(self.verticalLayout)
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(parent=MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 1001, 21))
        self.menubar.setObjectName("menubar")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(parent=MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "test"))
        self.label.setText(_translate("MainWindow", "radius_list"))
        self.pushButton_update.setText(_translate("MainWindow", "Get Your New Picture"))
        self.pushButton_save.setText(_translate("MainWindow", "Save Your Picture"))

3. 主程序 <qpainter_study_main.py>

import os.path
from PyQt6.QtCore import Qt
from PyQt6.QtGui import QIcon
from PyQt6.QtWidgets import QApplication, QMainWindow
import sys
from qpainter_study import Ui_MainWindow


class Ui_SiteDraw(Ui_MainWindow, QMainWindow):
    def __init__(self):
        super().__init__()
        self.setupUi(self)
        # [可选]给程序增加一个小图标
        ico_path = os.path.join(os.path.dirname(__file__), "fig_logo.ico")
        self.setWindowIcon(QIcon(ico_path))

        # [可选]运行程序时直接调至最大全屏状态
        self.setWindowState(Qt.WindowState.WindowMaximized)

        self.lineEdit_d.setClearButtonEnabled(True)
        self.lineEdit_d.setPlaceholderText("[100, 200, 300, 400, 500]")
        self.pushButton_update.clicked.connect(self.update_figure)
        self.pushButton_save.clicked.connect(self.save_figure)
        self.widget_figure.move(5, 5)
        self.tabel_info_list = []
        self.show()

    def update_figure(self):
        try:
            if not self.lineEdit_d.text():
                return

            input_d_list = list(eval(self.lineEdit_d.text().replace(',', ',')))
            self.widget_figure.true_d_list = input_d_list
            self.widget_figure.data_standardization()
            self.widget_figure.update()
        except Exception as e:
            return

    def save_figure(self):
        self.widget_figure.saveImage()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ui = Ui_SiteDraw()
    sys.exit(app.exec())

PyInstaller打包

涉及图片时,打包时需要使用--add-data把图片文件一并打包。

当前示例中

(1) main文件<qpainter_study_main.py>用到了图片fig_logo.ico,用于在程序界面左上角显示小图标,代码中,图片路径为:os.path.join(os.path.dirname(__file__), "fig_logo.ico"),打包时图片应添加到程序根目录;

(2) 文件<MyDraw.py> 中用到了图片fig_pea.PNG,即绘图界面中的豌豆图片,图片路径为:os.path.join(os.path.dirname(__file__), "fig_pea.PNG"),由于MyDraw.py文件与main文件在同一目录下,打包时fig_pea.PNG图片也应添加到程序根目录。

pyinstaller -F -w --icon="fig_logo.ico" qpainter_study_main.py --add-data fig_logo.ico;. --add-data fig_pea.PNG;.

效果展示

修改半径列表后,显示的圆圈会有相应改变。

PNG转ico网址:Convertio — 文件转换器

PyInstaller官网链接:What PyInstaller Does and How It Does It — PyInstaller 6.3.0 documentation

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值