diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 0c4a13a5b..45409cbc6 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -319,14 +319,14 @@ class TestFileGif(PillowTestCase): self.assertEqual(img.disposal_method, i + 1) def test_dispose2_palette(self): - out = self.tempfile('temp.gif') + out = self.tempfile("temp.gif") # 4 backgrounds: White, Grey, Black, Red circles = [(255, 255, 255), (153, 153, 153), (0, 0, 0), (255, 0, 0)] im_list = [] for circle in circles: - img = Image.new('RGB', (100, 100), (255, 0, 0)) + img = Image.new("RGB", (100, 100), (255, 0, 0)) # Red circle in center of each frame d = ImageDraw.Draw(img) @@ -334,18 +334,13 @@ class TestFileGif(PillowTestCase): im_list.append(img) - im_list[0].save( - out, - save_all=True, - append_images=im_list[1:], - disposal=2 - ) + im_list[0].save(out, save_all=True, append_images=im_list[1:], disposal=2) img = Image.open(out) for i, circle in enumerate(circles): img.seek(i) - rgb_img = img.convert('RGB') + rgb_img = img.convert("RGB") # Check top left pixel matches background self.assertEqual(rgb_img.getpixel((0, 0)), (255, 0, 0)) @@ -354,20 +349,20 @@ class TestFileGif(PillowTestCase): self.assertEqual(rgb_img.getpixel((50, 50)), circle) def test_dispose2_diff(self): - out = self.tempfile('temp.gif') + out = self.tempfile("temp.gif") # 4 frames: red/blue, red/red, blue/blue, red/blue circles = [ ((255, 0, 0, 255), (0, 0, 255, 255)), ((255, 0, 0, 255), (255, 0, 0, 255)), ((0, 0, 255, 255), (0, 0, 255, 255)), - ((255, 0, 0, 255), (0, 0, 255, 255)) + ((255, 0, 0, 255), (0, 0, 255, 255)), ] im_list = [] for i in range(len(circles)): # Transparent BG - img = Image.new('RGBA', (100, 100), (255, 255, 255, 0)) + img = Image.new("RGBA", (100, 100), (255, 255, 255, 0)) # Two circles per frame d = ImageDraw.Draw(img) @@ -377,18 +372,14 @@ class TestFileGif(PillowTestCase): im_list.append(img) im_list[0].save( - out, - save_all=True, - append_images=im_list[1:], - disposal=2, - transparency=0 + out, save_all=True, append_images=im_list[1:], disposal=2, transparency=0 ) img = Image.open(out) for i, colours in enumerate(circles): img.seek(i) - rgb_img = img.convert('RGBA') + rgb_img = img.convert("RGBA") # Check left circle is correct colour self.assertEqual(rgb_img.getpixel((20, 50)), colours[0]) @@ -399,6 +390,31 @@ class TestFileGif(PillowTestCase): # Check BG is correct colour self.assertEqual(rgb_img.getpixel((1, 1)), (255, 255, 255, 0)) + def test_dispose2_background(self): + out = self.tempfile("temp.gif") + + im_list = [] + + im = Image.new("P", (100, 100)) + d = ImageDraw.Draw(im) + d.rectangle([(50, 0), (100, 100)], fill="#f00") + d.rectangle([(0, 0), (50, 100)], fill="#0f0") + im_list.append(im) + + im = Image.new("P", (100, 100)) + d = ImageDraw.Draw(im) + d.rectangle([(0, 0), (100, 50)], fill="#f00") + d.rectangle([(0, 50), (100, 100)], fill="#0f0") + im_list.append(im) + + im_list[0].save( + out, save_all=True, append_images=im_list[1:], disposal=[0, 2], background=1 + ) + + im = Image.open(out) + im.seek(1) + self.assertEqual(im.getpixel((0, 0)), 0) + def test_iss634(self): img = Image.open("Tests/images/iss634.gif") # seek to the second frame diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index f55ca4471..bbf1c603f 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -426,9 +426,8 @@ def _write_multiple_frames(im, fp, palette): im_frames = [] frame_count = 0 - background = None - for imSequence in itertools.chain([im], - im.encoderinfo.get("append_images", [])): + background_im = None + for imSequence in itertools.chain([im], im.encoderinfo.get("append_images", [])): for im_frame in ImageSequence.Iterator(imSequence): # a copy is required here since seek can still mutate the image im_frame = _normalize_mode(im_frame.copy()) @@ -447,16 +446,23 @@ def _write_multiple_frames(im, fp, palette): if im_frames: # delta frame previous = im_frames[-1] - if disposal == 2: - base_image = background + if encoderinfo.get("disposal") == 2: + if background_im is None: + background = _get_background( + im, + im.encoderinfo.get("background", im.info.get("background")), + ) + background_im = Image.new("P", im_frame.size, background) + background_im.putpalette(im_frames[0]["im"].palette) + base_im = background_im else: - base_image = previous["im"] - - if _get_palette_bytes(im_frame) == _get_palette_bytes(base_frame): - delta = ImageChops.subtract_modulo(im_frame, base_image) + base_im = previous["im"] + if _get_palette_bytes(im_frame) == _get_palette_bytes(base_im): + delta = ImageChops.subtract_modulo(im_frame, base_im) else: delta = ImageChops.subtract_modulo( - im_frame.convert("RGB"), base_image.convert("RGB")) + im_frame.convert("RGB"), base_im.convert("RGB") + ) bbox = delta.getbbox() if not bbox: # This frame is identical to the previous frame @@ -465,7 +471,6 @@ def _write_multiple_frames(im, fp, palette): continue else: bbox = None - background = Image.new("P", im_frame.size, 0) im_frames.append({"im": im_frame, "bbox": bbox, "encoderinfo": encoderinfo}) if len(im_frames) > 1: @@ -726,6 +731,18 @@ def _get_palette_bytes(im): return im.palette.palette +def _get_background(im, infoBackground): + background = 0 + if infoBackground: + background = infoBackground + if isinstance(background, tuple): + # WebPImagePlugin stores an RGBA value in info["background"] + # So it must be converted to the same format as GifImagePlugin's + # info["background"] - a global color table index + background = im.palette.getcolor(background) + return background + + def _get_global_header(im, info): """Return a list of strings representing a GIF header""" @@ -745,14 +762,7 @@ def _get_global_header(im, info): if im.info.get("version") == b"89a": version = b"89a" - background = 0 - if "background" in info: - background = info["background"] - if isinstance(background, tuple): - # WebPImagePlugin stores an RGBA value in info["background"] - # So it must be converted to the same format as GifImagePlugin's - # info["background"] - a global color table index - background = im.palette.getcolor(background) + background = _get_background(im, info.get("background")) palette_bytes = _get_palette_bytes(im) color_table_size = _get_color_table_size(palette_bytes)