Merge pull request #2312 from wiredfool/pr_2298

List of individual frame durations for saving animated gifs. #2298
This commit is contained in:
wiredfool 2016-12-27 12:00:40 +00:00 committed by GitHub
commit 944a470a79
3 changed files with 82 additions and 16 deletions

View File

@ -352,10 +352,18 @@ def _save(im, fp, filename, save_all=False):
first_frame = None first_frame = None
append_images = im.encoderinfo.get("append_images", []) append_images = im.encoderinfo.get("append_images", [])
if "duration" in im.encoderinfo:
duration = im.encoderinfo["duration"]
else:
duration = None
frame_count = 0
for imSequence in [im]+append_images: for imSequence in [im]+append_images:
for im_frame in ImageSequence.Iterator(imSequence): for im_frame in ImageSequence.Iterator(imSequence):
encoderinfo = im.encoderinfo.copy() encoderinfo = im.encoderinfo.copy()
im_frame = _convert_mode(im_frame) im_frame = _convert_mode(im_frame)
if isinstance(duration, (list, tuple)):
encoderinfo["duration"] = duration[frame_count]
frame_count += 1
# To specify duration, add the time in milliseconds to getdata(), # To specify duration, add the time in milliseconds to getdata(),
# e.g. getdata(im_frame, duration=1000) # e.g. getdata(im_frame, duration=1000)

View File

@ -281,6 +281,50 @@ class TestFileGif(PillowTestCase):
self.assertEqual(reread.info['duration'], duration) self.assertEqual(reread.info['duration'], duration)
def test_multiple_duration(self):
duration_list = [1000, 2000, 3000]
out = self.tempfile('temp.gif')
im_list = [
Image.new('L', (100, 100), '#000'),
Image.new('L', (100, 100), '#111'),
Image.new('L', (100, 100), '#222'),
]
#duration as list
im_list[0].save(
out,
save_all=True,
append_images=im_list[1:],
duration=duration_list
)
reread = Image.open(out)
for duration in duration_list:
self.assertEqual(reread.info['duration'], duration)
try:
reread.seek(reread.tell() + 1)
except EOFError:
pass
# duration as tuple
im_list[0].save(
out,
save_all=True,
append_images=im_list[1:],
duration=tuple(duration_list)
)
reread = Image.open(out)
for duration in duration_list:
self.assertEqual(reread.info['duration'], duration)
try:
reread.seek(reread.tell() + 1)
except EOFError:
pass
def test_number_of_loops(self): def test_number_of_loops(self):
number_of_loops = 2 number_of_loops = 2

View File

@ -72,9 +72,6 @@ The :py:meth:`~PIL.Image.Image.open` method sets the following
**background** **background**
Default background color (a palette color index). Default background color (a palette color index).
**duration**
Time between frames in an animation (in milliseconds).
**transparency** **transparency**
Transparency color index. This key is omitted if the image is not Transparency color index. This key is omitted if the image is not
transparent. transparent.
@ -83,8 +80,8 @@ The :py:meth:`~PIL.Image.Image.open` method sets the following
Version (either ``GIF87a`` or ``GIF89a``). Version (either ``GIF87a`` or ``GIF89a``).
**duration** **duration**
May not be present. The time to display each frame of the GIF, in May not be present. The time to display the current frame
milliseconds. of the GIF, in milliseconds.
**loop** **loop**
May not be present. The number of times the GIF should loop. May not be present. The number of times the GIF should loop.
@ -98,20 +95,37 @@ the file by seeking to the first frame. Random access is not supported.
``im.seek()`` raises an ``EOFError`` if you try to seek after the last frame. ``im.seek()`` raises an ``EOFError`` if you try to seek after the last frame.
Saving sequences Saving
~~~~~~~~~~~~~~~~ ~~~~~~
When calling :py:meth:`~PIL.Image.Image.save`, if a multiframe image is used, When calling :py:meth:`~PIL.Image.Image.save`, the following options
by default only the first frame will be saved. To save all frames, the are available::
``save_all`` parameter must be present and set to ``True``. To append
additional frames when saving, the ``append_images`` parameter works with
``save_all`` to append a list of images containing the extra frames::
im.save(out, save_all=True, append_images=[im1, im2, ...]) im.save(out, save_all=True, append_images=[im1, im2, ...])
If present, the ``loop`` parameter can be used to set the number of times **save_all**
the GIF should loop, and the ``duration`` parameter can set the number of If present and true, all frames of the image will be saved. If
milliseconds between each frame. not, then only the first frame of a multiframe image will be saved.
**append_images**
A list of images to append as additional frames. Each of the
images in the list can be single or multiframe images.
**duration**
The display duration of each frame of the multiframe gif, in
milliseconds. Pass a single integer for a constant duration, or a
list or tuple to set the duration for each frame separately.
**loop**
Integer number of times the GIF should loop.
**optimize**
If present and true, attempt to compress the palette by
eliminating unused colors. This is only useful if the palette can
be compressed to the next smaller power of 2 elements.
**palette**
Use the specified palette for the saved image.
Reading local images Reading local images
~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~