DEV Community

Lank_M
Lank_M

Posted on

Where to put a GIF size budget check in an asset pipeline

Put the GIF size check on the finished file, after every encoder setting has been applied, and make it fail the build with a number. A rule on the settings ("max 64 colors, dither off") looks cheaper to write, but it cannot predict bytes.

I ran into this while measuring how colour count and dithering change GIF size. The sample is one 10-frame animation, 675×599, 3,003,893 bytes (MORPH.gif, by Kowloonese, Wikimedia Commons, CC BY-SA 3.0, resized and re-encoded for the tests). Everything below is that one file, one machine, one run each. Sizes are plain bytes.

The same switch gave 0.7% and 50.4%

In the animation workshop of ImgIng, a browser tool, I exported GIFs at four palette sizes with Floyd–Steinberg dithering off and on. At 32 colours the file was 811,310 bytes with dithering off and 1,110,431 with it on, which is +36.9%. At 256 colours it was 1,525,126 against 1,535,115, which is +0.7%. My guess is that the source is already close to a 256-colour image, so there is little error left to spread. I did not test that.

I then rebuilt the experiment outside the tool: a blurred copy of the frames (to approximate a true-colour source), a median-cut palette from Pillow, and gifsicle -O3. Over eight runs (original frames and the blurred copy, four palette sizes each) the dithered file was always larger, between +32.0% and +50.4%. At 32 colours the blurred copy gave 572,768 bytes without dithering and 861,321 with it.

So one toggle is worth anywhere from noise to half the file, and a settings rule has no way to know which case it is looking at.

What moves the bytes

I counted the output codes of a GIF-style LZW encoder on the same four files. At 32 colours, dithering raised the code count from 418,303 to 628,637 (+50.3%) while the file grew +50.4%. At 256 colours the two numbers were +32.0% and +32.0%. They move together, which is measured. Why they move together is my reading: LZW grows its dictionary from repeated pixel runs, and dither noise breaks the runs up. I did not test that mechanism on its own.

Two things did not help on this sample. gifsicle -O3 saved almost nothing on these rebuilt files because every frame changes, so there is no frame difference to exploit. And --lossy saved at most 22.3% (lossy 200 on the undithered source), while the dithered source stayed 32.7% to 35.4% larger at lossy 30 to 200. Lossy did not close the gap.

Where the gate goes

There are three candidate places. An encoder wrapper can check settings, which fails for the reason above. An upload hook can check size, but by then the build is done and the only fix is rework. A step in the build, right after encoding, can check the actual file against a budget and print the number.

I would pick the build step, keep the budget in repo config, and set it per asset class instead of one global number, because a hero animation and an inline reaction GIF have different limits. Keep the upload check only if users can bypass the build. When the gate fails, the order I would try levers in is colour count, then dithering, then lossy, then frame count or dimensions. I only tried the last two outside the tool, on the blurred copy: keeping every other frame saved 47.0% and scaling to 50% saved 66.6%.

$ gifsicle -O3 --colors 32 --no-dither SRC.gif -o budget.gif; stat -f%z budget.gif
426315
$ gifsicle -O3 --colors 32 -f SRC.gif -o budget.gif; stat -f%z budget.gif
1005103
$ gifsicle -O3 --lossy=60 SRC_256_plain.gif -o budget.gif; stat -f%z budget.gif
1265020
$ gifsicle -O3 --lossy=60 SRC_256_dith.gif -o budget.gif; stat -f%z budget.gif
1695376
Enter fullscreen mode Exit fullscreen mode

The first pair reduces the original GIF directly, the second pair starts from two 256-colour files I prepared in Pillow, so the pairs are not comparable with each other or with the tables above. The gate itself is two lines in the build script:

size=$(stat -f%z "$f")
[ "$size" -le "$BUDGET" ] || { echo "$f: $size > $BUDGET"; exit 1; }
Enter fullscreen mode Exit fullscreen mode

Trying settings by hand first

Before encoding a setting into the pipeline, it helps to see it on the actual asset. ImgIng ( https://imging.ai/ ) lets you set colour count and dithering by hand in its animation workshop, on a desktop browser. That module is not one I worked on, and I only looked at its GIF output. In my network capture of one run, no request carried the file during compression.

What this does not cover

This is one photographic gradient animation. Screen recordings, flat-colour clips, long animations, ordered dithering and in-tool frame dropping and scaling were not tested, and the percentages should not be carried over to them. If your assets look like that sample, the gate on the output is the part I would copy. The thresholds I would measure again on your own files.

Top comments (0)