diff --git a/PIL/GifImagePlugin.py b/PIL/GifImagePlugin.py index f227211a274..71d8ce59a5d 100644 --- a/PIL/GifImagePlugin.py +++ b/PIL/GifImagePlugin.py @@ -392,6 +392,7 @@ def _write_single_frame(im, fp, palette): def _write_multiple_frames(im, fp, palette): duration = im.encoderinfo.get("duration", None) + disposal = im.encoderinfo.get('disposal', None) im_frames = [] frame_count = 0 @@ -404,6 +405,8 @@ def _write_multiple_frames(im, fp, palette): encoderinfo = im.encoderinfo.copy() if isinstance(duration, (list, tuple)): encoderinfo['duration'] = duration[frame_count] + if isinstance(disposal, (list, tuple)): + encoderinfo["disposal"] = disposal[frame_count] frame_count += 1 if im_frames: @@ -503,15 +506,19 @@ def _write_local_header(fp, im, offset, flags): duration = int(im.encoderinfo["duration"] / 10) else: duration = 0 - if transparent_color_exists or duration != 0: - transparency_flag = 1 if transparent_color_exists else 0 + + disposal = int(im.encoderinfo.get('disposal', 0)) + + if transparent_color_exists or duration != 0 or disposal: + packed_flag = 1 if transparent_color_exists else 0 + packed_flag |= disposal << 2 if not transparent_color_exists: transparency = 0 fp.write(b"!" + o8(249) + # extension intro o8(4) + # length - o8(transparency_flag) + # packed fields + o8(packed_flag) + # packed fields o16(duration) + # duration o8(transparency) + # transparency index o8(0)) diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 2ca234173f9..22a2c007261 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -259,6 +259,40 @@ def test_dispose_previous(self): except EOFError: pass + def test_save_dispose(self): + 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'), + ] + for method in range(0,4): + im_list[0].save( + out, + save_all=True, + append_images=im_list[1:], + disposal=method + ) + img = Image.open(out) + for _ in range(2): + img.seek(img.tell() + 1) + self.assertEqual(img.disposal_method, method) + + + # check per frame disposal + im_list[0].save( + out, + save_all=True, + append_images=im_list[1:], + disposal=tuple(range(len(im_list))) + ) + + img = Image.open(out) + + for i in range(2): + img.seek(img.tell() + 1) + self.assertEqual(img.disposal_method, i+1) + def test_iss634(self): img = Image.open("Tests/images/iss634.gif") # seek to the second frame diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index 130d1590860..3fb98e53ed7 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -132,6 +132,17 @@ are available:: the palette can be passed in as an :py:class:`PIL.ImagePalette.ImagePalette` object. +**disposal** + Indicates the way in which the graphic is to be treated after being displayed. + + * 0 - No disposal specified. + * 1 - Do not dispose. + * 2 - Restore to background color. + * 3 - Restore to previous content. + + Pass a single integer for a constant disposal, or a list or tuple + to set the disposal for each frame separately. + Reading local images ~~~~~~~~~~~~~~~~~~~~