Repository: python-pillow/Pillow

Problem statement:
AnimGIF not transparent
Loading some anim-GIFs result in opaque background.
Example to reproduce: PNG has a black border on the right inside (should be transparent):

````python
import base64, io
import PIL.Image

animgif = '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'
with PIL.Image.open(io.BytesIO(base64.b64decode(animgif)), 'r') as img:
    img.save( '/tmp/blackborder.png', 'PNG' )
````


Hints:
The first frame has a `dispose_extent` of (0, 0, 27, 32) - which is not the full size of the image.

#3434 would fix this image, by setting the uncovered parts of the first frame to transparency.

I just don't understand why this is the solution on a theoretical level, as the [spec](https://www.w3.org/Graphics/GIF/spec-gif89a.txt) states that
> The Background Color is the color used for those pixels on the screen that are not covered by an image.

rather than the transparency.
@RLaursen since you're thinking a lot about GIFs at the moment, I'll ask you.

See this 75px by 50px image.

![out](https://user-images.githubusercontent.com/3112309/122673953-5c9f7680-d216-11eb-8680-a115c52d87c5.gif)

The first frame (well, the only frame) has an Image Descriptor with an Image Left Position of (25, 0) - in Pillow terms, those are the `x0` and `y0` of `dispose_extent`.

So the leftmost 25 pixels of the image are not covered. Since the spec states that the background color should be "used for those pixels on the screen that are not covered by an image", and the background color is #0f0 (0 index, meaning the first entry in the palette), I would think those pixels should be green.

Instead, the browser renders those pixels as transparent.

Any thoughts as to why this is? This is not a question about how Pillow behaves, rather a question about how GIFs behave.
@radarhere That's certainly interesting, and I agree that the uncovered portion on the left should be the background color. Even if the background index was the same as the transparency index, which it isn't, I still think it should display the background color, purely based on the 89a specs.

My guess is that viewers are generally set up to only use the background color when disposal method 2 is used, even though this seems like an incorrect interpretation of the format. Perhaps Pillow should mirror this behavior, if only to display GIFs consistently.
yeah, strangely enough, even with the transparency flag off, transparency is used for uncovered canvas

![weirdy](https://user-images.githubusercontent.com/67596484/122839858-bacb6880-d2ad-11eb-9b50-7968225d359a.gif)

I'm also trying to get Pillow working better with animated GIFs. Not much progress yet. @radarhere has already noticed some problems with the spec. E.g., when method 3 is used in the first frame, to dispose to previous, when there is no previous? Or what happens with disposal method 2 when there's no GCT, so no background color?

Apparently the interpretation "in the wild" may follow historic conventions. 

A clue: http://giflib.sourceforge.net/whatsinagif/bits_and_bytes.html says of the background color index: "GIFLIB supports reading and setting this byte, but modern viewers and browsers generally have no use for it." (Also says of the size in the Logical Screen Descriptor: "The canvas width and height are usually ignored by modern viewers.") 

A page apparently authored when animated GIFs were a new thing: http://www6.uniovi.es/gifanim/gifabout.htm says this: "The Logical Screen Block also chooses one of the colors in the Global Color Table to be the Background color of the screen. This color selection is ignored by Netscape Navigator. If a GIF's background area shows through, Navigator displays the color set in the BGCOLOR of the page's body or, if none is specified, the background color set in the menus under OPTIONS/GENERAL PREFERENCES/COLORS. Now, of course, the question arises; how do I get it to be transparent? Well, this SHOULDN'T work, but it does. Apparently, if Netscape's decoder finds a Control block (it must be first, before any images) with Transparency turned on (any color) the background of the GIF will be transparent. This will allows background GIFs to fill in the logical screen background."

It's not clear to me what Navigator did when no transparency was set, since it ignored the background color set in the LSD. But I'm guessing browsers may have always ignored it. That page also says "If your logical screen is larger than your image, you will have space around the image when displayed." But doesn't say what was in that space. Maybe just the page bgcolor or browser preference.

That page also says "The logical screen area should be large enough to display all of your individual frames in it. If an image in the GIF file is larger than the logical screen or, by its positioning, extends beyond the screen, the portion that is off-screen will not be displayed." Which contradicts Pillow's decision to expand the size when a frame exceeds its boundaries.

I opened 75x50 GIF in several programs. Irfanview indicated its size as 50x50, consistent with what giflib whatsinagif says. It does the same with @rlaursen's  GIF above.

I haven't found any more information about how programs actually interpret these features (background, transparency, image size, disposal methods.) I'll keep digging.

Patch:
diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py
--- a/src/PIL/GifImagePlugin.py
+++ b/src/PIL/GifImagePlugin.py
@@ -269,9 +269,14 @@ def _seek(self, frame):
                 dispose_size = (x1 - x0, y1 - y0)
 
                 Image._decompression_bomb_check(dispose_size)
-                self.dispose = Image.core.fill(
-                    "P", dispose_size, self.info.get("background", 0)
+
+                # by convention, attempt to use transparency first
+                color = (
+                    frame_transparency
+                    if frame_transparency is not None
+                    else self.info.get("background", 0)
                 )
+                self.dispose = Image.core.fill("P", dispose_size, color)
             else:
                 # replace with previous contents
                 if self.im:
@@ -317,6 +322,12 @@ def _seek(self, frame):
         if self.palette:
             self.mode = "P"
 
+    def load_prepare(self):
+        if not self.im and "transparency" in self.info:
+            self.im = Image.core.fill(self.mode, self.size, self.info["transparency"])
+
+        super(GifImageFile, self).load_prepare()
+
     def tell(self):
         return self.__frame
 


Test patch:
diff --git a/Tests/images/first_frame_transparency.gif b/Tests/images/first_frame_transparency.gif
new file mode 100644
Binary files /dev/null and b/Tests/images/first_frame_transparency.gif differ
diff --git a/Tests/images/transparent_dispose.gif b/Tests/images/transparent_dispose.gif
new file mode 100644
Binary files /dev/null and b/Tests/images/transparent_dispose.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
@@ -298,6 +298,12 @@ def test_eoferror():
         im.seek(n_frames - 1)
 
 
+def test_first_frame_transparency():
+    with Image.open("Tests/images/first_frame_transparency.gif") as im:
+        px = im.load()
+        assert px[0, 0] == im.info["transparency"]
+
+
 def test_dispose_none():
     with Image.open("Tests/images/dispose_none.gif") as img:
         try:
@@ -331,6 +337,16 @@ def test_dispose_background():
             pass
 
 
+def test_transparent_dispose():
+    expected_colors = [(2, 1, 2), (0, 1, 0), (2, 1, 2)]
+    with Image.open("Tests/images/transparent_dispose.gif") as img:
+        for frame in range(3):
+            img.seek(frame)
+            for x in range(3):
+                color = img.getpixel((x, 0))
+                assert color == expected_colors[frame][x]
+
+
 def test_dispose_previous():
     with Image.open("Tests/images/dispose_prev.gif") as img:
         try: