Create a GIF Using Python and Pillow: Frames, Timing, and Export

Quick answer: Create a GIF with Pillow by generating compatible image frames, saving the first frame with save_all=True, appending the remaining frames, and choosing explicit duration and loop behavior. Inspect the exported file because palette and transparency conversion can change the result.

Python Pool infographic showing Python Pillow frames moving through drawing, conversion, duration, looping, GIF export, and validation
A Pillow GIF is a sequence of compatible frames with explicit timing and loop behavior; validate the saved animation rather than trusting a single frame.

You can create a GIF using Python with Pillow by opening each frame and saving the first frame with save_all=True. The key options are append_images for the remaining frames, duration for frame speed, and loop for repeat behavior.

This guide shows two practical patterns: creating a GIF from existing image files and generating frames directly in Python. It also covers the common mistakes that make a GIF save only one frame or produce a file that is much larger than expected.

Install Pillow

Pillow is the maintained Python imaging library used for reading, editing, and saving image files. Install it in the same environment where your script will run:

python -m pip install pillow

Then import Image from PIL:

from PIL import Image

If you are not sure which interpreter your terminal is using, check your setup with this guide to checking the Python version before installing packages.

Create a GIF from image files

The simplest workflow is to prepare a list of image paths, open every image as a frame, and save the first frame as the GIF. Pillow writes the additional frames from append_images.

from PIL import Image

paths = ["frame1.png", "frame2.png", "frame3.png"]
frames = [Image.open(path).convert("RGB") for path in paths]

frames[0].save(
    "animation.gif",
    save_all=True,
    append_images=frames[1:],
    duration=150,
    loop=0,
)

for frame in frames:
    frame.close()

The important detail is append_images=frames[1:]. The first frame is already being saved by frames[0].save(), so the append list should start with the second frame. Passing the whole list can duplicate the first frame.

What the GIF options mean

save_all=True tells Pillow to save every frame instead of only the first image. append_images supplies the extra frames after the first one. duration controls how long each frame is displayed in milliseconds. A single integer applies the same delay to every frame, while a list can set different delays per frame.

loop=0 means the GIF loops forever. If you omit loop or set it to None, the GIF does not request endless looping. This is one of the most common differences between a GIF that behaves like a short animation and one that plays once.

Python Pool infographic showing Pillow images with common size, mode, order, and drawing
GIF frames: Pillow images with common size, mode, order, and drawing.

Create generated frames in Python

You do not need existing image files. You can also create frames with Pillow, draw on each frame, and save the result as an animation.

from PIL import Image, ImageDraw

frames = []

for i in range(12):
    frame = Image.new("RGB", (320, 180), "white")
    draw = ImageDraw.Draw(frame)
    x = 20 + i * 20
    draw.ellipse((x, 70, x + 40, 110), fill="royalblue")
    frames.append(frame)

frames[0].save(
    "ball.gif",
    save_all=True,
    append_images=frames[1:],
    duration=80,
    loop=0,
)

Image.new() creates a blank frame, and ImageDraw draws a different circle position for each frame. The same save pattern works because the final object is still a list of Pillow images.

Check frame count and timing

After saving the file, you can reopen it and inspect the GIF metadata. This is useful when debugging whether all frames were saved.

from PIL import Image

with Image.open("animation.gif") as im:
    print(getattr(im, "n_frames", 1))
    print(im.info.get("duration"))
    print(im.info.get("loop"))

Pillow can also move through GIF frames with seek() and tell(). When there are no more frames, Pillow raises EOFError, which is expected behavior at the end of the animation.

Python Pool infographic showing save_all, append_images, duration, and loop
GIF export: Save_all, append_images, duration, and loop.

Common mistakes when creating GIFs

If your GIF contains only one image, you probably forgot save_all=True or did not pass the remaining frames through append_images. If the first frame appears twice, check that you used frames[1:] instead of appending the full frame list.

Keep every frame the same size and image mode. Converting frames with .convert("RGB") is a practical way to make a batch of PNG or JPG files consistent before saving the animation.

Large GIFs grow quickly. Reduce image dimensions, frame count, or duration before sharing the file on the web. If you need to process paths dynamically, see getting a filename from a path in Python. If you need to compress related output files, the Python gzip guide covers another useful file-size tool.

Related Python image guides

For more image workflows, read the PythonPool guides on Matplotlib imread, ImageMagick with Python, and fixing Flask images that do not show. If you are building frame names dynamically, the guide to appending strings in Python is also relevant.

Official references

The examples above follow Pillow’s official documentation for GIF file options, Image.save(), and Image.new().

Conclusion

To create a GIF using Python and Pillow, save the first frame with save_all=True, pass the remaining frames with append_images, set duration for speed, and use loop=0 when the animation should repeat forever. This pattern works for both existing image files and frames generated directly in Python.

Create Compatible Frames

Build each Pillow Image with consistent dimensions and a deliberate mode. Drawing, resizing, and color conversion should happen before the frames are assembled.

Python Pool infographic showing RGB, RGBA, palette, transparency, and quality
GIF palette: RGB, RGBA, palette, transparency, and quality.

Save All Frames

Save the first frame with save_all=True and pass the rest through append_images according to the Pillow version’s documented API. A single saved image is not an animation.

Set Timing And Loop

Use duration in milliseconds or a per-frame sequence and define loop behavior. Test playback because viewers can interpret timing and finite loops differently.

Python Pool infographic reopening a GIF to inspect frames, timing, modes, and playback
GIF validation: Python Pool infographic reopening a GIF to inspect frames, timing, modes, and playback.

Handle Palette And Transparency

GIF has palette and format constraints. Convert modes deliberately, inspect transparent pixels, and accept that colors may differ from an RGB source after export.

Control File Size

Reduce frame dimensions, frame count, color variety, and redundant changes when appropriate. Optimize only after confirming that quality and timing remain acceptable.

Validate The Export

Reopen the saved GIF, inspect frame count and sizes, render representative frames, and test empty, one-frame, transparent, long, and Unicode-labeled workflows.

Use the official Pillow GIF documentation. Related Python Pool references include testing and frame lists.

For related media workflows, compare frame lists, export tests, and animation settings before saving a GIF.

Frequently Asked Questions

How do I create a GIF in Python?

Create compatible Pillow Image frames, then save the first frame with save_all=True and append_images for the remaining frames.

How do I control GIF speed?

Pass a duration in milliseconds or a per-frame duration sequence according to the Pillow API and verify playback in a real viewer.

Why does a GIF look different after saving?

GIF uses a limited palette and may convert image modes, transparency, or colors during export; inspect the saved frames and palette behavior.

How do I make a GIF loop?

Set loop according to the intended playback policy and test the exported file, including the difference between finite repetition and continuous looping.

Subscribe
Notify of
guest
0 Comments
Oldest
Newest Most Voted