diff --git a/Tests/test_imagegrab.py b/Tests/test_imagegrab.py index a29bc019eea..bea7f68b31c 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(all_screens=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..e94e21cb9df 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, 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,7 +20,13 @@ 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. + + .. 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() diff --git a/src/PIL/ImageGrab.py b/src/PIL/ImageGrab.py index 8f359e2294f..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): +def grab(bbox=None, include_layered_windows=False, all_screens=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, all_screens) 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..67f8e546c11 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, all_screens = 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, &all_screens)) 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 (all_screens) { + 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); 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");