Superimposing one image onto another is frequently used for adding copyright notices (watermarks) or synthesizing “SALE” badges onto product images.
While you use Pillow’s paste() method, specifying the 3rd argument (mask) is essential, especially when overlaying images with transparent backgrounds (like PNGs) cleanly. Here, I will explain how to composite a logo image onto a specified position on a background image while maintaining transparency information.
Executable Sample Code
The following code is a script that composites a semi-transparent logo mark onto the bottom right of a main photo and saves it. It calculates coordinates so that the logo is always placed at the bottom right, even if the image size changes.
from PIL import Image, ImageDraw
import os
def composite_images_demo():
# Define filenames
base_image_path = "scenery_photo.jpg"
overlay_image_path = "watermark_logo.png"
output_path = "photo_with_watermark.jpg"
# Generate dummy images for verification (not necessary if you have images)
if not os.path.exists(base_image_path):
_create_base_photo(base_image_path)
if not os.path.exists(overlay_image_path):
_create_watermark_icon(overlay_image_path)
try:
# 1. Load Images
# Base image (Background)
base_img = Image.open(base_image_path)
# Image to overlay (Foreground)
# Load as RGBA to handle transparency information safely
overlay_img = Image.open(overlay_image_path).convert("RGBA")
# 2. Calculate Paste Position
# Place at "Bottom Right" (with a 20px margin)
margin = 20
base_w, base_h = base_img.size
overlay_w, overlay_h = overlay_img.size
# Coordinate = (Base Width - Logo Width - Margin, Base Height - Logo Height - Margin)
position = (base_w - overlay_w - margin, base_h - overlay_h - margin)
# 3. Paste Image (paste)
# This is a destructive method that modifies base_img itself.
# Use .copy() beforehand if you need to keep the original.
#
# [IMPORTANT] Pass the same image to the 3rd argument (mask) to apply the alpha channel.
# If omitted, transparent parts will be filled with black or white.
base_img.paste(overlay_img, position, mask=overlay_img)
# 4. Save
# To save as JPEG, it needs to be in RGB mode
# (The base is likely RGB, but checking just in case)
if base_img.mode != "RGB":
base_img = base_img.convert("RGB")
base_img.save(output_path, quality=95)
print(f"Composite image saved: {output_path}")
except Exception as e:
print(f"Error occurred: {e}")
def _create_base_photo(filename):
"""Create a dummy landscape photo (Base)"""
img = Image.new("RGB", (600, 400), color=(100, 150, 200)) # Light Blue
draw = ImageDraw.Draw(img)
draw.rectangle((0, 300, 600, 400), fill=(50, 100, 50)) # Green Ground
img.save(filename)
def _create_watermark_icon(filename):
"""Create a circular logo for overlay (with transparency)"""
# Create a canvas with transparent background (0,0,0,0)
size = 100
img = Image.new("RGBA", (size, size), (0, 0, 0, 0))
draw = ImageDraw.Draw(img)
# Draw a semi-transparent red circle
draw.ellipse((0, 0, size, size), fill=(255, 100, 100, 200))
# Draw text
draw.text((20, 40), "COPY", fill="white")
img.save(filename)
if __name__ == "__main__":
composite_images_demo()
Explanation: The paste Method and Mask Specification
The arguments for the base_image.paste(im, box, mask) method are as follows:
- im: The image object to paste (Source).
- box: The position to paste. A tuple of
(x, y). - mask (Optional): An image for masking.
Why is mask necessary?
If you paste a transparent PNG without specifying the mask argument, the transparent background parts will be overwritten as an “opaque color (usually black).”
If the source image (overlay_img) has an alpha channel (transparency information), passing that image itself to the mask argument performs the correct compositing where “transparent parts are not pasted (showing the image below).”
# Failure Example (Transparent parts become black)
base_img.paste(overlay_img, position)
# Success Example (Clean transparent composition)
base_img.paste(overlay_img, position, mask=overlay_img)
