Image Resizing Automation with Python PIL

Image Resizing Automation with Python PIL

Image Resizing Automation with Python PIL

 

Introduction

Automating repetitive image tasks can save hours of manual work, especially for photographers, web developers, or marketing teams that manage large media libraries. In this tutorial, we’ll build an automation script using the Python Pillow (PIL) library to batch-resize images inside a folder while maintaining their aspect ratios. By the end of this article, you’ll have a working Python script that can be integrated into larger automation pipelines or used as a standalone utility.

1. Setting Up the Environment

Pillow is the actively maintained fork of the original PIL (Python Imaging Library). It provides an easy-to-use API for image processing.

bash
pip install pillow

Now import the necessary modules in Python:

python
from PIL import Image
import os

We’ll use os to navigate through folders and Image from PIL to handle resizing. Once the environment is set up, we can start coding the resizing logic.

2. Understanding Aspect Ratio and Resize Logic

Maintaining the aspect ratio ensures that images don’t appear stretched or distorted after resizing. The key is to scale both width and height proportionally.

Here’s how we can calculate the resize dimensions dynamically:

python
def resize_image(image_path, output_folder, max_width, max_height):
img = Image.open(image_path)
img.thumbnail((max_width, max_height)) # preserves aspect ratio automatically

base_name = os.path.basename(image_path)
output_path = os.path.join(output_folder, base_name)
img.save(output_path)
print(f"Resized and saved: {output_path}")

Using thumbnail() instead of resize() automatically keeps the aspect ratio intact. This prevents distortion and ensures high-quality downscaled images.

3. Automating Batch Processing

Once we have the core resize function, the next logical step is to loop over all images inside a folder and process them automatically. Let’s implement that:

python
def batch_resize(input_folder, output_folder, max_width, max_height):
if not os.path.exists(output_folder):
os.makedirs(output_folder)

for file_name in os.listdir(input_folder):
if file_name.lower().endswith((".jpg", ".jpeg", ".png")):
image_path = os.path.join(input_folder, file_name)
resize_image(image_path, output_folder, max_width, max_height)

# Example usage:
# batch_resize('images/input', 'images/resized', 800, 600)

This code iterates through all JPG and PNG files in the given input_folder and resizes them into output_folder. It’s perfect for large image sets or continuous integration workflows (e.g., resizing before deployment).

4. Adding Error Handling and Logging

Production-level scripts should handle unexpected issues gracefully—such as corrupt images or read/write errors. We can wrap our logic in try-except blocks and add logging support:

python
import logging

logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

def safe_resize(image_path, output_folder, max_width, max_height):
try:
resize_image(image_path, output_folder, max_width, max_height)
logging.info(f"Processed {image_path}")
except Exception as e:
logging.error(f"Failed to process {image_path}: {e}")

def batch_resize_safe(input_folder, output_folder, max_width, max_height):
if not os.path.exists(output_folder):
os.makedirs(output_folder)
for file_name in os.listdir(input_folder):
if file_name.lower().endswith((".jpg", ".jpeg", ".png")):
image_path = os.path.join(input_folder, file_name)
safe_resize(image_path, output_folder, max_width, max_height)

This makes the script more reliable for long-running tasks—ensuring one bad file doesn’t break the entire batch process.

5. Performance Considerations and Extensions

When dealing with hundreds of images, performance becomes important. While Pillow is optimized, you can further speed things up with concurrency.

python
from concurrent.futures import ThreadPoolExecutor

def batch_resize_concurrent(input_folder, output_folder, max_width, max_height):
if not os.path.exists(output_folder):
os.makedirs(output_folder)

image_files = [f for f in os.listdir(input_folder) if f.lower().endswith((".jpg", ".jpeg", ".png"))]

with ThreadPoolExecutor(max_workers=4) as executor:
for file_name in image_files:
image_path = os.path.join(input_folder, file_name)
executor.submit(safe_resize, image_path, output_folder, max_width, max_height)

Using multithreading improves performance for I/O-bound operations like reading and writing images. Adjust max_workers based on your system’s CPU and I/O capabilities.

6. Final Thoughts and Automation Ideas

With this simple yet powerful script, you’ve automated one of the most common tasks in image management. You can easily integrate this tool with file watchers (like watchdog) to automatically resize new images added to a folder, or as part of a larger deployment pipeline to ensure consistent image quality across platforms. This automation not only saves time but also ensures uniformity and optimized loading performance for websites or mobile apps.

In summary: Pillow provides a clean, Pythonic interface to handle complex image operations. When combined with practical automation logic—as we did here—it becomes a robust solution for real-world workflows.

 

Useful links: