Proof watermark for client drafts of AI images, in Pillow
Stamp a tiled diagonal PROOF text over an image from Sume before sending drafts. Layer, rotate, alpha-composite, and why it is a courtesy and not security.

Draw the text on a transparent layer, rotate it with Image.rotate(30, expand=True), crop it to the image size, and combine with Image.alpha_composite. Tile the word across the whole picture so a crop cannot remove it. This marks a draft as a draft. It is a visible overlay you add yourself, not a security feature, and anyone with the original file has the clean image.
What this is and is not
Client approval rounds often need proofs that cannot be mistaken for finals. A bold tiled word does that job. It also stops a draft from being used by accident on a live page.
It does not protect the file. If you need to withhold the clean image, serve only the proof and keep the original on your server. This overlay is your own mark; it does nothing to any provenance data inside the file, and it should not be used to hide such data.
The stamp
The layer is drawn at 1.5 times the diagonal so rotating leaves no empty corners. Alpha around 70 out of 255 stays readable without covering the product. Pillow's built-in font is small, so scale by drawing at a larger size when you have a TrueType file, using ImageFont.truetype(path, size).
import os, io, requests
from PIL import Image
H = {"Authorization": "Bearer " + os.environ["SUME_API_KEY"]}
def gen(**body):
r = requests.post("https://api.sume.com/v1/images", json=body, headers=H, timeout=60)
r.raise_for_status()
if r.status_code == 202:
raise SystemExit("queued, read /v1/jobs/{id}/result: " + r.text)
return r.json()
def fetch(u):
return Image.open(io.BytesIO(requests.get(u, timeout=60).content))
from PIL import ImageDraw
out = gen(model="bytedance-seed/seedream-4.5", aspect_ratio="4:3",
prompt="Modern living room with a gray sofa and a green plant, daylight")
img = fetch(out["data"][0]["url"]).convert("RGBA")
w, h = img.size
side = int((w * w + h * h) ** 0.5)
layer = Image.new("RGBA", (side, side), (0, 0, 0, 0))
d = ImageDraw.Draw(layer)
for y in range(0, side, 160):
for x in range(0, side, 320):
d.text((x, y), "PROOF", fill=(255, 255, 255, 70))
layer = layer.rotate(30, expand=True)
left, top = (layer.width - w) // 2, (layer.height - h) // 2
layer = layer.crop((left, top, left + w, top + h))
Image.alpha_composite(img, layer).convert("RGB").save("proof.jpg", quality=85)
print("saved", w, h, "cost", out["usage"]["cost"])Settings by purpose
Pick strength by how much damage a leaked draft would do. The proof file is also a good place to drop the quality and size.
| Purpose | Alpha (0-255) | Tile spacing | Export |
|---|---|---|---|
| Internal review | 40 | 480 x 240 | JPEG quality 85 |
| Client draft | 70 | 320 x 160 | JPEG quality 80, 1280 px wide |
| Public preview | 110 | 240 x 120 | JPEG quality 70, 800 px wide |
Practical tips
- Draw white text on dark images and dark text on light ones, or both with an offset, so it shows everywhere.
- Rotate and tile. A single corner mark is cropped out in one click.
- Keep file names distinct:
-proofversus-final, so a draft is never uploaded as a final. - Export proofs at lower size. It cuts transfer time and gives away less.
Fit it in your flow
Generate drafts, stamp proofs, send, and run the approved prompt again for the final if the model is deterministic enough for your needs. Compare candidates with the duplicate finder to avoid sending two near-identical proofs, track prompts with the regression test, and derive web sizes with the srcset recipe.
Sources
Related posts
More in Use cases
- Proposal walkthrough video with an AI avatar: 40 seconds, by tier
Turn a sales quote into a 40-second avatar walkthrough: scope, timeline and price in three scenes, the request body, and the per-quote cost on each Sume tier.
- Quarterly business review recap as an avatar video, three scenes
A QBR recap fits one 45-second Sume avatar video: result, one risk, next steps. A Plus render is $11.03; keep exact figures in the email, not the clip.
- Qwen-Image 2.1 circle-and-edit prompts, 10 references: a test plan
Qwen-Image 2.1 lists circle annotations, mask edits, 10 references. A test plan, plus the Sume route: mask_url and 16 references on ChatGPT Image 2.5.
- Real-estate listing video: 30-second Wan 3.0 clip from ten photos
Turn ten listing photos into a 30 second video on Sume: one reference-to-video clip or ten 3 second photo clips, both $3.75 at 720p. Which is more faithful.
Written by Sume