Skip to content
4 changes: 2 additions & 2 deletions docs/reference/ImageGrab.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's windowid? There's no other mention in the docs or code.

Copy link
Member

@radarhere radarhere Sep 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've pushed a commit to describe it as a CGWindowID instead.


.. versionadded:: 11.2.1
.. versionadded:: 11.2.1 (Windows), 12.0.0 (macOS)
:return: An image

.. py:function:: grabclipboard()
Expand Down
36 changes: 32 additions & 4 deletions src/PIL/ImageGrab.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,45 @@
fh, filepath = tempfile.mkstemp(".png")
os.close(fh)
args = ["screencapture"]
if bbox:
if window:
args += ["-l", str(window)]

Check warning on line 47 in src/PIL/ImageGrab.py

View check run for this annotation

Codecov / codecov/patch

src/PIL/ImageGrab.py#L47

Added line #L47 was not covered by tests
elif bbox:
left, top, right, bottom = bbox
args += ["-R", f"{left},{top},{right-left},{bottom-top}"]
subprocess.call(args + ["-x", filepath])
im = Image.open(filepath)
im.load()
os.unlink(filepath)
if bbox:
im_resized = im.resize((right - left, bottom - top))
im.close()
return im_resized
if window:
# 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(

Check warning on line 62 in src/PIL/ImageGrab.py

View check run for this annotation

Codecov / codecov/patch

src/PIL/ImageGrab.py#L60-L62

Added lines #L60 - L62 were not covered by tests
["screencapture", "-l", str(window), "-o", "-x", filepath]
)
with Image.open(filepath) as im_no_shadow:
retina = im.width - im_no_shadow.width > 100
Copy link

Copilot AI Nov 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The threshold of 100 pixels to detect Retina mode (im.width - im_no_shadow.width > 100) appears arbitrary and could be fragile. This assumes:

  1. The shadow is always more than 100 pixels wider in non-Retina mode
  2. The shadow scaling difference is consistent across different window sizes

Consider documenting why 100 pixels is the appropriate threshold, or using a relative threshold (e.g., percentage difference) instead of an absolute pixel count.

Suggested change
retina = im.width - im_no_shadow.width > 100
# Detect Retina mode by checking if the width difference is >30% of the no-shadow image width
retina = (im.width - im_no_shadow.width) / im_no_shadow.width > 0.3

Copilot uses AI. Check for mistakes.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current comment is

Determine if the window was in Retina mode or not by capturing it without the shadow, and checking how different the width is

I think it's reasonably apparent that we're using an absolute threshold

os.unlink(filepath)

Check warning on line 67 in src/PIL/ImageGrab.py

View check run for this annotation

Codecov / codecov/patch

src/PIL/ImageGrab.py#L65-L67

Added lines #L65 - L67 were not covered by tests

# Since screencapture's -R does not work with -l,
# crop the image manually
if retina:
left, top, right, bottom = bbox
im_cropped = im.resize(

Check warning on line 73 in src/PIL/ImageGrab.py

View check run for this annotation

Codecov / codecov/patch

src/PIL/ImageGrab.py#L71-L73

Added lines #L71 - L73 were not covered by tests
(right - left, bottom - top),
box=tuple(coord * 2 for coord in bbox),
)
Comment on lines +73 to +76
Copy link

Copilot AI Nov 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] In Retina mode, the code uses resize() with a box parameter to crop. However, resize() is primarily intended for scaling, not cropping. While this works (the box parameter specifies the region to sample from the source), it's unconventional and potentially confusing.

Consider using a two-step approach for clarity:

if retina:
    left, top, right, bottom = bbox
    im_cropped = im.crop(tuple(coord * 2 for coord in bbox))
    im_cropped = im_cropped.resize((right - left, bottom - top))

Or document why resize() with box is used instead of the more idiomatic crop() + resize() combination.

Suggested change
im_cropped = im.resize(
(right - left, bottom - top),
box=tuple(coord * 2 for coord in bbox),
)
im_cropped = im.crop(tuple(coord * 2 for coord in bbox))
im_cropped = im_cropped.resize((right - left, bottom - top))

Copilot uses AI. Check for mistakes.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling two methods would not be more efficient, from a performance perspective, than one.

else:
im_cropped = im.crop(bbox)
im.close()
return im_cropped

Check warning on line 80 in src/PIL/ImageGrab.py

View check run for this annotation

Codecov / codecov/patch

src/PIL/ImageGrab.py#L78-L80

Added lines #L78 - L80 were not covered by tests
else:
im_resized = im.resize((right - left, bottom - top))
im.close()
return im_resized
Comment on lines +82 to +84
Copy link

Copilot AI Nov 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The variable name im_cropped is used here, while a similar operation at line 82 uses im_resized. For consistency and accuracy, consider using im_cropped in both places since both operations effectively crop/extract a region from the original image (one uses crop() and one uses resize() with a box parameter, but both achieve cropping).

Suggested change
im_resized = im.resize((right - left, bottom - top))
im.close()
return im_resized
im_cropped = im.resize((right - left, bottom - top))
im.close()
return im_cropped

Copilot uses AI. Check for mistakes.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a resize operation, not a crop operation.

return im
elif sys.platform == "win32":
if window is not None:
Expand Down
Loading