One Load Path for Stills and Animation
Pass n=-1 to the libvips loader and one path handles stills and animation alike — because the default n=1 silently decodes only the first frame and drops the rest.
Pass n=-1 to the libvips loader and one unconditional path handles every input, with no format sniffing and no page-count branch.
importParams := vips.NewImportParams()
importParams.NumPages.Set(-1) // -1 = load ALL pages/frames
image, err := vips.LoadImageFromBuffer(imageBytes, importParams)
NumPages maps to the loader’s n. -1 means “read every page that exists”:
- Animated GIF (5 frames) stacks into 5 pages, so
Pages() == 5. - Single-frame image (JPEG, PNG, static GIF) is exactly 1 page, so
Pages() == 1andPageHeight() == Height(). Here-1is a genuine no-op: no “0 frames,” no error.
So single vs multi is decided after the load with Pages() and PageHeight(), never at the load call. -1 is also format-agnostic: animated WebP/AVIF and multi-page TIFF/PDF all take the same path, even if a comment says “for GIFs.”
The trap: the default n=1 silently drops animation
The loader’s n defaults to 1, so a GIF loads only page 0. The other frames never get decoded, so the animation is dropped and you get a still of the first frame:
n = 1 decodes only page 0 — the fix is to load with n = -1.No exception, no warning. The failure mode is a quietly lost animation, which is exactly why the -1 matters.
A default isn’t neutral. It will quietly drop the frames you didn’t read.
References:
Related: This builds on how libvips stores an animation as one tall stacked strip, and feeds uniform vs positional transforms on a stitched strip — or go back to the crop, resize, fill overview.