Screen Snipping Program - Transparent Snipping Area

Viewed 171

I'm in the process of converting my successful screen snipping program from Tkinter to PYQT5. My question is how to create a fully transparent snipping area (a dynamically updating square region would be nice). The outside of the square will be semi-opaque. I've looked all over stack overflow and the internet and could not find an example of this (others are not a fully transparent drawing window). I've attached my code and a picture example of what I am looking for. The "SnippingWidget" is the class that does the snipping logic.

enter image description here enter image description here

import sys

from PyQt5 import QtCore, QtGui
from PyQt5.QtWidgets import QMainWindow, QApplication, QAction, QWidget
from PyQt5.QtGui import QIcon, QKeySequence
from PIL import ImageGrab

class App(QMainWindow):
    def __init__(self):
        super().__init__()

        self.initUI()
        self.topMenu()

    def initUI(self):

        self.setWindowTitle('Lil Snippy')
        self.setWindowIcon(QIcon('assets/lilSnippyIcon.png'))
        self.setGeometry(400, 300, 400, 300)
        self.show()

    def topMenu(self):
        menubar = self.menuBar()

        fileMenu = menubar.addMenu('File')
        saveAct = QAction(QIcon('assets/saveIcon.png'), 'Save', self)
        saveAsAct = QAction(QIcon('assets/saveAsIcon.png'), 'Save As', self)


        modeMenu = menubar.addMenu('Mode')
        snipAct = QAction(QIcon('assets/cameraIcon.png'), 'Snip', self)
        snipAct.setShortcut(QKeySequence('F1'))
        snipAct.triggered.connect(self.activateSnipping)
        videoAct = QAction(QIcon('assets/videoIcon.png'), 'Video', self)
        videoAct.setShortcut('F2')
        soundAct = QAction(QIcon('assets/audioIcon.png'), 'Sound', self)
        soundAct.setShortcut('F3')
        autoAct = QAction(QIcon('assets/automationIcon.png'), 'Automation', self)
        autoAct.setShortcut('F4')

        helpMenu = menubar.addMenu('Help')
        helpAct = QAction(QIcon('assets/helpIcon.png'), 'Help', self)
        aboutAct = QAction(QIcon('assets/aboutIcon.png'), 'About', self)

        fileMenu.addAction(saveAct)
        fileMenu.addAction(saveAsAct)
        modeMenu.addAction(snipAct)
        modeMenu.addAction(videoAct)
        modeMenu.addAction(soundAct)
        modeMenu.addAction(autoAct)
        helpMenu.addAction(helpAct)
        helpMenu.addAction(aboutAct)

    def activateSnipping(self):
        print("yes")
        self.Snipper = SnippingWidget()
        application.hide()

class SnippingWidget(QMainWindow):
    def __init__(self, parent = None):
        super(SnippingWidget, self).__init__(parent)
        self.setStyleSheet("background-color: transparent;")
        self.setWindowOpacity(.2)
        self.showFullScreen()

        self.outsideSquareColor = 'red'
        self.squareThickness = 4

        self.startX = None
        self.startY = None
        self.endX = None
        self.endY = None
        self.begin = QtCore.QPoint()
        self.end = QtCore.QPoint()

    def mousePressEvent(self, event):
        self.begin = event.pos()
        self.end = self.begin
        self.startX = event.x()
        self.startY = event.y()
        self.update()

    def mouseReleaseEvent(self, QMouseEvent):
        self.destroy()
        x1 = min(self.begin.x(), self.end.x())
        y1 = min(self.begin.y(), self.end.y())
        x2 = max(self.begin.x(), self.end.x())
        y2 = max(self.begin.y(), self.end.y())
        img = ImageGrab.grab(bbox=(x1, y1, x2, y2))

        img.save('snips/testImage.png')
        application.show()

    def mouseMoveEvent(self, event):
        self.end = event.pos()
        self.update()

    def paintEvent(self, event):
        qp = QtGui.QPainter(self)
        qp.setPen(QtGui.QPen(QtGui.QColor('red'), self.squareThickness))
        trans = QtGui.QColor(255,255,255,255)
        qp.setBrush(trans)
        rect = QtCore.QRectF(self.begin, self.end)
        qp.drawRect(rect)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    application = App()
    sys.exit(app.exec_())
1 Answers

You have to use QPainterPath to subtract the rectangle from the window with the selected rectangle:

import sys

from PyQt5 import QtCore, QtGui, QtWidgets

from PIL import ImageGrab


class App(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()

        self.initUI()
        self.topMenu()

    def initUI(self):
        self.setWindowTitle("Lil Snippy")
        self.setWindowIcon(QtGui.QIcon("assets/lilSnippyIcon.png"))
        self.setGeometry(400, 300, 400, 300)

    def topMenu(self):
        menubar = self.menuBar()

        fileMenu = menubar.addMenu("File")
        saveAct = QtWidgets.QAction(QtGui.QIcon("assets/saveIcon.png"), "Save", self)
        saveAsAct = QtWidgets.QAction(
            QtGui.QIcon("assets/saveAsIcon.png"), "Save As", self
        )

        modeMenu = menubar.addMenu("Mode")
        snipAct = QtWidgets.QAction(QtGui.QIcon("assets/cameraIcon.png"), "Snip", self)
        snipAct.setShortcut(QtGui.QKeySequence("F1"))
        snipAct.triggered.connect(self.activateSnipping)
        videoAct = QtWidgets.QAction(QtGui.QIcon("assets/videoIcon.png"), "Video", self)
        videoAct.setShortcut("F2")
        soundAct = QtWidgets.QAction(QtGui.QIcon("assets/audioIcon.png"), "Sound", self)
        soundAct.setShortcut("F3")
        autoAct = QtWidgets.QAction(
            QtGui.QIcon("assets/automationIcon.png"), "Automation", self
        )
        autoAct.setShortcut("F4")

        helpMenu = menubar.addMenu("Help")
        helpAct = QtWidgets.QAction(QtGui.QIcon("assets/helpIcon.png"), "Help", self)
        aboutAct = QtWidgets.QAction(QtGui.QIcon("assets/aboutIcon.png"), "About", self)

        fileMenu.addAction(saveAct)
        fileMenu.addAction(saveAsAct)
        modeMenu.addAction(snipAct)
        modeMenu.addAction(videoAct)
        modeMenu.addAction(soundAct)
        modeMenu.addAction(autoAct)
        helpMenu.addAction(helpAct)
        helpMenu.addAction(aboutAct)

        self.snipper = SnippingWidget()
        self.snipper.closed.connect(self.on_closed)

    def activateSnipping(self):
        self.snipper.showFullScreen()
        self.hide()

    def on_closed(self):
        self.snipper.hide()
        self.show()


class SnippingWidget(QtWidgets.QMainWindow):
    closed = QtCore.pyqtSignal()

    def __init__(self, parent=None):
        super(SnippingWidget, self).__init__(parent)
        self.setStyleSheet("background-color: transparent;")
        self.setWindowOpacity(0.2)

        self.outsideSquareColor = "red"
        self.squareThickness = 4

        self.start_point = QtCore.QPoint()
        self.end_point = QtCore.QPoint()

    def mousePressEvent(self, event):
        self.start_point = event.pos()
        self.end_point = event.pos()
        self.update()

    def mouseMoveEvent(self, event):
        self.end_point = event.pos()
        self.update()

    def mouseReleaseEvent(self, QMouseEvent):
        r = QtCore.QRect(self.start_point, self.end_point).normalized()
        img = ImageGrab.grab(bbox=r.getCoords())
        img.save("snips/testImage.png")
        self.closed.emit()

    def paintEvent(self, event):
        qp = QtGui.QPainter(self)
        qp.setPen(
            QtGui.QPen(QtGui.QColor(self.outsideSquareColor), self.squareThickness)
        )
        trans = QtGui.QColor(255, 255, 255, 255)
        qp.setBrush(trans)

        outer = QtGui.QPainterPath()
        outer.addRect(QtCore.QRectF(self.rect()))

        inner = QtGui.QPainterPath()
        inner.addRect(QtCore.QRectF(self.start_point, self.end_point).normalized())
        r = outer - inner
        qp.drawPath(r)


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    application = App()
    application.show()
    sys.exit(app.exec_())

Update:

class SnippingWidget(QtWidgets.QMainWindow):
    closed = QtCore.pyqtSignal()

    def __init__(self, parent=None):
        super(SnippingWidget, self).__init__(parent)
        self.setAttribute(QtCore.Qt.WA_NoSystemBackground, True)
        self.setAttribute(QtCore.Qt.WA_TranslucentBackground, True)
        self.setStyleSheet("background:transparent;")
        self.setWindowFlags(QtCore.Qt.FramelessWindowHint)

        self.outsideSquareColor = "red"
        self.squareThickness = 4

        self.start_point = QtCore.QPoint()
        self.end_point = QtCore.QPoint()

    def mousePressEvent(self, event):
        self.start_point = event.pos()
        self.end_point = event.pos()
        self.update()

    def mouseMoveEvent(self, event):
        self.end_point = event.pos()
        self.update()

    def mouseReleaseEvent(self, QMouseEvent):
        r = QtCore.QRect(self.start_point, self.end_point).normalized()
        img = ImageGrab.grab(bbox=r.getCoords())
        img.save("snips/testImage.png")
        self.closed.emit()

    def paintEvent(self, event):
        trans = QtGui.QColor(255, 255, 255)
        r = QtCore.QRectF(self.start_point, self.end_point).normalized()

        qp = QtGui.QPainter(self)
        trans.setAlphaF(0.2)
        qp.setBrush(trans)
        outer = QtGui.QPainterPath()
        outer.addRect(QtCore.QRectF(self.rect()))
        inner = QtGui.QPainterPath()
        inner.addRect(r)
        r_path = outer - inner
        qp.drawPath(r_path)

        qp.setPen(
            QtGui.QPen(QtGui.QColor(self.outsideSquareColor), self.squareThickness)
        )
        trans.setAlphaF(0)
        qp.setBrush(trans)
        qp.drawRect(r)
Related