Using QPainter to paint into offscreen QPixmap fails
-
First of all I am relatively new to Python and to Qt however I'm not new to coding.
As a means of learning both and to produce something that is of use to me out of the learning process, I am attempting to convert the Qt C++ tablet example to Python. However when I execute my Python version I see the following printed to the console:QPainter::begin: Paint device returned engine == 0, type: 2 QPainter::setRenderHint: Painter must be active to set rendering hints QPainter::setPen: Painter not activeIn the description of the application it states amongst other things that the example consists of the following:
The TabletCanvas class inherits QWidget and receives tablet events. It uses the events to paint onto an offscreen pixmap, and then renders it.
The full C++ code can be found here
In summary, the two event handling methods of interest are
TabletCanvas::tabletEventandTabletCanvas::paintEvent:void TabletCanvas::tabletEvent(QTabletEvent *event) { switch (event->type()) { case QEvent::TabletPress: if (!m_deviceDown) { m_deviceDown = true; lastPoint.pos = event->position(); lastPoint.pressure = event->pressure(); lastPoint.rotation = event->rotation(); } break; case QEvent::TabletMove: #ifndef Q_OS_IOS if (event->pointingDevice() && event->pointingDevice()->capabilities().testFlag(QPointingDevice::Capability::Rotation)) updateCursor(event); #endif if (m_deviceDown) { updateBrush(event); QPainter painter(&m_pixmap); paintPixmap(painter, event); lastPoint.pos = event->position(); lastPoint.pressure = event->pressure(); lastPoint.rotation = event->rotation(); } break; case QEvent::TabletRelease: if (m_deviceDown && event->buttons() == Qt::NoButton) m_deviceDown = false; update(); break; default: break; } event->accept(); } void TabletCanvas::paintEvent(QPaintEvent *event) { if (m_pixmap.isNull()) initPixmap(); QPainter painter(this); QRect pixmapPortion = QRect(event->rect().topLeft() * devicePixelRatio(), event->rect().size() * devicePixelRatio()); painter.drawPixmap(event->rect().topLeft(), m_pixmap, pixmapPortion); }My Python conversion of
tableEventhandler is:def tabletEvent(self, event: QTabletEvent) -> None: if event.type() == QEvent.TabletPress: if not self._device_down: self._device_down = True self._lastPoint["pos"] = event.position() self._lastPoint["pressure"] = event.pressure() self._lastPoint["rotation"] = event.rotation() elif event.type() == QEvent.TabletMove: if event.pointingDevice() and event.pointingDevice().hasCapability(QInputDevice.Capability.Rotation): self._updateCursor(event) if self._device_down: self._updateBrush(event) q_painter: QPainter = QPainter(self._pixmap) self._paintPixMap(q_painter, event) self._lastPoint["pos"] = event.position() self._lastPoint["pressure"] = event.pressure() self._lastPoint["rotation"] = event.rotation() elif event.type() == QEvent.TabletRelease: if self._device_down and event.button() == Qt.NoButton: self._device_down = False self.update() elif event.type() == QEvent.TabletTrackingChange: print("Tablet tracking change") elif event.type() == QEvent.TabletEnterProximity: print("Table Enter proximity") elif event.type() == QEvent.TabletLeaveProximity: print("Tablet leave proximity") else: print("In the else") event.accept()Debugging this the first error to the console (
Paint device returned engine == 0) occurs in thetableEventat the lineq_painter: QPainter = QPainter(self._pixmap)and the second and third errors occur atself._paintPixMap(q_painter, event).I have read numerous similar issues online and the usual response is to state that QPainter should only be created in the
paintEventmethod however I'm not able to find that explicitly stated in QPainter's docs. In fact the second paragraph of the detailed description begins (emphasis by me):The common use of QPainter is inside a widget’s paint event: Construct and customize (e.g. set the pen or the brush) the painter.
I can understand the desire to use QPainter in the
tabletEventmethod to write to the off screen pixmap however in my Python conversion this doesn't seem to be possible. If using a QPainter outside of thepaintEventmethod is forbidden either in Qt or Pyside6 then I am not sure how the original C++ example functions or is this a difference between QT C++ and Pyside? Is it possible to use a QPainter for off-screen buffering in Pyside and how can this be achieved? -
Hi
Using QPainter to paint on a widget outside paintevent is forbidden but
you can bind it to a pixmap at any time and that should also works with python bindings.Are you sure the pixmap is valid/have a size when you assign it ?
I mean at this line ?
q_painter: QPainter = QPainter(self._pixmap)I asking as paint event says
if (m_pixmap.isNull())
initPixmap();So I wonder if you try to bind to an invalid pixmap as its
first created later ? When painEvent fires. -
Hi
Using QPainter to paint on a widget outside paintevent is forbidden but
you can bind it to a pixmap at any time and that should also works with python bindings.Are you sure the pixmap is valid/have a size when you assign it ?
I mean at this line ?
q_painter: QPainter = QPainter(self._pixmap)I asking as paint event says
if (m_pixmap.isNull())
initPixmap();So I wonder if you try to bind to an invalid pixmap as its
first created later ? When painEvent fires.@mrjj Excellent spot! You are indeed correct.
In short in my Python port of the original I had incorrectly initialisedself._pixmapto a pixmap of no dimension. Fixing this resolved the issue. I now understand how to use a QPainter object outside of thepaintEventhandler. Unless I missed it I think this would be useful information to have in the docs. -
@mrjj Excellent spot! You are indeed correct.
In short in my Python port of the original I had incorrectly initialisedself._pixmapto a pixmap of no dimension. Fixing this resolved the issue. I now understand how to use a QPainter object outside of thepaintEventhandler. Unless I missed it I think this would be useful information to have in the docs.