A collage is a beautiful way to showcase memories and display sets of images. Online collage makers may have security concerns and offline apps may cost money and lack the features you require.

By building your own image collage maker you can eliminate these concerns and retain complete control. So, how can you build one?

The Tkinter and PIL Module

To build an image collage application you need the Tkinter and the PIL module. Tkinter allows you to create desktop applications. It offers a variety of widgets that make it easier to develop GUIs.

The Pillow library—a fork of the Python Imaging Library (PIL)—provides image processing capabilities that help in editing, creating, converting file formats, and saving images.

To install Tkinter and Pillow, open a terminal and run:

 pip install tk pillow 

GUI Setup and Image Manipulation

You can find this project's source code in its GitHub repository.

Begin by importing the required modules. Create a class, ImageCollageApp, and set the title and dimensions of the window. Define a canvas using tk.Canvas() and set its parent element, width, height, and background color.

 import tkinter as tk
from tkinter import filedialog, simpledialog, messagebox
from PIL import Image, ImageTk

class ImageCollageApp:
    def __init__(self, root):
        self.root = root
        self.root.title("Image Collage Maker")
        self.images = []
        self.image_refs = []
        self.collage_size = (600, 500)

        self.collage_canvas = tk.Canvas(
            self.root,
            width=self.collage_size[0],
            height=self.collage_size[1],
            bg="white",
        )

        self.collage_canvas.pack()

Create two buttons: Add Image, and Create Collage. Define the parent element, the text to display, the command to run, and the font styles. Organize the buttons by adding appropriate padding. Initialize drag_data to store information about the dragging operation.

Initialize image_positions to store the positions of images on the canvas. Define three event handlers to respond to the selection, dragging, and releasing of images.

         self.btn_add_image = tk.Button(
            self.root,
            text="Add Image",
            command=self.add_images,
            font=("Arial", 12, "bold"),
        )

        self.btn_add_image.pack(pady=10)

        self.btn_create_collage = tk.Button(
            self.root,
            text="Create Collage",
            command=self.create_collage,
            font=("Arial", 12, "bold"),
        )

        self.btn_create_collage.pack(pady=5)
        self.drag_data = {"x": 0, "y": 0, "item": None}
        self.image_positions = []
        self.collage_canvas.bind("<ButtonPress-1>", self.on_press)
        self.collage_canvas.bind("<B1-Motion>", self.on_drag)
        self.collage_canvas.bind("<ButtonRelease-1>", self.on_release)

Define a method, on_press. Retrieve the closest canvas item from the location where the user clicks the mouse and store it under the item key of the drag_data dictionary. Store the x and y coordinates of the mouse click. You will use this to calculate the distance the user moves the mouse during dragging.

     def on_press(self, event):
        self.drag_data["item"] = self.collage_canvas.find_closest(event.x, event.y)[0]
        self.drag_data["x"] = event.x
        self.drag_data["y"] = event.y

Define a method, on_drag. Calculate the horizontal and the vertical distance the user moved the mouse during dragging and update the position of the image accordingly. Store the updated coordinates of the image under the x and y keys of the drag_data dictionary.

     def on_drag(self, event):
        delta_x = event.x - self.drag_data["x"]
        delta_y = event.y - self.drag_data["y"]
        self.collage_canvas.move(self.drag_data["item"], delta_x, delta_y)
        self.drag_data["x"] = event.x
        self.drag_data["y"] = event.y

Define a method, on_release. Clear the reference to the image the user was dragging along with its coordinates. Call the update_image_positions to update the positions of all images on the canvas after the user drags and releases it.

     def on_release(self, event):
        self.drag_data["item"] = None
        self.drag_data["x"] = 0
        self.drag_data["y"] = 0
        self.update_image_positions()

Define a method, update_image_positions. Clear the image_positions list and iterate over all canvas items. For each item, find the coordinates and add them to the list.

     def update_image_positions(self):
        self.image_positions.clear()

        for item in self.collage_canvas.find_all():
            x, y = self.collage_canvas.coords(item)
            self.image_positions.append((x, y))

Define a method, add_images. Create a dialog box that prompts the user to enter the number of images for the collage. If the user provided a valid number, open a file dialog box that only allows the user to select image files. Once the user has selected one or more images, open each one with Pillow's Image.open() method.

Call the resize_image method and create a Tkinter-compatible PhotoImage. Add this to the image_refs list and call the update_canvas method.

     def add_images(self):
        num_images = simpledialog.askinteger(
            "Number of Images", "Enter the number of images:"
        )

        if num_images is not None:
            file_paths = filedialog.askopenfilenames(
                filetypes=[("Image files", "*.png;*.jpg;*.jpeg;*.gif")]
            )

            if file_paths:
                for i in range(min(num_images, len(file_paths))):
                    file_path = file_paths[i]
                    image = Image.open(file_path)
                    resized_image = self.resize_image(image)
                    self.images.append(resized_image)
                    self.image_refs.append(ImageTk.PhotoImage(resized_image))

                self.update_canvas()

Define a method, resize_image. Get the width and height of the image and calculate its aspect ratio. If it is more than one, set the new width to be half of the collage's width. Calculate the corresponding new height while maintaining the aspect ratio.

If the aspect ratio is less than one, set the new height to be half of the collage's height. Similarly, calculate the corresponding width. Use Pillow's resize method to return a resized image using the calculated parameters.

     def resize_image(self, image):
        img_width, img_height = image.size
        aspect_ratio = img_width / img_height

        if aspect_ratio > 1:
            new_width = self.collage_size[0] // 2
            new_height = int(new_width / aspect_ratio)
        else:
            new_height = self.collage_size[1] // 2
            new_width = int(new_height * aspect_ratio)

        return image.resize((new_width, new_height))

Define a method, update_canvas. Clear all the items and ask the user for the desired number of rows and columns via a file dialog box. Set the collage's width and height to take half of the specified collage size. Clears the list of image positions. Initialize x and y offset to zero, so you can keep track of the position offsets for arranging images in rows and columns.

     def update_canvas(self):
        self.collage_canvas.delete("all")
        rows = simpledialog.askinteger("Number of Rows", "Enter the number of rows:")

        cols = simpledialog.askinteger(
            "Number of Columns", "Enter the number of columns:"
        )

        collage_width = self.collage_size[0] * cols // 2
        collage_height = self.collage_size[1] * rows // 2
        self.collage_canvas.config(width=collage_width, height=collage_height)
        self.image_positions.clear()
        x_offset, y_offset = 0, 0

Iterate over the image_refs list and create an image on the canvas using the specified offset. Set the anchor to Northwest so that you position the image's top-left corner at the specified coordinates. Append these coordinates to the image_positions list.

Update the x_offset to add half of the collage's width, to prepare for placing the next image. If the number of images placed in the current row is a multiple of the specified number of columns, set the x_offset to zero. This indicates the start of a new row. Add half of the collage's height to set the y coordinate for the next row.

         for i, image_ref in enumerate(self.image_refs):
            self.collage_canvas.create_image(
                x_offset, y_offset, anchor=tk.NW, image=image_ref
            )

            self.image_positions.append((x_offset, y_offset))
            x_offset += self.collage_size[0] // 2

            if (i + 1) % cols == 0:
                x_offset = 0
                y_offset += self.collage_size[1] // 2

Creating the Collage and Saving It

Define a method, create_collage. If there are no images on the collage, display a warning. Collect the collage's width and height. Create a Pillow Image with a white background. Iterate through the images list and paste each image onto the background at the specified positions.

Save the collage and display it using the default image viewer.

     def create_collage(self):
        if len(self.images) == 0:
            messagebox.showwarning("Warning", "Please add images first!")
            return

        collage_width = self.collage_canvas.winfo_width()
        collage_height = self.collage_canvas.winfo_height()
        background = Image.new("RGB", (collage_width, collage_height), "white")

        for idx, image in enumerate(self.images):
            x_offset, y_offset = self.image_positions[idx]
            x_offset, y_offset = int(x_offset), int(y_offset)

            paste_box = (
                x_offset,
                y_offset,
                x_offset + image.width,
                y_offset + image.height,
            )

            background.paste(image, paste_box)

        background.save("collage_with_white_background.jpg")
        background.show()

Create an instance of the Tkinter and ImageCollage App class. The mainloop() function tells Python to run the Tkinter event loop and listen for events until you close the window.

 if __name__ == "__main__":
    root = tk.Tk()
    app = ImageCollageApp(root)
    root.mainloop()

Testing Different Features of the Image Collage Maker

On running the program, a window appears with two buttons, Add Image, and Create Collage. On clicking the Add Image button, a dialog box asks the number of images to collage. On entering the number of images as five and selecting them, another dialog box appears. It asks for the number of rows followed by the number of columns.

Start Screen of Image Collage Application
Source: Screenshot by Sai Ashish -- no attribution required

On entering two rows and three columns, the window organizes the images in a grid structure.

Image Collage Default Preview for 5 images in 2 rows, 3 columns
Source: Screenshot by Sai Ashish -- no attribution required

The preview gives the ability to drag the images as per desire. On clicking the Create Collage button, the program saves the image.

Dragging Images to desired positions within canvas
Source: Screenshot by Sai Ashish -- no attribution required

On viewing the image, you can confirm that the program created the collage successfully.

Desired Collage Image
Source: Screenshot by Sai Ashish -- no attribution required

Enhancing the Functionality of the Image Collage Maker

Instead of a tabular format, you can provide different default templates for the user to choose from. Add features for changing the background color, adding text, applying filters to images, and inserting stickers from the internet.

While adding these features, make it easy to edit the collage with an option to undo or redo. Let the user crop, resize, and flip the images according to their liking. You should also add an option to save the image in their desired format.