Python Batch Image Collage Script: Natural Sorting, Centered Last Row, and High-Quality Output with Pillow

This project uses Python and Pillow to build batch image collages and solves common multi-image composition issues such as incorrect file ordering, awkward left-aligned final rows, and degraded output quality. The script supports grid layouts, automatic centering for the last row, and DPI preservation. Keywords: Python image collage, Pillow, natural sorting.

Technical Specification Snapshot

Parameter Description
Language Python
Image processing library Pillow
Sorting strategy Regex-based natural sorting
Input formats JPG, JPEG, PNG, BMP, WEBP, TIFF
Output strategy Inherits the first image format and can preserve DPI
Layout capabilities Grid layout, grouped output, centered last row
Quality control LANCZOS resampling, JPG quality 100
Stars Not provided in the original article
License Not provided in the original article
Core dependencies Pillow, os, re

This script delivers stable output for batch image collage workflows

Many online collage tools work well for one-off tasks, but once you move into research figures, presentation visuals, or asset archiving, three problems become obvious: weak batch-processing support, confusing filename ordering, and visually unbalanced final rows.

The real value of this script is that it turns “making a collage” into a repeatable engineering workflow. It uses the first image as the baseline for size, mode, and output parameters, then completes grouping, layout, and saving based on configuration. That makes it well suited for local automation.

The core configuration block defines collage behavior

from PIL import Image
import os
import re

# ==================== Manual configuration ====================
TARGET_PATH = r"your image folder path"  # Image directory
IMAGES_PER_GROUP = 3                # Number of images to process per group
ROWS = 2                            # Number of rows
COLS = 2                            # Number of columns
BOTTOM_PADDING = 0                  # Bottom padding in pixels
# ===================================================

This configuration block defines the input path, group size, and final canvas layout.

The natural sorting mechanism prevents incorrect orders like 1, 10, 2

Default string sorting compares characters one by one, so 10.jpg is placed before 2.jpg. That behavior is highly undesirable for numbered experiment figures, screenshot sequences, and result images.

Natural sorting works by splitting numeric and text segments in a filename with a regular expression, then converting numeric segments into integers for comparison. The resulting order matches how humans expect numbered files to appear.

The natural sort function is the foundation of correct file ordering

def natural_sort_key(s):
    """Natural sorting: ensure 1.jpg, 2.jpg, 10.jpg are ordered numerically"""
    return [
        int(text) if text.isdigit() else text.lower()  # Convert numeric parts to integers and text parts to lowercase
        for text in re.split(r'([0-9]+)', s)
    ]

image_files = sorted(raw_files, key=natural_sort_key)  # Sort in natural order

This code ensures that batch-loaded images follow human-readable order.

Automatically centering the last row significantly improves visual balance

Many collage scripts place an incomplete final row directly against the left edge, which leaves a large blank area on the right. For academic figures or presentation documents, that makes the output look rough and unfinished.

This script first splits images into rows based on the column count, then checks whether the current row is full. If the row is incomplete, it calculates an offset from the row width and canvas width so the entire row starts from a horizontally centered position.

Offset calculation is the key to centering the final row

rows = []
for i in range(0, len(group_images), COLS):
    rows.append(group_images[i:i + COLS])  # Split into multiple rows by column count

for row_idx, row_images in enumerate(rows):
    y = row_idx * img_height
    num_in_row = len(row_images)

    if num_in_row == COLS:
        offset_x = 0  # Start from the left edge for a full row
    else:
        total_width = num_in_row * img_width
        offset_x = (canvas_width - total_width) // 2  # Center horizontally for an incomplete row

This code solves the awkward appearance of incomplete final rows and stands out as the script’s most distinctive feature.

Unifying size and color mode keeps canvas composition stable

In real-world workflows, images often have inconsistent dimensions and may mix modes such as RGB and RGBA. If you paste them directly, you may get misalignment, errors, or unexpected background behavior.

The script uses the first image as the reference. If later images do not match the target size, it resizes them with high-quality resampling. If their mode differs, it converts the mode first. This strategy significantly reduces batch-processing failures.

Image normalization ensures consistent output

with Image.open(img_path) as img:
    if img.size != (img_width, img_height):
        print(f"Warning: {os.path.basename(img_path)} has a different size and has been adjusted")
        img = img.resize((img_width, img_height), Image.Resampling.LANCZOS)  # High-quality scaling

    if img.mode != img_mode:
        img = img.convert(img_mode)  # Normalize color mode

    canvas.paste(img, (x, y))  # Paste into the target position

This code normalizes heterogeneous images into a consistent input format before reliable composition.

The high-quality save strategy works well for print-ready papers and archived reports

After the collage is complete, save parameters directly affect the final appearance. If you keep the default JPG compression settings, fine details are often lost. If the source image includes DPI metadata but you do not preserve it, printed dimensions may also become inaccurate.

For JPG and JPEG output, the script enables quality=100 and subsampling=0 to minimize compression loss. If the input image contains DPI metadata, the script keeps that original DPI so physical dimensions remain stable in print and layout workflows.

Save parameters control final image quality

save_params = {}

if input_extension.lower() in ['.jpg', '.jpeg']:
    save_params['quality'] = 100      # Highest-quality JPG output
    save_params['subsampling'] = 0    # Disable chroma subsampling

if img_dpi:
    save_params['dpi'] = img_dpi      # Preserve original DPI

canvas.save(save_path, **save_params)

This code preserves image clarity and print-related properties as much as possible during export.

The execution flow is straightforward and works well for local automation

Before running the script, you only need to install Pillow and change TARGET_PATH to your image directory. The script automatically filters common image formats and skips previously generated collage_final files to avoid duplicate processing.

If the image count exceeds the per-group limit, the script automatically splits the input into multiple collage outputs named collage_final_1, collage_final_2, and so on. This makes it suitable for one-time processing of large image collections.

Installation and execution are intentionally simple

pip install pillow
python image_collage.py

These two commands install the dependency and run the script.

This approach works well as an extensible foundation for batch image collage automation

From an engineering perspective, this script does not aim to provide a complex UI. Instead, it focuses on four high-value capabilities: correct ordering, natural layout, stable compatibility, and controllable output quality. For developers, those qualities matter more than simply being able to “make a collage.”

If you want to extend it further, you can add gap control, background color configuration, text watermarks, automatic EXIF orientation handling, or package it as a CLI tool or desktop application. Even in its current form, it is already sufficient for most research, reporting, and content production scenarios.

WeChat share prompt

AI Visual Insight: This animated image shows a WeChat sharing prompt on a blog page. It illustrates platform interaction guidance rather than a technical architecture diagram, and it does not include script execution flow, layout algorithm details, or image processing results.

FAQ

1. Why does the script use the first image as the baseline?

Because the first image provides a consistent baseline for dimensions, color mode, output format, and DPI. That gives every subsequent image a clear normalization target before composition and reduces misalignment and mode-compatibility issues.

2. Why use Image.Resampling.LANCZOS?

It is one of Pillow’s high-quality scaling algorithms and is well suited for image resizing. If you are using an older Pillow version, you can replace it with Image.LANCZOS for compatibility.

3. Where should I modify the code if I want spacing between images?

You can add horizontal and vertical spacing when calculating canvas_width and canvas_height, then include a gap value in the x and y coordinates. The overall structure does not need to be rewritten; you only need to extend the layout formula.

AI Readability Summary

This article refactors a Python image collage script built with Pillow and explains the core implementation behind natural sorting, grid layout, automatic centering of the final row, size and color mode normalization, DPI preservation, and high-quality JPG export. It is especially useful for paper layout, report graphics, and batch asset-processing workflows.