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
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 im_frame in ImageSequence.Iterator(imSequence):
encoderinfo = im.encoderinfo.copy()
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(),
# e.g. getdata(im_frame, duration=1000)

View File

@ -281,6 +281,50 @@ class TestFileGif(PillowTestCase):
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):
number_of_loops = 2

View File

@ -72,9 +72,6 @@ The :py:meth:`~PIL.Image.Image.open` method sets the following
**background**
Default background color (a palette color index).
**duration**
Time between frames in an animation (in milliseconds).
**transparency**
Transparency color index. This key is omitted if the image is not
transparent.
@ -82,9 +79,9 @@ The :py:meth:`~PIL.Image.Image.open` method sets the following
**version**
Version (either ``GIF87a`` or ``GIF89a``).
**duration**
May not be present. The time to display each frame of the GIF, in
milliseconds.
**duration**
May not be present. The time to display the current frame
of the GIF, in milliseconds.
**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.
Saving sequences
~~~~~~~~~~~~~~~~
Saving
~~~~~~
When calling :py:meth:`~PIL.Image.Image.save`, if a multiframe image is used,
by default only the first frame will be saved. To save all frames, the
``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::
When calling :py:meth:`~PIL.Image.Image.save`, the following options
are available::
im.save(out, save_all=True, append_images=[im1, im2, ...])
If present, the ``loop`` parameter can be used to set the number of times
the GIF should loop, and the ``duration`` parameter can set the number of
milliseconds between each frame.
**save_all**
If present and true, all frames of the image will be saved. If
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
~~~~~~~~~~~~~~~~~~~~