Python informer

Improve your Python coding skills

Creating animated GIFs with Pillow

GIF format images were a common feature in the early days of the web. They aren’t used so much these days because PNG images are better than GIFs in almost every way.

There is still one thing that GIFs can do that most other image formats can’t - they can create animations. They aren’t a replacement for video, but they can be useful for small, short, low frame rate animations. They are easy to create and will run in any browser - just use an animated GIF file in the same way you would use an ordinary image, and the browser will run it automatically.

Modern video formats such as MP4 etc use sophisticated compression algorithms to reduce the size of the video file. Animated GIFs just use a simple image compression algorithm. If you tried to store a long, full screen video in GIF format, the file would be huge.

What is a GIF animation

A GIF file normally stores a single image, but the format allows multiple images to be stored in one file. The format also has parameters that can be used to sequence the images - that is, to display each image for a short time then replace it with the next one.

This technique was used a lot in the early days of the web to make animated banner ads - usually gaudy, flashing monstrosities that distracted you from the page you were trying to read. Most sites don’t do stuff like that any more.

But if you include 20 or so time lapse images, and set a duration of 0.1 seconds or less, you can get a reasonable 2 second video clip or animated diagram.

Creating an animated GIF with pillow

We will assume you know how the basics of reading and writing image files in pillow. Assuming im is a previously opened pillow Image, here is how you would save a single image as a GIF:

im.save('test.gif')

If we want to create an animated GIF, we need a group of images that make up the individual frames. Assuming images[] is a list of opened pillow Image objects, here is how you would save it:

images[0].save('anitest.gif',
               save_all=True,
               append_images=images[1:],
               duration=100,
               loop=0)

The way pillow works is that we save the first image in the group, and add a list of the extra images as a parameter in the save call. There are several extra parameters:

  • save_all tells pillow that we want to save multiple images in the same output file.
  • append_images is a list of the extra images to save. Since the first image (images[0]) is used to make the call, the extra images consist of images[1] and beyond. We use a slice to select those images from the full list.
  • duration controls how long each frame will be displayed for, in milliseconds. It is the inverse of the frame rate. Setting it to 100 milliseconds gives 10 frames per second. You can decrease the duration to give a smoother animation.
  • loop controls how many times the animation repeats. The default, 1, means that the animation will play once and then stop (displaying the last frame). 2 means it will repeat twice before stopping, and so on. A value of 0 means that the animation will repeat forever.

Example - a moving circle

Here is a really simple example, a moving circle. We will create this in 2 steps:

  • Create a set of single image GIF files, each containing 1 frame of the animation
  • Combine all the individual frames into a single image

To start with we will create a list of file names for our temporary single iamge gifs. We will allow 20 frames (2 sec at 10 frames per second), and call the files img00.gif, img01.gif etc:

names = ['img{:02d}.gif'.format(i) for i in range(20)]

Now we will create our frames. We start with a blank green background image (that we will reuse by creating a copy each time). We will draw a red circle onthe green background, at position (pos, pos). The value of pos will be updated every time through the loop, so the circle moves on each frame:

# Create the individual frames as png images
im = Image.new("RGB", (200, 200), 'green')

pos = 0
for n in names:
    frame = im.copy()
    draw = ImageDraw.Draw(frame)
    draw.ellipse((pos, pos, 50+pos, 50+pos),
                 'red')
    frame.save(n)
    pos += 10

Here is how the code works. After creating the background image, we loop over all the filenames in the list (we previously created 20 filenames):

  • take a copy of the background image and store it in the variable frame
  • create a Draw item, attached to the frame images, using ImageDraw.Draw
  • draw an ellipse with the Draw item - it will appear on the frame image
  • save the image as a file called imgxx.gif
  • increment pos to move the image for the next time round the loop

You should be able to look at the individual images and see the red circle in a different place each time.

Finally we create the animated GIF:

# Open all the frames
images = []

for n in names:
    frame = Image.open(n)
    images.append(frame)

# Save the frames as an animated GIF
images[0].save('anicircle.gif',
               save_all=True,
               append_images=images[1:],
               duration=100,
               loop=0)

We create an empty list of images. Then we loop over all the file names for a second time, this time opening the newly created GIF file. This creates and Image object that we append to the image list.

Finally we save the animated GIF file as described earlier. Here it is:

lioness

Things to try

Pillow doesn’t have a lot of great drawing tools, you might be better with something like Pycairo to create your initial GIFs, then use pillow to create the animated GIF.