Q-Branch · Item 1 · Theme: Darkroom Brass

The Invisible Watermark

A mark you cannot see, pressed into your own photographs — so a copy can still say whose it was. Local tool. Nothing leaves this machine.

proof-support — not DRM $0 · offline owner's own photos only

What this is — in one breath

watermark.py hides a short owner mark (for example Chris Fernandez — Berserk 2026) inside a photo's brightness, spread across the whole picture and repeated, so ordinary handling — a JPEG re-save, a resize, a screenshot — does not wash it out. Reading a picture back either finds the exact mark, verified by a checksum, or honestly reports NO MARK. It never guesses.

What it is not: it is not unremovable and it is not DRM. A determined editor, a heavy crop, or a re-photograph will defeat it (a 25% crop defeats it in our own tests — see below). Treat a found mark as supporting evidence of origin, never as proof on its own, and never as a lock on anyone's use.

Use it — three commands, in a terminal, in this folder

1 · Mark one of your own photos (mark text: up to 32 characters):

python3 watermark.py mark my-photo.jpg my-photo-marked.png --id "Chris Fernandez — Berserk 2026"

2 · Read a picture back (works on PNG or JPEG):

python3 watermark.py read some-copy.jpg

Success looks like MARK FOUND: Chris Fernandez — Berserk 2026 with crc: … (verified). Failure looks like NO MARK: … — that is a real answer, not an error to retry away.

3 · Re-run the whole test bench yourself (regenerates the sample and every attack):

python3 run_fixtures.py
Practical habits: keep the unmarked original somewhere safe; mark the copy you share. Mark from a PNG or a high-quality original where you can — marking a JPEG writes it back at quality 95. Very small images (under 128×128) are refused: there is not enough picture to hide a mark in honestly.

Measured survival — our own sample, our own attacks

Handling applied to the marked copyResult
Plain PNG re-saveSURVIVE
JPEG re-save, quality 85SURVIVE
JPEG re-save, quality 70SURVIVE
50% resize (800×600 → 400×300)SURVIVE
50% resize and JPEG q85 togetherSURVIVE
PNG screenshot-style re-saveSURVIVE
25% crop (kept top-left 75% × 75%)DIE — no mark read

6 of 7 attacks survived on the synthetic sample (seed-fixed, generated by make_sample.py). Controls also passed: an unmarked photo reads NO MARK; a different mark reads back as itself. Full commands and counts: FIXTURES-1.md. One sample is evidence about this sample — real-photo behaviour is labelled UNPROVEN in the build record until the owner tests his own.

How it works — the darkroom version

The picture is divided into a 32 × 32 grid drawn relative to the picture's size — halve the picture and the grid halves with it, which is why resizing does not tear the mark. Each cell's average brightness is nudged onto a numbered step; even steps whisper “0”, odd steps whisper “1”. The mark — a magic word, your text, and a checksum, 312 bits in all — is dealt across all 1,024 cells, so every bit gets 3–4 independent cells voting on it at read time. JPEG noise can flip a cell; it rarely flips a majority. Cropping moves the grid itself, and the mark, honestly, dies with it.