--- name: pixeloe_downscale kind: function lang: py domain: ml version: "1.0.0" purity: impure signature: "def pixeloe_downscale(src_path: str, dst_path: str, *, mode: str = 'contrast', target_size: int = 64, patch_size: int = 16, thickness: int = 2, color_matching: bool = True, no_upscale: bool = True, comfy_python: str | None = None) -> dict" description: "Downscale contrast-aware (Contrast-Aware Outline Expansion de Kohaku, lib `pixeloe`) que colapsa una ilustracion a un grid de pixel-art pequeno (64 personajes, 32 iconos) conservando contornos/silueta. Es la etapa de downscale del metodo SOTA de pixel-art (report 0215). NO cuantiza la paleta (eso lo hace despues comfyui_pixelize_image). Resuelve el gotcha de que `pixeloe` solo vive en el venv de ComfyUI con un 'bridge' de interprete: si falta en el interprete actual, re-ejecuta su nucleo por subprocess con el python de ComfyUI. No-throw: todo error viaja en `error`. Determinista; impura por I/O de disco + subprocess. Devuelve {ok, out_path, size, mode, target_size, via, error}." tags: [comfyui, gamedev-2d, pixelart, ml, pixeloe, downscale, contrast-aware, image, bridge] uses_functions: [] uses_types: [] returns: [] returns_optional: false error_type: "error_py_core" imports: [] params: - name: src_path desc: "ruta de la imagen de entrada (PNG/JPG/...). Si no existe -> ok=False con error." - name: dst_path desc: "ruta del PNG de salida; se crea el directorio padre si falta." - name: mode desc: "algoritmo de downscale de pixeloe: 'contrast' (SOTA, conserva silueta), 'bicubic', 'nearest', 'center' o 'k-centroid'. keyword-only." - name: target_size desc: "lado del grid resultante en pixeles (64 para personajes, 32 para iconos). keyword-only." - name: patch_size desc: "tamano del patch que pixeloe colapsa por celda del grid. keyword-only." - name: thickness desc: "grosor de la expansion de contorno (outline expansion). keyword-only." - name: color_matching desc: "corrige el color de cada celda contra el original si True. keyword-only." - name: no_upscale desc: "True devuelve el grid real target_size x target_size (lo habitual, para luego cuantizar); False re-escala al tamano original con pixeles duros (preview). keyword-only." - name: comfy_python desc: "ruta a un interprete con `pixeloe` para el bridge cuando el actual no la tiene. Si None: COMFY_PYTHON y luego ~/ComfyUI/.venv/bin/python3. keyword-only." output: "dict con ok (bool), out_path (str), size ([w,h] de la imagen escrita), mode (str usado), target_size (int pedido), via ('inproc' si pixeloe estaba en este interprete, 'bridge' si se delego por subprocess) y error (str, vacio si OK). No lanza excepciones." tested: true tests: [test_golden_downscale_64_or_clean_degrade, test_edge_target_size_32, test_edge_mode_nearest_no_color_matching, test_error_missing_src_no_throw, test_error_no_interpreter_with_pixeloe] test_file_path: "python/functions/ml/pixeloe_downscale_test.py" file_path: "python/functions/ml/pixeloe_downscale.py" --- ## Ejemplo ```python import sys, os sys.path.insert(0, os.path.join(os.environ["HOME"], "fn_registry", "python", "functions")) from ml.pixeloe_downscale import pixeloe_downscale # Colapsa el render del caballero (1024x1024) a un grid de pixel-art 64x64 # conservando la silueta. NO cuantiza paleta todavia. res = pixeloe_downscale( os.path.expanduser("~/ComfyUI/output/pixel_compare/knight_base_00001_.png"), "/tmp/knight_grid64.png", mode="contrast", target_size=64, no_upscale=True, ) # {'ok': True, 'out_path': '/tmp/knight_grid64.png', 'size': [64, 64], # 'mode': 'contrast', 'target_size': 64, 'via': 'bridge', 'error': ''} # Despues: dureza de color (cuantizacion) con la funcion hermana. from ml.comfyui_pixelize_image import comfyui_pixelize_image comfyui_pixelize_image("/tmp/knight_grid64.png", "/tmp/knight_q16.png", downscale=1, colors=16, upscale_back=False) ``` ## Cuando usarla Primera etapa del metodo SOTA de pixel-art: cuando ya tienes una ilustracion (render SDXL/Flux, sprite, foto) y quieres reducirla a un grid de pixel-art chico **sin perder los contornos** (lo que arruina un resize NEAREST/lanczos normal). Usala **antes** de la cuantizacion dura de paleta con `comfyui_pixelize_image` (paso de color). `target_size` 64 para personajes, 32 para iconos. Si solo necesitas el resize+cuantizado rapido sin contornos finos, `comfyui_pixelize_image` sola basta; para el resultado ganador, encadena `pixeloe_downscale` -> `comfyui_pixelize_image`. ## Gotchas - **`pixeloe` solo esta en el venv de ComfyUI** (`~/ComfyUI/.venv`), no en el del registry. La funcion lo resuelve con un *bridge*: si `import pixeloe` falla, re-ejecuta su nucleo por subprocess con el python de ComfyUI. El campo `via` dice si fue `inproc` o `bridge`. - **El modulo es `pixeloe.legacy.pixelize`**, no `pixeloe.pixelize` (ruta vieja eliminada). - **El nodo `PixelOEPixelize+` de ComfyUI_essentials estaba roto** por ese cambio de import; por eso aqui se llama la lib directa (numpy + PIL, sin cv2). - **NO cuantiza la paleta**: el resultado conserva muchos colores; la dureza retro la aplica despues `comfyui_pixelize_image`. No esperes pocos colores en la salida. - **No-throw**: src inexistente, pixeloe ausente en todos los interpretes, o subprocess caido -> `ok=False` con `error` explicado, nunca excepcion. El pipeline llamante hace fallback mirando `ok`. - Resolucion del interprete del bridge: arg `comfy_python` -> env `COMFY_PYTHON` -> `~/ComfyUI/.venv/bin/python3` (el primero que exista como archivo). - `no_upscale=True` (default) devuelve el grid real `target_size x target_size`; con `False` vuelve al tamano original con pixeles duros (preview), no el grid pequeno.