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.

Python screenshot code can be correct and still fail because capture depends on the operating system, display server, logged-in desktop session, native utilities, security policy, and pixel-coordinate scale. A script launched inside an interactive desktop may work while the same script fails from SSH, a service, a container, or CI. Start by identifying the capture library and launch context, then run an uncropped full-screen test before changing crop coordinates.

What actually causes a screenshot to fail

A screenshot library is not rendering a desktop by itself. It asks an operating-system capture interface (or an external utility) for pixels. The result depends on whether the process can see the active display and whether that display uses a backend the library supports.

  • Different runtime: a terminal, remote shell, scheduled task, service, container, or CI job may not have access to the logged-in user’s graphical session.
  • Different session backend: Linux X11 and Wayland expose different capture paths. A package documented for X11 may need a utility or portal integration in another session.
  • Missing native dependency: some libraries use fallback commands such as gnome-screenshot, grim, or spectacle when their primary route cannot return an image.
  • Coordinate mismatch: Retina scaling, Windows DPI scaling, multiple monitors, and negative monitor origins can make a valid crop select the wrong pixels.
  • Managed-device policy: organization-controlled Windows settings can allow, deny, or leave screen capture under user control for the applicable capture mechanism.
  • Separate save failure: capture may succeed while the output path is unwritable or invalid.

These causes explain why “the same code” behaves differently on two PCs. The package version, interpreter, operating system, desktop session, and launch method all matter.

First: identify the exact capture stack

Before reinstalling Python, record the facts from the failing process:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • Python version and the exact interpreter path.
  • Capture package and version (for example, Pillow).
  • Operating-system edition and version.
  • Whether the process starts from a desktop terminal, remote session, service, container, or CI runner.
  • The complete exception text and whether the result is an exception, a black image, a blank image, or an incorrectly positioned crop.

Run this diagnostic with the same interpreter that runs your application:

python -c "import sys; print(sys.executable); print(sys.version)
try:
 import PIL; print('Pillow', PIL.__version__)
except Exception as e:
 print('Pillow import failed:', repr(e))"

If your application uses another package, inspect that package’s backend rather than assuming Pillow’s behavior applies.

Use a minimal, uncropped Pillow test

Pillow’s ImageGrab is a useful cross-platform example. Test the complete screen first, and print the actual image metadata:

from PIL import ImageGrab

try:
    image = ImageGrab.grab()
    print("size:", image.size, "mode:", image.mode)
    image.save("screen-test.png")
    print("saved screen-test.png")
except Exception as exc:
    print(type(exc).__name__ + ":", exc)

Interpret the result:

  • Full-screen capture fails: investigate display access, session type, native dependencies, or policy.
  • Capture succeeds but the crop is wrong: investigate image dimensions, monitor coordinates, Retina scaling, DPI scaling, and negative origins.
  • Image saves nowhere: check the destination directory and filesystem permissions independently of capture.

Do not begin by adding a hard-coded bbox. A crop can hide the real display-access problem.

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

Linux: X11, Wayland, utilities, and portals

Check the graphical session

From the same process environment, inspect the display variables:

python -c "import os; print('DISPLAY=', os.getenv('DISPLAY')); print('WAYLAND_DISPLAY=', os.getenv('WAYLAND_DISPLAY')); print('XDG_SESSION_TYPE=', os.getenv('XDG_SESSION_TYPE')); print('XDG_CURRENT_DESKTOP=', os.getenv('XDG_CURRENT_DESKTOP'))"

An X11 session commonly exposes DISPLAY. A Wayland session commonly exposes WAYLAND_DISPLAY. An unset variable, or a variable pointing to a display the process cannot access, is a strong indication that the process is outside the user’s desktop session. Fix the launch context or permissions rather than changing crop values.

Understand Pillow’s Linux route

Pillow documents native Linux capture through X11/XCB support. When the default X11 route does not return a snapshot and no explicit display is supplied, its documented fallback checks for gnome-screenshot, grim, or spectacle. Those commands must be installed, callable by the process, and compatible with the active session. Installing one command is not a universal Wayland fix; compositor, packaging, and session details still determine the result.

Install the utility that matches your distribution and desktop only when your package documentation recommends it, then rerun the uncropped test. If a fallback command works interactively but not from a service, the service still lacks access to the user’s graphical session.

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

Sandboxed applications and the XDG portal

The XDG Desktop Portal defines a screenshot request interface with screen, window, area, and active-window targets. It is relevant to sandboxed Linux applications, but a portal is not an automatic drop-in backend for every Python library. Verify that your specific library or application integrates with the portal before treating it as the fix. If it does not, use a supported integration or run the capture in an appropriate desktop context.

ImageGrab.grabclipboard() is a separate operation and has separate wl-paste or xclip requirements; a successful screenshot does not prove clipboard capture is configured.

macOS: Retina dimensions and crop coordinates

On a Retina display, Pillow documents that an ImageGrab capture is 2x by default. A logical 800×600 region can therefore produce a 1600×1200 image. Measure the returned image instead of assuming that screen points equal image pixels:

from PIL import ImageGrab

image = ImageGrab.grab()
print("physical image pixels:", image.size)
# Use a bbox only after you have compared this size with your display layout.
image.save("mac-full.png")

If you need a 1x result, Pillow documents scale_down=True:

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

image = ImageGrab.grab(scale_down=True)
print(image.size)
image.save("mac-1x.png")

Do not silently double every crop coordinate. Confirm the installed Pillow behavior, the captured dimensions, and the monitor layout first. A coordinate adjustment that fixes one Mac can break a non-Retina display.

Windows: monitors, desktop sessions, and policy

Interactive versus non-interactive execution

Run the minimal test while logged into the desktop, then compare it with the failing service, scheduled task, or remote invocation. A process without the interactive desktop may return a black or empty image even though the same code works when launched from a visible session.

For multi-monitor captures, Pillow documents all_screens=True. When all monitors are included, the virtual screen can have a negative top-left coordinate:

from PIL import ImageGrab

image = ImageGrab.grab(all_screens=True)
print("virtual desktop pixels:", image.size)
image.save("all-monitors.png")

A crop copied from a single-monitor setup can therefore select the wrong location. Determine the virtual desktop bounds using the capture library’s documented coordinate convention and test each monitor arrangement.

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

Managed Windows 11 devices

Microsoft documents screenshot-access privacy policies that organizations can set to user-controlled, force-allow, or force-deny for the applicable Windows capture mechanism. Check with the device administrator and identify the backend your Python package actually uses; those policies do not automatically govern every third-party screenshot implementation.

Microsoft’s Windows.Graphics.Capture API provides a user-selected display or application-window flow. That documented API and its secure selection UI are not proof that Pillow or another Python package uses the same backend. Avoid granting broad permissions or disabling policy until the implementation is known.

Crop correctly across monitors and scaling modes

Once full-screen capture works, make the crop observable and defensive:

from PIL import ImageGrab

image = ImageGrab.grab(all_screens=True)
left, top, right, bottom = 100, 100, 900, 700
if not (0 <= left < right and 0 <= top < bottom):
    raise ValueError("Invalid crop ordering")
if right > image.width or bottom > image.height:
    raise ValueError(f"Crop {right}x{bottom} exceeds image {image.width}x{image.height}")
image.crop((left, top, right, bottom)).save("crop.png")

This example validates image-relative coordinates. If the library reports monitor bounds with negative origins, translate those bounds into the returned image’s coordinate space before cropping. Keep the diagnostic full-screen file whenever a crop is disputed; it shows whether the problem is capture or geometry.

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

Separate capture errors from file errors

A valid image object proves capture completed, not that saving will succeed. Test a known-writable absolute directory and catch the two stages separately:

from pathlib import Path
from PIL import ImageGrab

out = Path.home() / "screen-test.png"
try:
    image = ImageGrab.grab()
except Exception as exc:
    raise RuntimeError("Desktop capture failed") from exc
try:
    image.save(out)
except Exception as exc:
    raise RuntimeError(f"Capture succeeded but writing {out} failed") from exc
print(out)

Inspect permissions, free space, path spelling, and whether a security product blocks the destination. Do not diagnose a filesystem exception as a display-permission problem.

Choosing a capture route

Route Best fit Trade-offs to check
Pillow ImageGrab Desktop scripts where the documented platform backend is available OS support, X11 dependencies, fallback utilities, monitor geometry, and scaling
Native OS API Platform-specific applications needing the system capture flow User-selection or consent behavior, packaging, and implementation effort
Linux utility fallback Systems where a compatible gnome-screenshot, grim, or spectacle exists Utility availability, session compatibility, and process access to the desktop
Desktop portal Sandboxed Linux applications that integrate with the portal interface Whether the Python application actually implements the portal request flow
ScreenshotNeo (#1 hosted option) Capturing public web pages without configuring a local browser desktop It captures URLs, not your local interactive desktop

Choose a local route when you need pixels from the user’s own desktop. Choose a hosted web-page capture when the target is a URL and repeatable server-side rendering is more useful than desktop access.

Or skip the browser setup

For a website URL, ScreenshotNeo avoids the local display-server problem. Before capture it accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed; response headers identify the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.

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

One request returns a PNG, JPEG, WebP, or PDF:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

See the ScreenshotNeo API documentation for parameters and response headers. Equivalent Python and Node.js calls are:

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)
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 buffer = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', buffer));

Its 63 options include full-page lazy-image loading, CSS-selector element capture, dark mode, 12 device presets or custom viewports, retina scale, PDF paper and page controls, custom CSS and JavaScript, clicks, hidden selectors, selector/delay/network-idle waits, ad/tracker/request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, chosen cache TTLs, signed image links, asynchronous jobs with signed webhooks, bulk capture of 100 URLs per call, a usage API, an OpenAPI specification, and familiar parameter names 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

Yearly billing provides two months free, and every feature is included on every plan. Create a free ScreenshotNeo account to get 1,000 screenshots each month without a card.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Performance, reliability, and cost decisions

  • For local capture, keep the process in the interactive session, avoid unnecessary full-virtual-desktop images, and crop only after confirming dimensions.
  • For Linux automation, pin and verify the utility or portal integration used by your deployment image; a utility available on a developer workstation may be absent in a container.
  • For hosted URL capture, use caching with an explicit TTL when freshness permits, asynchronous jobs and signed webhooks for long pages, and bulk capture for up to 100 URLs per call.
  • With ScreenshotNeo, inspect X-Page-Verdict and X-Billed so retries distinguish a failed page from a billable clean shot.

Troubleshooting checklist by symptom

“Display not found” or an X11 connection error

Confirm DISPLAY, run under the logged-in desktop user, verify X11/XCB support, and test a documented fallback utility. If the process is a service or container, provide an intentional desktop integration rather than copying a variable blindly.

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.

Black or empty image over SSH or remote execution

Compare the launch context with an interactive desktop run. The remote process may have no visible session or may be blocked from it. Fix session access, or capture the web URL through a hosted service instead of trying to photograph a nonexistent remote desktop.

Wayland capture fails

Check the session type and whether the installed library supports it. Verify the availability and compatibility of grim, gnome-screenshot, or spectacle as documented fallbacks, or confirm portal integration for a sandboxed app. Do not treat Wayland as a universal failure.

The crop is shifted or empty on macOS

Print image.size, check whether the capture is 2x Retina output, and review monitor coordinates. Try scale_down=True when a 1x image is required, then recalculate the crop from measured dimensions.

The second monitor is missing or the crop has a negative position

Use the library’s all-monitor option, such as Pillow’s all_screens=True, and account for a virtual desktop whose top-left coordinate can be negative. Do not reuse single-monitor coordinates unchanged.

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

Windows says capture is blocked

Identify the backend, check whether the machine is managed, and ask the administrator whether the applicable screenshot policy is user-controlled, force-allow, or force-deny. Do not disable organizational controls as a generic fix.

The file is zero bytes or cannot be written

Print the returned image’s size before saving, then write to a known-writable absolute path. A valid image plus a write exception indicates a filesystem problem, not a display problem.

FAQ

Should I reinstall Python first?

No. Confirm the interpreter, package version, operating system, session, and full exception first; reinstalling can leave the actual display or policy issue unchanged.

Can a dummy HDMI plug fix software capture?

There is no general documented basis for buying hardware as the remedy for the software, session, dependency, policy, and coordinate causes described here.

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

Why does a full screenshot work but a window crop fail?

The capture path is functioning; the remaining issue is usually coordinate space, scaling, monitor origin, or a window position outside the image bounds.

Frequently Asked Questions

Does a Python screenshot package automatically use the XDG desktop portal?

No. Portal support depends on the specific library or application. Verify integration before treating the portal as a drop-in replacement.

What should I log in a bug report?

Include the interpreter path, Python and package versions, OS version, launch context, display/session variables, image dimensions, and complete exception text.

Can ScreenshotNeo capture my local Windows or Mac desktop?

No. ScreenshotNeo captures web URLs; use a local OS or Python capture API for an interactive desktop.

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

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.