How to Resize Images with Python PIL Image.open
Resize images with Pillow using exact dimensions, aspect-ratio-safe methods, quality filters, EXIF handling, and runnable Python examples.
Use Pillow’s Image.open() to open the source, then choose the resizing method that matches your layout requirement:
image.resize((width, height), Image.Resampling.LANCZOS)for exact dimensions.image.thumbnail((max_width, max_height))to fit within bounds while preserving aspect ratio.ImageOps.contain,cover,fit, orpadwhen you need a specific fit, crop, or padding behavior.
The basic exact-size example is:
from PIL import Image
with Image.open("input.jpg") as image:
resized = image.resize((800, 600), Image.Resampling.LANCZOS)
resized.save("output.jpg")
resize() uses a (width, height) tuple and returns a resized copy. Pillow documents LANCZOS as a high-quality resampling choice; BICUBIC is the documented default for typical image modes. See the Pillow Image reference.
Install Pillow
python -m pip install Pillow
Check the installation:
python -c "from PIL import Image; print(Image.__version__)"
Resize to exact width and height
Pass the target width first and height second. If the source and target aspect ratios differ, the image will be stretched or squashed.
from pathlib import Path
from PIL import Image
source = Path("input.jpg")
target = Path("output.jpg")
with Image.open(source) as image:
resized = image.resize((800, 600), resample=Image.Resampling.LANCZOS)
resized.save(target, quality=90, optimize=True)
Use exact resizing for fixed canvases such as sprite sheets, generated thumbnails with a known crop elsewhere, or an API contract that requires precise pixel dimensions. For photographs and general downscaling, LANCZOS is a quality-oriented example. BICUBIC or BILINEAR can be reasonable alternatives when processing speed matters more.
Preserve aspect ratio
Fit inside maximum bounds with thumbnail()
from PIL import Image
with Image.open("input.jpg") as image:
image.thumbnail((1200, 1200), resample=Image.Resampling.LANCZOS)
image.save("thumbnail.jpg", quality=90)
thumbnail() preserves the aspect ratio and ensures neither dimension exceeds the supplied bounds. It modifies the image object in place, so copy the image first if you still need the original. Pillow’s documentation describes this mutation explicitly in the thumbnail reference.
Calculate a proportional size yourself
from PIL import Image
def resize_to_width(image, width):
ratio = width / image.width
height = round(image.height * ratio)
return image.resize((width, height), Image.Resampling.LANCZOS)
with Image.open("input.jpg") as image:
resized = resize_to_width(image, 800)
resized.save("width-800.jpg", quality=90)
This approach is useful when the output width is fixed and the calculated height should be visible in your own code.
Choose contain, cover, crop, or padding
For a fixed rectangle without distortion, use PIL.ImageOps. The behaviors are summarized in the Pillow ImageOps reference.
| Goal | Method | Behavior |
|---|---|---|
| Fit without cropping | ImageOps.contain(image, size) |
Preserves the full image; one dimension can be smaller than the box. |
| Fill the box | ImageOps.cover(image, size) |
Preserves aspect ratio; parts outside the target ratio can extend beyond the box. |
| Exact dimensions with a crop | ImageOps.fit(image, size) |
Resizes and crops to the requested dimensions. |
| Exact dimensions with borders | ImageOps.pad(image, size, color=...) |
Resizes and adds background space. |
from PIL import Image, ImageOps
with Image.open("input.jpg") as image:
contained = ImageOps.contain(image, (800, 600))
contained.save("contain.jpg", quality=90)
with Image.open("input.jpg") as image:
covered = ImageOps.cover(image, (800, 600))
covered.save("cover.jpg", quality=90)
with Image.open("input.jpg") as image:
cropped = ImageOps.fit(image, (800, 600), method=Image.Resampling.LANCZOS)
cropped.save("fit.jpg", quality=90)
with Image.open("input.jpg") as image:
padded = ImageOps.pad(image, (800, 600), color="white")
padded.save("pad.jpg", quality=90)
Handle EXIF orientation before resizing
JPEG and TIFF files can contain EXIF orientation instructions. Apply those instructions to the pixels before measuring or resizing:
from PIL import Image, ImageOps
with Image.open("camera-photo.jpg") as image:
oriented = ImageOps.exif_transpose(image)
resized = oriented.resize((1200, 800), Image.Resampling.LANCZOS)
resized.save("camera-photo-resized.jpg", quality=90)
See Pillow’s exif_transpose documentation.
Resampling filters
| Filter | Typical use |
|---|---|
NEAREST |
Fast nearest-pixel selection; useful when discrete values must not be blended, such as pixel art or categorical masks. |
BILINEAR |
Faster interpolation with less detail than higher-quality filters. |
BICUBIC |
Cubic interpolation and a practical speed/quality compromise. |
LANCZOS |
Quality-oriented downscaling, generally slower than the simpler filters. |
Pillow’s filter descriptions and comparison are qualitative, not universal timing benchmarks. For mode 1 and palette mode P, Pillow forces NEAREST even if another filter is requested. Convert deliberately when smooth interpolation is required:
from PIL import Image
with Image.open("palette.png") as image:
rgb = image.convert("RGB")
resized = rgb.resize((800, 600), Image.Resampling.LANCZOS)
resized.save("palette-resized.jpg", quality=90)
Transparency, color mode, and output format
Keep an alpha channel when writing PNG or WebP:
from PIL import Image
with Image.open("logo.png") as image:
rgba = image.convert("RGBA")
resized = rgba.resize((400, 400), Image.Resampling.LANCZOS)
resized.save("logo-small.png")
JPEG does not support transparency. Flatten onto a chosen background before saving as JPEG:
from PIL import Image
with Image.open("logo.png") as image:
rgba = image.convert("RGBA")
background = Image.new("RGB", rgba.size, "white")
background.paste(rgba, mask=rgba.getchannel("A"))
resized = background.resize((400, 400), Image.Resampling.LANCZOS)
resized.save("logo-small.jpg", quality=90)
PNG is suitable for lossless graphics and transparency; JPEG is commonly used for photographs; WebP can be written when your deployment supports it. Choose the format based on the consumer’s requirements rather than resizing alone.
Resize a batch of files
from pathlib import Path
from PIL import Image, ImageOps
input_dir = Path("images")
output_dir = Path("resized")
output_dir.mkdir(exist_ok=True)
for source in input_dir.glob("*.*"):
try:
with Image.open(source) as image:
oriented = ImageOps.exif_transpose(image)
oriented.thumbnail((1600, 1600), Image.Resampling.LANCZOS)
destination = output_dir / f"{source.stem}.jpg"
oriented.convert("RGB").save(destination, quality=88, optimize=True)
except (OSError, ValueError) as error:
print(f"Skipping {source}: {error}")
Performance, reliability, and cost notes
- Downscale as early as practical to reduce memory and later processing work.
thumbnail()avoids enlarging an image beyond its maximum bounds; useresize()when enlargement is intentional.- LANCZOS trades processing speed for quality. Measure your own workload before selecting a faster filter.
- Use the context-manager form of
Image.open()so file handles are released after each image. - For untrusted uploads, validate file type and dimensions before processing and handle
OSErrorcleanly. - Resizing locally has no API charge; the cost is CPU, memory, storage, and any infrastructure used to run the script.
Troubleshooting
ModuleNotFoundError: No module named 'PIL'
Install the Pillow package in the same Python environment that runs the script: python -m pip install Pillow. The package is named Pillow, while the import namespace is PIL.
The result has the wrong shape
Check that the tuple is (width, height). If the requested ratio differs from the source, use thumbnail(), contain, cover, fit, or pad instead of direct resize().
The original image was unexpectedly changed
thumbnail() mutates its image object. Open the file again or call image.copy() before making the thumbnail. resize() returns a separate image.
The photo is rotated after processing
Apply ImageOps.exif_transpose() before reading dimensions and resizing. The camera may have stored the orientation as metadata rather than rotating pixels.
Transparency disappeared
Keep the image in RGBA and save to PNG or a transparency-capable format. If saving JPEG, composite the alpha channel onto a background first.
Edges look jagged or details are soft
Try Image.Resampling.LANCZOS for photographic downscaling. For pixel art or masks, use NEAREST to avoid blended values.
The output file is unexpectedly large
Choose an appropriate format and, for JPEG, set an explicit quality such as 85–90. For PNG, reduce unnecessary color information when your image permits it.
Or skip the browser setup
If the image you need starts as a webpage screenshot, ScreenshotNeo returns a PNG, JPEG, WebP, or PDF from one GET request. It accepts cookie banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers identify the page verdict and billing status.
Call the API directly; see the ScreenshotNeo API documentation for all options.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
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 data = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', data));
ScreenshotNeo also has full-page capture with lazy images loaded, CSS-selector element capture, custom CSS and JavaScript, device presets, retina scale, waits, request blocking, headers and cookies, caching, signed links, asynchronous jobs, bulk capture, PDF output, and an MCP server with take_screenshot, get_page_info, and capture_pdf for AI agents.
After downloading a screenshot, resize it with the Pillow examples above. ScreenshotNeo offers 1,000 screenshots a month free with no card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.
FAQ
Does Image.open() resize an image?
No. It opens and identifies the image. Call resize(), thumbnail(), or an ImageOps function on the returned object.
Which method preserves aspect ratio?
thumbnail(), contain, cover, fit, and pad preserve it. Direct resize() preserves it only when your target ratio matches the source ratio.
Can I resize without saving a file?
Yes. Keep the returned Pillow image in memory, pass it to another function, or write it to a BytesIO object instead of a filesystem path.
Should I use LANCZOS for every image?
No. It is a strong general photographic downscale choice, while NEAREST is appropriate for discrete masks and pixel art, and BILINEAR or BICUBIC may be faster for large batches.
Why does my palette image ignore the selected filter?
Pillow forces NEAREST for mode 1 and palette mode P. Convert to an appropriate color mode before resizing if smooth interpolation is required.


