Skip to content

Masks, stencil cutouts, and conditional region blending

Copy page

In image processing, a mask is a single-band (1-band) 8-bit grayscale image: 255 (white) represents full selection or full opacity, 0 (black) represents zero coverage, and intermediate values (1..254) produce feathered transitions.

Across the libvips ecosystem, three patterns cover masking and selective region processing:

  1. Attaching a grayscale mask as the image’s alpha channel (BandJoin / joinChannel);
  2. Cutting out a base image with an RGBA shape using Porter-Duff dest-in (BlendModeDestIn);
  3. Blending two images pixel-by-pixel from a mask (ifthenelse or alpha compositing).
Single-band grayscale image representing an alpha mask
Single-band grayscale mask: PNG640 × 360, 1 band (white = opaque, black = transparent)
RGBA image produced by joining the grayscale mask onto RGB channels
Masked RGBA output: PNG640 × 360, 4-band RGBA

1. Attaching a 1-band grayscale mask as alpha (BandJoin)

Section titled “1. Attaching a 1-band grayscale mask as alpha (BandJoin)”

Given a 3-band RGB image and a matching 1-band grayscale mask (from an SVG rasterization, Pango text render, or segmentation model), join the grayscale mask as the fourth band to create a 4-band RGBA cutout:

// Ensure the base image has 3 RGB bands (strip any existing alpha first)
if baseImg.Bands() > 3 {
if err := baseImg.ExtractBand(0, 3); err != nil {
return err
}
}
// Ensure the mask image is a single-band grayscale image
if maskImg.Bands() > 1 {
if err := maskImg.ExtractBand(0, 1); err != nil {
return err
}
}
// Append the 1-band mask as the 4th band (RGBA)
if err := baseImg.BandJoin(maskImg); err != nil {
return err
}

2. Rounded corners and SVG stencil cutouts with dest-in

Section titled “2. Rounded corners and SVG stencil cutouts with dest-in”

When the stencil is already an RGBA graphic (such as a rounded rectangle, circular avatar frame, or badge rendered from SVG), ensure the base image has an alpha channel and composite the stencil using BlendModeDestIn (dest-in) — keeping the base image’s RGB colours while multiplying its alpha by the stencil’s alpha:

// Build a 500×500 rounded-rectangle SVG stencil
svgMask := []byte(`<svg width="500" height="500" xmlns="http://www.w3.org/2000/svg">
<rect width="500" height="500" rx="64" ry="64" fill="#ffffff"/>
</svg>`)
mask, err := vips.NewImageFromBuffer(svgMask)
if err != nil {
return err
}
defer mask.Close()
if !cropImg.HasAlpha() {
if err := cropImg.AddAlpha(); err != nil {
return err
}
}
// BlendModeDestIn: retain base pixels only where the SVG stencil is opaque
if err := cropImg.Composite(mask, vips.BlendModeDestIn, 0, 0); err != nil {
return err
}

3. Selective region filtering and conditional blending (ifthenelse vs alpha over)

Section titled “3. Selective region filtering and conditional blending (ifthenelse vs alpha over)”

Suppose you want a sharp subject in the foreground and a Gaussian-blurred background controlled by a soft mask:

  • In pyvips and vips CLI, call mask.ifthenelse(sharp_img, blurred_img, blend=True) directly: mask values near 255 pick sharp_img, values near 0 pick blurred_img, and blend=True interpolates smoothly between them.
  • In govips and sharp, which do not export a standalone Ifthenelse method, join the 1-band mask onto the foreground image (BandJoin / joinChannel) and composite it over the blurred background with BlendModeOver (over) for the exact same mathematical blend.
// govips: attach the grayscale mask as foreground alpha, then composite Over the blurred background
if err := foreground.BandJoin(maskGray); err != nil {
return err
}
if err := blurredBg.Composite(foreground, vips.BlendModeOver, 0, 0); err != nil {
return err
}