1

Using Python , PYQT5 I want to draw a Polygon on top a Image, which is in a Qlabel widget. I used a simple Qmainwindow with a label widget generated in QT designer (code is below).

I am aware that there are several informations out abaut drawing in a Qmainwindow like here:

Let me know, if you have a solution for this problem.

import sys
from PyQt5.QtCore import Qt
from PyQt5 import QtWidgets, uic, QtCore, QtGui
from PyQt5.QtGui import QPixmap, QPainter, QPolygon, QPen, QBrush
from PyQt5.QtCore import QPoint

from polygon_ui import Ui_MainWindow


class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
    def __init__(self, *args, obj=None, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)
        self.setupUi(self)

        pixmap = QPixmap("img.png")
        self.label.setPixmap(pixmap)

        self.label.mousePressEvent = self.getPixel
        self.pol = []

    def getPixel(self, event):
        x = event.pos().x()
        y = event.pos().y()

        self.pol.append(QPoint(int(x),int(y)))

        print(x,y, self.pol)

    def paintEvent(self, event):
        painter = QPainter(self)
        #painter.drawPixmap(self.rect(), self.image)

        painter.setPen(QPen(Qt.black, 5, Qt.SolidLine))
        painter.setBrush(QBrush(Qt.red, Qt.VerPattern))

        #points = QPolygon([ QPoint(10,10), QPoint(10,100),
        #     QPoint(100,10), QPoint(100,100)])

        points = QPolygon(self.pol)
        painter.drawPolygon(points)

    def mouseMoveEvent(self, event):
        pass

if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())

the code for polygon_ui is here - was simply generated by QT-Designer using Mainwindow + Qlabel:

from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(621, 641)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.label = QtWidgets.QLabel(self.centralwidget)
        self.label.setGeometry(QtCore.QRect(10, 10, 600, 600))
        self.label.setObjectName("label")
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 621, 21))
        self.menubar.setObjectName("menubar")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.label.setText(_translate("MainWindow", "TextLabel"))

Erich
  • 41
  • 7
  • 1
    The links you provided are good enough. What do you mean by "paintevent on Qlabel is working, but not on top of the image"? If you are actually painting on the QLabel (or on its pixmap, *if `scaledContents` is *not* set*) it will work. Also remember that: 1. any painting on a widget **must** happen within the `paintEvent` of *that* widget: you cannot start a QPainter on a widget on your own, and if you want to paint on the label you must override the paintEvent of the label, not that of the main window; 2. you should never directly paint on a QMainWindow; – musicamante Mar 25 '20 at 15:23
  • @musicamante: I changed using paintEvent - How do I override the paintEvent to paint on Qlabel?? – Erich Mar 25 '20 at 16:15
  • You could do it using an eventFilter, but it's usually better to subclass the widget and override it there. – musicamante Mar 25 '20 at 16:27

1 Answers1

3

If you want to add elements such as polygons, lines, circles, etc. on an image then do not complicate yourself with a QLabel since for example with your current code you are painting in the window that is below the QLabel so it will not be seen , a possible solution using with QLabel is to get the QPixmap and paint it on top.

A better alternative is to use the Qt Graphics Framework, where the image is set to a QGraphicsPixmapItem, and a polygon as a child of the QGraphicsPixmapItem as I show below:

from PyQt5 import QtCore, QtGui, QtWidgets


class GraphicsView(QtWidgets.QGraphicsView):
    def __init__(self, parent=None):
        super().__init__(parent)
        scene = QtWidgets.QGraphicsScene(self)
        self.setScene(scene)

        self._pixmap_item = QtWidgets.QGraphicsPixmapItem()
        scene.addItem(self.pixmap_item)

        self._polygon_item = QtWidgets.QGraphicsPolygonItem(self.pixmap_item)
        self.polygon_item.setPen(QtGui.QPen(QtCore.Qt.black, 5, QtCore.Qt.SolidLine))
        self.polygon_item.setBrush(QtGui.QBrush(QtCore.Qt.red, QtCore.Qt.VerPattern))

    @property
    def pixmap_item(self):
        return self._pixmap_item

    @property
    def polygon_item(self):
        return self._polygon_item

    def setPixmap(self, pixmap):
        self.pixmap_item.setPixmap(pixmap)

    def resizeEvent(self, event):
        self.fitInView(self.pixmap_item, QtCore.Qt.KeepAspectRatio)
        super().resizeEvent(event)

    def mousePressEvent(self, event):
        sp = self.mapToScene(event.pos())
        lp = self.pixmap_item.mapFromScene(sp)

        poly = self.polygon_item.polygon()
        poly.append(lp)
        self.polygon_item.setPolygon(poly)


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)

        view = GraphicsView()
        self.setCentralWidget(view)

        view.setPixmap(QtGui.QPixmap("img.png"))

        self.resize(640, 480)


if __name__ == "__main__":

    import sys

    app = QtWidgets.QApplication(sys.argv)
    w = MainWindow()
    w.show()
    sys.exit(app.exec_())

Update:

If you want to use the OP design, the implementation is trivial.

Option1:

  1. Create a file called graphicsview.py where the GraphicsView logic is implemented:

    graphicsview.py

    from PyQt5 import QtCore, QtGui, QtWidgets
    
    
    class GraphicsView(QtWidgets.QGraphicsView):
        def __init__(self, parent=None):
            super().__init__(parent)
            scene = QtWidgets.QGraphicsScene(self)
            self.setScene(scene)
    
            self._pixmap_item = QtWidgets.QGraphicsPixmapItem()
            scene.addItem(self.pixmap_item)
    
            self._polygon_item = QtWidgets.QGraphicsPolygonItem(self.pixmap_item)
            self.polygon_item.setPen(QtGui.QPen(QtCore.Qt.black, 5, QtCore.Qt.SolidLine))
            self.polygon_item.setBrush(QtGui.QBrush(QtCore.Qt.red, QtCore.Qt.VerPattern))
    
        @property
        def pixmap_item(self):
            return self._pixmap_item
    
        @property
        def polygon_item(self):
            return self._polygon_item
    
        def setPixmap(self, pixmap):
            self.pixmap_item.setPixmap(pixmap)
    
        def resizeEvent(self, event):
            self.fitInView(self.pixmap_item, QtCore.Qt.KeepAspectRatio)
            super().resizeEvent(event)
    
        def mousePressEvent(self, event):
            sp = self.mapToScene(event.pos())
            lp = self.pixmap_item.mapFromScene(sp)
    
            poly = self.polygon_item.polygon()
            poly.append(lp)
            self.polygon_item.setPolygon(poly)
    
  2. Replace QLabel with QGraphicsView in polygon_ui:

    polygon_ui.py

    from PyQt5 import QtCore, QtGui, QtWidgets
    
    from graphicsview import GraphicsView
    
    class Ui_MainWindow(object):
        def setupUi(self, MainWindow):
            MainWindow.setObjectName("MainWindow")
            MainWindow.resize(621, 641)
            self.centralwidget = QtWidgets.QWidget(MainWindow)
            self.centralwidget.setObjectName("centralwidget")
            self.label = GraphicsView(self.centralwidget)
            self.label.setGeometry(QtCore.QRect(10, 10, 600, 600))
            self.label.setObjectName("label")
            # ...
  3. Restore the main.py

    main.py

    import sys
    from PyQt5 import QtCore, QtGui, QtWidgets
    
    from polygon_ui import Ui_MainWindow
    
    
    class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
        def __init__(self, *args, obj=None, **kwargs):
            super(MainWindow, self).__init__(*args, **kwargs)
            self.setupUi(self)
    
            pixmap = QtGui.QPixmap("img.png")
            self.label.setPixmap(pixmap)
    
    
    if __name__ == "__main__":
        app = QtWidgets.QApplication(sys.argv)
        window = MainWindow()
        window.show()
        sys.exit(app.exec_())
    

Option2:

Another approach is to promote the widget, and in SO there are many examples of that type so I will obviate showing the procedure:

Option3:

Another simpler alternative is to use QLabel as a container and set the GraphicsView with a layout:

import sys

from PyQt5 import QtCore, QtGui, QtWidgets

from polygon_ui import Ui_MainWindow


class GraphicsView(QtWidgets.QGraphicsView):
    def __init__(self, parent=None):
        super().__init__(parent)
        scene = QtWidgets.QGraphicsScene(self)
        self.setScene(scene)

        self._pixmap_item = QtWidgets.QGraphicsPixmapItem()
        scene.addItem(self.pixmap_item)

        self._polygon_item = QtWidgets.QGraphicsPolygonItem(self.pixmap_item)
        self.polygon_item.setPen(QtGui.QPen(QtCore.Qt.black, 5, QtCore.Qt.SolidLine))
        self.polygon_item.setBrush(QtGui.QBrush(QtCore.Qt.red, QtCore.Qt.VerPattern))

    @property
    def pixmap_item(self):
        return self._pixmap_item

    @property
    def polygon_item(self):
        return self._polygon_item

    def setPixmap(self, pixmap):
        self.pixmap_item.setPixmap(pixmap)

    def resizeEvent(self, event):
        self.fitInView(self.pixmap_item, QtCore.Qt.KeepAspectRatio)
        super().resizeEvent(event)

    def mousePressEvent(self, event):
        sp = self.mapToScene(event.pos())
        lp = self.pixmap_item.mapFromScene(sp)

        poly = self.polygon_item.polygon()
        poly.append(lp)
        self.polygon_item.setPolygon(poly)


class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
    def __init__(self, *args, obj=None, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)
        self.setupUi(self)

        self.graphicsview = GraphicsView()
        lay = QtWidgets.QVBoxLayout(self.label)
        lay.addWidget(self.graphicsview)

        pixmap = QtGui.QPixmap("img.png")
        self.graphicsview.setPixmap(pixmap)


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • how can I use my QT-Designer Mainwindow - including menubar and further controll element widgets - located beside the QGraphicsView? I changed the Qlabet to a QGraphicsView widget within QT-designer. – Erich Mar 25 '20 at 17:08
  • @Erich In my update I have provided 3 alternatives. – eyllanesc Mar 25 '20 at 17:23
  • in Option 3 - Class Mainwindow line: lay.addWiget(self.graphicsview) - I do get: AttributeError: 'QVBoxLayout' object has no attribute 'addWiget' – Erich Mar 25 '20 at 18:06
  • @Erich Oops, I fixed it – eyllanesc Mar 25 '20 at 18:07
  • Option 1 und 3 are working, have to try Option2, many thanks!!! – Erich Mar 25 '20 at 18:16