Search code examples
pythonpython-3.xqtpyqt5qpainter

Can I make a PyQt5 rect fully transparent?


I am making an imitation of the built-in Win+Shift+S screenshot function on windows. I am not very familiar with QPainter. Just like the windows function, I want to darken the background, but highlight the actual selected rect the user does. Everything works, but since the background is dark the actual image is darkened. Is there a workaround for this?


import sys
from PyQt5.QtWidgets import QApplication, QWidget
from PyQt5.QtCore import Qt, QPoint, QRect, Qt 
from PyQt5.QtGui import QPixmap, QPen, QPainter, QColor, QBrush
from win32api import GetSystemMetrics, GetKeyState, GetCursorPos
import pyautogui
import PIL

class MyApp(QWidget):

    def __init__(self):
        super().__init__()
        self.setFixedSize(GetSystemMetrics(0), GetSystemMetrics(1))
        self.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint)
        self.setAttribute(Qt.WA_TranslucentBackground, True)
        self.setWindowOpacity(.9)
        self.setWindowFlag(Qt.Tool)
        self.pix = QPixmap(self.rect().size())
    

        (self.begin, self.destination) = (QPoint(), QPoint())

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setOpacity(0.2)
        painter.setBrush(Qt.black)       #ACTUAL BACKGROUDN
        painter.setPen(QPen(Qt.white))   #BORDER OF THE RECTANGLE
        painter.drawRect(self.rect())
        

        painter.drawPixmap(QPoint(), self.pix)

        if not self.begin.isNull() and not self.destination.isNull():
            rect = QRect(self.begin, self.destination)
            painter.drawRect(rect.normalized())

    def mousePressEvent(self, event):
        global initial_x, initial_y
        initial_x, initial_y = GetCursorPos()
        print('down')
        if event.buttons() & Qt.LeftButton:
           
            self.begin = event.pos()
            self.destination = self.begin
            self.update()
            

    def mouseMoveEvent(self, event):
        if event.buttons() & Qt.LeftButton:
            
            self.destination = event.pos()
            
            self.update()

    def mouseReleaseEvent(self, event):
        final_x, final_y = GetCursorPos()
        print('up')
        a = pyautogui.screenshot(region=(initial_x,initial_y, (final_x - initial_x), (final_y - initial_y)))
        a.save(r'C:\Users\ohtitus\Documents\New folder\main.png')
        if event.button() & Qt.LeftButton:
            rect = QRect(self.begin, self.destination)
            painter = QPainter(self.pix)
            painter.drawRect(rect.normalized())
            painter.fillRect(rect, QColor(0,0,0,0))

            (self.begin, self.destination) = (QPoint(), QPoint())
            self.close()


if __name__ == '__main__':

    app = QApplication(sys.argv)
    app.setOverrideCursor(Qt.CrossCursor)
    app.setStyleSheet("background-color: rgb(0, 0, 0)")
    app.setStyleSheet('''
        QWidget {
            font-size: 30px;
        }
    ''')

    myApp = MyApp()
    myApp.show()

    try:
        sys.exit(app.exec_())
    except SystemExit:
        pass


Solution

  • In your paintEvent(self, event) method, add two lines of code before drawing the transparent rectangle which specifies the region to be captured

    if not self.begin.isNull() and not self.destination.isNull():
        painter.setOpacity(0.0) # Added
        painter.setCompositionMode(QPainter.CompositionMode.CompositionMode_Source) # Added
    
        rect = QRect(self.begin, self.destination)
        painter.drawRect(rect.normalized()) # Origianl code
    

    So paintEvent(self, event) will look like this

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setOpacity(0.2)
        painter.setBrush(Qt.black)  # ACTUAL BACKGROUDN
        painter.setPen(QPen(Qt.white))  # BORDER OF THE RECTANGLE
        painter.drawRect(self.rect())
    
        painter.drawPixmap(QPoint(), self.pix)
    
        if not self.begin.isNull() and not self.destination.isNull():
            painter.setOpacity(0.0)
            painter.setCompositionMode(QPainter.CompositionMode.CompositionMode_Source)
    
            rect = QRect(self.begin, self.destination)
            painter.drawRect(rect.normalized())
    

    Code explanation

    painter.setOpacity(0.0) needs to draw a transparent figure

    painter.setCompositionMode( mode ) changes the way how a new figure (source) will be merged into the the original drawing (destination).

    By default, it is set to CompositionMode.CompositionMode_SourceOver, the mode where a source will overwrite a destination while the destiantion still appear in a transparent region of the source.

    In your case, you want to make some part of the destination transparent . You can achieve this by making transparent source to overwrite the destination. The mode CompositionMode.CompositionMode_Source does it.

    Refer to https://doc.qt.io/archives/qt-4.8/qpainter.html#CompositionMode-enum for more information about the composition mode.