0%

63- Editing Images with Python

Advanced image manipulation. Paste images together, create composites, apply masks, and build complex image editing pipelines. Beyond basic filters and resizing.

You know how to open, resize, crop, and filter images. That is just the beginning. Python can do much more: paste one image onto another, blend images together, create thumbnails galleries, add watermarks, build contact sheets, and process thousands of images automatically.
This lesson covers advanced image editing techniques with Pillow. You will learn to paste and composite images, work with alpha channels and masks, create image galleries, add borders and frames, and build automated image processing pipelines. These skills are essential for web development, social media automation, content creation, and data preparation.

🕯️ Magic Note

Pillow’s paste() and composite() functions are the foundation of image composition. You can layer images, create collages, add logos, and build complex visual layouts entirely in Python.

Pasting One Image onto Another
Place one image on top of another at specified coordinates.

Python

from PIL import Image

# Load background and foreground

background = Image.new(“RGB”, (800, 600), (100, 150, 200))

logo = Image.open(“logo.png”)

# Paste at specific position (top-left corner)

background.paste(logo, (50, 50))

# Paste with transparency (using mask)

if logo.mode == “RGBA”:

background.paste(logo, (50, 50), logo) # Uses alpha channel as mask

# Paste using a separate mask image

mask = Image.new(“L”, logo.size, 128) # 50% transparent mask

background.paste(logo, (50, 50), mask)

# Paste in center

x = (background.width – logo.width) // 2

y = (background.height – logo.height) // 2

background.paste(logo, (x, y), logo if logo.mode == “RGBA” else None)

background.save(“pasted.jpg”)

💡 When pasting images with transparency, always use the third parameter (mask) to preserve the alpha channel. This ensures transparent areas stay transparent.
Creating a Collage / Montage
Combine multiple images into a single grid layout.

Python

from PIL import Image

from pathlib import Path

import math

def create_collage(image_paths, output_path, cols=3, thumbnail_size=200, spacing=10):

“””

Create a collage from multiple images.

Args:

image_paths: List of paths to images

output_path: Where to save the collage

cols: Number of columns

thumbnail_size: Size to resize each image (square)

spacing: Spacing between images

“””

rows = math.ceil(len(image_paths) / cols)

# Calculate total collage size

width = cols * thumbnail_size + (cols – 1) * spacing

height = rows * thumbnail_size + (rows – 1) * spacing

# Create blank canvas

collage = Image.new(“RGB”, (width, height), (255, 255, 255))

for idx, img_path in enumerate(image_paths[:rows * cols]):

row = idx // cols

col = idx % cols

# Open and resize image

img = Image.open(img_path)

img.thumbnail((thumbnail_size, thumbnail_size))

# Create square canvas for this cell

cell = Image.new(“RGB”, (thumbnail_size, thumbnail_size), (255, 255, 255))

# Center the image in the cell

x = (thumbnail_size – img.width) // 2

y = (thumbnail_size – img.height) // 2

cell.paste(img, (x, y))

# Calculate position in collage

x_pos = col * (thumbnail_size + spacing)

y_pos = row * (thumbnail_size + spacing)

collage.paste(cell, (x_pos, y_pos))

collage.save(output_path)

print(f”Collage saved to {output_path}”)

# Usage

# create_collage([“img1.jpg”, “img2.jpg”, “img3.jpg”], “collage.jpg”, cols=2)

🕯️ Magic Note

The collage pattern is highly customizable. You can add borders, rounded corners, captions, or different layouts. This is how social media grid images and product catalogs are often generated automatically.

Image Blending and Compositing
Blend two images together using alpha compositing or weighted blending.

Python

from PIL import Image

# Load two images (must be same size)

img1 = Image.open(“image1.jpg”).convert(“RGBA”)

img2 = Image.open(“image2.jpg”).convert(“RGBA”)

# Resize to match if needed

img2 = img2.resize(img1.size)

# Blend with alpha (0.0 = first image, 1.0 = second image)

blended = Image.blend(img1, img2, alpha=0.5)

blended.save(“blended_50.jpg”)

# Composite using a mask (0-255 grayscale)

mask = Image.new(“L”, img1.size, 128) # Uniform 50% mask

composite = Image.composite(img1, img2, mask)

composite.save(“composite_uniform.jpg”)

# Gradient mask for smooth transition

gradient = Image.new(“L”, img1.size)

pixels = gradient.load()

for x in range(gradient.width):

for y in range(gradient.height):

pixels[x, y] = int(255 * x / gradient.width)

gradient_composite = Image.composite(img1, img2, gradient)

gradient_composite.save(“gradient_composite.jpg”)

Creating Rounded Corners
Add rounded corners to any image using a mask.

Python

from PIL import Image, ImageDraw

def add_rounded_corners(img, radius=30):

“””Add rounded corners to an image.”””

# Create a mask for rounded corners

mask = Image.new(“L”, img.size, 0)

draw = ImageDraw.Draw(mask)

draw.rounded_rectangle((0, 0, img.width, img.height), radius, fill=255)

# Apply mask to create rounded corners

if img.mode != “RGBA”:

img = img.convert(“RGBA”)

output = Image.new(“RGBA”, img.size, (0, 0, 0, 0))

output.paste(img, (0, 0), mask)

return output

img = Image.open(“example.jpg”)

rounded = add_rounded_corners(img, radius=50)

rounded.save(“rounded_corners.png”)

🕯️ Magic Note

The rounded_rectangle() method draws a rectangle with rounded corners. When used as a mask, it creates the transparency effect. This is how profile pictures with circular or rounded corners are created.

Adding Borders and Frames
Add decorative borders, frames, or padding around images.

Python

from PIL import Image, ImageDraw

def add_border(img, border_size=10, border_color=”black”):

“””Add a solid border around an image.”””

width, height = img.size

new_width = width + 2 * border_size

new_height = height + 2 * border_size

# Create new image with border color

bordered = Image.new(“RGB”, (new_width, new_height), border_color)

bordered.paste(img, (border_size, border_size))

return bordered

def add_polaroid_frame(img, border_size=20, bottom_height=60, text=””):

“””Create a Polaroid-style frame with space for text.”””

width, height = img.size

new_width = width + 2 * border_size

new_height = height + border_size + bottom_height

# Create white frame

frame = Image.new(“RGB”, (new_width, new_height), “white”)

# Paste image

frame.paste(img, (border_size, border_size))

# Add text if provided

if text:

draw = ImageDraw.Draw(frame)

try:

from PIL import ImageFont

font = ImageFont.truetype(“arial.ttf”, 16)

except:

font = ImageFont.load_default()

bbox = draw.textbbox((0, 0), text, font=font)

text_width = bbox[2] – bbox[0]

text_x = (new_width – text_width) // 2

text_y = height + border_size + 10

draw.text((text_x, text_y), text, fill=”black”, font=font)

return frame

img = Image.open(“example.jpg”)

bordered = add_border(img, 20, “#333333”)

bordered.save(“bordered.jpg”)

polaroid = add_polaroid_frame(img, 15, 50, “Summer 2025”)

polaroid.save(“polaroid.jpg”)

Creating Contact Sheets
Generate a contact sheet showing thumbnails of all images in a folder with labels.

Python

from PIL import Image, ImageDraw, ImageFont

from pathlib import Path

import math

def create_contact_sheet(folder_path, output_path, cols=4, thumb_size=200, label=True):

“””Create a contact sheet of all images in a folder.”””

image_extensions = {“.jpg”, “.jpeg”, “.png”, “.gif”, “.bmp”}

image_paths = [p for p in Path(folder_path).iterdir() if p.suffix.lower() in image_extensions]

if not image_paths:

print(“No images found”)

return

rows = math.ceil(len(image_paths) / cols)

cell_width = thumb_size + 10

cell_height = thumb_size + (30 if label else 10)

width = cols * cell_width

height = rows * cell_height

sheet = Image.new(“RGB”, (width, height), “white”)

draw = ImageDraw.Draw(sheet)

try:

font = ImageFont.truetype(“arial.ttf”, 10)

except:

font = ImageFont.load_default()

for idx, img_path in enumerate(image_paths):

row = idx // cols

col = idx % cols

x = col * cell_width

y = row * cell_height

img = Image.open(img_path)

img.thumbnail((thumb_size, thumb_size))

paste_x = x + (thumb_size – img.width) // 2

paste_y = y + (thumb_size – img.height) // 2

sheet.paste(img, (paste_x, paste_y))

if label:

label_text = img_path.stem[:15]

bbox = draw.textbbox((0, 0), label_text, font=font)

text_width = bbox[2] – bbox[0]

text_x = x + (cell_width – text_width) // 2

text_y = y + thumb_size + 5

draw.text((text_x, text_y), label_text, fill=”gray”, font=font)

sheet.save(output_path)

print(f”Contact sheet saved to {output_path} ({len(image_paths)} images)”)

# create_contact_sheet(“photos”, “contact_sheet.jpg”, cols=5)

Image Effects: Vignette and Sepia
Create artistic effects by manipulating pixels.

Python

from PIL import Image, ImageDraw, ImageEnhance

import math

def add_vignette(img, strength=0.5):

“””Add a dark vignette effect around the edges.”””

if img.mode != “RGBA”:

img = img.convert(“RGBA”)

# Create gradient mask

mask = Image.new(“L”, img.size, 255)

draw = ImageDraw.Draw(mask)

center_x, center_y = img.width / 2, img.height / 2

max_radius = math.sqrt(center_x ** 2 + center_y ** 2)

for x in range(img.width):

for y in range(img.height):

distance = math.sqrt((x – center_x) ** 2 + (y – center_y) ** 2)

darkness = min(255, int(255 * (distance / max_radius) * strength))

mask.putpixel((x, y), 255 – darkness)

# Apply mask as alpha

if img.mode != “RGBA”:

img = img.convert(“RGBA”)

img.putalpha(mask)

return img

def sepia(img):

“””Convert image to sepia tone.”””

if img.mode != “RGB”:

img = img.convert(“RGB”)

width, height = img.size

sepia_img = Image.new(“RGB”, (width, height))

pixels = img.load()

sepia_pixels = sepia_img.load()

for x in range(width):

for y in range(height):

r, g, b = pixels[x, y]

# Apply sepia transformation

tr = int(0.393 * r + 0.769 * g + 0.189 * b)

tg = int(0.349 * r + 0.686 * g + 0.168 * b)

tb = int(0.272 * r + 0.534 * g + 0.131 * b)

sepia_pixels[x, y] = (min(tr, 255), min(tg, 255), min(tb, 255))

return sepia_img

# Apply effects

img = Image.open(“example.jpg”)

vignette_img = add_vignette(img, strength=0.7)

vignette_img.save(“vignette.png”)

sepia_img = sepia(img)

sepia_img.save(“sepia.jpg”)

Batch Processing Multiple Images
Process entire folders of images automatically.

Python

from PIL import Image

from pathlib import Path

def batch_process_images(input_folder, output_folder, operations):

“””

Apply a series of operations to all images in a folder.

operations: list of tuples (operation_name, params)

“””

input_path = Path(input_folder)

output_path = Path(output_folder)

output_path.mkdir(exist_ok=True)

image_extensions = {“.jpg”, “.jpeg”, “.png”, “.gif”, “.bmp”}

image_files = [f for f in input_path.iterdir() if f.suffix.lower() in image_extensions]

for img_file in image_files:

img = Image.open(img_file)

for op_name, params in operations:

if op_name == “resize”:

img = img.resize(params[“size”])

elif op_name == “thumbnail”:

img.thumbnail(params[“size”])

elif op_name == “rotate”:

img = img.rotate(params[“angle”], expand=params.get(“expand”, True))

elif op_name == “convert”:

img = img.convert(params[“mode”])

elif op_name == “crop”:

img = img.crop(params[“box”])

elif op_name == “filter”:

from PIL import ImageFilter

filter_map = {

“blur”: ImageFilter.BLUR,

“contour”: ImageFilter.CONTOUR,

“sharpen”: ImageFilter.SHARPEN,

“edges”: ImageFilter.FIND_EDGES

}

img = img.filter(filter_map.get(params[“name”]))

output_file = output_path / img_file.name

img.save(output_file)

print(f”Processed: {img_file.name}”)

print(f”Batch processing complete. {len(image_files)} images processed.”)

# Usage example

# operations = [

# (“thumbnail”, {“size”: (800, 800)}),

# (“rotate”, {“angle”: 90, “expand”: False}),

# (“convert”, {“mode”: “L”}) # Convert to grayscale

# ]

# batch_process_images(“originals”, “processed”, operations)

Working with Image Sequences (GIFs)
Create animated GIFs from multiple images.

Python

from PIL import Image

def create_animated_gif(image_paths, output_path, duration=100, loop=0):

“””Create an animated GIF from multiple images.”””

frames = []

for img_path in image_paths:

img = Image.open(img_path)

frames.append(img.copy())

if frames:

frames[0].save(

output_path,

save_all=True,

append_images=frames[1:],

duration=duration,

loop=loop

)

print(f”Animated GIF saved to {output_path}”)

# Extract frames from GIF

def extract_gif_frames(gif_path, output_folder):

“””Extract all frames from an animated GIF.”””

with Image.open(gif_path) as gif:

frame_count = 0

while True:

try:

gif.seek(frame_count)

frame = gif.copy()

frame.save(f”{output_folder}/frame_{frame_count:03d}.png”)

frame_count += 1

except EOFError:

break

print(f”Extracted {frame_count} frames”)

# create_animated_gif([“img1.jpg”, “img2.jpg”, “img3.jpg”], “animation.gif”, duration=200)

Common Mistakes in Image Editing
  • Forgetting to convert modes before compositing (ensure both images have same mode)
  • Pasting images of different sizes without resizing (causes ValueError)
  • Using JPEG for images with transparency (use PNG)
  • Modifying the original image instead of working on a copy
  • Not handling memory with large batches (use context managers or process images one by one)
  • Assuming all images have the same dimensions when creating collages
Check Your Understanding
  • How do you paste one image onto another with transparency?
  • Write a function that creates a 2×2 grid collage from four images.
  • How do you add rounded corners to an image?
  • What is the difference between Image.blend() and Image.composite()?
  • Write code that creates a Polaroid-style frame with a caption.
  • How do you create an animated GIF from multiple images?

⚡ Whisper

Editing images with Python is a superpower. You can process thousands of photos in seconds. Resize for web. Add watermarks. Create collages. Generate contact sheets. Convert formats. Apply effects. Batch process entire folders. The same operations that would take hours manually run in seconds automatically. This is not just convenience. This is scalability. A product catalog with 10,000 images? Process them in a loop. A photo gallery for a client? Generate thumbnails automatically. Social media posts? Create templates and fill them with images. The combination of Pillow’s capabilities and Python’s automation is transformative. Learn to paste, composite, blend. Master the mask. Understand alpha channels. Then build your pipelines. Images are data. Data can be processed. Process them well.

Related posts