Build1 publisher3 min readPublished
Dropping to every third frame beat the encoder settings search
A byte-level walk of a 1.51 MB screen recording puts the untouchable palette tables at under 4 percent of the file. The 38 percent floor has to be explained by redraw rectangles that average 59 percent of the canvas.
The Engineer · Build desk

What happened
- A 4.5-second 960x600 admin dashboard recording of 75 frames came out at 1.51 MB, and re-encoding it at every available setting stopped at 38 percent saved.
- Deleting frames saved 28.8 percent at every second frame and 42.5 percent at every third, with the frames that survive coming out pixel-identical.
- On the gradient sample, dithering 64 colors to hide banding took the file from 363,719 bytes to 1,004,544 bytes.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- decision A GIF size budget is better spent on the decimation pass than on the quantizer, because removing frames outperformed every encoder setting the author tried.
- constraint Any tool that drops frames has to own the delay rewrite, and 10 ms delay resolution means it will land near the original duration and miss it.
- contradiction The post attributes the floor to fixed container cost, while its own palette count covers a small fraction of the file, so a reader who follows the conclusion optimizes the wrong bytes.
- capability For UI recordings with motion in two corners, GIF differencing returns almost nothing, so the savings that remain sit with codecs that decide per macroblock.
Fifty-seven kilobytes of local color tables is real overhead, and it survives every quality slider. In that recording, 74 of the 75 frames carry their own 256-color table at 768 bytes each, 56,832 bytes in total [7]. It is also under 4 percent of a 1.51 MB file [1][1]. Four percent of the bytes cannot account for a wall at 38 percent saved [2]. The already-reduced 64-color sample spends 7,104 bytes across 37 tables [8]. That is the same overhead an order of magnitude down. The post says the parse showed "a large part of that file is fixed cost that no encoder can touch" [3]. That is true of the redraw rectangles, and the palette measurement it publishes is the wrong evidence for it. The parse itself is cheap, because GIF89a is a header followed by blocks. 0x21 opens an extension, 0x2C opens an image descriptor, and every payload is a chain of sub-blocks ending in a zero-length byte [4]. Walking the image descriptors showed 68 of the 75 frames using partial refresh, with the dirty region averaging 59.0 percent of the canvas [11]. One rectangle has to enclose every changed pixel, so a cursor moving on the left and a clock ticking in the top right corner drag the still table between them into the same redraw [12]. On the KPI demo, with numbers, bars and a pie animating in different corners, the dirty region reaches 90.5 percent and differencing is effectively off [13]. H.264 decides per macroblock, and a still region in the middle can cost nothing [14]. LZW scans pixel runs row by row and saves bytes only where it has seen the run before [15]. The gradient sample sits at the far end of that. None of its 30 frames can do a partial refresh, and it spends 0.176 bytes per pixel per frame against 0.037 for the dashboard recording, a factor of 4.8 [16]. Dithering the banding away costs more than the banding did. At 64 colors the gradient is 363,719 bytes undithered and 1,004,544 bytes dithered, 2.76 times larger and 69 percent larger than the same clip at 256 colors with no dither [17]. Counting along one pixel row: 20 runs of identical color undithered, 245 with dithering [18]. That leaves two levers, fewer colors and fewer frames [21]. Dropping to every second frame saved 28.8 percent and to every third 42.5 percent, with the surviving frames pixel-identical [9]. Halving the frame count removed only 28.8 percent of the bytes, so each surviving frame costs about 42 percent more than it did before [2]. The every-third figure beats the entire settings search by 4.5 points [3]. Then the timing has to be put back, because deleted frames do not lengthen the ones that remain and the clip plays at double speed [10]. The graphic control extension stores delay in centiseconds, and every frame here reads back as 6, so 60 ms and 4500 ms in total [5]. Resolution is 10 ms, and a 66 ms frame lands on 60 [6]. Keeping every second frame of 75 leaves 38 frames; 4500 divided by 38 is 118.4 ms. That is not a legal delay, and the closest legal one gives 38 times 120 ms, or 4560 ms [4]. A 15 fps target is out of reach too: 60 ms is 16.7 fps and the next step down is 14.3 [5]. Whether the percentages transfer depends on how the frames were made. All four samples are invented animations drawn frame by frame by a script: a dashboard recording with a moving cursor and a ticking clock, a smooth gradient, a file already squeezed once, and a KPI demo [19]. Each frame was drawn at 2x and downscaled with Lanczos, because a real capture is a retina capture plus a downscale.
What to watch
- Whether the generator scripts are published, so the 59.0 percent dirty-region average can be re-measured on real screen captures rather than script-drawn frames.
- A matched-quality encode of the same 75-frame clip in a per-macroblock codec would size the differencing gap the post only describes.
- Whether the 64-color sample, at 7,104 bytes of tables, responds to frame decimation the same way the 256-color recording did.