Repository: python-pillow/Pillow

Issue:
Image.show() defaults to PNG ignoring animated GIFs
When using show() for an animated GIF with version 9.2.0, Pillow converts the image to PNG by default thus eliminating the animation.

I suggest the following change in "ImageShow.py" to preserve the animation:

```python
def save_image(self, image):
    """Save to temporary file and return filename."""

    if image.format == 'GIF' and image.is_animated:
        suffix = "." + image.format
        f, filename = tempfile.mkstemp(suffix)
        os.close(f)

        image.save(filename, save_all=True)

        return filename
    else:
        image_format = self.get_format(image)
        return image._dump(format=image_format, **self.options)
```


Discussion:
PNG also supports multiple frames. If we were going to add this feature, I would recommend still using the PNG format, as GIF frames are limited to 256 colors.

Resolving patch:
diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py
--- a/src/PIL/PngImagePlugin.py
+++ b/src/PIL/PngImagePlugin.py
@@ -1089,28 +1089,28 @@ def write(self, data):
         self.seq_num += 1
 
 
-def _write_multiple_frames(im, fp, chunk, rawmode):
-    default_image = im.encoderinfo.get("default_image", im.info.get("default_image"))
+def _write_multiple_frames(im, fp, chunk, 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))
     blend = im.encoderinfo.get("blend", im.info.get("blend", Blend.OP_SOURCE))
 
     if default_image:
-        chain = itertools.chain(im.encoderinfo.get("append_images", []))
+        chain = itertools.chain(append_images)
     else:
-        chain = itertools.chain([im], im.encoderinfo.get("append_images", []))
+        chain = itertools.chain([im], append_images)
 
     im_frames = []
     frame_count = 0
     for im_seq in chain:
         for im_frame in ImageSequence.Iterator(im_seq):
-            im_frame = im_frame.copy()
-            if im_frame.mode != im.mode:
-                if im.mode == "P":
-                    im_frame = im_frame.convert(im.mode, palette=im.palette)
+            if im_frame.mode == rawmode:
+                im_frame = im_frame.copy()
+            else:
+                if rawmode == "P":
+                    im_frame = im_frame.convert(rawmode, palette=im.palette)
                 else:
-                    im_frame = im_frame.convert(im.mode)
+                    im_frame = im_frame.convert(rawmode)
             encoderinfo = im.encoderinfo.copy()
             if isinstance(duration, (list, tuple)):
                 encoderinfo["duration"] = duration[frame_count]
@@ -1221,7 +1221,26 @@ def _save_all(im, fp, filename):
 def _save(im, fp, filename, chunk=putchunk, save_all=False):
     # save an image to disk (called by the save method)
 
-    mode = im.mode
+    if save_all:
+        default_image = im.encoderinfo.get(
+            "default_image", im.info.get("default_image")
+        )
+        modes = set()
+        append_images = im.encoderinfo.get("append_images", [])
+        if default_image:
+            chain = itertools.chain(append_images)
+        else:
+            chain = itertools.chain([im], append_images)
+        for im_seq in chain:
+            for im_frame in ImageSequence.Iterator(im_seq):
+                modes.add(im_frame.mode)
+        for mode in ("RGBA", "RGB", "P"):
+            if mode in modes:
+                break
+        else:
+            mode = modes.pop()
+    else:
+        mode = im.mode
 
     if mode == "P":
 
@@ -1373,7 +1392,7 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False):
         chunk(fp, b"eXIf", exif)
 
     if save_all:
-        _write_multiple_frames(im, fp, chunk, rawmode)
+        _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images)
     else:
         ImageFile._save(im, _idat(fp, chunk), [("zip", (0, 0) + im.size, 0, rawmode)])
 


Test patch:
diff --git a/Tests/test_file_apng.py b/Tests/test_file_apng.py
--- a/Tests/test_file_apng.py
+++ b/Tests/test_file_apng.py
@@ -648,6 +648,16 @@ def test_seek_after_close():
         im.seek(0)
 
 
+@pytest.mark.parametrize("mode", ("RGBA", "RGB", "P"))
+def test_different_modes_in_later_frames(mode, tmp_path):
+    test_file = str(tmp_path / "temp.png")
+
+    im = Image.new("L", (1, 1))
+    im.save(test_file, save_all=True, append_images=[Image.new(mode, (1, 1))])
+    with Image.open(test_file) as reloaded:
+        assert reloaded.mode == mode
+
+
 def test_constants_deprecation():
     for enum, prefix in {
         PngImagePlugin.Disposal: "APNG_DISPOSE_",