Driver FixRecommendedSound, Wi-Fi or graphics acting up? Check drivers firstFind missing or outdated drivers fast.Check DriversOctober DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsPC HealthRecommendedCrashes, freezes, slowdowns? Check your PC nowSpot repairable issues before they interrupt work.Check PC×
Skip to content
Desktop automation

How to Take Screenshots with Pillow ImageGrab in Python

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
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.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
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:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
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, grim or spectacle.
  • 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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.Support on Ko-Fi

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Python 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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

What 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.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Leave a Reply

Your email address will not be published. Required fields are marked *

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Read next

Recommended PC Tool
Recommended PC Tool
Outdated Drivers Are Slowing You DownFree scan - exact matches
Windows Errors? Fix Them Before They SpreadFree repair scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.