Skip to content

Fast thumbnails and shrink-on-load

Copy page

When generating thumbnails in production, do not fully decode the source image with NewImageFromFile and then call Resize. libvips provides a dedicated thumbnail pipeline that performs format-aware block shrinking during decoding (shrink-on-load), cutting peak memory and CPU cost by an order of magnitude on large inputs.

1938×1292 night photograph used as the thumbnail input
Input photograph: JPEG1938 × 1292, 610,191 bytes
640×427 WebP thumbnail generated through the thumbnail pipeline
Output thumbnail: WebP640 × 427, 36,172 bytes (govips reference output)

Executable thumbnail code across four interfaces

Section titled “Executable thumbnail code across four interfaces”

The examples below downscale the 1938 × 1292 JPEG to fit within 640 × 427 without enlarging smaller images (SizeDown / withoutEnlargement: true) and export WebP at quality 82:

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()
// Load directly through the thumbnail pipeline to enable shrink-on-load
image, err := vips.NewThumbnailWithSizeFromFile(
"input.jpg",
640,
427,
vips.InterestingNone,
vips.SizeDown,
)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
defer image.Close()
params := vips.NewWebpExportParams()
params.Quality = 82
data, _, err := image.ExportWebp(params)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
if err := os.WriteFile("output.webp", data, 0o644); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}

Why Thumbnail is much faster than Load + Resize

Section titled “Why Thumbnail is much faster than Load + Resize”

Under the hood, vips_thumbnail coordinates five optimizations tailored for large-image downsampling:

  1. Format-aware shrink-on-load:
    • JPEG: uses libjpeg DCT block scaling (1/2, 1/4, or 1/8) during inverse DCT decoding. Downscaling a 4000 × 3000 JPEG to 400 × 300 decodes only 1000 × 750 pixels from libjpeg, skipping over 93% of full-resolution pixel decompression.
    • WebP / HEIF / AVIF: uses codec-level scaled decoding or embedded thumbnail extraction when available.
    • SVG / PDF: computes the exact rasterization DPI for the target box instead of rendering a huge bitmap first.
    • Pyramidal TIFF / OpenSlide: selects the sub-IFD level closest to the requested dimensions.
  2. Box pre-shrinking (vips_shrink): rapidly block-averages pixels to an intermediate resolution just above the final box.
  3. Premultiplied Lanczos3 reduction (vips_reduce): if the image has an alpha channel, premultiplies alpha before running the separable Lanczos3 filter and unpremultiplies afterwards, preventing dark fringes along translucent edges.
  4. Automatic EXIF orientation: applies EXIF Orientation rotation inside the thumbnail pipeline at the smaller resolution.
  5. Colour-profile awareness: transforms through embedded ICC profiles when requested.

Control whether images smaller than the target box should be upscaled:

Intent govips vips.Size sharp ResizeOptions pyvips / vips CLI size
Downscale only, never enlarge (recommended) vips.SizeDown withoutEnlargement: true "down" / --size down
Allow both downscaling and upscaling vips.SizeBoth Default (both flags false) "both" / --size both
Upscale only, never shrink vips.SizeUp withoutReduction: true "up" / --size up
Stretch to exact dimensions (ignore aspect ratio) vips.SizeForce fit: 'fill' "force" / --size force