saving animated gif drops frames #2314

Open
gboeing opened this Issue Dec 27, 2016 · 5 comments

Projects

None yet

4 participants

@gboeing
gboeing commented Dec 27, 2016

What did you do?

Saved a set of 100 png files as an animated gif then opened the gif

What did you expect to happen?

I expected the gif to have 100 frames, same as the number of png files

What actually happened?

The gif has only 70 frames

What versions of Pillow and Python are you using?

pillow 3.4.2, python 3.5.2

Demo

There are 100 png files in the folder save_folder, named 000.png through 099.png. The first 16 images are identical to one another, and the final 16 images are identical to one another. The other 68 images are all different from each other. In other words, the animation would show the first image for 16 frames, then run through the middle 68 frames before coming to the concluding image which it shows for 16 frames. But when I save the gif, it is missing 15 of the first 16 images and 15 of the final 16 images. Thus there is no "pause" at the beginning or end of the animation.

images = [Image.open(image) for image in glob.glob('{}/*.png'.format(save_folder))]
len(images)

returns: 100

gif = images[0]
gif.save(fp=gif_filepath, format='gif', save_all=True, append_images=images[1:])
Image.open(gif_filepath).n_frames

returns: 70

@radarhere
Contributor

As a partial answer, you could workaround this problem using #2298

@gboeing
gboeing commented Dec 28, 2016 edited

@radarhere thanks yes that serves as a workaround. But any idea why it drops (seemingly de-duplicates?) frames?

@radarhere
Contributor

For my sake, here is a complete script to reproduce the problem. It results in 100 / 65, rather than 100 / 70, but the principle is the same.

from PIL import Image
import os, glob

frames = [0]*16 + list(range(1,68)) + [68]*16

save_folder = os.path.expanduser('~/Desktop/images')
for i, r in enumerate(frames):
    im = Image.new('RGB',(100,100),(r*4,0,0))
    im.save(os.path.join(save_folder, str(i).zfill(3)+'.png'))

images = [Image.open(image) for image in glob.glob('{}/*.png'.format(save_folder))]
print(len(images))

gif_filepath = os.path.join(save_folder, 'output.gif')
gif = images[0]
gif.save(fp=gif_filepath, format='gif', save_all=True, append_images=images[1:])
print(Image.open(gif_filepath).n_frames)

When running this script, it hits 'FIXME: what should we do in this case?' - https://github.com/python-pillow/Pillow/blob/master/PIL/GifImagePlugin.py#L391 - the same number of times as the number of missing frames.

@wiredfool
Member

I can think of three options for the fixme -- either bump the duration on the previous frame, write a null frame of 0x0 pixels, or write a frame of 1x1.

Bumping the duration would be the smallest file, but it would be the hardest to pull off since by the time we need to bump the duration, we've already written it to the file.

I'm not sure if a 0x0 box frame would work for other decoders. I'm sure it's been done before though -- if only in a fuzzer. A 1x1 is probably the most compatible (at a wild guess).

@radarhere
Contributor

I've created a PR for the @wiredfool suggestion of extending the duration of the previous frame.

@aclark4life aclark4life added the Bug label Jan 8, 2017
@aclark4life aclark4life added this to the 4.1.0 milestone Jan 8, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment