From f7f65a74fd6795a7136cc11688a8f64f7536c7c1 Mon Sep 17 00:00:00 2001 From: Andrea Manzi Date: Fri, 23 Oct 2026 01:00:36 +0200 Subject: [PATCH] Andrea vero: camminata, parlato, prendere e di spalle dal foglio sprite tools/slice_sprites.py da Manzolo Rush e tools/prepare_character.py, che allinea i fotogrammi sul busto e sui piedi; il motore sceglie il fotogramma da quello che Andrea sta facendo. Co-Authored-By: Claude Opus 5.5 --- Dockerfile | 1 + README.md | 2 + assets/characters/andrea.json | 70 +++++ assets/characters/andrea.png | Bin 0 -> 84361 bytes src/engine/game.js | 28 +- src/engine/render.js | 58 +++- src/main.js | 4 +- src/story/story.js | 2 +- tests/logic.test.mjs | 23 ++ tests/story.test.mjs | 12 +- tools/prepare_character.py | 60 ++++ tools/slice_sprites.py | 562 ++++++++++++++++++++++++++++++++++ 12 files changed, 803 insertions(+), 19 deletions(-) create mode 100644 assets/characters/andrea.json create mode 100644 assets/characters/andrea.png create mode 100644 tools/prepare_character.py create mode 100644 tools/slice_sprites.py diff --git a/Dockerfile b/Dockerfile index c0716a0..d5c144b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,6 +5,7 @@ COPY nginx.conf /etc/nginx/conf.d/default.conf COPY index.html style.css /usr/share/nginx/html/ COPY src /usr/share/nginx/html/src COPY assets/scenes /usr/share/nginx/html/assets/scenes +COPY assets/characters /usr/share/nginx/html/assets/characters EXPOSE 80 HEALTHCHECK --interval=30s --timeout=3s CMD wget -qO- http://127.0.0.1/ >/dev/null || exit 1 diff --git a/README.md b/README.md index a36d19b..e8a490a 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,8 @@ make down - `src/story/`: la storia come dati (`story.js`) e la sua soluzione completa (`solution.js`); - `src/main.js`: collega tutto alla pagina; - `assets/scenes/`: sfondi a 640×360, preparati con `tools/prepare_background.py` dagli originali in `assets/raw/`. +- `assets/characters/`: fogli dei personaggi, tagliati con `tools/slice_sprites.py` e allineati con + `tools/prepare_character.py` (istruzioni in testa al file). ## Test diff --git a/assets/characters/andrea.json b/assets/characters/andrea.json new file mode 100644 index 0000000..0b900b8 --- /dev/null +++ b/assets/characters/andrea.json @@ -0,0 +1,70 @@ +{ + "image": "andrea.png", + "height": 120, + "frames": { + "walk_1": { + "x": 0, + "y": 0, + "w": 65, + "h": 111, + "ax": 36.6, + "ay": 111 + }, + "walk_2": { + "x": 67, + "y": 0, + "w": 35, + "h": 111, + "ax": 17.8, + "ay": 111 + }, + "walk_3": { + "x": 104, + "y": 0, + "w": 64, + "h": 111, + "ax": 34.5, + "ay": 111 + }, + "walk_4": { + "x": 170, + "y": 0, + "w": 38, + "h": 111, + "ax": 21.5, + "ay": 111 + }, + "stand": { + "x": 210, + "y": 0, + "w": 34, + "h": 120, + "ax": 15.1, + "ay": 120 + }, + "talk": { + "x": 246, + "y": 0, + "w": 56, + "h": 121, + "ax": 20.5, + "ay": 121 + }, + "take": { + "x": 304, + "y": 0, + "w": 68, + "h": 97, + "ax": 34.7, + "ay": 97 + }, + "back": { + "x": 374, + "y": 0, + "w": 43, + "h": 120, + "ax": 21.0, + "ay": 120 + } + } +} diff --git a/assets/characters/andrea.png b/assets/characters/andrea.png new file mode 100644 index 0000000000000000000000000000000000000000..22bc5a26932fe47df7a9a19104f4bb92192adedf GIT binary patch literal 84361 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

f7v_`rP#y*{9PVbDK>UykA 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 z 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 ArvLx| literal 0 HcmV?d00001 diff --git a/src/engine/game.js b/src/engine/game.js index 13fc7d0..2e41a88 100644 --- a/src/engine/game.js +++ b/src/engine/game.js @@ -17,6 +17,7 @@ const DEFAULT_LINES = { const SPEED = 95; // Solo profilo, come i cicli di camminata: 'up' e 'down' lasciano il verso com'è. const FACING = { left: -1, right: 1 }; +const WALK = ['walk_1', 'walk_2', 'walk_3', 'walk_4'], STEP = 15, REACH = 0.45; // Condizioni: 'flag', '!flag', 'has:oggetto', '!has:oggetto', oppure un elenco (tutte vere). export function check(cond, state) { @@ -57,12 +58,15 @@ export class Game { this.dialogue = null; this.walking = false; this.time = 0; + this.stride = 0; // strada fatta, per scegliere il passo della camminata + this.reach = 0; // secondi di braccio teso quando prende qualcosa + this.back = false; // di spalle davanti a una porta o a una bacheca } get scene() { return this.story.scenes[this.state.scene]; } // Occupato: c'è qualcosa in corso che il giocatore non può interrompere. La camminata verso un punto // o verso un oggetto si può sempre cambiare con un altro clic: la coda che la seguiva viene scartata. - get busy() { return !!this.speech || (this.task ? !this.task.interruptible : this.queue.length > 0); } + get busy() { return !!this.speech || this.reach > 0 || (this.task ? !this.task.interruptible : this.queue.length > 0); } get idle() { return !this.busy && !this.task && !this.dialogue; } snapshot() { @@ -151,6 +155,15 @@ export class Game { startWalk(p) { this.path = findPath(this.scene.walk, [this.state.x, this.state.y], p); this.walking = this.path.length > 0; + this.back = false; + } + + // Il fotogramma di Andrea in questo istante: un passo ogni STEP pixel percorsi (a grandezza piena). + pose() { + if (this.reach > 0) return 'take'; + if (this.walking) return WALK[Math.floor(this.stride / STEP) % WALK.length]; + if (this.speech?.who === 'andrea') return Math.floor(this.time / 0.2) % 2 ? 'talk' : 'stand'; + return this.back ? 'back' : 'stand'; } scaleAt(y) { @@ -174,8 +187,14 @@ export class Game { if (this.speech.left > 0) return; this.speech = null; } + if (this.reach > 0) { + this.reach -= dt; + if (this.reach > 0) return; + } if (this.walking) { - let step = SPEED * this.scaleAt(this.state.y) * dt; + const scale = this.scaleAt(this.state.y); + let step = SPEED * scale * dt; + this.stride += step / scale; while (step > 0 && this.path.length) { const target = this.path[0], d = dist([this.state.x, this.state.y], target); if (Math.abs(target[0] - this.state.x) > 0.5) this.state.facing = target[0] > this.state.x ? 1 : -1; @@ -203,8 +222,8 @@ export class Game { else if (e.walk) { this.task = e; this.startWalk(e.walk); } // `go` prima di `face`: un cambio scena porta con sé il verso d'arrivo. else if (e.go) this.go(e.go, e.at, e.face); - else if (e.face) s.facing = FACING[e.face] ?? s.facing; - else if (e.give) { if (!s.inventory.includes(e.give)) s.inventory.push(e.give); } + else if (e.face) { s.facing = FACING[e.face] ?? s.facing; this.back = e.face === 'up'; } + else if (e.give) { if (!s.inventory.includes(e.give)) s.inventory.push(e.give); this.reach = REACH; this.back = false; } else if (e.drop) s.inventory = s.inventory.filter(i => i !== e.drop); else if (e.set) s.flags[e.set] = true; else if (e.unset) delete s.flags[e.unset]; @@ -223,6 +242,7 @@ export class Game { this.state.facing = FACING[face] ?? this.state.facing; this.path = []; this.walking = false; + this.back = false; this.queue.unshift(...(this.scene.enter ?? [])); this.onSave(this.snapshot()); } diff --git a/src/engine/render.js b/src/engine/render.js index b6e8771..d2f5c86 100644 --- a/src/engine/render.js +++ b/src/engine/render.js @@ -56,15 +56,50 @@ export function drawAndrea(ctx, x, y, scale, facing, walkPhase) { ctx.restore(); } -// Sfondi delle scene, caricati una volta sola. Finché un'immagine non c'è si vedono le forme `art`. -const backgrounds = new Map(); -export function loadBackgrounds(story) { - return Promise.all(Object.values(story.scenes).filter(s => s.background).map(s => new Promise(resolve => { - const img = new Image(); - img.onload = () => { backgrounds.set(s.background, img); resolve(true); }; - img.onerror = () => resolve(false); - img.src = s.background; - }))); +// Sfondi e fogli dei personaggi, caricati una volta sola. Finché un'immagine non c'è si vedono i segnaposto. +const backgrounds = new Map(), sprites = new Map(); +const loadImage = src => new Promise(resolve => { + const img = new Image(); + img.onload = () => resolve(img); + img.onerror = () => resolve(null); + img.src = src; +}); + +export function loadAssets(story) { + const scenes = Object.values(story.scenes).filter(s => s.background).map(async s => { + const img = await loadImage(s.background); + if (img) backgrounds.set(s.background, img); + return !!img; + }); + const people = Object.entries(story.characters).filter(([, c]) => c.sprite).map(async ([id, c]) => { + try { + const atlas = await (await fetch(c.sprite)).json(); + const img = await loadImage(new URL(atlas.image, new URL(c.sprite, location.href)).href); + if (img) sprites.set(id, { atlas, img }); + return !!img; + } catch { + return false; + } + }); + return Promise.all([...scenes, ...people]); +} + +// Il personaggio alto PERSON pixel a grandezza 1, con i piedi in (x, y); rivolto a sinistra si specchia. +const PERSON = 62; +function drawSprite(ctx, { atlas, img }, frame, x, y, scale, facing) { + const f = atlas.frames[frame] ?? atlas.frames.stand, k = PERSON * scale / atlas.height; + ctx.save(); + ctx.translate(Math.round(x), Math.round(y)); + ctx.fillStyle = 'rgba(0,0,0,0.22)'; + ctx.beginPath(); + ctx.ellipse(0, 0, 13 * scale, 3 * scale, 0, 0, Math.PI * 2); + ctx.fill(); + // La pagina è ingrandita a pixel netti: qui invece si rimpicciolisce, e la media dei pixel evita lo sfarfallio. + ctx.imageSmoothingEnabled = true; + ctx.imageSmoothingQuality = 'high'; + if (frame !== 'back') ctx.scale(facing, 1); + ctx.drawImage(img, f.x, f.y, f.w, f.h, -f.ax * k, -f.ay * k, f.w * k, f.h * k); + ctx.restore(); } export function render(ctx, game, { hover } = {}) { @@ -74,8 +109,9 @@ export function render(ctx, game, { hover } = {}) { if (bg) ctx.drawImage(bg, 0, 0, WIDTH, HEIGHT); for (const shape of scene.art ?? []) drawShape(ctx, shape); for (const h of game.hotspots()) for (const shape of h.art ?? []) drawShape(ctx, shape); - const phase = game.walking ? (s.x + s.y) / 6 : null; - drawAndrea(ctx, s.x, s.y, game.scaleAt(s.y), s.facing, phase); + const andrea = sprites.get('andrea'); + if (andrea) drawSprite(ctx, andrea, game.pose(), s.x, s.y, game.scaleAt(s.y), s.facing); + else drawAndrea(ctx, s.x, s.y, game.scaleAt(s.y), s.facing, game.walking ? game.stride / 6 : null); if (hover) { ctx.strokeStyle = 'rgba(255,255,255,0.35)'; ctx.setLineDash([3, 3]); diff --git a/src/main.js b/src/main.js index 370e6c7..46e9a3d 100644 --- a/src/main.js +++ b/src/main.js @@ -2,7 +2,7 @@ import { story } from './story/story.js'; import { Game, VERBS, VERB_LABELS } from './engine/game.js'; import { Command } from './engine/command.js'; -import { render, loadBackgrounds, WIDTH, HEIGHT } from './engine/render.js'; +import { render, loadAssets, WIDTH, HEIGHT } from './engine/render.js'; import { readSave, writeSave, clearSave } from './engine/storage.js'; const $ = id => document.getElementById(id); @@ -167,4 +167,4 @@ game = new Game(story); command = new Command(game); showTitle(); requestAnimationFrame(frame); -loadBackgrounds(story).then(ok => { if (ok.includes(false)) console.warn('Alcuni sfondi non sono stati caricati.'); }); +loadAssets(story).then(ok => { if (ok.includes(false)) console.warn('Alcune immagini non sono state caricate.'); }); diff --git a/src/story/story.js b/src/story/story.js index 2ce055f..688be81 100644 --- a/src/story/story.js +++ b/src/story/story.js @@ -6,7 +6,7 @@ export const story = { title: 'La Leggenda del Regolo', start: { scene: 'piazza', at: [250, 320] }, characters: { - andrea: { name: 'Andrea', color: '#ffffff' }, + andrea: { name: 'Andrea', color: '#ffffff', sprite: 'assets/characters/andrea.json' }, vecchioA: { name: 'Vecchietto col cappello', color: '#ffd27a' }, vecchioB: { name: 'Vecchietto col bastone', color: '#9fe3ff' }, castello: { name: 'Il Castello', color: '#c8f0a0' }, diff --git a/tests/logic.test.mjs b/tests/logic.test.mjs index 12f7bc1..c82b66b 100644 --- a/tests/logic.test.mjs +++ b/tests/logic.test.mjs @@ -140,3 +140,26 @@ test('lo stato salvato si ricarica uguale', () => { const again = new Game(mini(), { state: JSON.parse(JSON.stringify(g.snapshot())) }); assert.deepEqual(again.snapshot(), g.snapshot()); }); + +test('il fotogramma segue quello che fa Andrea', () => { + const g = new Game(mini()); + assert.equal(g.pose(), 'stand'); + g.walkTo([150, 50]); + const seen = new Set(); + for (let i = 0; i < 40; i++) { g.update(0.05); if (g.walking) seen.add(g.pose()); } + assert.deepEqual([...seen].sort(), ['walk_1', 'walk_2', 'walk_3', 'walk_4']); + g.queue.push({ face: 'up' }); + settle(g); + assert.equal(g.pose(), 'back'); + g.queue.push({ give: 'sasso' }); + g.update(0.01); + assert.equal(g.pose(), 'take'); + assert.ok(g.busy, 'mentre prende qualcosa non si interrompe'); + settle(g); + assert.equal(g.pose(), 'stand'); + g.queue.push({ say: 'Ciao a tutti.' }); + g.update(0.01); + const poses = new Set(); + for (let i = 0; i < 10; i++) { g.time += 0.1; poses.add(g.pose()); } + assert.deepEqual([...poses].sort(), ['stand', 'talk']); +}); diff --git a/tests/story.test.mjs b/tests/story.test.mjs index 91aa9e4..91a62ea 100644 --- a/tests/story.test.mjs +++ b/tests/story.test.mjs @@ -4,7 +4,7 @@ import { story } from '../src/story/story.js'; import { solution } from '../src/story/solution.js'; import { Game, VERBS } from '../src/engine/game.js'; import { inside } from '../src/engine/geometry.js'; -import { existsSync } from 'node:fs'; +import { existsSync, readFileSync } from 'node:fs'; import { settle } from './helpers.mjs'; const EFFECTS = ['say', 'walk', 'face', 'give', 'drop', 'set', 'unset', 'if', 'dialogue', 'node', 'endDialogue', 'go', 'end']; @@ -95,3 +95,13 @@ test('la soluzione si percorre tutta dall\'inizio alla fine', () => { assert.ok(g.state.flags.indizio_regolo); assert.ok(saves.at(-1).ended, 'la fine non è salvata'); }); + +test('i fogli dei personaggi hanno tutti i fotogrammi e l\'immagine', () => { + for (const [id, c] of Object.entries(story.characters)) { + if (!c.sprite) continue; + const url = new URL('../' + c.sprite, import.meta.url); + const atlas = JSON.parse(readFileSync(url, 'utf8')); + assert.ok(existsSync(new URL(atlas.image, url)), `${id}: manca ${atlas.image}`); + for (const f of ['walk_1', 'walk_2', 'walk_3', 'walk_4', 'stand', 'talk', 'take', 'back']) assert.ok(atlas.frames[f], `${id}: manca ${f}`); + } +}); diff --git a/tools/prepare_character.py b/tools/prepare_character.py new file mode 100644 index 0000000..da4f47d --- /dev/null +++ b/tools/prepare_character.py @@ -0,0 +1,60 @@ +#!/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 + +Tutti i fotogrammi vengono ridotti con lo stesso fattore (l'altezza del fotogramma `stand` 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 _.png') + p.add_argument('name') + p.add_argument('out') + p.add_argument('--height', type=int, default=120) + a = p.parse_args() + + frames = {f: Image.open(Path(a.src) / f'{a.name}_{f}.png').convert('RGBA') for f in FRAMES} + stand = frames['stand'] + factor = a.height / (stand.getchannel('A').getbbox()[3] - stand.getchannel('A').getbbox()[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() diff --git a/tools/slice_sprites.py b/tools/slice_sprites.py new file mode 100644 index 0000000..2054ec8 --- /dev/null +++ b/tools/slice_sprites.py @@ -0,0 +1,562 @@ +#!/usr/bin/env python3 +"""Ritaglia lo sprite sheet su sfondo verde chroma in singoli PNG trasparenti. + +Uso: + python3 tools/slice_sprites.py [--sheet assets/raw/spritesheet.png] [--names main] + [--out assets/sprites] [--cols 4 --rows 5] + [--thresh 200] [--pad 4] [--no-key --inset 6] + +Esempi (vedi PROMPT_SPRITES.md per i tre sheet aggiuntivi): + python3 tools/slice_sprites.py --sheet assets/raw/sheet_hero_rear.png --names hero_rear --cols 4 --rows 5 + python3 tools/slice_sprites.py --sheet assets/raw/sheet_textures.png --names textures --cols 4 --rows 4 --no-key + python3 tools/slice_sprites.py --sheet assets/raw/sheet_props.png --names props --cols 4 --rows 4 + +Dipendenze: solo Pillow. La griglia viene rilevata cercando righe/colonne +interamente verdi (gutter); --cols/--rows forzano una griglia fissa se il +rilevamento non torna. +""" +import argparse +import json +import sys +from pathlib import Path + +from PIL import Image, ImageChops, ImageDraw, ImageFilter + +# Nomi delle celle di ogni sheet, riga per riga, da sinistra a destra (vedi PROMPT_SPRITES.md). +SHEETS = { + "main": [ + "hero_run_1", "hero_run_2", "hero_run_3", "hero_run_4", + "hero_jump", "hero_slide", "hero_shoot", "weapon_blaster", + "enemy_grunt", "enemy_dodger", "enemy_drone", "enemy_tank", + "coin", "powerup_triple", "powerup_shield", "powerup_magnet", + "powerup_multiplier", "bullet", "explosion", "boss_head", + ], + "hero_rear": [ + "hero_back_run_1", "hero_back_run_2", "hero_back_run_3", "hero_back_run_4", + "hero_back_jump", "hero_back_slide", "hero_back_shoot", "hero_back_lean", + "enemy_front_grunt", "enemy_front_dodger", "enemy_front_drone", "enemy_front_tank", + "hero_front_idle", "hero_front_run", "hero_front_victory", "hero_portrait", + "hero_front_hurt", "hero_front_tired", "hero_front_thumbs", "boss_head_front", + ], + "textures": [ + "city_facade_a", "city_facade_b", "city_road", "city_sidewalk", + "desert_facade_a", "desert_facade_b", "desert_road", "desert_ground", + "neon_facade_a", "neon_facade_b", "neon_road", "neon_plates", + "crate", "barrier", "train_side", "container", + ], + "props": [ + "prop_city_lamp", "prop_city_tree", "prop_city_busstop", "prop_city_sign", + "prop_desert_cactus", "prop_desert_tree", "prop_desert_barrels", "prop_desert_sign", + "prop_neon_antenna", "prop_neon_holo", "prop_neon_pylon", "prop_neon_canisters", + "prop_cone", "prop_block", "prop_bush", "prop_rocks", + ], + "nature_textures": [ + "forest_facade_a", "forest_facade_b", "forest_road", "forest_ground", + "volcano_facade_a", "volcano_facade_b", "volcano_road", "volcano_ground", + "snow_facade_a", "snow_facade_b", "snow_road", "snow_ground", + ], + "nature_props": [ + "prop_forest_tree", "prop_forest_arch", "prop_forest_pillar", "prop_forest_ferns", + "prop_volcano_basalt", "prop_volcano_vent", "prop_volcano_tree", "prop_volcano_crystals", + "prop_snow_fir", "prop_snow_crystals", "prop_snow_sign", "prop_snow_rocks", + ], + # Personaggio giocabile (sezione 7 di PROMPT_SPRITES.md), da usare con --prefix _ + "character": [ + "back_run_1", "back_run_2", "back_run_3", "back_run_4", + "back_jump", "back_slide", "back_shoot", "back_lean", + "front_idle", "portrait", "front_thumbs", "front_tired", + ], + # Secondo boss: la nuvola temporalesca (pose, colpi e impatti). + "storm": [ + "storm_front", "storm_angry", "storm_left", "storm_back", "storm_right", "storm_front_2", + "storm_glare", "storm_roar", "storm_blow", "storm_charge", "storm_tornado", "storm_hurt", + "storm_drop", "storm_drops", "storm_bolt", "storm_strike", "storm_twister", "storm_gust", + "storm_splash", "storm_splash_big", "storm_bolt_impact", "storm_burst", "storm_rain", "storm_twister_impact", + ], + # Nemico demone (al posto dei robot): righe da 7, 6, 6, 6, 6 celle. + "demon": [ + "demon_front", "demon_turn_1", "demon_left", "demon_back", "demon_turn_4", "demon_right", "demon_angry", + "demon_run_1", "demon_run_2", "demon_run_3", "demon_run_4", "demon_run_5", "demon_run_6", + "demon_leap", "demon_flail", "demon_breath", "demon_slash", "demon_pound", "demon_ready", + "demon_hit", "demon_knock", "demon_fall", "demon_ko", "demon_dazed", "demon_dead", + "demon_fireball_1", "demon_fireball_2", "demon_fireball_3", "demon_ring", "demon_blast", "demon_pillar", + ], + # Nemico di roccia: stessa disposizione del demone. + "rock": [ + "rock_front", "rock_turn_1", "rock_left", "rock_back", "rock_turn_4", "rock_right", "rock_angry", + "rock_run_1", "rock_run_2", "rock_run_3", "rock_run_4", "rock_run_5", "rock_run_6", + "rock_windup", "rock_throw", "rock_lift", "rock_pound", "rock_swing", "rock_ready", + "rock_hit", "rock_knock", "rock_fall", "rock_down", "rock_dazed", "rock_dead", + "rock_boulder_1", "rock_boulders", "rock_boulder_2", "rock_blast", "rock_spikes", "rock_ring", + ], + # Nemico di ferro: righe da 7, 6, 5, 6, 6 celle. + "iron": [ + "iron_front", "iron_turn_1", "iron_left", "iron_back", "iron_turn_4", "iron_right", "iron_angry", + "iron_run_1", "iron_run_2", "iron_run_3", "iron_run_4", "iron_run_5", "iron_run_6", + "iron_windup", "iron_punch", "iron_pound", "iron_charge", "iron_spin", + "iron_dazed", "iron_hit", "iron_knock", "iron_fall", "iron_down", "iron_dead", + "iron_spikeball", "iron_spikeballs", "iron_drill", "iron_blast", "iron_pillar", "iron_ring", + ], + # Camaleonte che si mimetizza (pose "ghost" semitrasparenti): righe da 8, 7, 5, 6, 7 celle. + "chameleon": [ + "cham_front", "cham_turn_1", "cham_ghost_front", "cham_back", "cham_turn_4", "cham_ghost_side", "cham_ghost_eyes", "cham_angry", + "cham_sneak_1", "cham_sneak_2", "cham_sneak_3", "cham_sneak_4", "cham_sneak_5", "cham_sneak_6", "cham_sneak_7", + "cham_leap", "cham_swipe", "cham_slash", "cham_emerge", "cham_spin", + "cham_fade_1", "cham_fade_2", "cham_fade_3", "cham_fade_4", "cham_appear", "cham_fade_5", + "cham_smoke", "cham_leafburst", "cham_tornado", "cham_leaves", "cham_ring", "cham_blast", "cham_dart", + ], + # Boss di magma: righe da 8, 8, 6, 6 celle (--row-cols 8,8,6,6). + "magma": [ + "magma_front", "magma_turn_1", "magma_turn_2", "magma_turn_3", "magma_back", "magma_turn_5", "magma_turn_6", "magma_front_2", + "magma_glare", "magma_roar", "magma_blow", "magma_charge", "magma_tornado", "magma_erupt", "magma_dash", "magma_hurt", + "magma_rock", "magma_rocks", "magma_flame", "magma_boulder", "magma_spikes", "magma_wave", + "magma_splash", "magma_splash_big", "magma_pillar", "magma_burst", "magma_ring", "magma_crystals", + ], + # Boss di ghiaccio: quattro righe da 7 celle (--row-cols 7,7,7,7). + "ice": [ + "ice_front", "ice_turn_1", "ice_turn_2", "ice_back", "ice_turn_4", "ice_angry", "ice_front_2", + "ice_glare", "ice_roar", "ice_blow", "ice_stomp", "ice_charge", "ice_dash", "ice_hurt", + "ice_shard", "ice_shards", "ice_volley", "ice_tornado", "ice_spike", "ice_boulder", "ice_spikes", + "ice_splash", "ice_splash_big", "ice_pillar", "ice_burst", "ice_twister_impact", "ice_crystals", "ice_debris", + ], + "weapons": [ + "weapon_scatter", "weapon_plasma", "weapon_laser", "weapon_rocket", + "shot_scatter", "shot_plasma", "shot_laser", "shot_rocket", + "impact_scatter", "impact_plasma", "impact_laser", "impact_rocket", + ], +} +NAMES = SHEETS["main"] +SENTINEL = (255, 0, 255) # colore usato dal flood fill per marcare lo sfondo + + +def anchor_for(name: str) -> str: + return "bottom-center" if name.startswith(("hero_", "enemy_", "prop_")) else "center" + + +def sample_background(im: Image.Image) -> tuple: + """Colore verde dominante più frequente nello sheet (lo sfondo delle celle, + non le linee della griglia che possono essere di un verde più scuro).""" + small = im.convert("RGB").resize((max(1, im.width // 4), max(1, im.height // 4))) + counts = {} + for p in small.get_flattened_data() if hasattr(small, "get_flattened_data") else small.getdata(): + if p[1] > p[0] + 100 and p[1] > p[2] + 100: + counts[p] = counts.get(p, 0) + 1 + if not counts: + sys.exit("nessuno sfondo verde trovato nello sheet") + return max(counts, key=counts.get) + + +def find_runs(flags): + """Trasforma una lista di booleani in intervalli [start, end) di True.""" + runs, start = [], None + for i, f in enumerate(flags): + if f and start is None: + start = i + elif not f and start is not None: + runs.append((start, i)) + start = None + if start is not None: + runs.append((start, len(flags))) + return runs + + +def is_screen(p) -> bool: + """Verde "da schermo" per tinta: sfondo chiaro e linee della griglia verde scuro + (es. (0,28,0)) contano entrambi; i verdi degli oggetti hanno rosso/blu più alti.""" + r, g, b = p[0], p[1], p[2] + m = max(r, b) + return m < 45 and g > 2 * m + 18 + + +def screen_mask(im: Image.Image) -> Image.Image: + """Maschera L: 255 dove il pixel è verde da schermo.""" + rgb = im.convert("RGB") + mask = Image.new("L", rgb.size) + data = rgb.get_flattened_data() if hasattr(rgb, "get_flattened_data") else rgb.getdata() + mask.putdata([255 if is_screen(p) else 0 for p in data]) + return mask + + +def detect_grid(im: Image.Image, bg: tuple, min_frac: float = .995, min_cell: int = 20): + """Restituisce (colonne, righe) come liste di intervalli di celle. + Una colonna/riga è gutter se almeno `min_frac` dei suoi pixel è verde da schermo.""" + mask = screen_mask(im) + w, h = mask.size + col_is_bg = [mask.crop((x, 0, x + 1, h)).histogram()[255] >= min_frac * h for x in range(w)] + row_is_bg = [mask.crop((0, y, w, y + 1)).histogram()[255] >= min_frac * w for y in range(h)] + cols = [r for r in find_runs([not c for c in col_is_bg]) if r[1] - r[0] >= min_cell] + rows = [r for r in find_runs([not c for c in row_is_bg]) if r[1] - r[0] >= min_cell] + return cols, rows + + +def fixed_grid(size: int, n: int): + step = size / n + return [(round(i * step), round((i + 1) * step)) for i in range(n)] + + +def snapped_grid(im: Image.Image, cols: int, rows: int, window: float = .06): + """Griglia "agganciata": per ogni linea attesa (i * lato / n) cerca entro ±window + la fascia di colonne/righe più verdi (gutter + linea scura) e taglia le celle ai + suoi bordi. Regge gutter irregolari e oggetti che sfiorano la linea.""" + mask = screen_mask(im) + w, h = mask.size + col_frac = [mask.crop((x, 0, x + 1, h)).histogram()[255] / h for x in range(w)] + row_frac = [mask.crop((0, y, w, y + 1)).histogram()[255] / w for y in range(h)] + + def axis(frac, size, n): + edges = [(0, 0)] + for i in range(1, n): + c = round(i * size / n) + lo, hi = max(1, c - round(window * size)), min(size - 1, c + round(window * size)) + best = max(range(lo, hi), key=lambda k: frac[k]) + # allargo la fascia finché resta quasi tutta verde + limit = max(.9, frac[best] * .97) + a = b = best + while a - 1 > lo and frac[a - 1] >= limit: a -= 1 + while b + 1 < hi and frac[b + 1] >= limit: b += 1 + edges.append((a, b + 1)) + edges.append((size, size)) + return [(edges[i][1], edges[i + 1][0]) for i in range(n)] + + return axis(col_frac, w, cols), axis(row_frac, h, rows) + + +def line_cuts(im: Image.Image, n: int, axis: int, min_frac: float = .3): + """Intervalli delle n celle lungo un asse (0 = colonne, 1 = righe) usando le sottili + linee scure (nere o verde scuro) di separazione; None se non se ne trovano esattamente n - 1.""" + rgb = im.convert("RGB") + gray = Image.new("L", rgb.size) + data = rgb.get_flattened_data() if hasattr(rgb, "get_flattened_data") else rgb.getdata() + gray.putdata([255 if r < 8 and b < 14 and g < 170 else 0 for r, g, b in data]) + w, h = gray.size + size, other = (w, h) if axis == 0 else (h, w) + def frac(k): + box = (k, 0, k + 1, h) if axis == 0 else (0, k, w, k + 1) + return gray.crop(box).histogram()[255] / other + lines = find_runs([frac(k) >= min_frac for k in range(size)]) + lines = [r for r in lines if 0 < r[0] and r[1] < size and r[1] - r[0] < 12] + if len(lines) != n - 1: + return None + edges = [0] + [x for r in lines for x in r] + [size] + return [(edges[2 * i], edges[2 * i + 1]) for i in range(n)] + + +def key_out_background(cell: Image.Image, bg: tuple, thresh: int, band: int = 8, + min_hole: int = 60, despill: bool = False) -> Image.Image: + """Chroma key "green excess" in tre passi: + + 1. maschera dura: verde puro e brillante ovunque (anche nelle sacche chiuse, + es. tra le gambe) + verde puro più scuro (linee della griglia) solo se + collegato al bordo della cella, così i verdi interni di un oggetto + (es. la capsula del magnete) sopravvivono; + 2. in una fascia di `band` px attorno alla maschera, alpha morbida stimata + dall'eccesso di verde e colore "smiscelato" dallo sfondo (niente frangia); + 3. tutto il resto resta opaco. + """ + rgb = cell.convert("RGB") + w, h = rgb.size + px = rgb.load() + bg_g = max(bg[1], 1) + + def excess(p): # quanto il pixel è "schermo verde": 0 = per niente, ~bg_g = sfondo puro + return p[1] - max(p[0], p[2]) + + hard = Image.new("L", (w, h), 0) # 255 = sfondo certo + cand = Image.new("L", (w, h), 0) # 255 = verde puro scuro, da confermare via flood fill + hp, cp = hard.load(), cand.load() + for y in range(h): + for x in range(w): + p = px[x, y] + d, s = excess(p), max(p[0], p[2]) + if d > thresh: + hp[x, y] = 255 + elif is_screen(p): # verde scuro delle linee di griglia: sfondo solo se collegato al bordo + cp[x, y] = 255 + # flood fill dei candidati collegati al bordo + for x in range(w): + for y in (0, h - 1): + if cp[x, y] == 255: + ImageDraw.floodfill(cand, (x, y), 128) + for y in range(h): + for x in (0, w - 1): + if cp[x, y] == 255: + ImageDraw.floodfill(cand, (x, y), 128) + for y in range(h): + for x in range(w): + if cp[x, y] == 128 or (cp[x, y] == 255 and hp[x, y] == 255): + hp[x, y] = 255 + # i candidati scuri adiacenti alla maschera dura sono comunque sfondo + grown = hard.filter(ImageFilter.MaxFilter(3)) + gp = grown.load() + for y in range(h): + for x in range(w): + if cp[x, y] == 255 and gp[x, y] == 255: + hp[x, y] = 255 + # componenti di sfondo isolate e minuscole (riflessi verdi dentro un oggetto) + # non sono buchi veri: le riporto a opaco + for y in range(h): + for x in range(w): + if hp[x, y] == 255: + ImageDraw.floodfill(hard, (x, y), 100) + area = hard.histogram()[100] + ImageDraw.floodfill(hard, (x, y), 0 if area < min_hole else 200) + for y in range(h): + for x in range(w): + if hp[x, y] == 200: + hp[x, y] = 255 + + # fascia di transizione attorno alla maschera dura + ring = hard.filter(ImageFilter.MaxFilter(2 * band + 1)) + rp = ring.load() + out = rgb.copy() + op = out.load() + alpha = Image.new("L", (w, h), 255) + ap = alpha.load() + for y in range(h): + for x in range(w): + if hp[x, y] == 255: + ap[x, y] = 0 + continue + if rp[x, y] != 255: + continue + p = px[x, y] + d = excess(p) + if d <= 0: + continue + a = max(0.0, min(1.0, 1.0 - d / bg_g)) + if a < 0.04: + ap[x, y] = 0 + continue + # smiscelo il colore dallo sfondo: c = (p - (1-a)*bg) / a + c = [max(0, min(255, round((p[i] - (1 - a) * bg[i]) / a))) for i in range(3)] + if despill and c[1] > max(c[0], c[2]): # solo per sheet senza oggetti verdi + c[1] = max(c[0], c[2]) + op[x, y] = tuple(c) + ap[x, y] = round(a * 255) + if despill: + # Passata globale (solo sheet senza oggetti verdi): i pixel ancora "verde schermo", + # anche fuori dalla fascia di bordo (tra i ricci, nel fumo), diventano trasparenti + # in proporzione al verde e perdono la dominante verde. + for y in range(h): + for x in range(w): + if ap[x, y] == 0: + continue + p = px[x, y] + e = p[1] - max(p[0], p[2]) + if e > 18: + ap[x, y] = round(ap[x, y] * max(0.0, 1 - (e - 18) / (bg_g * .5))) + r, g, b = op[x, y] + op[x, y] = (r, min(g, max(r, b)), b) + out.putalpha(alpha) + return out + + +def drop_border_slivers(img: Image.Image, max_frac: float = 0.015) -> Image.Image: + """Elimina i frammenti piccoli attaccati al bordo della cella (pezzi di + sprite vicini finiti nel ritaglio quando la griglia non è perfetta).""" + alpha = img.getchannel("A") + w, h = alpha.size + solid = alpha.point(lambda v: 255 if v > 32 else 0) + sp = solid.load() + limit = max_frac * w * h + border = [(x, y) for x in range(w) for y in (0, h - 1)] + [(x, y) for y in range(h) for x in (0, w - 1)] + cleared = False + for x, y in border: + if sp[x, y] == 255: + ImageDraw.floodfill(solid, (x, y), 100) + area = solid.histogram()[100] + if area < limit: + ImageDraw.floodfill(solid, (x, y), 50) # da cancellare + cleared = True + else: + ImageDraw.floodfill(solid, (x, y), 200) # da tenere + if not cleared: + return img + ap = alpha.load() + for y in range(h): + for x in range(w): + if sp[x, y] == 50: + ap[x, y] = 0 + out = img.copy() + out.putalpha(alpha) + return out + + +def trim(img: Image.Image, pad: int, min_alpha: int = 1) -> Image.Image: + # Ignora l'alone quasi invisibile nel calcolo dei margini, preservando l'alpha + # originale dentro il ritaglio (nessun chroma key sugli sprite già trasparenti). + bbox = img.getchannel("A").point(lambda a: 255 if a >= min_alpha else 0).getbbox() + if not bbox: + return img + l, t, r, b = bbox + l, t = max(0, l - pad), max(0, t - pad) + r, b = min(img.width, r + pad), min(img.height, b + pad) + return img.crop((l, t, r, b)) + + +def checkerboard(size, tile=16): + board = Image.new("RGBA", size, (200, 200, 200, 255)) + d = ImageDraw.Draw(board) + for y in range(0, size[1], tile): + for x in range(0, size[0], tile): + if (x // tile + y // tile) % 2: + d.rectangle((x, y, x + tile - 1, y + tile - 1), fill=(150, 150, 150, 255)) + return board + + +def main(): + ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + ap.add_argument("--sheet", default="assets/raw/spritesheet.png") + ap.add_argument("--out", default="assets/sprites") + ap.add_argument("--cols", type=int, default=0, help="forza il numero di colonne") + ap.add_argument("--rows", type=int, default=0, help="forza il numero di righe") + ap.add_argument("--fixed-grid", action="store_true", help="celle esattamente uguali, senza cercare separatori verdi") + ap.add_argument("--alpha", action="store_true", help="preserva l'alpha originale e rifila i margini trasparenti, senza chroma key") + ap.add_argument("--row-cols", default="", + help="celle per riga quando le righe hanno larghezze diverse (es. 8,8,6,6)") + ap.add_argument("--thresh", type=int, default=200, + help="eccesso di verde (g - max(r,b)) oltre cui un pixel è sfondo certo (0-255)") + ap.add_argument("--pad", type=int, default=4) + ap.add_argument("--band", type=int, default=8, help="larghezza in px della fascia di smiscelamento ai bordi") + ap.add_argument("--despill", action="store_true", + help="toglie il verde residuo dai bordi (solo per sheet senza oggetti verdi)") + ap.add_argument("--min-hole", type=int, default=60, help="area minima (px) di un buco interno per essere tenuto") + ap.add_argument("--small", type=int, default=128, help="lato della versione ridotta per l'HUD") + ap.add_argument("--webp-quality", type=int, default=88, help="qualità WebP (0-100)") + ap.add_argument("--names", default="main", + help="preset (" + ", ".join(SHEETS) + ") oppure elenco di nomi separati da virgola") + ap.add_argument("--prefix", default="", help="prefisso dei nomi (es. erika_ per un personaggio)") + ap.add_argument("--same-box", action="store_true", + help="stesso ritaglio per tutte le celle (cicli di animazione: il corpo non salta di lato)") + ap.add_argument("--no-key", action="store_true", + help="niente chroma key: ritaglia le celle piene (texture) con un bordo interno di --inset px") + ap.add_argument("--inset", type=int, default=6, help="pixel scartati sul bordo di ogni cella con --no-key") + args = ap.parse_args() + if args.fixed_grid and (not args.cols or not args.rows or args.row_cols): + ap.error("--fixed-grid richiede --cols e --rows, senza --row-cols") + if args.alpha and args.no_key: + ap.error("usa --alpha per sprite trasparenti oppure --no-key per texture piene") + names = SHEETS[args.names] if args.names in SHEETS else [n.strip() for n in args.names.split(",") if n.strip()] + names = [args.prefix + n for n in names] + preset = args.names if args.names in SHEETS else "custom" + + sheet = Image.open(args.sheet).convert("RGBA") + bg = (0, 255, 0) if args.fixed_grid and (args.alpha or args.no_key) else sample_background(sheet) + print(f"sheet {sheet.size[0]}x{sheet.size[1]}, sfondo campionato {bg}") + + if args.fixed_grid: + cols = [(round(i * sheet.width / args.cols), round((i + 1) * sheet.width / args.cols)) for i in range(args.cols)] + rows = [(round(i * sheet.height / args.rows), round((i + 1) * sheet.height / args.rows)) for i in range(args.rows)] + cells = [(x0, y0, x1, y1) for y0, y1 in rows for x0, x1 in cols] + ncols = args.cols + print(f"griglia esatta: {args.cols}x{args.rows}") + elif args.row_cols: + # Righe con celle di larghezza diversa: prima le linee orizzontali, poi le + # verticali di ogni riga, entrambe agganciate alle fasce più verdi. + counts = [int(c) for c in args.row_cols.split(",")] + rows = line_cuts(sheet, len(counts), 1) or snapped_grid(sheet, 1, len(counts))[1] + cells = [] + for (y0, y1), n in zip(rows, counts): + band = sheet.crop((0, y0, sheet.width, y1)) + band_cols = line_cuts(band, n, 0) or snapped_grid(band, n, 1)[0] + cells += [(x0, y0, x1, y1) for x0, x1 in band_cols] + ncols = max(counts) + print(f"griglia per righe: {counts}") + else: + cols, rows = detect_grid(sheet, bg) + if args.cols and args.rows and (len(cols) != args.cols or len(rows) != args.rows): + print(f"griglia rilevata {len(cols)}x{len(rows)} diversa da quella richiesta: aggancio le linee attese") + cols, rows = snapped_grid(sheet, args.cols, args.rows) + print(f"griglia: {len(cols)} colonne x {len(rows)} righe") + cells = [(x0, y0, x1, y1) for (y0, y1) in rows for (x0, x1) in cols] + ncols = len(cols) + if len(cells) != len(names): + sys.exit(f"attese {len(names)} celle, trovate {len(cells)}: usa --cols/--rows o --row-cols") + + out_dir = Path(args.out) + small_dir = out_dir / str(args.small) + out_dir.mkdir(parents=True, exist_ok=True) + small_dir.mkdir(parents=True, exist_ok=True) + + # L'atlas accumula gli sheet: si aggiornano solo le voci di questo sheet. + atlas_path = out_dir / "atlas.json" + atlas = json.loads(atlas_path.read_text()) if atlas_path.exists() else {} + sprites = [] + # --same-box: un unico riquadro (unione dei contenuti di tutte le celle, in coordinate di cella) + # per ogni frame, così personaggio e linea dei piedi restano nello stesso punto. + keyed, box = {}, None + if args.same_box: + for name, (x0, y0, x1, y1) in zip(names, cells): + keyed[name] = drop_border_slivers(key_out_background(sheet.crop((x0, y0, x1, y1)), bg, args.thresh, args.band, args.min_hole, args.despill)) + b = keyed[name].getchannel("A").point(lambda a: 255 if a >= 1 else 0).getbbox() + if b: + box = b if box is None else (min(box[0], b[0]), min(box[1], b[1]), max(box[2], b[2]), max(box[3], b[3])) + for name, (x0, y0, x1, y1) in zip(names, cells): + if True: + cell = sheet.crop((x0, y0, x1, y1)) + if args.alpha: + sprite = trim(cell, args.pad, min_alpha=32) + elif args.no_key and args.fixed_grid: + k = args.inset + sprite = cell.crop((k, k, cell.width - k, cell.height - k)) + elif args.no_key: + # Texture piena: rifilo righe/colonne di bordo con più del 2% di verde + # da schermo, poi scarto `inset` px di sicurezza. + mask = screen_mask(cell) + l, t, r, b = 0, 0, cell.width, cell.height + mx, my = cell.width // 8, cell.height // 8 # mai più di 1/8 per lato + green = lambda box, n, f: mask.crop(box).histogram()[255] > f * n + # prima i margini quasi interamente verdi, poi i residui sottili + for f in (.5, .02): + while l < mx and green((l, t, l + 1, b), b - t, f): l += 1 + while r > cell.width - mx and green((r - 1, t, r, b), b - t, f): r -= 1 + while t < my and green((l, t, r, t + 1), r - l, f): t += 1 + while b > cell.height - my and green((l, b - 1, r, b), r - l, f): b -= 1 + k = args.inset + sprite = cell.crop((l + k, t + k, r - k, b - k)) + elif args.same_box and box: + k = keyed[name] + sprite = k.crop((max(0, box[0] - args.pad), max(0, box[1] - args.pad), min(k.width, box[2] + args.pad), min(k.height, box[3] + args.pad))) + else: + sprite = key_out_background(cell, bg, args.thresh, args.band, args.min_hole, args.despill) + sprite = trim(drop_border_slivers(sprite), args.pad) + alpha_bbox = sprite.getchannel("A").getbbox() + coverage = sum(sprite.getchannel("A").histogram()[33:]) / (sprite.width * sprite.height) + if not alpha_bbox or coverage < 0.02: + print(f" ATTENZIONE: {name} quasi vuoto (copertura {coverage:.1%})") + sprite.save(out_dir / f"{name}.png") + # WebP con alpha: ~10x più leggero del PNG, è quello che carica il gioco. + sprite.save(out_dir / f"{name}.webp", quality=args.webp_quality, method=6) + small = sprite.copy() + small.thumbnail((args.small, args.small), Image.LANCZOS) + small.save(small_dir / f"{name}.png") + small.save(small_dir / f"{name}.webp", quality=args.webp_quality, method=6) + atlas[name] = { + "file": f"{name}.webp", + "png": f"{name}.png", + "small": f"{args.small}/{name}.webp", + "width": sprite.width, + "height": sprite.height, + "anchor": anchor_for(name), + "cell": [x0, y0, x1, y1], + } + sprites.append((name, sprite)) + print(f" {name:20s} {sprite.width:4d}x{sprite.height:<4d} copertura {coverage:.0%}") + + atlas_path.write_text(json.dumps(atlas, indent=2) + "\n") + + # Anteprima su scacchiera per controllo visivo. + tile = max(max(s.width, s.height) for _, s in sprites) + nrows = -(-len(sprites) // ncols) + prev = checkerboard((tile * ncols, tile * nrows)) + for k, (_, s) in enumerate(sprites): + px = (k % ncols) * tile + (tile - s.width) // 2 + py = (k // ncols) * tile + (tile - s.height) // 2 + prev.alpha_composite(s, (px, py)) + preview = "preview.png" if preset == "main" and not args.prefix else f"preview_{args.prefix}{preset}.png" + prev.save(out_dir / preview) + print(f"scritti {len(sprites)} sprite in {out_dir}/, atlas.json e {preview}") + + +if __name__ == "__main__": + main()