pyqt5qlabelqimageqpixmapmousepress

pyqt5 QLabel Image setScaledContents(True) don't allow Qpainter updates


I want to display an image and put a marker at the current mouse position for every left mouse click.

Below code does the job however, it works only if ("self.imglabel.setScaledContents(True)") is commented. Any reason?

I have to do this job on various images of different resolutions, I read to maintain the proper aspect ratio and display the image appropriately we need to use setScaledContents(True). But why enabling this is not allowing update() (PaintEvent)??

import sys
from PyQt5.QtCore import Qt, QPoint
from PyQt5.QtWidgets import QMainWindow, QApplication, QLabel, QSizePolicy, QMessageBox
from PyQt5.QtGui import QPixmap, QPainter, QPen, QColor, QImage, QPalette

class Menu(QMainWindow):
    def __init__(self):
        super().__init__()
        self.central_widget = QWidget()               # define central widget
        self.setCentralWidget(self.central_widget)
        self.vbox = QVBoxLayout(self.central_widget)       
        self.vbox.addWidget(self.imgWidget())
        self.vbox.addWidget(QPushButton("test"))

    def imgWidget(self):
        self.imglabel = QLabel()
        self.imglabel.setScaledContents(True)
        self.image = QImage("calib.jpeg")
        self.imagepix = QPixmap.fromImage(self.image)
        self.imglabel.setPixmap(self.imagepix)
        self.imglabel.mousePressEvent = self.imgMousePress
        return self.imglabel

    def imgMousePress(self, e):
        painter = QPainter(self.imglabel.pixmap())
        pen = QPen()
        pen.setWidth(10)
        pen.setColor(QColor('red'))
        painter.setPen(pen)
        painter.drawPoint(e.x(), e.y())
        painter.end()
        self.imglabel.update()

if __name__ == '__main__':
    app = QApplication(sys.argv)
    mainMenu = Menu()
    mainMenu.show()
    sys.exit(app.exec_())

Solution

  • To avoid unnecessary computation for each paintEvent of the QLabel, whenever the scaledContents property is True the scaled image is cached, and all the painting is automatically discarded.

    To avoid that, you should create a new instance of QPixmap using the existing one, and then set the new painted pixmap again. Note that if the image is scaled, the widget coordinates won't reflect the actual position on the pixmap, so you need to use a transformation to get the actual point to paint at.

        def imgMousePress(self, e):
            pm = QPixmap(self.imglabel.pixmap())
            painter = QPainter(pm)
            pen = QPen()
            pen.setWidth(10)
            pen.setColor(QColor('red'))
            painter.setPen(pen)
            transform = QTransform().scale(
                pm.width() / self.imglabel.width(), 
                pm.height() / self.imglabel.height())
            painter.drawPoint(transform.map(e.pos()))
            painter.end()
            self.imglabel.setPixmap(pm)
    

    Consider that all the "points" will become stretched rectangles if the width/height ratio is not the same of the source image, but this is only a problem of appearance: if you save the pixmap later, they will be squares again, since saving is based on the source pixmap.
    If you want to keep their squared shape while displaying instead, you'll need to keep track of the points and overwrite paintEvent to paint them manually on the label.