Get Screenshot on Windows with Python

Fastest way to take a screenshot with python on windows

You could use win32 APIs directly .

  1. First give the focus to the App that you want to take screenshot of.
    link text

  2. Win32 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:

  1. On mouse button down, store coords as first edge
  2. On mouse move, update the rectangle selection with coords as second edge
  3. On mouse button up, store the second edge
  4. 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



Leave a reply



Submit