FFmpeg GIF Palette: The High-Quality GIF Command, Explained

Sep 23, 2026

FFmpeg makes a good GIF with two filters, not one. palettegen studies your video and writes out a palette image; paletteuse encodes the GIF against it. The filter docs put it in those terms: palettegen will "Generate one palette for a whole video stream", and paletteuse will "Use a palette to downsample an input video stream."

That word downsample is the point. Colour reduction is where a GIF turns muddy, and these two filters are your only vote on how it happens.

One caveat before any default below: FFmpeg's online documentation "is regenerated nightly, and corresponds to the newest FFmpeg revision," and tells you to "Consult your locally installed documentation for older versions." Confirm against ffmpeg -h filter=palettegen on your build.

The command

The docs give a two-step version, and it is the one to learn first because each half is inspectable:

# 1. build a palette from the whole clip
ffmpeg -i input.mkv -vf palettegen palette.png

# 2. encode the GIF against that palette
ffmpeg -i input.mkv -i palette.png -lavfi paletteuse output.gif

Open palette.png and you get a tiny square of swatches, not a picture. That is expected. The docs: "The filter takes two inputs: one video stream and a palette. The palette must be a 256 pixels image." The palette file is data, not a preview.

The single-command form you see everywhere else looks like this:

ffmpeg -i input.mkv -filter_complex "split[a][b];[a]palettegen[p];[b][p]paletteuse" output.gif

It is a community convention, not something FFmpeg's documentation prescribes. It works because split will "Split input into several identical outputs." and its count, "If unspecified, it defaults to 2." — one copy to palettegen, one held back for paletteuse.

What each flag does

This is where the quality difference lives, and where most tutorials stop.

palettegen

max_colors — "Set the maximum number of colors to quantize in the palette." Then the sentence almost nobody repeats: "Note: the palette will still contain 256 colors; the unused palette entries will be black."

That kills a popular assumption. Lowering max_colors limits how many colours get quantized into the palette; it does not produce a smaller one. The palette stays 256 entries and the leftovers are black. No default is documented.

reserve_transparent — "Create a palette of 255 colors maximum and reserve the last one for transparency. Reserving the transparency color is useful for GIF optimization. If not set, the maximum of colors in the palette will be 256. You probably want to disable this option for a standalone image. Set by default."

So it is on by default, your usable colour budget is 255 unless you say otherwise, and for a standalone still image the docs themselves suggest turning it off to reclaim that last colour.

transparency_color — "Set the color that will be used as background for transparency." No default is documented, so do not assume one.

stats_mode — "Set statistics mode." Three values, with a documented default of full:

  • full — "Compute full frame histograms."
  • diff — "Compute histograms only for the part that differs from previous frame. This might be relevant to give more importance to the moving part of your input if the background is static."
  • single — "Compute new histogram for each frame."

diff is the most under-explained option here, and the docs spell out its purpose: when the background is static, it weights the palette toward the part that moves. That is screen recordings, UI demos, terminal captures — exactly where full spends colour budget on a wall that never changes.

Frame metadata you can read. palettegen "exports the frame metadata lavfi.color_quant_ratio (nb_color_in / nb_color_out) which you can use to evaluate the degree of color quantization of the palette. This information is also visible at info logging level." You do not have to eyeball quality.

paletteuse

dither — the documented algorithms, with FFmpeg's own descriptions:

ValueWhat the docs say
bayer"Ordered 8x8 bayer dithering (deterministic)"
heckbertPaul Heckbert's 1982 "simple error diffusion" — the docs note it is "sometimes considered" wrong and "included as a reference"
floyd_steinbergerror diffusion
sierra2error diffusion
sierra2_4aSierra v2 "Lite" (error diffusion) — the default
sierra3error diffusion
burkeserror diffusion
atkinsonerror diffusion, "by Bill Atkinson at Apple Computer"
nonedithering off

"Default is sierra2_4a." Note what the docs do and do not say: bayer is the only mode tagged (deterministic); every other mode is error diffusion without that tag. Nothing claims which one yields a smaller file, so any ranking by output size is somebody's own testing.

bayer_scale — only relevant with bayer, and the clearest trade-off in the filter: "A low value means more visible pattern for less banding, and higher value means less visible pattern at the cost of more banding." Integer, "in the range [0,5]. Default is 2." That is the guidance — the popular bayer_scale=5 advice is a preference, not an official recommendation.

diff_mode — "Default is none." The rectangle value is worth the read in full: "Only the changing rectangle will be reprocessed. This is similar to GIF cropping/offsetting compression mechanism. This option can be useful for speed if only a part of the image is changing, and has use cases such as limiting the scope of the error diffusal dither to the rectangle that bounds the moving scene (it leads to more deterministic output if the scene doesn't change much, and as a result less moving noise and better GIF compression)."

FFmpeg rarely promises this much in one paragraph: speed, dither confined to the moving region, more deterministic output, less moving noise, better compression — all conditional on only part of the image changing. Pair it with stats_mode=diff and you attack the same property from both ends. None of it is quantified, so expect no percentages.

new — "Take new palette for each output frame." That is the entire documented description; what it pairs with and what it costs are not in the docs.

alpha_threshold — "Alpha values above this threshold will be treated as completely opaque, and values below this threshold will be treated as completely transparent." Integer "in the range [0,255]. Default is 128." Transparency is binary: no partial alpha, only a cut-off you can move.

Variations

fps will "Convert the video to specified constant frame rate by duplicating or dropping frames as necessary," and its own default is 25. FFmpeg publishes no recommended frame rate or width for GIF output, so any numbers you insert are your call, not a documented setting:

ffmpeg -i input.mkv -filter_complex \
  "fps=15,scale=480:-1:flags=lanczos,split[a][b];[a]palettegen=stats_mode=diff[p];[b][p]paletteuse=dither=bayer:bayer_scale=2:diff_mode=rectangle" \
  output.gif

The flags=lanczos part of that line deserves a footnote, because it is in virtually every GIF tutorial online. The scale filter's flags option sets "libswscale scaling flags", and the scaler docs now say of sws_flags: "This is also used to set the scaling algorithm, though this usage is deprecated in favor of setting ‘scaler’." The lanczos value under sws_flags is marked "(Deprecated)", and the documented sws_flags default is bicubic.

Three limits, and dropping any one turns it into a false claim:

  1. This is the nightly trunk documentation. On an older installed FFmpeg, flags=lanczos is not necessarily marked deprecated at all.
  2. What is deprecated is selecting an algorithm through sws_flags — not Lanczos itself. Under the newer scaler option, lanczos is an ordinary entry with no deprecation tag: "Lanczos resampling (sinc windowed sinc). The number of filter taps can be configured by setting param0, defaulting to 3."
  3. The docs do not say flags=lanczos will stop working or be removed.

One flag you can drop. Two gifflags are pre-enabled: offsetting ("Enables picture offsetting. Default is enabled.") and transdiff ("Enables transparency detection between frames. Default is enabled.") — making the widely copied -gifflags +transdiff redundant.

Skip the install

Need one clip as a GIF without an FFmpeg build on the machine? This converter does it in the browser tab — read from disk, processed locally, never uploaded.

Try it right here

Runs in your browser. Your file is not uploaded anywhere.

Loading tool…

Same tool as the standalone MP4 to GIF page, running on browser-based video processing.

Troubleshooting

  • paletteuse rejects your palette. It "must be a 256 pixels image." A frame grab or a resized palette.png is not that — regenerate it with palettegen.
  • Large areas come out black. Expected if you lowered max_colors: "the unused palette entries will be black."
  • The palette follows the background, not your subject. stats_mode defaults to full, across the whole frame. Switch to diff.
  • A crosshatch pattern across flat areas. Ordered Bayer dithering. Raising bayer_scale (range [0,5]) makes it less visible, at the documented cost of more banding.
  • Semi-transparent edges look hard-cut. alpha_threshold is a binary cut-off defaulting to 128.
  • An option here is missing on your build. Run ffmpeg -h filter=paletteuse and trust it over any article, this one included.

FAQ

Why does my FFmpeg GIF look grainy or washed out? Because it was encoded against a palette that does not represent your footage — wrong stats_mode, or max_colors cut down. Build the palette from the clip itself, then read the lavfi.color_quant_ratio value at info logging level.

What is the best dither setting for FFmpeg GIF? The default is sierra2_4a, and the docs stop short of ranking the alternatives. The one documented distinction: bayer is labelled "(deterministic)" while the others are error diffusion without that label. If frame-to-frame consistency matters, that is the mode with the documented property.

Do I still need flags=lanczos for a high-quality GIF? Your existing commands still run. But choosing an algorithm through sws_flags is now "deprecated in favor of setting ‘scaler’", the sws_flags lanczos value carries a "(Deprecated)" tag, and bicubic is the documented default. Lanczos itself is not deprecated — it is a normal entry under scaler.

Can I run palettegen and paletteuse in one command? Yes, with split, documented to produce identical outputs and to default to 2 of them. Note the one-line filtergraph everyone shares is a community pattern — the official examples are the two-step form.

How do I make an FFmpeg GIF smaller? The documented lever is diff_mode=rectangle, which FFmpeg says gives "less moving noise and better GIF compression" when only part of the image is changing. Fewer frames via fps and a smaller frame via scale also cut what has to be encoded.


More command walkthroughs live in the FFmpeg guides hub, including extracting audio without re-encoding. Prefer no command line? Every converter runs locally in your browser — see all Clapr tools.

Behaviour and defaults cited from FFmpeg's official filter, codec and scaler documentation, checked 16 September 2026. It tracks the newest revision — verify against ffmpeg -h filter=<name> on your build.

Clapr team

Clapr team

FFmpeg GIF Palette: The High-Quality GIF Command, Explained | Clapr Blog