Repository: python-pillow/Pillow

Issue:
Exception using "seek" on GIF that contains frames with different modes
I have an animated GIF that has about 60 frames, some of the frames are encoded in 'L' mode and others are in 'RGB' mode. Many actions, including a simple "seek" command" fail with a "ValueError: images do not match" exception when `self.im.paste(frame_im, self.dispose_extent)` is issued in GifImagePlugin.py. This happens when "self.im" and "frame_im" have different modes.

This might be a protection technique for not being able to process such files with Pillow.



Discussion:
I implemented a small hardcoded fix, though am not sure its the best way to solve this issue, it works for me.

I added the following code (in Pillow 9.2.0):
```python
        if self.im.mode != frame_im.mode:
            if self.im.bands > frame_im.bands:
                frame_im = frame_im.convert(self.im.mode)
            else:
                self.im = self.im.convert(frame_im.mode)
```

Immediately before the following block in "GifImagePlugin.py":

```python
        if frame_im.mode == "RGBA":
            self.im.paste(frame_im, self.dispose_extent, frame_im)
        else:
            self.im.paste(frame_im, self.dispose_extent)
```

Either way, the current behavior seems like a bug, even though this might not adhere to animated GIF's spec, it's probably worth a fix.

Could you upload a copy of the image, and simple code that triggers the error?
Here you go, use the code below and the attached GIF.

```python
def main():
    image = Image.open('example.gif')
    image.seek(6)

if __name__ == '__main__':
    main()
```
![example](https://user-images.githubusercontent.com/4147381/189899683-85e19549-d37b-44f7-b420-e5ca82a96a12.gif)


Resolving patch:
diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py
--- a/src/PIL/GifImagePlugin.py
+++ b/src/PIL/GifImagePlugin.py
@@ -274,6 +274,8 @@ def _seek(self, frame, update_image=True):
                     p = self.fp.read(3 << bits)
                     if self._is_palette_needed(p):
                         palette = ImagePalette.raw("RGB", p)
+                    else:
+                        palette = False
 
                 # image data
                 bits = self.fp.read(1)[0]
@@ -298,7 +300,7 @@ def _seek(self, frame, update_image=True):
         if self.dispose:
             self.im.paste(self.dispose, self.dispose_extent)
 
-        self._frame_palette = palette or self.global_palette
+        self._frame_palette = palette if palette is not None else self.global_palette
         self._frame_transparency = frame_transparency
         if frame == 0:
             if self._frame_palette:
@@ -438,16 +440,13 @@ def load_end(self):
                     self.mode = "RGB"
                 self.im = self.im.convert(self.mode, Image.Dither.FLOYDSTEINBERG)
             return
-        if self.mode == "P" and self._prev_im:
-            if self._frame_transparency is not None:
-                self.im.putpalettealpha(self._frame_transparency, 0)
-                frame_im = self.im.convert("RGBA")
-            else:
-                frame_im = self.im.convert("RGB")
+        if not self._prev_im:
+            return
+        if self._frame_transparency is not None:
+            self.im.putpalettealpha(self._frame_transparency, 0)
+            frame_im = self.im.convert("RGBA")
         else:
-            if not self._prev_im:
-                return
-            frame_im = self.im
+            frame_im = self.im.convert("RGB")
         frame_im = self._crop(frame_im, self.dispose_extent)
 
         self.im = self._prev_im


Test patch:
diff --git a/Tests/images/no_palette_after_rgb.gif b/Tests/images/no_palette_after_rgb.gif
new file mode 100644
Binary files /dev/null and b/Tests/images/no_palette_after_rgb.gif differ
diff --git a/Tests/images/palette_not_needed_for_second_frame.gif b/Tests/images/palette_not_needed_for_second_frame.gif
new file mode 100644
Binary files /dev/null and b/Tests/images/palette_not_needed_for_second_frame.gif differ
diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py
--- a/Tests/test_file_gif.py
+++ b/Tests/test_file_gif.py
@@ -83,6 +83,21 @@ def test_l_mode_transparency():
         assert im.load()[0, 0] == 128
 
 
+def test_l_mode_after_rgb():
+    with Image.open("Tests/images/no_palette_after_rgb.gif") as im:
+        im.seek(1)
+        assert im.mode == "RGB"
+
+        im.seek(2)
+        assert im.mode == "RGB"
+
+
+def test_palette_not_needed_for_second_frame():
+    with Image.open("Tests/images/palette_not_needed_for_second_frame.gif") as im:
+        im.seek(1)
+        assert_image_similar(im, hopper("L").convert("RGB"), 8)
+
+
 def test_strategy():
     with Image.open("Tests/images/iss634.gif") as im:
         expected_rgb_always = im.convert("RGB")