🍱 Lunchbox Hands

file-formats

Frames, Disposal Methods, and LZW: How Animated GIFs Actually Work

A developer-friendly deep dive into the GIF89a format — the graphic control extension, LZW compression, the four disposal methods, and why extracting a single raw frame often looks broken.

A GIF is not a sequence of independent pictures — it’s a sequence of instructions for how to paint over the previous picture. Pull one frame out of the middle of an animation and treat it as a standalone image, and it can look like a corrupted mess: stray pixels, a half-drawn character, a background that doesn’t match. That’s not a bug in the frame. It’s a disposal method the naive extractor ignored. Here’s the actual mechanism, block by block.

GIF87a vs GIF89a: two signatures, one big feature gap

Every GIF starts with a 6-byte header that’s also its version signature — one of the ASCII magic bytes we cover in how file signatures work: GIF87a (47 49 46 38 37 61) or GIF89a (47 49 46 38 39 61).

GIF87a, the original 1987 format, supports static and multi-image files but has no concept of animation timing, transparency, or user-defined loops. GIF89a, released in 1989, added the extension blocks that make everything people associate with “animated GIF” possible: the Graphics Control Extension (per-frame delay, transparency, disposal), the Comment Extension, the Plain Text Extension, and the Application Extension (which is how Netscape later smuggled in looping). If a decoder sees GIF87a, it should treat any extension blocks as unrecognized and skip them — in practice, virtually every animated GIF in the wild is GIF89a.

The logical screen and the palette question

Right after the header comes the Logical Screen Descriptor: canvas width and height, a packed byte of flags, a background color index, and the pixel aspect ratio. One of those flags says whether a Global Color Table follows — up to 256 RGB entries, since GIF pixels are indices into a palette, never raw RGB.

That 256-color ceiling is the format’s best-known limitation, and it’s why GIFs of photos look banded while GIFs of flat-color UI mockups look pristine. But the palette isn’t fixed for the whole file: each individual image block can carry its own local color table instead of using the global one. A GIF animator can give every frame a different, tailored 256-color palette optimized for what’s actually in that frame — useful for animations with big color shifts frame to frame, at the cost of a larger file, since each local table adds up to 768 bytes.

LZW compression, briefly

Each image’s pixel data is compressed with LZW (Lempel-Ziv-Welch), a dictionary-based algorithm. The encoder builds a table of pixel-index sequences it has already seen; the first time it hits a run of index values, it adds that sequence to the dictionary, and the next time that exact sequence appears, it emits a single code referencing the dictionary entry instead of the raw values. Runs of repeated or previously-seen patterns compress well; visual noise doesn’t compress at all, which is the core reason GIF is a poor choice for photos and a good one for screenshots, icons, and simple animation.

You don’t need to implement LZW to work with GIFs day to day, but it explains a real symptom: a GIF of a busy photograph is often larger than the same image as a JPEG, because LZW’s dictionary approach doesn’t do anything useful with high-entropy pixel data the way JPEG’s frequency-domain compression does.

The Graphics Control Extension: delay, transparency, disposal

Immediately before each image block, an optional Graphics Control Extension can set three things for that specific frame:

  • Delay time — a 2-byte field in hundredths of a second. A value of 50 means wait half a second before the next frame; 100 means a full second.
  • Transparent color index — if the transparency flag is set, one palette index is treated as “see-through,” letting the previous canvas content show through instead of being overwritten. This is how a GIF can have a non-rectangular, antialiasing-free silhouette.
  • Disposal method — a 3-bit field that tells the decoder what to do with the current frame’s pixels before drawing the next one.

The disposal methods, and why they matter

ValueNameWhat the decoder does before the next frame
0UnspecifiedNo disposal specified — decoder’s default behavior applies (usually treated like 1).
1Do not disposeLeave this frame’s pixels in place; draw the next frame on top of them.
2Restore to backgroundClear the area this frame occupied back to the background color before drawing the next frame.
3Restore to previousRevert the canvas to whatever it looked like before this frame was drawn, then draw the next frame. Poorly and inconsistently supported across decoders.

This is the mechanism behind the “broken raw frame” problem. Animators routinely draw only the changed region of a frame — a small rectangle around a moving character, say — and rely on disposal method 1 to let everything outside that rectangle keep showing whatever the previous frames already painted. Pull that frame’s raw image data out in isolation and you get a small patch of new pixels on a transparent or undefined background, floating in the middle of an otherwise blank canvas. It’s not corrupted; it was never meant to be viewed alone. Correctly reconstructing what a frame “looks like” requires compositing it on top of the accumulated canvas state, honoring whatever disposal method the previous frame specified.

The zero-delay quirk

The delay field is often set to 0 or a very low value, either intentionally or by an export tool trying to make an animation feel instant. Browsers refuse to honor that literally. Documented behavior in Firefox and Chromium both clamp GIF frame delays of 10ms or less up to 100ms — a compatibility holdover from ancient Netscape-era encoders that used near-zero delays as an implicit “play as fast as the browser can manage” signal rather than a literal duration. Practical upshot: if you want the fastest visually-distinct animation a modern browser will actually render, you want a delay around 20ms (a raw value of 2), not 0.

Looping wasn’t in the original spec — Netscape bolted it on

GIF89a has no native “loop N times” field. Looping exists because of an Application Extension block containing the literal ASCII string NETSCAPE2.0, followed by a sub-block with a 2-byte loop count (0 meaning loop forever). Every browser and image viewer since has kept honoring this de facto standard purely for compatibility — it was never ratified into the GIF specification itself, it just won by being universally implemented.

Why GIF survives despite being technically worse

WebP, AVIF, and MP4/H.264 all beat GIF on every measurable axis — better compression, full 24-bit color, smaller files at equal quality. GIF persists anyway for one structural reason: browsers autoplay GIFs without a user gesture and without sound, by default, everywhere — in <img> tags, in chat apps, in social feeds. Video formats have historically required explicit autoplay muted playsinline handling and different embedding rules, and platform-level autoplay policies treat <video> differently from <img>. GIF’s format limitations (256 colors, per-frame full palettes, LZW-only compression) became irrelevant next to the fact that it just works as a static-looking image tag that happens to move — no player chrome, no click-to-play, no JavaScript required.

Working with GIFs

If you need to pull frames out of a GIF the right way — with disposal composited correctly, so a frame that’s really just a “changed rectangle” over an unchanged background renders as the full, correct picture rather than a broken patch — the GIF Frame Extractor does exactly that. It reads every frame’s delay and disposal method, composites each frame against the accumulated canvas the way a real GIF renderer would, and lets you download one frame or the whole set as PNGs — entirely in your browser.

If you’re extracting frames because you actually want a smaller or better animated result, not individual stills, the Image Format Converter is the better next step for converting between PNG, JPEG, and WebP. And if you’re deciding which format to use for a new asset in the first place, PNG vs JPG vs WebP vs AVIF covers the tradeoffs GIF was never designed to compete on.