Lavoro della sessione precedente: colonna sonora (titolo, paese, ricordi) con muto e volume, vecchietta e vecchietto sulla panchina, scene di Pieve, oratorio, Via de' Medici, Villa Adami, salita, campeggio, Bosco ai Frati e viale dei cipressi, panorama dell'introduzione, tools/genera.py per gli sfondi dalle foto. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UdeKd62opy9PKgpQXtVctF
65 lines
3.0 KiB
Python
65 lines
3.0 KiB
Python
#!/usr/bin/env python3
|
|
"""Prepara i fotogrammi di un personaggio (già ritagliati da slice_sprites.py) per il motore.
|
|
|
|
python3 tools/slice_sprites.py --sheet assets/raw/andrea-sheet.png --out /tmp/andrea --cols 4 --rows 2 \
|
|
--despill --names walk_1,walk_2,walk_3,walk_4,stand,talk,take,back --prefix andrea_
|
|
python3 tools/prepare_character.py /tmp/andrea andrea assets/characters --height 120
|
|
|
|
Per gli altri personaggi: --frames sit,talk,... --ref sit (i nomi dei fotogrammi e quello che dà l'altezza).
|
|
|
|
Tutti i fotogrammi vengono ridotti con lo stesso fattore (l'altezza del fotogramma --ref diventa --height) e
|
|
salvati in un'unica striscia PNG. Il JSON accanto dice per ogni fotogramma dove sta nella striscia e dove sono
|
|
i piedi: x è il centro del busto (la parte alta del corpo, che in una camminata resta ferma), y la base.
|
|
"""
|
|
import argparse
|
|
import json
|
|
from pathlib import Path
|
|
from PIL import Image
|
|
|
|
FRAMES = ['walk_1', 'walk_2', 'walk_3', 'walk_4', 'stand', 'talk', 'take', 'back']
|
|
|
|
|
|
def torso_center(im):
|
|
"""Centro orizzontale dei pixel opachi nel 40% alto della figura (testa e spalle)."""
|
|
alpha = im.getchannel('A')
|
|
l, t, r, b = alpha.getbbox()
|
|
limit = t + (b - t) * 0.4
|
|
xs = [x for y in range(t, int(limit)) for x in range(l, r) if alpha.getpixel((x, y)) > 128]
|
|
return sum(xs) / len(xs), b
|
|
|
|
|
|
def main():
|
|
p = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
p.add_argument('src', help='cartella con <nome>_<fotogramma>.png')
|
|
p.add_argument('name')
|
|
p.add_argument('out')
|
|
p.add_argument('--height', type=int, default=120)
|
|
p.add_argument('--frames', default=','.join(FRAMES), help='nomi dei fotogrammi, separati da virgola')
|
|
p.add_argument('--ref', default='stand', help='fotogramma la cui altezza diventa --height')
|
|
a = p.parse_args()
|
|
|
|
frames = {f: Image.open(Path(a.src) / f'{a.name}_{f}.png').convert('RGBA') for f in a.frames.split(',')}
|
|
ref = frames[a.ref].getchannel('A').getbbox()
|
|
factor = a.height / (ref[3] - ref[1])
|
|
strip, meta, x = [], {}, 0
|
|
for f, im in frames.items():
|
|
im = im.crop(im.getchannel('A').getbbox())
|
|
cx, base = torso_center(im)
|
|
w, h = max(1, round(im.width * factor)), max(1, round(im.height * factor))
|
|
im = im.resize((w, h), Image.LANCZOS)
|
|
strip.append(im)
|
|
meta[f] = {'x': x, 'y': 0, 'w': w, 'h': h, 'ax': round(cx * factor, 1), 'ay': h}
|
|
x += w + 2
|
|
sheet = Image.new('RGBA', (x, max(im.height for im in strip)), (0, 0, 0, 0))
|
|
for im, m in zip(strip, meta.values()):
|
|
sheet.paste(im, (m['x'], 0))
|
|
out = Path(a.out)
|
|
out.mkdir(parents=True, exist_ok=True)
|
|
sheet.save(out / f'{a.name}.png', optimize=True)
|
|
(out / f'{a.name}.json').write_text(json.dumps({'image': f'{a.name}.png', 'height': a.height, 'frames': meta}, indent=1) + '\n')
|
|
print(f'{out / a.name}.png: {sheet.width}x{sheet.height}, {len(meta)} fotogrammi')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|