Gif Animation in tkinter with PILL Flickering On E

2019-07-23 05:48发布

问题:

I am writing a simple widget to animate gifs in tkinter using PILL since tkinter does not natively support them. The issue I am having is that some gifs are flickering. Below is an example if this effect.

I am trying to animate this gif:

This gifs source can be found here.

However, when I run my code, the animation turns out like this:

I split apart the gif, and every other frame is partial like this:

After some research I believe this is a way of compressing gif files so that some frames only represent movement. I am not 100% on this however, and I could be wrong there. If that is the case, how can I reform the images in a way to recreate the quality in the original gif?

I have been able to create create a simple work around that just skips every other frame, but that does not fix the actual issue, and that most likely will not work with every gif like this.

How can I display the frames as such that the animation recreates the original quality of the gif.

Implimentation:

import tkinter as tk
from PIL import Image, ImageTk, ImageSequence


class AnimatedGif:
    def __init__(self, root, src=''):
        self.root = root

        # Load Frames
        self.image = Image.open(src)
        self.frames = []
        self.duration = []
        for frame in ImageSequence.Iterator(self.image):
                self.duration.append(frame.info['duration'])
                self.frames.append(ImageTk.PhotoImage(frame))
        self.counter = 0
        self.image = self.frames[self.counter]

        # Create Label
        self.label = tk.Label(self.root, image=self.image)
        self.label.pack()

        # Start Animation
        self.__step_frame()

    def __step_frame(self):
        # Update Frame
        self.label.config(image=self.frames[self.counter])
        self.image = self.frames[self.counter]

        # Loop Counter
        self.counter += 1
        if self.counter >= len(self.frames):
            self.counter = 0

        # Queue Frame Update
        self.root.after(self.duration[self.counter], lambda: self.__step_frame())

    def pack(self, **kwargs):
        self.label.pack(**kwargs)

    def grid(self, **kwargs):
        self.label.grid(**kwargs)

if __name__ in '__main__':
    root = tk.Tk()
    gif = AnimatedGif(root, '144.gif')
    gif.pack()
    root.mainloop()

回答1:

The disposal method was the cause of the issue like I previously thought. The partial frames have their disposal method set to 2, and it was happening every other frame because the other frames were set to 1 like so:

1, 2, 1, 2, 1, 2, 1, 2...

Disposal method 2 uses the previous frame as a background, and changes only the opaque pixels in the current frame. Method 1 will copy the entire image over the other one while leaving the transparency intact.

iv) Disposal Method - Indicates the way in which the graphic is to be treated after being displayed.

        Values :    0 -   No disposal specified. The decoder is
                          not required to take any action.
                    1 -   Do not dispose. The graphic is to be left
                          in place.
                    2 -   Restore to background color. The area used by the
                          graphic must be restored to the background color.
                    3 -   Restore to previous. The decoder is required to
                          restore the area overwritten by the graphic with
                          what was there prior to rendering the graphic.
                 4-7 -    To be defined.

Source: www.w3.org

In the code below, a disposal method of 1 is handles equivalently to method 0. (In that it takes just the current frame rather than pasting it onto the last frame first) It does this because I was getting the tan color on the partial frames was bleeding through, and handling it in the way it is handled in the code provides the desirable result.

Methods 3+ are omitted from this script due to them being rare and not being relevant to to this question as this gif uses methods 0 and 1.

from PIL import Image, ImageSequence


def unpack_gif(src):
    # Load Gif
    image = Image.open(src)

    # Get frames and disposal method for each frame
    frames = []
    disposal = []
    for gifFrame in ImageSequence.Iterator(image):
        disposal.append(gifFrame.disposal_method)
        frames.append(gifFrame.convert('P'))

    # Loop through frames, and edit them based on their disposal method
    output = []
    lastFrame = None
    thisFrame = None
    for i, loadedFrame in enumerate(frames):
        # Update thisFrame
        thisFrame = loadedFrame

        # If the disposal method is 2
        if disposal[i] == 2:
            # Check that this is not the first frame
            if i != 0:
                # Pastes thisFrames opaque pixels over lastFrame and appends lastFrame to output
                lastFrame.paste(thisFrame, mask=thisFrame.convert('RGBA'))
                output.append(lastFrame)
            else:
                output.append(thisFrame)

        # If the disposal method is 1 or 0
        elif disposal[i] == 1 or disposal[i] == 0:
            # Appends thisFrame to output
            output.append(thisFrame)

        # If disposal method if anything other than 2, 1, or 0
        else:
            raise ValueError('Disposal Methods other than 2:Restore to Background, 1:Do Not Dispose, and 0:No Disposal are supported at this time')

        # Update lastFrame
        lastFrame = loadedFrame

    return output

This script returns a list of image object that can be further modified to be used with tkinter or other GUI frameworks.