3 min read

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

programming/python/python