Solved 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 active
In 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::tabletEvent
andTabletCanvas::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
tableEvent
handler 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 thetableEvent
at 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
paintEvent
method 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
tabletEvent
method 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 thepaintEvent
method 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. -
@mrjj Excellent spot! You are indeed correct.
In short in my Python port of the original I had incorrectly initialisedself._pixmap
to a pixmap of no dimension. Fixing this resolved the issue. I now understand how to use a QPainter object outside of thepaintEvent
handler. Unless I missed it I think this would be useful information to have in the docs. -
@D-Drum
Hi
super :)
I agree there could be some more info about this in the docs.