PySide6 app crashes when using QPainter.drawLine()

Question:

On Windows 10, python3.10, PySide6 (or PyQt6) QApplication crashes when calling QPainter.drawLine() .

The terminal just displays :

Process finished with exit code -1073741819 (0xC0000005)

Please find below the code:

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


# from PyQt6.QtCore import QPoint, Qt
# from PyQt6.QtGui import QColor, QPainter, QPen, QPixmap
# from PyQt6.QtWidgets import QApplication, QLabel, QMainWindow

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.label = QLabel()
        canvas = QPixmap(400, 300)
        canvas.fill(Qt.GlobalColor.white)

        self.label.setPixmap(canvas)
        self.setCentralWidget(self.label)
        self.draw_something()

    def draw_something(self):
        painter = QPainter(self.label.pixmap())
        painter.drawLine(10, 10, 300, 200)  # >=========== Crash Here
        painter.end()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    app.exec()
Asked By: u2gilles

||

Answers:

This is caused by a slight (and not well documented) change in the API that happened starting with Qt5.15.

Until Qt5, pixmap() returned a direct pointer to the current pixmap of the label, while in Qt6 it returns an implicit copy of the pixmap. The difference is highlighted only for the latest Qt5 documentation of the pixmap() property:

Previously, Qt provided a version of pixmap() which returned the pixmap by-pointer. That version is now deprecated. To maintain compatibility with old code, you can explicitly differentiate between the by-pointer function and the by-value function:

To Python developers that’s not obvious, but to C++ it’s clear by the change of the const QPixmap * (note the asterisk, meaning that it’s a pointer) to a pure QPixmap type, meaning that the returned object is a new QPixmap object based on the current pixmap, and not a reference to the pixmap object currently set for the label.

Now, the fact is that, conceptually speaking, we should not be able to directly "live draw" on the current pixmap of the label, because:

  • setPixmap() always creates a copy of the pixmap for the label;
  • due to the point above, there is no point in sharing the same pixmap object among different labels, so, as a consequence, pixmap() should always return a copy of that object;

Previously, it was possible to directly paint on a QLabel’s pixmap (while ensuring that update() was immediately called on the label). The current API instead requests an explicit call to setPixmap() after drawing.

So, the solution is to create a reference to the pixmap as long as it’s needed:

    def draw_something(self):
        pm = self.label.pixmap()
        painter = QPainter(pm)
        painter.drawLine(10, 10, 300, 200)
        painter.end()
        self.label.setPixmap(pm)
Answered By: musicamante
Categories: questions Tags: , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.