How to open optimized GIF without bugs?

Viewed 195

So this GIF looks perfectly fine before opening:

The gif before python opens

But, when opened using Pillow using

imageObject = Image.open(path.join(petGifs, f"{pokemonName}.gif"))

it bugs out, adding various boxes that have colors similar to that of the source image. This is an example frame, but almost every frame is different, and it's in different spots depending on the GIF:

one of the frames after the gif is opened in Python

another frame after the gif is opened in Python

The only thing, that has worked to fix this, is ezgif's unoptimize option (found in their optimize page). But, I'd need to do that on each GIF, and there's a lot of them.

I need either a way to bulk unoptimize, or a new way to open the GIF in Python (currently using Pillow), that will handle this.

3 Answers

At least for extracting proper single frames there might be a solution.

The disposal method for all frames (except the first) is set to 2, which is "restore to background color".

Diving through Pillow's source code, you'll find the according line where the disposal method 2 is considered, and, in the following, you'll find:

# 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)

If you check the faulty frames, you'll notice that this dark green color of the unwanted boxes is located at position 0 of the palette. So, it seems, the wrong color is picked for the disposal, because – I don't know why, yet – the above else case is picked instead of using the transparency information – which would be there!

So, let's just override the possibly faulty stuff:

from PIL import Image, ImageSequence

# Open GIF
gif = Image.open('223vK.gif')

# Initialize list of extracted frames
frames = []

for frame in ImageSequence.Iterator(gif):

    # If dispose is set, and color is set to 0, use transparency information
    if frame.dispose is not None and frame.dispose[0] == 0:
        frame.dispose = Image.core.fill('P', frame.dispose.size,
                                        frame.info['transparency'])

    # Convert frame to RGBA
    frames.append(frame.convert('RGBA'))

# Visualization overhead
import matplotlib.pyplot as plt
plt.figure(figsize=(8, 8))
for i, f in enumerate(frames, start=1):
    plt.subplot(8, 8, i), plt.imshow(f), plt.axis('off')
plt.tight_layout(), plt.show()

The extracted frames look like this:

Output

That seems fine to me.

If, by chance, the transparency information is actually set to 0, no harm should be done here, since we (re)set with the still correct transparency information.

I don't know, if (re)saving to GIF will work, since frames are now in RGBA mode, and saving to GIF from there is tricky as well.

----------------------------------------
System information
----------------------------------------
Platform:      Windows-10-10.0.19041-SP0
Python:        3.9.1
PyCharm:       2021.1.3
Matplotlib:    3.4.2
Pillow:        8.3.1
----------------------------------------

You can try to use:

from PIL import Image, ImageSequence

im = Image.open(f"{pokemonName}.gif")

index = 1
for frame in ImageSequence.Iterator(im):
    frame.save("frame%d.png" % index)
    index += 1

I've found a solution that I like for unoptimizing gifs which might be of use to you.

It uses the gifsicle library, which is a command line tool for working with gifs. Crucially, gifsicle lets you unoptimize gifs like yours (I think the specific name of the optimization in your gif is "cumulative layers").

Once you install it with your package manager of choice, you can either call it within your code via Python's subprocess library, or use it yourself from the command line.

You specifically mentioned a way to bulk unoptimize, and you can do that very easily with gifsicle via something like:

gifsicle -U -b *.gif

This will overwrite every gif in the working directory with an unoptimized version simultaneously. If you want to keep optimized copies make backups. See the manual page for more info about how to use gifsicle.

Once the gif is unoptimized python should be able to open it normally.

Related