Files
fn_registry/python/functions/ml/comfyui_build_grid.py
T
egutierrez ff41f4f053 feat(ml): auto-commit con 7 cambios
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-24 02:52:51 +02:00

115 lines
4.2 KiB
Python

"""Monta un grid / contact-sheet PIL de N imagenes para comparacion visual.
Funcion impura: lee N imagenes de disco y escribe un PNG de salida. Usa PIL
(Pillow), presente en el venv del registry.
El compañero natural de comfyui_batch_generate: ese encola N variantes de un
workflow (una por seed) pero no junta los resultados. Esta funcion toma las N
imagenes ya descargadas (p.ej. con comfyui_fetch_output_image) y las dispone en
una rejilla regular para compararlas de un vistazo. Cada celda conserva el aspect
ratio (thumbnail centrado sobre fondo oscuro). Opcionalmente rotula cada celda.
"""
import math
import os
def comfyui_build_grid(
image_paths: list,
*,
cols: int | None = None,
cell: int = 512,
out_path: str | None = None,
labels: list | None = None,
) -> dict:
"""Compone una rejilla de imagenes y la guarda como PNG.
Args:
image_paths: lista de rutas a las imagenes (PNG/JPG/...) a montar, en
orden de lectura (izquierda->derecha, arriba->abajo).
cols: numero de columnas; si None se usa ceil(sqrt(N)) para una rejilla
casi cuadrada. keyword-only.
cell: lado en pixeles de cada celda cuadrada; cada imagen se reduce para
caber dentro conservando su proporcion. keyword-only.
out_path: ruta del PNG de salida; si None se escribe "comfy_grid.png" en
el directorio de la primera imagen. keyword-only.
labels: rotulos opcionales, uno por imagen (mismo orden); si se pasan, se
reserva una franja bajo cada celda y se dibuja el texto. keyword-only.
Returns:
dict con:
- ok (bool): True si el grid se monto y guardo.
- out_path (str): ruta del PNG generado.
- rows (int): filas de la rejilla.
- cols (int): columnas de la rejilla.
- error (str): mensaje de error; cadena vacia si todo OK.
"""
out = {"ok": False, "out_path": "", "rows": 0, "cols": 0, "error": ""}
if not image_paths:
out["error"] = "image_paths vacio: nada que montar"
return out
try:
from PIL import Image, ImageDraw
except ImportError:
out["error"] = "PIL (Pillow) no esta instalado en este interprete"
return out
missing = [p for p in image_paths if not os.path.isfile(p)]
if missing:
out["error"] = f"no existen {len(missing)} rutas: {missing[:5]}"
return out
n = len(image_paths)
cols = int(cols) if cols and cols > 0 else max(1, math.ceil(math.sqrt(n)))
rows = math.ceil(n / cols)
cell = max(16, int(cell))
label_h = 22 if labels else 0
bg = (24, 24, 28)
fg = (232, 232, 236)
canvas = Image.new("RGB", (cols * cell, rows * (cell + label_h)), bg)
draw = ImageDraw.Draw(canvas) if labels else None
try:
for i, path in enumerate(image_paths):
with Image.open(path) as src:
im = src.convert("RGB")
im.thumbnail((cell, cell))
r, c = divmod(i, cols)
x = c * cell + (cell - im.width) // 2
y = r * (cell + label_h) + (cell - im.height) // 2
canvas.paste(im, (x, y))
if draw is not None and i < len(labels):
tx = c * cell + 4
ty = r * (cell + label_h) + cell + 3
draw.text((tx, ty), str(labels[i]), fill=fg)
except OSError as exc:
out["error"] = f"no se pudo leer/decodificar una imagen: {exc}"
return out
if out_path is None:
out_path = os.path.join(os.path.dirname(os.path.abspath(image_paths[0])),
"comfy_grid.png")
try:
os.makedirs(os.path.dirname(os.path.abspath(out_path)), exist_ok=True)
canvas.save(out_path)
except OSError as exc:
out["error"] = f"no se pudo escribir {out_path!r}: {exc}"
return out
out.update(ok=True, out_path=out_path, rows=rows, cols=cols)
return out
if __name__ == "__main__":
import json
import sys
paths = sys.argv[1:]
if not paths:
print("uso: comfyui_build_grid.py <img1> <img2> ...", file=sys.stderr)
sys.exit(2)
res = comfyui_build_grid(paths, out_path="/tmp/comfy_grid.png")
print(json.dumps(res, indent=2))