Repository: python-pillow/Pillow

Issue:
Converting animated gif to webp produces faulty images
### What did you do?
Converted some animated gifs to animated webp. Used different qualtity settings.

### What did you expect to happen?
Getting a identical converted image. Beside the(logical) format and possible size change.

### What actually happened?
Webp outputs are defect. Three different problems appear.
The output images:
1. play too slow
2. do not play in a image viewer (gwenview for me OR in Pillow's Image.show() method)
3. some images have after convert also visual defects like wrong pixels (flickering and line style artifacts)

ps.: Converting the same images with the same settings with webp CLI tools works without issues.

### What are your OS, Python and Pillow versions?

* OS: Kubuntu 18.04.3
* Python: 3.6.9
* Pillow: 6.2.1

### Code example
Python:
```python
from pathlib import Path as pt
from PIL import Image

src_f = pt("animated.gif")
dst_f = pt(src_f).with_suffix('.webp')

# other used quality settings for fast copy past:
# {'lossless': True} {'allow_mixed': True}
quali = {'quality': 90}

Image.open(src_f).save(dst_f, 'webp', **quali, save_all=True)
```
Shell variant:
```bash
gif2webp -v -lossy -q 90 paris_metro.gif -o paris_metro.webp
```

### Example images
Content of the archive:
- Two input gifs
- both output files (converted with "quality 90") with problems 1. and 2.
- paris_metro.webp has also problem 3. present. (pixel errors are to see while playing)
[anim.tar.gz](https://github.com/python-pillow/Pillow/files/4008165/anim.tar.gz)



Discussion:

Resolving patch:
diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py
--- a/src/PIL/GifImagePlugin.py
+++ b/src/PIL/GifImagePlugin.py
@@ -145,7 +145,6 @@ def _seek(self, frame):
             self.dispose_extent = [0, 0, 0, 0]  # x0, y0, x1, y1
             self.__frame = -1
             self.__fp.seek(self.__rewind)
-            self._prev_im = None
             self.disposal_method = 0
         else:
             # ensure that the previous frame was loaded
@@ -174,6 +173,8 @@ def _seek(self, frame):
         self.palette = copy(self.global_palette)
 
         info = {}
+        frame_transparency = None
+        interlace = None
         while True:
 
             s = self.fp.read(1)
@@ -192,7 +193,7 @@ def _seek(self, frame):
                     #
                     flags = block[0]
                     if flags & 1:
-                        info["transparency"] = block[3]
+                        frame_transparency = block[3]
                     info["duration"] = i16(block, 1) * 10
 
                     # disposal method - find the value of bits 4 - 6
@@ -250,9 +251,6 @@ def _seek(self, frame):
                 # image data
                 bits = self.fp.read(1)[0]
                 self.__offset = self.fp.tell()
-                self.tile = [
-                    ("gif", (x0, y0, x1, y1), self.__offset, (bits, interlace))
-                ]
                 break
 
             else:
@@ -278,11 +276,26 @@ def _seek(self, frame):
         except (AttributeError, KeyError):
             pass
 
-        if not self.tile:
+        if interlace is not None:
+            transparency = -1
+            if frame_transparency is not None:
+                if frame == 0:
+                    self.info["transparency"] = frame_transparency
+                else:
+                    transparency = frame_transparency
+            self.tile = [
+                (
+                    "gif",
+                    (x0, y0, x1, y1),
+                    self.__offset,
+                    (bits, interlace, transparency),
+                )
+            ]
+        else:
             # self.__fp = None
             raise EOFError
 
-        for k in ["transparency", "duration", "comment", "extension", "loop"]:
+        for k in ["duration", "comment", "extension", "loop"]:
             if k in info:
                 self.info[k] = info[k]
             elif k in self.info:
@@ -295,20 +308,6 @@ def _seek(self, frame):
     def tell(self):
         return self.__frame
 
-    def load_end(self):
-        ImageFile.ImageFile.load_end(self)
-
-        # if the disposal method is 'do not dispose', transparent
-        # pixels should show the content of the previous frame
-        if self._prev_im and self._prev_disposal_method == 1:
-            # we do this by pasting the updated area onto the previous
-            # frame which we then use as the current image content
-            updated = self._crop(self.im, self.dispose_extent)
-            self._prev_im.paste(updated, self.dispose_extent, updated.convert("RGBA"))
-            self.im = self._prev_im
-        self._prev_im = self.im.copy()
-        self._prev_disposal_method = self.disposal_method
-
     def _close__fp(self):
         try:
             if self.__fp != self.fp:


Test patch:
diff --git a/Tests/images/different_transparency.gif b/Tests/images/different_transparency.gif
new file mode 100644
Binary files /dev/null and b/Tests/images/different_transparency.gif differ
diff --git a/Tests/images/different_transparency_merged.gif b/Tests/images/different_transparency_merged.gif
new file mode 100644
Binary files /dev/null and b/Tests/images/different_transparency_merged.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
@@ -468,12 +468,25 @@ def test_dispose2_background(tmp_path):
         assert im.getpixel((0, 0)) == 0
 
 
-def test_iss634():
+def test_transparency_in_second_frame():
+    with Image.open("Tests/images/different_transparency.gif") as im:
+        assert im.info["transparency"] == 0
+
+        # Seek to the second frame
+        im.seek(im.tell() + 1)
+        assert im.info["transparency"] == 0
+
+        assert_image_equal_tofile(im, "Tests/images/different_transparency_merged.gif")
+
+
+def test_no_transparency_in_second_frame():
     with Image.open("Tests/images/iss634.gif") as img:
         # Seek to the second frame
         img.seek(img.tell() + 1)
+        assert "transparency" not in img.info
+
         # All transparent pixels should be replaced with the color from the first frame
-        assert img.histogram()[img.info["transparency"]] == 0
+        assert img.histogram()[255] == 0
 
 
 def test_duration(tmp_path):
diff --git a/Tests/test_file_webp_animated.py b/Tests/test_file_webp_animated.py
--- a/Tests/test_file_webp_animated.py
+++ b/Tests/test_file_webp_animated.py
@@ -45,12 +45,12 @@ def test_write_animation_L(tmp_path):
             # Compare first and last frames to the original animated GIF
             orig.load()
             im.load()
-            assert_image_similar(im, orig.convert("RGBA"), 25.0)
+            assert_image_similar(im, orig.convert("RGBA"), 32.9)
             orig.seek(orig.n_frames - 1)
             im.seek(im.n_frames - 1)
             orig.load()
             im.load()
-            assert_image_similar(im, orig.convert("RGBA"), 25.0)
+            assert_image_similar(im, orig.convert("RGBA"), 32.9)
 
 
 @pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian")