Add or remove alpha, flatten backgrounds, and premultiply
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).


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 oneif !image.HasAlpha() { if err := image.AddAlpha(); err != nil { return err }}import sharp from 'sharp';
// ensureAlpha() is idempotent; optional 0..1 sets the opacity of a newly added alpha channelawait sharp('input.jpg') .ensureAlpha(1.0) .png() .toFile('rgba.png');import pyvips
image = pyvips.Image.new_from_file("input.jpg")if not image.hasalpha(): image = image.addalpha()image.write_to_file("rgba.png")vips addalpha input.jpg rgba.png2. 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) }}import sharp from 'sharp';
// 1. Recommended: blend alpha over a chosen background colour (4 bands -> 3 bands)await sharp('alpha-rings.svg') .flatten({ background: { r: 240, g: 230, b: 242 } }) .webp({ quality: 82 }) .toFile('flatten-light.webp');
// 2. Drop the alpha channel without blending (exposes hidden raw RGB values)await sharp('alpha-rings.svg') .removeAlpha() .png() .toFile('dropped-alpha.png');import pyvips
image = pyvips.Image.new_from_file("alpha-rings.svg")
# 1. Recommended: blend alpha onto an explicit background colourflattened = image.flatten(background=[240, 230, 242])flattened.write_to_file("flatten-light.webp", Q=82)
# 2. Extract only the first 3 colour bands (drop alpha without blending)rgb_only = image.extract_band(0, n=3)# 1. Flatten alpha onto an explicit background colourvips flatten alpha-rings.svg 'flatten-light.webp[Q=82]' --background 240,230,242
# 2. Extract only the first 3 bands (discard band 3)vips extract_band alpha-rings.svg dropped-alpha.png 0 --n 33. 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 spaceif err := image.GaussianBlur(3.0); err != nil { return err}
// Restore straight alpha before exportingif err := image.UnpremultiplyAlpha(); err != nil { return err}// sharp manages premultiplication automatically inside resize() and blur();// when passing an already-premultiplied overlay to composite(), declare premultiplied: trueawait sharp('base.png') .composite([{ input: 'premultiplied-overlay.png', premultiplied: true }]) .png() .toFile('out.png');import pyvips
image = pyvips.Image.new_from_file("alpha-rings.svg")premult = image.premultiply()blurred = premult.gaussblur(3.0)restored = blurred.unpremultiply().cast("uchar")restored.write_to_file("blurred-rgba.png")vips premultiply alpha-rings.svg pre.vvips gaussblur pre.v blur.v 3.0vips unpremultiply blur.v unpre.vvips cast unpre.v blurred-rgba.png uchar