e3c8979e8d
- 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>
81 lines
2.6 KiB
Python
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
|