18

What's the fastest way to take a screenshot on windows? PIL.ImageGrab is rather slow.. it takes between 4-5 seconds to take 30 screenshots of the same small window. Taking screenshots of the whole desktop is even slower.

kennytm
  • 510,854
  • 105
  • 1,084
  • 1,005
Claudiu
  • 224,032
  • 165
  • 485
  • 680

4 Answers4

38

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())
TankorSmash
  • 12,186
  • 6
  • 68
  • 106
pyfunc
  • 65,343
  • 15
  • 148
  • 136
  • 14
    VERY IMPORTANT, as this recently bit me in the ass: you HAVE TO delete/release all the DCs created by this, or after taking ~90 images, you won't be able to anymore. in this case, `dcObj.DeleteDC(); cDC.DeleteDC(); win32gui.ReleaseDC(hwnd, wDC)` – Claudiu Sep 01 '10 at 15:39
  • See this script for more details: http://bytes.com/topic/python/answers/576924-win32ui-vs-wxpy-screen-capture-multi-monitor – Alex L Jan 06 '13 at 07:33
  • 2
    As a followup from @Claudiu's comment (which is indeed necessary as he notes), I found `win32gui.DeleteObject(dataBitMap.GetHandle())` also necessary. I've edited the answer to include both comments. – jedwards Jul 15 '14 at 05:19
  • This is SO much quicker than pyscreenshot and PIL thanks! – Blake Oct 20 '17 at 03:46
  • 1
    Where is `windowname` defined and/or what is it supposed to be? – Hendy Jun 12 '21 at 17:21
  • For some reason this isn't working for me. If I create a DC object like this `dcObj = win32ui.CreateDCFromHandle(wDC)` I can't access the functions of that object, so `dcObj.CreateCompatibleDC()` isn't working (I use VSCode and it usually gives suggestions when typing a dot after an object, but here it doesn't do that so I'm thinking I need to import something else? I installed pywin32 in my virtual environment and imported `win32gui, win32ui, win32con` in the program. Anyone an idea? – xvk Sep 05 '21 at 16:41
  • To take a screenshot, yes. But in the case what you screenshot is a 3D rendered image, isn't it faster to grab the Framebuffer, instead of waiting that windows gets hold of it back? – Sandburg Jan 14 '22 at 16:10
6

Just found out how to do it with gtk. Seems fastest by far:

def image_grab_gtk(window):
    left, top, right, bot = get_rect(window)
    w = right - left
    h = bot - top

    s = gtk.gdk.Pixbuf(
        gtk.gdk.COLORSPACE_RGB, False, 8, w, h)

    s.get_from_drawable(
        gtk.gdk.get_default_root_window(),
        gtk.gdk.colormap_get_system(),
        left, top, 0, 0, w, h )

    final = Image.frombuffer(
        "RGB",
        (w, h),
        s.get_pixels(),
        "raw",
        "RGB",
        s.get_rowstride(), 1)
    return final

Without converting to a PIL Image, it's 8x faster than PIL on my test case. With converting, it's still ~2.7x faster.

Claudiu
  • 224,032
  • 165
  • 485
  • 680
2

You can try my newly created project DXcam: I think for raw speed it's the fastest out there (in python, and without going too deep into the rabbit hole). It's originally created for a deep learning pipeline for FPS games where the higher FPS you get the better. Plus I (am trying to) design it to be user-friendly: For a screenshot just do

import dxcam
camera = dxcam.create()
frame = camera.grab()  # full screen
frame = camera.grab(region=(left, top, right, bottom))  # region

For screen capturing:

camera.start(target_fps=60)  # threaded
for i in range(1000):
    image = camera.get_latest_frame()  # Will block until new frame available
camera.stop()

I copied the part of the benchmarks section from the readme:

DXcam python-mss D3DShot
Average FPS 238.79 75.87 118.36
Std Dev 1.25 0.5447 0.3224

The benchmarks is conducted through 5 trials on my 240hz monitor with a constant 240hz rendering rate synced w/the monitor (using blurbuster ufo test).

You can read more about the details here: https://github.com/ra1nty/DXcam

ra1n
  • 61
  • 1
1

You can use package mss:

Save screenshot to image file

import mss

with mss.mss() as sct:
    filename = sct.shot(output="output.png")

Get the numpy representation of screenshot

import mss
import numpy as np

with mss.mss() as sct:
    monitor = {"top": 160, "left": 160, "width": 160, "height": 135}
    img_array = np.array(sct.grab(monitor))
    # Do whatever you want...
SeanCheey
  • 143
  • 1
  • 4