← Blog

Animated GIFs, multi-page TIFFs, and what survives a conversion

Three code paths depending on input and target Default: single frame ImageMagick.read() Reads only the first image in the file. Used for almost everything. Animated GIF → JPG/PNG/AVIF: frame 1 only. Animated WebP → anything: frame 1 only. Timing and loop counts Animated: GIF → WebP readCollection() frames.coalesce() The one case where every frame is preserved. Coalesce makes each frame a complete RGBA picture (not a delta), then writes an animated WebP. No analogous AVIF path — AVIF output is always static. Multi-page TIFF readCollection() convertPages() → ZIP Detected automatically when frame count > 1 on TIFF input. Each page is encoded separately to the chosen output format, all wrapped in a ZIP archive, named name_001.jpg, name_002.jpg, ...

Some image formats hold more than one picture per file. Animations (GIF, animated WebP) and multi-page documents (TIFF) all fit that shape. The converter accepts them as inputs and routes them through three different code paths depending on what is going in and what is coming out. The output formats this converter writes are jpg, png, webp, and avif.

The single-frame default

By default, every conversion runs through ImageMagick.read(), which decodes only the first image in the file and hands it to the encoder. Most inputs only have one image anyway, so this is the right choice almost all of the time.

For animated inputs, the consequence is what you would expect: the first frame survives and the rest are discarded. Convert an animated GIF to JPG, PNG, or AVIF and you get a single still image of frame 1. Convert an animated WebP to anything and you get a single still image of frame 1. Animation timing, loop counts, and frame disposal flags are all dropped because the destination has nowhere to store them — and because the read path never sees frames 2 onwards.

The animated path: GIF → WebP

The one case where the converter preserves animation is gif → webp. When the input extension is GIF and the output is WebP, the worker switches to ImageMagick.readCollection(), calls coalesce() on the frame list, then writes the whole collection as an animated WebP.

Coalescing resolves frame disposal. GIF stores animations as a base frame plus a series of deltas — frame 2 might be "leave frame 1 in place, draw a 30×30 patch at (40, 60)" — and frame disposal flags decide whether the previous frame stays under the next one or gets cleared first. Without coalescing, only the first frame is a complete picture; the rest are partial overlays that depend on their predecessors. Coalescing replaces the collection with a series of complete RGBA pictures, ready for the WebP animation encoder.

There is no equivalent path for AVIF: even though AVIF supports animation in the spec, the converter writes static AVIF only. Convert an animated GIF to AVIF and you get a single still image of frame 1.

Multi-page TIFFs become ZIP archives

TIFF's multi-image support predates animation by a decade. It was designed for documents — a 30-page contract scanned to a single TIFF, with each page a separate image inside. The converter detects multi-page TIFFs at the read step (the frame count is exposed in the UI) and uses a different code path entirely.

For multi-page TIFF input, the worker calls convertPages(), which walks the frame collection and encodes each page separately into the chosen output format. The resulting files are packaged into a ZIP archive named after the source, with each page indexed: document_001.jpg, document_002.jpg, and so on. You get every page out as an individual file without having to re-run the conversion per page.

The frame counter that appears in the UI for multi-page TIFFs is the same counter used for animated GIF input, populated by a one-shot frame count probe before the conversion runs.

Alpha and quality during multi-frame conversion

Each frame goes through the same alpha and quality treatment that a single-frame conversion would. If the output format does not support alpha (JPG), every frame is composited onto a white canvas before encoding. If the output format is lossy (JPG, WebP, AVIF), every frame is encoded at the converter's fixed quality 85.

What doesn't survive

Frame timing and loop counts are preserved when the path is GIF → animated WebP, and dropped in every other case. Per-frame transparency in GIF is converted to per-frame alpha in WebP, since both formats support it. Audio tracks — there are none in any image format the converter accepts. Embedded video streams in HEIC Live Photos are read but only the still image is decoded; the video portion is discarded.