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 may 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 cgif encode DECODE f1 == f2 == f3 (synthetic, identical) writes 3, merges dupes delays summed 1 frame same total duration f1 != f2 != f3 (real animation) writes 3 frames nothing to merge 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. If the encoder were truly dropping data, that log line shouldn’t exist.

  • 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 didn’t disappear. They were legally merged. 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.

Tags #devops #debugging #docker
// connect

Be brave | Be wise | Be grateful

21 BreakinCode

// elsewhere
LinkedInMedium (lang: en)Life RecordYoutube
wh:~$William Hung· © 2026 Taipei · GMT+8 · Available for collaboration