Use Pillow’s ImageGrab.grab() to copy the current desktop into a PIL image, then save it with image.save(). Omit bbox for the whole available screen, or pass bbox=(left, top, right, bottom) for a rectangle. The exact result depends on your operating system, display server, monitor layout and Pillow version.
This guide shows full-screen, region, multi-monitor and window captures, explains Retina and Linux behavior, and includes a production-friendly script with diagnostics. The API details are from the Pillow ImageGrab reference; version-specific Retina behavior is documented in the Pillow 12.3.0 release notes.
Install Pillow and verify the version
Install or upgrade Pillow in the environment that will run the capture:
python -m pip install --upgrade Pillow
Check the installed version before using newer keyword arguments such as scale_down:
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
#1 Best Overall
python -c "import PIL; print(PIL.__version__)"
The current API documentation is for a 13.0.0.dev0 development reference. For stable usage, note that scale_down was added in Pillow 12.3.0, released 2026-07-01. Window capture support arrived on Windows in Pillow 11.2.1 and on macOS in Pillow 12.1.0, so older installations may reject those arguments.
Capture the entire screen
A minimal, runnable example is:
from PIL import ImageGrab
screenshot = ImageGrab.grab()
screenshot.save("screenshot.png")
grab() returns a PIL image object. You can save it as PNG, inspect its dimensions, or pass it to any other Pillow operation. The call without bbox asks Pillow for the full screen available through the platform capture path.
Inspect the result before saving
from PIL import ImageGrab
image = ImageGrab.grab()
print("size:", image.size) # (width, height)
print("mode:", image.mode) # RGBA on macOS; RGB elsewhere
image.save("desktop.png")
The documented modes are RGBA on macOS and RGB on other platforms. If downstream code expects one mode, normalize it explicitly:
if image.mode != "RGB":
image = image.convert("RGB")
image.save("desktop-rgb.jpg", quality=90)
Capture only a rectangular region
Pass a four-value bounding box in screen coordinates: (left, top, right, bottom). The right and bottom values define the far edge of the requested rectangle.
The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →from PIL import ImageGrab
region = ImageGrab.grab(bbox=(100, 100, 800, 600))
region.save("region.png")
Coordinates are not browser coordinates or window-relative coordinates; they are positions in the desktop’s screen coordinate system. On a multi-monitor Windows setup, the virtual desktop can extend into negative coordinates, so a monitor positioned to the left or above the primary display may require negative values.
Make the rectangle configurable
from PIL import ImageGrab
left = int(input("Left: "))
top = int(input("Top: "))
right = int(input("Right: "))
bottom = int(input("Bottom: "))
if right <= left or bottom <= top:
raise ValueError("right must be greater than left and bottom greater than top")
image = ImageGrab.grab(bbox=(left, top, right, bottom))
print(image.size, image.mode)
image.save("selected-region.png")
Useful ImageGrab options
The same method supports platform-specific and version-specific controls. Use only arguments supported by the Pillow version installed on the machine.
| Option | What it does | Availability or qualification |
|---|---|---|
bbox |
Limits the capture to (left, top, right, bottom). Omit it for the full screen. |
General ImageGrab behavior. |
all_screens=True |
Requests all monitors instead of only the primary screen. | Windows option; the virtual desktop can have negative top-left coordinates. |
include_layered_windows=True |
Includes layered windows in a Windows capture. | Windows-only option. |
window=... |
Captures one window identified by a native handle. | Uses an HWND on Windows and a CGWindowID on macOS. Windows support was added in Pillow 11.2.1; macOS support in 12.1.0. |
scale_down=True |
Requests one-times logical sizing for a Retina capture. | Added in Pillow 12.3.0; use the keyword form with a compatible Pillow release. |
xdisplay=... |
Selects the X11 display on Linux. | None uses the normal X11 path. An empty string disables Pillow’s external screenshot fallback. |
Do not combine an option simply because it exists in the signature: test the resulting image’s size and mode on each target operating system.
Rank #2
Windows: multiple monitors and individual windows
Capture the complete virtual desktop
from PIL import ImageGrab
all_displays = ImageGrab.grab(all_screens=True)
print(all_displays.size)
all_displays.save("all-monitors.png")
With all_screens=True, the returned image spans the Windows virtual desktop. If you then use bbox, express that box in the same virtual coordinate system, including negative values where necessary.
Capture a native window
For a single window, pass its Windows HWND to window. Obtaining an HWND is outside ImageGrab itself; once you have one from your window-management code, the capture call is:
from PIL import ImageGrab
hwnd = 123456 # replace with the target window's HWND
image = ImageGrab.grab(window=hwnd)
image.save("window.png")
The window argument is not a CSS selector and cannot identify an arbitrary webpage element. It addresses a native operating-system window.
macOS: RGBA output and Retina scaling
macOS captures are documented as RGBA. Retina displays can produce images at twice the logical dimensions. If your layout or file pipeline needs one-times sizing, Pillow 12.3.0 and later provide:
from PIL import ImageGrab
image = ImageGrab.grab(scale_down=True)
print(image.size, image.mode)
image.save("macos-1x.png")
On an older Pillow version, passing scale_down can raise an unexpected-keyword error. Upgrade Pillow or omit the argument and handle the larger image yourself. A macOS single-window capture uses a CGWindowID:
Recommended Free Tools
from PIL import ImageGrab
window_id = 987654 # replace with a CGWindowID
image = ImageGrab.grab(window=window_id)
image.save("mac-window.png")
Linux: display paths, X11 and fallbacks
On Linux, ImageGrab.grab() uses an X11 display path when xdisplay is None. If that path does not return a snapshot, Pillow documents fallback attempts using gnome-screenshot, grim or spectacle when an applicable utility is installed. Set xdisplay="" to disable this fallback behavior.
from PIL import ImageGrab
# Normal X11 path, with Pillow's documented fallback behavior.
image = ImageGrab.grab()
image.save("linux.png")
# Explicitly disable the external fallback:
# image = ImageGrab.grab(xdisplay="")
Pillow exposes an XCB feature check that can help diagnose an X11 installation:
from PIL import features
print("XCB support:", features.check_feature(feature="xcb"))
Wayland, X11 bridges and containerized sessions can expose different display capabilities. Confirm that the Python process has a usable graphical session, check XCB support, and verify that the relevant fallback utility is installed if your environment relies on it. Pillow’s platform-support documentation distinguishes CI-tested operating systems from platforms merely reported to work; it is not a guarantee for every local desktop configuration.
A defensive capture script for automation
This script keeps the capture logic small while recording useful diagnostics and making the Retina argument optional:
from pathlib import Path
import sys
from PIL import ImageGrab, __version__ as pillow_version
output = Path("capture.png")
kwargs = {}
# Use scale_down only when the installed Pillow supports it and you want 1x
# logical sizing on a Retina Mac.
if sys.platform == "darwin":
major, minor, *_ = (int(part) for part in pillow_version.split(".")[:2])
if (major, minor) >= (12, 3):
kwargs["scale_down"] = True
try:
image = ImageGrab.grab(**kwargs)
except Exception as exc:
raise RuntimeError(
"Screen capture failed; verify the graphical session, display path, "
"and Pillow version"
) from exc
print({"pillow": pillow_version, "size": image.size, "mode": image.mode})
image.save(output)
print(f"Saved {output.resolve()}")
The version check prevents this particular script from sending scale_down to an older Pillow release. It does not make an unavailable display server work; that remains an operating-system and session requirement.
Troubleshoot common failures
“No screenshot” or an exception on Linux
- Run the script inside a graphical session with access to the intended display.
- Check
features.check_feature(feature="xcb"). - If the X11 path is unavailable, install and configure the fallback utility appropriate to your desktop:
gnome-screenshot,grimorspectacle. - Do not pass
xdisplay=""unless you intentionally want to disable that fallback.
The image is the wrong size on a Retina Mac
Print image.size. A two-times logical size is expected for Retina captures. On Pillow 12.3.0 or newer, call ImageGrab.grab(scale_down=True) when one-times output is required; on older versions, upgrade or resize after capture.
A multi-monitor crop misses the target
Inspect the monitor arrangement in the operating system and use virtual-desktop coordinates. Windows’ all_screens=True can create a coordinate space whose origin is negative when a monitor sits left of or above the primary display. A box written as if every monitor began at (0, 0) can therefore select the wrong area.
An argument is rejected
Check PIL.__version__ and compare it with the option’s documented introduction. In particular, scale_down requires Pillow 12.3.0 or later, Windows window support requires 11.2.1 or later, and macOS window support requires 12.1.0 or later.
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Repair Windows errors before they cause bigger problemsFix Now →Downstream code rejects the image mode
Print image.mode. Convert deliberately with image.convert("RGB") or image.convert("RGBA") before handing the image to code that assumes a fixed channel layout.
Performance, reliability and file choices
Full-screen capture copies every pixel exposed by the display path. A smaller bbox reduces the captured area and is the simplest way to reduce memory and encoding work when you need only one panel or dialog. For repeat captures, reuse a predictable output path or generate unique names, and check image.size and image.mode before expensive processing.
PNG preserves the captured pixels and is a straightforward default for evidence, tests and UI documentation. JPEG can reduce file size for photographic content but is lossy; choose it only when that trade-off is acceptable. The capture itself is synchronous: the call returns after Pillow has obtained the image or raised an error, so automation should catch exceptions and record the platform and Pillow version.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server, not a replacement for capturing your local desktop. It is useful when your real input is a public URL and you want a repeatable server-side shot instead of configuring a browser. Its clean-shot pipeline accepts consent banners and removes more than 60 known consent platforms, newsletter popups and chat widgets before capture; bot checks, CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and response headers report the page verdict and billing status. Its MCP server exposes take_screenshot, get_page_info and capture_pdf to Claude, Cursor and other MCP clients.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Fix the driver behind crashes, sound loss and screen glitches3Repair Windows errors before they cause bigger problemsPython one-call example (see the ScreenshotNeo documentation):
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
The equivalent cURL request is:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
And in Node.js:
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
ScreenshotNeo offers full-page captures with lazy images loaded, CSS-selector element shots, dark mode, device presets or custom viewports, Retina scale, PDF output, custom CSS and JavaScript, pre-capture clicks, selector hiding, selector/delay/network-idle waits, request and resource blocking, custom headers, cookies, user agents and Authorization, timezone and geolocation, transparent backgrounds, resizing, configurable-TTL caching, signed image links, asynchronous jobs with signed webhooks, bulk capture for up to 100 URLs per call, a usage API and an OpenAPI specification. Parameter names used by other screenshot APIs also work for easier migration.
| Plan | Allowance | Price |
|---|---|---|
| Free | 1,000 shots/month | $0, no card |
| Starter | 3,000 shots | $5 |
| Growth | 15,000 shots | $15 |
| Pro | 60,000 shots | $39 |
| Scale | 250,000 shots | $99 |
| Business | 1,000,000 shots | $249 |
Every feature is included on every plan, and yearly billing gives two months free. If you need URL screenshots rather than the pixels currently visible on your computer, start with 1,000 free screenshots a month with no card.
FAQ
Can ImageGrab capture a browser tab or CSS element?
It captures a desktop region or native window, not a DOM element. Use a screen-coordinate bbox for a visible rectangle; a CSS selector requires a browser-aware service or automation tool.
PC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteWhat does grabclipboard() do on Linux?
Pillow also documents clipboard image capture. On Linux, that path requires wl-paste or xclip; it is separate from ImageGrab.grab() and does not replace a display capture.
Best Value
Should I always request all_screens=True?
No. Use it only when you need the Windows virtual desktop. The default call targets the normal primary-screen capture path, which avoids unexpectedly large images and coordinate spaces.
How do I make a capture reproducible across computers?
Record the Pillow version, operating system, image mode and dimensions, and keep the capture rectangle or native window identifier alongside the output. Display scaling, monitor arrangement and Linux display availability can otherwise change the result.
Frequently Asked Questions
Can ImageGrab capture a browser tab or CSS element?
It captures a desktop region or native window, not a DOM element. Use a screen-coordinate bbox for a visible rectangle; a CSS selector requires a browser-aware service or automation tool.
What does grabclipboard() do on Linux?
Pillow also documents clipboard image capture. On Linux, that path requires wl-paste or xclip and is separate from ImageGrab.grab().
Should I always request all_screens=True?
No. Use it only when you need the Windows virtual desktop; the default call targets the normal primary-screen capture path.
How do I make a capture reproducible across computers?
Record the Pillow version, operating system, image mode and dimensions, and keep the capture rectangle or native window identifier with the output.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




