Pixellab based system
This commit is contained in:
64
preview.py
64
preview.py
@@ -1,6 +1,8 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Contact sheets for the sprite part library. Writes PNGs into preview/ (or $PREVIEW_OUT).
|
||||
Contact sheets for a sprite part library. Writes PNGs into preview/ (or $PREVIEW_OUT).
|
||||
|
||||
python preview.py [--library pixellab|classic] <command> ... # default library: pixellab
|
||||
|
||||
python preview.py bases # every base creature, bare and lightly varied
|
||||
python preview.py body <name> # one base with markings and a few variations
|
||||
@@ -13,7 +15,7 @@ Contact sheets for the sprite part library. Writes PNGs into preview/ (or $PREVI
|
||||
|
||||
Options: --markings <kind> (default none), --scale N, --type <type> (type ramp)
|
||||
Grid sheets are drawn in three test palettes (dark / light / near-gray), one row block each.
|
||||
Cells with a red frame triggered "canvas expanded". Set PARTS_DIR to preview another library.
|
||||
Cells with a red frame triggered "canvas expanded". Set PARTS_DIR to override the library's parts folder.
|
||||
"""
|
||||
import os
|
||||
import random
|
||||
@@ -23,16 +25,27 @@ from pathlib import Path
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
import generate_sprite as gs
|
||||
import sprite_parts as cfg
|
||||
from sprite_parts import TYPES
|
||||
|
||||
OUT = Path(os.environ.get("PREVIEW_OUT", "preview")) # set PREVIEW_OUT to keep sheets apart
|
||||
PARTS = os.environ.get("PARTS_DIR", "parts")
|
||||
lib = None # registry module, set in main
|
||||
PARTS = None
|
||||
PALETTES = [("dark", ["#0D47A1", "#FFC107"]), ("light", ["#FFEB3B", "#5D4037"]), ("gray", ["#757575", "#E53935"])]
|
||||
BG = (226, 226, 220, 255)
|
||||
LABEL_H = 9
|
||||
|
||||
BY_NAME = {slot: {p["name"]: p for p in parts} for slot, parts in cfg.SLOTS.items()}
|
||||
DEFAULT_BODY = "base_fox"
|
||||
BY_NAME = {}
|
||||
DEFAULT_BODY = None
|
||||
|
||||
|
||||
def use_library(name):
|
||||
"""Point the module state at a registry module."""
|
||||
global lib, PARTS, BY_NAME, DEFAULT_BODY
|
||||
lib = gs.load_library(name)
|
||||
PARTS = os.environ.get("PARTS_DIR", lib.PARTS_DIR)
|
||||
BY_NAME = {slot: {p["name"]: p for p in parts} for slot, parts in lib.SLOTS.items()}
|
||||
names = [b["name"] for b in lib.BODY]
|
||||
DEFAULT_BODY = "base_fox" if "base_fox" in names else (names[0] if names else None)
|
||||
|
||||
|
||||
def P(slot, name):
|
||||
@@ -62,7 +75,7 @@ def allowed(slot, name, chosen):
|
||||
|
||||
def top_type(slot, name):
|
||||
p = P(slot, name)
|
||||
return max(cfg.TYPES, key=lambda t: gs.part_weight(p, t))
|
||||
return max(TYPES, key=lambda t: gs.part_weight(p, t))
|
||||
|
||||
|
||||
def typed(chosen, ptype):
|
||||
@@ -94,7 +107,7 @@ def sheet(cells, path, ptype=None, markings="none", scale=4, cols=6):
|
||||
return
|
||||
cols = min(cols, len(cells))
|
||||
rows_per = (len(cells) + cols - 1) // cols
|
||||
cw, ch = gs.CANVAS, gs.CANVAS + LABEL_H
|
||||
cw, ch = lib.CANVAS, lib.CANVAS + LABEL_H
|
||||
block_h = rows_per * ch + 12
|
||||
img = Image.new("RGBA", (cols * cw, block_h * len(PALETTES)), BG)
|
||||
d = ImageDraw.Draw(img)
|
||||
@@ -104,15 +117,15 @@ def sheet(cells, path, ptype=None, markings="none", scale=4, cols=6):
|
||||
for i, (label, chosen) in enumerate(cells):
|
||||
cx, cy = (i % cols) * cw, y0 + 12 + (i // cols) * ch
|
||||
sprite = compose(chosen, markings)
|
||||
expanded = max(sprite.width + 2, sprite.height + gs.GROUND_MARGIN + 1) > gs.CANVAS
|
||||
spr = colored(gs.place_on_canvas(sprite), colors, typed(chosen, ptype))
|
||||
expanded = max(sprite.width + 2, sprite.height + lib.GROUND_MARGIN + 1) > lib.CANVAS
|
||||
spr = colored(gs.place_on_canvas(sprite, lib), colors, typed(chosen, ptype))
|
||||
if spr.size[0] > cw:
|
||||
spr = spr.crop(((spr.width - cw) // 2, spr.height - gs.CANVAS, (spr.width + cw) // 2, spr.height))
|
||||
spr = spr.crop(((spr.width - cw) // 2, spr.height - lib.CANVAS, (spr.width + cw) // 2, spr.height))
|
||||
img.alpha_composite(spr, (cx, cy))
|
||||
if expanded:
|
||||
d.rectangle((cx, cy, cx + cw - 1, cy + gs.CANVAS - 1), outline=(220, 0, 0, 255))
|
||||
d.rectangle((cx, cy, cx + cw - 1, cy + lib.CANVAS - 1), outline=(220, 0, 0, 255))
|
||||
label = f"{label} {sprite.width}x{sprite.height}!"
|
||||
d.text((cx + 1, cy + gs.CANVAS - 1), label[:19], fill=(40, 40, 40, 255))
|
||||
d.text((cx + 1, cy + lib.CANVAS - 1), label[:19], fill=(40, 40, 40, 255))
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
img.resize((img.width * scale, img.height * scale), Image.NEAREST).save(path)
|
||||
print(path)
|
||||
@@ -184,12 +197,16 @@ def raw_sheet(items, path, scale=8, cols=6):
|
||||
|
||||
|
||||
def variation(b):
|
||||
"""A light, legal variation for a base: first allowed head_top, back and tail in a fixed list."""
|
||||
"""A light, legal variation for a base: first allowed head_top, back and tail in a fixed list
|
||||
(any part of the slot if the library has none of the listed names)."""
|
||||
chosen = {"body": b}
|
||||
prefs = {"head_top": ["leaf_sprout", "crest_flame", "gem_forehead", "horn_single"],
|
||||
"back": ["wings_feather_small", "wings_bug", "spikes_back", "dorsal_fin"],
|
||||
"tail": ["tail_flame_tip", "tail_leaf", "tail_fish_fin", "tail_stinger", "tail_wispy", "tail_rattle"]}
|
||||
for slot, names in prefs.items():
|
||||
known = BY_NAME.get(slot, {})
|
||||
if not any(n in known for n in names):
|
||||
names = [n for n in known if n != "none"]
|
||||
for n in names:
|
||||
if exists(slot, n) and allowed(slot, n, {**chosen, slot: n}):
|
||||
chosen[slot] = n
|
||||
@@ -198,9 +215,9 @@ def variation(b):
|
||||
|
||||
|
||||
def parse_opts(args):
|
||||
opts, rest, it = {"markings": "none", "scale": None, "type": None}, [], iter(args)
|
||||
opts, rest, it = {"markings": "none", "scale": None, "type": None, "library": gs.DEFAULT_LIBRARY}, [], iter(args)
|
||||
for a in it:
|
||||
if a in ("--markings", "--scale", "--type"):
|
||||
if a in ("--markings", "--scale", "--type", "--library"):
|
||||
opts[a[2:]] = next(it)
|
||||
else:
|
||||
rest.append(a)
|
||||
@@ -209,6 +226,10 @@ def parse_opts(args):
|
||||
|
||||
def main(argv):
|
||||
opts, args = parse_opts(argv)
|
||||
if opts["library"] not in gs.LIBRARIES:
|
||||
print(f"unknown library {opts['library']!r}, choose from {', '.join(gs.LIBRARIES)}")
|
||||
return
|
||||
use_library(opts["library"])
|
||||
scale = int(opts["scale"]) if opts["scale"] else None
|
||||
kw = {"markings": opts["markings"], "scale": scale or 4, "ptype": opts["type"]}
|
||||
if not args:
|
||||
@@ -223,7 +244,7 @@ def main(argv):
|
||||
elif cmd == "body":
|
||||
b = args[1]
|
||||
cells = [("bare", {"body": b}), ("varied", variation(b))]
|
||||
cells += [(m, {"body": b, "_markings": m}) for m in cfg.MARKINGS if m != "none"]
|
||||
cells += [(m, {"body": b, "_markings": m}) for m in lib.MARKINGS if m != "none"]
|
||||
sheet(cells, OUT / f"body_{b}.png", **kw)
|
||||
elif cmd == "part":
|
||||
slot, name = args[1], args[2]
|
||||
@@ -258,10 +279,11 @@ def main(argv):
|
||||
groups.append(cur)
|
||||
zoom_sheet(groups, OUT / "zoom.png", scale=scale or 5, markings=opts["markings"], ptype=opts["type"])
|
||||
elif cmd == "all":
|
||||
main(["bases"])
|
||||
for slot in cfg.SLOTS:
|
||||
main(["raw", slot])
|
||||
main(["slot", slot])
|
||||
lflag = ["--library", opts["library"]]
|
||||
main(lflag + ["bases"])
|
||||
for slot in lib.SLOTS:
|
||||
main(lflag + ["raw", slot])
|
||||
main(lflag + ["slot", slot])
|
||||
else:
|
||||
print(__doc__)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user