If a GIF refuses to get smaller, I would check the colour count and then whether dithering is switched on. Dithering sounds like a quality feature that should cost nothing, but on the animation I tested it made the file between 0.7% and 36.9% bigger in a browser tool and between 32% and 50% bigger in my own rebuild. I wanted to know why, so I counted what a GIF-style encoder actually writes.
Everything below comes from one sample, one laptop and one run per setting. The sample is MORPH.gif, 675×599, 10 frames, 3,003,893 bytes, by Kowloonese on Wikimedia Commons, CC BY-SA 3.0. I re-encoded it for the tests. It is a photo-like fade with text, and almost every pixel changes in every frame. Sizes are plain bytes. A screen recording with flat colour blocks may behave very differently, and I did not test one.
Check the palette size first
For the first set of numbers I used the animation workshop in ImgIng https://imging.ai/ in a desktop browser, exporting GIF with the colour count and dithering set by hand. With dithering off, 256 colours gave 1,525,126 bytes, 128 gave 1,337,247, 64 gave 1,083,830 and 32 gave 811,310. Colour count is the cheapest lever to try, and going from 256 to 32 colours took the file down by 46.8%.
Then check the dithering switch
Same tool, same four palette sizes, Floyd–Steinberg dithering on. At 32 colours the file was 1,110,431 bytes, which is 36.9% more than with it off. At 64 it was +23.6%, at 128 it was +10.4%, and at 256 it was 1,535,115 against 1,525,126, only +0.7%.
That last row confuses me. My guess is that the source already looks like a 256-colour image, so there is little error left for dithering to spread around. I did not test that, so treat it as a guess.
Dithering breaks the repeats that LZW needs
To see whether the tool's numbers reproduce, I rebuilt the test outside it. I blurred the frames slightly to approximate a true-colour source, built a median-cut palette with Pillow, and ran gifsicle -O3. Across eight combinations (the original frames and the blurred copy, four palette sizes each), the dithered file was always bigger, between +32.0% and +50.4%. On the blurred copy at 32 colours the plain version was 572,768 bytes and the dithered one 861,321.
Then I counted the codes a GIF-style LZW encoder writes for those blurred frames. At 32 colours, dithering raised the code count from 418,303 to 628,637, which is +50.3%, while the file grew +50.4%. The share of pixels equal to their left neighbour dropped from 85.8% to 55.8%. My reading, and it is an inference I did not verify further, is that LZW grows its dictionary from repeated pixel runs. Dithering sprinkles single-pixel noise through flat areas, the runs get cut short, and each code covers fewer pixels (9.67 on average down to 6.43). This is Floyd–Steinberg only. I did not try ordered dithering.
Try gifsicle on the original before blaming the file
On the original GIF I also ran gifsicle's own colour reduction, once with dithering off and once with it forced on:
$ gifsicle --info in.gif | head -4
* in.gif 10 images
logical screen 675x599
global color table [256]
background 0
$ gifsicle -O3 --colors 32 --no-dither in.gif -o out.gif; stat -f%z out.gif
426315
$ gifsicle -O3 --colors 32 -f in.gif -o out.gif; stat -f%z out.gif
1005103
426,315 against 1,005,103 is a bigger gap than anything above. These numbers are not comparable with the earlier tables, because here gifsicle merges colours directly on frames that already carry baked-in noise, so I only keep them as a command example.
Lossy comes last
I tried --lossy only after the other two. On the 256-colour blurred source, lossy 200 took the plain file from 1,364,178 to 1,059,906 bytes, which is 22.3% less. The dithered file went from 1,800,055 to 1,430,275, and it was still 34.9% bigger than the plain one. Lossy did not close the gap that dithering opened.
For a flat-colour screen capture the answer might be different, and that is the part I have not tried yet.
Top comments (0)