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

2

5

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!

Updraft answered 25/9, 2012 at 20:58 Comment(4)
If you are on X Windows, then GStreamer and its ximagesrc plug-in can be a good solution.Mythology
30 * 0.01 = 0.3 sec. Are you sure you moved windows during this time?Ne
Do you want something cross-platform, or is Windows-specific OK? Can you use a heavy-duty framework (like Qt or wx)?Gidgetgie
What part of pywin32 is "very slow"? In general, it's a much thinner wrapper around the native Windows APIs than wx, Qt, or anything else.Gidgetgie
G
6

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.)

Gidgetgie answered 25/9, 2012 at 21:36 Comment(3)
I ended up doing the last one, except with the WX module.Updraft
The latter method is a bit faster, but it produces much heavier files (~5mb for 1366x786 resolution image) and it's limited to .bmp images, unlike the first method.Lor
@MadeOfAir: If you're already using PIL, you can easily convert the bmp to a png or whatever else you want before saving it. (The reason it's 5MB is that bmp files are uncompressed.) I believe there are also higher-level Win32 API methods for creating PNG files from bitmap objects, but you'll have to scan MSDN or google for them yourself.Gidgetgie
U
5

A modern cross-platform solution is to use Python MSS.

from mss import mss
from PIL import Image

def capture_screenshot():
    # Capture entire screen
    with mss() as sct:
        monitor = sct.monitors[1]
        sct_img = sct.grab(monitor)
        # Convert to PIL/Pillow Image
        return Image.frombytes('RGB', sct_img.size, sct_img.bgra, 'raw', 'BGRX')

img = capture_screenshot()
img.show()

On my slow laptop this function can return screenshots at up to 27 fps.

Ubana answered 2/7, 2018 at 7:4 Comment(4)
this saved me.. should be top voted (especially for mac users who can't use win32)Vallejo
@jchaykow is win32 api faster than mss?Ultima
@Ultima I've read other people have gotten win32 down to 16ms which is like 60fps, so if that's true I think it's slightly faster. But for my use I didn't need anything past 20fps and mss is much faster than thisVallejo
I tested on my i5-4440 @ 3.10GHz and the performance is quite the same as the Pillow oneValentinavalentine

© 2022 - 2024 — McMap. All rights reserved.