Is there any better way to capture the screen than PIL.ImageGrab.grab()?

Tom picture Tom · Sep 25, 2012 · Viewed 11.1k times · Source

I am making a screen capture program with python. My current problem is PIL.ImageGrab.grab() gives me the same output as 2 seconds later. For instance, for I think I am not being clear, in the following program, almost all the images are the same, have the same Image.tostring() value, even though I was moving my screen during the time the PIL.ImageGrab.grab loop was executing.

>>> from PIL.ImageGrab import grab
>>> l = []
>>> import time
>>> for a in l:
        l.append(grab())
        time.sleep(0.01)


    >>> for a in range(0, 30):
        l.append(grab())
        time.sleep(0.01)


>>> b = []
>>> for a in l:
        b.append(a.tostring())


>>> len(b)
30
>>> del l
>>> last = []
>>> a = 0
>>> a = -1
>>> last = ""
>>> same = -1
>>> for pic in b:
        if b == last:
            same = same + 1
        last = b


>>> same
28
>>> 

This is a problem, as all the images are the same but 1. 1 out of 30 is different. That would make for a absolutly horrable quality video. Please, tell me if there is any better quality alternative to PIL.ImageGrab.grab(). I need to capture the whole screen. Thanks!

EDIT:

So far, it looks like the best alternative is to use pywin32. I am using that for now, but it is very slow. I don't really care about compatability, as for now this project is personal. Every time I work out the frame rate of the program, pywin32 is so slow, it's in the negatives. Please tell me if there is a faster alternative. Thanks!

Answer

abarnert picture abarnert · Sep 25, 2012

There are many alternatives to PIL.ImageGrab.

If you want cross-platform, nearly every major windowing library has screen grab capabilities. Here's an example using PyQt4:

import sys
from PyQt4.QtGui import QPixmap, QApplication
app = QApplication(sys.argv)
QPixmap.grabWindow(QApplication.desktop().winId()).save('screenshot.jpg', 'jpg')

If you want something different from the default settings for what Qt considers "the desktop", you'll need to dig into the PyQt or Qt documentation.

The downside is that these cross-platform windowing libraries are usually pretty heavy duty in terms of installation, distribution, and even sometimes how you structure your program.

The alternative is to use Windows-specific code. While it's possible to ctypes your way directly to the Windows APIs, a much simpler solution is PyWin32.

import win32gui, win32ui, win32con, win32api
hwin = win32gui.GetDesktopWindow()
width = win32api.GetSystemMetrics(win32con.SM_CXVIRTUALSCREEN)
height = win32api.GetSystemMetrics(win32con.SM_CYVIRTUALSCREEN)
left = win32api.GetSystemMetrics(win32con.SM_XVIRTUALSCREEN)
top = win32api.GetSystemMetrics(win32con.SM_YVIRTUALSCREEN)
hwindc = win32gui.GetWindowDC(hwin)
srcdc = win32ui.CreateDCFromHandle(hwindc)
memdc = srcdc.CreateCompatibleDC()
bmp = win32ui.CreateBitmap()
bmp.CreateCompatibleBitmap(srcdc, width, height)
memdc.SelectObject(bmp)
memdc.BitBlt((0, 0), (width, height), srcdc, (left, top), win32con.SRCCOPY)
bmp.SaveBitmapFile(memdc, 'screenshot.bmp')

As usual with Win32, you have to specify exactly what you mean by "the desktop"; this version specifies the "virtual screen" for the current session, which is the combination of all of the monitors if you're running locally at the console, or the remote view if you're running over Terminal Services. If you want something different, you'll have to read the relevant Win32 documentation at MSDN. But for the most part, translating from that documentation to PyWin32 is trivial.

(By the way, even though I say "Win32", the same all works for Win64.)