[object Object]

← back to Wallco Ai

wallco.ai: overlay composer (Pillow) — tile background pattern + scatter overlay elements at lattice intersections · auto-mask near-white pixels as alpha · saves to spoon_all_designs with generator='wallco-overlay'

fcefc8872c2ee41c664eb92e9daf5683ff8b90b8 · 2026-05-11 19:55:12 -0700 · Steve

Files touched

Diff

commit fcefc8872c2ee41c664eb92e9daf5683ff8b90b8
Author: Steve <steve@designerwallcoverings.com>
Date:   Mon May 11 19:55:12 2026 -0700

    wallco.ai: overlay composer (Pillow) — tile background pattern + scatter overlay elements at lattice intersections · auto-mask near-white pixels as alpha · saves to spoon_all_designs with generator='wallco-overlay'
---
 scripts/overlay_compose.py | 119 +++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 119 insertions(+)

diff --git a/scripts/overlay_compose.py b/scripts/overlay_compose.py
new file mode 100755
index 0000000..6eb50ac
--- /dev/null
+++ b/scripts/overlay_compose.py
@@ -0,0 +1,119 @@
+#!/usr/bin/env python3
+"""
+Overlay-on-trellis composer.
+
+Tiles a 'background' pattern image (typically a trellis) into a square,
+then scatters cut-outs of an 'overlay' element (typically butterflies)
+at trellis lattice intersections — semi-random offsets, varied rotation,
+optional scale variation. Output is a single 1024×1024 image that the
+existing wallco palette/tileability pipeline can ingest.
+
+Usage:
+  python3 scripts/overlay_compose.py \\
+    --bg     ~/Projects/wallco-ai/data/inspiration/loma-linda-trellis.png \\
+    --overlay ~/Projects/wallco-ai/data/inspiration/bh-fantasy-butterfly.png \\
+    --out    ~/Projects/wallco-ai/data/generated/corey_loma_bh.png \\
+    --count  6 \\
+    --title "Corey custom: Loma Linda trellis + BH fantasy butterfly"
+
+The overlay image should ideally be alpha-channelled (transparent background).
+If not, the script uses a luminance threshold to mask out near-white pixels.
+"""
+import argparse, random, json, subprocess
+from pathlib import Path
+from PIL import Image, ImageOps, ImageFilter
+
+def load_with_alpha(p):
+    img = Image.open(p)
+    img = ImageOps.exif_transpose(img)
+    if img.mode != 'RGBA':
+        # Auto-mask: pixels near pure white → transparent
+        img = img.convert('RGBA')
+        data = img.getdata()
+        new = []
+        for r, g, b, a in data:
+            if r > 240 and g > 240 and b > 240:
+                new.append((r, g, b, 0))
+            else:
+                new.append((r, g, b, a))
+        img.putdata(new)
+    return img
+
+def tile_background(bg_path, size=1024):
+    bg = Image.open(bg_path).convert('RGB')
+    bg = ImageOps.exif_transpose(bg)
+    # If bg is small, tile; if large, downscale to fit one repeat
+    out = Image.new('RGB', (size, size))
+    bw, bh = bg.size
+    if bw > size or bh > size:
+        bg.thumbnail((size, size), Image.LANCZOS)
+        bw, bh = bg.size
+    for y in range(0, size + bh, bh):
+        for x in range(0, size + bw, bw):
+            out.paste(bg, (x, y))
+    return out.crop((0, 0, size, size))
+
+def compose(bg_path, overlay_path, out_path, count=6, seed=None):
+    if seed is None: seed = random.randint(1, 2**31)
+    random.seed(seed)
+    canvas = tile_background(bg_path, 1024).convert('RGBA')
+    overlay = load_with_alpha(overlay_path)
+    # Normalize overlay to ~22% of canvas width
+    target_w = int(1024 * 0.22)
+    ratio = target_w / overlay.width
+    overlay = overlay.resize((target_w, int(overlay.height * ratio)), Image.LANCZOS)
+
+    placements = []
+    grid = 4   # 4×4 lattice intersections; pick `count` of them at random
+    cells = [(i, j) for i in range(grid) for j in range(grid)]
+    random.shuffle(cells)
+    for i, j in cells[:count]:
+        cx = int((i + 0.5) * 1024 / grid + random.randint(-40, 40))
+        cy = int((j + 0.5) * 1024 / grid + random.randint(-40, 40))
+        angle = random.uniform(-25, 25)
+        scale = random.uniform(0.85, 1.2)
+        w = int(overlay.width * scale); h = int(overlay.height * scale)
+        thumb = overlay.resize((w, h), Image.LANCZOS).rotate(angle, expand=True, resample=Image.BICUBIC)
+        canvas.alpha_composite(thumb, (max(0, cx - thumb.width // 2), max(0, cy - thumb.height // 2)))
+        placements.append({"x": cx, "y": cy, "angle": round(angle, 1), "scale": round(scale, 2)})
+
+    canvas.convert('RGB').save(out_path, 'PNG', quality=95)
+    return { "seed": seed, "placements": placements, "count": count }
+
+def main():
+    ap = argparse.ArgumentParser()
+    ap.add_argument('--bg', required=True)
+    ap.add_argument('--overlay', required=True)
+    ap.add_argument('--out', required=True)
+    ap.add_argument('--count', type=int, default=6)
+    ap.add_argument('--seed', type=int, default=None)
+    ap.add_argument('--title', default='Custom composite')
+    ap.add_argument('--no-db', action='store_true', help='skip PG insert')
+    args = ap.parse_args()
+
+    info = compose(args.bg, args.overlay, args.out, count=args.count, seed=args.seed)
+    print(json.dumps(info, indent=2))
+    print(f"Wrote: {args.out}")
+
+    if not args.no_db:
+        # Insert into spoon_all_designs
+        sql = f"""
+INSERT INTO spoon_all_designs (kind, width_in, height_in, generator, prompt, seed, local_path, is_published, request_text, category)
+VALUES ('seamless_tile', 24, 24, 'wallco-overlay',
+        {repr(args.title).replace(chr(39), chr(39)+chr(39))},
+        {info['seed']},
+        {repr(str(args.out)).replace(chr(39), chr(39)+chr(39))},
+        FALSE,
+        {repr(args.title).replace(chr(39), chr(39)+chr(39))},
+        'mixed')
+RETURNING id;"""
+        sql = sql.replace('"', "'")  # use single quotes for PG literals
+        try:
+            out = subprocess.run(['psql', 'dw_unified', '-At', '-c', sql],
+                                 capture_output=True, text=True, check=True, timeout=10)
+            print(f"PG inserted as design id: {out.stdout.strip()}")
+        except Exception as e:
+            print(f"PG insert skipped: {e}")
+
+if __name__ == '__main__':
+    main()

← 3055bb2 Apply debate-team fixes to /age-themes — implement APCA-W3 L  ·  back to Wallco Ai  ·  tick 10: expand Wikimedia PD categories — +10 (Tapa cloth, I 8d7aa6f →