
本教程详细介绍了如何在pyside6/qt应用中,利用qpainter在qwidget上绘制图形,并同时将这些绘制内容实时显示在窗口上,以及高效地捕获窗口帧并将其保存为视频文件。文章将涵盖从qpainter的正确使用、qwidget内容抓取到使用imageio库进行视频编码的关键步骤和最佳实践,旨在提供一个结构清晰、易于理解的专业教程。
在PySide6/Qt应用程序开发中,我们经常需要在自定义的QWidget上进行图形绘制,例如绘制实时数据图表、动画或游戏场景。同时,为了记录或分享这些动态内容,将绘制过程导出为视频文件也是一个常见的需求。然而,在实现这一目标时,开发者可能会遇到一些挑战,例如QPainter的正确使用方式、如何高效地捕获QWidget的显示内容以及如何将捕获的帧序列编码为视频。
本教程将提供一个全面的解决方案,演示如何利用QPainter在QWidget上进行绘制,并通过QTimer驱动动画,最终结合QWidget::grab()方法捕获屏幕内容,并使用第三方库imageio将其保存为视频文件。
实现QWidget内容显示与视频生成的核心策略可以概括为以下几点:
首先,确保你已经安装了PySide6和imageio库。imageio还需要ffmpeg后端来处理大多数视频格式。
pip install PySide6 imageio imageio[ffmpeg] numpy
创建一个继承自QWidget的自定义类,用于处理绘制逻辑、动画定时器和视频帧捕获。
import imageio
import numpy as np
from PySide6.QtWidgets import QWidget, QApplication
from PySide6.QtCore import QPoint, QRect, QTimer, Qt
from PySide6.QtGui import QPainter, QPointList, QImage, QPixmap
# 定义窗口尺寸
WIDTH = 720
HEIGHT = 720
class PlotWidget(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setFixedSize(WIDTH, HEIGHT) # 设置固定窗口大小
        # 初始化动画定时器
        self._timer = QTimer(self)
        self._timer.setInterval(100) # 100毫秒刷新一次,即10帧/秒
        self._timer.timeout.connect(self.frame)
        # 存储绘制点列表
        self._points = QPointList()
        self._current_x = 0 # 用于演示动画效果
        # 视频录制相关
        self._totalFrames = 100 # 计划录制的总帧数
        self._vid_writer = imageio.get_writer('output_video.avi', fps=10) # 初始化视频写入器,指定输出文件名和帧率
        self._timer.start() # 启动定时器
    def closeEvent(self, event):
        """
        窗口关闭事件处理,确保视频写入器被正确关闭,释放资源。
        """
        if not self._vid_writer.closed:
            self._vid_writer.close()
        self._timer.stop()
        super().closeEvent(event) # 调用父类的closeEvent
    def frame(self):
        """
        定时器触发的帧更新函数,负责更新数据、触发重绘和捕获帧。
        """
        # 更新绘制数据 (这里仅作简单示例,实际应用中会更复杂)
        self._points.clear()
        self._points.append(QPoint(self._current_x, HEIGHT // 2))
        self._current_x = (self._current_x + 5) % WIDTH # 模拟点移动
        # 触发QWidget重绘
        self.update()
        # 捕获当前帧并写入视频
        if self._totalFrames > 0:
            pixmap = self.grab() # 捕获整个QWidget的显示内容为QPixmap
            # 将QPixmap转换为QImage,并确保格式为RGB888,以便Numpy和imageio处理
            # QImage.Format_RGB888 对应 24位RGB,每个通道8位
            qimg = pixmap.toImage().convertToFormat(QImage.Format_RGB888)
            # 将QImage的像素数据转换为Numpy数组
            # buffer=qimg.constBits() 获取图像的原始字节数据
            # strides=[qimg.bytesPerLine(), 3, 1] 定义了每个维度(行、像素、通道)的步长
            # dtype=np.uint8 指定数据类型为无符号8位整数
            array = np.ndarray((qimg.height(), qimg.width(), 3),
                               buffer=qimg.constBits(),
                               strides=[qimg.bytesPerLine(), 3, 1],
                               dtype=np.uint8)
            if not self._vid_writer.closed:
                self._vid_writer.append_data(array) # 将Numpy数组帧添加到视频
            self._totalFrames -= 1
        else:
            # 录制帧数达到上限,停止定时器并关闭视频写入器
            self._timer.stop()
            if not self._vid_writer.closed:
                self._vid_writer.close()
            print("视频录制完成!")
    def paintEvent(self, event):
        """
        QWidget的绘制事件,在此方法中使用QPainter进行实际的图形绘制。
        """
        with QPainter(self) as painter: # QPainter直接作用于当前QWidget
            # 填充背景
            rect = QRect(QPoint(0, 0), self.size())
            painter.fillRect(rect, Qt.white)
            # 绘制点
            painter.setPen(Qt.blue)
            painter.setBrush(Qt.blue)
            painter.drawPoints(self._points)
            # 绘制一个简单的文本作为帧计数器
            painter.setPen(Qt.black)
            painter.drawText(10, 20, f"Frame: {100 - self._totalFrames}")
创建一个QApplication实例并显示PlotWidget。
if __name__ == '__main__':
    app = QApplication([])
    widget = PlotWidget()
    widget.show()
    app.exec()在paintEvent中,我们使用with QPainter(self) as painter:来创建一个作用于当前PlotWidget的QPainter。这意味着所有的绘制操作(如fillRect、drawPoints、drawText)都会直接呈现在PlotWidget的屏幕区域上。
错误示例回顾: 原始问题中尝试在paintEvent内部先绘制到QImage (QPainter(self._qimg)),然后又调用self.render(self._qimg)。这种做法是错误的,QWidget::render()用于将一个QWidget的内容渲染到另一个QPaintDevice(例如QImage),而不是将一个QImage渲染到自身。而且,在一个paintEvent中同时激活两个QPainter实例(一个作用于QImage,一个隐含地可能影响QWidget)会导致QPainter::begin: A paint device can only be painted by one painter at a time这类错误。
正确做法: paintEvent应专注于将内容绘制到QWidget自身。如果需要将绘制内容用于其他目的(如保存为图片或视频),应该在paintEvent完成并显示后,通过grab()等方法获取其屏幕快照。
通过本教程,我们学习了如何在PySide6/Qt应用程序中,高效且正确地将QPainter绘制的内容实时显示在QWidget上,并通过QTimer驱动动画。更重要的是,我们掌握了如何利用QWidget::grab()捕获这些动态绘制的帧,并结合imageio库将它们编码保存为视频文件。
关键要点包括:
遵循这些原则,开发者可以轻松地在PySide6/Qt项目中实现复杂的图形动画并将其导出为高质量的视频。
以上就是PySide6/Qt实现QPainter内容显示与视频生成教程的详细内容,更多请关注php中文网其它相关文章!
 
                        
                        每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
 
                Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号