Skip to content

GIF

currentMigrated from docs/images/GIF.mdx at 37c3317fd058; modified for Encode Wiki · CC BY-SA 4.0

GIF (CompuServe, 1987) is an indexed-colour format with LZW compression: at most 256 colours per frame, one fully transparent colour at most, no partial alpha. It survives because every chat app, forum and browser plays it without asking. Compression is poor by modern standards, so GIFs of video-like content are many times larger than the same clip as video, animated WebP, animated AVIF or APNG.

PropertyValue
CompressionLossless LZW of palette indices (making the palette is the lossy step)
Colours256 per frame (a global table plus optional per-frame tables)
TransparencyOne fully transparent palette index, no partial alpha
AnimationYes, frame delays in hundredths of a second, loop count
Royalty-freeYes
  • Short loops that must work in apps that accept nothing else: chat, forums, issue trackers, email.
  • Small flat-colour animations, pixel art and UI demos with few colours and little motion.
  • Don’t use it for screen recordings or clips of film or games if you control the page. A muted, looping <video> (MP4 or WebM) is far smaller and keeps full colour. Animated WebP or AVIF is the middle path when you need an <img>.

Converting straight to GIF makes FFmpeg use a generic palette, which looks dithered and muddy. Generate a palette from the clip and apply it. In one command:

Terminal window
ffmpeg -i input.mp4 -vf "fps=15,scale=480:-1:flags=lanczos,split[a][b];[a]palettegen[p];[b][p]paletteuse" output.gif

or in two steps, which lets you reuse or inspect the palette:

Terminal window
ffmpeg -i input.mp4 -vf "fps=15,scale=480:-1:flags=lanczos,palettegen" palette.png
ffmpeg -i input.mp4 -i palette.png -lavfi "fps=15,scale=480:-1:flags=lanczos[x];[x][1:v]paletteuse" output.gif

Frame rate and width have the biggest effect on size. Use a trim (-ss and -t) to keep clips short.

OptionUse
palettegen=stats_mode=diffWeights colours towards the parts of the image that change. Better when the background is static (screen recordings).
palettegen=stats_mode=single with paletteuse=new=1One palette per frame. Helps when colours change a lot between scenes, but frames carry their own palettes and the file usually grows.
paletteuse=dither=bayer:bayer_scale=5Ordered dithering gives much smaller files and no shimmering noise, at the cost of a visible crosshatch pattern. Default is sierra2_4a, which looks smoother but compresses worse.
paletteuse=diff_mode=rectangleOnly re-encodes the changed rectangle of each frame. Helps whenever part of the picture is static.
palettegen=max_colors=128Fewer colours, smaller file.
-loop0 loops forever (the default), -1 plays once.

Frame delays are stored in centiseconds, so 15 fps (6.67 cs) is approximated, and browsers play delays of 1 cs or less as 10 cs, so 50 fps is the ceiling. 25 fps (4 cs) and 20 fps (5 cs) are exact.

gifski (AGPL-3.0) produces much better-looking GIFs than FFmpeg’s filters by using per-frame palettes with temporal dithering, so frames can use thousands of colours across a clip.

Terminal window
ffmpeg -i input.mp4 -f yuv4mpegpipe - | gifski --fps 15 --width 640 -o output.gif -
gifski --quality 80 --width 640 --fps 15 -o output.gif frame*.png

gifski limits animations to about 800×600 unless you pass --width. --fps defaults to 20: video input (Y4M) is resampled to that rate by dropping or duplicating frames, so set it in gifski rather than with an FFmpeg fps filter; for PNG frames it sets the playback speed. To shrink the output, reduce --width first (the biggest effect), then --quality (default 90), --lossy-quality and --motion-quality. If the source was lossy video, downscale it too: compression noise inflates the file.

Terminal window
gifsicle -O3 input.gif -o output.gif
gifsicle -O3 --lossy=80 --colors 128 input.gif -o output.gif

-O3 is lossless frame-rectangle optimisation. --lossy (bare = 20) trades noise for size, and --colors re-quantises the palette. Use gifsicle --info to inspect frames, delays and disposal. Quote frame selectors such as '#0' so the shell doesn’t read them as comments.

  • Palette per scene: a single global palette over a clip with different scenes looks bad; use stats_mode=single or split the clip.
  • Transparency has no soft edges: antialiased cut-outs get a halo against backgrounds other than the one they were made on. Use paletteuse=alpha_threshold=... and test on light and dark backgrounds, or use APNG/WebP.
  • Resizing after palette creation adds colours that don’t exist in the palette. Scale before palettegen, as in the commands above.
  • Services that convert GIFs to video anyway: if the service accepts a video, upload that instead.

Every option and default for Gifsicle 1.96 and the FFmpeg 9.0.2 GIF encoder, muxer and palette filters. Reading option tables explains the Default column.

Most frame options apply to the inputs that follow them, so order matters. A default is the state when the option is omitted; --same-... resets an earlier override. Bare --lossy means 20, bare --optimize means level 1 and bare --threads means 8.

All Gifsicle options, aliases and omission defaults
Parameter / aliasesDefaultValues / effect
--appendnot invokedAppends frames from the following GIFs to the end of the input.
--app-extension, -xnot invokedAdds an application extension (name, data) to the next frames.
--no-app-extensionsoffRemoves application extensions from the input.
--background, -B, +B, --no-background, --bg, --no-bginput-dependentSets the background colour, as a palette index or colour; the negated form picks index 0.
--batch, -boffModifies the named input files in place; overwrites every input.
--careful, --no-carefuloffWrites larger GIFs that avoid bugs in some other programs.
--change-color, --no-change-colornoneReplaces one colour with another throughout: —change-color COL1 COL2.
--color-info, --cinfo, --no-cinfo, --no-color-infooffAdds palette details to —info output.
--crop, --clip, --no-clip, --no-cropnoneCrops frames to X,Y+WxH or X,Y-X2,Y2.
--colors, -k, +k, --no-colors0 (no reduction)Reduces the palette to N colours (2–256); changes pixels.
--color-method, --methoddiversityPalette selection used by —colors: diversity, blend-diversity or median-cut.
--comment, -cnone addedAdds a comment before the next frame.
--no-comments, +coffRemoves comments from the input; +c is the same.
--conserve-memory, --no-conserve-memory0 (not requested)Uses less memory at the cost of speed.
--crop-transparency, --no-crop-transparencyoffCrops transparent borders off the image.
--delay, -d, +d, --no-delayinput-dependent (-1)Frame delay in hundredths of a second (16-bit).
--deletenot invokedDeletes the selected frames until —done.
--disposal, -D, +D, --no-disposalinput-dependent (-1)Frame disposal: none/0, asis/1, background/2, previous/3; 4–7 are reserved.
--dither, -f, +f, --no-dithernoneDithers after a palette change; optional method name (see below), bare means default.
--donenot invokedEnds a —delete, —insert-before, —append or —replace operation.
--explode, -eoffWrites each frame to its own file, named input.frame-number.
--explode-by-name, -EoffLike —explode, but names files after the stored frame names.
--extensionnot invokedAdds a raw extension: —extension NUMBER DATA.
--no-extension, --no-extensions, +xoffRemoves non-application extensions from the input; +x is the same.
--extension-info, --no-extension-info, --xinfo, --no-xinfooffAdds extension details to —info output.
--flip-horizontal, --no-flip-horizontaloffMirrors the following frames left to right.
--flip-vertical, --no-flip-verticaloffMirrors the following frames top to bottom.
--no-flipnot invokedClears pending flips.
--gamma, --no-gammasRGBGamma for colour reduction: sRGB, a number, or oklab on builds that support them.
--grayoffReduces to a grayscale palette; same as —use-colormap gray.
--help, -hnot invokedPrints help and exits.
--ignore-errors, --no-ignore-errorsonKeeps going after recoverable input errors; —no-ignore-errors quits on badly damaged GIFs.
--info, -I, +I, --no-infooffPrints input information and suppresses GIF output; -II keeps the output.
--insert-beforenot invokedInserts the following GIFs before the selected frame.
--interlace, -i, +i, --no-interlaceinput-dependent (-1)Writes interlaced frames; +i turns it off.
--logical-screen, -S, +S, --no-logical-screen, --screen, --no-screeninput-dependentCanvas size WxH; the negated form recomputes it from the frames.
--loopcount, -l, +l, --no-loopcountinput-dependent (-2)Repeats after the first play: =1 shows the animation twice; bare or forever loops endlessly.
--lossy0Trades colour accuracy for smaller files, adding noise; bare means 20.
--merge, -mon (normal mode)Combines all inputs into one GIF; this is the default mode.
--multifile, --no-multifileoffReads concatenated GIF streams from one file.
--name, -nnone addedSets the next frame’s name.
--nextfile, --no-nextfileoffReads concatenated GIF streams, one stream per input.
--no-names, +noffRemoves frame names from the input; +n is the same.
--optimize, -O, +O, --no-optimize0Shrinks animations: 1 stores changed areas, 2 adds transparency, 3 tries more methods; bare means 1.
--output, -omode-dependentOutput file; - means stdout.
--position, -p, +p, --no-positioninput-dependent (-1,-1)Frame offset as X,Y; the negated form sets 0,0.
--replacenot invokedReplaces the selected frames with the following GIFs.
--resize, --no-resize, --resiz, --no-resiz, --resi, --no-resi, --res, --no-resnoneResizes to WxH; 0 for one side keeps the aspect ratio, both sides can change it.
--resize-width, --no-resize-widthnoneResizes to this width, keeping the aspect ratio.
--resize-height, --no-resize-heightnoneResizes to this height, keeping the aspect ratio.
--resize-fit, --no-resize-fit, --resize-fi, --no-resize-fi, --resize-f, --no-resize-fnoneShrinks to fit inside WxH, keeping the aspect ratio; never enlarges.
--resize-fit-width, --no-resize-fit-widthnoneShrinks to this width if wider, keeping the aspect ratio.
--resize-fit-height, --no-resize-fit-heightnoneShrinks to this height if taller, keeping the aspect ratio.
--resize-touch, --no-resize-touch, --resize-touc, --no-resize-touc, --resize-tou, --no-resize-tou, --resize-to, --no-resize-to, --resize-t, --no-resize-tnoneResizes up or down until it touches WxH, keeping the aspect ratio.
--resize-touch-width, --no-resize-touch-widthnoneResizes to this width, enlarging if needed, keeping the aspect ratio.
--resize-touch-height, --no-resize-touch-heightnoneRegistered, but 1.96 treats the value as a width; use —resize-touch WxH.
--resize-geometry, --no-resize-geometrynoneResizes using a geometry string with optional fit, fill and percentage modifiers.
--resize-method, --scale-methodmixResampling filter: point, mix, box, catrom, lanczos2, lanczos3 or mitchell.
--resize-colors, --no-resize-colors, --scale-colors, --no-scale-colors0Lets resizing add colours up to N (at most 256).
--rotate-90offRotates the following frames by 90 degrees.
--rotate-180offRotates the following frames 180 degrees.
--rotate-270offRotates the following frames by 270 degrees.
--no-rotatenot invokedClears the pending rotation.
--same-app-extensionsnot invokedGoes back to the source’s application extensions.
--same-background, --same-bgnot invokedGoes back to the source’s background colour.
--same-clip, --same-cropnot invokedGoes back to the source’s frame size, with no crop.
--same-commentsnot invokedGoes back to the source’s comments.
--same-extensionsnot invokedGoes back to the source’s extensions.
--same-interlacenot invokedGoes back to the source’s interlacing.
--same-logical-screen, --same-screennot invokedGoes back to the source’s canvas size.
--same-loopcountnot invokedGoes back to the source’s loop count.
--same-disposalnot invokedGoes back to the source’s frame disposal.
--same-delaynot invokedGoes back to the source’s frame delays.
--same-namesnot invokedGoes back to the source’s frame names.
--same-positionnot invokedGoes back to the source’s frame positions.
--same-transparentnot invokedGoes back to the source’s transparent colour.
--scale, --no-scalenoneScales by X or XxY factors.
--size-info, --sinfo, --no-sinfo, --no-size-infooffAdds compression details to —info output.
--transform-colormap, --no-transform-colormapnonePipes each output palette through a shell command; never insert untrusted text.
--transparent, -t, +t, --no-transparentinput-dependentMakes a colour transparent, by palette index or colour; the negated form removes transparency.
--unoptimize, -U, +U, --no-unoptimizeoffExpands optimized frames into full images before editing.
--use-colormap, --no-use-colormapnoneUses web, gray, bw or a GIF file’s palette; output may use only part of it.
--use-exact-colormap, --no-use-exact-colormapnoneSame palette sources, but keeps the whole palette.
--verbose, -V, +V, --no-verbose, -v, +voffPrints progress, listing files read and written.
--versionnot invokedPrints the version and exits.
--warnings, -w, +w, --no-warningson-w hides warnings, +w shows them.
--threads, -j, +j, --no-threads0Uses N threads (bare means 8); needs a build with thread support.
FILEstdin when no files are namedInput GIF; reads stdin when none is named.
FRAMEall input framesFrame selector: #num, #first-last, #first- or #name; quote it so the shell keeps the #.
--not invokedEnds options so later arguments are filenames.

Dither names: none/posterize, default, floyd-steinberg/fs, atkinson/at, o3, o4, o8, ro64/ordered, diag45, halftone, sqhalftone. Comma-separated numbers select matrix size or level count (for example o,3).

--resize-touch-height is registered but dispatches its value as a width in 1.96, so use --resize-touch WxH. The initial gamma mode is sRGB although the help prints [2.2].

Parameter / aliasesDefaultValues / effect
-gifflagsoffsetting+transdiffoffsetting (1) crops each frame to the changed area; transdiff (2) makes unchanged pixels transparent.
-gifimagefalseWrites each frame as its own complete single-image GIF.
-global_palettetrueWrites a global palette in the header when the frames allow it.

Loop counts and the last frame’s delay change presentation, not decoded colours.

Parameter / aliasesDefaultValues / effect
-loop0Loop count, -1 to 65535: 0 loops forever, -1 omits the loop extension.
-final_delay-1Hold time after the last frame in hundredths of a second; -1 uses that frame’s own duration.

Filter syntax is palettegen=key=value:key=value.

Parameter / aliasesDefaultValues / effect
max_colors256Palette size limit, 2–256; reserve_transparent uses one of the slots.
reserve_transparenttrueReserves a palette entry for transparency; set 0 for opaque sources to gain a colour.
transparency_colorlimeColour stored in the reserved transparent entry.
stats_modefullfull (0) analyzes every frame, diff (1) only changed regions, single (2) builds one palette per frame.

debug_kdtree is a debugging option outside the stable filter API.

Parameter / aliasesDefaultValues / effect
dithersierra2_4aDither algorithm by name or number: 0 off, 1 bayer, 2 heckbert, 3 floyd_steinberg, 4 sierra2, 5 sierra2_4a, 6 sierra3, 7 burkes, 8 atkinson.
bayer_scale20–5 for dither=bayer: lower shows more crosshatch, higher shows more banding.
diff_mode0rectangle (1) re-maps only the area that changed since the last frame; 0 maps whole frames.
newfalseTakes a new palette for every frame; pair with palettegen stats_mode=single.
alpha_threshold128Pixels with alpha below this value, 0–255, become transparent.
debug_kdtreeunsetWrites a Graphviz kd-tree dump to this path; for debugging only.