0

I want to show an image in Qt and paint some points and lines over it with QPainter. When the user scrolls or drags I would like only the background image to be zoomed or moved while the painted layer does not change. I am currently using QGraphicsView similarly to the answer here https://stackoverflow.com/a/35514531/9152951. I found out that I can draw my points in the drawForeground() function but then my paintings will be scaled and moved with the image. I tried to paint the points in the paintEvent() function:

class ImageViewer(QtWidgets.QGraphicsView):
image_clicked = QtCore.Signal(QtCore.QPoint)

def __init__(self, parent, corners: List[Point]):
    super(ImageViewer, self).__init__(parent)
    self.corners = corners
    self._zoom = 0
    self._empty = True
    self._scene = QtWidgets.QGraphicsScene(self)
    self._img = QtWidgets.QGraphicsPixmapItem()
    self._scene.addItem(self._img)
    self.setScene(self._scene)
    self.setTransformationAnchor(QtWidgets.QGraphicsView.AnchorUnderMouse)
    self.setResizeAnchor(QtWidgets.QGraphicsView.AnchorUnderMouse)
    self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
    self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
    self.setBackgroundBrush(QtGui.QBrush(QtGui.QColor(30, 30, 30)))
    self.setFrameShape(QtWidgets.QFrame.NoFrame)

def hasPhoto(self):
    return not self._empty

def fitInView(self, scale=True):
    rect = QtCore.QRectF(self._img.pixmap().rect())
    if not rect.isNull():
        self.setSceneRect(rect)
        if self.hasPhoto():
            unity = self.transform().mapRect(QtCore.QRectF(0, 0, 1, 1))
            self.scale(1 / unity.width(), 1 / unity.height())
            viewrect = self.viewport().rect()
            scenerect = self.transform().mapRect(rect)
            factor = min(viewrect.width() / scenerect.width(),
                         viewrect.height() / scenerect.height())
            self.scale(factor, factor)
        self._zoom = 0

def set_image(self, img=None):
    self._zoom = 0
    if img is not None:
        if type(img) == np.ndarray:
            img = np_img_to_q_img(img)
        self._empty = False
        self.setDragMode(QtWidgets.QGraphicsView.ScrollHandDrag)
        self._img.setPixmap(img)
    else:
        self._empty = True
        self.setDragMode(QtWidgets.QGraphicsView.NoDrag)
        self._img.setPixmap(QtGui.QPixmap())
    self.fitInView()

def wheelEvent(self, event):
    if self.hasPhoto():
        if event.angleDelta().y() > 0:
            factor = 1.05
            self._zoom += 1
        else:
            factor = 1 / 1.05
            self._zoom -= 1
        if self._zoom > 0:
            self.scale(factor, factor)
        elif self._zoom == 0:
            self.fitInView()
        else:
            self._zoom = 0

def toggleDragMode(self):
    if self.dragMode() == QtWidgets.QGraphicsView.ScrollHandDrag:
        self.setDragMode(QtWidgets.QGraphicsView.NoDrag)
    elif not self._img.pixmap().isNull():
        self.setDragMode(QtWidgets.QGraphicsView.ScrollHandDrag)

def mousePressEvent(self, event):
    if self._img.isUnderMouse():
        self.image_clicked.emit(self.mapToScene(event.pos()).toPoint())
    super(ImageViewer, self).mousePressEvent(event)

def paintEvent(self, event: PySide2.QtGui.QPaintEvent) -> None:
    qp = QPainter(self)
    qp.begin(self)
    q_color = QColor(10, 205, 255)
    qp.setBrush(q_color)
    for point in self.corners:
        qp.drawEllipse(QPoint(point.x, point.y), 5, 5)
    qp.end()

but it does not seem to be allowed as I get:

QWidget::paintEngine: Should no longer be called
QPainter::begin: Paint device returned engine == 0, type: 1
QPainter::setPen: Painter not active
QPainter::setBrush: Painter not active
QPainter::end: Painter not active, aborted

How can I solve this?

Philip Z.
  • 206
  • 2
  • 6
  • In what class this `paintEvent` is defined? It makes sence if it is subclass of `QWidget`, but you have mentions something about `QGraphicsView`. If this is subclass of `QGraphicsItem` then this is wrong. Please provide more context. – Marek R Mar 09 '22 at 11:05
  • Also quick question, are you invoking this event by yourself or let only Qt library to invoke it? – Marek R Mar 09 '22 at 11:08
  • I added the complete code of my class. The paintEvents are invoked by Qt – Philip Z. Mar 09 '22 at 11:12
  • The error messages you see are at least partly due to using both `qp = QPainter(self)` *and* `qp.begin(self)`: you effectively initialize the `QPainter` twice. Just use `qp = QPainter(self)` and drop the `qp.begin(self)` (and probably `qp.end()`) call. – G.M. Mar 09 '22 at 11:53
  • When using QAbstractScrollArea subclasses (like QGraphicsView), all painting should happen in the viewport and the painter initialized on it, as explained in the [`QAbstractScrollArea.paintEvent()`](https://doc.qt.io/qt-5/qabstractscrollarea.html#paintEvent) docs: change to `qp = QPainter(self.viewport())` and remove both `qp.begin(self)` (the painter is already started on the viewport due to the constructor argument) and `qp.end()` (which is unnecessary as the painter will automatically end when it's destroyed at the function's end). – musicamante Mar 09 '22 at 17:49

0 Answers0