Play Animations in Gif with Tkinter

Play Animations in GIF with Tkinter

You have to drive the animation yourself in Tk. An animated gif consists of a number of frames in a single file. Tk loads the first frame but you can specify different frames by passing an index parameter when creating the image. For example:

frame2 = PhotoImage(file=imagefilename, format="gif -index 2")

If you load up all the frames into separate PhotoImages and then use timer events to switch the frame being shown (label.configure(image=nextframe)). The delay on the timer lets you control the animation speed. There is nothing provided to give you the number of frames in the image other than it failing to create a frame once you exceed the frame count.

See the photo Tk manual page for the official word.

Play an Animated GIF in python with tkinter

The error means that you tried to load 100 frames, but the gif has less than that.

Animated gifs in tkinter are notoriously bad. I wrote this code an age ago that you can steal from, but will get laggy with anything but small gifs:

import tkinter as tk
from PIL import Image, ImageTk
from itertools import count

class ImageLabel(tk.Label):
"""a label that displays images, and plays them if they are gifs"""
def load(self, im):
if isinstance(im, str):
im = Image.open(im)
self.loc = 0
self.frames = []

try:
for i in count(1):
self.frames.append(ImageTk.PhotoImage(im.copy()))
im.seek(i)
except EOFError:
pass

try:
self.delay = im.info['duration']
except:
self.delay = 100

if len(self.frames) == 1:
self.config(image=self.frames[0])
else:
self.next_frame()

def unload(self):
self.config(image="")
self.frames = None

def next_frame(self):
if self.frames:
self.loc += 1
self.loc %= len(self.frames)
self.config(image=self.frames[self.loc])
self.after(self.delay, self.next_frame)

root = tk.Tk()
lbl = ImageLabel(root)
lbl.pack()
lbl.load('ball-1.gif')
root.mainloop()

Play an animated GIF in python with tkinter while loading

First using while loop and time.sleep() in the main thread of a tkinter application is not recommended because it will block tkinter mainloop() and then cause the GUI not responding.

Suggest to:

  • use .after() to show the animated GIF because it is not safe to update tkinter widgets in a thread
  • use thread on loadingAnimation() instead
import threading
import tkinter
import sys
import time

root = tkinter.Tk()
frames = [tkinter.PhotoImage(file='./myScripts/M-95.gif', format='gif -index %i'%(i)) for i in range(10)]

def center_window(win):
win.wait_visibility() # make sure the window is ready
x = (win.winfo_screenwidth() - win.winfo_width()) // 2
y = (win.winfo_screenheight() - win.winfo_height()) // 2
win.geometry(f'+{x}+{y}')

def M_95(n=0, top=None, lbl=None):
# Play GIF (file name = m95.gif) in a 320x320 tkinter window
# Play GIF concurrently with the loading animation below
# Close tkinter window after play
global process_is_alive # used in loadingAnimation()
delay = 4000 // len(frames) # make one cycle of animation around 4 secs
if n == 0:
root.withdraw()
top = tkinter.Toplevel()
lbl = tkinter.Label(top, image=frames[0])
lbl.pack()
center_window(top)
process_is_alive = True
if n < len(frames)-1:
lbl.config(image=frames[n])
lbl.after(delay, M_95, n+1, top, lbl)
else:
top.destroy()
root.deiconify()
process_is_alive = False

def loadingAnimation():
animation = ["[■□□□□□□□□□]","[■■□□□□□□□□]", "[■■■□□□□□□□]", "[■■■■□□□□□□]", "[■■■■■□□□□□]", "[■■■■■■□□□□]", "[■■■■■■■□□□]", "[■■■■■■■■□□]", "[■■■■■■■■■□]", "[■■■■■■■■■■]"]
i = 0
while process_is_alive:
sys.stdout.write("\r | Loading..." + animation[i % len(animation)])
sys.stdout.flush()
time.sleep(0.4)
i += 1

M_95() # start GIF animation
threading.Thread(target=loadingAnimation).start()

root.mainloop()

Update: animate GIF more than one cycle in around 4 secs:

def M_95(n=0, top=None, lbl=None):
# Play GIF (file name = m95.gif) in a 320x320 tkinter window
# Play GIF concurrently with the loading animation below
# Close tkinter window after play
global process_is_alive
num_cycles = 2
count = len(frames) * num_cycles
delay = 4000 // count # make required cycles of animation in around 4 secs
if n == 0:
root.withdraw()
top = tkinter.Toplevel()
lbl = tkinter.Label(top, image=frames[0])
lbl.pack()
center_window(top)
process_is_alive = True
lbl.after(delay, M_95, n+1, top, lbl)
elif n < count-1:
lbl.config(image=frames[n%len(frames)])
lbl.after(delay, M_95, n+1, top, lbl)
else:
top.destroy()
root.destroy()
process_is_alive = False

Tkinter GIF not display

Tkinter doesn't automatically show animated gifs. You will have to do the animation yourself by loading each frame in a loop.

See Play Animations in GIF with Tkinter

If your problem is that you're not seeing any image at all, it's probably due to the image being garbage-collected.

See Cannot Display an Image in Tkinter

Displaying animated GIFs in Tkinter using PIL

For one, you are creating a new canvas object for every frame. Eventually you will have thousands of images stacked on top of one another. This is highly inefficient; the canvas widget has performance issues when you start to have thousands of objects.

Instead of creating new image objects on the canvas, just reconfigure the existing object with the itemconfig method of the canvas.

Second, you don't need the complexities of threading for such a simple task. There is a well known pattern in tkinter for doing animations: draw a frame, then have that function use after to call itself in the future.

Something like this:

def animate(self):
if self._image_id is None:
self._image_id = self.display.create_image(...)
else:
self.itemconfig(self._image_id, image= the_new_image)
self.display.after(self.gif["delay"], self.animate)

Finally, unless there's a strict reason to use a canvas, you can lower the complexity a little more by using a Label widget.

Python: Have gifs animate with TKinter while a thread is running

The trouble is in your generate() method.
That while loop is unnecessary.

Bear in mind that you can't re-start a thread.
If you want to use the generate button multiple times, you'll need to create a new thread each time.

class App:
generating = False
def __init__(self, master):
self.master=master

#Initializing frames
self.buttonFrame = Frame(master, background='light gray')
self.loadingFrame = Frame(master, background='light gray')
self.loadingFrame.grid(row=0)
self.buttonFrame.grid(row=1)
self.anim = Gif(self.loadingFrame, '4Y9UJ.gif')
self.anim.pack()
## self.update_Thread = threading.Thread(target=time.sleep, args=(5,))

self.buttonSetup()

def buttonSetup(self):
#ALL THE BUTTONS
self.generateBt = Button(self.buttonFrame, text="Generate!", command=self.generate, background='light gray', highlightbackground='light gray')
self.generateBt.pack(side=LEFT)
self.quitBt = Button(self.buttonFrame, text="Quit!", fg="red", command=self.buttonFrame.quit, background='light gray', highlightbackground='light gray')
self.quitBt.pack(side=LEFT)

def wait_generate(self):
if self.update_Thread.isAlive():
self.master.after(500, self.wait_generate)
else:
tkMessageBox.showinfo("Complete", "Report generation completed!")
self.reset()

def generate(self):
self.hideForGen()
self.update_Thread = threading.Thread(target=time.sleep, args=(5,))
self.update_Thread.start()
## while(self.update_Thread.isAlive()):
## self.master.update_idletasks()
## self.reset()
## self.master.update_idletasks()
## tkMessageBox.showinfo("Complete", "Report generation completed!")
self.wait_generate()

def hideForGen(self):
self.buttonFrame.grid_forget()

def reset(self):
self.buttonFrame.grid(row=1)

Animated GIF with Python Turtle

I suggest to change ImageLabel from tk.Label to image item of Canvas as below:

class ImageLabel:
def __init__(self, canvas):
self.canvas = canvas

def load(self, im, x=0, y=0):
# create a canvas image item
self.image = self.canvas.create_image(x, y, image=None)
self.canvas.tag_lower(self.image)

if isinstance(im, str):
im = Image.open(im)

self.frames = []
try:
for i in count(1):
self.frames.append(ImageTk.PhotoImage(im.copy()))
im.seek(i)
except EOFError:
pass

try:
self.delay = im.info['duration']
except:
self.delay = 100

num_frames = len(self.frames)
if num_frames == 1:
self.canvas.itemconfig(self.image, image=self.frames[0])
else:
self.next_frame(0, num_frames)

def unload(self):
self.canvas.delete(self.image)
self.frames = None

def next_frame(self, loc, total):
if self.frames:
self.canvas.itemconfig(self.image, image=self.frames[loc])
loc = (loc + 1) % total
self.canvas.after(self.delay, self.next_frame, loc, total)

Then load it at specific position:

gif_window = tkinter_gif.ImageLabel(canvas)
gif_window.load("giphy.gif", -200, -200) # 0, 0 is the center of canvas

Animated GIF with Python Tkinter? No more PIL module?

Development on the official PIL has been stopped, however, a fork called Pillow is available for you to use. That being said, it's still a bad option to use this for animated gifs. For that, look into ImageMagik. You'll find it more convinient to work with.



Related Topics



Leave a reply



Submit