---
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.
