Pillow icon indicating copy to clipboard operation
Pillow copied to clipboard

First frame of transparent GIF is read wrong

Open jerrutledge opened this issue 3 years ago • 4 comments

What did you do?

I have a four-frame transparent GIF I want to do some manipulation with (i.e. comparing frames, cropping). squidcat

I ran this code to turn each frame into an array, and saved each array as a png for visualization purposes.

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 pngcat_1 pngcat_2 pngcat_3

What are your OS, Python and Pillow versions?

  • OS: Mac OS 12.5.1 (21G83)
  • Python: 3.10.6
  • Pillow: 9.2.0

jerrutledge avatar Sep 16 '22 22:09 jerrutledge

Additional information: I have tried using LoadingStrategy.RGB_ALWAYS, but it produces results with no transparency:

pngcat_0

Is this different treatment of the first frame intended behaviour?

jerrutledge avatar Sep 16 '22 23:09 jerrutledge

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.

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.

radarhere avatar Sep 17 '22 01:09 radarhere

I've created PR #6592 to fix the LoadingStrategy.RGB_ALWAYS bug, resolving this.

radarhere avatar Sep 17 '22 08:09 radarhere

This was immensely helpful. Thank you especially for the PR. I've tested the #6592 version of GifImagePlugin.py with my project locally using LoadingStrategy.RGB_ALWAYS and it has solved my issue. Much appreciated.

jerrutledge avatar Sep 18 '22 18:09 jerrutledge