This commit is contained in:
Andrew Murray 2017-02-28 06:29:11 +00:00 committed by GitHub
commit 6be2fa3c6a
2 changed files with 56 additions and 40 deletions

View File

@ -353,10 +353,12 @@ def _save(im, fp, filename, save_all=False):
else: else:
duration = None duration = None
frame_count = 0 frame_count = 0
im_frames = []
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):
im_frames.append(_convert_mode(im_frame))
for im_frame in im_frames:
encoderinfo = im.encoderinfo.copy() encoderinfo = im.encoderinfo.copy()
im_frame = _convert_mode(im_frame)
if isinstance(duration, (list, tuple)): if isinstance(duration, (list, tuple)):
encoderinfo["duration"] = duration[frame_count] encoderinfo["duration"] = duration[frame_count]
frame_count += 1 frame_count += 1
@ -365,7 +367,7 @@ def _save(im, fp, filename, save_all=False):
# e.g. getdata(im_frame, duration=1000) # e.g. getdata(im_frame, duration=1000)
if not previous: if not previous:
# global header # global header
first_frame = getheader(im_frame, palette, encoderinfo)[0] first_frame = getheader(im_frame, palette, encoderinfo, im_frames[1:])[0]
first_frame += getdata(im_frame, (0, 0), **encoderinfo) first_frame += getdata(im_frame, (0, 0), **encoderinfo)
else: else:
if first_frame: if first_frame:
@ -549,7 +551,7 @@ def _save_netpbm(im, fp, filename):
# cases where it took lots of memory and time previously. # cases where it took lots of memory and time previously.
_FORCE_OPTIMIZE = False _FORCE_OPTIMIZE = False
def _get_optimize(im, info): def _get_optimize(im, info, more_frames=[]):
if im.mode in ("P", "L") and info and info.get("optimize", 0): if im.mode in ("P", "L") and info and info.get("optimize", 0):
# Potentially expensive operation. # Potentially expensive operation.
@ -562,6 +564,9 @@ def _get_optimize(im, info):
# create the new palette if not every color is used # create the new palette if not every color is used
used_palette_colors = _get_used_palette_colors(im) used_palette_colors = _get_used_palette_colors(im)
for im_frame in more_frames:
used_palette_colors += _get_used_palette_colors(im_frame)
used_palette_colors.sort()
if _FORCE_OPTIMIZE or im.mode == 'L' or \ 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 max(used_palette_colors) > len(used_palette_colors) and
@ -598,7 +603,7 @@ def _get_header_palette(palette_bytes):
palette_bytes += o8(0) * 3 * actual_target_size_diff palette_bytes += o8(0) * 3 * actual_target_size_diff
return palette_bytes return palette_bytes
def _get_palette_bytes(im, palette, info): def _get_palette_bytes(im, palette, info, more_frames=[]):
if im.mode == "P": if im.mode == "P":
if palette and isinstance(palette, bytes): if palette and isinstance(palette, bytes):
source_palette = palette[:768] source_palette = palette[:768]
@ -612,7 +617,7 @@ def _get_palette_bytes(im, palette, info):
palette_bytes = None palette_bytes = None
used_palette_colors = _get_optimize(im, info) used_palette_colors = _get_optimize(im, info, more_frames)
if used_palette_colors is not None: if used_palette_colors is not None:
palette_bytes = b"" palette_bytes = b""
new_positions = [0]*256 new_positions = [0]*256
@ -674,7 +679,7 @@ def _get_palette_bytes(im, palette, info):
# returning palette, _not_ padded to 768 bytes like our internal ones. # returning palette, _not_ padded to 768 bytes like our internal ones.
return palette_bytes, used_palette_colors return palette_bytes, used_palette_colors
def getheader(im, palette=None, info=None): def getheader(im, palette=None, info=None, more_frames=[]):
"""Return a list of strings representing a GIF header""" """Return a list of strings representing a GIF header"""
# Header Block # Header Block
@ -698,7 +703,7 @@ def getheader(im, palette=None, info=None):
o16(im.size[1]) # canvas height o16(im.size[1]) # canvas height
] ]
palette_bytes, used_palette_colors = _get_palette_bytes(im, palette, info) palette_bytes, used_palette_colors = _get_palette_bytes(im, palette, info, more_frames)
# Logical Screen Descriptor # Logical Screen Descriptor
color_table_size = _get_color_table_size(palette_bytes) color_table_size = _get_color_table_size(palette_bytes)

View File

@ -406,10 +406,10 @@ class TestFileGif(PillowTestCase):
def test_transparent_optimize(self): def test_transparent_optimize(self):
# from issue #2195, if the transparent color is incorrectly # from issue #2195, if the transparent color is incorrectly
# optimized out, gif loses transparency Need a palette that # optimized out, gif loses transparency
# isn't using the 0 color, and one that's > 128 items where # Need a palette that isn't using the 0 color, and one
# the transparent color is actually the top palette entry to # that's > 128 items where the transparent color is actually
# trigger the bug. # the top palette entry to trigger the bug.
from PIL import ImagePalette from PIL import ImagePalette
@ -426,6 +426,17 @@ class TestFileGif(PillowTestCase):
self.assertEqual(reloaded.info['transparency'], 253) self.assertEqual(reloaded.info['transparency'], 253)
def test_multiple_optimize(self):
# When optimizing, all frames need to be considered
out = self.tempfile('temp.gif')
im = Image.new('RGB', (100,100), '#fff')
ims = [Image.new("RGB", (100,100), '#000')]
im.save(out, save_all=True, append_images=ims)
reread = Image.open(out)
self.assertEqual(reread.n_frames, 2)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()