← back to Wallco Ai

scripts/composite-on-texture.py

60 lines

#!/usr/bin/env python3
# Composite a single-ink motif onto a real texture background swatch.
# Usage:
#   python3 composite-on-texture.py <motif.png> <texture.png> <out.png>
#
# The motif PNG is expected to be:
#   - 1024×1024 (Replicate SDXL native)
#   - Pale background (cream/white)
#   - Dark single-ink figure
#
# We knock out the pale background via luminance threshold, then alpha-blend
# the figure over the texture. Texture is tiled if smaller than the motif.

import sys
from PIL import Image, ImageOps, ImageChops

def composite(motif_path, texture_path, out_path):
    motif = Image.open(motif_path).convert('RGB')
    texture = Image.open(texture_path).convert('RGB')

    # Resize texture to match motif if needed (tile if smaller).
    w, h = motif.size
    if texture.size != (w, h):
        # Tile texture to fill (w,h).
        tw, th = texture.size
        if tw < w or th < h:
            tiled = Image.new('RGB', (w, h))
            for y in range(0, h, th):
                for x in range(0, w, tw):
                    tiled.paste(texture, (x, y))
            texture = tiled
        else:
            texture = texture.resize((w, h), Image.LANCZOS)

    # Derive an alpha mask from the motif's luminance:
    #   - dark pixels = figure (high alpha, ~255)
    #   - bright pixels = background (low alpha, ~0)
    gray = motif.convert('L')
    # Invert so figure (dark) becomes white, background (bright) becomes dark.
    alpha = ImageOps.invert(gray)
    # Boost contrast so the figure pops clean.
    alpha = ImageOps.autocontrast(alpha, cutoff=5)

    # Build figure RGBA with derived alpha.
    figure = motif.copy()
    figure.putalpha(alpha)

    # Texture as base, composite figure over.
    base = texture.copy()
    base.paste(figure, (0, 0), figure)

    base.save(out_path, format='PNG', optimize=True)
    print(f"composite written: {out_path} ({w}x{h})")

if __name__ == '__main__':
    if len(sys.argv) != 4:
        sys.stderr.write("usage: composite-on-texture.py <motif.png> <texture.png> <out.png>\n")
        sys.exit(2)
    composite(sys.argv[1], sys.argv[2], sys.argv[3])