diff --git a/Tests/test_file_apng.py b/Tests/test_file_apng.py index 15689476c91..1b253916cf6 100644 --- a/Tests/test_file_apng.py +++ b/Tests/test_file_apng.py @@ -686,6 +686,38 @@ def test_apng_save_disposal_previous(tmp_path: Path) -> None: assert im.getpixel((64, 32)) == (0, 255, 0, 255) +@pytest.mark.parametrize("mode", ("L", "RGB")) +def test_apng_save_disposal_background_mode(tmp_path: Path, mode: str) -> None: + test_file = tmp_path / "temp.png" + im = Image.new(mode, (32, 32), 128) + + # the black frame is different from the transparent background + im.save( + test_file, + save_all=True, + append_images=[Image.new(mode, (32, 32))], + disposal=PngImagePlugin.Disposal.OP_BACKGROUND, + duration=100, + ) + with Image.open(test_file) as reloaded: + assert isinstance(reloaded, PngImagePlugin.PngImageFile) + assert reloaded.n_frames == 2 + + +def test_apng_save_info_disposal(tmp_path: Path) -> None: + test_file = tmp_path / "temp.png" + red = Image.new("RGBA", (32, 32), (255, 0, 0, 255)) + red_green = red.copy() + red_green.paste((0, 255, 0, 255), (0, 0, 10, 10)) + + red.info["disposal"] = PngImagePlugin.Disposal.OP_PREVIOUS + red.save(test_file, save_all=True, append_images=[red_green]) + with Image.open(test_file) as im: + im.seek(1) + assert im.getpixel((0, 0)) == (0, 255, 0, 255) + assert im.getpixel((20, 20)) == (255, 0, 0, 255) + + def test_apng_save_blend(tmp_path: Path) -> None: test_file = tmp_path / "temp.png" size = (128, 64) diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index 56f64f927e4..da8eceaf08f 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -1225,21 +1225,25 @@ def _write_multiple_frames( encoderinfo["duration"] = duration[frame_count] elif duration is None and "duration" in im_frame.info: encoderinfo["duration"] = im_frame.info["duration"] - if isinstance(disposal, (list, tuple)): - encoderinfo["disposal"] = disposal[frame_count] - if isinstance(blend, (list, tuple)): - encoderinfo["blend"] = blend[frame_count] + encoderinfo["disposal"] = ( + disposal[frame_count] + if isinstance(disposal, (list, tuple)) + else disposal + ) + encoderinfo["blend"] = ( + blend[frame_count] if isinstance(blend, (list, tuple)) else blend + ) frame_count += 1 if im_frames: previous = im_frames[-1] - prev_disposal = previous.encoderinfo.get("disposal") - prev_blend = previous.encoderinfo.get("blend") + prev_disposal = previous.encoderinfo["disposal"] + prev_blend = previous.encoderinfo["blend"] if prev_disposal == Disposal.OP_PREVIOUS and len(im_frames) < 2: prev_disposal = Disposal.OP_BACKGROUND if prev_disposal == Disposal.OP_BACKGROUND: - base_im = previous.im.copy() + base_im = previous.im.convert("RGBA") dispose = Image.core.fill("RGBA", im.size, (0, 0, 0, 0)) bbox = previous.bbox if bbox: @@ -1257,8 +1261,8 @@ def _write_multiple_frames( bbox = delta.getbbox(alpha_only=False) if ( not bbox - and prev_disposal == encoderinfo.get("disposal") - and prev_blend == encoderinfo.get("blend") + and prev_disposal == encoderinfo["disposal"] + and prev_blend == encoderinfo["blend"] and "duration" in encoderinfo ): previous.encoderinfo["duration"] += encoderinfo["duration"] @@ -1303,8 +1307,8 @@ def _write_multiple_frames( if delay.numerator > 65535: msg = "cannot write duration" raise ValueError(msg) - frame_disposal = encoderinfo.get("disposal", disposal) - frame_blend = encoderinfo.get("blend", blend) + frame_disposal = encoderinfo["disposal"] + frame_blend = encoderinfo["blend"] # frame control chunk( fp,