Search code examples
pythonpython-imaging-librarygifanimated-gif

How to open optimized GIF without bugs?


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.


Solution

  • 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
    ----------------------------------------