Search code examples
pythontkinter

Is there a way to load images in tkinter faster?


I am trying to implement images into buttons in tkinter, and when i try to load it, it takes more than 5 seconds for the images and window to load.

I want it so that when next button is clicked, the images load almost instantly so that i can debug and test the code faster (without wasting 6 seconds everytime). Is there a better way (or faster) to load these images?

from tkinter import *
import math
from PIL import Image, ImageTk
import time
root = Tk()

# Tkinter stuff 
root.title("Projectile motion")
root.geometry("800x500")
root.resizable(False, False)
scale_label = None

def switch():
    page1.grid_forget()  # Hide the welcome page
    page2.grid(row=0, column=0, sticky="nsew")  # Show the instructions page
page1 = Frame(root, width=800, height=500)
page2 = Frame(root, width=800, height=500, bg = "azure3")


page1.grid(row=0, column=0, sticky="nsew")
page2.grid(row=0, column=0, sticky="nsew")

# main page stuff
main_window_title = Label(page1, text="Projectile Motion Simulation", font=("Times New Roman bold", 35))
main_window_title.place(relx=0.13, rely=0.26)

main_window_button = Button(page1, text="Start", font=("Arial bold", 15), background= "#ff0000", activebackground= "#f08080", command=switch,
                            height=4, width=20)
main_window_button.place(relx=0.5, rely=0.6, anchor="center")

# page 2/ instructions page
welcome_text = Label(page2, text="Welcome to Projectile Motion simulation",bg = "azure3", font=("Times New Roman bold", 30))
welcome_text.place(relx = 0,rely = 0)

htp_text = Label(page2, text="How to play?", bg = "azure3" ,font=("Times New Roman", 25))
htp_text.place(relx = 0, rely = 0.1)

instruction_text = ("1) Use sliders, checkboxes, and buttons to adjust the simulation\n" 
                    "2) Press 'Launch' button to show output values\n" 
                    "3) Press checkboxes to show values on animation window\n"
                    "4) Press 'projectile 1' and 'projectile 2' to switch between projectiles \n"
                    "5) Press 'Explanation' button to get the theory on projectile motion\n"
                    "6) Press 'Quiz' button to try a quiz on projectile motion\n"
                    "7) Press back button to close window")


htp2_text = Label(page2, text=instruction_text,font=("Times New Roman", 20),  bg = "azure3",justify='left', anchor='w')
htp2_text.place(relx = 0,rely = 0.2)


def selection_window():      
    selection_window = Toplevel()
    selection_window.title("Selection page")
    selection_window.config(width=500, height=500)
    selection_window.resizable(False, False)
    selection_window.bind("<Destroy>", enable_button)

    click_btn= PhotoImage(file="scenario 1.png")
    click_btn = click_btn.zoom(25)
    click_btn = click_btn.subsample(40)
    
    click_btn2= PhotoImage(file="scenario 2.png")
    click_btn2 = click_btn2.zoom(25) 
    click_btn2 = click_btn2.subsample(40)
    
    global button1
    button1 = Button(selection_window, image = click_btn,command=vertical_animation)
    button1.image = click_btn
    button1.grid(row=0, column=0)

    global button2
    button2 = Button(selection_window,image = click_btn2 ,command=lambda: animation_window(1))
    button2.image = click_btn2
    button2.grid(row=0, column=1)
    
    root.withdraw()

when next button is clicked, it loads for around 6 seconds] when next button is clicked, it loads for around 6 seconds

I have changed my code from PhotoImage to Image.open. I tried it on a separate file and it works perfectly fine. But when i copy it to my code it doesn't seem to work.

this is the code in the separate file

root = tk.Tk()
root.title("Image Buttons")
# Load images
scenario1_img = Image.open("scenario 1.png")
scenario1_img = ImageTk.PhotoImage(scenario1_img)
scenario2_img = Image.open("scenario 2.png")
scenario2_img = ImageTk.PhotoImage(scenario2_img)

# Create buttons with images
button1 = tk.Button(root, image=scenario1_img, command=lambda: button_clicked(1))
button2 = tk.Button(root,image=scenario2_img , command=lambda: button_clicked(2))

# Display buttons
button1.grid(row=0, column=0, padx=10, pady=10)
button2.grid(row=0, column=1, padx=10, pady=10)

root.mainloop()

then this is my code.

def enable_button(event=None):
    global next_button
    next_button["state"] = "normal"
    if 'button2' in globals() and button2.winfo_exists():
        button2["state"] = "normal"
    if 'button1' in globals() and button1.winfo_exists():
        button1["state"] = "normal"

def selection_window():
    next_button["state"] = "disabled"

    
    selection_window = Toplevel()
    selection_window.title("Selection page")
    selection_window.config(width=500, height=500)
    selection_window.resizable(False, False)
    selection_window.bind("<Destroy>", enable_button)

    scenario1_img = Image.open("scenario 1.png")
    scenario1_img = ImageTk.PhotoImage(scenario1_img)
    
    scenario2_img = Image.open("scenario 2.png")
    scenario2_img = ImageTk.PhotoImage(scenario2_img)   
    
    global button1
    
    button1= Button(selection_window, image=scenario1_img,command=vertical_animation)
    button1.grid(row=0, column=0,padx=10, pady=10)

    global button2
    
    button2= Button(selection_window,image=scenario2_img,command=lambda: animation_window(1))
    button2.grid(row=0, column=1,padx=10, pady=10)
    
    root.withdraw()

This is the result enter image description here

I am not sure if it has anything to do with the enable_button() function, but when i close the window this error shows up. enter image description here


Solution

  • Unfortunately hasn't way to do that. Creating a tkimage takes much time. And if you have a gif with hundreds of frames...

    But here are some things you maybe can considerate:

    • Think a litle, if the images and configurations will always be the same , you do not need to recreate then every time.
    • Load all images at beginning of script. So the waiting time will be at "initialization" not at "runtime".
    • Use images with the proper size to avoid using .zoom() and .subsample(). Less work, more speed.
    • In specific cases you can put all images in a generator, so it will be created only when called:
        tkimages = (tk.PhotoImage(file=img) for img in list_images_png)
        button.configure(image=next(tkimages))