PySide6 GUI 编程(42):QPainter 的使用

2024-09-15 09:50:53 浏览数 (3)

QPainter的作用

QPainter 是 PySide6 中用于在小部件和其他绘图设备上进行低级绘图的类。

它提供了一系列的绘图函数,可以绘制从简单线条到复杂形状(如饼图和和弦图)的各种图形。

QPainter 还可以绘制对齐文本和图片。

通常,它使用“自然”坐标系进行绘图,但也支持视图和世界坐标转换。

QPainter 的基础使用范式

绘制线条的示例代码

代码语言:python代码运行次数:0复制
from __future__ import annotations

import sys
from datetime import datetime

from PySide6.QtCore import QLine, QPoint, Qt
from PySide6.QtGui import QPainter, QPixmap
from PySide6.QtWidgets import QApplication, QLabel, QMainWindow


def get_time_str() -> str:
    return datetime.now().isoformat(sep = ' ')


class MyMainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('Hello, PySide6!')
        self.setToolTip('A PySide6 GUI Application Demo')

        # 设置画布大小
        self.pixmap = QPixmap(400, 300)
        self.pixmap.fill(Qt.GlobalColor.black)

        # 在画布上绘制图线
        self.painter = QPainter(self.pixmap)
        self.painter.setPen(Qt.GlobalColor.red)
        # 使用坐标画线
        self.painter.drawLine(0, 0, 400, 300)
        self.painter.drawLine(400, 0, 0, 300)
        """
        如果不调用这个方法,那么这些资源可能会被锁定,导致无法被其他QPainter对象使用,
        或者在某些情况下可能会导致内存泄漏,特别是当你在一个循环中多次使用QPainter对象时,
        如果不调用end()方法,可能会导致程序崩溃或者运行效率降低
        """
        self.painter.end()

        # 使用 QLine 画线
        self.painter = QPainter(self.pixmap)
        self.painter.setPen(Qt.GlobalColor.blue)
        self.painter.drawLine(QLine(0, 150, 400, 150))
        self.painter.end()

        # 使用 QPoint 画线
        self.painter = QPainter(self.pixmap)
        self.painter.setPen(Qt.GlobalColor.cyan)
        self.painter.drawLine(QPoint(0, 100), QPoint(400, 100))
        self.painter.end()

        self.label = QLabel()
        self.label.setPixmap(self.pixmap)
        self.setCentralWidget(self.label)

        self.update()


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MyMainWindow()
    window.show()
    app.exec()

通常来说,使用 QPainter 画图的步骤为:

  1. 设置画布大小 self.pixmap = QPixmap(400, 300)
  2. 初始化QPainter self.painter = QPainter(self.pixmap)
  3. 设置画笔 self.painter.setPen(Qt.GlobalColor.red)
  4. 画图 self.painter.drawxxxx
  5. 释放 painter 实例 self.painter.end()
  6. 刷新图形到画布 self.label.setPixmap(self.pixmap)
  7. 刷新所有视图 self.update()

运行效果

使用 QPainter 画线使用 QPainter 画线

QPainter常用的基础图形

绘制坐标点

示例代码

代码语言:python代码运行次数:0复制
from __future__ import annotations

import sys
from random import choice, randint

from PySide6.QtCore import QPoint, Qt
from PySide6.QtGui import QPainter, QPen, QPixmap
from PySide6.QtWidgets import QApplication, QLabel, QMainWindow


class MyMainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('Hello, PySide6!')
        self.setToolTip('A PySide6 GUI Application Demo')

        # 设置画布大小
        self.pixmap = QPixmap(400, 300)
        self.pixmap.fill(Qt.GlobalColor.black)

        # 在画布上绘制图线
        self.painter = QPainter(self.pixmap)
        self.pen = QPen()
        self.pen.setWidth(3)
        self.pen.setColor(Qt.GlobalColor.red)
        self.painter.setPen(self.pen)
        colors = [x for x in Qt.GlobalColor]
        # 使用坐标画线
        for i in range(1, 10000):
            self.pen.setColor(choice(colors))
            # 这里需要每次都重新设置 pen,否则颜色不生效
            # There can only ever be one  QPen active on a QPainter
            # -- the current pen.
            self.painter.setPen(self.pen)
            self.painter.drawPoint(QPoint(200   randint(-100, 100), 150   randint(-100, 100)))
        """
        如果不调用这个方法,那么这些资源可能会被锁定,导致无法被其他QPainter对象使用,
        或者在某些情况下可能会导致内存泄漏,特别是当你在一个循环中多次使用QPainter对象时,
        如果不调用end()方法,会导致程序崩溃或者运行效率降低
        """
        self.painter.end()

        self.label = QLabel()
        self.label.setPixmap(self.pixmap)
        self.setCentralWidget(self.label)

        self.update()


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MyMainWindow()
    window.show()
    app.exec()

运行效果

随机彩色矩形点阵随机彩色矩形点阵

绘制矩形

示例代码

代码语言:python代码运行次数:0复制
from __future__ import annotations

import sys

from PySide6.QtCore import QRect, Qt
from PySide6.QtGui import QBrush, QColor, QPainter, QPen, QPixmap
from PySide6.QtWidgets import QApplication, QLabel, QMainWindow


class MyMainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('Hello, PySide6!')
        self.setToolTip('A PySide6 GUI Application Demo')

        # 设置画布大小
        self.pixmap = QPixmap(400, 300)
        self.pixmap.fill(Qt.GlobalColor.black)

        # 在画布上绘制图线
        self.painter = QPainter(self.pixmap)
        self.pen = QPen()
        self.pen.setWidth(3)
        self.pen.setColor(Qt.GlobalColor.red)
        self.painter.setPen(self.pen)
        # 第一个参数:x 坐标,表示矩形左上角的水平位置
        # 第二个参数:y 坐标,表示矩形左上角的垂直位置
        # 第三个参数:width,表示矩形的宽度
        # 第四个参数:height,表示矩形的高度
        self.painter.drawRect(10, 10, 200, 200)

        self.pen.setColor(Qt.GlobalColor.blue)
        self.painter.setPen(self.pen)
        self.painter.drawRects([QRect(20, 20, 20, 20),
                                QRect(20, 20, 30, 30),
                                QRect(20, 20, 40, 40),
                                QRect(20, 20, 50, 50),
                                QRect(20, 20, 60, 60)])

        self.pen.setColor(Qt.GlobalColor.green)
        self.painter.setPen(self.pen)
        self.brush = QBrush()
        self.brush.setStyle(Qt.BrushStyle.SolidPattern)
        self.brush.setColor(QColor("#FFD007"))
        self.painter.setBrush(self.brush)
        self.painter.drawRoundedRect(QRect(85, 85, 100, 100), 100, 100, Qt.SizeMode.RelativeSize)

        """
        如果不调用这个方法,那么这些资源可能会被锁定,导致无法被其他QPainter对象使用,
        或者在某些情况下可能会导致内存泄漏,特别是当你在一个循环中多次使用QPainter对象时,
        如果不调用end()方法,可能会导致程序崩溃或者运行效率降低
        """
        self.painter.end()

        self.label = QLabel()
        self.label.setPixmap(self.pixmap)
        self.setCentralWidget(self.label)

        self.update()


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MyMainWindow()
    window.show()
    app.exec()

上述示例中,使用drawRectdrawRectsdrawRoundedRect分别绘制了矩形,其中,drawRoundedRect中,当把曲率调位 100 时,矩形将会变为圆形。

drawRoundedRect函数原型drawRoundedRect函数原型

运行效果

绘制矩形绘制矩形

绘制椭圆形

示例代码

代码语言:python代码运行次数:0复制
from __future__ import annotations

import sys

from PySide6.QtCore import QPoint, Qt
from PySide6.QtGui import QBrush, QPainter, QPen, QPixmap
from PySide6.QtWidgets import QApplication, QLabel, QMainWindow


class MyMainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('Hello, PySide6!')
        self.setToolTip('A PySide6 GUI Application Demo')

        # 设置画布大小
        self.pixmap = QPixmap(400, 300)
        self.pixmap.fill(Qt.GlobalColor.black)

        # 在画布上绘制图线
        self.painter = QPainter(self.pixmap)
        self.pen = QPen()
        self.pen.setWidth(3)
        self.pen.setColor(Qt.GlobalColor.green)
        self.painter.setPen(self.pen)
        self.brush = QBrush()
        self.brush.setStyle(Qt.BrushStyle.SolidPattern)
        self.brush.setColor(Qt.GlobalColor.red)
        self.painter.setBrush(self.brush)
        self.painter.drawEllipse(QPoint(200, 150), 100, 50)

        """
        如果不调用这个方法,那么这些资源可能会被锁定,导致无法被其他QPainter对象使用,
        或者在某些情况下可能会导致内存泄漏,特别是当你在一个循环中多次使用QPainter对象时,
        如果不调用end()方法,可能会导致程序崩溃或者运行效率降低
        """
        self.painter.end()

        self.label = QLabel()
        self.label.setPixmap(self.pixmap)
        self.setCentralWidget(self.label)

        self.update()


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MyMainWindow()
    window.show()
    app.exec()

运行效果

绘制椭圆形绘制椭圆形

绘制文本

示例代码

代码语言:python代码运行次数:0复制
from __future__ import annotations

import sys
from datetime import datetime

from PySide6.QtCore import Qt
from PySide6.QtGui import QFont, QPainter, QPen, QPixmap
from PySide6.QtWidgets import QApplication, QLabel, QMainWindow


class MyMainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('Hello, PySide6!')
        self.setToolTip('A PySide6 GUI Application Demo')

        # 设置画布大小
        self.pixmap = QPixmap(600, 400)
        self.pixmap.fill(Qt.GlobalColor.black)

        # 在画布上绘制图线
        self.painter = QPainter(self.pixmap)
        self.pen = QPen()
        self.pen.setWidth(5)
        self.pen.setColor(Qt.GlobalColor.darkMagenta)
        self.painter.setPen(self.pen)

        self.font = QFont('ComicShannsMono Nerd Font', 40)
        # self.font.setBold(True)
        self.painter.setFont(self.font)
        self.painter.drawText(0, 0, 600, 400, Qt.AlignmentFlag.AlignCenter,
                              'Hello, PySide6!n你好,GUI 编程!n{}n{}'.format(datetime.now().isoformat(), self.font.family()))
        """
        如果不调用这个方法,那么这些资源可能会被锁定,导致无法被其他QPainter对象使用,
        或者在某些情况下可能会导致内存泄漏,特别是当你在一个循环中多次使用QPainter对象时,
        如果不调用end()方法,可能会导致程序崩溃或者运行效率降低
        """
        self.painter.end()

        self.label = QLabel()
        self.label.setPixmap(self.pixmap)
        self.setCentralWidget(self.label)

        self.update()


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MyMainWindow()
    window.show()
    app.exec()

运行效果

绘制文本绘制文本

QPainter 的高级用法

基于鼠标坐标绘制线条

示例代码

代码语言:python代码运行次数:0复制
from __future__ import annotations

import sys

from PySide6.QtCore import Qt
from PySide6.QtGui import QMouseEvent, QPainter, QPen, QPixmap
from PySide6.QtWidgets import QApplication, QLabel, QMainWindow, QPushButton, QVBoxLayout, QWidget


class MyMainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('Hello, PySide6!')
        self.setToolTip('A PySide6 GUI Application Demo')

        # 设置画布初始状态
        self.pixmap = QPixmap(800, 600)
        self.pixmap.fill(Qt.GlobalColor.white)

        # 设置布局
        self.label = QLabel()
        self.label.setPixmap(self.pixmap)
        self.button = QPushButton('清除所有')
        self.button.clicked.connect(self.clear_all)
        self.v_layout = QVBoxLayout()
        self.v_layout.addWidget(self.label)
        self.v_layout.addWidget(self.button)
        self.container = QWidget()
        self.container.setLayout(self.v_layout)
        self.setCentralWidget(self.container)

        # 鼠标坐标
        self.last_x = None
        self.last_y = None

    def mouseMoveEvent(self, event: QMouseEvent) -> None:
        pos = event.position()
        if self.last_x is None and self.last_y is None:
            self.last_x = pos.x()
            self.last_y = pos.y()
            return

        # 在画布上绘制图线
        painter = QPainter(self.pixmap)
        pen = QPen()
        pen.setWidth(3)
        pen.setStyle(Qt.PenStyle.SolidLine)
        pen.setCosmetic(True)
        pen.setColor(Qt.GlobalColor.darkMagenta)
        painter.setPen(pen)
        painter.drawLine(self.last_x, self.last_y, int(pos.x()), int(pos.y()))
        """
        如果不调用这个方法,那么这些资源可能会被锁定,导致无法被其他QPainter对象使用,
        或者在某些情况下可能会导致内存泄漏,特别是当你在一个循环中多次使用QPainter对象时,
        如果不调用end()方法,可能会导致程序崩溃或者运行效率降低
        """
        painter.end()
        # 更新QLabel中显示的内容,使其显示最新的QPixmap
        self.label.setPixmap(self.pixmap)
        # 记录上一次的鼠标坐标
        self.last_x = pos.x()
        self.last_y = pos.y()
        # 显式刷新界面
        self.update()

    def mouseReleaseEvent(self, event: QMouseEvent) -> None:
        self.last_x = None
        self.last_y = None

    def clear_all(self) -> None:
        self.pixmap = QPixmap(800, 600)
        self.pixmap.fill(Qt.GlobalColor.white)
        self.label.setPixmap(self.pixmap)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MyMainWindow()
    window.show()
    app.exec()

运行效果

使用鼠标画图使用鼠标画图

更改画笔颜色

示例代码

代码语言:python代码运行次数:0复制
from __future__ import annotations

import sys

from PySide6.QtCore import Qt
from PySide6.QtGui import QMouseEvent, QPainter, QPen, QPixmap
from PySide6.QtWidgets import QApplication, QHBoxLayout, QLabel, QMainWindow, QPushButton, QVBoxLayout, QWidget


class MyMainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('Hello, PySide6!')
        self.setToolTip('A PySide6 GUI Application Demo')

        # 设置画布初始状态
        self.pixmap = QPixmap(800, 600)
        self.pixmap.fill(Qt.GlobalColor.white)

        # 设置布局
        self.label = QLabel()
        self.label.setPixmap(self.pixmap)

        # 颜色列表
        self.colors_v_layout = QVBoxLayout()
        colors_h_layout = QHBoxLayout()
        self.colors_map = {}
        colors_count = 0
        for x in Qt.GlobalColor:
            self.colors_map[x.name] = x
            # css中没有 darkYellow 这种颜色,需要用 #CCCC00 来表示
            # 因此为了逻辑上的方便,QPushButton 中不使用 darkYellow 来作为背景色
            # 同时像 transparent、color0、color1 这样的颜色也过滤掉
            # 由于画板的背景色已经是白色,因此这里也将 white 过滤掉
            if x.name in ('transparent', 'white', 'color0', 'color1', 'darkYellow'):
                continue
            color_button = QPushButton(x.name)
            style = """
            QPushButton { background-color: %s; color: white; }
            QPushButton:pressed { background-color: #CCCC00; }
            """ % x.name
            color_button.setStyleSheet(style)
            color_button.clicked.connect(self.color_changed)
            colors_h_layout.addWidget(color_button)
            colors_count  = 1
            if colors_count % 9 == 0:
                self.colors_v_layout.addLayout(colors_h_layout)
                colors_h_layout = QHBoxLayout()
                colors_count = 0
        self.colors_v_layout.addLayout(colors_h_layout)

        # 清除按钮
        self.button = QPushButton('清除所有')
        self.button.clicked.connect(self.clear_all)

        # 布局
        self.v_layout = QVBoxLayout()
        self.v_layout.addWidget(self.label)
        self.v_layout.addLayout(self.colors_v_layout)
        self.v_layout.addWidget(self.button)

        # 容器
        self.container = QWidget()
        self.container.setLayout(self.v_layout)

        # 组件排布
        self.setCentralWidget(self.container)

        # 鼠标坐标
        self.last_x = None
        self.last_y = None
        self.current_color = Qt.GlobalColor.darkMagenta

    def mouseMoveEvent(self, event: QMouseEvent) -> None:
        pos = event.position()
        if self.last_x is None and self.last_y is None:
            self.last_x = pos.x()
            self.last_y = pos.y()
            return

        # 在画布上绘制图线
        painter = QPainter(self.pixmap)
        pen = QPen()
        pen.setWidth(3)
        pen.setStyle(Qt.PenStyle.SolidLine)
        pen.setCosmetic(True)
        pen.setColor(self.current_color)
        painter.setPen(pen)
        painter.drawLine(self.last_x, self.last_y, int(pos.x()), int(pos.y()))
        """
        如果不调用这个方法,那么这些资源可能会被锁定,导致无法被其他QPainter对象使用,
        或者在某些情况下可能会导致内存泄漏,特别是当你在一个循环中多次使用QPainter对象时,
        如果不调用end()方法,可能会导致程序崩溃或者运行效率降低
        """
        painter.end()
        # 更新QLabel中显示的内容,使其显示最新的QPixmap
        self.label.setPixmap(self.pixmap)
        # 记录上一次的鼠标坐标
        self.last_x = pos.x()
        self.last_y = pos.y()
        # 显式刷新界面
        self.update()

    def mouseReleaseEvent(self, event: QMouseEvent) -> None:
        self.last_x = None
        self.last_y = None

    def clear_all(self) -> None:
        self.pixmap = QPixmap(800, 600)
        self.pixmap.fill(Qt.GlobalColor.white)
        self.label.setPixmap(self.pixmap)

    def color_changed(self) -> None:
        self.current_color = self.colors_map[self.sender().text()]


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MyMainWindow()
    window.show()
    app.exec()

界面静态效果

界面静态效果界面静态效果

动态效果

更改画笔颜色更改画笔颜色

喷雾效果

示例代码

代码语言:python代码运行次数:0复制
from __future__ import annotations

import random
import sys
from cmath import pi

from PySide6.QtCore import Qt
from PySide6.QtGui import QMouseEvent, QPainter, QPen, QPixmap
from PySide6.QtWidgets import QApplication, QHBoxLayout, QLabel, QMainWindow, QPushButton, QVBoxLayout, QWidget


class MyMainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('Hello, PySide6!')
        self.setToolTip('A PySide6 GUI Application Demo')

        # 设置画布初始状态
        self.pixmap = QPixmap(800, 600)
        self.pixmap.fill(Qt.GlobalColor.white)

        # 设置布局
        self.label = QLabel()
        self.label.setPixmap(self.pixmap)

        # 颜色列表
        self.colors_v_layout = QVBoxLayout()
        colors_h_layout = QHBoxLayout()
        self.colors_map = {}
        colors_count = 0
        for x in Qt.GlobalColor:
            self.colors_map[x.name] = x
            # css中没有 darkYellow 这种颜色,需要用 #CCCC00 来表示
            # 因此为了逻辑上的方便,QPushButton 中不使用 darkYellow 来作为背景色
            # 同时像 transparent、color0、color1 这样的颜色也过滤掉
            # 由于画板的背景色已经是白色,因此这里也将 white 过滤掉
            if x.name in ('transparent', 'white', 'color0', 'color1', 'darkYellow'):
                continue
            color_button = QPushButton(x.name)
            style = """
            QPushButton { background-color: %s; color: white; }
            QPushButton:pressed { background-color: #CCCC00; }
            """ % x.name
            color_button.setStyleSheet(style)
            color_button.clicked.connect(self.color_changed)
            colors_h_layout.addWidget(color_button)
            colors_count  = 1
            if colors_count % 9 == 0:
                self.colors_v_layout.addLayout(colors_h_layout)
                colors_h_layout = QHBoxLayout()
                colors_count = 0
        self.colors_v_layout.addLayout(colors_h_layout)

        # 清除按钮
        self.button = QPushButton('清除所有')
        self.button.clicked.connect(self.clear_all)

        # 布局
        self.v_layout = QVBoxLayout()
        self.v_layout.addWidget(self.label)
        self.v_layout.addLayout(self.colors_v_layout)
        self.v_layout.addWidget(self.button)

        # 容器
        self.container = QWidget()
        self.container.setLayout(self.v_layout)

        # 组件排布
        self.setCentralWidget(self.container)

        # 鼠标坐标
        self.current_color = Qt.GlobalColor.darkMagenta

    def mousePressEvent(self, event: QMouseEvent) -> None:
        pos = event.position()
        # 在画布上绘制图线
        painter = QPainter(self.pixmap)
        pen = QPen()
        pen.setWidth(3)
        pen.setStyle(Qt.PenStyle.SolidLine)
        pen.setCosmetic(True)
        pen.setColor(self.current_color)
        painter.setPen(pen)
        radius = 15
        for _ in range(0, int(pi * (radius ** 2))):
            x_offset = random.randint(0 - radius, radius)
            y_offset = random.randint(0 - radius, radius)
            # 画出一个半径为 radius 的圆
            if int(x_offset) ** 2   int(y_offset) ** 2 <= radius ** 2:
                painter.drawPoint(int(pos.x()   x_offset), int(pos.y()   y_offset))
        """
        如果不调用这个方法,那么这些资源可能会被锁定,导致无法被其他QPainter对象使用,
        或者在某些情况下可能会导致内存泄漏,特别是当你在一个循环中多次使用QPainter对象时,
        如果不调用end()方法,可能会导致程序崩溃或者运行效率降低
        """
        painter.end()
        # 更新QLabel中显示的内容,使其显示最新的QPixmap
        self.label.setPixmap(self.pixmap)
        # 显式刷新界面
        self.update()

    def clear_all(self) -> None:
        self.pixmap = QPixmap(800, 600)
        self.pixmap.fill(Qt.GlobalColor.white)
        self.label.setPixmap(self.pixmap)

    def color_changed(self) -> None:
        self.current_color = self.colors_map[self.sender().text()]


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MyMainWindow()
    window.show()
    app.exec()

运行效果

喷雾效果喷雾效果

0 人点赞