Play an Animated Gif in Python 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

Question on how to animate gif in tkinter

An animated GIF contains a number of frames and you have to read each frame into a PhotoImage. Then you have to play them back explicitly. Tkinter can't read the delay time between frames so you'll have to test different values. See example below:

import tkinter as tk

root = tk.Tk()

framelist = [] # List to hold all the frames
frame_index = 0 # Frame index

while True:
try:
# Read a frame from GIF file
part = 'gif -index {}'.format(frame_index)
frame = tk.PhotoImage(file='images/animated.gif', format=part)
except:
last_frame = frame_index - 1 # Save index for last frame
break # Will break when GIF index is reached
framelist.append(frame)
frame_index += 1 # Next frame index

def animate(frame_number):
if frame_number > last_frame:
frame_number = 0
label.config(image=framelist[frame_number])
root.after(50, animate, frame_number+1)

label = tk.Label(root, bg='#202020')
label.pack()

animate(0) # Start animation

root.mainloop()

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.

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

Python Showing a gif as background using Tkinter and PIL

I got your code working.
Note that you haven't set everything on it. You must to "Image.open" in your file before.
"Image" is imported as PIM from PIL to prevent conflict.

import os, tkinter as tk
from PIL import Image as PIM, ImageTk
from tkinter import*

root= tk.Tk()
root.title("SCP-079")
root.resizable(0,0)
root.iconbitmap("079.ico")
root.geometry("540x360")

im = PIM.open("C:\\...\\CeEOy.gif")
ph = ImageTk.PhotoImage(im)
gif = Label(root, image = ph, bg="black", bd=3)
gif.image = ph
framnr = 79
frames = [PhotoImage(file="C:\\...\\CeEOy.gif",
format = 'gif -index %i' %(i)) for i in range(framnr)]

def update(ind):
frame = frames[ind]
ind += 1
if ind>78:
ind = 0
gif.configure(image = frame)
root.after(100, update, ind)

gif = Label(root)
gif.pack()
root.after(0, update, 0)
root.mainloop()


Related Topics



Leave a reply



Submit