From 5ce88dbe53a3d46e20d464fbd6ef06031645bfda Mon Sep 17 00:00:00 2001 From: GUO YANKE Date: Mon, 7 Jul 2025 13:57:11 +0800 Subject: [PATCH 1/4] feat(ImageGrab): enhance grab function to support window-based screenshot capturing on macOS --- src/PIL/ImageGrab.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/PIL/ImageGrab.py b/src/PIL/ImageGrab.py index 1eb450734..ba2c9b141 100644 --- a/src/PIL/ImageGrab.py +++ b/src/PIL/ImageGrab.py @@ -43,7 +43,10 @@ def grab( fh, filepath = tempfile.mkstemp(".png") os.close(fh) args = ["screencapture"] - if bbox: + if window: + args += ["-l", str(window)] + # -R is not working with -l + if bbox and not window: left, top, right, bottom = bbox args += ["-R", f"{left},{top},{right-left},{bottom-top}"] subprocess.call(args + ["-x", filepath]) @@ -51,9 +54,16 @@ def grab( im.load() os.unlink(filepath) if bbox: - im_resized = im.resize((right - left, bottom - top)) - im.close() - return im_resized + # manual crop for windowed mode + if window: + left, top, right, bottom = bbox + im_cropped = im.crop((left, top, right, bottom)) + im.close() + return im_cropped + else: + im_resized = im.resize((right - left, bottom - top)) + im.close() + return im_resized return im elif sys.platform == "win32": if window is not None: From 1f7e9c3b51db100de0164eab45ca16ec4e24bd78 Mon Sep 17 00:00:00 2001 From: Yan-Ke Guo Date: Mon, 7 Jul 2025 16:29:38 +0800 Subject: [PATCH 2/4] Apply suggestions from code review Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- src/PIL/ImageGrab.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/PIL/ImageGrab.py b/src/PIL/ImageGrab.py index ba2c9b141..c18874581 100644 --- a/src/PIL/ImageGrab.py +++ b/src/PIL/ImageGrab.py @@ -56,8 +56,7 @@ def grab( if bbox: # manual crop for windowed mode if window: - left, top, right, bottom = bbox - im_cropped = im.crop((left, top, right, bottom)) + im_cropped = im.crop(bbox) im.close() return im_cropped else: From 7eaac3fcf0cd0fa54ba91784727f1d1a7654b31b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 7 Jul 2025 18:13:07 +1000 Subject: [PATCH 3/4] Updated documentation --- docs/reference/ImageGrab.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/reference/ImageGrab.rst b/docs/reference/ImageGrab.rst index f6a2ec5bc..25afc9926 100644 --- a/docs/reference/ImageGrab.rst +++ b/docs/reference/ImageGrab.rst @@ -42,9 +42,9 @@ or the clipboard to a PIL image memory. .. versionadded:: 7.1.0 :param window: - HWND, to capture a single window. Windows only. + Capture a single window. On Windows, this is a HWND. On macOS, it uses windowid. - .. versionadded:: 11.2.1 + .. versionadded:: 11.2.1 (Windows), 12.0.0 (macOS) :return: An image .. py:function:: grabclipboard() From 79914ec8a57e1beeb2a5c62809044c116828962b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 9 Jul 2025 20:00:20 +1000 Subject: [PATCH 4/4] Check for scaling in macOS windows --- src/PIL/ImageGrab.py | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/PIL/ImageGrab.py b/src/PIL/ImageGrab.py index c18874581..b82a2ff3a 100644 --- a/src/PIL/ImageGrab.py +++ b/src/PIL/ImageGrab.py @@ -45,8 +45,7 @@ def grab( args = ["screencapture"] if window: args += ["-l", str(window)] - # -R is not working with -l - if bbox and not window: + elif bbox: left, top, right, bottom = bbox args += ["-R", f"{left},{top},{right-left},{bottom-top}"] subprocess.call(args + ["-x", filepath]) @@ -54,9 +53,29 @@ def grab( im.load() os.unlink(filepath) if bbox: - # manual crop for windowed mode if window: - im_cropped = im.crop(bbox) + # Determine if the window was in retina mode or not + # by capturing it without the shadow, + # and checking how different the width is + fh, filepath = tempfile.mkstemp(".png") + os.close(fh) + subprocess.call( + ["screencapture", "-l", str(window), "-o", "-x", filepath] + ) + with Image.open(filepath) as im_no_shadow: + retina = im.width - im_no_shadow.width > 100 + os.unlink(filepath) + + # Since screencapture's -R does not work with -l, + # crop the image manually + if retina: + left, top, right, bottom = bbox + im_cropped = im.resize( + (right - left, bottom - top), + box=tuple(coord * 2 for coord in bbox), + ) + else: + im_cropped = im.crop(bbox) im.close() return im_cropped else: