Published: December 30, 2023
Imagine you have a bunch of photos with differing size that you'd like to combine into a collage
Our task in this post to combine these pictures into a collage like below, while preserving their aspect ratios.
Below code converts image files contained in a folder into a collage:
# Import necessary libraries
import os
from PIL import Image, ImageDraw
# Define folder that has images
directory = "./images"
# Get list of images
images = [i for i in os.listdir(directory) if i.endswith(".jpg") or i.endswith(".jpeg") or i.endswith(".png")]
# Define the size of output collage and the size of 1 image in the collage as (width, height) tuple
# Below values will determine how many images will be put horizontally vs. vertically
# E.g. in below case - 3600/600 = 6 and 1600/4 = 4 - The collage will be 6x4
expected_size_collage = (3600, 1600)
expected_size_image = (600, 400)
# Create collage canvas to paste the images
collage = Image.new("RGBA", expected_size_collage, color=(255,255,255,255))
# Loop through image files
file_count = 0
for h in range(0, expected_size_collage[1], expected_size_image[1]):
for w in range(0, expected_size_collage[0], expected_size_image[0]):
# Read image
file_name = images[file_count]
path = directory + "/" + file_name
image = Image.open(path).convert("RGBA")
# Get the original image width and height
image_width = image.size[0]
image_height = image.size[1]
# Get how the width and height should be
width_factor = image_width / expected_size_image[0]
height_factor = image_height / expected_size_image[1]
# If width and height factors are same, no cropping is needed
# If not, we need to crop image to the same ratio as expected_size_image
if width_factor != height_factor:
# Get the limiting factor
factor = min(width_factor, height_factor)
# Calculate the resulting image width and height
expected_width = round(factor * expected_size_image[0])
expected_height = round(factor * expected_size_image[1])
# Get minx, miny, maxx, and maxy coordinates of new image
start_width = round((image_width - expected_width) / 2)
start_height = round((image_height - expected_height) / 2)
end_width = expected_width + round((image_width - expected_width) / 2)
end_height = expected_height + round((image_height - expected_height) / 2)
# Crop the image
image = image.crop((start_width, start_height, end_width, end_height))
# Once the image is cropped, resize the image
# Image should have the aspect ratio as the expected_size_image so resize won't disturb the image
image = image.resize(expected_size_image)
# Copy image to collage canvas
collage.paste(image, (w, h))
file_count += 1
# Save collage
collage.save("collage.png")
Now let's go through the code step by step to see what it is doing.
# Import necessary libraries
import os
from PIL import Image, ImageDraw
# Define folder that has images
directory = "./images"
# Get list of images
images = [i for i in os.listdir(directory) if i.endswith(".jpg") or i.endswith(".jpeg") or i.endswith(".png")]
# Define the size of output collage and the size of 1 image in the collage as (width, height) tuple
# Below values will determine how many images will be put horizontally vs. vertically
# E.g. in below case - 3600/600 = 6 and 1600/4 = 4 - The collage will be 6x4
expected_size_collage = (3600, 1600)
expected_size_image = (600, 400)
# Create collage canvas to paste the images
collage = Image.new("RGBA", expected_size_collage, color=(255,255,255,255))
In above code, we import the necessary libraries, including PIL
which we'll use for
cropping and resizing the images.
Images that end with .png
.jpg
.jpeg
in a defined folder are
collected and put into a list.
Then we define the size of the desired collage. Here we have 24 images in the folder and we'd like to have a 6x4 images in the collage.
The tiles (images) are assigned to have 600x400
size, which yields a collage size
of
3600x1600
. A blank image with this size is initialized. In the next section, we'll
be pasting the image tiles into this blank canvas.
# Loop through image files
file_count = 0
for h in range(0, expected_size_collage[1], expected_size_image[1]):
for w in range(0, expected_size_collage[0], expected_size_image[0]):
# Read image
file_name = images[file_count]
path = directory + "/" + file_name
image = Image.open(path).convert("RGBA")
# Get the original image width and height
image_width = image.size[0]
image_height = image.size[1]
# Get how the width and height should be
width_factor = image_width / expected_size_image[0]
height_factor = image_height / expected_size_image[1]
# If width and height factors are same, no cropping is needed
# If not, we need to crop image to the same ratio as expected_size_image
if width_factor != height_factor:
# Get the limiting factor
factor = min(width_factor, height_factor)
# Calculate the resulting image width and height
expected_width = round(factor * expected_size_image[0])
expected_height = round(factor * expected_size_image[1])
# Get minx, miny, maxx, and maxy coordinates of new image
start_width = round((image_width - expected_width) / 2)
start_height = round((image_height - expected_height) / 2)
end_width = expected_width + round((image_width - expected_width) / 2)
end_height = expected_height + round((image_height - expected_height) / 2)
# Crop the image
image = image.crop((start_width, start_height, end_width, end_height))
# Once the image is cropped, resize the image
# Image should have the aspect ratio as the expected_size_image so resize won't disturb the image
image = image.resize(expected_size_image)
# Copy image to collage canvas
collage.paste(image, (w, h))
file_count += 1
Here, we create 2 loops, where the 1st loop tiles images horizontally along a row, and second loop, goes through rows. E.g., at the end of the first loop, we have below image:
Within the loop 4 actions are conducted:
Here, the image needs to be converted into an aspect ratio of 600x400
. In order to
achieve this, we identify how large the actual side is by dividing the actual size with the
expected size. Results are stored in width_factor
and height_factor
variables. Then we use the smaller of these 2 ratios, to identify the smallest multiple of
600x400
we can fit into this image.
E.g. an image with a size 2400x3000
can be fit into an aspect ratio of
600x400
as a 2400x1600
size. This means we need to crop out of 3000 - 1600 = 1400
pixels vertically. When removing
pixels, we remain the middle portion of the image.
Once the image is cropped, it can be safely resized to 600x400
as it already has the
same aspect ratio. Then the tile image is pasted onto the collage canvas.
# Save collage
collage.save("collage.png")
With this last bit of code, the collage is saved into the desired location. Note that, since the
canvas was started as an RGBA
image, it can't be saved as JPG
.
Hope you enjoyed this blog post. Some further improvements to this algorithm can be made such as
Any questions? Let me know below in the comment section.
Leave comment
Comments
Check out other blog posts
2024/06/19
Create A Simple and Dynamic Tooltip With Svelte and JavaScript
2024/06/17
Create an Interactive Map of Tokyo with JavaScript
2024/06/14
How to Easily Fix Japanese Character Issue in Matplotlib
2024/06/13
Book Review | Talking to Strangers: What We Should Know about the People We Don't Know by Malcolm Gladwell
2024/06/07
Most Commonly Used 3,000 Kanjis in Japanese
2024/06/07
Replace With Regex Using VSCode
2024/06/06
Do Not Use Readable Store in Svelte
2024/06/05
Increase Website Load Speed by Compressing Data with Gzip and Pako
2024/05/31
Find the Word the Mouse is Pointing to on a Webpage with JavaScript
2024/05/29
Create an Interactive Map with Svelte using SVG
2024/05/28
Book Review | Originals: How Non-Conformists Move the World by Adam Grant & Sheryl Sandberg
2024/05/27
How to Algorithmically Solve Sudoku Using Javascript
2024/05/26
How I Increased Traffic to my Website by 10x in a Month
2024/05/24
Life is Like Cycling
2024/05/19
Generate a Complete Sudoku Grid with Backtracking Algorithm in JavaScript
2024/05/16
Why Tailwind is Amazing and How It Makes Web Dev a Breeze
2024/05/15
Generate Sitemap Automatically with Git Hooks Using Python
2024/05/14
Book Review | Range: Why Generalists Triumph in a Specialized World by David Epstein
2024/05/13
What is Svelte and SvelteKit?
2024/05/12
Internationalization with SvelteKit (Multiple Language Support)
2024/05/11
Reduce Svelte Deploy Time With Caching
2024/05/10
Lazy Load Content With Svelte and Intersection Oberver
2024/05/10
Find the Optimal Stock Portfolio with a Genetic Algorithm
2024/05/09
Convert ShapeFile To SVG With Python
2024/05/08
Reactivity In Svelte: Variables, Binding, and Key Function
2024/05/07
Book Review | The Art Of War by Sun Tzu
2024/05/06
Specialists Are Dead. Long Live Generalists!
2024/05/03
Analyze Voter Behavior in Turkish Elections with Python
2024/05/01
Create Turkish Voter Profile Database With Web Scraping
2024/04/30
Make Infinite Scroll With Svelte and Tailwind
2024/04/29
How I Reached Japanese Proficiency In Under A Year
2024/04/25
Use-ready Website Template With Svelte and Tailwind
2024/01/29
Lazy Engineers Make Lousy Products
2024/01/28
On Greatness
2024/01/28
Converting PDF to PNG on a MacBook
2023/12/31
Recapping 2023: Compilation of 24 books read
2024/01/09
Detect Device & Browser of Visitors to Your Website
2024/01/19
Anatomy of a ChatGPT Response