--- name: assemble_animated_sprite kind: function lang: py domain: ml version: "1.0.0" purity: impure signature: "def assemble_animated_sprite(frame_paths: list, out_dir: str, *, name: str = \"anim\", fps: int = 8, fmt: str = \"webp\", loop: bool = True, spritesheet: bool = True, pad: int = 0) -> dict" description: "Ensambla N frames PNG RGBA (p.ej. los frames de un walk cycle ya pixelizados a 32x32 con alpha) en DOS entregables: un sprite sheet horizontal (1 fila x N columnas) PNG RGBA con la transparencia intacta, y una animacion en loop WEBP lossless o GIF animado. Es la pieza de ensamblado final de cualquier animacion de sprite. Salta frames que falten o no abran (aviso en error, no aborta); normaliza tamano al primer frame valido reescalando con NEAREST. Solo PIL. No-throw. Devuelve {ok, spritesheet_path, animation_path, n_frames, frame_size, fmt, error}." tags: [gamedev-2d, comfyui, sprite, animation] uses_functions: [] uses_types: [] returns: [] returns_optional: false error_type: "error_go_core" imports: [] params: - name: frame_paths desc: "lista de rutas a PNG RGBA en orden de reproduccion; los que falten o no abran se saltan (aviso en error)." - name: out_dir desc: "directorio de salida; se crea si no existe. Se escriben '_sheet.png' y '.' dentro." - name: name desc: "nombre base de los ficheros generados (keyword-only, default 'anim')." - name: fps desc: "frames por segundo de la animacion; duration_ms = round(1000/max(1,fps)) por frame (keyword-only, default 8)." - name: fmt desc: "formato de la animacion: 'webp' (recomendado, lossless, alpha completo) o 'gif' (alpha binario) (keyword-only)." - name: loop desc: "si True la animacion se repite indefinidamente (loop=0); si False una sola vez (keyword-only, default True)." - name: spritesheet desc: "si True genera tambien el sprite sheet horizontal PNG RGBA (keyword-only, default True)." - name: pad desc: "pixeles de separacion transparente entre columnas del sheet (keyword-only, default 0)." output: "dict con ok (bool, True si se produjo la animacion con >=1 frame valido), spritesheet_path (str, '' si spritesheet=False o fallo), animation_path (str, '' si fallo), n_frames (int, frames validos usados), frame_size ([w,h] del frame normalizado), fmt (str, 'webp'|'gif'), error (str, avisos y/o error; '' si limpio)." tested: false tests: [] test_file_path: "" file_path: "python/functions/ml/assemble_animated_sprite.py" --- ## Ejemplo ```python import sys, os sys.path.insert(0, os.path.join(os.environ["HOME"], "fn_registry", "python", "functions")) from ml.assemble_animated_sprite import assemble_animated_sprite # Frames de un walk cycle ya pixelizados a 32x32 RGBA (p.ej. salida del pipeline ComfyUI): frames = [ "/tmp/walk/frame_00.png", "/tmp/walk/frame_01.png", "/tmp/walk/frame_02.png", "/tmp/walk/frame_03.png", ] res = assemble_animated_sprite(frames, "/tmp/walk_out", name="hero_walk", fps=8, fmt="webp") # {'ok': True, # 'spritesheet_path': '/tmp/walk_out/hero_walk_sheet.png', # 'animation_path': '/tmp/walk_out/hero_walk.webp', # 'n_frames': 4, 'frame_size': [32, 32], 'fmt': 'webp', 'error': ''} ``` ## Cuando usarla Al final de cualquier pipeline de animacion de sprite, cuando ya tienes los frames sueltos (pixelizados, con alpha) y necesitas (a) verlos animados en bucle para validar el ciclo a ojo y (b) un sprite sheet horizontal listo para que un motor de juego lo trocee por columnas. Tipico despues de generar un walk cycle frame a frame con ComfyUI y pasarlo por el pixelizado: este es el paso de "juntarlo todo". Usa `fmt="webp"` por defecto; `fmt="gif"` solo si necesitas compatibilidad con visores que no abren WEBP. ## Gotchas - **GIF solo tiene alpha binario** (1 bit): cada pixel es opaco o totalmente transparente, los pixeles con `alpha < 128` se vuelven transparentes y se pierde el anti-aliasing del borde. **WEBP (lossless) es el formato recomendado** para sprites con alpha — conserva el canal alpha completo y no ensucia el pixel-art. Usa GIF solo por compatibilidad. - Al guardar GIF, PIL **reoptimiza la paleta** y el indice de transparencia puede cambiar (p.ej. de 255 a 1 al releer): es normal, los pixeles transparentes se preservan (verificable convirtiendo el frame a RGBA y mirando el canal alpha). - **Frames que faltan o no abren se SALTAN** (se anota en `error`), no se aborta: la animacion se monta con los frames validos. Si quedan **0 frames validos** → `ok=False`. - El campo `error` puede venir **no vacio aunque `ok=True`**: ahi van los avisos de frames saltados. `ok` refleja si se genero la animacion, no la ausencia de avisos. - El tamano se normaliza al **primer frame valido**; los frames de tamano distinto se reescalan con **NEAREST** (sin interpolacion, preserva el pixel-art duro), lo que puede deformarlos si su aspect ratio difiere. Asegurate de que todos los frames ya vienen al mismo tamano. - Escribe en disco: crea `out_dir` si no existe; si no hay permiso de escritura, el fallo del sheet va a `error` como aviso y el de la animacion pone `ok=False`. - `disposal=2` limpia el lienzo entre frames (transparencia correcta en cada paso); sin el, los frames se acumularian unos sobre otros.