DEV Community

Anuj Kabra
Anuj Kabra

Posted on

Watermarking an ID copy before I send it for KYC

When a bank, broker or landlord asks for a photo of my ID, that image leaves my hands for good. I can't control where it ends up, but I can make a leaked copy less useful: write on it who it was for and when. A copy stamped "For KYC at Example Bank only, 10 Nov 2026" is awkward to reuse anywhere else. Below is how I do it with built-in apps or a short Python script, and the placement mistake that makes most watermarks pointless.

Everything here was tested on a dummy ID image I generated, with Python 3 and Pillow 13.0.0 on Linux.

What the watermark should say

Three things, in plain words:

  • Who it's for: the company name.
  • Why: "for KYC" or "for account opening".
  • When: today's date.

Skip anything that adds more personal data, like your phone number. The point is to narrow what the copy can be used for, not to add more to it.

Where it goes matters more than how it looks

A small mark in an empty corner comes off with one crop, and the ID is still perfectly usable. The mark has to cross the parts someone would want to keep: the photo, the name and the number. That's why I repeat the text in diagonal rows across the whole image. Any piece cut out of it still carries the text.

It also has to stay see-through. If it hides the number or the photo, the KYC team will reject the copy and ask again. Semi-transparent text over the details keeps them readable while still marking every part of the image.

With built-in apps

  • Mac: open the image in Preview, show the Markup toolbar, and use the Text tool. Type the line, make it large, then duplicate it a few times and drag the copies across the photo and the details. Export as a new file.
  • Windows: open the image in Paint and use the Text tool. Paint draws the text in a solid colour, so pick a light one and place the lines so they cross the details without covering the characters.
  • Phone: most gallery apps have a markup or edit mode with a text option. Same rule: across the details, not in a corner.

Always save as a new file and keep the original unmarked, so you can make a fresh copy for the next request.

With a Python script

This tiles the text diagonally across the whole image at about 35% opacity. It needs Pillow (pip install pillow).

import sys
from PIL import Image, ImageDraw, ImageFont

src, out, text = sys.argv[1], sys.argv[2], sys.argv[3]
img = Image.open(src).convert("RGBA")
w, h = img.size
size = max(16, w // 25)
try:
    font = ImageFont.truetype("DejaVuSans-Bold.ttf", size)
except OSError:
    font = ImageFont.load_default(size)

# Draw the text in rows on a layer twice the image size, rotate it, then crop the middle back out.
layer = Image.new("RGBA", (w * 2, h * 2), (0, 0, 0, 0))
d = ImageDraw.Draw(layer)
tw = d.textlength(text, font=font)
step_x, step_y = int(tw + size * 2), size * 4
for row, y in enumerate(range(0, h * 2, step_y)):
    offset = (row % 2) * step_x // 2
    for x in range(-offset, w * 2, step_x):
        d.text((x, y), text, font=font, fill=(200, 0, 0, 90))
layer = layer.rotate(30, resample=Image.BICUBIC)
layer = layer.crop((w // 2, h // 2, w // 2 + w, h // 2 + h))

Image.alpha_composite(img, layer).convert("RGB").save(out, quality=90)
Enter fullscreen mode Exit fullscreen mode

Run it like this:

python3 watermark.py id.jpg id-marked.jpg "For KYC at Example Bank only, 10 Nov 2026"
Enter fullscreen mode Exit fullscreen mode

A few notes on the choices:

  • The text layer is drawn at twice the image size and rotated before cropping, so the rotated rows still reach every corner.
  • Every other row is shifted by half a step, so there's no clean vertical strip without text.
  • The fill alpha of 90 out of 255 is what keeps the details readable underneath. Raise it if the mark is too faint on a busy background.
  • The font size scales with the image width. DejaVuSans-Bold.ttf is common on Linux; on Mac or Windows, put the path to any .ttf font there, or the script falls back to Pillow's built-in font.

To check coverage, I split the dummy ID into a 4 by 4 grid and compared each cell with the original. All 16 cells had watermark pixels in them, so no rectangle that size can be cropped out clean.

For Aadhaar, mask the number first

If the ID is an Aadhaar card, UIDAI offers a masked e-Aadhaar where the first 8 digits are hidden and only the last 4 show. Their FAQ also notes that masking is only available on the e-Aadhaar download (UIDAI online services FAQ). Where the receiver accepts a masked copy, watermark that one instead of a photo of the full card.

What a watermark doesn't do

It doesn't stop anyone from reading or saving the image, and someone with time and an editor can paint over it. What it does is make a copy traceable to the one request it was sent for, and much less convenient to pass off somewhere else. Some receivers may also have rules about how a submitted copy must look, so if a copy is rejected, ask what they accept rather than sending the unmarked original by default.

I built ToolOcean, and it has a watermark page that places text over an image in the browser without uploading it, with settings for position, size, colour and opacity. It's at toolocean.co/image-watermark.

Drafted with AI help, tested and checked by me.

Top comments (0)