Files
fn_registry/python/functions/ml/image_grid.py
T
egutierrez e3c8979e8d chore: auto-commit (95 archivos)
- cmd/fn/doctor.go
- cmd/fn/main.go
- cpp/apps/primitives_gallery/playground/tables/CMakeLists.txt
- cpp/apps/primitives_gallery/playground/tables/data_table.cpp
- cpp/apps/primitives_gallery/playground/tables/data_table_logic.cpp
- cpp/apps/primitives_gallery/playground/tables/data_table_logic.h
- cpp/apps/primitives_gallery/playground/tables/self_test.cpp
- cpp/apps/primitives_gallery/playground/tables/tql.cpp
- cpp/apps/primitives_gallery/playground/tables/viz.cpp
- cpp/apps/primitives_gallery/playground/tables/viz.h
- ...

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 00:50:34 +02:00

81 lines
2.6 KiB
Python

"""Combina una lista de PIL Images en un grid NxM con gap y labels opcionales."""
from __future__ import annotations
import math
def image_grid(
images: list["PIL.Image.Image"],
cols: int = 4,
labels: list[str] | None = None,
gap_px: int = 8,
bg_color: tuple = (20, 20, 20),
) -> "PIL.Image.Image":
"""Combina una lista de imagenes en un grid NxM.
Asume que todas las imagenes tienen el mismo tamano (usa el maximo
ancho/alto detectado). Calcula rows = ceil(n / cols) automaticamente.
Args:
images: lista de PIL.Image.Image a colocar en el grid.
cols: numero de columnas del grid (default 4).
labels: lista opcional de strings. Si se proporciona, se escribe
un label encima de cada celda usando la fuente default de PIL.
gap_px: espacio en pixeles entre celdas y en los bordes (default 8).
bg_color: color de fondo RGB del canvas (default casi negro (20,20,20)).
Returns:
Una sola PIL.Image en modo RGB con el grid montado.
Raises:
ImportError: si Pillow no esta instalado.
ValueError: si images esta vacio.
"""
try:
from PIL import Image, ImageDraw, ImageFont
except ImportError as exc:
raise ImportError(
"Pillow no esta instalado. Instalar con: pip install Pillow"
) from exc
if not images:
raise ValueError("image_grid: la lista de imagenes no puede estar vacia")
n = len(images)
rows = math.ceil(n / cols)
# Tamano de celda: max ancho y alto de todas las imagenes
cell_w = max(img.width for img in images)
cell_h = max(img.height for img in images)
canvas_w = cols * cell_w + (cols + 1) * gap_px
canvas_h = rows * cell_h + (rows + 1) * gap_px
canvas = Image.new("RGB", (canvas_w, canvas_h), color=bg_color)
draw = ImageDraw.Draw(canvas) if labels else None
font = None
if draw:
try:
font = ImageFont.load_default()
except Exception:
font = None
for idx, img in enumerate(images):
row = idx // cols
col = idx % cols
x = gap_px + col * (cell_w + gap_px)
y = gap_px + row * (cell_h + gap_px)
# Convertir a RGB si hace falta (RGBA, L, P, etc.)
paste_img = img.convert("RGB") if img.mode != "RGB" else img
canvas.paste(paste_img, (x, y))
if draw and labels and idx < len(labels):
label = labels[idx]
# Texto en la esquina superior izquierda de la celda
draw.text((x + 2, y + 2), label, fill=(255, 255, 255), font=font)
return canvas