When saving multiple frames, convert to mode rather than raw mode

This commit is contained in:
Andrew Murray 2024-05-27 18:09:46 +10:00
parent bbe1effd63
commit f34360d1e3
2 changed files with 12 additions and 10 deletions

View File

@ -655,11 +655,12 @@ class TestFilePng:
png.call(cid, 0, 0) png.call(cid, 0, 0)
ImageFile.LOAD_TRUNCATED_IMAGES = False 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") im = hopper("P")
out = str(tmp_path / "temp.png") 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: with Image.open(out) as reloaded:
assert len(reloaded.png.im_palette[1]) == 48 assert len(reloaded.png.im_palette[1]) == 48

View File

@ -1104,7 +1104,7 @@ class _fdat:
self.seq_num += 1 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)) duration = im.encoderinfo.get("duration", im.info.get("duration", 0))
loop = im.encoderinfo.get("loop", im.info.get("loop", 0)) loop = im.encoderinfo.get("loop", im.info.get("loop", 0))
disposal = im.encoderinfo.get("disposal", im.info.get("disposal", Disposal.OP_NONE)) 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 frame_count = 0
for im_seq in chain: for im_seq in chain:
for im_frame in ImageSequence.Iterator(im_seq): for im_frame in ImageSequence.Iterator(im_seq):
if im_frame.mode == rawmode: if im_frame.mode == mode:
im_frame = im_frame.copy() im_frame = im_frame.copy()
else: else:
im_frame = im_frame.convert(rawmode) im_frame = im_frame.convert(mode)
encoderinfo = im.encoderinfo.copy() encoderinfo = im.encoderinfo.copy()
if isinstance(duration, (list, tuple)): if isinstance(duration, (list, tuple)):
encoderinfo["duration"] = duration[frame_count] 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) # default image IDAT (if it exists)
if default_image: if default_image:
if im.mode != rawmode: if im.mode != mode:
im = im.convert(rawmode) im = im.convert(mode)
ImageFile._save(im, _idat(fp, chunk), [("zip", (0, 0) + im.size, 0, rawmode)]) ImageFile._save(im, _idat(fp, chunk), [("zip", (0, 0) + im.size, 0, rawmode)])
seq_num = 0 seq_num = 0
@ -1262,6 +1262,7 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False):
size = im.size size = im.size
mode = im.mode mode = im.mode
outmode = mode
if mode == "P": if mode == "P":
# #
# attempt to minimize storage requirements for palette images # attempt to minimize storage requirements for palette images
@ -1282,7 +1283,7 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False):
bits = 2 bits = 2
else: else:
bits = 4 bits = 4
mode = f"{mode};{bits}" outmode += f";{bits}"
# encoder options # encoder options
im.encoderconfig = ( im.encoderconfig = (
@ -1294,7 +1295,7 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False):
# get the corresponding PNG mode # get the corresponding PNG mode
try: try:
rawmode, bit_depth, color_type = _OUTMODES[mode] rawmode, bit_depth, color_type = _OUTMODES[outmode]
except KeyError as e: except KeyError as e:
msg = f"cannot write mode {mode} as PNG" msg = f"cannot write mode {mode} as PNG"
raise OSError(msg) from e raise OSError(msg) from e
@ -1415,7 +1416,7 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False):
if save_all: if save_all:
im = _write_multiple_frames( im = _write_multiple_frames(
im, fp, chunk, rawmode, default_image, append_images im, fp, chunk, mode, rawmode, default_image, append_images
) )
if im: if im:
ImageFile._save(im, _idat(fp, chunk), [("zip", (0, 0) + im.size, 0, rawmode)]) ImageFile._save(im, _idat(fp, chunk), [("zip", (0, 0) + im.size, 0, rawmode)])