0

I'm developing a GUI in Tkinter and want to apply animation in the below GIF on the image when it appears.

enter image description here

Here is my code,

from tkinter import *
from PIL import Image, ImageTk

root = Tk()

frame = Frame(root)
frame.pack()

canvas = Canvas(frame, width=300, height=300, bd=0, highlightthickness=0, relief='ridge')
canvas.pack()

background = PhotoImage(file="background.png")
canvas.create_image(300,300,image=background)

my_pic = PhotoImage(file="start000-befored.png")
frame.after(1000, lambda: (canvas.create_image(50,50,image=my_pic, anchor=NW))) #and on this image, I want to give the effect.
root.mainloop()

Instead of clicking on the play button as shown in GIF, the image should automatically appears after 1 second like this animation and stays on screen. (No closing option).

sodmzs1
  • 324
  • 1
  • 12

1 Answers1

1

I'm not 100% sure I understood the problem, but I'll describe how to animate an image.

Tkinter does not contain functions for animating images so you'll have to write them yourself. You will have to extract all subimages, subimage duration and then build a sequencer to swap subimages on your display.

Pillow can extract image sequences. WEBP images seems to only support one frame duration whereas GIF images may have different frame duration for each subimage. I will use only the first duration for GIF images even if there is many. Pillow does not support getting frame duration from WEBP images as far as I have seen but you gan read it from the file, see WebP Container Specification.

Example implementation:

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

root = tk.Tk()
display = tk.Label(root)
display.pack(padx=10, pady=10)

filename = 'images/animated-nyan-cat.webp'
pil_image = Image.open(filename)
no_of_frames = pil_image.n_frames

# Get frame duration, assuming all frame durations are the same
duration = pil_image.info.get('duration', None)   # None for WEBP
if duration is None:
    with open(filename, 'rb') as binfile:
        data = binfile.read()
    pos = data.find(b'ANMF')    # Extract duration for WEBP sequences
    duration = int.from_bytes(data[pos+12:pos+15], byteorder='big')

# Create an infinite cycle of PIL ImageTk images for display on label
frame_list = []
for frame in ImageSequence.Iterator(pil_image):
    cp = frame.copy()
    frame_list.append(cp)
tkframe_list = [ImageTk.PhotoImage(image=fr) for fr in frame_list]
tkframe_sequence = itertools.cycle(tkframe_list)
tkframe_iterator = iter(tkframe_list)

def show_animation():
    global after_id
    after_id = root.after(duration, show_animation)
    img = next(tkframe_sequence)
    display.config(image=img)

def stop_animation(*event):
    root.after_cancel(after_id)

def run_animation_once():
    global after_id
    after_id = root.after(duration, run_animation_once)
    try:
        img = next(tkframe_iterator)
    except StopIteration:
        stop_animation()
    else:
        display.config(image=img)
    
root.bind('<space>', stop_animation)

# Now you can run show_animation() or run_animation_once() at your pleasure
root.after(1000, run_animation_once)

root.mainloop()

There are libraries, like imgpy, which supports GIF animation but I have no experience in usig any such library.

Addition

The duration variable sets the animation rate. To slow the rate down just increase the duration.

The simplest way to put the animation on a canvas it simply to put the label on a canvas, see example below:

# Replace this code
root = tk.Tk()
display = tk.Label(root)
display.pack(padx=10, pady=10)

# with this code
root = tk.Tk()
canvas = tk.Canvas(root, width=500, height=500)
canvas.pack(padx=10, pady=10)
display = tk.Label(canvas)
window = canvas.create_window(250, 250, anchor='center', window=display)

Then you don't have to change anything else in the program.

figbeam
  • 7,001
  • 2
  • 12
  • 18