I have a script where I'm going through a loop, and once a condition is met, an image is added to my Tkinter window. I'm able to add and resize the image in Tkinter, however what I want to do is replace the image (or rather, just add another image on top of the previous one), as I go through my while loop. However, while Images is redefined as 'greencar.jpg', the actual image is not posted in the window.
import tkinter as tk
from tkinter import *
from PIL import ImageTk, Image
Images=()
class Example(Frame):
global Images
def __init__(self, master, *pargs):
Frame.__init__(self, master, *pargs)
self.image = Image.open(Images)
self.img_copy= self.image.copy()
self.background_image = ImageTk.PhotoImage(self.image)
self.background = Label(self, image=self.background_image)
self.background.pack(fill=BOTH, expand=YES)
self.background.bind('<Configure>', self._resize_image)
def _resize_image(self,event):
new_width = event.width
new_height = event.height
self.image = self.img_copy.resize((new_width, new_height))
self.background_image = ImageTk.PhotoImage(self.image)
self.background.configure(image = self.background_image)
def main():
global Images
x=0
root.update_idletasks()
while x<100000:
x+=1
if x == 500:
Images='mountain.jpg'
e = Example(root)
e.pack(fill=BOTH, expand=YES)
root.update_idletasks()
if x == 5000:
Images='greencar.jpg'
e = Example(root)
e.pack(fill=BOTH, expand=YES)
root.update_idletasks()
root = tk.Tk()
root.geometry('600x600')
main()
root.mainloop()
Edit:
So trying Saad's solution works really nicely, but I tried to extend it one step further, by implementing a couple of functions and a second loop.
import tkinter as tk
from tkinter import *
from PIL import ImageTk, Image
Images= "mountain.jpg"
class Example(Frame):
def __init__(self, master, *pargs):
Frame.__init__(self, master, *pargs)
self.image = Image.open(Images)
self.img_copy= self.image.copy()
self.background_image = ImageTk.PhotoImage(self.image)
self.background = Label(self, image=self.background_image)
self.background.pack(fill=BOTH, expand=YES)
self.background.bind('<Configure>', self._resize_image)
def _resize_image(self,event):
new_width = event.width
new_height = event.height
self.image = self.img_copy.resize((new_width, new_height))
self.background_image = ImageTk.PhotoImage(self.image)
self.background.configure(image = self.background_image)
def change_image(self, file):
"""Change background image of the window."""
size = (self.winfo_width(), self.winfo_height())
self.image = Image.open(file).resize(size)
self.background_image = ImageTk.PhotoImage(self.image)
self.background.configure(image=self.background_image)
def add(x):
return x+3
def subtract(x):
return x-1
def check_image(x=0):
x=add(x)
if x >= 1000:
return # Break the loop
if x == 50:
e.change_image('mountain.jpg')
elif x == 500:
e.change_image('greencar.jpg')
print(x)
root.after(1, check_image,subtract(x))
def loop():
while True:
question=input('go again?')
if question == 'n':
break
check_image(x=0)
root = tk.Tk()
root.geometry('600x600')
e = Example(root)
e.pack(fill=BOTH, expand=YES)
loop()
root.mainloop()
So what I wanted was the option for the user to go through the loop again. So I made another function to do this. However, check_image() no longer loops. It stops at the first iteration. If I break my while in loop() (by typing 'n'), then it will go through the check_image() iterations, however, the images no longer update. In short, I seem to have broken the program again, but I don't quite understand why.
There are a couple of issues with your code that needs to be addressed.
To replace the image you have to create a new instance of PhotoImage with file='new_image.png' argument every time and then self.background.configure(image=new_image_instance). As in your main function, you are just creating a new Example widget in the same window.
I don't recommend using update_idletasks() in a while loop instead the whole while loop can be replaced by after(ms, func, *args) method of Tkinter.
def check_image(x=0):
# Break the loop
if x >= 100000: return
if x == 500:
e.change_image('mountain.jpg')
elif x == 5000:
e.change_image('greencar.jpg')
root.after(1, check_image, x+1)
I think it would be best to just create a method that will change the image of the Example widget.
def change_image(self, file):
"""Change background image of the window."""
size = (self.winfo_width(), self.winfo_height())
self.image = Image.open(file).resize(size)
self.background_image = ImageTk.PhotoImage(self.image)
self.background.configure(image=self.background_image)
Complete Code:
import tkinter as tk
from tkinter import *
from PIL import ImageTk, Image
Images= "image.png"
class Example(Frame):
def __init__(self, master, *pargs):
Frame.__init__(self, master, *pargs)
self.image = Image.open(Images)
self.img_copy= self.image.copy()
self.background_image = ImageTk.PhotoImage(self.image)
self.background = Label(self, image=self.background_image)
self.background.pack(fill=BOTH, expand=YES)
self.background.bind('<Configure>', self._resize_image)
def _resize_image(self,event):
new_width = event.width
new_height = event.height
self.image = self.img_copy.resize((new_width, new_height))
self.background_image = ImageTk.PhotoImage(self.image)
self.background.configure(image = self.background_image)
def change_image(self, file):
"""Change background image of the window."""
size = (self.winfo_width(), self.winfo_height())
self.image = Image.open(file).resize(size)
self.background_image = ImageTk.PhotoImage(self.image)
self.background.configure(image=self.background_image)
def check_image(x=0):
if x >= 100000: return # Break the loop
if x == 500:
e.change_image('mountain.jpg')
elif x == 5000:
e.change_image('greencar.jpg')
root.after(1, check_image, x+1)
root = tk.Tk()
root.geometry('600x600')
e = Example(root)
e.pack(fill=BOTH, expand=YES)
check_image()
root.mainloop()
Related
I'm quite new to Python. I'm trying to update the image periodically. I've searched around but I'm still struggling to make this work how I want. I'm just going to paste the whole .py file I have.
Right now, It seems as though it's incrementing properly. I know the __init__ function in the Window class gets run only once so it is iterating but not actually updating the ImageTk.PhotoImage object. I think it has to do with my resize_image function because in change_photo when I try to configure the label to the new image with the updated index, I just get a blank image after the allotted time.
I just think I'm not quite on the right track and need a nudge in the right direction here. Thank you
class Window(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.master = master
self.pack(fill=BOTH, expand=YES)
self.photos = getPhotos()
self.index = 0
self.image = Image.open(path + self.photos[self.index])
self.img_copy = self.image.copy()
self.imageTK = ImageTk.PhotoImage(self.image)
self.label = Label(self, image=self.imageTK)
self.label.pack(fill=BOTH, expand=YES)
self.label.bind('<Configure>', self.resize_image)
def resize_image(self, event):
orig_width = self.image.width
orig_height = self.image.height
new_width = updateWidth(orig_width, orig_height)
new_height = event.height
self.image = self.img_copy.resize((new_width, new_height))
self.imageTK = ImageTk.PhotoImage(self.image)
self.label.configure(image=self.imageTK)
def change_photo(self):
if self.index == (len(self.photos) - 1):
self.index = 0
else:
self.index += 1
self.label.configure(image=ImageTk.PhotoImage(Image.open(path + self.photos[self.index])))
root.after(1000, self.change_photo)
app = Window(root)
app.pack(fill=BOTH, expand=YES)
app.change_photo()
root.mainloop()
I need to add upload button so that I can upload the picture and display with this class. Everything working but when I am adding the upload button its giving me some error and my code is not wokring.
import tkinter as tk
from tkinter import *
from PIL import Image, ImageTk
from tkinter import filedialog
class Layout:
def __init__(self,master):
self.master = master
self.rootgeometry()
self.canvas = tk.Canvas(self.master)
self.canvas.pack()
self.background_image = Image.open(self.openfn())
self.image_copy = self.background_image.copy()
self.background = ImageTk.PhotoImage(self.background_image)
self.loadbackground()
def loadbackground(self):
self.label = tk.Label(self.canvas, image = self.background)
self.label.bind('<Configure>',self.resizeimage)
self.label.pack(fill='both', expand='yes')
def openfn(self):
filename = filedialog.askopenfilename(title='open')
return filename
def rootgeometry(self):
x=int(self.master.winfo_screenwidth()*0.7)
y=int(self.master.winfo_screenheight()*0.7)
z = str(x) +'x'+str(y)
self.master.geometry(z)
def resizeimage(self,event):
image = self.image_copy.resize((self.master.winfo_width(),self.master.winfo_height()))
self.image1 = ImageTk.PhotoImage(image)
self.label.config(image = self.image1)
root = tk.Tk()
a = Layout(root)
root.mainloop()
Create the Button widget within the class constructor and bind it with self.loadbackground. Also, you don't need to recreate the Label widget every time instead use label.configure(image=yourimage).
Here is the code:
import tkinter as tk
from tkinter import *
from PIL import Image, ImageTk
from tkinter import filedialog
class Layout:
def __init__(self,master):
self.master = master
self.rootgeometry()
self.button = Button(self.master, text='Upload', command=self.loadbackground)
self.button.pack()
self.canvas = tk.Canvas(self.master)
self.canvas.pack(fill=BOTH, expand=True)
self.background_image = None
self.image_copy = None
self.background = None
self.label = tk.Label(self.canvas)
self.label.pack(fill='both', expand=True)
def loadbackground(self):
self.background_image = Image.open(self.openfn())
self.image_copy = self.background_image.copy()
self.background = ImageTk.PhotoImage(self.background_image.resize((self.canvas.winfo_width(), self.canvas.winfo_height())))
self.label.configure(image=self.background)
self.label.bind('<Configure>',self.resizeimage)
def openfn(self):
filename = filedialog.askopenfilename(title='open')
return filename
def rootgeometry(self):
x=int(self.master.winfo_screenwidth()*0.7)
y=int(self.master.winfo_screenheight()*0.7)
z = str(x) +'x'+str(y)
self.master.geometry(z)
def resizeimage(self,event):
image = self.image_copy.resize((event.width, event.height))
self.image1 = ImageTk.PhotoImage(image)
self.label.config(image = self.image1)
root = tk.Tk()
a = Layout(root)
root.mainloop()
Based on this answer, I wanted to add a second widget below the main image.
The image is intended to fit the window when this one is resized by the user.
See the example from #Marcin:
The issue is that if I add any widget to the window, something weird is happening (here with a Text):
The image is growing progressively until it fill the entire window, and the second widget disappears.
Here is my code:
from tkinter import *
from PIL import Image, ImageTk
from io import BytesIO
import base64
root = Tk()
root.title("Title")
root.geometry("600x600")
class ImageFrame(Frame):
def __init__(self, master, *pargs):
Frame.__init__(self, master, *pargs)
self.black_pixel = BytesIO(base64.b64decode("R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="))
self.image = Image.open(self.black_pixel)
self.img_copy= self.image.copy()
self.background_image = ImageTk.PhotoImage(self.image)
self.background = Label(self, image=self.background_image)
self.background.pack(fill=BOTH, expand=YES)
self.background.bind('<Configure>', self._resize_image)
self.width = 1
self.height = 1
def _resize_image(self,event):
new_width = event.width
new_height = event.height
if new_width != self.width or new_height != self.height:
self.width = new_width
self.height = new_height
self.image = self.img_copy.resize((new_width, new_height))
self.background_image = ImageTk.PhotoImage(self.image)
self.background.configure(image = self.background_image)
img = ImageFrame(root)
txt = Text(root)
img.pack(fill=BOTH, expand=YES)
txt.pack()
root.mainloop()
I tried by packing img and txt with different options but it chnanged nothing.
Does anyone have an idea of what I am doing wrong, please?
Calling the .configure on a widget from within the <Configure> callback on same widget is a lot like recursion, there is always a risk of never ending.
When <Configure> triggers the event.width and event.height include the automatic padding of 2 pixels, so setting the image to that size increases the size of the Label by 4 pixels firing the <Configure> event again.
This doesn't happen in the example by #markus because once the Label is the size of a window with forced geometry it won't reconfigure again, this is also what happens in your program once the image has finished consuming all the space on the window.
The really quick fix is to change:
new_width = event.width
new_height = event.height
To:
new_width = event.width - 4
new_height = event.height - 4
To compensate for the spacing but I give absolutely no guarantee that it will work consistently.
Here is a different implementation that uses a Canvas instead of a Frame and Label:
class ImageFrame(Canvas):
def __init__(self, master, *pargs ,**kw):
Canvas.__init__(self, master, *pargs,**kw)
self.black_pixel = BytesIO(base64.b64decode("R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="))
self.img_copy = Image.open(self.black_pixel)
self.image = None #this is overriden every time the image is redrawn so there is no need to make it yet
self.bind("<Configure>",self._resize_image)
def _resize_image(self,event):
origin = (0,0)
size = (event.width, event.height)
if self.bbox("bg") != origin + size:
self.delete("bg")
self.image = self.img_copy.resize(size)
self.background_image = ImageTk.PhotoImage(self.image)
self.create_image(*origin,anchor="nw",image=self.background_image,tags="bg")
self.tag_lower("bg","all")
This way instead of reconfiguring the widget it just redraws the image on the canvas.
Trying to set up a background for my tkinter window. I have a square background image, which fades to black around the edges, and then the main window has a black background. The image is placed over the background, and if the window is wider than it is tall, the image centers itself in the middle over the black background, and it all looks very nice.
However when the window is smaller than the image in width and height, it puts the center of the image in the center of the window, so you don't see the whole image, and it looks a little odd. Is there a way of resizing the image so that if the largest of the width and height of the window is smaller than the image, the image is adjusted to that size, keeping aspect ratio.
So say the background image is 600x600:
In a 800x400 window, the image does not resize, and centers itself vertically.
In a 500x400 window, the image resizes to 500x500, and still centers itself vertically.
In a 400x900 window, the image does not resize, and centers itself horizontally.
The centering functionality is already there, I just need the resize functionality.
Currently what I have is:
from tkinter import *
root = Tk()
root.title("Title")
root.geometry("600x600")
root.configure(background="black")
background_image = PhotoImage(file="Background.gif")
background = Label(root, image=background_image, bd=0)
background.pack()
root.mainloop()
Not sure if there is a way of doing this in tkinter? Or if perhaps I would write my own function that resizes the image according to the window size, however the image needs to resize relatively smoothly and quickly if the user resizes the window at any point.
This is example application that uses Pillow to resize image on the Label as the label changes size:
from tkinter import *
from PIL import Image, ImageTk
root = Tk()
root.title("Title")
root.geometry("600x600")
root.configure(background="black")
class Example(Frame):
def __init__(self, master, *pargs):
Frame.__init__(self, master, *pargs)
self.image = Image.open("./resource/Background.gif")
self.img_copy= self.image.copy()
self.background_image = ImageTk.PhotoImage(self.image)
self.background = Label(self, image=self.background_image)
self.background.pack(fill=BOTH, expand=YES)
self.background.bind('<Configure>', self._resize_image)
def _resize_image(self,event):
new_width = event.width
new_height = event.height
self.image = self.img_copy.resize((new_width, new_height))
self.background_image = ImageTk.PhotoImage(self.image)
self.background.configure(image = self.background_image)
e = Example(root)
e.pack(fill=BOTH, expand=YES)
root.mainloop()
This is how it works using Lenna image as example:
I have modified the above code so it is not in a class
#!/usr/bin/python3.5
from tkinter import *
from tkinter import ttk
from PIL import Image, ImageTk
root = Tk()
root.title("Title")
root.geometry('600x600')
def resize_image(event):
new_width = event.width
new_height = event.height
image = copy_of_image.resize((new_width, new_height))
photo = ImageTk.PhotoImage(image)
label.config(image = photo)
label.image = photo #avoid garbage collection
image = Image.open('image.gif')
copy_of_image = image.copy()
photo = ImageTk.PhotoImage(image)
label = ttk.Label(root, image = photo)
label.bind('<Configure>', resize_image)
label.pack(fill=BOTH, expand = YES)
root.mainloop()
Just sugesting a slight change in the answer. Using self.master.winfo_width(),self.master.winfo_height() instead of 'event' makes he adjustment to size much quicker.
import tkinter as tk
from PIL import Image, ImageTk
class Layout:
def __init__(self,master):
self.master = master
self.rootgeometry()
self.canvas = tk.Canvas(self.master)
self.canvas.pack()
self.background_image = Image.open('image_file.PNG')
self.image_copy = self.background_image.copy()
self.background = ImageTk.PhotoImage(self.background_image)
self.loadbackground()
def loadbackground(self):
self.label = tk.Label(self.canvas, image = self.background)
self.label.bind('<Configure>',self.resizeimage)
self.label.pack(fill='both', expand='yes')
def rootgeometry(self):
x=int(self.master.winfo_screenwidth()*0.7)
y=int(self.master.winfo_screenheight()*0.7)
z = str(x) +'x'+str(y)
self.master.geometry(z)
def resizeimage(self,event):
image = self.image_copy.resize((self.master.winfo_width(),self.master.winfo_height()))
self.image1 = ImageTk.PhotoImage(image)
self.label.config(image = self.image1)
root = tk.Tk()
a = Styling.Layout(root)
root.mainloop()
i have created function for calling resize a single time with methods after et after cancel
def on_resize(self, evt):
if self.inter == 0:
self.inter = 1
self.minuteur = self.fenetrePrincipale.after(100, self.endResize)
else:
self.minuteur = self.fenetrePrincipale.after_cancel(self.minuteur)
self.minuteur = self.fenetrePrincipale.after(100, self.endResize)
def endResize(self):
self.inter = 0
self.fenetrePrincipale.background = self.fenetrePrincipale.background.resize((self.fenetrePrincipale.winfo_width(), self.fenetrePrincipale.winfo_height()))
self.pixi = ImageTk.PhotoImage(self.fenetrePrincipale.background)
self.canvas.configure(width=self.fenetrePrincipale.winfo_width(), height=self.fenetrePrincipale.winfo_height())
self.canvas.create_image(0, 0, anchor=NW, image=self.pixi)
Here is the principle, after defines a timer and a function to be recalled at the end, after_cancel cleans the timer so each iteration of the function cleans the timer and starts it, at the last iteration of resize the timer remains triggered.
for more information on cancel and timer with after:
after detailled
So, as I'm sure you can tell from my rather long subject what I'm asking but to reiterate. I am trying to have a gif animate while a thread in the background is running a calculation with in TKinter. For simplicities sake, I am using this gif: http://i.imgur.com/4Y9UJ.gif
The gif animation is currently working as you can see if you run the script. But as soon as the Generate button is pressed and the thread initiates, the gif pauses until it finishes. I'm using time.sleep() to simulate the large amount of calculations that I will be doing in the background (though I am unsure if this is what is actually causing the issue).
I'm sure it has something to do with not understanding exactly how the animation is functioning. Any advice?
Code as follows:
from Tkinter import *
import tkMessageBox
import time
import os
import threading
from PIL import Image, ImageTk
class Gif(Label):
def __init__(self, master, filename):
evanGif = Image.open(filename)
gifSeq = []
try:
while 1:
gifSeq.append(evanGif.copy())
evanGif.seek(len(gifSeq)) # skip to next frame
except EOFError:
pass # we're done
try:
#Special case for the evangalion gif
if evanGif.info['duration'] == 0:
self.delay = 100
else:
self.delay = evanGif.info['duration']
except KeyError:
self.delay = 100
gifFirst =gifSeq[0].convert('RGBA')
self.gifFrames = [ImageTk.PhotoImage(gifFirst)]
Label.__init__(self, master, image=self.gifFrames[0])
temp =gifSeq[0]
for image in gifSeq[1:]:
temp.paste(image)
frame = temp.convert('RGBA')
self.gifFrames.append(ImageTk.PhotoImage(frame))
self.gifIdx = 0
self.cancel = self.after(self.delay, self.play)
def play(self):
self.config(image=self.gifFrames[self.gifIdx])
self.gifIdx += 1
if self.gifIdx == len(self.gifFrames):
self.gifIdx = 0
self.cancel = self.after(self.delay, self.play)
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').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 generate(self):
self.hideForGen()
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!")
def hideForGen(self):
self.buttonFrame.grid_forget()
def reset(self):
self.buttonFrame.grid(row=1)
root = Tk()
root.title('Test')
root.configure(background='light gray')
app = App(root)
root.mainloop()
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)
In the method below:
def generate(self):
self.hideForGen()
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!")
If you put
self.master.update()
instead of
self.master.update_idletasks()
it should work. At least it did for me.