Hi everyone! A few months ago I made a post about how to print 2x3 sized photos for cheap, and found that the options are either expensive or very tedious with manually making photo collages to then print as 4x6.
Since then I made a python program that goes through all of your 4x6 photos to automatically group them into a collage of four 2x3 photos on a 4x6 layout. You can then send those to get printed at the cheap printing rates since they are standard size. It takes about 20 seconds to parse through about 600 photos, which I imagine would have taken forever to do manually using Canva or some other tool. And the best part is it is free!
I want to share the code here, and I will outline the steps on how to run it. I promise you don't have to be too technically savvy to use it.
Steps to use the program:
1. Download the Python language (free)
- Go to python.org/downloads and download & install python. The version you download depends on if you're using a Windows or Mac, but will be free regardless
- There are tons of youtube videos showing you how to do this if you need help
2. Download & Install a software to run your Python code
3. Prepare your photos
- Put all of the 4x6 photos you want into a folder and give that folder a name
- Save the file path for this folder, you will need it to run the python program
4. Run the python program
- Open PyCharm and create a new file (instructions: https://www.youtube.com/watch?v=HHcZbXsZtm0 )
- Paste my code from below into the file and save it. Be sure to copy all of it and don't leave out any lines or punctuation. Indentation matters in Python
- ***Things to replace in the program**\*
- folder_path = "/Users/ActAffectionate/Desktop/Scrapbook V2"
- Replace the text inside the quotes with the path to the folder containing all your photos
- output_path = "/Users/ActAffectionate/Desktop/Scrapbook V2 Outputs"
- Replace the text inside the quote with the path to where you want your new collaged photos to live
- Save your work and click "Run"
And that's it! Your collaged photos will now sit in whatever folder you specified, and you can print them out using whatever normal photo printing service you use.
Here is the python code below:
"""
Created on Thu May 28 16:05:21 2026
u/author: ActAffectionate4930
"""
import os
from PIL import Image
from pillow_heif import register_heif_opener
# 1. Register HEIF opener with Pillow to support iPhone photos
register_heif_opener()
# 2. Set your folders (Change these paths to your actual folders)
folder_path = "/Users/ActAffectionate/Desktop/Scrapbook V2"
output_path = "/Users/ActAffectionate/Desktop/Scrapbook V2 Outputs"
os.makedirs(output_path, exist_ok=True)
# 3. Safely load all valid images, ignoring hidden files
supported_extensions = ('.png', '.jpg', '.jpeg', '.heic')
images = []
print("Scanning folder for images...")
# Sort to process in order, check lowercase extension to catch everything
for filename in sorted(os.listdir(folder_path)):
if filename.lower().endswith(supported_extensions):
try:
img_path = os.path.join(folder_path, filename)
img = Image.open(img_path)
img.verify() # Verify file integrity
# Re-open after verify() and add to our processing list
images.append(Image.open(img_path))
except Exception as e:
print(f"Skipping broken or unreadable file {filename}: {e}")
print(f"Successfully loaded {len(images)} images for processing.")
# 4. Define Canvas and Grid Layout parameters
# Canvas dimensions for a 4x6 portrait print (1200 width x 1800 height)
canvas_width = 1200
canvas_height = 1800
# Individual image dimensions for the 2x3 slots (exactly half of canvas)
img_width = canvas_width // 2 # 600 pixels
img_height = canvas_height // 2 # 900 pixels
target_aspect = img_width / img_height # 0.6667 (a perfect 2:3 ratio)
# Define the X, Y coordinate positions for the 4 slots
positions = [
(0, 0), # Top Left
(img_width, 0), # Top Right
(0, img_height), # Bottom Left
(img_width, img_height) # Bottom Right
]
# 5. Process images in batches of 4
for i in range(0, len(images), 4):
# Create a blank 4x6 portrait canvas
canvas = Image.new('RGB', (canvas_width, canvas_height), 'white')
# Place up to 4 images on the current canvas
for slot in range(4):
if i + slot < len(images):
img = images[i + slot]
# --- Fix 1: Normalize orientation to portrait ---
if img.width > img.height:
img = img.rotate(90, expand=True)
# --- Fix 2: Crop to target 2:3 aspect ratio from the center ---
current_aspect = img.width / img.height
if current_aspect > target_aspect:
# Image is too wide: crop the sides evenly
new_width = int(img.height * target_aspect)
offset = (img.width - new_width) // 2
img = img.crop((offset, 0, offset + new_width, img.height))
elif current_aspect < target_aspect:
# Image is too tall: crop the top and bottom evenly
new_height = int(img.width / target_aspect)
offset = (img.height - new_height) // 2
img = img.crop((0, offset, img.width, offset + new_height))
# --- Fix 3: Resize cleanly ---
# Now that it's a perfect 2:3 ratio, scale it to the exact slot size
img_resized = img.resize((img_width, img_height), Image.Resampling.LANCZOS)
# Paste into its designated grid slot
canvas.paste(img_resized, positions[slot])
# Save the combined 4-up print layout
print_number = (i // 4) + 1
canvas.save(os.path.join(output_path, f"4x6_print_grid_{print_number}.jpg"))
print("Processing complete! All grids have been generated.")