DrawLine within Qlabel in PyQt5

Hi everyone, I’m new here. Like the title says, I have a seemingly simple problem. I have created a Qlabel to insert an image into, and want to draw a red cross on that image.
If I put the init_cross() function in init like the code below, when I run the program, a red cross will be drawn as shown in the picture.

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)
        #!Load the UI Page
        uic.loadUi('myGUI.ui', self)
        # Init cross
        self.init_cross()
    def init_cross(self):
        #! Set the target image
        self.pixmap = self.label.pixmap()
        self.painter = QtGui.QPainter(self.pixmap)
        #! set colour and width of line
        self.painter.setPen(QtGui.QPen(QtGui.QColor('red'), 5))
        #! Draw the cross
        self.painter.drawLine(550, 550, 550 + CROSS_SIZE, 550 + CROSS_SIZE)
        self.painter.drawLine(550, 550 + CROSS_SIZE, 550 + CROSS_SIZE, 550)
        self.label.update()
        #self.label.setPixmap(pixmap)

However, I want to call the draw_cross() function after completing the calculation to draw the red cross at the desired coordinates like the code below. There is absolutely no red cross drawn.

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)
        #!Load the UI Page
        uic.loadUi('myGUI.ui', self)
        # Init cross
        self.init_cross()

    def init_cross(self):
        #! Set the target image
        self.pixmap = self.label.pixmap()
        self.painter = QtGui.QPainter(self.pixmap)
        #! set colour and width of line
        self.painter.setPen(QtGui.QPen(QtGui.QColor('red'), 5))
        
    def draw_cross(self): 
        #! draw cross
        self.painter.drawLine(100, 100, 110 + CROSS_SIZE, 110 + CROSS_SIZE)
        self.painter.drawLine(100, 100 + CROSS_SIZE, 110 + CROSS_SIZE, 110)
        self.label.setPixmap(self.pixmap)
    
    def some_calculation(self): 
        self.draw_cross()

A user on Stackoverflow suggested me with the following code but it also doesn’t work

def init_cross(self):
        #! Set the target image
        self.pixmap = self.label.pixmap()
        
 def draw_cross(self): 
        #! draw cross
        self.painter = QtGui.QPainter(self.pixmap)
        self.painter.setPen(QtGui.QPen(QtGui.QColor('red'), 5))
        self.painter.drawLine(100, 100, 110 + CROSS_SIZE, 110 + CROSS_SIZE)
        self.painter.drawLine(100, 100 + CROSS_SIZE, 110 + CROSS_SIZE, 110)
        self.painter.end()
        self.label.setPixmap(self.pixmap)

Can someone help me please.

Hi @ThanhPC welcome to the forum!

The code should work as far as I can see.

There was a change in PyQt6 which meant that label.pixmap() returned a copy of the pixmap, not a reference & you needed to re-apply the pixmap to the label (as suggested by the user on SO) but that’s not the case in PyQt5. However, you will get the error on exit if you don’t call .end() on the painter:

QPaintDevice: Cannot destroy paint device that is being painted

You can also create the painter as a local variable when you need it and then as it falls out of scope (leaving the method) it will be ended automatically.

Anyway, we can put that to one side as I don’t think it’s the problem here.

The following code works for me. The cross.png file is just the image you posted above downloaded, hence why there are two crosses in the resulting image.

from PyQt5 import QtWidgets, QtGui, QtCore


CROSS_SIZE = 10

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)
        #!Load the UI Page
        self.label = QtWidgets.QLabel()
        self.label.setPixmap(QtGui.QPixmap("cross.png"))
        self.setCentralWidget(self.label)
        # Init cross
        self.init_cross()
        self.draw_cross()

    def init_cross(self):
        #! Set the target image
        self.pixmap = self.label.pixmap()
        self.painter = QtGui.QPainter(self.pixmap)
        #! set colour and width of line
        self.painter.setPen(QtGui.QPen(QtGui.QColor('red'), 5))
        
    def draw_cross(self): 
        #! draw cross
        self.painter.drawLine(100, 100, 110 + CROSS_SIZE, 110 + CROSS_SIZE)
        self.painter.drawLine(100, 100 + CROSS_SIZE, 110 + CROSS_SIZE, 110)
        self.label.setPixmap(self.pixmap)
    
    def some_calculation(self): 
        self.draw_cross()
        
app = QtWidgets.QApplication([])
w = MainWindow()
w.show()
app.exec_()

This gives –

If you could provide the UI file I could better debug what is the issue in your own code.

Some tips –

Generally you don’t want to be creating a painter a long time before using it. Just create it when you need it. Same goes for getting the pixmap of the image, you may as well just do it when you want to draw the image.

If you only want to draw a single cross on the image, create the pixmap separately, copy it, draw on it after the calculation and then apply it to the label. Below is an example using two single-shot timers to re-draw a cross on the image at 5 and 10 seconds.

from PyQt5 import QtWidgets, QtGui, QtCore
from random import randint

CROSS_SIZE = 10

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)
        #!Load the UI Page
        self.label = QtWidgets.QLabel()
        self._blank_target = QtGui.QPixmap("cross.png")
        self.label.setPixmap(self._blank_target)
        self.setCentralWidget(self.label)
        
        QtCore.QTimer.singleShot(5000, self.draw_cross)
        QtCore.QTimer.singleShot(10000, self.draw_cross)
        
    def draw_cross(self): 
        #! Create a new copy of the blank target.
        self.pixmap = QtGui.QPixmap(self._blank_target)
        #! set colour and width of line
        self.painter = QtGui.QPainter(self.pixmap)
        self.painter.setPen(QtGui.QPen(QtGui.QColor('red'), 5))
        #! draw cross
        x, y = randint(50, 150), randint(50, 150)
        self.painter.drawLine(x, y, x + CROSS_SIZE, y + CROSS_SIZE)
        self.painter.drawLine(x, y + CROSS_SIZE, x + CROSS_SIZE, y)
        self.label.setPixmap(self.pixmap)
        self.painter.end()
    
    def some_calculation(self): 
        self.draw_cross()
        
app = QtWidgets.QApplication([])
w = MainWindow()
w.show()
app.exec_()
2 Likes

Thank you for your very detailed reply. It is currently working as I wanted it to.

1 Like