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

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Use Pillow’s ImageGrab.grab() to capture the screen, then call getpixel((x, y)) on the returned image. For a guaranteed three-channel result, normalize the capture to RGB first:

from PIL import ImageGrab

image = ImageGrab.grab()
r, g, b = image.convert("RGB").getpixel((100, 100))
print(r, g, b)

The coordinate is measured in the image returned by grab(), and the tuple format follows that image’s mode. macOS captures are documented as RGBA, while other platforms normally return RGB. See the Pillow ImageGrab documentation and Image documentation for the API definitions.

Minimal example: capture one pixel

Install Pillow into the Python environment that will run the script:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
python -m pip install --upgrade Pillow

Then capture the display and read one point:

from PIL import ImageGrab

image = ImageGrab.grab()
pixel = image.getpixel((100, 100))
print("mode:", image.mode)
print("pixel:", pixel)

getpixel((x, y)) accepts a two-item coordinate and returns the value at that location. On an RGB image, pixel is a three-item tuple in red, green, blue order. On an RGBA image it contains red, green, blue and alpha. The image is already in memory after grab(); no separate save operation is required.

Why the tuple sometimes has three values and sometimes four

Pillow’s pixel APIs are mode-dependent. Inspect image.mode before unpacking the result, especially when the script runs on more than one operating system.

Image mode Typical value from getpixel() Use it when
RGB (red, green, blue) You need three color channels and no alpha channel.
RGBA (red, green, blue, alpha) You need transparency information as well as color.
P A palette index, not direct RGB channels The image uses an indexed color palette; convert before reading channel values.

The ImageGrab reference documents RGB output on Windows and Linux and RGBA output on macOS. A palette-mode image returns an index into its palette, so treating that integer as a red value produces the wrong result. Pillow’s concepts documentation explains how modes determine pixel representations.

Normalize to exactly three channels

If dropping transparency is acceptable, convert the image before calling getpixel():

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

image = ImageGrab.grab()
rgb_image = image.convert("RGB")
r, g, b = rgb_image.getpixel((100, 100))
print(r, g, b)

This also handles a palette-mode image by resolving its palette to direct RGB channels. Conversion discards alpha, so preserve the original mode when transparency matters:

from PIL import ImageGrab

image = ImageGrab.grab()
value = image.getpixel((100, 100))

if image.mode == "RGBA":
    r, g, b, a = value
    print(f"red={r}, green={g}, blue={b}, alpha={a}")
else:
    r, g, b = image.convert("RGB").getpixel((100, 100))
    print(f"red={r}, green={g}, blue={b}")

Coordinates, bounding boxes and screen layout

Without arguments, ImageGrab.grab() captures the available desktop and the returned image’s coordinate system starts at its own top-left corner. The point (0, 0) is therefore the top-left pixel of that returned image, not necessarily a coordinate you should assume for every multi-monitor arrangement.

Use bbox to limit the capture:

from PIL import ImageGrab

# Capture a rectangle from the desktop.
region = ImageGrab.grab(bbox=(200, 120, 1000, 720))

# Coordinates are local to `region`, not the original desktop.
pixel = region.getpixel((10, 20))
print(pixel)

After applying a bounding box, index the cropped image with local coordinates. In this example, (10, 20) means 10 pixels from the crop’s left edge and 20 pixels from its top edge. Check the dimensions before indexing dynamically chosen points:

x, y = 10, 20
if 0 <= x < region.width and 0 <= y < region.height:
    print(region.getpixel((x, y)))
else:
    raise ValueError(f"Point {(x, y)} is outside {region.size}")

On Windows, the API also documents an all_screens option for including all monitors. Its result and coordinate range depend on the desktop arrangement, so inspect image.size and test a known point on the target display rather than hard-coding assumptions.

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.
from PIL import ImageGrab

image = ImageGrab.grab(all_screens=True)
print(image.size, image.mode)
print(image.getpixel((100, 100)))

macOS Retina captures and Pillow 12.3.0

Retina displays can produce a capture at roughly twice the logical screen scale. Pillow 12.3.0 added scale_down=True to request a 1x image on macOS. That option is documented in the ImageGrab reference and was added in the Pillow 12.3.0 release notes, dated 2026-07-01.

from PIL import ImageGrab

# Requires Pillow 12.3.0 or newer.
image = ImageGrab.grab(scale_down=True)
print(image.size, image.mode)
print(image.convert("RGB").getpixel((100, 100)))

Check the installed Pillow version before using this keyword. If the argument is rejected, upgrade Pillow or omit it and account for the captured image’s actual dimensions when translating logical UI coordinates to pixel coordinates. Do not assume that a point from a Retina screenshot maps one-to-one to a point measured in a window toolkit.

A complete helper function

This helper returns either RGB or RGBA while making the coordinate frame explicit:

from PIL import ImageGrab


def pixel_at(x, y, *, bbox=None, keep_alpha=False, scale_down=False):
    """Return a pixel from an ImageGrab capture.

    x and y are local to the captured image. With bbox, they are not
    original desktop coordinates.
    """
    options = {}
    if bbox is not None:
        options["bbox"] = bbox
    if scale_down:
        options["scale_down"] = True  # Pillow 12.3.0+

    image = ImageGrab.grab(**options)
    if not (0 <= x < image.width and 0 <= y < image.height):
        raise ValueError(
            f"({x}, {y}) is outside the captured image {image.size}"
        )

    if keep_alpha and image.mode == "RGBA":
        return image.getpixel((x, y))
    return image.convert("RGB").getpixel((x, y))


print(pixel_at(100, 100))
print(pixel_at(10, 20, bbox=(200, 120, 1000, 720)))
print(pixel_at(100, 100, keep_alpha=True, scale_down=True))

Only pass scale_down=True when the installed Pillow version supports it and the capture is running on a platform where the option is meaningful. The keep_alpha branch deliberately preserves RGBA only when that mode is present; otherwise the helper returns normalized RGB.

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

Platform prerequisites and capture limitations

  • macOS: expect RGBA output according to the documentation, and grant the operating system’s screen-recording permission to the application running Python. Retina scaling can change the pixel dimensions.
  • Windows: use all_screens=True when the capture must include every monitor. The desktop’s monitor arrangement determines the resulting coordinate space.
  • Linux: when the default X11 display cannot provide a capture, Pillow may fall back to available screenshot utilities. Those utilities, a graphical session and their permissions must exist on the host; a headless machine is not guaranteed to work.

These are environment-dependent behaviors, not guarantees that every workstation has a display server, permission or fallback utility configured.

Reading many pixels

For a single point, getpixel() is the direct API. If you need a large grid or every pixel, treat that as a separate bulk-processing problem: capture once, avoid calling ImageGrab.grab() for every point, and choose an image-processing representation suited to your workload. The official references used here do not publish a universal speed figure, so do not rely on an assumed pixels-per-second rate. Measure on the operating system, display scale and image size that your application will actually use.

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

Troubleshooting common failures

ModuleNotFoundError: No module named 'PIL'

Install the Pillow package in the same interpreter that launches the script:

python -m pip install --upgrade Pillow

The package is named Pillow, but the import namespace is PIL. In a virtual environment, activate that environment before installing.

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

The capture raises an operating-system or display error

Run the program inside an interactive graphical session, verify screen-capture permissions, and confirm that the Linux display server or required screenshot utility is available. A remote shell or headless container may not expose a capturable desktop.

The result is four values instead of three

Print image.mode. macOS captures are documented as RGBA. Use image.convert("RGB") when alpha is not needed, or unpack all four values and retain the alpha channel.

The result is a single integer

The image may be palette mode (P). Convert it to RGB before reading channels:

rgb = image.convert("RGB")
red, green, blue = rgb.getpixel((x, y))

The sampled color is from the wrong place

Check whether a bbox was used. Coordinates passed to the cropped image are local to that crop. On Retina displays, compare logical UI coordinates with image.size and consider scale_down=True on Pillow 12.3.0 or newer.

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

IndexError or an invalid coordinate

Ensure 0 <= x < image.width and 0 <= y < image.height. Print image.size immediately after capture; a bounding box or display scale may have produced a smaller image than expected.

Or skip the browser setup:

If your real goal is a clean image of a public web page rather than a pixel from your local desktop, ScreenshotNeo provides a website screenshot API. It is not a replacement for ImageGrab when you need an application window or an arbitrary desktop coordinate, but it avoids installing and operating a browser automation stack.

One GET request returns a PNG, JPEG, WebP or PDF. The API accepts a URL and can remove cookie-consent banners, newsletter popups and chat widgets before capture. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed; response headers identify the page verdict and billing status.

Python:

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)

cURL:

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

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 API documentation for request options. It also offers an MCP server with take_screenshot, get_page_info and capture_pdf tools for Claude, Cursor and other MCP clients. Every plan includes the features; the free plan provides 1,000 shots per month without a card, and paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account to try it.

Practical checklist

  1. Install Pillow and import ImageGrab from PIL.
  2. Capture once with ImageGrab.grab(), optionally supplying bbox, all_screens or a supported scaling option.
  3. Print image.mode and image.size while developing.
  4. Use getpixel((x, y)) with coordinates local to the returned image.
  5. Convert to RGB for a guaranteed three-channel tuple, or preserve RGBA when alpha matters.
  6. Validate bounds and test on each target operating system and display scale.

Frequently Asked Questions

Does ImageGrab.grab() automatically sample the color under the mouse pointer?

No. It captures an image; your code must supply the pixel coordinate to getpixel(). Pointer tracking, if required, is a separate operating-system or GUI-toolkit task.

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

Can ScreenshotNeo capture a local desktop window like ImageGrab?

No. ScreenshotNeo accepts a web URL and captures that page. Use ImageGrab for local desktop content and ScreenshotNeo when the input is a webpage.

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.