Search code examples
pythonpyqtpyqt5qpainter

Overlapping semitransparent QRectangles in QPainter


When two rectangles overlap, I noticed that the intersection between the two become more opaque, which, as I understand, is due to the addition of the QBrush alphas.

Visual example of the overlapping intersection becoming more opaque:

Visual example of the overlapping intersection becoming more opaque

I would like to prevent the overlapping intersection from becoming more opaque, the area should have the same color as the rest of the rectangles. (Which is what happens when using fully opaque QColors, but I want to use semitransparent ones).

I have read several topics on the subject (including this very similar question: Qt: Overlapping semitransparent QgraphicsItem), every answer is stating that the problem can be solved by changing the Composition Mode of the painter, however I just can't find a composition mode that provide the expected result, I have tried all of the 34 Composition Modes listed in the Qpainter documentation (https://doc.qt.io/qt-5/qpainter.html#setCompositionMode). Some of the modes just turn the QColor fully opaque, and most are irrelevant to this problem, none of them allow me to have the intersection of the two rectangles keep the same semitransparent color as the rest.

I would be thankful if anyone has an idea.

You can find below a short code reproducing the problem:

from PyQt5.QtWidgets import QWidget, QApplication
from PyQt5.QtGui import QPainter, QColor
from PyQt5.QtCore import QRect
import sys


class Drawing(QWidget):
    def __init__(self):
        super().__init__()
        self.setGeometry(300, 300, 350, 300)
        self.show()

    def paintEvent(self, event):
            painter = QPainter(self)
            painter.setCompositionMode(QPainter.CompositionMode_SourceOver)
            painter.setPen(QColor(128, 128, 255, 128))
            painter.setBrush(QColor(128, 128, 255, 128))
            rect1 = QRect(10,10,100,100)
            rect2 = QRect(50,50,100,100)
            painter.drawRect(rect1)
            painter.drawRect(rect2)
                 
def main():
    app = QApplication(sys.argv)
    ex = Drawing()
    sys.exit(app.exec_())


if __name__ == '__main__':
    main() 

Solution

  • The simple solution is to use a QPainterPath, ensuring that it both uses setFillRule(Qt.WindingFill) and use the simplified() version of the path, that merges all subpaths:

    path = QPainterPath()
    path.setFillRule(Qt.WindingFill)
    path.addRect(10, 10, 100, 100)
    path.addRect(50, 50, 100, 100)
    painter.drawPath(path.simplified())