From fa6b80fddff43efe9a79b6b24ce6c36c76149360 Mon Sep 17 00:00:00 2001 From: nulano Date: Thu, 4 Jul 2019 23:52:35 +0200 Subject: [PATCH 1/4] add option to capture all monitors on Windows --- Tests/test_imagegrab.py | 6 +++++- docs/reference/ImageGrab.rst | 3 ++- src/PIL/ImageGrab.py | 12 ++++++++---- src/display.c | 21 ++++++++++++++------- 4 files changed, 29 insertions(+), 13 deletions(-) diff --git a/Tests/test_imagegrab.py b/Tests/test_imagegrab.py index a29bc019eea..8207177a6b7 100644 --- a/Tests/test_imagegrab.py +++ b/Tests/test_imagegrab.py @@ -8,7 +8,11 @@ class TestImageGrab(PillowTestCase): 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(multimonitor=True), + ]: self.assert_image(im, im.mode, im.size) def test_grabclipboard(self): diff --git a/docs/reference/ImageGrab.rst b/docs/reference/ImageGrab.rst index ed7482e99f5..6dae63ae4d5 100644 --- a/docs/reference/ImageGrab.rst +++ b/docs/reference/ImageGrab.rst @@ -11,7 +11,7 @@ or the clipboard to a PIL image memory. .. 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, multimonitor=False) Take a snapshot of the screen. The pixels inside the bounding box are returned as an "RGB" image on Windows or "RGBA" on macOS. @@ -21,6 +21,7 @@ or the clipboard to a PIL image memory. :param bbox: What region to copy. Default is the entire screen. :param include_layered_windows: Includes layered windows. Windows OS only. + :param multimonitor: Capture all monitors. Windows OS only. :return: An image .. py:function:: PIL.ImageGrab.grabclipboard() diff --git a/src/PIL/ImageGrab.py b/src/PIL/ImageGrab.py index 8f359e2294f..0fafb3bfe50 100644 --- a/src/PIL/ImageGrab.py +++ b/src/PIL/ImageGrab.py @@ -29,7 +29,7 @@ raise ImportError("ImageGrab is macOS and Windows only") -def grab(bbox=None, include_layered_windows=False): +def grab(bbox=None, include_layered_windows=False, multimonitor=False): if sys.platform == "darwin": fh, filepath = tempfile.mkstemp(".png") os.close(fh) @@ -37,8 +37,10 @@ def grab(bbox=None, include_layered_windows=False): im = Image.open(filepath) im.load() os.unlink(filepath) + if bbox: + im = im.crop(bbox) else: - size, data = grabber(include_layered_windows) + offset, size, data = grabber(include_layered_windows, multimonitor) im = Image.frombytes( "RGB", size, @@ -49,8 +51,10 @@ def grab(bbox=None, include_layered_windows=False): (size[0] * 3 + 3) & -4, -1, ) - if bbox: - im = im.crop(bbox) + if bbox: + x0, y0 = offset + left, top, right, bottom = bbox + im = im.crop((left - x0, top - y0, right - x0, bottom - y0)) return im diff --git a/src/display.c b/src/display.c index ab005d4b468..fa22b736f24 100644 --- a/src/display.c +++ b/src/display.c @@ -322,15 +322,15 @@ PyImaging_DisplayModeWin32(PyObject* self, PyObject* args) PyObject* PyImaging_GrabScreenWin32(PyObject* self, PyObject* args) { - int width, height; - int includeLayeredWindows = 0; + int x = 0, y = 0, width, height; + int includeLayeredWindows = 0, multimonitor = 0; HBITMAP bitmap; BITMAPCOREHEADER core; HDC screen, screen_copy; DWORD rop; PyObject* buffer; - if (!PyArg_ParseTuple(args, "|i", &includeLayeredWindows)) + if (!PyArg_ParseTuple(args, "|ii", &includeLayeredWindows, &multimonitor)) return NULL; /* 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_copy = CreateCompatibleDC(screen); - width = GetDeviceCaps(screen, HORZRES); - height = GetDeviceCaps(screen, VERTRES); + if (multimonitor) { + x = GetSystemMetrics(SM_XVIRTUALSCREEN); + y = GetSystemMetrics(SM_YVIRTUALSCREEN); + width = GetSystemMetrics(SM_CXVIRTUALSCREEN); + height = GetSystemMetrics(SM_CYVIRTUALSCREEN); + } else { + width = GetSystemMetrics(SM_CXSCREEN); + height = GetSystemMetrics(SM_CYSCREEN); + } bitmap = CreateCompatibleBitmap(screen, width, height); if (!bitmap) @@ -354,7 +361,7 @@ PyImaging_GrabScreenWin32(PyObject* self, PyObject* args) rop = SRCCOPY; if (includeLayeredWindows) 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; /* step 3: extract bits from bitmap */ @@ -376,7 +383,7 @@ PyImaging_GrabScreenWin32(PyObject* self, PyObject* args) DeleteDC(screen_copy); DeleteDC(screen); - return Py_BuildValue("(ii)N", width, height, buffer); + return Py_BuildValue("(ii)(ii)N", x, y, width, height, buffer); error: PyErr_SetString(PyExc_IOError, "screen grab failed"); From 19426417ff25397dc3b34a3ea4fb447e85381030 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 17 Aug 2019 20:38:54 +1000 Subject: [PATCH 2/4] Reverted unrelated change --- src/display.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/display.c b/src/display.c index fa22b736f24..8a78b718af7 100644 --- a/src/display.c +++ b/src/display.c @@ -345,8 +345,8 @@ PyImaging_GrabScreenWin32(PyObject* self, PyObject* args) width = GetSystemMetrics(SM_CXVIRTUALSCREEN); height = GetSystemMetrics(SM_CYVIRTUALSCREEN); } else { - width = GetSystemMetrics(SM_CXSCREEN); - height = GetSystemMetrics(SM_CYSCREEN); + width = GetDeviceCaps(screen, HORZRES); + height = GetDeviceCaps(screen, VERTRES); } bitmap = CreateCompatibleBitmap(screen, width, height); From 6a2d8f8da0becafb4b8f9e95a5747801843b58d0 Mon Sep 17 00:00:00 2001 From: nulano Date: Fri, 20 Sep 2019 17:35:08 +0200 Subject: [PATCH 3/4] rename parameter, add note to docs --- Tests/test_imagegrab.py | 2 +- docs/reference/ImageGrab.rst | 5 +++-- src/PIL/ImageGrab.py | 4 ++-- src/display.c | 6 +++--- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/Tests/test_imagegrab.py b/Tests/test_imagegrab.py index 8207177a6b7..bea7f68b31c 100644 --- a/Tests/test_imagegrab.py +++ b/Tests/test_imagegrab.py @@ -11,7 +11,7 @@ def test_grab(self): for im in [ ImageGrab.grab(), ImageGrab.grab(include_layered_windows=True), - ImageGrab.grab(multimonitor=True), + ImageGrab.grab(all_screens=True), ]: self.assert_image(im, im.mode, im.size) diff --git a/docs/reference/ImageGrab.rst b/docs/reference/ImageGrab.rst index 6dae63ae4d5..a7d7be6fef4 100644 --- a/docs/reference/ImageGrab.rst +++ b/docs/reference/ImageGrab.rst @@ -11,7 +11,7 @@ or the clipboard to a PIL image memory. .. versionadded:: 1.1.3 -.. py:function:: PIL.ImageGrab.grab(bbox=None, include_layered_windows=False, multimonitor=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 returned as an "RGB" image on Windows or "RGBA" on macOS. @@ -20,8 +20,9 @@ or the clipboard to a PIL image memory. .. versionadded:: 1.1.3 (Windows), 3.0.0 (macOS) :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 multimonitor: Capture all monitors. Windows OS only. + :param all_screens: Capture all monitors. Windows OS only. :return: An image .. py:function:: PIL.ImageGrab.grabclipboard() diff --git a/src/PIL/ImageGrab.py b/src/PIL/ImageGrab.py index 0fafb3bfe50..9b4413536ea 100644 --- a/src/PIL/ImageGrab.py +++ b/src/PIL/ImageGrab.py @@ -29,7 +29,7 @@ raise ImportError("ImageGrab is macOS and Windows only") -def grab(bbox=None, include_layered_windows=False, multimonitor=False): +def grab(bbox=None, include_layered_windows=False, all_screens=False): if sys.platform == "darwin": fh, filepath = tempfile.mkstemp(".png") os.close(fh) @@ -40,7 +40,7 @@ def grab(bbox=None, include_layered_windows=False, multimonitor=False): if bbox: im = im.crop(bbox) else: - offset, size, data = grabber(include_layered_windows, multimonitor) + offset, size, data = grabber(include_layered_windows, all_screens) im = Image.frombytes( "RGB", size, diff --git a/src/display.c b/src/display.c index 8a78b718af7..67f8e546c11 100644 --- a/src/display.c +++ b/src/display.c @@ -323,14 +323,14 @@ PyObject* PyImaging_GrabScreenWin32(PyObject* self, PyObject* args) { int x = 0, y = 0, width, height; - int includeLayeredWindows = 0, multimonitor = 0; + int includeLayeredWindows = 0, all_screens = 0; HBITMAP bitmap; BITMAPCOREHEADER core; HDC screen, screen_copy; DWORD rop; PyObject* buffer; - if (!PyArg_ParseTuple(args, "|ii", &includeLayeredWindows, &multimonitor)) + if (!PyArg_ParseTuple(args, "|ii", &includeLayeredWindows, &all_screens)) return NULL; /* step 1: create a memory DC large enough to hold the @@ -339,7 +339,7 @@ PyImaging_GrabScreenWin32(PyObject* self, PyObject* args) screen = CreateDC("DISPLAY", NULL, NULL, NULL); screen_copy = CreateCompatibleDC(screen); - if (multimonitor) { + if (all_screens) { x = GetSystemMetrics(SM_XVIRTUALSCREEN); y = GetSystemMetrics(SM_YVIRTUALSCREEN); width = GetSystemMetrics(SM_CXVIRTUALSCREEN); From 3c311f5619b1b82e2b91cd2a42e3a44b9526755c Mon Sep 17 00:00:00 2001 From: nulano Date: Fri, 27 Sep 2019 00:58:32 +0200 Subject: [PATCH 4/4] add version added [ci skip] --- docs/reference/ImageGrab.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/reference/ImageGrab.rst b/docs/reference/ImageGrab.rst index a7d7be6fef4..e94e21cb9df 100644 --- a/docs/reference/ImageGrab.rst +++ b/docs/reference/ImageGrab.rst @@ -22,7 +22,11 @@ or the clipboard to a PIL image memory. :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. + + .. versionadded:: 6.1.0 :param all_screens: Capture all monitors. Windows OS only. + + .. versionadded:: 6.2.0 :return: An image .. py:function:: PIL.ImageGrab.grabclipboard()