mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-08-17 18:54:46 +03:00
can pass list of integer to set different duration for each frame when saving GIF
This commit is contained in:
parent
954fc52877
commit
8f44fa4aec
|
@ -352,10 +352,21 @@ 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 duration is not None:
|
||||
if not isinstance(duration, list):
|
||||
encoderinfo["duration"] = duration
|
||||
else:
|
||||
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)
|
||||
|
@ -587,7 +598,7 @@ def _get_header_palette(palette_bytes):
|
|||
return palette_bytes
|
||||
|
||||
# Force optimization so that we can test performance against
|
||||
# cases where it took lots of memory and time previously.
|
||||
# cases where it took lots of memory and time previously.
|
||||
_FORCE_OPTIMIZE = False
|
||||
|
||||
def _get_palette_bytes(im, palette, info):
|
||||
|
@ -613,12 +624,12 @@ def _get_palette_bytes(im, palette, info):
|
|||
# lengths are restricted to 3*(2**N) bytes. Max saving would
|
||||
# be 768 -> 6 bytes if we went all the way down to 2 colors.
|
||||
# * If we're over 128 colors, we can't save any space.
|
||||
# * If there aren't any holes, it's not worth collapsing.
|
||||
# * If there aren't any holes, it's not worth collapsing.
|
||||
# * If we have a 'large' image, the palette is in the noise.
|
||||
|
||||
# create the new palette if not every color is used
|
||||
if _FORCE_OPTIMIZE or im.mode == 'L' or \
|
||||
(len(used_palette_colors) <= 128 and
|
||||
(len(used_palette_colors) <= 128 and
|
||||
max(used_palette_colors) > len(used_palette_colors) and
|
||||
im.width * im.height < 512 * 512):
|
||||
palette_bytes = b""
|
||||
|
@ -655,15 +666,15 @@ def _get_palette_bytes(im, palette, info):
|
|||
m_im.palette = ImagePalette.ImagePalette("RGB",
|
||||
palette=mapping_palette*3,
|
||||
size=768)
|
||||
#possibly set palette dirty, then
|
||||
#possibly set palette dirty, then
|
||||
#m_im.putpalette(mapping_palette, 'L') # converts to 'P'
|
||||
# or just force it.
|
||||
# UNDONE -- this is part of the general issue with palettes
|
||||
m_im.im.putpalette(*m_im.palette.getdata())
|
||||
|
||||
|
||||
m_im = m_im.convert('L')
|
||||
|
||||
# Internally, we require 768 bytes for a palette.
|
||||
|
||||
# Internally, we require 768 bytes for a palette.
|
||||
new_palette_bytes = (palette_bytes +
|
||||
(768 - len(palette_bytes)) * b'\x00')
|
||||
m_im.putpalette(new_palette_bytes)
|
||||
|
@ -672,9 +683,9 @@ def _get_palette_bytes(im, palette, info):
|
|||
size=len(palette_bytes))
|
||||
|
||||
# oh gawd, this is modifying the image in place so I can pass by ref.
|
||||
# REFACTOR SOONEST
|
||||
# REFACTOR SOONEST
|
||||
im.frombytes(m_im.tobytes())
|
||||
|
||||
|
||||
if not palette_bytes:
|
||||
palette_bytes = source_palette
|
||||
|
||||
|
|
|
@ -56,7 +56,7 @@ class TestFileGif(PillowTestCase):
|
|||
# 256 color Palette image, posterize to > 128 and < 128 levels
|
||||
# Size bigger and smaller than 512x512
|
||||
# Check the palette for number of colors allocated.
|
||||
# Check for correctness after conversion back to RGB
|
||||
# Check for correctness after conversion back to RGB
|
||||
def check(colors, size, expected_palette_length):
|
||||
# make an image with empty colors in the start of the palette range
|
||||
im = Image.frombytes('P', (colors,colors),
|
||||
|
@ -70,7 +70,7 @@ class TestFileGif(PillowTestCase):
|
|||
# check palette length
|
||||
palette_length = max(i+1 for i,v in enumerate(reloaded.histogram()) if v)
|
||||
self.assertEqual(expected_palette_length, palette_length)
|
||||
|
||||
|
||||
self.assert_image_equal(im.convert('RGB'), reloaded.convert('RGB'))
|
||||
|
||||
|
||||
|
@ -281,6 +281,25 @@ 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'),
|
||||
]
|
||||
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
|
||||
|
||||
def test_number_of_loops(self):
|
||||
number_of_loops = 2
|
||||
|
||||
|
|
|
@ -111,7 +111,9 @@ additional frames when saving, the ``append_images`` parameter works with
|
|||
|
||||
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.
|
||||
milliseconds between each frame. The ``duration`` parameter can be either
|
||||
an integer or a list of integer. Passing a list to ``duration`` parameter
|
||||
will set the ``duration`` of each frame respectively.
|
||||
|
||||
Reading local images
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
|
Loading…
Reference in New Issue
Block a user