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