---
name: comfyui_build_outpaint_asset_workflow
kind: function
lang: py
domain: ml
purity: pure
version: 1.0.0
signature: "def comfyui_build_outpaint_asset_workflow(input_image: str, prompt: str, *, left: int = 0, right: int = 0, top: int = 0, bottom: int = 0, feather: int = 40, checkpoint: str = \"IMG_dreamshaper_8.safetensors\", denoise: float = 1.0, style: str = \"game background\", grow_mask: int = 0, seed: int = 0, lora: str | None = None, lora_strength: float = 1.0, negative: str | None = None, steps: int = 28, cfg: float = 7.0, sampler_name: str = \"dpmpp_2m\", scheduler: str = \"karras\", filename_prefix: str = \"outpaint_asset\") -> dict"
description: "Construye el dict (API format) del workflow que EXTIENDE EL LIENZO de un asset 2D ya pintado (outpaint): recibe el asset + cuanto extender por cada lado (left/right/top/bottom px) + un prompt de que generar en la franja nueva, y agranda el canvas generando contenido coherente con el original mas alla de sus bordes. Es el quinto vertice del eje transform de gamedev-2d: distinto de txt2img (enemy_creature/item_icon, inventan la forma desde texto), de img2img (asset_variant, reescribe TODO el asset), de ControlNet (sprite_from_sketch, pinta un sprite desde un boceto) y de inpaint (inpaint_asset, edita una region INTERIOR con una mascara externa del mismo tamano). Mecanismo: el nodo nativo ImagePadForOutpaint amplia el lienzo left/top/right/bottom px, rellena la franja y EMITE la mascara feathered de la zona nueva (la genera el grafo, NO la recibe el caller); VAEEncodeForInpaint codifica respetando esa mascara y el KSampler (denoise alto) genera lo nuevo con '{prompt}, {style}, seamless extension'. feather difumina la costura entre lo viejo y lo nuevo; grow_mask dilata adicionalmente el borde si aparece costura. Compone comfyui_build_inpaint_workflow (base; su LoadImageMask se elimina) + comfyui_inject_lora (estilo opcional). Pura, sin red ni I/O. class_types verificados contra /object_info (8GB lowvram). Probado e2e en GPU con SD1.5: seamless 512x512 extendido right=256 -> 768x512, original conservado + franja nueva coherente."
tags: [comfyui, ml, gamedev-2d, outpaint, asset-transform, canvas-extend, stable-diffusion, workflow]
uses_functions: [comfyui_build_inpaint_workflow_py_ml, comfyui_inject_lora_py_ml]
uses_types: []
returns: []
returns_optional: false
error_type: ""
params:
  - name: input_image
    desc: "Nombre del archivo del asset a extender dentro de la carpeta input/ del servidor ComfyUI (un fondo, parallax, card_art, splash). Lo carga el nodo LoadImage. Subelo antes con POST /upload/image o copialo a ~/ComfyUI/input/. No puede estar vacio."
  - name: prompt
    desc: "Que generar en la franja ampliada ('more forest and trees', 'open sky and clouds', 'continuing stone wall'). Describe la CONTINUACION de la escena hacia los lados extendidos, no un objeto centrado. No puede estar vacio."
  - name: left
    desc: "Pixeles a extender por la izquierda. Se redondea al multiplo de 8 mas cercano (step del nodo + SD trabaja en latentes de 8 px) y se clampa a [0, 16384]. keyword-only."
  - name: right
    desc: "Pixeles a extender por la derecha (mismo tratamiento que left). keyword-only."
  - name: top
    desc: "Pixeles a extender por arriba (mismo tratamiento que left). keyword-only."
  - name: bottom
    desc: "Pixeles a extender por abajo (mismo tratamiento que left). Al menos uno de left/right/top/bottom debe ser > 0 tras redondear: sin extension no hay outpaint (para editar una region INTERIOR usa comfyui_build_inpaint_asset_workflow). keyword-only."
  - name: feather
    desc: "Pixeles de difuminado del borde entre el asset original y la franja nueva (input 'feathering' del nodo ImagePadForOutpaint). Default 40. Mas alto = transicion mas gradual pero invade mas la imagen original; no debe superar mucho la extension del lado mas pequeno. Se clampa a [0, 16384]. keyword-only."
  - name: checkpoint
    desc: "Checkpoint del servidor. 'IMG_dreamshaper_8.safetensors' (SD1.5, holgado en 8GB lowvram) por defecto. keyword-only."
  - name: denoise
    desc: "Fuerza de denoising del KSampler dentro de la mascara (la franja nueva). En outpaint suele ser 1.0 (por defecto): la zona ampliada parte de relleno y se genera por completo. Se clampa a [0.0, 1.0]. keyword-only."
  - name: style
    desc: "Descriptor de estilo que mantiene la franja coherente con el asset y el set ('game background', 'pixel art landscape', 'dark fantasy scenery'). Mismo style + checkpoint + (lora) que el resto del set. keyword-only."
  - name: grow_mask
    desc: "Pixeles que se dilata ADEMAS el borde de la mascara hacia dentro de la imagen original (sobre el feathering del pad) para reforzar el blend si aparece costura. Default 0 (el feathering del pad suele bastar). Se clampa a [0, 64] (limite de VAEEncodeForInpaint). keyword-only."
  - name: seed
    desc: "Semilla del KSampler. keyword-only."
  - name: lora
    desc: "LoRA de estilo opcional en models/loras (ej. 'SD15_dark_fantasy.safetensors'). None = sin LoRA. keyword-only."
  - name: lora_strength
    desc: "Fuerza del LoRA sobre model y clip. Se clampa a [0.0, 2.0]. keyword-only."
  - name: negative
    desc: "Prompt negativo. None usa el negativo por defecto pensado para extension de escena (continuacion sin costura, sin repetir/espejar el original, un solo sujeto). keyword-only."
  - name: steps
    desc: "Pasos de sampling del KSampler. keyword-only."
  - name: cfg
    desc: "Classifier-free guidance scale. keyword-only."
  - name: sampler_name
    desc: "Nombre del sampler (ej. 'dpmpp_2m', 'euler'). keyword-only."
  - name: scheduler
    desc: "Scheduler del sampler (ej. 'karras', 'normal'). keyword-only."
  - name: filename_prefix
    desc: "Prefijo del archivo de salida en SaveImage. keyword-only."
output: "dict en API format listo para comfyui_submit_workflow: outpaint que extiende el lienzo por los lados pedidos y genera lo nuevo con '{prompt}, {style}, seamless extension...', conservando el asset original. Nodos: CheckpointLoaderSimple '4', LoadImage '10', ImagePadForOutpaint (id nuevo, reusa el '12' que libera el LoadImageMask eliminado), VAEEncodeForInpaint '11' (pixels <- pad IMAGE, mask <- pad MASK), CLIPTextEncode '6'/'7', KSampler '3', VAEDecode '8', SaveImage '9' (+ LoraLoader si lora). El LoadImageMask de la base inpaint se elimina: la mascara la GENERA el pad."
tested: true
tests: ["estructura outpaint (ImagePadForOutpaint, sin LoadImageMask)", "pad cableado a VAEEncodeForInpaint (pixels<-IMAGE, mask<-MASK)", "extensiones redondeadas a multiplo de 8", "sin extension (todo 0 tras redondear) -> ValueError", "feather y prompt reflejados", "lora opcional + filename_prefix", "errores: input/prompt vacios", "determinismo"]
test_file_path: "python/functions/ml/tests/test_comfyui_build_outpaint_asset_workflow.py"
file_path: python/functions/ml/comfyui_build_outpaint_asset_workflow.py
---

Construye el dict (API format) del workflow que **extiende el lienzo** de un asset 2D ya
pintado (**outpaint**). Quinto vértice del eje **transform** de `gamedev-2d`, junto a
`asset_variant` (img2img: reescribe todo), `sprite_from_sketch` (ControlNet: pinta desde un
boceto) e `inpaint_asset` (edita una región **interior** con máscara externa). Aquí el dev
tiene un asset terminado y necesita **más canvas del que tiene** —recortar/extender un
fondo o parallax a otra resolución o aspect ratio, ampliar un card_art o un splash más allá
de sus bordes, completar la escena hacia un lado— sin regenerarla entera. El lienzo se
agranda por los lados pedidos y se genera contenido **nuevo coherente** con el original en
esa franja.

La clave frente a `inpaint_asset`: **la máscara no la aporta el caller**. El nodo nativo
`ImagePadForOutpaint` amplía el lienzo y emite a la vez la imagen extendida **y** la máscara
feathered de la zona nueva (blanco = generar, negro = conservar). Por eso `inpaint_asset`
recibe `mask_image` y `outpaint_asset` no: la genera el grafo.

## Ejemplo

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

# Extender un fondo 512x512 -> 768x512 generando más escena por la derecha.
# (input_image debe estar ya subido al input/ del server; POST /upload/image)
wf = comfyui_build_outpaint_asset_workflow(
    "seamless_00004_.png",                              # el fondo (512x512, ya en ~/ComfyUI/input/)
    "more forest floor, grass and foliage extending to the right",  # qué generar en lo nuevo
    right=256,                                          # +256 px por la derecha (múltiplo de 8)
    feather=40,                                         # difumina la costura 40 px
    style="game background, top-down 2d, seamless tile",
    denoise=1.0,                                        # la franja nueva se genera por completo
    seed=7,
)
resp = comfyui_submit_workflow(wf, server="127.0.0.1:8188")
out = comfyui_wait_result(resp["prompt_id"], server="127.0.0.1:8188")
# La imagen sale en ~/ComfyUI/output/outpaint_asset_*.png: 768x512, el original intacto
# a la izquierda y la franja nueva coherente a la derecha.
```

## Cuando usarla

Úsala cuando tengas un asset 2D **ya pintado** y necesites **más lienzo** del que tiene:
adaptar un fondo/parallax a otra resolución o aspect ratio, ampliar un card_art o splash
más allá de sus bordes, o completar la escena hacia uno o varios lados. Elige entre los
hermanos del eje transform así:

- **más canvas, generar lo de fuera de los bordes** → este builder (outpaint).
- **una región interior, el resto intacto, con máscara propia** → `inpaint_asset`.
- **todo el asset, mismo diseño/pose, otro material/tier** → `asset_variant` (img2img).
- **un sprite nuevo cuya silueta marca un boceto** → `sprite_from_sketch` (ControlNet).
- **un asset de cero desde texto** → `enemy_creature` / `item_icon` (txt2img).

## Gotchas

- **No recibe máscara: la genera el grafo.** A diferencia de `inpaint_asset`, NO se pasa
  `mask_image`. `ImagePadForOutpaint` extiende el lienzo y emite la máscara feathered de la
  zona nueva. Eso es lo que diferencia outpaint de inpaint.
- **Al menos un lado > 0.** Si las cuatro extensiones quedan en 0 tras redondear (p. ej.
  pasar `left=3` → 0), lanza `ValueError`: sin extensión no hay outpaint. Para editar una
  región **interior** usa `inpaint_asset`.
- **Extensiones a múltiplo de 8.** `left/right/top/bottom` se redondean al múltiplo de 8 más
  cercano (el nodo declara `step: 8` y SD trabaja en latentes de 8 px). `250 → 248`,
  `60 → 64`. Pásalos ya redondeados si quieres dimensiones exactas.
- **`feather` no debe pasarse de la extensión.** Un `feathering` mayor que la franja añadida
  invade la imagen original y emborrona parte de lo que querías conservar. Mantenlo por
  debajo del lado más pequeño que extiendes. Default 40 (el del nodo).
- **`grow_mask` es refuerzo opcional.** Por defecto 0: el feathering del pad ya difumina la
  costura. Súbelo (6–10) solo si aparece una línea dura entre lo viejo y lo nuevo; se clampa
  a `[0, 64]`.
- **`denoise` alto por defecto (1.0).** La franja nueva parte de relleno y se genera entera;
  bajarlo no tiene el mismo sentido que en inpaint (no hay píxeles originales útiles bajo la
  zona ampliada salvo el relleno del pad).
- **Función pura.** No sube ni valida que `input_image`/`checkpoint`/`lora` existan en el
  servidor: súbelos antes (`POST /upload/image`) y valida con `comfyui_validate_workflow` si
  quieres atrapar nombres inexistentes antes de enviar.
