Search code examples
pythonqtpyqtqpixmap

How to interactively draw points onto a Pixmap that is on a QLabel in PyQT


I am building a small GUI interface and I have a QLabel that draws images from a directory onto itself by setting them as the pixmap. I want the user to be able to interactively click points on this pixmap and have a small green 'x' appear at the clicked locations. I have tried re-implementing the paintEvent method, but this is not the functionality I want at all. paintEvent is what gets called every time there is a reason to paint on the QLabel... whereas I want to leave the base drawing (the image) alone and simply place points on top of the already-drawn image. Eventually, I am going to want the user to be able to interactively track these points through a sequence of images, using third-party tracking algorithms. Thus, I need the capability of having the user move points around, remove points, add points, and modify them without needing to re-draw the image behind them. There are several tutorials online about simply drawing points, but they all involve re-implementing the paintEvent method and then connecting a mouseEvent to the paintEvent. I am trying to specifically avoid this. Any suggestions?


Solution

  • You actually do want to use a paintEvent() override. Even if you do want to only draw things over an image, the widget still needs to be able to redraw portions of the image if you remove or move things that are drawn over it and this happens in the paintEvent(). If you want to add things to the rendering, that's where you have to do it too.

    Think of the paintEvent() as draw-the-entire-part-of-the-widget-that's-visible-Event(). If you are using a subclass of QLabel, then your override should call QLabel::paintEvent(e) and then your stuff.

    It sounds like you will be maintaining a list of points so your part of the paintEvent() should draw all of your points in the manner you require.

    Your mouse events should do something to your list of points (like add a point or move a point) and then call update() which will trigger a call to paintEvent() which will redraw the entire widget with the background image and your updated points. There should be no connecting of mouse events to paintEvent() or messing around with trying to track or handle mouse events inside paintEvent().

    The main thing to remember is that mouse events will modify your "model" and the paintEvent() will draw the entire model whenever it changes. Even if there was an abstraction/function that only required you to draw new things or things that changed, under the covers, lots of stuff still needs to be erased and redrawn.

    This can be optimized by making use of the region information in the event object passed into paintEvent() but this usually isn't necessary.

    You can look at a somewhat similar example in an answer to another question that I provided although it may not be that different from other examples you've already seen. It isn't in Python unfortunately.

    You could do all of your rendering to an in-memory image and then just update the image that the label is displaying but the paintEvent() override is a better option.