Image Watermarker
This utility adds a text watermark to all images in a specified directory. It handles transparency (alpha channels) to ensure the watermark overlays smoothly on top of the image.
Modules Used:
- Pillow: For image manipulation.
- argparse: To handle command-line arguments.
- pathlib: For file system paths.
Installation
pip install Pillow
The Code
Save this as watermark.py.
import argparse
from pathlib import Path
from PIL import Image, ImageDraw, ImageFont
def add_watermark(input_dir, output_dir, text, opacity=128):
input_path = Path(input_dir)
output_path = Path(output_dir)
output_path.mkdir(parents=True, exist_ok=True)
# Supported extensions
exts = {'.jpg', '.jpeg', '.png', '.bmp'}
files = [f for f in input_path.iterdir() if f.is_file() and f.suffix.lower() in exts]
print(f"Found {len(files)} images in {input_dir}")
for file in files:
try:
# Open image and convert to RGBA to allow transparency
with Image.open(file).convert("RGBA") as base:
# Create a transparent layer for the watermark
txt_layer = Image.new("RGBA", base.size, (255, 255, 255, 0))
# Load font
try:
# Try to load Arial (Windows/macOS common)
# You can point this to a specific .ttf file path
font = ImageFont.truetype("arial.ttf", 40)
except IOError:
# Fallback to default if arial not found
print("Warning: Arial font not found, using default.")
font = ImageFont.load_default()
draw = ImageDraw.Draw(txt_layer)
# Calculate text size to position it at bottom right
# textbbox returns (left, top, right, bottom)
bbox = draw.textbbox((0, 0), text, font=font)
text_width = bbox[2] - bbox[0]
text_height = bbox[3] - bbox[1]
width, height = base.size
x = width - text_width - 20 # 20px padding
y = height - text_height - 20
# Draw text: White color with specified opacity
draw.text((x, y), text, font=font, fill=(255, 255, 255, opacity))
# Composite the watermark layer onto the base image
watermarked = Image.alpha_composite(base, txt_layer)
# Save result
save_path = output_path / file.name
# Convert back to RGB if saving as JPEG (JPEG doesn't support alpha)
if file.suffix.lower() in ['.jpg', '.jpeg']:
watermarked = watermarked.convert("RGB")
watermarked.save(save_path)
print(f"Saved: {save_path.name}")
except Exception as e:
print(f"Error processing {file.name}: {e}")
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Batch Image Watermarker")
parser.add_argument("input", help="Input directory of images")
parser.add_argument("output", help="Output directory")
parser.add_argument("--text", required=True, help="Text to watermark")
parser.add_argument("--opacity", type=int, default=128, help="Opacity (0-255, default: 128)")
args = parser.parse_args()
add_watermark(args.input, args.output, args.text, args.opacity)
Usage
# Basic usage
python watermark.py ./photos ./watermarked --text "Copyright 2023"
# Adjust opacity (0 is invisible, 255 is solid)
python watermark.py ./photos ./watermarked --text "DRAFT" --opacity 50