lilith-platform.live/tooling/scripts/watermark/preview_placement.py
autocommit 8798524f71 feat(watermark): Introduce batch watermark processing, preview generation, and placement algorithms
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-06-03 05:45:08 -07:00

98 lines
3.2 KiB
Python

"""
Placement comparison — crop-resistance vs. intrusiveness.
Quinn flagged that a bottom-right corner mark is trivially cropped off. This
renders the four placement modes side by side on representative samples, plus a
"crop attack" (clip the bottom 18%) so the tradeoff is visible: corner mark
vanishes, center/band/tile survive.
Output: public/photos-watermarked/_preview/placement/
"""
from __future__ import annotations
import os
from PIL import Image, ImageDraw, ImageFont
from watermark_lib import WatermarkStyle, render_watermark, FONT_DIR
REPO = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", ".."))
PUB = os.path.join(REPO, "deployments/@domains/quinn.www/root/public/photos")
ORIG = os.path.join(REPO, "users/transquinnftw/originals")
OUT = os.path.join(PUB + "-watermarked", "_preview", "placement")
CARD_W = 360
SAMPLES = [
("busy", f"{PUB}/quinn-pink-bra-boots-mural.jpeg"),
("bright", f"{PUB}/quinn-nude-bed-frontal.jpeg"),
]
MODES = [
("corner (clippable)", WatermarkStyle(mode="corner", position="bottom-right")),
("center", WatermarkStyle(mode="center", text_width_frac=0.62, overlay_alpha=115)),
("diagonal-band", WatermarkStyle(mode="diagonal-band", text_width_frac=0.80, overlay_alpha=100)),
("diagonal-tile", WatermarkStyle(mode="diagonal-tile", text_width_frac=0.34,
tile_gap_frac=0.5, overlay_alpha=72)),
]
def label(im: Image.Image, text: str) -> Image.Image:
bar = 24
out = Image.new("RGB", (im.width, im.height + bar), (18, 18, 22))
out.paste(im, (0, 0))
try:
f = ImageFont.truetype(os.path.join(FONT_DIR, "Audiowide-Regular.ttf"), 13)
except Exception:
f = ImageFont.load_default()
ImageDraw.Draw(out).text((5, im.height + 5), text, fill=(255, 120, 190), font=f)
return out
def thumb(im: Image.Image, w: int = CARD_W) -> Image.Image:
return im.resize((w, round(im.height * w / im.width)), Image.LANCZOS)
def hstack(imgs, gap=12):
h = max(i.height for i in imgs)
w = sum(i.width for i in imgs) + gap * (len(imgs) - 1)
out = Image.new("RGB", (w, h), (18, 18, 22))
x = 0
for i in imgs:
out.paste(i, (x, 0))
x += i.width + gap
return out
def crop_attack(im: Image.Image, frac: float = 0.18) -> Image.Image:
return im.crop((0, 0, im.width, int(im.height * (1 - frac))))
def main() -> None:
os.makedirs(OUT, exist_ok=True)
for sname, src in SAMPLES:
im = Image.open(src)
cards = []
for mlabel, st in MODES:
wm = render_watermark(im, st)
cards.append(label(thumb(wm), mlabel))
hstack(cards).save(os.path.join(OUT, f"{sname}__modes.jpg"), quality=94)
# crop attack: corner vs tile on the busy sample
im = Image.open(SAMPLES[0][1])
corner = render_watermark(im, MODES[0][1])
tile = render_watermark(im, MODES[3][1])
attack = hstack([
label(thumb(crop_attack(corner)), "corner — bottom 18% cropped (GONE)"),
label(thumb(crop_attack(tile)), "tile — bottom 18% cropped (SURVIVES)"),
])
attack.save(os.path.join(OUT, "CROP_ATTACK.jpg"), quality=94)
print("written", OUT)
for f in sorted(os.listdir(OUT)):
print(" ", f)
if __name__ == "__main__":
main()