Deep Dive8 min read·March 2025

The Complete Guide to FFmpeg GIF Palette Optimisation

FFmpeg's two-pass GIF encoding pipeline — using the palettegen and paletteuse filters — is the most powerful free approach to high-quality GIF compression. Understanding every parameter in this pipeline gives you precise control over the trade-off between file size and visual quality.

Why Two Passes?

GIF's 256-colour limit means that before you can encode a frame, you need to decide which 256 colours best represent all the colours in your animation. A naive approach picks colours from the first frame only, which produces poor results for animations where the colour content changes significantly between frames. The two-pass approach solves this: the first pass (palettegen) analyses every frame to build an optimal global palette, and the second pass (paletteuse) uses that palette to encode the animation.

The Basic Two-Pass Command

The standard two-pass FFmpeg GIF command looks like this:

# Pass 1: Generate palette
ffmpeg -i input.mp4 -vf "fps=12,scale=480:-1:flags=lanczos,palettegen" palette.png
# Pass 2: Encode GIF using palette
ffmpeg -i input.mp4 -i palette.png -filter_complex "fps=12,scale=480:-1:flags=lanczos[x];[x][1:v]paletteuse" output.gif

The scale=480:-1 scales the width to 480px and calculates the height automatically to preserve the aspect ratio. The flags=lanczos uses the Lanczos resampling algorithm, which produces sharper results than the default bilinear scaling.

palettegen Parameters

The palettegen filter accepts several parameters that control how the palette is built:

ParameterDefaultRangeEffect
max_colors2564–256Maximum palette size. Reducing to 64 or 128 significantly shrinks file size.
stats_modefullfull / diff / singlefull: analyse all pixels. diff: only changed pixels. single: first frame only.
reserve_transparent10 or 1Reserves one palette slot for transparency. Set to 0 if no transparency needed.

The stats_mode=diff option is particularly useful for animations where most of the frame is static and only a small area changes — for example, a loading spinner on a white background. By analysing only the pixels that change between frames, it builds a palette optimised for the animated region rather than wasting palette slots on the static background.

paletteuse Parameters

The paletteuse filter controls how the palette is applied to each frame, including the dithering algorithm:

Dither OptionQualityFile SizeBest For
noneLowestSmallestSimple graphics, flat colours
bayerMediumSmallEmail, screen recordings
sierra2HighMediumPhotographic content
sierra2_4aHighestLargestMaximum quality, web pages
floyd_steinbergHighMedium-largeGeneral purpose

The diff_mode Parameter

The diff_mode parameter in paletteuse controls whether the filter encodes only the rectangular region that changed between frames (rectangle mode) or the full frame. Setting diff_mode=rectangle can significantly reduce file size for animations with small moving elements on a static background, because unchanged pixels don't need to be re-encoded.

ffmpeg -i input.mp4 -i palette.png \
-filter_complex "fps=10,scale=480:-1:flags=lanczos[x];[x][1:v]paletteuse=dither=sierra2_4a:diff_mode=rectangle" \
output.gif

Practical Recipes for Common Use Cases

Maximum quality, web page: Use max_colors=256, stats_mode=full, dither=sierra2_4a, 15 fps, scale to 100% of intended display size.

Balanced quality/size, social media: Use max_colors=128, stats_mode=full, dither=sierra2, 10 fps, scale to 480px wide.

Minimum size, email: Use max_colors=64, stats_mode=diff, dither=bayer:bayer_scale=5, 6–8 fps, scale to 600px wide.

Screen recording / UI animation: Use max_colors=32, stats_mode=diff, dither=none, 10–15 fps, scale to actual display size. Screen recordings have very few colours and benefit enormously from dither=none because flat UI colours compress perfectly without dithering noise.

Automating with a Shell Script

For repeated use, wrap the two-pass command in a shell function:

#!/bin/bash
gif_encode() {
local input="$1" output="$2" fps="${3:-10}" scale="${4:-480}" colors="${5:-128}"
local palette="/tmp/palette_$$.png"
ffmpeg -i "$input" -vf "fps=$fps,scale=$scale:-1:flags=lanczos,palettegen=max_colors=$colors:stats_mode=full" "$palette"
ffmpeg -i "$input" -i "$palette" -filter_complex "fps=$fps,scale=$scale:-1:flags=lanczos[x];[x][1:v]paletteuse=dither=sierra2_4a" "$output"
rm "$palette"
}
# Usage: gif_encode input.mp4 output.gif [fps] [width] [colors]
gif_encode clip.mp4 result.gif 10 480 128

Ready to try it yourself?

Use our free online GIF compressor and MP4-to-GIF converter — no signup required.