---
name: image_grid
kind: function
lang: py
domain: ml
version: "1.0.0"
purity: impure
signature: "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"
description: "Combina una lista de PIL Images en un grid NxM con gap configurable, fondo oscuro y labels opcionales sobre cada celda. rows se calcula como ceil(n/cols). Retorna una sola PIL.Image RGB."
tags: [image, grid, pil, pillow, visualization, ml, montage, collage, pendiente-usar]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: [Pillow]
params:
  - name: images
    desc: "lista de PIL.Image.Image a colocar en el grid (todas deben tener el mismo tamano o se usa el maximo)"
  - name: cols
    desc: "numero de columnas del grid (default 4)"
  - name: labels
    desc: "lista opcional de strings para etiquetar cada celda en la esquina superior izquierda"
  - name: gap_px
    desc: "espacio en pixeles entre celdas y en los bordes del canvas (default 8)"
  - name: bg_color
    desc: "color RGB de fondo del canvas como tupla (R, G, B), default (20,20,20) casi negro"
output: "PIL.Image.Image: imagen RGB con el grid montado. Lista con n imagenes en cols columnas y ceil(n/cols) filas."
tested: true
tests:
  - "grid de 4 imagenes 16x16 cols=2 produce ancho/alto correcto"
  - "grid de 4 imagenes cols=2 gap_px=8 tiene dimensiones correctas con gap"
  - "grid de 1 imagen 1 col"
  - "el resultado es una imagen RGB"
  - "labels opcionales no lanza excepcion"
  - "sin labels funciona correctamente"
  - "lista vacia levanta ValueError"
test_file_path: "python/functions/ml/tests/test_image_grid.py"
file_path: "python/functions/ml/image_grid.py"
---

## Ejemplo

```python
from PIL import Image
from ml.image_grid import image_grid
from ml.image_save_png import image_save_png

# Generar 6 imagenes de prueba con colores distintos
colors = [(255,0,0),(0,255,0),(0,0,255),(255,255,0),(0,255,255),(255,0,255)]
imgs = [Image.new("RGB", (256, 256), c) for c in colors]

grid = image_grid(
    imgs,
    cols=3,
    labels=["rojo", "verde", "azul", "amarillo", "cyan", "magenta"],
    gap_px=10,
    bg_color=(30, 30, 30),
)
# grid.size == (3*256 + 4*10, 2*256 + 3*10) == (788, 542)

image_save_png(grid, "outputs/preview_grid.png")
```

## Notas

- Impure: asigna memoria para el canvas y ejecuta `ImageDraw` (efectos en
  objetos PIL internos). Aunque no hace I/O de disco, las allocations PIL
  y el draw tienen side-effects sobre objetos mutables.
- Las imagenes en modo no-RGB (RGBA, L, P, palette) se convierten a RGB
  automaticamente con `.convert("RGB")` antes de pegar.
- Si la lista tiene menos imagenes que `cols * rows`, las celdas sobrantes
  quedan en blanco (solo el color de fondo).
- El label usa `ImageFont.load_default()` (fuente bitmap monospace de PIL,
  sin dependencias externas). Para fuentes TTF customizadas usar
  `ImageFont.truetype(path, size)` externamente y pasar un `font` propio.
- Pillow se importa lazy para no bloquear `fn index`.
