Animation keeps one frame or becomes a tall image
When an animation keeps only its first frame, becomes one tall image, or develops frame alignment errors, check the loaded page count, page-height, and animation metadata separately. They are related, but they are not the same failure.
Symptoms
Section titled “Symptoms”- An animated GIF/WebP/AVIF exports as a single static frame.
- Resizing an animation puts frames out of order, looking like a “toilet-roll”, or reports
bad grid geometry. - Joining several frames into one animation yields one very tall image instead of a multi-page animation.
- The loop count and frame delay are lost after export.
Root cause: the libvips multi-page model
Section titled “Root cause: the libvips multi-page model”libvips models GIF, animated WebP/AVIF, PDF, and multi-page TIFF uniformly as “multi-page images”. The n (page count) argument controls how many pages load:
- Default
n=1: only the first page (for an animation, the first frame). n=-1: loads every page.
govips exposes this through ImportParams.NumPages. If you do not set it, an animation keeps only its first frame. This is the root cause of most “the animation is gone” reports.
import "github.com/davidbyttow/govips/v2/vips"
// loadAnimation returns a multi-page image that the caller must Close.func loadAnimation(buf []byte) (*vips.ImageRef, error) { params := vips.NewImportParams() params.NumPages.Set(-1) return vips.LoadImageFromBuffer(buf, params)}1. Load every page before transforming an animation
Section titled “1. Load every page before transforming an animation”Any “whole-image” operation (resize / rotate / flip / zoom) on an animation must be preceded by loading all pages with NumPages=-1, otherwise it acts on only the first frame or scrambles the frames.
2. When building an animation from frames, remember SetPageHeight
Section titled “2. When building an animation from frames, remember SetPageHeight”The correct way to join multiple frames into an animation is to concatenate vertically and then use SetPageHeight() to slice the tall image back into equal pages, plus SetPageDelay to set the inter-frame delay. If you omit SetPageHeight, the export is one very tall image — the cause of the “joined into a long image” reports.
3. loop and frame-delay metadata must be kept or set explicitly
Section titled “3. loop and frame-delay metadata must be kept or set explicitly”Some sources (such as GIF87a) carry no loop count, so loop semantics may be lost after export. If your output depends on a repeat count, set loop and delay explicitly during export or metadata handling; do not assume they survive from the source.
Affected versions and scope
Section titled “Affected versions and scope”- Taking only the first frame of an animation is a deliberate need, for example to build a preview thumbnail. Do not treat that as a bug.
- The
toilet-rollframe-misalignment on specific scales has an official fix in a newer version; upgrade govips before debugging your own code. - The API and boundaries on this page were checked against govips v2.19.0. Animation support for each format still depends on the runtime libvips build.
Sources
Section titled “Sources”- govips #445 — GIF to WebP loses animation
- govips #449 — ArrayJoin + SetPageHeight yields a tall image
- govips #350 — animated resize toilet-roll
- govips #500 — fix animated resize producing toilet-roll
- govips #554 — rotate/flip breaks frame order and bad grid geometry
- govips #381 / #503 — loop and delay metadata
- govips v2.19.0 README
The default multi-page behaviour of pyvips, sharp, and the CLI still needs individual verification; this page does not project govips defaults onto those bindings.