7

My goal is to make a little PC/Windows program which will allow me to literally draw on top of my screen and save the result as png with transparent background. Like softwares like Epic Pen or gInk but my way. All using Python 3.7 and PyQt5.

So far, I managed to get a functional drawing app (basically following this tutorial) because I'm learning PyQt at the same time. I managed to save my drafts as png with transparent background. I can make the drawing board fullscreen and borderless.

Now the issue is, I can't find a way to make the whole background transparent. Though I've found ways to make a window transparent and borderless using these:

Window = Window()
Window.setStyleSheet("background:transparent;")
Window.setAttribute(Qt.WA_TranslucentBackground)
Window.setWindowFlags(Qt.FramelessWindowHint)
Window.show()

And it works... Until I have a drawing area. I can draw on it, it will save with a transparent background, but it displays black.

So I'm searching for that solution. Even without PyQt, I don't really care, as long as I can make my program work.

So here is what I have (I show you windowed with the frame to make it easier to explain): enter image description here

And here is what I want: enter image description here

And here is my code:

import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QMenuBar, QMenu, QAction, QShortcut, QFileDialog
from PyQt5.QtGui import QIcon, QImage, QPainter, QPen
from PyQt5.QtCore import Qt, QPoint


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

        top = 400
        left = 400
        width = 800
        height = 600

        icon = "icons/icon.png"

        self.setWindowTitle("ScreenPen drawing board")
        self.setGeometry(top, left, width, height)
        self.setWindowIcon(QIcon(icon))

# ---------- sets image ----------
        self.image = QImage(self.size(), QImage.Format_RGBA64)
        self.image.fill(Qt.transparent)

# ---------- init drawing state ----------
        self.drawing = False
        self.brushSize = 2
        self.brushColor = Qt.red
        self.lastPoint = QPoint()

# ---------- Define Menus ----------
    # mainmenu
        mainMenu = self.menuBar()
        fileMenu = mainMenu.addMenu("File")
        toolMenu = mainMenu.addMenu("Tool")
        toolColor = mainMenu.addMenu("Color")
    # smenu save
        saveAction = QAction(QIcon("icons/save.png"), "Save", self)
        saveAction.setShortcut("Ctrl+S")
        fileMenu.addAction(saveAction)
        saveAction.triggered.connect(self.saveFrame)
    # smenu clear frame
        clearFrameAction = QAction(QIcon("icons/clear.png"), "Clear Frame", self)
        clearFrameAction.setShortcut("Ctrl+Del")
        fileMenu.addAction(clearFrameAction)
        clearFrameAction.triggered.connect(self.clearFrame)
    # smenu Tool Pen
        toolPenAction = QAction(QIcon("icons/toolPen.png"), "Pen", self)
        # clearAction.setShortcut("Ctrl+Del")
        toolMenu.addAction(toolPenAction)

# ---------- Catch Mouse Down --------

    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            self.drawing = True
            self.lastPoint = event.pos()

# ---------- Catch Mouse Move --------
    def mouseMoveEvent(self, event):
        if (event.buttons() & Qt.LeftButton) & self.drawing:
            painter = QPainter(self.image)
            painter.setPen(QPen(self.brushColor, self.brushSize, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
            painter.drawLine(self.lastPoint, event.pos())
            self.lastPoint = event.pos()
            self.update()

# ---------- Catch Mouse Up --------
    def mouseReleaseEvent(self, event):
        if event.button == Qt.LeftButton:
            self.drawing = False

# ---------- Paint --------
    def paintEvent(self, event):
        canvasPainter = QPainter(self)
        canvasPainter.drawImage(self.rect(), self.image, self.image.rect())

# ---------- Save Action ----------
    def saveFrame(self):
        filePath,  _ = QFileDialog.getSaveFileName(self, "Save Image", "", "PNG(*.png);;JPEG(*.jpg *.jpeg);; ALL Files(*.*)")
        if filePath == "":
            return
        self.image.save(filePath)

# ---------- Clear Frame Action ----------
    def clearFrame(self):
        self.image.fill(Qt.white)
        self.update()

if __name__ == "__main__":
    app = QApplication(sys.argv)
    Window = Window()
    # Window style
    Window.setStyleSheet("background:transparent;")
    Window.setAttribute(Qt.WA_TranslucentBackground)
    # Window.setWindowFlags(Qt.FramelessWindowHint)
    Window.show()
    app.exec()
Hitech Hitesh
  • 1,641
  • 1
  • 9
  • 18
L0Lock
  • 147
  • 13
  • Wild guess: Is transparency preserved upon updating the Window or would you need to add setting the background to transparent to some kind of `redraw` or `update` method of the Window? – jbndlr Oct 06 '19 at 21:50
  • I'm sure this isn't exactly what you want, but is there a way to delete the entire window altogether and just keep the drawing? Start with no window at all, just let the program run on its own. Like when you do the detect mouse down thing, you could maybe only keep that part, like `bring to front` on Powerpoint/Slides – Arnav Poddar Oct 06 '19 at 23:49
  • I honestly don't know, I'm too much of a noob for that. To me it seems that in my current code, the "things" that makes the black background is the draw area itself, because with the exact same code I can have a 100% transparent background if I just remove the drawing area. So I suppose either there's a way to force qT's drawing area to display no background, or I should use something else instead, or... Dunno. – L0Lock Oct 07 '19 at 16:38

2 Answers2

2

One way to do this (which should work on most platforms) is to create an image of the whole desktop that is then cropped to the area covered by your window. This can be done quite easily in Qt using QScreen.grabWindow:

def saveFrame(self):
    filePath,  _ = QFileDialog.getSaveFileName(self, "Save Image", "", "PNG(*.png);;JPEG(*.jpg *.jpeg);; ALL Files(*.*)")
    if filePath == "":
        return

    screen = QApplication.desktop().windowHandle().screen()
    wid = QApplication.desktop().winId()
    pixmap = screen.grabWindow(wid, self.x(), self.y(), self.width(), self.height())
    pixmap.save(filePath)

Or possibly:

    screen = self.windowHandle().screen()
    pixmap = screen.grabWindow(0, self.x(), self.y(), self.width(), self.height())
    pixmap.save(filePath)

These both work for me on Linux, but I have not tested them on other platforms. If you want to get the window frame as well, use self.frameGeometry() to get the required dimensions.

ekhumoro
  • 115,249
  • 20
  • 229
  • 336
0

I'm not sure if that is possible. As a workaround you could take a screenshot of the relavant area with python and use it as background. You would have to update the screenshot if you move the window of course.

Take a screenshot with python on windows: Get screenshot on Windows with Python?

Flostian
  • 93
  • 1
  • 7
  • 1
    No, I purposely need to see whatever happens on my screen, a screenshot won't do it. – L0Lock Oct 04 '19 at 09:39
  • You can prepare another window (assign an area on the screen) for screenshots and drawings can also be done on it. You can still do other operations on the main screen that you can see what happens on it. – Carson Oct 09 '19 at 02:21