Python12 PyQt5实现鼠标移动绘制图(自定义画笔粗细和颜色,橡皮擦功能,保存文件)

本文详细介绍了一个基于PyQt5的画板程序的设计与实现过程,包括界面布局、功能实现如清空画板、更改画笔颜色和粗细、橡皮擦模式、保存作品等,以及如何响应鼠标事件进行绘图。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

作业要求

在这里插入图片描述
下载后直接用python运行软件打开就行了(Tensorflow不支持)
附件链接://download.youkuaiyun.com/download/weixin_44382897/12036092

可供参考实现的学习文件

老师给出的例子是可以实现图片的转换,然后让我们按照例子来实现画板程序的制作。老师例子的主要代码如下:
附件链接:
链接:https://pan.baidu.com/s/19CRNcntfriRGlP62_0cZIQ
提取码:ikqp

import os
import platform
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *

class myMainWindow(QMainWindow):
      def __init__(self, parent=None):
            super().__init__(parent)
            self.image = QImage()
            self.dirty = False
            self.filename = None
            self.mirroredvertically = False
            self.mirroredhorizontally = False

            #图像
            self.imageLabel = QLabel()
            self.imageLabel.setAlignment(Qt.AlignCenter)
            self.imageLabel.setContextMenuPolicy(Qt.ActionsContextMenu)
            self.setCentralWidget(self.imageLabel)

            #右侧停靠窗口
            logDockWidget = QDockWidget("Log", self)
            logDockWidget.setAllowedAreas(Qt.LeftDockWidgetArea|Qt.RightDockWidgetArea)
            self.listWidget = QListWidget()
            logDockWidget.setWidget(self.listWidget)
            self.addDockWidget(Qt.RightDockWidgetArea, logDockWidget)

            #状态栏
            self.sizeLabel = QLabel()
            self.sizeLabel.setFrameStyle(QFrame.StyledPanel|QFrame.Sunken)
            status = self.statusBar()
            status.setSizeGripEnabled(False)
            status.addPermanentWidget(self.sizeLabel)
            status.showMessage("Ready", 5000)

            #各种动作
            self.fileOpenAction = QAction(QIcon("images/fileopen.png"), "&Open", self)
            self.fileOpenAction.setShortcut(QKeySequence.Open)
            self.fileOpenAction.setToolTip("Open an image.")
            self.fileOpenAction.setStatusTip("Open an image.")
            self.fileOpenAction.triggered.connect(self.fileOpen)

            self.fileSaveAction = QAction(QIcon("images/filesave.png"), "&Save", self)
            self.fileSaveAction.setShortcut(QKeySequence.Save)
            self.fileSaveAction.setToolTip("Save an image.")
            self.fileSaveAction.setStatusTip("Save an image.")
            self.fileSaveAction.triggered.connect(self.fileSaveAs)
            
            self.editUnMirrorAction = QAction(QIcon("images/editunmirror.png"), "&Unmirror", self)
            self.editUnMirrorAction.setShortcut("Ctrl+U")
            self.editUnMirrorAction.setToolTip("Unmirror the image")
            self.editUnMirrorAction.setStatusTip("Unmirror the image")
            self.editUnMirrorAction.setCheckable(True)
            self.editUnMirrorAction.setChecked(True)
            self.editUnMirrorAction.toggled.connect(self.editUnMirror)
            
            editMirrorHorizontalAction = QAction(QIcon("images/editmirrorhoriz.png"), "Mirror &Horizontally", self)
            editMirrorHorizontalAction.setShortcut("Ctrl+H")
            editMirrorHorizontalAction.setToolTip("Horizontally mirror the image")
            editMirrorHorizontalAction.setStatusTip("Horizontally mirror the image")
            editMirrorHorizontalAction.setCheckable(True)
            editMirrorHorizontalAction.toggled.connect(self.editMirrorHorizontal)

            editMirrorVerticalAction = QAction(QIcon("images/editmirrorvert.png"), "Mirror &Vertically", self)
            editMirrorVerticalAction.setShortcut("Ctrl+V")
            editMirrorVerticalAction.setToolTip("Vertically mirror the image")
            editMirrorVerticalAction.setStatusTip("Vertically mirror the image")
            editMirrorVerticalAction.setCheckable(True)
            editMirrorVerticalAction.toggled.connect(self.editMirrorVertical)
            
            mirrorGroup = QActionGroup(self)
            mirrorGroup.addAction(self.editUnMirrorAction)
            mirrorGroup.addAction(editMirrorHorizontalAction)
            mirrorGroup.addAction(editMirrorVerticalAction)

            #菜单栏
            self.fileMenu = self.menuBar().addMenu("&File")            
            self.fileMenu.addAction(self.fileOpenAction)
            self.fileMenu.addAction(self.fileSaveAction)

            editMenu = self.menuBar().addMenu("&Edit")
            editMenu.addAction(self.editUnMirrorAction)
            editMenu.addAction(editMirrorHorizontalAction)
            editMenu.addAction(editMirrorVerticalAction)
            
            #工具栏
            fileToolbar = self.addToolBar("File")
            fileToolbar.addAction(self.fileOpenAction)
            fileToolbar.addAction(self.fileSaveAction)

            editToolbar = self.addToolBar("Edit")
            editToolbar.addAction(self.editUnMirrorAction)
            editToolbar.addAction(editMirrorHorizontalAction)
            editToolbar.addAction(editMirrorVerticalAction)

            self.recentFiles = []
            self.setWindowTitle("Image Changer")      

      def okToContinue(self): #警告当前图像未保存
            if self.dirty:
                reply = QMessageBox.question(self,
                        "Image Changer - Unsaved Changes",
                        "Save unsaved changes?",
                        QMessageBox.Yes|QMessageBox.No|QMessageBox.Cancel)
                if reply == QMessageBox.Cancel:
                    return False
                elif reply == QMessageBox.Yes:
                    return self.fileSaveAs()
            return True
      
      def fileOpen(self):
            if not self.okToContinue():
                  return
            dir = (os.path.dirname(self.filename)
                   if self.filename is not None else ".")
            formats = (["*.{}".format(format.data().decode("ascii").lower())
                  for format in QImageReader.supportedImageFormats()])
            fname = QFileDialog.getOpenFileName(self,
                  "Image Changer - Choose Image", dir,
                  "Image files ({})".format(" ".join(formats)))
            if fname:
                  print(fname[0])
                  self.loadFile(fname[0])
                  self.updateFileMenu()
            
      def loadFile(self, fname=None):
            if fname is None:
                  action = self.sender()
                  if isinstance(action, QAction):
                        fname = action.data()
                        if not self.okToContinue():
                            return
                  else:
                        return
            if fname:
                  self.filename = None
                  image = QImage(fname)
                  if image.isNull():
                        message = "Failed to read {}".format(fname)
                  else:
                        self.addRecentFile(fname)
                        self.image = QImage()
                        self.editUnMirrorAction.setChecked(True)
                        self.image = image
                        self.filename = fname
                        self.showImage()
                        self.dirty = False
                        self.sizeLabel.setText("{} x {}".format(
                                               image.width(), image.height()))
                        message = "Loaded {}".format(os.path.basename(fname))
                  self.updateStatus(message)
            
      def updateStatus(self, message):
            self.statusBar().showMessage(message, 5000)
            self.listWidget.addItem(message)
            if self.filename:
                  self.setWindowTitle("Image Changer - {}[*]".format(
                        os.path.basename(self.filename)))
            elif not self.image.isNull():
                  self.setWindowTitle("Image Changer - Unnamed[*]")
            else:
                  self.setWindowTitle("Image Changer[*]")
            self.setWindowModified(self.dirty)


      def updateFileMenu(self):
            self.fileMenu.clear()
            self.fileMenu.addAction(self.fileOpenAction)
            self.fileMenu.addAction(self.fileSaveAction)
            current = self.filename
            recentFiles = []
            print(self.recentFiles)
            for fname in self.recentFiles:
                if fname != current and QFile.exists(fname):
                    recentFiles.append(fname)
            if recentFiles:
                self.fileMenu.addSeparator()
                for i, fname in enumerate(recentFiles):
                    action = QAction(QIcon("images/icon.png"),
                            "&{} {}".format(i + 1, QFileInfo(
                            fname).fileName()), self)
                    action.setData(fname)
                    action.triggered.connect(lambda: self.loadFile(fname))
                    self.fileMenu.addAction(action)


      def addRecentFile(self, fname):
            if fname is None:
                  return
            if fname not in self.recentFiles:                 
                  if len(self.recentFiles) < 10:
                        self.recentFiles = [fname] + self.recentFiles
                  else:
                        self.recentFiles = [fname] + self.recentFiles[:8]
                  print(len(self.recentFiles))
                  

      def fileSaveAs(self):
            if self.image.isNull():
                  return True
            fname = self.filename if self.filename is not None else "."
            formats = (["*.{}".format(format.data().decode("ascii").lower())
                  for format in QImageWriter.supportedImageFormats()])
            fname = QFileDialog.getSaveFileName(self,
                  "Image Changer - Save Image", fname,
                  "Image files ({})".format(" ".join(formats)))
            fname = fname[0]
            if fname:
                  print(fname)
                  if "." not in fname:
                        fname += ".png"
                  self.addRecentFile(fname)
                  self.filename = fname

                  if self.image.save(self.filename, None):
                      self.updateStatus("Saved as {}".format(self.filename))
                      self.dirty = False
                      return True
                  else:
                      self.updateStatus("Failed to save {}".format(
                                        self.filename))
                      return False                  
            return False
                    
      def editUnMirror(self, on):
            if self.image.isNull():
                  return
            if self.mirroredhorizontally:
                  self.editMirrorHorizontal(False)
            if self.mirroredvertically:
                  self.editMirrorVertical(False)


      def editMirrorHorizontal(self, on):
            if self.image.isNull():
                  return
            self.image = self.image.mirrored(True, False)
            self.showImage()
            self.mirroredhorizontally = not self.mirroredhorizontally
            self.dirty = True
            self.updateStatus(("Mirrored Horizontally"
                        if on else "Unmirrored Horizontally"))


      def editMirrorVertical(self, on):
            if self.image.isNull():
                  return
            self.image = self.image.mirrored(False, True)
            self.showImage()
            self.mirroredvertically = not self.mirroredvertically
            self.dirty = True
            self.updateStatus(("Mirrored Vertically"
                              if on else "Unmirrored Vertically"))
            
      def showImage(self, percent=None):
            if self.image.isNull():
                return
            self.imageLabel.setPixmap(QPixmap.fromImage(self.image))
        
app = QApplication(sys.argv)
form = myMainWindow()
form.setMinimumSize(1000, 1000)
form.show()
app.exec_()

代码的运行效果:
可以实现图片的倒转
根据老师提出的问题,我的解决方案参考了网上其他博客的代码:

在这里插入代码片
# 参考本节例程,实现一个鼠标绘图程序:
# 可以更改笔刷的颜色和粗细,具体方式自己设计,合理即可
# 可以读取和保存图片

import platform
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *



from PyQt5.QtWidgets import QApplication
import sys
from PyQt5.QtWidgets import QWidget
from PyQt5.Qt import QPixmap, QPainter, QPoint, QPaintEvent, QMouseEvent, QPen,\
    QColor, QSize
from PyQt5.QtCore import Qt
from PyQt5.Qt import QWidget, QColor, QPixmap, QIcon, QSize, QCheckBox
from PyQt5.QtWidgets import QHBoxLayout, QVBoxLayout, QPushButton, QSplitter,\
    QComboBox, QLabel, QSpinBox, QFileDialog


def main():
    app = QApplication(sys.argv)

    mainWidget = MainWidget() #新建一个主界面
    mainWidget.show()    #显示主界面

    exit(app.exec_()) #进入消息循环


class PaintBoard(QWidget):

    def __init__(self, Parent=None):
        '''
        Constructor
        '''
        super().__init__(Parent)

        self.__InitData()  # 先初始化数据,再初始化界面
        self.__InitView()
        self.setWindowTitle("画笔")

    def __InitData(self):

        self.__size = QSize(480, 460)

        # 新建QPixmap作为画板,尺寸为__size
        self.__board = QPixmap(self.__size)
        self.__board.fill(Qt.white)  # 用白色填充画板

        self.__IsEmpty = True  # 默认为空画板
        self.EraserMode = False  # 默认为禁用橡皮擦模式

        self.__lastPos = QPoint(0, 0)  # 上一次鼠标位置
        self.__currentPos = QPoint(0, 0)  # 当前的鼠标位置

        self.__painter = QPainter()  # 新建绘图工具

        self.__thickness = 10  # 默认画笔粗细为10px
        self.__penColor = QColor("black")  # 设置默认画笔颜色为黑色
        self.__colorList = QColor.colorNames()  # 获取颜色列表

    def __InitView(self):
        # 设置界面的尺寸为__size
        self.setFixedSize(self.__size)

    def Clear(self):
        # 清空画板
        self.__board.fill(Qt.white)
        self.update()
        self.__IsEmpty = True

    def ChangePenColor(self, color="black"):
        # 改变画笔颜色
        self.__penColor = QColor(color)

    def ChangePenThickness(self, thickness=10):
        # 改变画笔粗细
        self.__thickness = thickness

    def IsEmpty(self):
        # 返回画板是否为空
        return self.__IsEmpty

    def GetContentAsQImage(self):
        # 获取画板内容(返回QImage)
        image = self.__board.toImage()
        return image

    def paintEvent(self, paintEvent):
        # 绘图事件
        # 绘图时必须使用QPainter的实例,此处为__painter
        # 绘图在begin()函数与end()函数间进行
        # begin(param)的参数要指定绘图设备,即把图画在哪里
        # drawPixmap用于绘制QPixmap类型的对象
        self.__painter.begin(self)
        # 0,0为绘图的左上角起点的坐标,__board即要绘制的图
        self.__painter.drawPixmap(0, 0, self.__board)
        self.__painter.end()

    def mousePressEvent(self, mouseEvent):
        # 鼠标按下时,获取鼠标的当前位置保存为上一次位置
        self.__currentPos = mouseEvent.pos()
        self.__lastPos = self.__currentPos

    def mouseMoveEvent(self, mouseEvent):
        # 鼠标移动时,更新当前位置,并在上一个位置和当前位置间画线
        self.__currentPos = mouseEvent.pos()
        self.__painter.begin(self.__board)

        if self.EraserMode == False:
            # 非橡皮擦模式
            self.__painter.setPen(QPen(self.__penColor, self.__thickness))  # 设置画笔颜色,粗细
        else:
            # 橡皮擦模式下画笔为纯白色,粗细为10
            self.__painter.setPen(QPen(Qt.white, 10))

        # 画线
        self.__painter.drawLine(self.__lastPos, self.__currentPos)
        self.__painter.end()
        self.__lastPos = self.__currentPos

        self.update()  # 更新显示

    def mouseReleaseEvent(self, mouseEvent):
        self.__IsEmpty = False  # 画板不再为空


class MainWidget(QWidget):

    def __init__(self, Parent=None):
        '''
        Constructor
        '''
        super().__init__(Parent)

        self.__InitData()  # 先初始化数据,再初始化界面
        self.__InitView()

    def __InitData(self):
        '''
                  初始化成员变量
        '''
        self.__paintBoard = PaintBoard(self)
        # 获取颜色列表(字符串类型)
        self.__colorList = QColor.colorNames()

    def __InitView(self):
        '''
                  初始化界面
        '''
        self.setFixedSize(640, 480)
        self.setWindowTitle("Paint brush")

        # 新建一个水平布局作为本窗体的主布局
        main_layout = QHBoxLayout(self)
        # 设置主布局内边距以及控件间距为10px
        main_layout.setSpacing(10)

        # 在主界面左侧放置画板
        main_layout.addWidget(self.__paintBoard)

        # 新建垂直子布局用于放置按键
        sub_layout = QVBoxLayout()

        # 设置此子布局和内部控件的间距为10px
        sub_layout.setContentsMargins(10, 10, 10, 10)

        self.__btn_Clear = QPushButton("清空画板")
        self.__btn_Clear.setParent(self)  # 设置父对象为本界面

        # 将按键按下信号与画板清空函数相关联
        self.__btn_Clear.clicked.connect(self.__paintBoard.Clear)
        sub_layout.addWidget(self.__btn_Clear)

        self.__btn_Quit = QPushButton("退出")
        self.__btn_Quit.setParent(self)  # 设置父对象为本界面
        self.__btn_Quit.clicked.connect(self.Quit)
        sub_layout.addWidget(self.__btn_Quit)

        self.__btn_Save = QPushButton("保存作品")
        self.__btn_Save.setParent(self)
        self.__btn_Save.clicked.connect(self.on_btn_Save_Clicked)
        sub_layout.addWidget(self.__btn_Save)

        self.__cbtn_Eraser = QCheckBox("  使用橡皮擦")
        self.__cbtn_Eraser.setParent(self)
        self.__cbtn_Eraser.clicked.connect(self.on_cbtn_Eraser_clicked)
        sub_layout.addWidget(self.__cbtn_Eraser)

        splitter = QSplitter(self)  # 占位符
        sub_layout.addWidget(splitter)

        self.__label_penThickness = QLabel(self)
        self.__label_penThickness.setText("画笔粗细")
        self.__label_penThickness.setFixedHeight(20)
        sub_layout.addWidget(self.__label_penThickness)

        self.__spinBox_penThickness = QSpinBox(self)
        self.__spinBox_penThickness.setMaximum(20)
        self.__spinBox_penThickness.setMinimum(2)
        self.__spinBox_penThickness.setValue(10)  # 默认粗细为10
        self.__spinBox_penThickness.setSingleStep(2)  # 最小变化值为2
        self.__spinBox_penThickness.valueChanged.connect(
            self.on_PenThicknessChange)  # 关联spinBox值变化信号和函数on_PenThicknessChange
        sub_layout.addWidget(self.__spinBox_penThickness)

        self.__label_penColor = QLabel(self)
        self.__label_penColor.setText("画笔颜色")
        self.__label_penColor.setFixedHeight(20)
        sub_layout.addWidget(self.__label_penColor)

        self.__comboBox_penColor = QComboBox(self)
        self.__fillColorList(self.__comboBox_penColor)  # 用各种颜色填充下拉列表
        self.__comboBox_penColor.currentIndexChanged.connect(
            self.on_PenColorChange)  # 关联下拉列表的当前索引变更信号与函数on_PenColorChange
        sub_layout.addWidget(self.__comboBox_penColor)

        main_layout.addLayout(sub_layout)  # 将子布局加入主布局

    def __fillColorList(self, comboBox):

        index_black = 0
        index = 0
        for color in self.__colorList:
            if color == "black":
                index_black = index
            index += 1
            pix = QPixmap(70, 20)
            pix.fill(QColor(color))
            comboBox.addItem(QIcon(pix), None)
            comboBox.setIconSize(QSize(70, 20))
            comboBox.setSizeAdjustPolicy(QComboBox.AdjustToContents)

        comboBox.setCurrentIndex(index_black)

    def on_PenColorChange(self):
        color_index = self.__comboBox_penColor.currentIndex()
        color_str = self.__colorList[color_index]
        self.__paintBoard.ChangePenColor(color_str)

    def on_PenThicknessChange(self):
        penThickness = self.__spinBox_penThickness.value()
        self.__paintBoard.ChangePenThickness(penThickness)

    def on_btn_Save_Clicked(self):
        savePath = QFileDialog.getSaveFileName(self, 'Save Your Paint', '.\\', '*.png')
        print(savePath)
        if savePath[0] == "":
            print("Save cancel")
            return
        image = self.__paintBoard.GetContentAsQImage()
        image.save(savePath[0])

    def on_cbtn_Eraser_clicked(self):
        if self.__cbtn_Eraser.isChecked():
            self.__paintBoard.EraserMode = True  # 进入橡皮擦模式
        else:
            self.__paintBoard.EraserMode = False  # 退出橡皮擦模式

    def Quit(self):
        self.close()


if __name__ == '__main__':
    main()



代码实现效果:
画板实现效果图

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值