Todas as publicações

Como embuto um PNG com transparência no gpdf?

Passe os bytes do PNG para c.Image. O gpdf decodifica o canal alpha em um objeto SMask do PDF e fundos transparentes são renderizados corretamente.

A pergunta, em outras palavras

Tenho um logo ou um carimbo salvo como PNG com fundo transparente — logo.png, RGBA, o tipo que o Photoshop ou o Figma exportam. Quando eu embuto isso em um PDF gerado pelo gpdf, a área transparente continua transparente para a cor da minha página aparecer? Ou eu vou acabar com um quadrado branco em volta do logo?

A resposta rápida

Passe os bytes do PNG para c.Image e nada mais. O gpdf decodifica o canal alpha e escreve um objeto SMask (máscara suave) do PDF junto com a imagem. Pixels transparentes são renderizados como transparentes.

logo, _ := os.ReadFile("logo.png")
c.Image(logo, template.FitWidth(document.Mm(40)))

Essa é a receita inteira. Você não achata o alpha contra um fundo branco. Não converte RGBA em RGB. Não passa nenhuma opção para "ativar transparência". O PNG continua sendo PNG até chegar no PDF.

Um exemplo completo que você pode rodar

Para ver o alpha funcionando de verdade, precisa ter algo embaixo do PNG. Marca d'água sobre o texto do corpo é o caso clássico — page.Absolute prende o logo em coordenadas fixas enquanto o conteúdo de fluxo normal preenche a página abaixo.

package main

import (
    "log"
    "os"

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

func main() {
    stamp, err := os.ReadFile("draft-stamp.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) {
        r.Col(12, func(c *template.ColBuilder) {
            c.Text("Relatório trimestral — Q1 2026", template.FontSize(20), template.Bold())
            c.Text("A receita cresceu 38% em base anual, impulsionada por renovações enterprise e três novos clientes em serviços financeiros. A margem operacional expandiu para 24% conforme o gasto com infraestrutura se estabilizou.")
            c.Text("O quadro encerrou o trimestre com 142 pessoas, contra 128 no fim do Q4. Engenharia respondeu por 9 das 14 contratações líquidas.")
        })
    })

    page.Absolute(document.Mm(60), document.Mm(120), func(c *template.ColBuilder) {
        c.Image(stamp, template.FitWidth(document.Mm(80)))
    })

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

O carimbo "DRAFT" é um PNG RGBA com letras vermelhas em negrito e fundo totalmente transparente. Quando ele cai em cima do texto do corpo, cada pixel transparente revela o parágrafo de baixo. Substitua draft-stamp.png por qualquer logo, selo ou imagem de assinatura — mesmo caminho de código, mesmo tratamento do SMask.

O que o gpdf realmente faz com o PNG

A parte interessante está no lado do writer. O PDF não tem um único objeto "imagem RGBA". Ele tem um objeto de imagem RGB mais uma imagem SMask (máscara suave) opcional em escala de cinza, onde cada pixel da máscara é o valor alpha do pixel correspondente da imagem principal. O leitor de PDF compõe os dois em tempo de render.

Quando você entrega um PNG ao gpdf, o renderer (document/render/pdftarget.go) percorre a grade de pixels uma vez:

  • 24 bits de RGB vão para o stream principal da imagem, comprimidos com FlateDecode.
  • 8 bits de alpha vão para um stream SMask separado, também com FlateDecode.
  • O dicionário da imagem recebe /SMask <ref> apontando para o stream alpha.

Se cada amostra alpha acabar sendo 0xFF (totalmente opaca), o gpdf joga o buffer alpha fora e pula a escrita do SMask. Então um PNG opaco no estilo JPEG não te custa nada a mais na saída. O custo só aparece quando o alpha está fazendo trabalho real.

Esse caminho todo é Go puro — image/png da biblioteca padrão faz a decodificação, compress/flate faz a compressão. Sem CGO, sem dependência de libpng. Cross-compilar de macOS para linux/arm64 para uma Lambda continua produzindo um binário estático.

A armadilha do JPEG

Se o seu logo "transparente" saiu de alguma ferramenta como JPEG, a transparência já se foi antes do gpdf ver o arquivo. JPEG não pode carregar canal alpha. A ferramenta que exportou achatou o alpha contra alguma cor de fundo (geralmente branco).

c.Image(jpegBytes) funciona normalmente, mas a imagem embutida vai ter um retângulo opaco branco (ou preto, ou rosa) onde antes havia pixels transparentes. A correção é a montante: re-exporte como PNG. Não há opção no gpdf que traga a transparência de volta a partir de um JPEG.

O mesmo vale para o "PNG-8" com transparência de paleta. O decoder do gpdf usa o image/png padrão do Go, que trata PNGs de paleta corretamente, então esse caso funciona. Mas se o seu pipeline de assets passou sem querer por uma etapa JPEG, os dados foram embora.

Escala e marcas d'água

Duas extensões práticas cobrem a maioria dos casos reais.

Escalando o logo: passe template.FitWidth(document.Mm(40)) ou template.FitHeight(document.Mm(20)). O PNG é decodificado em resolução total e depois escalado em tempo de render usando a transformação de coordenadas do PDF — sem etapa de reamostragem no alpha. Bordas nítidas de qualquer jeito.

Marcas d'água "DRAFT" diagonais: gere a marca d'água como um PNG com alpha fraco (cerca de 25–40%) e solte na página com page.Absolute, do mesmo jeito que o exemplo acima coloca o carimbo. Como o alpha é por pixel, dá para variar a opacidade dentro da marca d'água — degradês de fade, preenchimentos semitransparentes em volta de linhas sólidas do logo. O leitor de PDF compõe corretamente com o texto debaixo.

Se você precisa de um overlay com 30% de opacidade pixel-perfect, isso é uma decisão de hornear o alpha no seu editor de imagem. O gpdf reproduz os valores alpha que recebe; não há um multiplicador de opacidade por imagem na API do builder.

Verificação rápida de tamanho de arquivo

PNG com alpha → stream RGB + stream SMask em escala de cinza significa cerca de 33% mais bytes que a mesma imagem sem alpha. Um embed de PNG opaco de 100 KB vira ~133 KB com o canal alpha junto. Para um logo, é invisível. Para um relatório de 50 páginas com marca d'água em todas elas, também é invisível — o SMask é registrado uma vez e referenciado em cada página, não duplicado.

Se uma única imagem de repente custa megabytes, é o PNG fonte, não a codificação do gpdf. Passe por pngquant ou oxipng antes de embutir. O canal alpha sobrevive a ambos.

Receitas relacionadas

Experimente o gpdf

O gpdf é uma biblioteca Go para gerar PDFs. Licença MIT, zero dependências externas, manuseio de PNG e TrueType em Go puro.

go get github.com/gpdf-dev/gpdf

⭐ Star on GitHub · Read the docs