"""compose.py: put dollz layers where they belong, from a layout JSON (schema vonk-dollz/1). CC0. python compose.py layout.json folder_with_pngs out.png [--zoom 4] [--wear id,id,...] Each item has x, y = its top-left corner in BASE pixels. Draw order = order of "slots" (bottom to top); within a slot only one item is worn. Give this to an agent instead of asking it to guess coordinates. """ import json import sys from pathlib import Path from PIL import Image def compose(layout, folder, worn=None): folder = Path(folder) worn = layout.get("worn", []) if worn is None else worn b = layout["base"] out = Image.new("RGBA", (b["w"], b["h"]), (0, 0, 0, 0)) out.alpha_composite(Image.open(folder / b["src"]).convert("RGBA"), (0, 0)) items = {i["id"]: i for i in layout["items"]} z = {s["slot"]: n for n, s in enumerate(layout["slots"])} for iid in sorted(worn, key=lambda i: z[items[i]["slot"]]): it = items[iid] layer = Image.open(folder / it["src"]).convert("RGBA") # alpha_composite needs the layer inside the canvas: clip it ourselves, so negative offsets work tmp = Image.new("RGBA", out.size, (0, 0, 0, 0)) tmp.paste(layer, (it["x"], it["y"])) out = Image.alpha_composite(out, tmp) return out if __name__ == "__main__": a = sys.argv[1:] zoom = int(a[a.index("--zoom") + 1]) if "--zoom" in a else 1 wear = a[a.index("--wear") + 1].split(",") if "--wear" in a else None lay = json.loads(Path(a[0]).read_text(encoding="utf-8")) im = compose(lay, a[1], wear) if zoom > 1: im = im.resize((im.width * zoom, im.height * zoom), Image.NEAREST) im.save(a[2]) print("wrote", a[2], im.size)