Todas las publicaciones

¿Cómo escalo una imagen proporcionalmente para que quepa en una columna?

Pasa los bytes a c.Image y listo. gpdf escala a la columna manteniendo la proporción por defecto. Usa FitWidth o FitHeight cuando necesites un límite explícito.

La pregunta, en otras palabras

Tengo un logo, un gráfico o una captura — pongamos un PNG de 1200×800 — y quiero meterlo en una de mis columnas de gpdf. No quiero hacer la cuenta de la relación de aspecto a mano. No quiero que se estire como un óvalo. No quiero que se desborde a la columna de al lado. Reducir manteniendo la proporción y ya está.

TL;DR

c.Image(imgBytes)

En el caso más común, esa es la receta entera. c.Image usa FitContain por defecto, que reduce la imagen al ancho de la columna respetando la relación de aspecto original. Si la imagen ya es más pequeña que la columna, gpdf la dibuja a su tamaño natural.

¿Necesitas un límite menor que la columna entera? Añade template.FitWidth o template.FitHeight:

c.Image(imgBytes, template.FitWidth(document.Mm(40)))
c.Image(imgBytes, template.FitHeight(document.Mm(20)))

Las dos opciones preservan la proporción del original. Solo especificas una dimensión; gpdf calcula la otra.

Un ejemplo completo

package main

import (
    "log"
    "os"

    "github.com/gpdf-dev/gpdf"
    "github.com/gpdf-dev/gpdf/document"
    "github.com/gpdf-dev/gpdf/template"
)

func main() {
    logo, err := os.ReadFile("logo.png")
    if err != nil {
        log.Fatal(err)
    }
    chart, err := os.ReadFile("chart.png")
    if err != nil {
        log.Fatal(err)
    }

    doc := gpdf.NewDocument(
        gpdf.WithPageSize(gpdf.A4),
        gpdf.WithMargins(document.UniformEdges(document.Mm(20))),
    )

    page := doc.AddPage()

    page.AutoRow(func(r *template.RowBuilder) {
        // Columna estrecha para el logo, fijada a 30mm
        r.Col(3, func(c *template.ColBuilder) {
            c.Image(logo, template.FitWidth(document.Mm(30)))
        })
        // Columna ancha para el gráfico, fit por defecto usa todo el ancho
        r.Col(9, func(c *template.ColBuilder) {
            c.Image(chart)
        })
    })

    data, err := doc.Generate()
    if err != nil {
        log.Fatal(err)
    }
    if err := os.WriteFile("report.pdf", data, 0o644); err != nil {
        log.Fatal(err)
    }
}

La celda del logo (3 columnas) usa FitWidth(30mm) porque queremos un logo pequeño y consistente sin importar cuánto espacio tenga la columna. La del gráfico (9 columnas) lleva un c.Image(chart) pelado para usar todo el ancho disponible. Las dos mantienen la proporción. Ninguna necesita conocer las dimensiones en píxeles del original.

Qué significa "proporcional" exactamente en gpdf

Hay 4 modos de fit; uno es el default y cubre el 90% de los casos reales:

ModoQué haceCuándo usarlo
FitContain (default)Reduce hasta caber dentro de la caja, preserva proporción, puede dejar espacio vacíoLogos, gráficos, capturas — casi todo
FitCoverEscala hasta cubrir toda la caja, preserva proporción, recorta lo que sobraBanners hero, recortes cuadrados de fotos de perfil
FitStretchEscala a llenar exactamente la caja, deforma la proporciónCasi nunca — si lo usas, suele ser un bug
FitOriginalRenderiza al tamaño de píxel original convertido a 72 DPIDiagramas hechos a resolución de imprenta que no deben re-muestrearse

FitWidth y FitHeight fijan una dimensión y usan FitContain para la otra. Son el atajo ergonómico para "me importa el ancho" o "me importa el alto" — casi nunca hace falta llamar a WithFitMode directamente.

La trampa habitual

El error más común es indicar a la vez un width y un height que no coinciden con la proporción del original, y luego quejarse de que la imagen se ve aplastada. Algo así:

// No hagas esto a menos que realmente lo quieras
c.Image(img,
    template.FitWidth(document.Mm(40)),
    template.FitHeight(document.Mm(40)),
)

Si tu PNG es 1200×800, meterlo a la fuerza en una caja de 40×40 obliga a sacrificar una de dos cosas: la proporción (comportamiento FitStretch) o parte de la imagen (comportamiento FitCover). El default es FitContain, así que gpdf mantiene la proporción y deja una dimensión sin llenar — la imagen sale 40mm de ancho y ~26mm de alto, dejando espacio vacío debajo en la caja de 40mm.

La solución es elegir una sola dimensión y confiar en el cálculo. Si de verdad necesitas un recorte cuadrado de una imagen no cuadrada, lo que quieres es FitCover, no dos dimensiones que se contradicen:

c.Image(img,
    template.FitWidth(document.Mm(40)),
    template.FitHeight(document.Mm(40)),
    template.WithFitMode(document.FitCover),
)

Los píxeles no mienten

gpdf lee las dimensiones intrínsecas en píxeles del header del PNG o JPEG antes de cualquier decisión de escala. Así que una foto de 4000×3000 metida en una columna de 60mm no es "escalada en origen" — gpdf incrusta los bytes completos y el lector PDF hace el re-muestreo al renderizar. El PDF resultante pesa lo mismo independientemente del tamaño de visualización.

Si te importa más el peso del archivo que la calidad de impresión máxima, reduce la imagen tú con algo como image/draw antes de pasársela a gpdf. La librería no descartará píxeles silenciosamente. Esa decisión es tuya.

¿Y si la columna se queda demasiado estrecha?

Si una columna acaba más angosta de lo previsto al renderizar — porque la página se rompió de forma inesperada o una celda de tabla se ajustó al contenido — el FitContain por defecto reducirá tu logo al tamaño de un sello sin protestar. Si eso te molesta, pon un suelo:

c.Image(logo,
    template.FitWidth(document.Mm(30)),
    template.MinDisplayWidth(document.Mm(20)),
)

MinDisplayWidth le dice al motor de layout: si tienes que reducir esta imagen por debajo de 20mm para que entre, mándala a la página siguiente. La imagen sale legible o no se dibuja — nunca el peor de los dos mundos en medio.

Recetas relacionadas

Prueba gpdf

gpdf es una librería Go para generar PDFs. MIT, sin dependencias externas, manejo de imágenes y fuentes en Go puro.

go get github.com/gpdf-dev/gpdf

⭐ Star en GitHub · Lee la documentación