Skip to content
All writing ‹ Part 05 of 09 · A Golang Upgrade's Chain Reaction
Engineering · 2 min read

GIF Encoders Legally Merge Identical Frames

What looked like frame loss was a spec-legal optimization. A conforming GIF encoder may merge consecutive identical frames and fold their delays.

What looked like frame loss was a spec-legal optimization. A conforming animated GIF encoder can drop consecutive identical frames and fold their delays into the survivor. Total duration and rendered output are preserved. Only the frame count drops. Optimization, not corruption.

INPUT f1 == f2 == f3 (synthetic, identical) f1 != f2 != f3 (real animation) cgif encode writes 3, merges dupes delays summed writes 3 frames nothing to merge DECODE 1 frame same total duration 3 frames round-trip intact
The encoder writes every frame either way. The log proves it. Only the decode output differs.

Note the middle column: the encoder writes every frame either way. The log shows cgifsave: N frames even when the decode reads 1. An encoder that truly drops data does not produce that log line.

  • libvips gifsave/cgifsave does this by default: keep_duplicate_frames=FALSE, with interframe_maxerror as the “close enough to merge” threshold. Set keep_duplicate_frames=TRUE to force all frames through.
  • Real animated GIFs have differing frames, so they round-trip N-in/N-out. Only all-identical synthetic inputs collapse.
  • Any encode-and-count test assertion must use per-frame-differing fixtures (test input files), or it asserts on output the encoder is free to change.

Your frames did not disappear. The encoder legally merged them. The test input is the problem, not the encoder.


Sources:

Related: Back to the chain reaction overview, or see contaminated test oracles and a test result is FACT only about its input.

↑↓ navigate ↵ open esc close