All formats

Pillow

LanguagePython 3
LicenseMIT-CMU (permissive, HPND-style)
Repositorygithub.com/python-pillow/Pillow
DocumentationGIF in Pillow's format docs
Depends onnothing for GIF (built-in LZW codec)
Read / writeyes / yes; animation, loop count, durations, disposal, transparency, comments, interlacing

Pillow opens a GIF as an image positioned on the first frame. im.n_frames gives the frame count, im.seek(i) or ImageSequence.Iterator(im) moves through frames. Pillow composites each frame with the previous ones following the disposal method, so every frame you get is the full canvas; frames after the first are returned in RGB or RGBA mode (configurable with GifImagePlugin.LOADING_STRATEGY). im.info holds duration (ms) of the current frame, loop, transparency, background and comment.

Saving with save_all=True writes an animation from a list of images; Pillow quantizes RGB frames to 256 colors and, with optimize=True, crops frames to the changed area.

Installation#

Shell
pip install pillow

Basic usage: read frames and durations#

Python
from PIL import Image, ImageSequence

with Image.open("anim.gif") as im:
    print(im.size, im.n_frames, "frames, loop =", im.info.get("loop"))  # loop 0 = forever
    for i, frame in enumerate(ImageSequence.Iterator(im)):
        print(i, frame.mode, frame.info.get("duration"), "ms")

How to create an animated GIF#

Python
from PIL import Image, ImageDraw

frames = []
for i in range(20):
    im = Image.new("RGB", (120, 120), "white")
    ImageDraw.Draw(im).ellipse((i * 5, 40, i * 5 + 30, 70), fill="red")
    frames.append(im)

frames[0].save(
    "anim.gif",
    save_all=True,
    append_images=frames[1:],
    duration=50,      # ms per frame, or a list with one value per frame
    loop=0,           # 0 = loop forever; omit to play once
    disposal=2,       # restore to background between frames
    optimize=True,
)

How to resize an animated GIF#

Python
from PIL import Image, ImageSequence

with Image.open("anim.gif") as im:
    durations = []
    frames = []
    for frame in ImageSequence.Iterator(im):
        durations.append(frame.info.get("duration", 100))
        frames.append(frame.convert("RGBA").resize((im.width // 2, im.height // 2), Image.Resampling.LANCZOS))
    loop = im.info.get("loop", 0)

frames[0].save("half.gif", save_all=True, append_images=frames[1:],
               duration=durations, loop=loop, disposal=2)

How to extract GIF frames as PNG#

Python
from PIL import Image, ImageSequence

with Image.open("anim.gif") as im:
    for i, frame in enumerate(ImageSequence.Iterator(im)):
        frame.convert("RGBA").save(f"frame-{i:03}.png")

How to control the palette and dithering#

Quantize frames yourself to choose the number of colors and the dithering:

Python
from PIL import Image

im = Image.open("photo.png").convert("RGB")
pal = im.quantize(colors=64, method=Image.Quantize.MEDIANCUT, dither=Image.Dither.NONE)
pal.save("photo.gif", optimize=True)

dither=Image.Dither.FLOYDSTEINBERG (the default) looks smoother but compresses worse.

Tips#