Add option to capture all monitors on Windows (#3950)

Add option to capture all monitors on Windows
This commit is contained in:
Hugo van Kemenade 2019-09-27 08:47:55 +03:00 committed by GitHub
commit 310d2c9005
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 34 additions and 13 deletions

View File

@ -8,7 +8,11 @@ try:
class TestImageGrab(PillowTestCase): class TestImageGrab(PillowTestCase):
def test_grab(self): def test_grab(self):
for im in [ImageGrab.grab(), ImageGrab.grab(include_layered_windows=True)]: for im in [
ImageGrab.grab(),
ImageGrab.grab(include_layered_windows=True),
ImageGrab.grab(all_screens=True),
]:
self.assert_image(im, im.mode, im.size) self.assert_image(im, im.mode, im.size)
def test_grabclipboard(self): def test_grabclipboard(self):

View File

@ -11,7 +11,7 @@ or the clipboard to a PIL image memory.
.. versionadded:: 1.1.3 .. versionadded:: 1.1.3
.. py:function:: PIL.ImageGrab.grab(bbox=None, include_layered_windows=False) .. py:function:: PIL.ImageGrab.grab(bbox=None, include_layered_windows=False, all_screens=False)
Take a snapshot of the screen. The pixels inside the bounding box are Take a snapshot of the screen. The pixels inside the bounding box are
returned as an "RGB" image on Windows or "RGBA" on macOS. returned as an "RGB" image on Windows or "RGBA" on macOS.
@ -20,7 +20,13 @@ or the clipboard to a PIL image memory.
.. versionadded:: 1.1.3 (Windows), 3.0.0 (macOS) .. versionadded:: 1.1.3 (Windows), 3.0.0 (macOS)
:param bbox: What region to copy. Default is the entire screen. :param bbox: What region to copy. Default is the entire screen.
Note that on Windows OS, the top-left point may be negative if ``all_screens=True`` is used.
:param include_layered_windows: Includes layered windows. Windows OS only. :param include_layered_windows: Includes layered windows. Windows OS only.
.. versionadded:: 6.1.0
:param all_screens: Capture all monitors. Windows OS only.
.. versionadded:: 6.2.0
:return: An image :return: An image
.. py:function:: PIL.ImageGrab.grabclipboard() .. py:function:: PIL.ImageGrab.grabclipboard()

View File

@ -29,7 +29,7 @@ else:
raise ImportError("ImageGrab is macOS and Windows only") raise ImportError("ImageGrab is macOS and Windows only")
def grab(bbox=None, include_layered_windows=False): def grab(bbox=None, include_layered_windows=False, all_screens=False):
if sys.platform == "darwin": if sys.platform == "darwin":
fh, filepath = tempfile.mkstemp(".png") fh, filepath = tempfile.mkstemp(".png")
os.close(fh) os.close(fh)
@ -37,8 +37,10 @@ def grab(bbox=None, include_layered_windows=False):
im = Image.open(filepath) im = Image.open(filepath)
im.load() im.load()
os.unlink(filepath) os.unlink(filepath)
if bbox:
im = im.crop(bbox)
else: else:
size, data = grabber(include_layered_windows) offset, size, data = grabber(include_layered_windows, all_screens)
im = Image.frombytes( im = Image.frombytes(
"RGB", "RGB",
size, size,
@ -49,8 +51,10 @@ def grab(bbox=None, include_layered_windows=False):
(size[0] * 3 + 3) & -4, (size[0] * 3 + 3) & -4,
-1, -1,
) )
if bbox: if bbox:
im = im.crop(bbox) x0, y0 = offset
left, top, right, bottom = bbox
im = im.crop((left - x0, top - y0, right - x0, bottom - y0))
return im return im

View File

@ -322,15 +322,15 @@ PyImaging_DisplayModeWin32(PyObject* self, PyObject* args)
PyObject* PyObject*
PyImaging_GrabScreenWin32(PyObject* self, PyObject* args) PyImaging_GrabScreenWin32(PyObject* self, PyObject* args)
{ {
int width, height; int x = 0, y = 0, width, height;
int includeLayeredWindows = 0; int includeLayeredWindows = 0, all_screens = 0;
HBITMAP bitmap; HBITMAP bitmap;
BITMAPCOREHEADER core; BITMAPCOREHEADER core;
HDC screen, screen_copy; HDC screen, screen_copy;
DWORD rop; DWORD rop;
PyObject* buffer; PyObject* buffer;
if (!PyArg_ParseTuple(args, "|i", &includeLayeredWindows)) if (!PyArg_ParseTuple(args, "|ii", &includeLayeredWindows, &all_screens))
return NULL; return NULL;
/* step 1: create a memory DC large enough to hold the /* step 1: create a memory DC large enough to hold the
@ -339,8 +339,15 @@ PyImaging_GrabScreenWin32(PyObject* self, PyObject* args)
screen = CreateDC("DISPLAY", NULL, NULL, NULL); screen = CreateDC("DISPLAY", NULL, NULL, NULL);
screen_copy = CreateCompatibleDC(screen); screen_copy = CreateCompatibleDC(screen);
width = GetDeviceCaps(screen, HORZRES); if (all_screens) {
height = GetDeviceCaps(screen, VERTRES); x = GetSystemMetrics(SM_XVIRTUALSCREEN);
y = GetSystemMetrics(SM_YVIRTUALSCREEN);
width = GetSystemMetrics(SM_CXVIRTUALSCREEN);
height = GetSystemMetrics(SM_CYVIRTUALSCREEN);
} else {
width = GetDeviceCaps(screen, HORZRES);
height = GetDeviceCaps(screen, VERTRES);
}
bitmap = CreateCompatibleBitmap(screen, width, height); bitmap = CreateCompatibleBitmap(screen, width, height);
if (!bitmap) if (!bitmap)
@ -354,7 +361,7 @@ PyImaging_GrabScreenWin32(PyObject* self, PyObject* args)
rop = SRCCOPY; rop = SRCCOPY;
if (includeLayeredWindows) if (includeLayeredWindows)
rop |= CAPTUREBLT; rop |= CAPTUREBLT;
if (!BitBlt(screen_copy, 0, 0, width, height, screen, 0, 0, rop)) if (!BitBlt(screen_copy, 0, 0, width, height, screen, x, y, rop))
goto error; goto error;
/* step 3: extract bits from bitmap */ /* step 3: extract bits from bitmap */
@ -376,7 +383,7 @@ PyImaging_GrabScreenWin32(PyObject* self, PyObject* args)
DeleteDC(screen_copy); DeleteDC(screen_copy);
DeleteDC(screen); DeleteDC(screen);
return Py_BuildValue("(ii)N", width, height, buffer); return Py_BuildValue("(ii)(ii)N", x, y, width, height, buffer);
error: error:
PyErr_SetString(PyExc_IOError, "screen grab failed"); PyErr_SetString(PyExc_IOError, "screen grab failed");