fix(comfyui): pixelart_real_oneshot — sprite llena el frame + fondo transparente
Arregla los dos defectos reportados del pipeline comfyui_pixelart_real_oneshot:
el sujeto salía diminuto respecto al frame y siempre traía fondo (sin opción de
transparencia).
Causa raíz: comfyui_pixelize_image hacía convert("RGB") y descartaba el alpha;
comfyui_build_pixelart_workflow no inyectaba rembg (a diferencia de sus hermanos
item_icon/enemy_creature); y no había ningún paso de auto-crop al contenido.
Orden correcto del pipeline ahora:
generar (rembg) -> autocrop al bbox + cuadrar -> downscale (alpha aparte por
PixelOE) -> cuantización alpha-aware -> PNG RGBA transparente.
Piezas:
- comfyui_pixelize_image (1.1.0): keep_alpha/alpha_threshold. Con RGBA cuantiza
solo el RGB (fondo transparente relleno con la moda del sujeto, fuera de la
paleta) y preserva/binariza el alpha aparte. RGB sin alpha intacto.
- crop_to_content (NUEVA, pura PIL): bbox del contenido (alpha o diff-fondo) ->
recorta -> margen -> cuadra centrando. No-throw; imagen vacía -> copia intacta.
- comfyui_build_pixelart_workflow (1.1.0): transparent=True + rembg_model.
Inyecta nodo Image Rembg tras VAEDecode (patrón de item_icon).
- comfyui_pixelart_real_oneshot (1.1.0): transparent + autocrop + crop_pad_ratio
+ rembg_model. Recombina el alpha aparte tras PixelOE (trabaja en RGB). Campos
nuevos: has_alpha, autocrop_applied.
Verificado en GPU (knight 64px): RGBA con 4 esquinas alpha==0, contenido cubre
88% del frame (antes 48%), 16 colores, 64x64. 32 tests offline en verde.
Report: reports/0218-2026-06-28-pixelart-sprite-fix.md
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,11 +3,11 @@ name: comfyui_pixelize_image
|
||||
kind: function
|
||||
lang: py
|
||||
domain: ml
|
||||
version: "1.0.0"
|
||||
version: "1.1.0"
|
||||
purity: impure
|
||||
signature: "def comfyui_pixelize_image(src_path: str, dst_path: str, *, downscale: int = 8, colors: int = 16, palette=None, dither: bool = False, upscale_back: bool = True) -> dict"
|
||||
description: "Post-proceso pixel-perfect (Fase 2 pixelart): imagen -> downscale nearest-neighbor por factor (colapsa cada bloque borroso a un pixel duro) -> cuantizacion a N colores (MEDIANCUT) o a una paleta fija embebida (game-boy / pico-8 / nes / lista de hex) -> opcional re-upscale nearest conservando los pixeles duros. Convierte el 'pixelart borroso de IA' en pixelart de verdad. Nucleo PIL puro, CPU-only: sin GPU, sin red. Devuelve {ok, out_path, size, n_colors_final, error}. Impura solo por la lectura/escritura de disco."
|
||||
tags: [comfyui, gamedev-2d, pixelart, ml, pil, quantize, palette, image]
|
||||
signature: "def comfyui_pixelize_image(src_path: str, dst_path: str, *, downscale: int = 8, colors: int = 16, palette=None, dither: bool = False, upscale_back: bool = True, keep_alpha: bool = True, alpha_threshold: int = 128) -> dict"
|
||||
description: "Post-proceso pixel-perfect (Fase 2 pixelart): imagen -> downscale nearest-neighbor por factor (colapsa cada bloque borroso a un pixel duro) -> cuantizacion a N colores (MEDIANCUT) o a una paleta fija embebida (game-boy / pico-8 / nes / lista de hex) -> opcional re-upscale nearest conservando los pixeles duros. Alpha-aware: si la entrada es RGBA y keep_alpha, cuantiza SOLO el RGB (el fondo transparente no entra en la paleta) y preserva/binariza el alpha por separado -> PNG RGBA con transparencia real. Convierte el 'pixelart borroso de IA' en pixelart de verdad. Nucleo PIL puro, CPU-only: sin GPU, sin red. Devuelve {ok, out_path, size, n_colors_final, has_alpha, error}. Impura solo por la lectura/escritura de disco."
|
||||
tags: [comfyui, gamedev-2d, pixelart, ml, pil, quantize, palette, image, alpha, transparent]
|
||||
uses_functions: []
|
||||
uses_types: []
|
||||
returns: []
|
||||
@@ -29,9 +29,13 @@ params:
|
||||
desc: "aplica Floyd-Steinberg al cuantizar (off por defecto = pixelart limpio). keyword-only."
|
||||
- name: upscale_back
|
||||
desc: "re-escala nearest al tamano original (preview con pixeles duros). False deja la imagen pequena. keyword-only."
|
||||
output: "dict con ok (bool), out_path (str), size ([w,h] de la imagen final), n_colors_final (int, colores distintos del resultado), error (str, vacio si OK)."
|
||||
- name: keep_alpha
|
||||
desc: "si True (default) y la entrada tiene canal alpha, preserva la transparencia: cuantiza solo el RGB y downscalea/binariza el alpha aparte -> PNG RGBA. Sin efecto si la imagen no tiene alpha (sale RGB igual que antes). keyword-only."
|
||||
- name: alpha_threshold
|
||||
desc: "umbral (0..255) para binarizar el alpha en opaco (255) o transparente (0). Solo aplica cuando se preserva el alpha. keyword-only."
|
||||
output: "dict con ok (bool), out_path (str), size ([w,h] de la imagen final), n_colors_final (int, colores RGB distintos; en la zona opaca si es RGBA), has_alpha (bool, True si la salida es RGBA), error (str, vacio si OK)."
|
||||
tested: true
|
||||
tests: [test_golden_downscale_quantize, test_no_upscale_back_keeps_small, test_edge_fixed_palette_game_boy, test_edge_palette_list_hex, test_edge_downscale_1_only_quantizes, test_error_missing_src, test_error_downscale_zero, test_error_bad_palette]
|
||||
tests: [test_golden_downscale_quantize, test_no_upscale_back_keeps_small, test_edge_fixed_palette_game_boy, test_edge_palette_list_hex, test_edge_downscale_1_only_quantizes, test_error_missing_src, test_error_downscale_zero, test_error_bad_palette, test_alpha_preserved_transparent_corners, test_alpha_off_flattens_to_rgb, test_rgb_input_unaffected_by_keep_alpha, test_error_all_transparent_no_crash]
|
||||
test_file_path: "python/functions/ml/comfyui_pixelize_image_test.py"
|
||||
file_path: "python/functions/ml/comfyui_pixelize_image.py"
|
||||
---
|
||||
@@ -54,14 +58,21 @@ res = comfyui_pixelize_image(
|
||||
# Forzar la paleta retro Game Boy (4 colores) y dejar la imagen pequena (sin upscale)
|
||||
comfyui_pixelize_image("/tmp/hero_pixel.png", "/tmp/hero_gb.png",
|
||||
palette="game-boy", upscale_back=False)
|
||||
|
||||
# Sprite RGBA (tras rembg): preserva la transparencia, cuantiza solo el sujeto
|
||||
res = comfyui_pixelize_image("/tmp/knight_rgba.png", "/tmp/knight_px.png",
|
||||
downscale=1, colors=16, keep_alpha=True)
|
||||
# {'ok': True, 'has_alpha': True, 'n_colors_final': 16, ...} -> fondo transparente intacto
|
||||
```
|
||||
|
||||
## Cuando usarla
|
||||
|
||||
Fase 2 del pipeline pixelart: tras generar el crudo (SDXL + LoRA `SDXL_pixel-art`),
|
||||
para colapsar el grid borroso a pixeles duros y limitar la paleta. Tambien sirve
|
||||
para "pixelizar" cualquier imagen (sprite, render, foto) a estetica retro sin
|
||||
tocar la GPU. Para llevar el resultado a Godot con filtro Nearest:
|
||||
para colapsar el grid borroso a pixeles duros y limitar la paleta. Si la imagen
|
||||
viene de `rembg` con fondo recortado (RGBA), `keep_alpha=True` mantiene la
|
||||
transparencia y deja el fondo fuera de la paleta. Tambien sirve para "pixelizar"
|
||||
cualquier imagen (sprite, render, foto) a estetica retro sin tocar la GPU. Para
|
||||
llevar el resultado a Godot con filtro Nearest:
|
||||
`comfyui_export_asset_to_godot(out, "pixelart", proj)`.
|
||||
|
||||
## Gotchas
|
||||
@@ -76,7 +87,22 @@ tocar la GPU. Para llevar el resultado a Godot con filtro Nearest:
|
||||
duros (preview).
|
||||
- Todo error es **dict `ok=False`** (no excepcion): `src_path` inexistente,
|
||||
`downscale<1`, paleta desconocida -> `error` explica. No crashea ni borra nada.
|
||||
- `n_colors_final` cuenta colores distintos reales del PNG escrito; con paleta fija
|
||||
puede ser **menor** que el tamano de la paleta si la imagen no usa todos.
|
||||
- `n_colors_final` cuenta colores RGB distintos reales del PNG escrito; con salida
|
||||
RGBA cuenta **solo la zona opaca** (el transparente no es un color del pixel-art);
|
||||
con paleta fija puede ser **menor** que el tamano de la paleta si la imagen no usa todos.
|
||||
- **alpha-aware (v1.1.0)**: con entrada RGBA y `keep_alpha=True` (default), el fondo
|
||||
transparente se rellena internamente con la moda del sujeto antes de cuantizar, asi
|
||||
NO gasta una entrada de la paleta; el alpha se downscalea nearest aparte y se
|
||||
binariza por `alpha_threshold` (0/255 = bordes duros pixel-art). Entrada sin alpha
|
||||
-> comportamiento RGB identico al de antes (retrocompatible).
|
||||
- Si la entrada RGBA esta **toda transparente** (rembg sin sujeto), no crashea:
|
||||
devuelve `ok=True`, `has_alpha=True`, `n_colors_final=0` y el PNG sigue transparente.
|
||||
- CPU-only: no toca la GPU ni el servidor ComfyUI; corre en cualquier interprete
|
||||
con Pillow.
|
||||
con Pillow (numpy acelera el relleno alpha; sin numpy degrada limpio).
|
||||
|
||||
## Capability growth log
|
||||
|
||||
- v1.1.0 (2026-06-28) — alpha-aware: `keep_alpha`/`alpha_threshold`. Si la entrada
|
||||
es RGBA, cuantiza solo el RGB (fondo transparente fuera de la paleta) y preserva el
|
||||
alpha binarizado -> PNG RGBA con transparencia real. Cierra el bug del pipeline
|
||||
pixelart que perdia el fondo transparente por el `convert("RGB")` (issue sprite-fix).
|
||||
|
||||
Reference in New Issue
Block a user