Fastest way to take a screenshot with python on windows
You could use win32 APIs directly .
First give the focus to the App that you want to take screenshot of.
link textWin32 API can help with the screenshot:
import win32gui
import win32ui
import win32con
w = 1920 # set this
h = 1080 # set this
bmpfilenamename = "out.bmp" #set this
hwnd = win32gui.FindWindow(None, windowname)
wDC = win32gui.GetWindowDC(hwnd)
dcObj=win32ui.CreateDCFromHandle(wDC)
cDC=dcObj.CreateCompatibleDC()
dataBitMap = win32ui.CreateBitmap()
dataBitMap.CreateCompatibleBitmap(dcObj, w, h)
cDC.SelectObject(dataBitMap)
cDC.BitBlt((0,0),(w, h) , dcObj, (0,0), win32con.SRCCOPY)
dataBitMap.SaveBitmapFile(cDC, bmpfilenamename)
# Free Resources
dcObj.DeleteDC()
cDC.DeleteDC()
win32gui.ReleaseDC(hwnd, wDC)
win32gui.DeleteObject(dataBitMap.GetHandle())
Screenshotting the windows desktop when working through WSL
The problem with your attempted solution is that the WSL/Linux Python's mss
, as you've found, isn't able to capture the Windows desktop. Being the Linux version of MSS, it will only be able to communicate with Linux processes and protocols like X. Starting up VcXsrv might get you part of the way there, in that you might be able to capture X output, but you may need to be running the Python app from inside a terminal that is running in a X window.
Regardless, you've said that your goal is to capture the entire Windows desktop, not just the X output in VcXsrv. To do that, you'll need to use a Windows process of some sort.
But don't worry; using WSL's interop, you can still do that from inside WSL/Linux Python. We just need to call out to a Windows .exe
of some sort.
There are literally dozens of third-party apps that you could use in Windows to create a screenshot. But I prefer to use a solution that doesn't require any additional installation.
So I resorted to PowerShell for this, since you can easily call powershell.exe
and pass in a script from WSL. There are a number of examples here on Stack Overflow, but I ended up going slightly "lower tech" to try to simplify a bit. The code here is most similar to this solution, so refer to that if you want to expand on this.
From WSL/Linux Python:
import os
os.system("""
powershell.exe \"
Add-Type -AssemblyName System.Windows.Forms
[Windows.Forms.Sendkeys]::SendWait('+{Prtsc}')
\$img = [Windows.Forms.Clipboard]::GetImage()
\$img.Save(\\\"\$env:USERPROFILE\\Pictures\\Screenshots\\screenshot.jpg\\\", [Drawing.Imaging.ImageFormat]::Jpeg)\"
""")
That essentially sends the ShiftPrintScreen key chord to capture the current desktop to the clipboard, then saves the clipboard. It can get slightly hairy with the quoting, since you are essentially wrapping PowerShell inside a /bin/sh
inside a Python script.
Note that, even though you are in Linux Python, since it's the Windows PowerShell that we are calling, it takes the Windows path format (C:\Users\Username\Pictures...
) rather than the Linux version (/mnt/c/Users/...
).
While I didn't have any timing issues with this, you may need to insert small delays. Again, refer to the existing answer for that. This solution is primarily to explain how to do it through WSL's Python using PowerShell.
Python: Fastest way to take and save screenshots
Your first solution should be giving you more than one picture per second. The problem though is that you will be overwriting any pictures that occur within the same second, i.e. they will all have the same filename. To get around this you could create filenames that include 10ths of a second as follows:
from PIL import ImageGrab
from datetime import datetime
while True:
im = ImageGrab.grab()
dt = datetime.now()
fname = "pic_{}.{}.png".format(dt.strftime("%H%M_%S"), dt.microsecond // 100000)
im.save(fname, 'png')
On my machine, this gave the following output:
pic_1143_24.5.png
pic_1143_24.9.png
pic_1143_25.3.png
pic_1143_25.7.png
pic_1143_26.0.png
pic_1143_26.4.png
pic_1143_26.8.png
pic_1143_27.2.png
Area selection and capture of the screen with Python on Windows
(DISCLAIMER: I cannot test right now the samples. If there are some bugs, let me know to fix them)
What you want to achieve is OS specific.
- To access screen resources, use pywin32 (reference) a Python extension for the Win32 API.
- To handle the mouse, use the PyHook library, a wrapper of hooks in the Windows Hooking API.
To get a screen shot area (source):
import win32gui
import win32ui
import win32con
import win32api
def saveScreenShot(x,y,width,height,path):
# grab a handle to the main desktop window
hdesktop = win32gui.GetDesktopWindow()
# create a device context
desktop_dc = win32gui.GetWindowDC(hdesktop)
img_dc = win32ui.CreateDCFromHandle(desktop_dc)
# create a memory based device context
mem_dc = img_dc.CreateCompatibleDC()
# create a bitmap object
screenshot = win32ui.CreateBitmap()
screenshot.CreateCompatibleBitmap(img_dc, width, height)
mem_dc.SelectObject(screenshot)
# copy the screen into our memory device context
mem_dc.BitBlt((0, 0), (width, height), img_dc, (x, y),win32con.SRCCOPY)
# save the bitmap to a file
screenshot.SaveBitmapFile(mem_dc, path)
# free our objects
mem_dc.DeleteDC()
win32gui.DeleteObject(screenshot.GetHandle())
To handle mouse events:
# Callback function when the event is fired
def onMouseDown(event):
# Here, the beginning of your rectangle drawing
# [...]
# Subscribe the event to the callback:
hm = pyHook.HookManager()
hm.SubscribeMouseAllButtonsDown(onMouseDown)
Finally to draw a rectangle selection, you may have to process this way:
- On mouse button down, store coords as first edge
- On mouse move, update the rectangle selection with coords as second edge
- On mouse button up, store the second edge
- Process the capture
The tricky part of drawing a rectangle is restoring the pixels where the previous rectangle was drawn. I see two ways:
- Before you draw your rectangle, you store the pixels to be overwritten in memory; before drawing the next rectangle, you restore the previous pixels and so on.
- You draw your rectangle performing a XOR operation between its pixels and the pixels to be overwritten; before drawing the next rectangle, you redraw the previous rectangle again with the XOR operation. XOR is a logical operation which restores a value if applied twice with another value.
The easiest way to draw a rectangle with a XOR operation is DrawFocusRect().
To solve further problems, remember that pywin32 wraps the Win32 API. You can search in this scope of to perform something.
Related Topics
Best Way to Set Entry Background Color in Python Gtk3 and Set Back to Default
I Can't Import Python Modules in Xcode 11 Using Pythonkit
Generate Correlated Data in Python (3.3)
R Markdown: How to Make Rstudio Display Python Plots Inline Instead of in New Window
How to Add Sum to Zero Constraint to Glm in Python
Running Ruby, Node, Python and Docker on the New Apple Silicon Architecture
List Comprehension in Haskell, Python and Ruby
Combine a Folder of Text Files into a CSV with Each Content in a Cell
Install Rpy2 on Windows7 64Bit for Python 2.7
How to Implement R's P.Adjust in Python
Dealing with the Class Imbalance in Binary Classification
Numpy/Scipy Equivalent of R Ecdf(X)(X) Function
How to Get Access of Individual Trees of a Xgboost Model in Python /R
Using Headers with the Python Requests Library's Get Method
MAC Osx Python Ssl.Sslerror: [Ssl: Certificate_Verify_Failed] Certificate Verify Failed (_Ssl.C:749)