Shrink a GIF and two complaints come back over and over: the colours went blotchy, and the thing now plays faster than it used to. I had assumed both were the compressor being heavy-handed. Only one of them is about compression at all, and the faster playback isn't it. I found that out by drawing a few test animations with a script and reading numbers out of the files instead of watching them loop.
Two words first, because the rest depends on them. A GIF is a stack of still images played in order; each one is a frame, and each frame carries a number saying how long it stays on screen. That number is the frame delay, and the running time of the animation is just the sum of those delays. Nothing else in the file controls playback speed.
So I exported the same source several ways and read frame count, per-frame delay, total duration and loop flag back out of every file. The source was a gradient clip I drew with a script: 480×270, 30 frames, 80 ms each, 2400 ms total, infinite loop. Across seven cases — palette cut to 64 colours, cut to 16, dithering on and off, a different animated container — every output came back as 30 frames, 80 ms per frame, 2400 ms total, looping forever. Identical to the source, down to the field.
What does change playback speed is dropping frames. If you keep every second frame, you have half as many frames, but the ones you kept still carry their original delay — nobody doubles it for you. The total duration halves, so the animation plays at 2x. On a second clip I drew the same way, a fake admin UI at 960×600, 75 frames, 60 ms each, keeping every second frame cut the file by 28.8% with the surviving frames measuring 68.5 dB against the source, while the duration went from 4500 ms to 2280 ms. Every third frame saved 42.5% and gave me 3x speed. That is the format working as designed: dropping frames and adjusting delays are two separate operations, and doing one doesn't imply the other. Set playback back to 0.5x and you're at the original pace, at no cost in bytes — I compared a speed-adjusted export against the baseline and the two files were byte-for-byte identical, only the delay field written differently.
The other half of "it looks wrong now" is banding, and that one really is caused by reducing colours. A GIF frame can use at most 256 colours, and that list is the palette. Cut the list and smooth gradients collapse into visible bands. I measured it instead of judging by eye: take one row of pixels across a frame, count the distinct colours and how wide each run of identical colour is. At 256 colours my sample row had 22 colours, runs averaging 17.8 px. At 16 colours it was down to 5 colours, average run 96 px, widest 134 px — on a frame 480 px wide, one colour covering a quarter of it. The file dropped from 670 KB to 186 KB, so the saving is real, and so is the cost.
Dithering is the usual fix for banding — scatter two available colours so they read as a third from a distance — and on a GIF it costs more than the banding does. At 64 colours it took the file from 363,719 bytes to 1,004,544 bytes: 2.76x, and 69% larger than exporting at 256 colours with no reduction at all. LZW only rewards runs of repeated pixels along a row, and dithering replaces smooth areas with pixel-level noise, so there is nothing left to collapse. My sample row went from 20 runs to 245. Dithering a GIF is effectively opting out of compression.
If the clip is a gradient or a photo, the 256-colour ceiling is the actual problem and no parameter gets around it. The same source as animated WebP came out at 167 KB with 129 colours on the sampled row — slightly smaller than the 16-colour GIF and twenty times richer. For UI captures and flat line art I still use GIF; the colour count is low to begin with.
I ran these exports in ImgIng (https://imging.ai/ ), mostly because the work happens locally in the browser — I watched the Network panel from import to save and there wasn't a single non-GET request — and because all 16 exports matched the source on frame count, delay, duration and loop. Had the tool rewritten the timeline, the claim above would be worthless. What I still don't have is a rule for how far you can drop frames before it reads as broken: two-for-one is usually invisible on a UI capture, three-for-one makes cursor movement jump, and past that it depends on the footage.
Top comments (0)