What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
When a PyAutoGUI screenshot fails, first determine whether the failure is in capture or in image matching. Verify Pillow and the platform capture backend, record the actual image dimensions, inspect a saved full-screen image, and only then debug locateOnScreen(). Retina scaling, Linux display sessions, missing utilities, headless execution and mismatched templates are the common fault lines.
Start with a minimal diagnostic
Run this in the same Python environment as your application. It tests imports, reports versions, captures the complete screen, prints both coordinate and image dimensions, and then captures a small region.
import sys
import pyautogui
from PIL import Image
print("Python:", sys.version)
print("PyAutoGUI:", getattr(pyautogui, "__version__", "unknown"))
try:
import PIL
print("Pillow:", PIL.__version__)
except Exception as exc:
print("Pillow import failed:", repr(exc))
screen_size = pyautogui.size()
image = pyautogui.screenshot("screen.png")
print("pyautogui.size():", screen_size)
print("screenshot.size:", image.size)
print("mode:", image.mode)
left, top, width, height = 0, 0, min(400, image.width), min(300, image.height)
region = pyautogui.screenshot(region=(left, top, width, height))
region.save("region.png")
print("region.size:", region.size)
PyAutoGUI provides screenshot and image-location functions through PyScreeze, and screenshot functionality requires Pillow. The screenshot call returns a Pillow image and can save directly to a filename. The official documentation estimates roughly 100 milliseconds for a 1,920×1,080 full-screen capture and about one to two seconds for a locate call; these are documentation estimates, not a performance guarantee. See the Screenshot Functions documentation.
Check the capture prerequisites
Use the interpreter that runs the script
Import both packages explicitly:
python -c "import pyautogui, PIL; print(pyautogui.__version__); print(PIL.__version__)"
If this fails, install or repair the packages in that interpreter, not merely in a different system Python or virtual environment. A missing Pillow import is a dependency error, not a display-scaling problem.
The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →#1 Best Overall
Linux backend and desktop session
PyAutoGUI’s installation documentation lists scrot, Tkinter and Python development headers for Linux. Confirm that the capture utility is installed and that your script can access the active graphical session. A local X11 desktop, an SSH session, a container and a CI runner can expose very different displays.
Pillow’s current ImageGrab documentation says that, when the default X11 display returns no snapshot, it may fall back to gnome-screenshot, grim or spectacle when available. That describes Pillow’s layer and version, not a universal guarantee for every PyAutoGUI installation. The available documentation does not establish one reliable Wayland, privacy-permission or headless fix; identify the display server and installed stack before changing code.
macOS capture path
PyAutoGUI invokes macOS’s built-in screencapture command. If the command works but the result is blank or inaccessible, record the macOS version, Python environment and whether the process is running locally or through a remote session. The cited documentation does not establish a single current screen-recording permission procedure for every macOS release, so do not treat an old permission recipe as universal.
Windows implementation
The project description says PyAutoGUI reaches Windows through WinAPI using Python’s built-in ctypes, with Pillow providing screenshots. A GitHub issue opened on December 21, 2016 reported undersized images on Windows 10 with Python 3.5.2, PyAutoGUI 0.9.33 and PIL 3.4.2. The reporter described a DPI-scaling compatibility workaround. That is a historical clue, not a current prescription: measure your dimensions and check the process DPI context on the versions you actually support. See issue #116.
Rank #2
When the screenshot is black, blank or missing
Separate file writing from capture
A successful return from pyautogui.screenshot() only proves that an image object was produced and, when a filename was supplied, that saving completed. Open the saved file independently and inspect it with an image viewer. Print its mode, size and a few pixels:
image = pyautogui.screenshot()
print(image.size, image.mode, image.getpixel((0, 0)))
image.save("debug.png")
If the file is all black, the backend may not be seeing the desktop at all. Check the active display, graphical-session permissions, Linux capture utility and whether the process is headless. Test the same script while physically logged into the desktop; if that works, the execution context rather than PyAutoGUI’s Python API is the differentiator.
Test full screen before a region
Use a full-screen capture first, then a known region:
full = pyautogui.screenshot("full.png")
part = pyautogui.screenshot(region=(100, 100, 500, 300))
print(full.size, part.size)
The region tuple is (left, top, width, height). Negative coordinates, a monitor arrangement with an offset, or coordinates from a different display can produce an unexpected crop. Keep the test inside the dimensions reported by pyautogui.size().
Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Fix the driver behind crashes, sound loss and screen glitches3Clear out junk files and repair common Windows errorsWhen the screenshot has the wrong size
Compare both coordinate spaces
Immediately compare:
logical = pyautogui.size()
shot = pyautogui.screenshot()
print("logical:", logical, "pixels:", shot.size)
These values can differ without a failed write. Pillow’s ImageGrab documentation states that macOS Retina captures are 2× by default. Pillow 12.3.0 added a scale_down=True option to ImageGrab, but you should not assume that PyAutoGUI’s screenshot wrapper exposes that option. Instead, make the screenshot and template use the same pixel scale, or explicitly resize one image after capture when your matching logic allows it.
A template captured at logical dimensions will not match a 2× screenshot. Conversely, a 2× template will fail against a logical-resolution image. Save both images and compare their pixel dimensions before changing confidence.
Retest with a controlled region
Capture a fixed region whose coordinates and expected size are known:
left, top, width, height = 50, 50, 300, 200
shot = pyautogui.screenshot(region=(left, top, width, height))
assert shot.size == (width, height), shot.size
shot.save("controlled-region.png")
If the assertion fails, investigate scaling or the backend. If it passes while full-screen dimensions look surprising, the discrepancy is likely the desktop’s logical-versus-physical coordinate model.
Recommended Free Tools
When locateOnScreen() cannot find the image
Capture validity and matching are separate stages. First open the saved screenshot and verify that the target is visibly present at the size represented by the template. Then check that the template was captured from the same theme, zoom level, display scale, window state and rendering state.
Use a deterministic match test
import pyautogui
try:
box = pyautogui.locateOnScreen("button.png")
print("match:", box)
except pyautogui.ImageNotFoundException:
print("No match in the current screenshot")
Current PyAutoGUI documentation says a failed locate raises ImageNotFoundException. Code that expects None should be checked against the behavior and configuration of the installed version.
Understand confidence
The optional confidence argument requires OpenCV. Install and validate OpenCV in the same interpreter before using it:
import cv2
box = pyautogui.locateOnScreen("button.png", confidence=0.85)
Confidence is not a cure for wrong scale, a stale template or a target hidden behind a popup. Start with an exact match and add confidence only for genuine rendering variation. Lowering the threshold too far can return a visually incorrect location.
Best Value
Make the target stable
- Wait until the application has finished animating or loading.
- Use the same browser zoom, operating-system scale and application theme used to create the template.
- Capture a tight template that excludes changing text, timestamps and shadows.
- Ensure the target is not covered by a consent dialog, tooltip, chat widget or another window.
- Restrict the search with
region=when the target’s location is known; this reduces work and false matches.
A repeatable troubleshooting workflow
- Record operating system and version, Python version, PyAutoGUI and Pillow versions, Linux display session, and whether execution is local, remote or headless.
- Verify
import pyautoguiandimport PILin the exact interpreter used by the job. - Check the platform capture utility and graphical display environment, especially Linux
scrot, Tkinter and development headers. - Capture and inspect one full-screen image before attempting any locate call.
- Print
pyautogui.size()and the Pillow image’s.size; investigate any unexpected scale. - Capture a small known region and confirm its exact dimensions.
- Only after capture is valid, compare the template’s dimensions and appearance with the current screenshot.
- Add OpenCV and
confidenceonly when approximate matching is actually required.
Reliability and performance considerations
Full-screen captures are generally cheaper than repeated broad searches, but the documented timing estimates vary with resolution, operating system, backend and machine load. Capture once, reuse the image for several analyses when possible, and search a constrained region. Avoid taking screenshots in a tight polling loop without a delay; it can consume CPU and create race conditions with UI animation.
For unattended jobs, log the environment and retain a failed image. A screenshot showing the real desktop is more useful than a traceback alone. Treat a blank image, a timeout and a missing match as different outcomes so retries do not hide a configuration error.
Or skip the browser setup
If your goal is a clean image of a web page rather than the pixels on your own desktop, ScreenshotNeo makes one HTTP request and returns PNG, JPEG, WebP or PDF. It accepts cookie and consent banners like a visitor, then removes more than 60 known consent platforms, newsletter popups and chat widgets; each cleanup step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and response headers report the page verdict and billing status.
cURL:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python:
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
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}`);
See the ScreenshotNeo documentation for options including full-page lazy-image loading, CSS-selector element capture, dark mode, device presets, arbitrary viewports, retina scale, PDF settings, custom CSS and JavaScript, clicks, waits, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, chosen cache TTLs, signed image links, asynchronous webhooks, bulk capture of up to 100 URLs per call, usage reporting and the OpenAPI specification. Its MCP server exposes take_screenshot, get_page_info and capture_pdf to Claude, Cursor and other MCP clients.
The Free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots; yearly billing gives two months free, and every feature is included on every plan. Create a free ScreenshotNeo account.
Common errors and fixes
| Symptom | Likely layer | Fix |
|---|---|---|
ModuleNotFoundError: PIL |
Dependency | Install Pillow in the interpreter running the script and retry the import. |
| Linux screenshot command unavailable | Backend | Install and validate the utility listed for your PyAutoGUI/Linux setup, including scrot; verify Tkinter and development headers. |
| Black or empty image | Display/session | Test locally in the active graphical session; inspect display-server access and headless/remote conditions. |
| Image dimensions are doubled | Scaling | Check Retina or desktop scaling and make screenshot and template pixel scales consistent. |
| Full screen works, region is wrong | Coordinates | Use (left, top, width, height) within the reported coordinate space and account for monitor offsets. |
ImageNotFoundException |
Matching | Confirm target visibility, template size, theme, zoom and rendering; then consider OpenCV confidence matching. |
confidence argument fails |
Optional dependency | Install OpenCV in the same environment or remove confidence for exact matching. |
Frequently asked questions
Does a saved PNG prove PyAutoGUI captured the correct screen?
No. It proves an image was produced and saved. Inspect its content, dimensions and display context separately.
Can I use Pillow’s Retina scaling option directly through PyAutoGUI?
Do not assume so. Pillow 12.3.0 documents scale_down=True for ImageGrab, while PyAutoGUI’s wrapper may not expose that parameter.
Should I lower confidence until a match appears?
No. First correct scale, template age, theme, zoom and target visibility. A very low threshold can match the wrong pixels.
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.

