Search code examples
pythonpyqtpyqt5qlabelqpolygon

How draw Polygon from MouseEvents on top of Image in Qlabel


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"))


Solution

  • 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_())