diff --git a/PIL/GifImagePlugin.py b/PIL/GifImagePlugin.py index 7fe662fc6..0b6f41648 100644 --- a/PIL/GifImagePlugin.py +++ b/PIL/GifImagePlugin.py @@ -298,7 +298,7 @@ except ImportError: RAWMODE = { "1": "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): - im.encoderinfo.update(im.info) if _imaging_gif: # call external driver @@ -344,62 +343,68 @@ def _save(im, fp, filename, save_all=False): im.encoderinfo["optimize"] = im.encoderinfo.get("optimize", True) if save_all: - previous = None - - first_frame = None - append_images = im.encoderinfo.get("append_images", []) + # To specify duration, add the time in milliseconds to getdata(), + # e.g. getdata(im_frame, duration=1000) if "duration" in im.encoderinfo: duration = im.encoderinfo["duration"] else: duration = None + im_frames = [] + append_images = im.encoderinfo.get("append_images", []) 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) + + encoderinfo = im.encoderinfo.copy() if isinstance(duration, (list, tuple)): - encoderinfo["duration"] = duration[frame_count] + 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) - 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 - + if im_frames: # 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() - - if bbox: - # compress difference - encoderinfo['include_color_table'] = True - for s in getdata(im_frame.crop(bbox), - bbox[:2], **encoderinfo): - fp.write(s) - else: - # FIXME: what should we do in this case? - pass - previous = im_frame - if first_frame: + if not bbox: + # This frame is identical to the previous frame + if duration: + previous['encoderinfo']['duration'] += encoderinfo['duration'] + continue + else: + bbox = None + im_frames.append({ + 'im':im_frame, + 'bbox':bbox, + 'encoderinfo':encoderinfo + }) + if len(im_frames) < 2: 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: - header = getheader(im_out, palette, im.encoderinfo)[0] - for s in header: + for s in getheader(im_out, palette, im.encoderinfo)[0]: fp.write(s) + # local image header flags = 0 - if get_interlace(im): flags = flags | 64 - - # local image header _get_local_header(fp, im, (0, 0), flags) im_out.encoderconfig = (8, get_interlace(im)) diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index d987f6851..df17efe8d 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -287,7 +287,7 @@ class TestFileGif(PillowTestCase): im_list = [ Image.new('L', (100, 100), '#000'), Image.new('L', (100, 100), '#111'), - Image.new('L', (100, 100), '#222'), + Image.new('L', (100, 100), '#222') ] #duration as list @@ -322,7 +322,31 @@ class TestFileGif(PillowTestCase): except EOFError: 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): number_of_loops = 2 @@ -425,7 +449,7 @@ class TestFileGif(PillowTestCase): reloaded = Image.open(out) self.assertEqual(reloaded.info['transparency'], 253) - + if __name__ == '__main__': unittest.main()