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

Multi tool use
Is there any better way to capture the screen than PIL.ImageGrab.grab()?
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!
30 * 0.01 = 0.3 sec. Are you sure you moved windows during this time?
– swang
Sep 25 '12 at 21:14
Do you want something cross-platform, or is Windows-specific OK? Can you use a heavy-duty framework (like Qt or wx)?
– abarnert
Sep 25 '12 at 21:18
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.– abarnert
Oct 16 '13 at 18:56
pywin32
2 Answers
2
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.)
I ended up doing the last one, except with the WX module.
– Tom
Dec 16 '12 at 23:12
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.– MadeOfAir
Oct 16 '13 at 12:42
.bmp
@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.– abarnert
Oct 16 '13 at 18:59
bmp
png
bmp
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.
By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.
If you are on X Windows, then GStreamer and its ximagesrc plug-in can be a good solution.
– Pedro Romano
Sep 25 '12 at 21:14