Skip to content

Add or remove alpha, flatten backgrounds, and premultiply

Copy page

Production alpha handling revolves around four operations: adding an alpha channel to opaque images (AddAlpha / ensureAlpha), dropping the fourth band (removeAlpha / ExtractBand), blending translucent pixels onto a solid background (Flatten), and premultiplying alpha before alpha-unaware spatial filtering (PremultiplyAlpha).

Translucent ring test graphic on a transparent background
Input: transparent SVG720 × 480, 4-band RGBA
240×160 PNG output preserving translucent edges and transparency
Alpha preserved: PNG240 × 160, 4 bands, 35,763 bytes
240×160 WebP output flattened onto a #f0e6f2 background
Flattened: WebP240 × 160, 3 bands, background #f0e6f2

1. Idempotently ensuring an alpha channel (AddAlpha vs ensureAlpha)

Section titled “1. Idempotently ensuring an alpha channel (AddAlpha vs ensureAlpha)”

When joining an opaque 3-band JPEG with a 4-band RGBA overlay, you first need to give the 3-band image an alpha channel.

// Append an opaque alpha band (255) only when the image does not already have one
if !image.HasAlpha() {
if err := image.AddAlpha(); err != nil {
return err
}
}

2. Flattening onto a background (Flatten) vs dropping alpha (removeAlpha)

Section titled “2. Flattening onto a background (Flatten) vs dropping alpha (removeAlpha)”

When exporting to a format that does not support transparency (such as JPEG) or converting transparent product cutouts into solid-background cards, distinguish between flattening and dropping the alpha band:

Operation Band change Effect on transparent & translucent pixels When to use
Flatten (Flatten / flatten) 4 → 3 (or 2 → 1) Alpha-blends foreground RGB over the specified background colour Recommended for almost all exports; preserves smooth anti-aliased edges
Drop alpha (ExtractBand(0, 3) / removeAlpha) 4 → 3 Discards band 3 and exposes the raw underlying RGB values unchanged Only when you intentionally want the unblended hidden RGB channels

Many graphics tools store (0, 0, 0) black inside fully transparent pixels (A=0). Dropping the alpha channel without flattening turns transparent backgrounds and anti-aliased text fringes into harsh black blocks.

package main
import (
"fmt"
"os"
"github.com/davidbyttow/govips/v2/vips"
)
func main() {
if err := vips.Startup(nil); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
defer vips.Shutdown()
image, err := vips.NewImageFromFile("alpha-rings.svg")
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
defer image.Close()
// Explicitly flatten over #f0e6f2 before exporting as opaque WebP or JPEG
if image.HasAlpha() {
bg := &vips.Color{R: 240, G: 230, B: 242}
if err := image.Flatten(bg); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
data, _, err := image.ExportWebp(vips.NewWebpExportParams())
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
if err := os.WriteFile("flatten-light.webp", data, 0o644); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}

3. Explicit premultiply and unpremultiply (PremultiplyAlpha / UnpremultiplyAlpha)

Section titled “3. Explicit premultiply and unpremultiply (PremultiplyAlpha / UnpremultiplyAlpha)”

Before applying alpha-unaware spatial operations such as Gaussian blur, custom convolution, or affine transforms to an image with translucent edges, straight-alpha RGB values must be scaled by their alpha coverage so transparent black (0, 0, 0, 0) does not bleed into neighbouring pixels (note: Composite / composite2 and govips.Resize already premultiply and unpremultiply internally—do not call PremultiplyAlpha() before them).

Follow the sequence premultiply → spatial filter/transform → unpremultiply:

if image.HasAlpha() {
if err := image.PremultiplyAlpha(); err != nil {
return err
}
}
// Run Gaussian blur or geometry transforms safely in premultiplied space
if err := image.GaussianBlur(3.0); err != nil {
return err
}
// Restore straight alpha before exporting
if err := image.UnpremultiplyAlpha(); err != nil {
return err
}