Search code examples
pythonpyqtpyqt5qpainterqpixmap

Overlay two pixmaps with alpha value using QPainter


I'm trying to overlay 2 pixmaps and convert them into a single pixmap in a QGraphics scene. Both pixmaps are transparent at certain locations. I want to combine the maps using the 'SourceOver' blend type listed here: I have a simple toy example below to illustrate my issue where I have created two dummy transparent pixmaps, one green and one blue. In reality, these maps are loaded from images and painted over, but this example reproduces the problem. Based on this How to add an image on the top of another image?, the approach I tried (4 lines commented out) was to create a QPainter with one of the pixmaps and then draw the other pixmap on top of it, however that crashes the program. Any ideas on how to fix this? I eventually want to be able to save the combined pixmap.

import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtGui import QPixmap, QPainter, QPen, QBrush, QPainterPath
from PyQt5.QtCore import (QLineF, QPointF, QRectF, Qt)

class Viewer(QtWidgets.QGraphicsView):
    def __init__(self, parent):
        super(Viewer, self).__init__(parent)
        self._scene = QtWidgets.QGraphicsScene(self)
        self.photo = QtWidgets.QGraphicsPixmapItem()
        self.label = QtWidgets.QGraphicsPixmapItem()
        self._scene.addItem(self.photo)
        self._scene.addItem(self.label)
        self.setScene(self._scene)        

    def overlayMaps(self):
        blue = QtGui.QPixmap(600, 600) 
        blue.fill(QtGui.QColor(0,0,255,0))
        p = QPainter(blue)
        self.pen = QPen()
        self.pen.setColor(QtGui.QColor(0,0,255,255))
        self.pen.setWidth(10)
        p.setPen(self.pen)
        p.drawLine(0,0,600,600)


        green = QtGui.QPixmap(600, 600)
        green.fill(QtGui.QColor(0,255,0,0))            
        p = QPainter(green)
        self.pen = QPen()
        self.pen.setColor(QtGui.QColor(0,255,0,255))
        self.pen.setWidth(10)
        p.setPen(self.pen)
        p.drawLine(600,0,0,600)

        self.photo.setPixmap(blue)
        self.label.setPixmap(green) 

        resultPixmap = QtGui.QPixmap(self.photo.pixmap().width(), self.photo.pixmap().height())
#        resultPainter = QtGui.QPainter(resultPixmap)
#        resultPainter.setCompositionMode(QtGui.QPainter.CompositionMode_SourceOver)
#        resultPainter.drawPixmap(300,300, self.photo.pixmap()) 
#        resultPainter.drawPixmap(300,300, self.label.pixmap()) 


    def saveOverlayMap(self):
        pass          

class Window(QtWidgets.QWidget):
    def __init__(self):
        super(Window, self).__init__()
        self.viewer = Viewer(self)
        self.viewer.overlayMaps()

if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    window = Window()
    window.setGeometry(500, 300, 600, 600)
    window.show()
    sys.exit(app.exec_())

Solution

  • I have implemented a function that performs the action of joining depending on the mode, for a better appreciation I have moved the items.

    import sys
    from PyQt5 import QtCore, QtGui, QtWidgets
    
    def join_pixmap(p1, p2, mode=QtGui.QPainter.CompositionMode_SourceOver):
        s = p1.size().expandedTo(p2.size())
        result =  QtGui.QPixmap(s)
        result.fill(QtCore.Qt.transparent)
        painter = QtGui.QPainter(result)
        painter.setRenderHint(QtGui.QPainter.Antialiasing)
        painter.drawPixmap(QtCore.QPoint(), p1)
        painter.setCompositionMode(mode)
        painter.drawPixmap(result.rect(), p2, p2.rect())
        painter.end()
        return result
    
    class Viewer(QtWidgets.QGraphicsView):
        def __init__(self, parent=None):
            super(Viewer, self).__init__(parent)
            self._scene = QtWidgets.QGraphicsScene(self)
            self.setScene(self._scene)  
    
            blue = QtGui.QPixmap(100, 100) 
            blue.fill(QtCore.Qt.transparent)
            p = QtGui.QPainter(blue)
            pen = QtGui.QPen(QtGui.QBrush(QtGui.QColor(0,0,255)), 10)
            p.setPen(pen)
            p.drawLine(0, 0, 100, 100)
            p.end()
            self.photo = self._scene.addPixmap(blue)
    
            green = QtGui.QPixmap(100, 100)
            green.fill(QtCore.Qt.transparent)            
            p = QtGui.QPainter(green)
            pen = QtGui.QPen(QtGui.QBrush(QtGui.QColor(0, 255, 0, 255)), 10)
            p.setPen(pen)
            p.drawLine(100, 0, 0, 100)
            p.end()
            self.label = self._scene.addPixmap(green) 
            self.label.setPos(200, 0)     
    
            self.overlayMaps()
    
        def overlayMaps(self):
            p1 = QtGui.QPixmap(self.photo.pixmap())
            p2 = QtGui.QPixmap(self.label.pixmap())
    
            result_pixmap = join_pixmap(self.photo.pixmap(), self.label.pixmap())
            self.result_item = self._scene.addPixmap(result_pixmap)
            self.result_item.setPos(100, 200)
    
            result_pixmap.save("result_pixmap.png")
    
    if __name__ == '__main__':
        import sys
        app = QtWidgets.QApplication(sys.argv)
        window = Viewer()
        window.resize(640, 480)
        window.show()
        sys.exit(app.exec_())
    

    enter image description here

    result_pixmap.png

    enter image description here