I recently built WatermarkX, a lightweight desktop app for batch image watermarking using Python.
What started as a small internal tool for my own content workflow turned into a full GUI application with drag & drop, live progress tracking, and repeat diagonal watermarks.
In this post, I’ll walk through what it does, how it’s built, and some lessons learned.
✨ What WatermarkX Does
WatermarkX is a local desktop application that lets you apply text and logo watermarks to multiple images at once.
Key features:
Text watermarks with rotation, opacity, font size, and stroke/outline
Repeat diagonal (tiled) watermark mode
PNG logo watermark support (with transparency)
Drag & drop image loading
Batch processing with progress bar, ETA, and speed
Stop / resume processing
Everything runs locally — no uploads, no accounts
Supported formats:
PNG
JPG / JPEG
BMP
GIF
🧠 Tech Stack
The app is written entirely in Python:
Tkinter + ttkbootstrap – GUI
tkinterdnd2 – drag & drop support
Pillow (PIL) – image processing
threading – background processing so the UI stays responsive
The core watermarking logic uses Pillow’s ImageDraw and ImageFont, creating a rotated RGBA text layer that gets composited onto each image.
For tiled watermarks, I generate a single rotated watermark layer and then paste it repeatedly across the canvas with calculated spacing.
Everything runs locally on your machine.
🔧 Interesting Implementation Details
- Zero-clip rotated text
To avoid cropped text after rotation, I first measure the text using textbbox, add margins, render it onto a transparent canvas, and only then rotate:
bbox = d.textbbox((0,0), text, font=font, stroke_width=stroke)
This ensures the watermark never gets clipped.
- Diagonal repeat watermarking
Instead of rotating the entire image, I rotate just the watermark and tile it diagonally across the image:
Create watermark layer once
Calculate spacing from watermark dimensions
Paste in nested loops across the canvas
This keeps things fast even for large batches.
- Non-blocking UI
Processing runs in a background thread so the GUI stays responsive:
Progress bar updates per image
ETA is calculated from elapsed time
Speed shown as images/sec
Stop button sets a shared flag checked inside the processing loop
Simple, but effective.
📦 Packaging
The app is packaged as a standalone desktop executable so users don’t need Python installed.
That involved:
Handling resource paths (_MEIPASS)
Bundling fonts and icons
Making sure Pillow + Tkinter play nicely in production builds
Desktop Python distribution is still a bit rough around the edges, but totally doable.
🚀 Why I Built It
I frequently need to watermark screenshots and marketing images, and existing tools were either:
Web-based (slow + privacy concerns)
Overkill
Subscription-heavy
So I built exactly what I needed: fast, offline, and simple.
After friends started asking for it, I decided to polish it and release it.
🔗 Project Link
If you’d like to check it out:
https://gum.new/gum/cmka62o2l001w04l42jmy3i9y
Feedback, feature ideas, and code suggestions are very welcome.
🙏 Final Thoughts
This was a fun reminder that Python is still great for shipping real desktop tools — not just scripts and APIs.
If you’re thinking about building your own GUI utilities: just start. Tkinter + Pillow can take you surprisingly far.
Thanks for reading 🙂

Top comments (0)