What's making my tkinter graphics so slow? - python

I'm trying to implement GUI editing tool by python.
I implemented preliminary code to test how work well.
However, it is too slow to use even if small sample case.
My code is below:
import numpy as np
from tkinter import *
nx=10
ny=8
world=np.zeros((ny,nx))
world[:,3]=1
world[1,:]=1
# Tkinter ========
root = Tk()
root.title("make Land/Sea mask")
root.resizable(0,0)
menu_ROOT = Menu(root)
root.configure(menu = menu_ROOT)
menu_GAME = Menu(menu_ROOT, tearoff = False)
menu_ROOT.add_cascade(label = 'Menu', under = 4, menu = menu_GAME)
###Frame objects###
root_frame = Frame(root, relief = 'groove', borderwidth = 5, bg = 'LightGray')
game_frame = Frame(root_frame, relief = 'sunken', borderwidth = 3, bg = 'LightGray')
root_frame.pack()
game_frame.pack(pady = 5, padx = 5)
def create_map(world,nx,ny):
c = 0
frame_list = []
for j in np.arange(ny):
for i in np.arange(nx):
if world[j,i]==1:
frame = Frame(game_frame, width = 20, height = 20, bd = 0.5, relief = 'groove', bg = 'yellow green')
else:
frame = Frame(game_frame, width = 20, height = 20, bd = 0.5, relief = 'ridge', bg = 'pale green')
frame.num = c
frame_list.append(frame)
frame.grid(row=j, column=i)
c += 1
create_map(world,nx,ny)
root.mainloop()
In this case, I set 10x8 matrix, but I finally will use 360x180 matrix and it may takes an enormous amount of time to draw.
Does anyone know how improve this code?

You shouldn't use Tkinter for this purpose.
Here's an example in PyGame, which in my opinion is pretty easy to learn:
import sys
import time
import pygame
import numpy as np
DISPLAY_SIZE = 840, 680
def generate_map(nx, ny):
return np.rint(np.random.rand(ny, nx))
def render_map(world, display, selected_img, unselected_img, img_size):
for j in np.arange(world.shape[0]):
for i in np.arange(world.shape[1]):
if world[j, i] == 1:
display.blit(selected_img, (i * img_size[0], j * img_size[0]))
else:
display.blit(unselected_img, (i * img_size[0], j * img_size[0]))
display = pygame.display.set_mode(DISPLAY_SIZE, pygame.HWSURFACE | pygame.DOUBLEBUF)
selected = pygame.image.load("selected_cell.png")
unselected = pygame.image.load("unselected_cell.png")
img_size = selected.get_size() # Assuming both images are the same size
clock = pygame.time.Clock()
world = generate_map(26, 21)
fps_counter_time = time.perf_counter()
while True:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit(0)
render_map(world, display, selected, unselected, img_size)
pygame.display.flip()
if (time.perf_counter() - fps_counter_time) > 0.1:
pygame.display.set_caption("FPS: %.4f" % (clock.get_fps(),))
fps_counter_time = time.perf_counter()

Related

What is the fastest Method to display an Image in a Frame?

I use 2 different Threads to Capture and to Display a Webcamstream.
I checked the FPS in the Capture and in the Display Function.
While the Capture Function can deliver full FPS, the Display Function is slower and getting slower with higher Resolutions.
Is there a faster Way to display the (now synced)Webcamstream in a TKinter Frame?
My goal would be getting synced 20 FPS in Display (and later in Record) after Resizing.
My Testprogramm:
global myfps
myfps = 0
global my_width
my_width = 640
global my_height
my_height = 480
def fakefunc():
_=7
def display_start():
print('display_start activated')
gv.test_wiedergabe = 'True'
mts = multi_thread_stream(ready)
def display_stop():
print('display_stop activated')
gv.test_wiedergabe = 'False'
def aufnahme_start():
aufnahme = 'True'
class multi_thread_stream:
def __init__(self, ready=None):
self.ready = ready
self.cap = cv2.VideoCapture(0)
self.cap.set(cv2.CAP_PROP_BUFFERSIZE, 2)
self.AIM_FPS = 1/25
self.AIM_FPS_MS = int(self.AIM_FPS * 1000)
self.cap.set(3, my_width)
self.cap.set(4, my_height)
self.frame = {}
self.fps = 0
self.wiedergabe = 'False'
#Create the Threads
self.t1 = threading.Thread(target=self.capture_stream)
self.t2 = threading.Thread(target=self.display_image)
self.t1.name = 'capture_thread'
self.t2.name = 'display_thread'
#die starts in eine funktion packen, für start und stop ?
self.t1.start()
self.t2.start()
#self.t1.join()
#self.t2.join()
self.aufnahme = 'False'
frames_per_second = 20
basepathinc = r'C:/Videotool/Videos'
isExist = os.path.exists(basepathinc)
print('path exists :',isExist)
print('video fullpath :',basepathinc)
if not isExist:
os.makedirs(basepathinc)
print(f'path {basepathinc} created')
os.chdir(basepathinc)
def set_res(key):
global my_width
global my_height
match key:
case 480:
my_width = 640
my_height = 480
print('480p selected')
case 720:
my_width = 1280
my_height = 720
print('720p selected')
case 1080:
my_width = 1920
my_height = 1080
print('1080p selected')
case 4:
my_width = 3840
my_height = 2160
print('4k selected')
case _:
print('wrong selection')
def capture_stream(self):
capture_counter = 1
old_seconds = 0
seconds = 0
while True:
self.ret, self.frame = self.cap.read()
self.ready.set()
now = datetime.now()
seconds = int(now.strftime("%S"))
#print(f'Frame: {capture_counter} in Second :{seconds}')
if seconds > old_seconds:
print(f'Captured Frames: {capture_counter-1} in Second : {old_seconds}')
capture_counter = 2
else:
capture_counter += 1
old_seconds = seconds
time.sleep(self.AIM_FPS)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
def display_image(self):
self.start_time = time.time()
self.framerate_trigger = 1
self.fps_counter = 0
display_counter = 0
old_seconds = 0
seconds = 0
while True:
# Display the resulting frame
if gv.test_wiedergabe == 'True':
self.ready.wait()
#_________________________________
now = datetime.now()
seconds = int(now.strftime("%S"))
if seconds > old_seconds:
print(f'Displayed Frames: {display_counter-1} in Second : {old_seconds}')
display_counter = 2
else:
display_counter += 1
old_seconds = seconds
#_________________________________
framexd=cv2.cvtColor(self.frame,cv2.COLOR_BGR2RGB)
self.ready.clear()
self.fps_counter+=1
if (time.time() - self.start_time) > self.framerate_trigger :
#print("FPS: ", self.fps_counter / (time.time() - self.start_time))
self.fps = round(int(self.fps_counter / (time.time() - self.start_time)))
self.fps_counter = 0
self.start_time = time.time()
global myfps
myfps = self.fps
stream_width = self.cap.get(cv2.CAP_PROP_FRAME_WIDTH)
stream_height = self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
global_cam_display_fps = int(self.cap.get(cv2.CAP_PROP_FPS))
#print('FPS:',self.fps)
fps_x_string = str(self.fps)
font = cv2.FONT_HERSHEY_TRIPLEX
fontScale = 1
fontColor = (255,0,0)
thickness = 2
lineType = 2
frame_wfps = cv2.putText(framexd,fps_x_string, (0,30), font, fontScale,fontColor,thickness,cv2.LINE_AA)
framexd=Image.fromarray(framexd)
#framexd=Image.fromarray(framexd)
framexd=ImageTk.PhotoImage(framexd)
videolabel.configure(image=framexd)
videolabel.image=framexd
infolabel.configure(text = f'myFPS: {myfps} /n Auflösung: {stream_width}x{stream_height}')
#Aufnahme________________________________________________________________________________________
"""
if self.aufnahme == 'True':
self.out.write(frame_wtext_startup)
else:
self.out.release()
"""
#________________________________________________________________________________________________
if cv2.waitKey(1) & 0xFF == ord('q'):
break;
else:
self.cap.release()
#self.t1._stop() funkst erst wenn der thread daemonisch ist! also thread daemonisch öffnen
#self.t2._stop()
break
def __del__(self):
# When everything done, release the capture
self.cap.release()
cv2.destroyAllWindows()
if __name__ == "__main__":
print('start.py was started directly')
ready = threading.Event()
ready = ready
root = Tk()
root.title('Test Videoverarbeitung')
root.geometry('800x600')
helv36 = font.Font(family='Helvetica', size=12, weight=font.BOLD)
mainframe = Frame(root)
mainframe.place(relheight = 1, relwidth = 1)
videoframe = Frame(mainframe)
videoframe.place(x = 0, y = 0, relheight = 1, relwidth = 0.8)
videolabel = Label(videoframe, text = "Videoframe")
videolabel.place(relx = 0.5, rely = 0.5, relwidth = 1, relheight = 1, anchor="center")
infolabel = Label(videoframe, text = "Videoframe")
infolabel.place(relx = 0, rely = 0.8, relwidth = 1, relheight = 0.2)
controlframe = Frame(mainframe)
controlframe.place(relx = 0.8, y = 0, relheight = 1, relwidth = 0.2)
button1 = Button(controlframe, width=50, text = "Wiedergabe", command=lambda:display_start(), bd = 2, relief = "groove", overrelief = "sunken", font = helv36)
button1.place(relx = 0, rely = 0, relheight=0.2, relwidth = 1)
button2 = Button(controlframe, width=50, text = "Wiedergabe Stop", command=lambda:display_stop(), bd = 2, relief = "groove", overrelief = "sunken", font = helv36)
button2.place(relx = 0, rely =0.2, relheight=0.2, relwidth = 1)
button3 = Button(controlframe, width=50, text = "Aufnahme", command=lambda:aufnahme_start(), bd = 2, relief = "groove", overrelief = "sunken", font = helv36)
button3.place(relx = 0, rely =0.4, relheight=0.2, relwidth = 1)
button3 = Button(controlframe, width=50, text = "Aufnahme Stop (oF)", command=lambda:fakefunc(), bd = 2, relief = "groove", overrelief = "sunken", font = helv36)
button3.place(relx = 0, rely =0.6, relheight=0.2, relwidth = 1)
checkbutton1 = Checkbutton(controlframe, text = '480p',command=lambda:multi_thread_stream.set_res(480))
checkbutton1.place(relx = 0, rely = 0.8)
checkbutton1 = Checkbutton(controlframe, text = '720p',command=lambda:multi_thread_stream.set_res(720))
checkbutton1.place(relx = 0, rely = 0.84)
checkbutton1 = Checkbutton(controlframe, text = '1080p',command=lambda:multi_thread_stream.set_res(1080))
checkbutton1.place(relx = 0, rely = 0.88)
checkbutton1 = Checkbutton(controlframe, text = '4k',command=lambda:multi_thread_stream.set_res(4))
checkbutton1.place(relx = 0, rely = 0.92)
root.mainloop()
else:
_=7
I am through many Threads about this, but didnt find a Solution.
I created this as my minimal Code Example, to prevent the "use Threads" Suggestion.
But to cut out the Part I would like to improve:
framexd=cv2.cvtColor(self.frame,cv2.COLOR_BGR2RGB)
self.ready.clear()
framexd=Image.fromarray(framexd)
framexd=ImageTk.PhotoImage(framexd)
videolabel.configure(image=framexd)
videolabel.image=framexd
This Part is working up to 50% slower then the Capture.
I would like to speed it up with other Ideas like Reducing Resolution and the Use of better Hardware.
Next Time I will avoid the bizarre Capitalizations in my Code.

Switching between frames in tkinter, clock app

Good day, guys. I've got a problem with switching between frames in tkinter. I'm trying to make a clock app with time, stopwatch, timer and alarm. If I click on the first frame it's working, but if I select the second frame it's breaks. The previous frame just don't open because the second frame refreshing too fast (lbl.after). Can you help me with switching between frames?
from tkinter import *
from tkmacosx import *
from time import strftime
root = Tk()
root.title('Time')
root.geometry('500x500')
root.resizable(False, False)
counter = -1
running = False
def time_frame():
string = strftime("%H:%M:%S %A %d %Y")
frame_time = Frame(root, width = 480, height = 410, bg = 'gray70')
frame_time.place(x = 10, y = 80)
lbl1 = Label(frame_time, text = list, width = 32, height = 10, font = ('Arial', 25))
lbl1.place(x = 10, y = 80)
lbl1.config(text=string)
lbl1.after(1000, time_frame)
def stopwatch_frame():
frame_stopwatch = Frame(root, width = 480, height = 410, bg = 'gray70')
frame_stopwatch.place(x = 10, y = 80)
lbl2 = Label(frame_stopwatch, text = '0', width = 25, height = 5, font = ('Arial', 25))
lbl2.place(x = 10, y = 80)
start_button = Button(frame_stopwatch, width = 80, height = 40, text = 'Start')
start_button.place(x = 10, y = 240)
stop_button = Button(frame_stopwatch, width = 80, height = 40, text = 'Stop')
stop_button.place(x = 100, y = 240)
reset_button = Button(frame_stopwatch, width = 80, height = 40, text = 'Reset')
reset_button.place(x = 190, y = 240)
lbl2.after(1, stopwatch_frame)
frame_top = Frame(root, width = 500, height = 70, bg = 'gray64')
frame_top.place(x = 0, y = 0)
time_btn = Button(frame_top, text = 'Time', width = 50, height = 50, command = time_frame)
time_btn.place(x = 10, y = 10)
stopwatch_btn = Button(frame_top, text = 'Stopwatch', width = 50, height = 50, font = ('Arial', 9), command = stopwatch_frame)
stopwatch_btn.place(x = 80, y = 10)
timer_btn = Button(frame_top, text = 'Timer', width = 50, height = 50)
timer_btn.place(x = 150, y = 10)
alarm_btn = Button(frame_top, text = 'Alarm', width = 50, height = 50)
alarm_btn.place(x = 220, y = 10)
root.mainloop()
Here is a simplified example of a clock and stop watch class.
import tkinter as tk
from tkmacosx import *
from time import strftime, process_time
class timer:
delaya = None
delayb = None
def __init__( self ):
self.root = tk.Tk()
self.root.title('Time')
self.buttona = tk.Button( self.root, text = 'clock', width = 40, command = self.setclock )
self.buttonb = tk.Button( self.root, text = 'watch', width = 40, command = self.setwatch )
self.buttona.grid( row=0, column=0, sticky='ew' )
self.buttonb.grid( row=0, column=1, sticky='ew' )
self.root.update_idletasks()
self.root.resizable( False, False )
def clock( self ):
self.root.after_cancel( self.delaya )
self.delaya = None
self.buttona[ 'text' ] = strftime("%H:%M:%S %A %d %Y")
self.delaya = self.root.after( 1000, self.clock )
def watch( self ):
self.root.after_cancel( self.delayb )
self.delayb = None
self.buttonb[ 'text' ] = f'{process_time():0.5f}'
self.delayb = self.root.after( 1, self.watch )
def setclock( self ):
if self.delaya == None:
self.delaya = self.root.after( 1000, self.clock )
else:
self.root.after_cancel( self.delaya )
self.delaya = None
self.buttona[ 'text' ] = 'clock'
def setwatch( self ):
if self.delayb == None:
self.delayb = self.root.after( 1, self.watch )
else:
self.root.after_cancel( self.delayb )
self.delayb = None
self.buttonb[ 'text' ] = 'watch'
if __name__ == '__main__':
device = timer( )
tk.mainloop()

Tkinter not letting me insert a text in a Text Widget

from tkinter import *
from PIL import Image, ImageTk
import time
schermata = Tk()
screen_width = schermata.winfo_screenwidth()
screen_height = schermata.winfo_screenheight()
indice = 0
schermata.iconbitmap("immagini\icona.ico")
screen_resolution = str(screen_width)+'x'+str(screen_height)
large_font = ('Verdana',30)
schermata.geometry(screen_resolution)
schermata.title("Jovan's RPG")
class GUI(Frame):
def __init__(self, master):
super(GUI, self).__init__(master)
self.pack()
self.bg()
self.immagine()
self.testo()
self.statistiche()
self.inserimenti()
def bg(self):
load = Image.open("immagini\\background.png")
render = ImageTk.PhotoImage(load)
img = Label(schermata, image = render)
img.image = render
img.pack()
def immagine(self):
load = Image.open("immagini\\dn.png")
render = ImageTk.PhotoImage(load)
img = Label(schermata, image = render)
img.image = render
img.place( x = 10, y = 10 )
def testo(self):
self.testo = Text(schermata, width = 110, height = 35, border = 5, bg = "black", fg ="white")
self.testo.place( x = 400, y = 20 )
def statistiche(self):
self.stats = Text(schermata, width = 40, height = 10, border = 5, bg = "black", fg ="white")
self.stats.place( x = 10, y = (screen_height - 200))
def inserisci(self):
fraseInserita = self.inserimento.get()
scrivere(fraseInserita)
self.inserimento.delete('0', END)
def inserimenti(self):
self.inserimento = Entry(schermata,font=large_font, width = 25, border = 5, bg = "black", fg ="white")
self.inserimento.place( x = 400, y = (screen_height - 100))
self.bottone = Button(schermata, width = 30, height = 3, border = 5, text = "Inserisci", command = self.inserisci)
self.bottone.place( x = (screen_width - 300), y = (screen_height - 100))
g = GUI(schermata)
def scrivere(scrittura):
g.testo.insert('1.0', scrittura)
def cancellaTesti():
g.testo.delete('0',END)
def wait(secondi):
time.sleep(secondi)
Levels class
from GUI import *
g = GUI(schermata)
class Livelli():
def __init__(self): pass
def cicloLivelli(self):
self.presentazione()
def presentazione(self):
scrivere("Salve avventuriero, qual e' il tuo nome?")
Main
from GUI import *
a = GUI(schermata)
l = Livelli()
if __name__ == "__main__":
a.mainloop()
l.cicloLivelli()
As you see i called the function back[(scrivere)], but the interpreter won't let the string appear in the Text widget. I've just posted the class of the GUI and the class of the "levels" that i'm looking forward to use for creating, of course, my levels for the game i'm creating. I'm searching for an answer and can't find it, hope you guys can help.
The get something on the screen you need to include self.config(width=700, heigh=800) (width and height totally arbitrary :)!) before self.pack() in class GUI and change all schermata into self (as you have defined the instance of GUI as the master frame).
I made the program put something on screen with the version below and I had to define some variables like screen_height, screen_width just so to prove the concept.
I also defined the method scrivere. Anyway it is rendering something so hopefully you can proceed. Good luck.
import tkinter as tk
class GUI(tk.Frame):
def __init__(self):
super(GUI, self).__init__()
self.config(width=700, height=500)
self.pack()
# self.bg()
# self.immagine()
self.testo()
self.statistiche()
self.inserimenti()
def bg(self):
load = Image.open("immagini\\background.png")
render = ImageTk.PhotoImage(load)
img = Label(self, image = render)
img.image = render
img.pack()
def immagine(self):
load = Image.open("immagini\\dn.png")
render = ImageTk.PhotoImage(load)
img = Label(self, image = render)
img.image = render
img.place( x = 10, y = 10 )
def testo(self):
self.testo = tk.Text(self, width = 110, height = 35, border = 5, bg = "black", fg ="white")
self.testo.place( x = 400, y = 20 )
def statistiche(self):
screen_height = 400
self.stats = tk.Text(self, width = 40, height = 10, border = 5, bg = "black", fg ="white")
self.stats.place( x = 10, y = (screen_height - 200))
def inserisci(self):
fraseInserita = self.inserimento.get()
self.scrivere(fraseInserita)
self.inserimento.delete('0', 'end')
def inserimenti(self):
large_font = ('calibri', 12)
screen_height = 400
screen_width = 600
self.inserimento = tk.Entry(self,font=large_font, width = 25, border = 5, bg = "black", fg ="white")
self.inserimento.place( x = 400, y = (screen_height - 100))
self.bottone = tk.Button(self, width = 30, height = 3, border = 5, text = "Inserisci", command = self.inserisci)
self.bottone.place( x = (screen_width - 300), y = (screen_height - 100))
def scrivere(self, frase):
print(' you need to define this function when button is pressed')
class Livelli():
def __init__(self):
pass
def cicloLivelli(self):
self.presentazione()
def presentazione(self):
print("Salve avventuriero, qual e' il tuo nome?")
if __name__ == "__main__":
a = GUI()
l = Livelli()
l.cicloLivelli()
a.mainloop()

How to move Tkinter widget precisely. Place method not working

Here's the code so far:
# -*- coding: utf-8 -*-
from Tkinter import *
#Creates game window
master = Tk()
master.geometry("640x480")
master.resizable(width = False, height = False)
master.title("YeeHaw Poker")
#Divides window into subsections
menuFrame = Frame(master, bg = "black", height = 60)
menuFrame.pack(fill = X, side = TOP)
tableFrame = Frame(master, highlightbackground = "black", highlightthickness = 4)
tableFrame.pack(fill = BOTH, expand = True)
optionsFrame = Frame(master, bg = "black", height = 100)
optionsFrame.pack(fill = X, side = BOTTOM)
#Draws poker table decorations
tableDecorations = Canvas(tableFrame, bg = "#771427", highlightthickness = 0)
tableDecorations.pack(fill = BOTH, expand = True)
#Renders window thus far so that dimensions can be found
master.update()
tWidth = tableDecorations.winfo_width()
tHeight = tableDecorations.winfo_height()
#Main edge
gap = 10
tableDecorations.create_rectangle(gap, gap, tWidth - gap, tHeight - gap, fill ="#277714", width = 4)
#Table outline
gap = 30
tableDecorations.create_rectangle(gap, gap, tWidth - gap, tHeight - gap, outline = "#35a31b", width = 2)
#Card outline coordinates
cardNum = 5
cardSize = 20
cardHeight = cardSize * 3.5
cardWidth = cardSize * 2.5
cardSpace = 10
cardTop = tHeight / 4
cardLeft = (tWidth - (cardNum * (cardWidth + cardSpace))) / 2
cardY1 = cardTop + cardHeight
cardY2 = cardTop
cardX1 = [0 for i in range(0, cardNum)]
cardX2 = [0 for i in range(0, cardNum)]
suit = [0 for i in range(0, cardNum)]
for i in range(0, cardNum):
cardX1[i] = cardLeft + (i * (cardWidth + cardSpace))
cardX2[i] = cardX1[i] + cardWidth
suit[i] = Label(tableDecorations, text = "", bg = "white", font = (None, 50))
suit[i].place(x = 5000, y = 5000)
#Draws specified card in specified place
def drawCard(pos, type, pip):
if type == "empty":
tableDecorations.create_rectangle(cardX1[pos], cardY1, cardX2[pos], cardY2, outline = "#35a31b", width = 2)
suit[pos].pack_forget()
else:
tableDecorations.create_rectangle(cardX1[pos], cardY1, cardX2[pos], cardY2, fill = "white", outline = "grey", width = 1)
if type == "diamond":
suit[pos].config(text = "♦", fg = "red")
elif type == "heart":
suit[pos].config(text = "♥", fg = "red")
elif type == "spade":
suit[pos].config(text = "♠", fg = "black")
elif type == "club":
suit[pos].config(text = "♣", fg = "black")
suit[pos].pack()
#Creates new table
def newTable():
for i in range(0, cardNum):
drawCard(i, "diamond", 0)
newTable()
master.mainloop()
However this doesn't move the labels with the diamonds in at all, as shown here:
It's infuriating...
I'm wanting the diamond to appear on each individual card, but clearly that's not happening here...
Any ideas?

Tkinter: Can't add more than 3 PhotoImages to a list for animation?

I'm trying to program a game in Tkinter (trust that I'd much rather use another better-suited library), and I'm having trouble coding in animations. I tried 2 ways of working around the problem but both have failed.
What I've done is to write an Animation class, which, in my first implementation, takes a list of PhotoImages, and deletes/draws each frame:
class Animation():
def __init__(self, container, pos, images, framerate):
self.container = container
self.x = pos[0]
self.y = pos[1]
self.images = copy(images)
self.imagescopy = copy(images)
self.framerate = framerate # ms between frames
self.done = False
def animate(self):
if len(self.images)>0:
self.container.delete("currentframe")
self.container.create_image(self.x, self.y, image = self.images.pop(0), tag = "currentframe")
self.animation = self.container.after(self.framerate, self.animate)
else:
self.container.delete("currentframe")
self.container.after_cancel(self.animation)
self.done = True
In the init method of the class which draws the Tk window, I loaded the PhotoImages (I tried this with a simple placeholder 5 frame animation):
for i in range(1,6):
self.imgs_playbgready.append(PhotoImage(file ='Graphics\\PlayArea\\ani_bgready'+str(i)+'.gif'))
But when I try to execute the program, it takes very very long to load beyond the third image. Even writing out each line explicitly like so:
self.imgs_playbgready = [PhotoImage(file = 'Graphics\\PlayArea\\ani_bgready1.gif'),
PhotoImage(file = 'Graphics\\PlayArea\\ani_bgready2.gif'),
PhotoImage(file = 'Graphics\\PlayArea\\ani_bgready3.gif'),
PhotoImage(file = 'Graphics\\PlayArea\\ani_bgready4.gif'),
PhotoImage(file = 'Graphics\\PlayArea\\ani_bgready5.gif') ]
didn't help. Removing the last 2 items seems to make everything fine, so it looks like there's a limit on 3 PhotoImages in a list though I can't imagine why that'd be the case?
In my second implementation, I tried to change the animate method so that the PhotoImages would be 'lazily loaded' (so now self.images is a list of file names rather than PhotoImages):
def animate(self):
if len(self.images)>0:
self.container.delete("currentframe")
image = PhotoImage(file = self.images.pop(0))
self.container.create_image(self.x, self.y, image = image, tag = "currentframe")
self.animation = self.container.after(self.framerate, self.animate)
else:
self.container.delete("currentframe")
self.container.after_cancel(self.animation)
self.done = True
Now when I try to run the program it freezes at frame 4 and I have to force quit. Again, it seems 3 is the magic number?
I'm not really sure at all what's going on with this. From googling about animation in Tkinter, redrawing PhotoImages frame by frame seems the way to go. Please be patient with me because I only started learning to use the Tkinter library last week. Any help is much appreciated.
EDIT
Thanks for your help.
To clarify the points raised in the comment (full working code at the end):
It takes a few minutes before the last two images load in and the program starts up. My framerate as defined in the Animation class is more like an inverse framerate (number of miliseconds between each redraw) as you can see in the animate function. My whole code is really quite long so I will try to extract the more relevant parts (for the first implementation I tried). Basically in my GUI class (named PlayArea), under the __init__ method, I try to load in the image list for the animation as well as create an Animation instance:
class PlayArea():
def __init__(self):
self.window = Tk()
.....
#---------------------
# Button and background images
#---------------------
......
self.imgs_playbgready = [PhotoImage(file = 'Graphics\\PlayArea\\ani_bgready1.gif'),
PhotoImage(file = 'Graphics\\PlayArea\\ani_bgready2.gif'),
PhotoImage(file = 'Graphics\\PlayArea\\ani_bgready3.gif'),
PhotoImage(file = 'Graphics\\PlayArea\\ani_bgready4.gif'),
PhotoImage(file = 'Graphics\\PlayArea\\ani_bgready5.gif') ]
.......
self.ani_playbgready = Animation(self.playCanvas,(self.playWidth/2, self.playHeight/2),self.imgs_playbgready, 5000)
The animation will only run after a user clicks a button, and the command callback for the button is this(I've put in only the relevant portions):
startGame(self):
# Play Area
self.playCanvas.delete("bgready")
self.ani_playbgready.animate() # animating the images
I know for sure that the program is freezing up after loading the third PhotoImage, because when I try to do the loading using a for loop, and print the list after each loop, I can see it hang up in the fourth one. As mentioned, this code works as long as I only load 3 images into the list. In other words, it probably isn't a problem with my animate method
The monstrous block of working code as requested:
from Tkinter import *
import tkFont
from PIL import Image, ImageTk
from copy import deepcopy, copy
#------------------------------------------------
# Player, Controller, Levels, and Customer
#------------------------------------------------
class Player():
'''
Player class to keep track of progress of specific player.
Progress and highscore will be written to a save file
'''
def __init__(self, name = "Default", highscore = 0, currentlevel = 1):
self.name = name
self.highscore = highscore
self.currentlevel = currentlevel
class Controller():
'''
Controller which keeps track of score as well as the timer.
To be instantiated by the GUI class to determine the score and time left to display
'''
def __init__(self):
self.score = 0
self.level = 1
self.timer = 10
def runTimer(self):
if self.timer > 0:
self.timer -= 1
else:
self.timer = 10
class Levels():
'''
Contains the commands(tasks) for each level, as well as the order of customers and the
corresponding answers.
To be instantiated by the GUI class to determine the current command and customer to display
'''
def __init__(self):
#--------------------------------------------------------------
# Initialize commands, anwers and customers for each level (hardcode for now, may be a way to
# randomize or dynamically generate challenges depending on the level design)
# Customers can have more than one attribute, so we store it as a tuple
# Answers for each customer can be more than one pizza (eg. for a while loop), so store it as a tuple
# If the statement doesn't execute, correct answer is to move on to the next customer
#---------------------------------------------------------------
# Level 1
# Action images
self.img_pepperoni = (PhotoImage(file = 'Graphics\\Actions\\pepperoniup.gif'),
PhotoImage(file = 'Graphics\\Actions\\pepperonihover.gif'),
PhotoImage(file = 'Graphics\\Actions\\pepperonidown.gif'))
self.img_hawaiian = (PhotoImage(file = 'Graphics\\Actions\\hawaiianup.gif'),
PhotoImage(file = 'Graphics\\Actions\\hawaiianhover.gif'),
PhotoImage(file = 'Graphics\\Actions\\hawaiiandown.gif'))
self.img_vegetarian = (PhotoImage(file = 'Graphics\\Actions\\vegetarianup.gif'),
PhotoImage(file = 'Graphics\\Actions\\vegetarianhover.gif'),
PhotoImage(file = 'Graphics\\Actions\\vegetariandown.gif'))
self.img_nextcustomer = (PhotoImage(file = 'Graphics\\Actions\\nextcustomerup.gif'),
PhotoImage(file = 'Graphics\\Actions\\nextcustomerhover.gif'),
PhotoImage(file = 'Graphics\\Actions\\nextcustomerdown.gif'))
level1commands = ['if customer_is_red:\n\tserve pepperoni', 'if customer_is_blue:\n\tserve hawaiian']
level1customers = [('red'), ('green')] #make this a dict corresponding to image too?
level1answers = [('pepperoni'), ('next')]
level1actions = {'pepperoni': self.img_pepperoni , 'hawaiian':self.img_hawaiian, 'vegetarian': self.img_vegetarian,
'next': self.img_nextcustomer}
self.levelscommands = [level1commands]
self.levelscustomers = [level1customers]
self.levelsanswers = [level1answers]
self.levelsactions = [level1actions]
class Customer():
def __init__(self, **attributes):
for k,v in attributes:
self.k = v
#--------------------------------
# Animations
#--------------------------------
class Animation():
def __init__(self, container, pos, images, framerate):
self.container = container
self.x = pos[0]
self.y = pos[1]
self.images = copy(images)
self.imagescopy = copy(images)
self.framerate = framerate # ms between frames
self.done = False
def animate(self):
if len(self.images)>0:
print "animating", len(self.images), self.images
self.container.delete("currentframe") # won't throw an error if non-existant
print self.images[0], "image name"
image = PhotoImage(file = 'Graphics\\PlayArea\\ani_bgready1.gif')
print image, "image"
self.container.create_image(self.x, self.y, image = image, tag = "currentframe")
self.animation = self.container.after(self.framerate, self.animate)
else:
print "finished"
self.container.delete("currentframe")
self.container.after_cancel(self.animation)
self.done = True
def resetAnimation(self):
self.done = False
self.images = copy(self.imagescopy)
#-------------------------
# GUI classes
#-------------------------
class PlayArea():
def __init__(self):
self.window = Tk()
self.window.title("Pizza Program!")
#------------------------------------------------------------
# Initialize player, controller, levels and customer classes
#------------------------------------------------------------
self.player = Player()
self.controller = Controller()
self.levels = Levels()
self.customer = Customer()
#---------------------
# Button and background images
#---------------------
self.img_buttonstart = (PhotoImage(file = 'Graphics\\Controller\\button_startup.gif'),
PhotoImage(file = 'Graphics\\Controller\\button_starthover.gif'),
PhotoImage(file = 'Graphics\\Controller\\button_startdown.gif'))
self.img_buttonpause = (PhotoImage(file = 'Graphics\\Controller\\button_pauseup.gif'),
PhotoImage(file = 'Graphics\\Controller\\button_pausehover.gif'),
PhotoImage(file = 'Graphics\\Controller\\button_pausedown.gif'))
self.img_buttonresume = (PhotoImage(file = 'Graphics\\Controller\\button_resumeup.gif'),
PhotoImage(file = 'Graphics\\Controller\\button_resumehover.gif'),
PhotoImage(file = 'Graphics\\Controller\\button_resumedown.gif'))
self.img_playbgplay = Image.open("Graphics\\PlayArea\\bgplay.gif")
self.img_playbgready = Image.open("Graphics\\PlayArea\\bgready.gif")
# self.imgs_playbgready = [PhotoImage(file = 'Graphics\\PlayArea\\ani_bgready1.gif'),
# PhotoImage(file = 'Graphics\\PlayArea\\ani_bgready2.gif'),
# PhotoImage(file = 'Graphics\\PlayArea\\ani_bgready3.gif'),
# PhotoImage(file = 'Graphics\\PlayArea\\ani_bgready4.gif'),
# PhotoImage(file = 'Graphics\\PlayArea\\ani_bgready5.gif') ]
#-----------------------
# Animations
#-----------------------
self.imgs_playbgready = []
self.imgs_startserving = []
for i in range(1,4):# change later
self.imgs_playbgready.append('Graphics\\PlayArea\\ani_bgready'+str(i)+'.gif')
self.imgs_startserving.append('Graphics\\PlayArea\\ani_startserving'+str(i)+'.gif')
#self.imgs_playbgready = []
#for filename in imgs_playbgready:
# self.imgs_playbgready.append(PhotoImage(file = filename))
#--------------
# Font styles
#--------------
self.controlFont = tkFont.Font(family = "Calibri", size = 10, weight = "bold" )
self.commandFont = tkFont.Font(family = "Courier New", size = 14, weight = "bold")
#-------------------------------------------------------------------------------------------
# Frames to contain the play area (graphics), the command area (what players must do),
# the controller area (start button, timer, score), and action area (possible actions players can take)
#-------------------------------------------------------------------------------------------
self.playFrame = Frame(self.window, bd = 0, highlightthickness = 0)
self.commandFrame = Frame(self.window, bd =0, highlightthickness =0)
self.controlFrame = Frame(self.window, bd =0, highlightthickness =0)
self.actionsFrame = Frame(self.window, bd =0, highlightthickness =0)
self.commandFrame.grid(column = 1, row = 1)
self.playFrame.grid(column = 1, row = 2)
self.controlFrame.grid(column = 2, row = 1)
self.actionsFrame.grid(column = 2, row = 2)
self.actionsFrame.pack_propagate(False)
#self.actionsFrame.columnconfigure(1,weight=1)
#self.window.columnconfigure(2, weight =1)
#-----------------------------------
# Play Area Elements
#-----------------------------------
self.playWidth = 500
self.playHeight = 500
self.playCanvas = Canvas(self.playFrame, width = self.playWidth, height = self.playHeight, bd = -2)
self.playCanvas.pack()
resized = self.img_playbgplay.resize((self.playWidth, self.playHeight), Image.ANTIALIAS)
self.img_playbgplay = ImageTk.PhotoImage(resized)
resized = self.img_playbgready.resize((self.playWidth, self.playHeight), Image.ANTIALIAS)
self.img_playbgready = ImageTk.PhotoImage(resized)
self.playCanvas.create_image(self.playWidth/2, self.playHeight/2, image = self.img_playbgplay)
self.playCanvas.create_image(self.playWidth/2, self.playHeight/2, image = self.img_playbgready,tag = "bgready")
# Bgready animation for when start is pressed
self.ani_playbgready = Animation(self.playCanvas,(self.playWidth/2, self.playHeight/2),self.imgs_playbgready, 5000)
self.ani_startserving = Animation(self.playCanvas,(self.playWidth/2, self.playHeight/2), self.imgs_playbgready,5000)
# Controller elements
# timer and score labels only appear after game starts
self.controlColor = "sky blue"
self.controlWidth = 200
self.controlHeight = 200
self.controllerCanvas = Canvas(self.controlFrame, width = self.controlWidth, height = self.controlHeight, bg = self.controlColor,
bd =0 , highlightthickness =0)
# Frame for containing button, timer and score
self.controlLabelFrame = LabelFrame(self.controllerCanvas, bd = 0, highlightthickness = 0, relief = "ridge", bg = self.controlColor)
#print self.controlLabelFrame.config(), "configs for controlLabelFrame"
self.btStart = Button(self.controlLabelFrame, command = self.startGame, highlightthickness =0, bd =0,
bg = self.controlColor, image = self.img_buttonstart[0])
self.btPause = Button(self.controlLabelFrame, command = self.pauseGame, highlightthickness =0, bd =0,
bg = self.controlColor, image = self.img_buttonpause[0])
self.btResume = Button(self.controlLabelFrame, command = self.resumeGame, highlightthickness =0, bd =0,
bg = self.controlColor, image = self.img_buttonstart[0])
self.controllerCanvas.pack()
self.btStart.grid(row=1, pady = 5)
self.controllerCanvas.create_window(self.controlWidth/2, self.controlHeight/2, window = self.controlLabelFrame)
#Toggle button image
self.changeButtonImage(self.btStart, self.img_buttonstart)
# Timer label
self.timerText = StringVar()
self.timerLabel = Label(self.controlLabelFrame, textvariable = self.timerText, bg = self.controlColor, font = self.controlFont)
self.timerLabel.grid( row = 2, pady=5)
# Level label
self.levelText = StringVar()
self.levelLabel = Label(self.controlLabelFrame, textvariable = self.levelText, bg = self.controlColor, font = self.controlFont)
self.levelLabel.grid( row = 3, pady=5)
# Command elements
self.commandColor = "light sky blue"
self.commandWidth = 500
self.commandHeight = 200
self.commandCanvas = Canvas(self.commandFrame, width = self.commandWidth, height = self.commandHeight, bg = self.commandColor)
self.commandCanvas.pack()
self.commText = StringVar()
self.commLabel = Label(self.commandCanvas, textvariable = self.commText, bg = self.commandColor, font = self.commandFont)
self.commandCanvas.create_window(self.commandWidth/2, self.commandHeight/2, window = self.commLabel)
# Action elements
self.actionColor = "cornflower blue"
self.actionWidth = 200
self.actionHeight = 500
self.actionsFrame.config(width = self.actionWidth, height = self.actionHeight, bg = self.actionColor,)
#self.actionCanvas = Canvas(self.actionsFrame, width = self.actionWidth, height = self.actionHeight, bg = self.actionColor, highlightthickness =0)
#self.actionCanvas.pack()
self.window.mainloop()
#-------------------------------------
# Step methods
# (for checking conditions each step)
#-------------------------------------
def checkAnimation(self, animation, triggerevent, *args):
if not animation.done:
self.window.after(100, self.checkAnimation)
else:
triggerevent(args)
#-------------------------------------
# Controller methods
#-------------------------------------
def changeButtonImage(self, button, images):
def toImage(event, button, image):
button["image"] = image
up, hover, down = images
button.bind("<Enter>", lambda event, button = button, image = hover : toImage(event, button, image))
button.bind("<Leave>", lambda event, button = button, image = up : toImage(event, button, image))
button.bind("<Button-1>", lambda event, button = button, image = down : toImage(event, button, image))
button.bind("<ButtonRelease-1>", lambda event, button = button, image = up : toImage(event, button, image))
def updateController(self):
self.timerText.set("Next Customer: " + str(self.controller.timer))
self.controller.runTimer()
self.update = self.controlFrame.after(1000, self.updateController)
def startGame(self):
#--------------------------
# Update controller elements
#-----------------------------
# Change button
self.btStart.grid_remove()
self.btPause.grid(row=1, pady = 5)
self.changeButtonImage(self.btPause, self.img_buttonpause)
# Timer label
self.timerText.set("Next Customer:" + str(self.controller.timer))
# Level label
self.levelText.set("Level: " + str(self.controller.level))
self.updateController()
#--------------------------------
# Access level commands, customers, answers, and set command display
#--------------------------------
# Commands
self.levelcommands = deepcopy(self.levels.levelscommands[self.controller.level-1]) #copy the list
self.currentcommand = self.levelcommands.pop(0)
self.displayCommand(self.currentcommand)
# Actions
self.levelactions = copy(self.levels.levelsactions[self.controller.level-1])
self.btActions = []
for action, img in self.levelactions.iteritems():
if action != "next":
self.btActions.append(Button(self.actionsFrame, command = self.servePizza,
highlightthickness =0, bd =0, bg = self.actionColor, image = img[0]))
else:
self.btActions.append(Button(self.actionsFrame, command = self.nextCustomer,
highlightthickness =0, bd =0, bg = self.actionColor, image= img[0]))
index = self.btActions.index(self.btActions[-1]) +1
self.btActions[-1].pack(pady = 35)
self.changeButtonImage(self.btActions[-1], img)
# Play Area
self.playCanvas.delete("bgready")
self.ani_playbgready.animate()
#self.checkAnimation(self.ani_playbgready, self.ani_startserving.animate)
self.levelcustomers = deepcopy(self.levels.levelscustomers[self.controller.level-1]) #copy the list
self.currentcustomer = self.levelcustomers.pop(0)
#self.displayCustomer(self.currentcustomer) #need to write this method
# Answers
self.levelanswers = self.levels.levelsanswers[self.controller.level-1][:] # no nested elements
self.currentanswer = self.levelanswers.pop(0)
def pauseGame(self):
#Pause controller updating
# Change button
self.btPause.grid_remove()
self.btResume.grid(row=1, pady = 5)
self.changeButtonImage(self.btResume, self.img_buttonresume)
self.controlFrame.after_cancel(self.update)
def resumeGame(self):
# Resume controller updating
# Change button
self.btResume.grid_remove()
self.btPause.grid(row=1, pady = 5)
self.changeButtonImage(self.btResume, self.img_buttonpause)
self.updateController()
def readyLevel(self):
pass
#---------------------------------
# Command methods
#---------------------------------
def updateCommand(self):
pass
def displayCommand(self, command):
self.commText.set(command)
#--------------------------------
# Action methods
#--------------------------------
def servePizza(self):
pass
def nextCustomer(self):
pass
PlayArea()

Categories