Skip to content

Commit 3d49166

Browse files
authored
Merge pull request #6610 from radarhere/png_save_all
2 parents 1f5be89 + b2b3b62 commit 3d49166

File tree

2 files changed

+40
-11
lines changed

2 files changed

+40
-11
lines changed

Tests/test_file_apng.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -647,6 +647,16 @@ def test_seek_after_close():
647647
im.seek(0)
648648

649649

650+
@pytest.mark.parametrize("mode", ("RGBA", "RGB", "P"))
651+
def test_different_modes_in_later_frames(mode, tmp_path):
652+
test_file = str(tmp_path / "temp.png")
653+
654+
im = Image.new("L", (1, 1))
655+
im.save(test_file, save_all=True, append_images=[Image.new(mode, (1, 1))])
656+
with Image.open(test_file) as reloaded:
657+
assert reloaded.mode == mode
658+
659+
650660
def test_constants_deprecation():
651661
for enum, prefix in {
652662
PngImagePlugin.Disposal: "APNG_DISPOSE_",

src/PIL/PngImagePlugin.py

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1089,28 +1089,28 @@ def write(self, data):
10891089
self.seq_num += 1
10901090

10911091

1092-
def _write_multiple_frames(im, fp, chunk, rawmode):
1093-
default_image = im.encoderinfo.get("default_image", im.info.get("default_image"))
1092+
def _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images):
10941093
duration = im.encoderinfo.get("duration", im.info.get("duration", 0))
10951094
loop = im.encoderinfo.get("loop", im.info.get("loop", 0))
10961095
disposal = im.encoderinfo.get("disposal", im.info.get("disposal", Disposal.OP_NONE))
10971096
blend = im.encoderinfo.get("blend", im.info.get("blend", Blend.OP_SOURCE))
10981097

10991098
if default_image:
1100-
chain = itertools.chain(im.encoderinfo.get("append_images", []))
1099+
chain = itertools.chain(append_images)
11011100
else:
1102-
chain = itertools.chain([im], im.encoderinfo.get("append_images", []))
1101+
chain = itertools.chain([im], append_images)
11031102

11041103
im_frames = []
11051104
frame_count = 0
11061105
for im_seq in chain:
11071106
for im_frame in ImageSequence.Iterator(im_seq):
1108-
im_frame = im_frame.copy()
1109-
if im_frame.mode != im.mode:
1110-
if im.mode == "P":
1111-
im_frame = im_frame.convert(im.mode, palette=im.palette)
1107+
if im_frame.mode == rawmode:
1108+
im_frame = im_frame.copy()
1109+
else:
1110+
if rawmode == "P":
1111+
im_frame = im_frame.convert(rawmode, palette=im.palette)
11121112
else:
1113-
im_frame = im_frame.convert(im.mode)
1113+
im_frame = im_frame.convert(rawmode)
11141114
encoderinfo = im.encoderinfo.copy()
11151115
if isinstance(duration, (list, tuple)):
11161116
encoderinfo["duration"] = duration[frame_count]
@@ -1221,7 +1221,26 @@ def _save_all(im, fp, filename):
12211221
def _save(im, fp, filename, chunk=putchunk, save_all=False):
12221222
# save an image to disk (called by the save method)
12231223

1224-
mode = im.mode
1224+
if save_all:
1225+
default_image = im.encoderinfo.get(
1226+
"default_image", im.info.get("default_image")
1227+
)
1228+
modes = set()
1229+
append_images = im.encoderinfo.get("append_images", [])
1230+
if default_image:
1231+
chain = itertools.chain(append_images)
1232+
else:
1233+
chain = itertools.chain([im], append_images)
1234+
for im_seq in chain:
1235+
for im_frame in ImageSequence.Iterator(im_seq):
1236+
modes.add(im_frame.mode)
1237+
for mode in ("RGBA", "RGB", "P"):
1238+
if mode in modes:
1239+
break
1240+
else:
1241+
mode = modes.pop()
1242+
else:
1243+
mode = im.mode
12251244

12261245
if mode == "P":
12271246

@@ -1373,7 +1392,7 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False):
13731392
chunk(fp, b"eXIf", exif)
13741393

13751394
if save_all:
1376-
_write_multiple_frames(im, fp, chunk, rawmode)
1395+
_write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images)
13771396
else:
13781397
ImageFile._save(im, _idat(fp, chunk), [("zip", (0, 0) + im.size, 0, rawmode)])
13791398

0 commit comments

Comments
 (0)