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
Python: Converting from Iso-8859-1/Latin1 to Utf-8
How to Make a Multidimension Numpy Array with a Varying Row Size
Retrieve List of Tasks in a Queue in Celery
Python Script to Copy Text to Clipboard
How to Convert CSV File to Multiline JSON
How to Create Key or Append an Element to Key
Python Enum, When and Where to Use
How to Load a Module from Code in a String
How to Print One Character at a Time on One Line
How to Send Cookies in a Post Request with the Python Requests Library
How to Add Multiple Annotations to a Barplot