环境
- Win10
- PyCharm2019 社区版
- PyQt5
- Qt Designer
设计界面
使用Qt Designer进行快速设计,选择 MainWindow,在左侧添加 QLabel 和两个 PushButton。其中,QLabel 用于显示图片,两个按键分别用于获取图片和保存图片。
设计如下:
然后对生成的 .ui文件转换为 .py 代码文件。但注意这里代码需要进行一个改动,见下面代码注释。
from PyQt5 import QtCore, QtGui, QtWidgets
from drawline import MyLabel
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(600, 480)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setGeometry(QtCore.QRect(140, 370, 131, 41))
font = QtGui.QFont()
font.setFamily("Arial")
font.setPointSize(14)
self.pushButton.setFont(font)
self.pushButton.setObjectName("pushButton")
# 生成代码为:
# self.label = QtWidgets.QLabel(self.centralwidget)
# 这里改为MyLabel,因为QLabel的鼠标点击事件没有具体实现,我们需要重写这个类的鼠标点击方法
self.label = MyLabel(self.centralwidget)
self.label.setGeometry(QtCore.QRect(110, 40, 401, 281))
self.label.setText("")
self.label.setObjectName("label")
self.pushButton_2 = QtWidgets.QPushButton(self.centralwidget)
self.pushButton_2.setGeometry(QtCore.QRect(360, 370, 131, 41))
font = QtGui.QFont()
font.setFamily("Arial")
font.setPointSize(14)
self.pushButton_2.setFont(font)
self.pushButton_2.setObjectName("pushButton_2")
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 600, 22))
self.menubar.setObjectName("menubar")
self.menuOpen = QtWidgets.QMenu(self.menubar)
self.menuOpen.setObjectName("menuOpen")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.actionNew = QtWidgets.QAction(MainWindow)
self.actionNew.setObjectName("actionNew")
self.menuOpen.addAction(self.actionNew)
self.menubar.addAction(self.menuOpen.menuAction())
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.pushButton.setText(_translate("MainWindow", "Get Image"))
self.pushButton_2.setText(_translate("MainWindow", "Save Image"))
self.menuOpen.setTitle(_translate("MainWindow", "Open"))
self.actionNew.setText(_translate("MainWindow", "New"))
重写QLabel
- 写一个 MyLabel 类继承 QLabel 类
- 重写鼠标事件方法:paintEvent, mousePressEvent, mouseMoveEvent, mouseReleaseEvent,并记得先调用父类的该方法,看下面注释(否则可能功能失效)
- 画矩形方法:当左键点击,保存左上角矩形点;鼠标拖动,不断复制 上一张图, 再在复制后的图绘制矩形,刷新界面;当左键释放,更新 上一张图 为当前图。
# -*- coding: utf-8 -*-
from PyQt5.QtWidgets import QLabel
from PyQt5.QtGui import QPainter, QPixmap, QPen
from PyQt5.QtCore import Qt, QPoint
class MyLabel(QLabel):
def __init__(self, parent):
super().__init__(parent)
self.drawable = False
# 是否允许作图
def drawingPermission(self, a):
if isinstance(a, bool):
self.drawable = a
# 初始化画布
def initDrawing(self, img):
self.pix = img # 当前图
self.tmpPix = self.pix.copy() # 上一张图
self.lastPoint = QPoint()
self.endPoint = QPoint()
def paintEvent(self, event):
# 先执行父类方法,必须加上
super().paintEvent(event)
if self.drawable:
pp = QPainter(self)
pp.begin(self)
pp.drawPixmap(0, 0, self.pix)
pp.end()
def mousePressEvent(self, event):
# 先执行父类方法,可不加
super().mousePressEvent(event)
if self.drawable:
# 鼠标左键按下
if event.button() == Qt.LeftButton:
self.lastPoint = event.pos()
def mouseMoveEvent(self, event):
# 先执行父类方法,可不加
super().mouseMoveEvent(event)
if self.drawable:
# 鼠标左键按下的同时移动鼠标
if event.buttons() and Qt.LeftButton:
self.endPoint = event.pos()
# 当前图复制上一张图
self.pix = self.tmpPix.copy()
pp = QPainter(self.pix)
pp.setPen(QPen(Qt.green, 5))
pp.drawRect(self.lastPoint.x(), self.lastPoint.y(),
self.endPoint.x() - self.lastPoint.x(),
self.endPoint.y() - self.lastPoint.y())
# 更新label
self.update()
def mouseReleaseEvent(self, event):
# 先执行父类方法,可不加
super().mouseReleaseEvent(event)
if self.drawable:
# 鼠标左键释放
if event.button() == Qt.LeftButton:
# 上一张图指向当前图
self.tmpPix = self.pix
note: QPainter(device),中 device 是作图的上下文,也就是在哪作图(device上)。上面代码有QPainter(self) 和 QPainter(self.pix),这两个作图对象是不同的。
主函数
- 初始化label背景
- 按钮连接槽函数:打开选择文件框和保存文件框
import sys
from PIL import ImageQt, Image
from PyQt5 import QtCore
from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog, QLabel
from FileTest import Ui_MainWindow
class MyMainWindow(QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self)
# 初始化label背景
img = Image.open(r'./upload.jpg')
img = self.transImg(img)
self.label.setPixmap(img)
# 设置当前不可作图
self.flagPaint = False
# connect the slot when you push the button
self.pushButton.clicked.connect(self.getImage)
self.pushButton_2.clicked.connect(self.saveImage)
def getImage(self):
# 该方法会启动文件对话框,当你选择某文件后,返回文件绝对路径和文件类型
fdir, ftype = QFileDialog.getOpenFileName(self,
"Select Image",
"./",
"Image Files (*.png *.jpg)")
# 把选择的图片展示在label上
img = Image.open(fdir)
img = self.transImg(img)
self.label.setPixmap(img)
# 初始化画布,允许作图
self.label.initDrawing(img)
self.label.drawingPermission(True)
self.flagPaint = True
def saveImage(self):
if self.flagPaint:
img = self.label.pix.toImage()
# 该方法同上
fdir, ftype = QFileDialog.getSaveFileName(self, "Save Image",
"./", "Image Files (*.jpg)")
img.save(fdir)
def transImg(self, img):
'''
use to trans PIL img to Qt img
:param img: PIL object
:return: Qt object
'''
img = img.resize((self.label.width(), self.label.height()))
return ImageQt.toqpixmap(img)
if __name__ == "__main__":
# 此句解决Qt Designer和Pycharm显示不同的问题
QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling)
app = QApplication(sys.argv)
myWin = MyMainWindow()
myWin.show()
sys.exit(app.exec_())
效果
参考
- https://blog.csdn.net/zzzzjh/article/details/82985209
- https://blog.csdn.net/jeekmary/article/details/79590570?utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2allsobaiduend~default-3-79590570.nonecase&utm_term=pyqt%20%E8%AF%BB%E5%8F%96%E6%96%87%E4%BB%B6
- 《PyQt5快速开发与实践》王硕,孙洋洋 著