From f34360d1e3c8f29f1a0b0ccae62b5f7c95700440 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 27 May 2024 18:09:46 +1000 Subject: [PATCH] When saving multiple frames, convert to mode rather than raw mode --- Tests/test_file_png.py | 5 +++-- src/PIL/PngImagePlugin.py | 17 +++++++++-------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index 19462dcb5a4..55db5ef852f 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -655,11 +655,12 @@ def test_truncated_chunks(self, cid: bytes) -> None: png.call(cid, 0, 0) ImageFile.LOAD_TRUNCATED_IMAGES = False - def test_specify_bits(self, tmp_path: Path) -> None: + @pytest.mark.parametrize("save_all", (True, False)) + def test_specify_bits(self, save_all: bool, tmp_path: Path) -> None: im = hopper("P") out = str(tmp_path / "temp.png") - im.save(out, bits=4) + im.save(out, bits=4, save_all=save_all) with Image.open(out) as reloaded: assert len(reloaded.png.im_palette[1]) == 48 diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index 0d5751962c0..6b6c01d1b8e 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -1104,7 +1104,7 @@ def write(self, data: bytes) -> None: self.seq_num += 1 -def _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images): +def _write_multiple_frames(im, fp, chunk, mode, rawmode, default_image, append_images): duration = im.encoderinfo.get("duration", im.info.get("duration", 0)) loop = im.encoderinfo.get("loop", im.info.get("loop", 0)) disposal = im.encoderinfo.get("disposal", im.info.get("disposal", Disposal.OP_NONE)) @@ -1119,10 +1119,10 @@ def _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images) frame_count = 0 for im_seq in chain: for im_frame in ImageSequence.Iterator(im_seq): - if im_frame.mode == rawmode: + if im_frame.mode == mode: im_frame = im_frame.copy() else: - im_frame = im_frame.convert(rawmode) + im_frame = im_frame.convert(mode) encoderinfo = im.encoderinfo.copy() if isinstance(duration, (list, tuple)): encoderinfo["duration"] = duration[frame_count] @@ -1184,8 +1184,8 @@ def _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images) # default image IDAT (if it exists) if default_image: - if im.mode != rawmode: - im = im.convert(rawmode) + if im.mode != mode: + im = im.convert(mode) ImageFile._save(im, _idat(fp, chunk), [("zip", (0, 0) + im.size, 0, rawmode)]) seq_num = 0 @@ -1262,6 +1262,7 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False): size = im.size mode = im.mode + outmode = mode if mode == "P": # # attempt to minimize storage requirements for palette images @@ -1282,7 +1283,7 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False): bits = 2 else: bits = 4 - mode = f"{mode};{bits}" + outmode += f";{bits}" # encoder options im.encoderconfig = ( @@ -1294,7 +1295,7 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False): # get the corresponding PNG mode try: - rawmode, bit_depth, color_type = _OUTMODES[mode] + rawmode, bit_depth, color_type = _OUTMODES[outmode] except KeyError as e: msg = f"cannot write mode {mode} as PNG" raise OSError(msg) from e @@ -1415,7 +1416,7 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False): if save_all: im = _write_multiple_frames( - im, fp, chunk, rawmode, default_image, append_images + im, fp, chunk, mode, rawmode, default_image, append_images ) if im: ImageFile._save(im, _idat(fp, chunk), [("zip", (0, 0) + im.size, 0, rawmode)])