ScreenshotNeo

BlogHow-to

How to Crop Images with Python

Crop a rectangular region with Pillow’s Image.crop(), understand its coordinate order, validate crop bounds, and save the result.

By the ScreenshotNeo team4 October 20265 min read

Use Pillow’s Image.crop() to select a rectangular region. Its box is (left, upper, right, lower), measured from the image’s upper-left corner. Install Pillow, choose the rectangle, and save the returned image:

python -m pip install Pillow
from PIL import Image

with Image.open("input.jpg") as im:
    cropped = im.crop((100, 50, 400, 250))
    cropped.save("cropped.jpg")

This selects x=100 through x=400 and y=50 through y=250, producing a 300 × 200 pixel result. The box values are boundary coordinates, not (x, y, width, height). Pillow’s tutorial explains the box order and upper-left origin in its image cropping guide; the Image.crop reference documents the method.

1. Crop from x, y, width, and height

If your input describes the crop by its origin and dimensions, convert it to Pillow’s boundary box by adding the width to x and the height to y:

from PIL import Image

x, y = 100, 50
crop_width, crop_height = 300, 200
box = (x, y, x + crop_width, y + crop_height)

with Image.open("input.jpg") as im:
    cropped = im.crop(box)
    cropped.save("cropped.jpg")

The origin (0, 0) is at the top-left. Moving right increases x; moving down increases y. For a crop beginning at the image’s top-left, use (0, 0, width, height).

2. Check dimensions and crop coordinates

For input image size (W, H) and crop box (left, upper, right, lower), the expected result size is (right - left, lower - upper). Validate the box before cropping when coordinates come from users, a database, or a detection model:

from PIL import Image

box = (100, 50, 400, 250)

with Image.open("input.jpg") as im:
    left, upper, right, lower = box
    width, height = im.size

    if not (0 <= left < right <= width):
        raise ValueError(f"Invalid horizontal crop for image width {width}: {box}")
    if not (0 <= upper < lower <= height):
        raise ValueError(f"Invalid vertical crop for image height {height}: {box}")

    cropped = im.crop(box)
    cropped.save("cropped.jpg")

This rejects empty, reversed, or out-of-image boxes rather than relying on version-specific boundary behavior. If your application intentionally allows a crop to extend beyond the image, check the Pillow version’s documented behavior and decide how to fill any added area before adopting that policy.

3. Save to the format you need

Pillow generally selects the output format from the filename extension when saving. Choose an extension that matches the desired format, and convert modes when the destination format does not support the source image’s mode. For example, JPEG does not preserve transparency; convert an RGBA or palette image to RGB if a JPEG is required:

from PIL import Image

with Image.open("input.png") as im:
    cropped = im.crop((100, 50, 400, 250))
    cropped.convert("RGB").save("cropped.jpg", quality=90)

For a PNG output that should retain transparency, save as PNG instead:

from PIL import Image

with Image.open("input.png") as im:
    im.crop((100, 50, 400, 250)).save("cropped.png")

4. Crop a batch of images

Apply the same box to each image, while opening files in a context manager so each input is closed after processing. Ensure the crop fits every image, or validate each image’s dimensions first:

from pathlib import Path
from PIL import Image

box = (100, 50, 400, 250)
source_dir = Path("images")
output_dir = Path("cropped")
output_dir.mkdir(exist_ok=True)

for path in source_dir.glob("*.jpg"):
    with Image.open(path) as im:
        left, upper, right, lower = box
        if right > im.width or lower > im.height:
            print(f"Skipping {path}: image is too small for {box}")
            continue
        im.crop(box).save(output_dir / path.name)

5. Fit an image to a target size

A crop selects a rectangle from the source. If your real requirement is a fixed output size or aspect ratio, Pillow’s ImageOps helpers may be a better fit. The ImageOps reference documents fit, contain, cover, and pad. In practical terms, choose based on whether the whole source must remain visible, whether the target must be filled exactly, and whether trimming or padding is acceptable. Check the reference for the helper’s parameters and resampling behavior before using it in a production pipeline.

6. Troubleshooting

Problem Likely cause Fix
ModuleNotFoundError: No module named 'PIL' Pillow is not installed in the Python environment running the script. Run python -m pip install Pillow using that same Python interpreter.
Wrong area or unexpected output dimensions The box was supplied as (x, y, width, height) or the origin was treated as bottom-left. Use (left, upper, right, lower), with coordinates measured from the top-left. Set right to x + width and lower to y + height.
Empty result or invalid crop Coordinates are reversed, equal, negative, or outside the intended bounds. Check left < right and upper < lower; compare the box with im.size.
Cannot save as JPEG The cropped image has a mode or transparency that JPEG cannot represent. Convert to RGB before saving, or choose PNG to preserve transparency.
File cannot be opened The path is wrong, the file is unreadable, or the file is not a supported image. Check the path and permissions, and handle OSError around image opening for files that may be damaged.

7. Performance, reliability, and cost

For a local image, Pillow performs the crop in your Python process and writes the result to your chosen destination; this workflow has no screenshot API request or per-crop service charge. Large source images still require decoding and memory, so process files one at a time, close them promptly, and avoid keeping many full-resolution images in memory. Validate dimensions and output format at the input boundary to make batch jobs more predictable.

Or skip the browser setup

If what you need is a screenshot of a webpage rather than a crop of an existing image, ScreenshotNeo returns a PNG, JPEG, WebP, or PDF from one GET request. See the API documentation.

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

ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before capture. Bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Sign up for the free plan.

FAQ

Does Pillow crop an image in place?

No. crop() returns a cropped image object. Save that returned object to create an output file.

Can I crop a circle with Image.crop()?

crop() selects a rectangle. For a circular appearance, crop a square and apply a separate mask.

How do I get the cropped image’s dimensions?

Read cropped.size; it returns a pair of width and height values.