This commit is contained in:
Andrew Murray 2017-02-27 08:53:34 +00:00 committed by GitHub
commit 2486f48d40
2 changed files with 69 additions and 40 deletions

View File

@ -298,7 +298,7 @@ except ImportError:
RAWMODE = { RAWMODE = {
"1": "L", "1": "L",
"L": "L", "L": "L",
"P": "P", "P": "P"
} }
@ -321,7 +321,6 @@ def _save_all(im, fp, filename):
def _save(im, fp, filename, save_all=False): def _save(im, fp, filename, save_all=False):
im.encoderinfo.update(im.info) im.encoderinfo.update(im.info)
if _imaging_gif: if _imaging_gif:
# call external driver # call external driver
@ -344,62 +343,68 @@ def _save(im, fp, filename, save_all=False):
im.encoderinfo["optimize"] = im.encoderinfo.get("optimize", True) im.encoderinfo["optimize"] = im.encoderinfo.get("optimize", True)
if save_all: if save_all:
previous = None # To specify duration, add the time in milliseconds to getdata(),
# e.g. getdata(im_frame, duration=1000)
first_frame = None
append_images = im.encoderinfo.get("append_images", [])
if "duration" in im.encoderinfo: if "duration" in im.encoderinfo:
duration = im.encoderinfo["duration"] duration = im.encoderinfo["duration"]
else: else:
duration = None duration = None
im_frames = []
append_images = im.encoderinfo.get("append_images", [])
frame_count = 0 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()
im_frame = _convert_mode(im_frame) im_frame = _convert_mode(im_frame)
encoderinfo = im.encoderinfo.copy()
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
# To specify duration, add the time in milliseconds to getdata(), if im_frames:
# e.g. getdata(im_frame, duration=1000)
if not previous:
# global header
first_frame = getheader(im_frame, palette, encoderinfo)[0]
first_frame += getdata(im_frame, (0, 0), **encoderinfo)
else:
if first_frame:
for s in first_frame:
fp.write(s)
first_frame = None
# delta frame # delta frame
delta = ImageChops.subtract_modulo(im_frame, previous.copy()) previous = im_frames[-1]
delta = ImageChops.subtract_modulo(im_frame,
previous['im'])
bbox = delta.getbbox() bbox = delta.getbbox()
if not bbox:
if bbox: # This frame is identical to the previous frame
# compress difference if duration:
encoderinfo['include_color_table'] = True previous['encoderinfo']['duration'] += encoderinfo['duration']
for s in getdata(im_frame.crop(bbox), continue
bbox[:2], **encoderinfo): else:
fp.write(s) bbox = None
else: im_frames.append({
# FIXME: what should we do in this case? 'im':im_frame,
pass 'bbox':bbox,
previous = im_frame 'encoderinfo':encoderinfo
if first_frame: })
if len(im_frames) < 2:
save_all = False save_all = False
else:
for frame_data in im_frames:
im_frame = frame_data['im']
if not frame_data['bbox']:
# global header
for s in getheader(im_frame, palette, frame_data['encoderinfo'])[0]:
fp.write(s)
offset = (0, 0)
else:
# compress difference
frame_data['encoderinfo']['include_color_table'] = True
im_frame = im_frame.crop(frame_data['bbox'])
offset = frame_data['bbox'][:2]
for s in getdata(im_frame, offset, **frame_data['encoderinfo']):
fp.write(s)
if not save_all: if not save_all:
header = getheader(im_out, palette, im.encoderinfo)[0] for s in getheader(im_out, palette, im.encoderinfo)[0]:
for s in header:
fp.write(s) fp.write(s)
# local image header
flags = 0 flags = 0
if get_interlace(im): if get_interlace(im):
flags = flags | 64 flags = flags | 64
# local image header
_get_local_header(fp, im, (0, 0), flags) _get_local_header(fp, im, (0, 0), flags)
im_out.encoderconfig = (8, get_interlace(im)) im_out.encoderconfig = (8, get_interlace(im))

View File

@ -287,7 +287,7 @@ class TestFileGif(PillowTestCase):
im_list = [ im_list = [
Image.new('L', (100, 100), '#000'), Image.new('L', (100, 100), '#000'),
Image.new('L', (100, 100), '#111'), Image.new('L', (100, 100), '#111'),
Image.new('L', (100, 100), '#222'), Image.new('L', (100, 100), '#222')
] ]
#duration as list #duration as list
@ -322,7 +322,31 @@ class TestFileGif(PillowTestCase):
except EOFError: except EOFError:
pass pass
def test_identical_frames(self):
duration_list = [1000, 1500, 2000, 4000]
out = self.tempfile('temp.gif')
im_list = [
Image.new('L', (100, 100), '#000'),
Image.new('L', (100, 100), '#000'),
Image.new('L', (100, 100), '#000'),
Image.new('L', (100, 100), '#111')
]
#duration as list
im_list[0].save(
out,
save_all=True,
append_images=im_list[1:],
duration=duration_list
)
reread = Image.open(out)
# Assert that the first three frames were combined
self.assertEqual(reread.n_frames, 2)
# Assert that the new duration is the total of the identical frames
self.assertEqual(reread.info['duration'], 4500)
def test_number_of_loops(self): def test_number_of_loops(self):
number_of_loops = 2 number_of_loops = 2
@ -425,7 +449,7 @@ class TestFileGif(PillowTestCase):
reloaded = Image.open(out) reloaded = Image.open(out)
self.assertEqual(reloaded.info['transparency'], 253) self.assertEqual(reloaded.info['transparency'], 253)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()