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()
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.
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.
This script returns a list of image object that can be further modified to be used with tkinter or other GUI frameworks.