---
name: comfyui_flatten_alpha_on_color
kind: function
lang: py
domain: ml
version: "1.0.0"
purity: impure
signature: "def comfyui_flatten_alpha_on_color(image_path: str, *, out_path: str | None = None, color: tuple = (255, 255, 255), size: int | None = None, resample: str = 'lanczos') -> dict"
description: "Aplana (compone) un PNG con canal alpha sobre un fondo de color SOLIDO y opaco, produciendo un PNG RGB sin transparencia. Sirve para preparar un sprite ya recortado (fondo transparente) como entrada de modelos 3D / multivista (SV3D, Stable Zero123, Hunyuan3D), que esperan el sujeto sobre fondo limpio y opaco (tipicamente blanco), no sobre alpha 0. Evita el bug del LoadImage de ComfyUI, que hace convert('RGB') y descarta el alpha mostrando los RGB crudos bajo la transparencia (basura -> mala reconstruccion 3D). Opcionalmente reescala a size x size para encajar el tamano nativo del modelo (SV3D 576, Zero123 256). Nucleo PIL puro, CPU-only: sin GPU, sin red. Devuelve {ok, out_path, size, error}. Impura solo por la lectura/escritura de disco."
tags: [comfyui, gamedev-2d, ml, matting, alpha, flatten, composite, 3d, pil, image]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_py_core"
imports: []
params:
  - name: image_path
    desc: "ruta del PNG de entrada (idealmente con alpha; un sprite ya recortado). Se expande '~'. Si no existe -> ok=False sin lanzar excepcion."
  - name: out_path
    desc: "ruta del PNG RGB de salida; si None se escribe junto al original con sufijo '_flat' antes de la extension. Se crea el directorio destino si falta. keyword-only."
  - name: color
    desc: "color de fondo (r,g,b) en 0..255. Si llega (r,g,b,a) se ignora el alpha entrante (el fondo siempre es opaco). Por defecto blanco (255,255,255). keyword-only."
  - name: size
    desc: "si no es None, reescala el resultado a size x size (cuadrado), para encajar el tamano nativo del modelo 3D (SV3D 576, Zero123 256). keyword-only."
  - name: resample
    desc: "filtro de reescalado: 'lanczos' (por defecto), 'nearest', 'bilinear', 'bicubic', 'area'. String desconocido -> LANCZOS. keyword-only."
output: "dict con ok (bool), out_path (str, ruta del PNG RGB; vacio si error), size ([w,h] final), error (str, vacio si OK)."
tested: true
tests: ["aplana transparente sobre blanco -> RGB sin alpha", "color de fondo personalizado", "size redimensiona a cuadrado", "out_path por defecto con sufijo _flat", "error: imagen inexistente", "determinismo (mismos bytes de salida)"]
test_file_path: "python/functions/ml/tests/test_comfyui_flatten_alpha_on_color.py"
file_path: "python/functions/ml/comfyui_flatten_alpha_on_color.py"
---

## Ejemplo

```python
import sys, os
sys.path.insert(0, os.path.join(os.environ["HOME"], "fn_registry", "python", "functions"))
from ml.comfyui_flatten_alpha_on_color import comfyui_flatten_alpha_on_color

# Sprite recortado (fondo transparente) -> base limpia sobre BLANCO a 576x576,
# lista como entrada de SV3D / Stable Zero123 / Hunyuan3D.
res = comfyui_flatten_alpha_on_color(
    os.path.expanduser("~/ComfyUI/output/goblin_cutout.png"),
    out_path="/tmp/goblin_flat.png",
    color=(255, 255, 255),
    size=576,
)
# {'ok': True, 'out_path': '/tmp/goblin_flat.png', 'size': [576, 576], 'error': ''}

# Sin out_path: escribe junto al original con sufijo _flat (goblin_cutout_flat.png)
comfyui_flatten_alpha_on_color("~/ComfyUI/output/goblin_cutout.png", size=256)
```

## Cuando usarla

Justo ANTES de meter un sprite recortado (PNG con alpha, p.ej. salida de rembg o de
`comfyui_matting_luma_to_alpha`) en un workflow image-to-3D / multivista (SV3D
turntable, Stable Zero123, Hunyuan3D). Esos modelos asumen el sujeto sobre un fondo
SOLIDO y opaco, no sobre alpha 0; si les pasas el PNG con transparencia tal cual, el
`LoadImage` de ComfyUI tira el alpha y reconstruye sobre los RGB basura que habia
debajo. Tambien para "poner fondo blanco" a cualquier asset recortado antes de un
catalogo o un thumbnail. Usa `size` para dejar la base ya al tamano nativo del modelo.

## Gotchas

- **Impura**: lee y escribe disco. Crea el directorio destino si no existe. No borra
  el original.
- **El `LoadImage` de ComfyUI hace `convert("RGB")`** y descarta el alpha: por eso hay
  que aplanar ANTES, no confiar en que el nodo respete la transparencia.
- Todo error es **dict `ok=False`** (no excepcion): path inexistente, fallo de I/O o
  de decodificado de Pillow -> `error` lo explica. No crashea.
- **Fondo blanco por defecto** `(255,255,255)`: es lo que esperan SV3D/Zero123. Cambia
  `color` solo si el modelo concreto pide otro fondo (algunos preferen gris medio).
- Si la imagen ya es RGB (sin alpha) se compone igual (alpha implicito 255): el
  resultado es la misma imagen sobre el color, idempotente respecto al fondo.
- Reescalado con **LANCZOS por defecto** (suave); usa `resample="nearest"` si la base
  es pixelart y no quieres interpolar el grid.
- `color` con 4 componentes `(r,g,b,a)`: el `a` se ignora, el fondo es siempre opaco.
