How to Resize Images with Pillow in Python

Published on: May 12, 2026
Reading time: 9 minutes

Resizing images is one of the most common and fundamental tasks in visual data processing. Whether you need to optimize website loading speed, prepare a dataset for machine learning, or automate thumbnail generation, knowing how to manipulate image dimensions programmatically saves hours of manual editing. In the Python ecosystem, the Pillow library is the industry-standard choice for anyone who wants to resize images efficiently and at scale. Pillow is a modern fork of the original PIL (Python Imaging Library) and brings a clean, approachable API to even complex image operations. According to the official Pillow documentation, it supports formats including JPEG, PNG, BMP, GIF, and WebP.

Why Use Python and Pillow for Image Resizing?

The main advantage of using scripts over graphical editors like Photoshop is scalability. With a few lines of code you can process thousands of photos in seconds. If you already understand programming logic with Python, you will find that manipulating image objects feels as intuitive as working with lists or dictionaries. Pillow is also lightweight and has a large, active community maintaining it.

Python automation through Pillow is widely used in e-commerce platforms that auto-generate product thumbnails, in data science pipelines that standardize image dimensions before model training, and in content management systems that convert uploaded photos to web-optimized sizes. All of these workflows rely on the same library and the same basic patterns covered in this guide.

Installing Pillow

Pillow is not included in Python’s standard library, so you install it via pip. If you are using a dedicated Python virtual environment, activate it first before running this command:

Bash
pip install Pillow

Once installed, verify it works by importing the Image class in a Python script. If no error appears, you are ready to start processing images programmatically.

Opening an Image and Reading Its Properties

The first step in any image processing workflow is loading the file into memory. Pillow uses the Image class to represent a graphic file as a Python object. It is good practice to check the original dimensions before applying any transformation, and to catch path errors that could arise if the file does not exist:

Python
from PIL import Image

# Open the image file
image = Image.open("photo.jpg")

# Read basic properties
width, height = image.size
print(f"Original dimensions: {width}x{height}")
print(f"Format: {image.format}")
print(f"Color mode: {image.mode}")

Managing file paths correctly here is essential. A common beginner issue is a FileNotFoundError in Python, which occurs when the script runs from a different directory than the one where the image is stored.

Basic Resizing with the resize Method

The most direct way to change image dimensions is the resize() method. It accepts a tuple with the target width and height and returns a new image object, leaving the original untouched:

Python
from PIL import Image

image = Image.open("input.png")

# Set the new size
new_size = (800, 600)
resized_image = image.resize(new_size)

# Save the result
resized_image.save("output_800x600.png")

A plain resize() call without considering aspect ratio can distort the image by stretching it horizontally or vertically. To avoid this, you need to calculate the correct proportional height for any given target width, which is covered in the next section.

Maintaining Aspect Ratio

To resize without distortion, calculate the new height based on a fixed target width and the original proportions. The LANCZOS resampling filter is the highest-quality option available and is strongly recommended for photographs:

Python
target_width = 500
w_percent = target_width / float(image.size[0])
new_height = int(float(image.size[1]) * float(w_percent))

resized = image.resize((target_width, new_height), Image.Resampling.LANCZOS)
resized.save("output_proportional.jpg")

LANCZOS applies a mathematically complex smoothing algorithm that prevents the jagged edges and blurring that simpler resampling methods produce on reduction.

Using thumbnail for Smart Resizing

Pillow offers a more convenient alternative called thumbnail(). It automatically preserves the aspect ratio and, critically, will never enlarge an image beyond its original dimensions, which protects image sharpness:

Python
image = Image.open("landscape.jpg")

# Fits the image inside a 400x400 box while preserving aspect ratio
image.thumbnail((400, 400))
image.save("thumbnail.jpg")

Unlike resize(), thumbnail() modifies the object in place rather than returning a new one. This makes it slightly more memory-efficient for batch operations where you process and discard images sequentially.

Batch Resizing an Entire Folder

The real power of Pillow appears when applied at scale. Combining it with the Python os module, you can iterate over every image in a folder and process them all automatically. This is the core pattern behind many Python automation workflows in media production:

Python
import os
from PIL import Image

source_folder = "original_photos"
output_folder = "optimized_photos"

if not os.path.exists(output_folder):
    os.makedirs(output_folder)

for filename in os.listdir(source_folder):
    if filename.endswith((".jpg", ".png", ".jpeg")):
        img = Image.open(os.path.join(source_folder, filename))
        img.thumbnail((1024, 1024))
        img.save(os.path.join(output_folder, filename), optimize=True, quality=85)

The optimize=True and quality=85 parameters in the save() call reduce disk file size significantly with no perceptible loss of visual quality, which is a technique fundamental to web performance optimization.

Handling Errors During Batch Processing

When processing folders with many files, some may be corrupted, locked, or in unsupported formats. Without error handling, the script will stop at the first problem and leave hundreds of files unprocessed. Wrapping the core logic in try and except in Python blocks makes the script resilient:

Python
from PIL import Image, UnidentifiedImageError

try:
    img = Image.open("possibly_corrupt.jpg")
    img.thumbnail((800, 800))
    img.save("output.jpg")
except UnidentifiedImageError:
    print("File is not a valid image and was skipped.")
except Exception as e:
    print(f"Unexpected error: {e}")

Converting Between Formats

Pillow makes format conversion straightforward. Simply open an image in one format and save it with a different file extension. One important caveat is that JPEG does not support transparency. If you convert a PNG with an alpha (transparency) channel to JPEG, you must first paste it onto a solid background to avoid a black fill or a runtime error:

Python
from PIL import Image

# Safe PNG to JPEG conversion
img = Image.open("transparent.png").convert("RGBA")
background = Image.new("RGB", img.size, (255, 255, 255))
background.paste(img, mask=img.split()[3])
background.save("converted.jpg", quality=90)

Complete Project Code

Here is the complete professional batch resizer that processes every image in a folder while maintaining aspect ratio, applying LANCZOS-quality resampling, handling errors gracefully, and saving optimized versions to a separate output directory:

Python
import os
from PIL import Image, UnidentifiedImageError

def resize_images(input_dir, output_dir, max_width):
    """Resize all images in a directory while preserving aspect ratio."""
    if not os.path.exists(output_dir):
        os.makedirs(output_dir)
        print(f"Created output directory: {output_dir}")

    for filename in os.listdir(input_dir):
        input_path = os.path.join(input_dir, filename)

        try:
            with Image.open(input_path) as img:
                print(f"Processing: {filename}...")

                original_width, original_height = img.size
                ratio = max_width / float(original_width)
                new_height = int(float(original_height) * ratio)

                resized = img.resize((max_width, new_height), Image.Resampling.LANCZOS)
                output_path = os.path.join(output_dir, filename)
                resized.save(output_path, optimize=True, quality=90)

        except UnidentifiedImageError:
            print(f"Skipped: {filename} is not a valid image.")
        except Exception as e:
            print(f"Error processing {filename}: {e}")

if __name__ == "__main__":
    INPUT_FOLDER = "my_photos"
    OUTPUT_FOLDER = "my_photos_web"
    MAX_WIDTH = 1200

    if not os.path.exists(INPUT_FOLDER):
        os.makedirs(INPUT_FOLDER)
        print(f"Place your images inside the '{INPUT_FOLDER}' folder and run the script again.")
    else:
        resize_images(INPUT_FOLDER, OUTPUT_FOLDER, MAX_WIDTH)
        print("All done!")

Resizing Images from a URL

Pillow cannot download images from URLs by itself. You need the Python Requests library to fetch the image content and the built-in io.BytesIO module to convert it into a file-like object that Pillow can open directly, without saving a temporary file to disk.

Performance Considerations for Large-Scale Processing

Image processing is CPU and RAM intensive. If you need to resize tens of thousands of high-resolution photos, Python’s multiprocessing module lets you distribute the workload across all available CPU cores, which can reduce total processing time dramatically. The NumPy in Python guide covers related mathematical array operations that are often combined with Pillow in advanced machine learning data pipelines.

Frequently Asked Questions

Can Pillow resize animated GIFs?

Yes, but the basic resize method only affects the first frame. To resize a GIF while preserving its animation, you need to iterate through every frame, resize each one individually, and reassemble the sequence before saving.

What is the difference between LANCZOS and NEAREST resampling?

LANCZOS is a complex algorithm that produces the highest quality output, ideal for photographs. NEAREST is the fastest but produces jagged results; it is useful for pixel art where you want to preserve exact pixel boundaries.

Does save() overwrite existing files?

Yes. If you provide the same file path as the original, Python overwrites it without warning. Always save to a dedicated output folder to protect your originals.

How do I resize a PNG without losing its transparency?

Pillow automatically handles the alpha channel when opening RGBA PNG files. Transparency is preserved during resizing as long as you save back to a format that supports it (PNG or WebP). Never save a transparent PNG directly as JPEG without compositing it on a background first.

What quality value gives the best size-to-quality tradeoff for JPEG?

Values between 70 and 85 generally offer the best balance. The difference in file size between quality 85 and 95 is significant, while the visual difference is barely perceptible to the naked eye.

Why did my image turn black when converting from PNG to JPEG?

JPEG does not support transparency. The transparent areas of the PNG fill with black (or generate an error) when converted directly. Always paste the PNG onto a white or colored RGB background before saving as JPEG, as shown in the conversion example above.

Is Pillow the fastest image processing library for Python?

For general use and ease of development, yes. For extreme throughput in production systems, OpenCV (combined with NumPy) can offer higher speeds on low-level pixel matrix operations, but requires significantly more complex code for basic tasks like resizing.

Can I add a watermark while resizing?

Yes. After resizing, open a watermark image and use Image.paste() with a mask to composite it onto the resized photo before saving. This is a common production pattern for e-commerce and photography platforms.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    Database
    Foto do Leandro Hirt

    How to Integrate Python with SQL Server: Step-by-Step Guide

    Learn how to integrate Python with SQL Server step by step. This guide covers pyodbc setup, connection strings, CRUD operations,

    Ler mais

    Tempo de leitura: 9 minutos
    12/05/2026
    Automation and Scripts
    Foto do Leandro Hirt

    Build a News Scraper to Telegram with Python

    Learn how to build a Python web scraper that extracts news headlines and sends them to Telegram automatically. Complete guide

    Ler mais

    Tempo de leitura: 9 minutos
    12/05/2026
    Automation and Scripts
    Foto do Leandro Hirt

    How to Access and Edit Google Sheets with Python in Minutes

    Learn how to access and edit Google Sheets with Python using gspread. This complete guide covers API setup, credential configuration,

    Ler mais

    Tempo de leitura: 9 minutos
    12/05/2026
    Automation and Scripts
    Foto do Leandro Hirt

    How to Extract Text from PDFs with Python in Minutes

    Learn how to extract text from PDFs with Python using PyPDF2 and pdfplumber. This complete guide covers single-page reading, table

    Ler mais

    Tempo de leitura: 9 minutos
    12/05/2026
    Automation and Scripts
    Foto do Leandro Hirt

    Tweepy: How to Automate Twitter Posts with Python

    Learn how to automate Twitter posts with Python using Tweepy. This complete guide covers API setup, authentication, scheduling tweets, sending

    Ler mais

    Tempo de leitura: 8 minutos
    12/05/2026
    Automation and Scripts
    Foto do Leandro Hirt

    How to Download YouTube Videos with Python in 5 Minutes

    Learn how to download YouTube videos with Python in 5 minutes using pytube. This guide covers resolution selection, audio-only downloads,

    Ler mais

    Tempo de leitura: 8 minutos
    12/05/2026