DEV Community

NullPointerZen
NullPointerZen

Posted on

Fewer colors made an already-optimized GIF 91% bigger: gifsicle knobs, one at a time


I keep a small script that runs gifsicle on GIFs before they go into our docs, and I'd assumed every knob I add can only take bytes off. Then a 3.56 MB cat GIF came out of it bigger, so I sat down and tried gifsicle's knobs one at a time, each run with -O3 on top. Five of the eight runs left the file bigger than it started, and cutting the palette was the worst of them: at 128 colors the file was 191.2% of the original. Only dropping half the frames got anywhere near half.

The clip isn't mine. It's a dim phone video of a cat wagging its tail, uploaded to Wikimedia Commons by User:Mateus2019 under CC BY-SA 4.0: 320×240, 208 frames, 17.41 s, 3,728,810 bytes. MB here means 1024×1024 bytes. I used gifsicle 1.98 on an M4 Mac on 2026-10-21. There was a clue before any of the runs: gifsicle -I shows a comment in the file saying it was optimized with ezgif, so someone had already squeezed it once before uploading it.

Fewer colors and light lossy both made it bigger

To have something to compare against, I also made a clip that had never seen an optimizer: a 240×180 window slid across the cat's first frame, 2 px right and 1 px down per frame, 40 frames, saved with Pillow at 256 colors (852,486 bytes). Same knobs, same order, each one alone plus -O3:

gifsicle knob Cat (already optimized) Pan clip (never optimized)
-O3 only 103.4% 96.5%
--scale 0.5 88.6% 33.7%
--colors 128 191.2% 94.3%
--colors 64 157.7% 81.9%
--colors 64 --dither 191.3% 98.7%
--lossy=30 116.3% 43.8%
--lossy=80 94.8% 33.0%
keep every 2nd frame 52.0% not run

On the pan clip the knobs did what I expected, and the lossy pass and half size each left about a third. On the cat, lossy 30 grew the file by 16% and lossy 80 only got about 5% under the original. Half the width and height, a quarter of the pixels, still left 88.6%. I can't pin all of that on the earlier optimization, though. The pan is built from one still frame, so it has none of the frame-to-frame sensor noise the cat has, and that alone could explain a lot of the gap. For the color runs my guess is that re-quantizing a file someone already tuned scrambles the pixel order LZW was reusing. I haven't looked inside the encoder to confirm it.

Four GIF re-optimization attempts on the cat clip compared with the 3.56 MB original

Three of those gifsicle runs against the dashed original, plus ezgif's optimizer at default settings, which I tried once. The half-size bar is the one that bugs me: a quarter of the pixels, and it still sits at 3.15 MB. ezgif uploads the file to its server (its page says files are deleted after an hour) and came back 0.48% bigger, but a single default run doesn't tell me much about ezgif. Sample: Mateus2019 / Wikimedia Commons, CC BY-SA 4.0; the chart is shared under the same license.

A second lossy pass didn't stack

My script runs before every docs build, so some files go through it more than once. I took the copy that came out of -O3 --lossy=80 (3,536,073 bytes, 3.37 MB) and fed it back in. Another --lossy=80 gave 100.6%, plain -O3 100.1%, half size 96.4%, and 64 colors 167.3%. It looks like the first lossy pass had already given up whatever it was going to give up. The ezgif comment was still in that lossy copy, by the way, so that comment tells you a file was optimized at least once and nothing about what touched it last.

Dropping frames was the only knob near half

Keeping every second frame gave 1,938,784 bytes, 52.0% of the original. The catch is that gifsicle removes frames and leaves the delays alone, so the 17.41 s loop played in 8.75 s and the cat wagged at double speed. These frames mix 80, 90 and 170 ms delays, so getting the pace back means doubling each one, and I didn't do that step. The other way out is leaving GIF altogether. I ran the original through ImgIng (https://imging.ai/), whose animation tool works on the file inside a desktop browser tab; across my runs in Chromium 149 the page sent no non-GET requests. It picked animated WebP at quality 76 by default and got 951,206 bytes, 25.5% of the original, with the same 208 frames and timing. Zoomed in 4x, the dark grain was smoothed away and the fur went soft, which I judged by eye. The tool isn't offered on phones or tablets, can't compress an existing animated GIF in Safari, and has no target-size setting. Whether WebP is an option at all depends on where the file goes, and I didn't test any chat app.

What I'm changing in the script

After each gifsicle step it now compares the output with the input and keeps the input if the output grew. It also leaves the palette alone on files whose comment says they've been optimized. Frame dropping stays a manual call, since the delays need doubling and somebody has to watch the result. What I still can't explain is why plain -O3 came out 3.4% bigger rather than equal. I didn't take its output apart.

Top comments (0)