NiceNAS
NiceNAS

Reputation: 105

Display and create video from QPainter/QImage

My objective is to create a gif or video from paintEvents and also show on QWidget (window).

I tried to paint the frame onto a QImage and then render it onto the QWidget, but I encounter some errors. Im not sure why I get such errors, the painter is only painting on the image, and from the docs it says I can render paint device after an end() (when I do put it after an end it complains for different reasons).

https://doc.qt.io/qtforpython-6/PySide6/QtWidgets/QWidget.html#PySide6.QtWidgets.PySide6.QtWidgets.QWidget.render

Before end()

QPainter::begin: A paint device can only be painted by one painter at a time.
QWidget::render: Cannot render with an inactive painter

After end(), should I not be inheriting QWidget?

QWidget::repaint: Recursive repaint detected
QPainter::begin: A paint device can only be painted by one painter at a time.
QPainter::fillRect: Painter not active
QPainter::scale: Painter not active
QPainter::drawPoints: Painter not active
QPainter::end: Painter not active, aborted
QPainter::begin: A paint device can only be painted by one painter at a time.
QWidget::render: Cannot render with an inactive painter
class PlotWidget(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self._timer = QTimer(self)
        self._timer.setInterval(100)
        self._timer.timeout.connect(self.frame)

        self._points = QPointList()
        
        self._qimg = QImage(WIDTH, HEIGHT, QImage.Format_RGB32)

    # I simplified the frame def
    def frame(self):
        self._points.clear()
        self._points.append(QPoint(0,0))
        self.update()

    def paintEvent(self, event):
        with QPainter(self._qimg) as painter:
            rect = QRect(QPoint(0, 0), self.size())
            painter.fillRect(rect, Qt.white)
            painter.drawPoints(self._points)
            self.render(self._qimg)
        self._qimg.save('test.png', 'PNG') # Id like to later make it into a video. Preferably hold img in memory and append frame to video.

Upvotes: 1

Views: 203

Answers (1)

NiceNAS
NiceNAS

Reputation: 105

I simplified my version of a solution of what I envisioned. I used imageio to be able to save as a video by append a frame on every iteration.

Youll require to pip install imageio and imageio[ffmpeg]

import imageio, numpy as np
from PySide6.QtWidgets import QWidget
from PySide6.QtCore import QPoint, QRect, QTimer, Qt
from PySide6.QtGui import QPainter, QPointList

WIDTH = 720
HEIGHT = 720

class PlotWidget(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self._timer = QTimer(self)
        self._timer.setInterval(100)
        self._timer.timeout.connect(self.frame)

        self._points = QPointList()

        self.setFixedSize(WIDTH, HEIGHT)

        self._totalFrames = 10
        self._vid_writer = imageio.get_writer('video.avi', fps=10)

    def closeEvent(self, event):
        self._vid_writer.close()
        self._timer.stop()
        event.accept()

    def frame(self):
        self._points.clear()
        self._points.append(QPoint(0,0))
        
        if self._totalFrames > 0:
            self.update()
            pixmap = self.grab()
            qimg = pixmap.toImage().convertToFormat(QImage.Format_RGB888)
            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)
        else:
            self._timer.stop()
            self._vid_writer.close()

        self._totalFrames -= 1

    def paintEvent(self, event):
        with QPainter(self) as painter:
            rect = QRect(QPoint(0, 0), self.size())
            painter.fillRect(rect, Qt.white)
            painter.drawPoints(self._points)

Upvotes: 0

Related Questions