Spots

Why Your Video-to-GIF Conversions Look Dirty, and the Two-Pass Palette Fix

Most video-to-GIF conversions look dirty for one reason: they quantize every frame against a generic palette. A fixed 256-color table has no idea that your clip is mostly dark green grass with a single red ball in it. The fix is a two-pass approach: build a palette that belongs to this one video, then map the video onto it. That is the whole idea. The rest of this post is the mechanics, plus an honest list of what a GIF still cannot do. Why a generic palette fails

A typical converter does one of two things

A typical converter does one of two things. It maps every frame onto the web-safe 216-color table, which ignores the real colors of your clip entirely. Or it builds one palette from the first frame and reuses it for the whole clip, which only works if the color story never changes. One cut, one camera pan, or one light change, and the reused palette is wrong for everything after it.

Either way, the 256 slots get spent on

Either way, the 256 slots get spent on colors that are not in your video, and the colors that are in your video get approximated. GIF gives you 256 colors, and that is a hard ceiling

A GIF frame is not RGB. It is

A GIF frame is not RGB. It is an indexed image: each pixel is an index into a color table, and that table holds at most 256 entries. Video is roughly 16.7 million possible RGB values. Converting to GIF is lossy quantization, and no filter chain can bring back what the index step discards.

Dithering helps with the symptom. It spreads the

Dithering helps with the symptom. It spreads the quantization error spatially so gradients stop banding into flat steps. But dithering is a perceptual trick, it does not add colors. On noisy live-action footage it also adds visible grain and can make the file bigger, because a noisy pattern compresses worse.

So the goal is not a clean image

So the goal is not a clean image. The goal is a palette built from your actual frames, so the 256 slots land where the colors are. The two-pass filter chain Both passes live in a single filter_complex graph, which is why the split filter appears: Walking through the pieces:

fps=15 reduces the source to 15 frames per

fps=15 reduces the source to 15 frames per second. GIF has no inter-frame compression worth depending on, so every kept frame is a full indexed image. Fewer frames means a smaller file. 15 is a common balance point for screen recordings and short UI clips.

scale=480:-2:flags=lanczos resizes to 480 px wide and computes

scale=480:-2:flags=lanczos resizes to 480 px wide and computes the height to preserve the aspect ratio, forced to an even number by -2. lanczos resamples more sharply than the default bilinear filter, which matters because a soft downscale hides detail the color step is about to mangle anyway.

split [a][b] duplicates the processed stream. This is

split [a][b] duplicates the processed stream. This is the part people skip and then wonder why nothing works. palettegen has to look at the video to build a palette, and paletteuse has to apply that palette to the same frames. Without split, one branch would consume the stream and the other would have nothing, or you would build a palette from a different input than the one you convert. The split feeds both branches from a single decode. palettegen=max_colors=256 emits one PNG holding the 256 colors chosen across the sampled frames.

paletteuse=dither=sierra2_4a:diff_mode=rectangle applies the palette.

paletteuse=dither=sierra2_4a:diff_mode=rectangle applies the palette. sierra2_4a is an error-diffusion dither that looks cleaner than classic Floyd-Steinberg on video. diff_mode=rectangle restricts the error diffusion to the rectangle that actually changed between two frames, which speeds the encode up and keeps static regions from shimmering. -loop 0 writes the Netscape looping extension with an infinite loop count. Without it, the GIF plays once and stops.

News

Why Your Video-to-GIF Conversions Look Dirty, and the Two-Pass Palette Fix

Most video-to-GIF conversions look dirty for one reason: they quantize every frame against a generic palette.

@spots #dev
Source: Dev.to
See more like this