How do I embed a PNG with transparency in gpdf?
Pass the PNG bytes directly to c.Image. gpdf decodes the alpha channel into a PDF SMask so transparent backgrounds render correctly.
The question, in other words
I have a logo or a stamp saved as a PNG with a transparent background — logo.png, RGBA, the kind Photoshop or Figma exports. When I embed it in a gpdf PDF, will the transparent area stay transparent so my page color shows through? Or am I going to get a white box around the logo?
The quick answer
Pass the PNG bytes to c.Image and nothing else. gpdf decodes the alpha channel and writes a PDF SMask (soft mask) object alongside the image. Transparent pixels render as transparent.
logo, _ := os.ReadFile("logo.png")
c.Image(logo, template.FitWidth(document.Mm(40)))
That is the entire recipe. You do not flatten the alpha onto a white background. You do not convert RGBA to RGB. You do not pass an option to "enable transparency". The PNG stays a PNG, all the way to the PDF.
A complete example you can run
To actually see the alpha working, the PNG needs something underneath to show through. A watermark on top of body text is the canonical case — page.Absolute puts the logo at fixed coordinates while normal flow content fills the page below it.
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("Quarterly report — Q1 2026", template.FontSize(20), template.Bold())
c.Text("Revenue grew 38% year over year, driven by enterprise renewals and three new logos in financial services. Operating margin expanded to 24% as infrastructure spend flattened.")
c.Text("Headcount ended the quarter at 142, up from 128 at the close of Q4. Engineering hires accounted for 9 of the 14 net adds.")
})
})
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)
}
}
The "DRAFT" stamp is an RGBA PNG with bold red letters and a fully transparent background. When it lands on top of the body text, every transparent pixel reveals the paragraph underneath. Replace draft-stamp.png with any logo, seal, or signature image — same code path, same SMask handling.
What gpdf actually does with the PNG
The interesting part is on the writer side. PDF doesn't have a single "RGBA image" object. It has an RGB image object plus an optional grayscale SMask (soft mask) image, where each pixel of the mask is the alpha value for the corresponding pixel of the main image. The PDF reader composites them at render time.
When you hand gpdf a PNG, the renderer (document/render/pdftarget.go) walks the pixel grid once:
- 24 bits of RGB go into the main image stream, FlateDecode compressed.
- 8 bits of alpha go into a separate SMask stream, also FlateDecode compressed.
- The image dictionary gets
/SMask <ref>pointing at the alpha stream.
If every alpha sample turns out to be 0xFF (fully opaque), gpdf throws the alpha buffer away and skips the SMask write. So a JPEG-style opaque PNG costs you nothing extra in the output. The cost only kicks in when the alpha is doing real work.
This whole path is pure Go — image/png from the standard library does the decode, compress/flate does the compression. No CGO, no libpng dependency. Cross-compiling from macOS to linux/arm64 for a Lambda still produces a static binary.
The JPEG trap
If your "transparent" logo came out of a tool that exported it as JPEG, transparency is already gone before gpdf sees the file. JPEG cannot carry an alpha channel. Whatever tool exported it has flattened the alpha onto whatever background color it felt like — usually white.
c.Image(jpegBytes) works fine, but the embedded image will have an opaque white (or black, or pink) rectangle where the transparent pixels used to be. The fix is upstream: re-export as PNG. There is no flag in gpdf that brings transparency back from a JPEG.
The same applies to "PNG-8" with palette transparency. gpdf's decoder uses Go's standard image/png, which handles palette PNGs correctly, so that case works. But if your asset pipeline accidentally went through a JPEG step, the data is lost.
Sizing and watermarks
Two practical extensions cover most real use cases.
Scaling the logo: pass template.FitWidth(document.Mm(40)) or template.FitHeight(document.Mm(20)). The PNG is decoded at full resolution, then scaled at render time using PDF's coordinate transform — no resampling step on the alpha. Crisp edges either way.
Diagonal "DRAFT" watermarks: bake the watermark as a PNG with a faint alpha (around 25–40%) and drop it on the page with page.Absolute, the same way the example above places the stamp. Because the alpha is per-pixel, you can vary the opacity across the watermark — gradient fades, semi-transparent fills around solid logo lines. The PDF reader composites it correctly with the text underneath.
If you need a pixel-perfect 30%-opacity overlay, that's an alpha pre-bake decision in your image editor. gpdf reproduces whatever alpha values it finds; it does not provide a per-image opacity multiplier in the builder API.
File size sanity check
PNG with alpha → RGB stream + grayscale SMask stream means roughly 33% more bytes than the same image without alpha. A 100 KB opaque-PNG embed becomes ~133 KB with the alpha channel attached. For one logo this is invisible. For a 50-page report with a watermark on every page, it's still invisible — the SMask is registered once and referenced on each page, not duplicated.
If a single image suddenly costs you megabytes, it's the source PNG, not gpdf's encoding. Run it through pngquant or oxipng before embedding. The alpha channel survives both.
Related recipes
- How do I embed a Japanese font in gpdf? — same "just pass the bytes" pattern, but for TrueType
- Generate an invoice PDF in Go in under 50 lines — where a transparent company logo usually ends up in a real document
- Why gpdf is 10–30× faster than other Go PDF libraries — what the pure-Go decode path costs (and saves) in microseconds
Try gpdf
gpdf is a Go library for generating PDFs. MIT licensed, zero external dependencies, pure-Go PNG and TrueType handling.
go get github.com/gpdf-dev/gpdf