Repository: python-pillow/Pillow

Issue:
First frame of transparent GIF is read incorrectly
### What did you do?
I have a four-frame transparent GIF I want to do some manipulation with (i.e. comparing frames, cropping). 
![squidcat](https://user-images.githubusercontent.com/5808025/190772980-287de95d-3b75-4280-9d46-e16858017d92.gif)

I ran this code to turn each frame into an array, and saved each array as a png for visualization purposes. 
```python
from PIL import Image
import numpy

inputFileName = "squidcat.gif"
outputFileName = "pngcat"

frames = []
with Image.open(inputFileName, formats=["GIF"]) as imageObject:
    # save each frame to a numpy array
    for frame_num in range(0, imageObject.n_frames):
        imageObject.seek(frame_num)
        frames.append(numpy.array(imageObject))

# do something with the numpy frames

for i in range(len(frames)):
    # save each frame as a png to visualize
    fileName = outputFileName + "_" + str(i) + ".png"
    Image.fromarray(frames[i]).save(fileName)
```

### What did you expect to happen?
I expected the program to generate four png files containing the four transparent frames.

### What actually happened?
The first frame was completely black, with the subsequent three turning out normally. I've included my results below:
![pngcat_0](https://user-images.githubusercontent.com/5808025/190796728-bc635649-511d-4be1-8ab9-cde1f771071a.png)
![pngcat_1](https://user-images.githubusercontent.com/5808025/190796846-0ac66e01-c4ac-4672-be85-0695a3b2f5c0.png)
![pngcat_2](https://user-images.githubusercontent.com/5808025/190796959-30dd3357-e3a2-4835-96cf-fab2985da9df.png)
![pngcat_3](https://user-images.githubusercontent.com/5808025/190796425-654cc31f-fab1-4647-8afa-e431a633b825.png)

### What are your OS, Python and Pillow versions?

* OS: Mac OS 12.5.1 (21G83)
* Python: 3.10.6
* Pillow: 9.2.0


Discussion:
Additional information: I have tried using `LoadingStrategy.RGB_ALWAYS`, but it produces results with no transparency:

![pngcat_0](https://user-images.githubusercontent.com/5808025/190826719-6c76b88a-41db-4169-9798-120f3e729710.png)

Is this different treatment of the first frame intended behaviour? 
If you run the following code, you will see that the GIF is in P mode for the first frame, and then RGBA mode for the subsequent frames that. However, when it is converted to NumPy and then back, the first frame becomes L. You have lost the palette. This is understandable, because a P mode image consists of each pixel as an index in a palette, and the palette itself. When converting to a NumPy array, it would only be copying the indexes of the pixels.

```python
from PIL import Image
import numpy

inputFileName = "squidcat.gif"
outputFileName = "pngcat"

frames = []
with Image.open(inputFileName, formats=["GIF"]) as imageObject:
    # save each frame to a numpy array
    for frame_num in range(0, imageObject.n_frames):
        imageObject.seek(frame_num)
        print("before", imageObject.mode)
        frames.append(numpy.array(imageObject))

# do something with the numpy frames

for i in range(len(frames)):
    # save each frame as a png to visualize
    fileName = outputFileName + "_" + str(i) + ".png"
    im = Image.fromarray(frames[i])
    print("after", im.mode)
    im.save(fileName)
```

As for `LoadingStrategy.RGB_ALWAYS`, yes, that it does appear to be a Pillow bug that the first frame is not using the transparency.

Resolving patch:
diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py
--- a/src/PIL/GifImagePlugin.py
+++ b/src/PIL/GifImagePlugin.py
@@ -299,11 +299,13 @@ def _seek(self, frame, update_image=True):
             self.im.paste(self.dispose, self.dispose_extent)
 
         self._frame_palette = palette or self.global_palette
+        self._frame_transparency = frame_transparency
         if frame == 0:
             if self._frame_palette:
-                self.mode = (
-                    "RGB" if LOADING_STRATEGY == LoadingStrategy.RGB_ALWAYS else "P"
-                )
+                if LOADING_STRATEGY == LoadingStrategy.RGB_ALWAYS:
+                    self.mode = "RGBA" if frame_transparency is not None else "RGB"
+                else:
+                    self.mode = "P"
             else:
                 self.mode = "L"
 
@@ -313,7 +315,6 @@ def _seek(self, frame, update_image=True):
                 palette = copy(self.global_palette)
             self.palette = palette
         else:
-            self._frame_transparency = frame_transparency
             if self.mode == "P":
                 if (
                     LOADING_STRATEGY != LoadingStrategy.RGB_AFTER_DIFFERENT_PALETTE_ONLY
@@ -386,7 +387,8 @@ def _rgb(color):
             transparency = -1
             if frame_transparency is not None:
                 if frame == 0:
-                    self.info["transparency"] = frame_transparency
+                    if LOADING_STRATEGY != LoadingStrategy.RGB_ALWAYS:
+                        self.info["transparency"] = frame_transparency
                 elif self.mode not in ("RGB", "RGBA"):
                     transparency = frame_transparency
             self.tile = [
@@ -410,9 +412,9 @@ def load_prepare(self):
         temp_mode = "P" if self._frame_palette else "L"
         self._prev_im = None
         if self.__frame == 0:
-            if "transparency" in self.info:
+            if self._frame_transparency is not None:
                 self.im = Image.core.fill(
-                    temp_mode, self.size, self.info["transparency"]
+                    temp_mode, self.size, self._frame_transparency
                 )
         elif self.mode in ("RGB", "RGBA"):
             self._prev_im = self.im
@@ -429,8 +431,12 @@ def load_prepare(self):
     def load_end(self):
         if self.__frame == 0:
             if self.mode == "P" and LOADING_STRATEGY == LoadingStrategy.RGB_ALWAYS:
-                self.mode = "RGB"
-                self.im = self.im.convert("RGB", Image.Dither.FLOYDSTEINBERG)
+                if self._frame_transparency is not None:
+                    self.im.putpalettealpha(self._frame_transparency, 0)
+                    self.mode = "RGBA"
+                else:
+                    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:


Test patch:
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
@@ -84,17 +84,24 @@ def test_l_mode_transparency():
 
 
 def test_strategy():
+    with Image.open("Tests/images/iss634.gif") as im:
+        expected_rgb_always = im.convert("RGB")
+
     with Image.open("Tests/images/chi.gif") as im:
-        expected_zero = im.convert("RGB")
+        expected_rgb_always_rgba = im.convert("RGBA")
 
         im.seek(1)
-        expected_one = im.convert("RGB")
+        expected_different = im.convert("RGB")
 
     try:
         GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_ALWAYS
-        with Image.open("Tests/images/chi.gif") as im:
+        with Image.open("Tests/images/iss634.gif") as im:
             assert im.mode == "RGB"
-            assert_image_equal(im, expected_zero)
+            assert_image_equal(im, expected_rgb_always)
+
+        with Image.open("Tests/images/chi.gif") as im:
+            assert im.mode == "RGBA"
+            assert_image_equal(im, expected_rgb_always_rgba)
 
         GifImagePlugin.LOADING_STRATEGY = (
             GifImagePlugin.LoadingStrategy.RGB_AFTER_DIFFERENT_PALETTE_ONLY
@@ -105,7 +112,7 @@ def test_strategy():
 
             im.seek(1)
             assert im.mode == "P"
-            assert_image_equal(im.convert("RGB"), expected_one)
+            assert_image_equal(im.convert("RGB"), expected_different)
 
         # Change to RGB mode when a frame has an individual palette
         with Image.open("Tests/images/iss634.gif") as im: