mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-07-03 11:23:05 +03:00
Merge pull request #2312 from wiredfool/pr_2298
List of individual frame durations for saving animated gifs. #2298
This commit is contained in:
commit
944a470a79
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
Loading…
Reference in New Issue
Block a user