mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-08-18 11:14:46 +03:00
Consider all frames when optimizing a GIF image
This commit is contained in:
parent
da8f2737a8
commit
69e8d95cc0
|
@ -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:
|
||||||
|
@ -556,7 +558,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.
|
||||||
|
|
||||||
|
@ -569,6 +571,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
|
||||||
|
@ -605,7 +610,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]
|
||||||
|
@ -619,7 +624,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
|
||||||
|
@ -681,7 +686,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
|
||||||
|
@ -705,7 +710,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)
|
||||||
|
|
|
@ -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()
|
||||||
|
|
Loading…
Reference in New Issue
Block a user