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.
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 want to make a program that begins as a small window, then when given a path to an image, it maximises the screen and places the image in the centre.
If you run the code below you will see that the window maximises, the image is loaded into memory, the code runs with no errors and self.open_image calls self.draw_image(self.pimg) which runs without error, however the image is not present on the canvas.
If I click the button "Fix" and call self.fix it calls self.draw_image(self.pimg) which runs without error and correctly draws the image.
How can you call the same function twice with the same arguments and get different results. What is different.
I get the feeling this is happening because something has taken place in the main loop that hasn't taken place at the end of self.__init__, so that when i call self.draw_image the second time self.cv.create_image is able to interact with something in the resizable canvas.
In this example I am happy to assume the program will always begin as a small window and become a maximised window untill it is closed, never being resized again, however in my real program I would like to make it more dynamic where the window handles resizing sensibly, this is just a minimum reproducible example. It is for this reason that I would like to use the ResizingCanvas class (or one like it) even though I feel that it is likely the cause of the issue I am experiencing.
I have tried using breakpoints and stepping through the code watching the variables get created but I cant see the difference between the self.cv the first time around and self.cv after I click the button.
I read about a similar issue here on this question and he suggests binding "<Configure>" To the canvas and passing the coords from the event to the canvas. However this has already been implemented in ResizingCanvas
from tkinter import *
from PIL import Image, ImageTk
class ResizingCanvas(Canvas):
# https://stackoverflow.com/a/22837522/992644
def __init__(self,parent,**kwargs):
Canvas.__init__(self,parent,**kwargs)
self.bind("<Configure>", self.on_resize)
self.height = self.winfo_reqheight()
self.width = self.winfo_reqwidth()
def on_resize(self,event):
""" determine the ratio of old width/height to new width/height"""
wscale = float(event.width)/self.width
hscale = float(event.height)/self.height
self.width = event.width
self.height = event.height
# resize the canvas
self.config(width=self.width, height=self.height)
# rescale all the objects tagged with the "all" tag
self.scale("all",0,0,wscale,hscale)
class main():
def __init__(self, name = None):
self.root = Tk()
self.name = name # Filename
myframe = Frame(self.root)
myframe.pack(fill=BOTH, expand=YES)
self.cv = ResizingCanvas(myframe, width=850, height=400, bg="dark grey", highlightthickness=0)
self.cv.pack(fill=BOTH, expand=YES)
self.b = Button(self.cv, text = 'Fix', command = self.fix).grid(row=1,column=1)
self.open_img()
def draw_image(self, img, x = None, y = None):
""" Handles the drawing of the main image"""
self.img = ImageTk.PhotoImage(img)
self.cv.create_image(self.root.winfo_screenwidth()/2,
self.root.winfo_screenheight()/2, image=self.img, tags=('all'))
def open_img(self, event=''):
self.pimg = Image.open(self.name)
self.root.state("zoomed")
self.draw_image(self.pimg)
def fix(self, event=''):
self.draw_image(self.pimg)
def run(self):
self.root.mainloop()
if __name__ == "__main__":
path = 'example.png'
app = main(path)
app.run()
What should happen in the video:
I click run and the image is displayed immediately, without having to click the fix button.
What does happen in the video:
I click run and the image is not displayed until I click the fix button, afterwhich it works.
Changing
self.root.state("zoomed") to self.root.state("normal")
in your code (I am working on Python3) I can only get:
[
the image above, played a little bit starting from How to get tkinter canvas to dynamically resize to window width?
and now the code seems to work with me:
from time import sleep
from tkinter import *
from PIL import Image, ImageTk
class ResizingCanvas(Canvas):
# https://stackoverflow.com/a/22837522/992644
def __init__(self,parent, **kwargs):
# Canvas.__init__(self,parent,**kwargs)
print(kwargs)
Canvas.__init__(self,parent,**kwargs)
self.bind("<Configure>", self.on_resize)
# self.height = self.winfo_reqheight()
# self.width = self.winfo_reqwidth()
self.height = self.winfo_height()
self.width = self.winfo_width()
# self.height = height
# self.width = width
# self.__dict__.update(kwargs)
def on_resize(self,event):
""" determine the ratio of old width/height to new width/height"""
wscale = (event.width)//self.width
hscale = (event.height)//self.height
self.width = event.width
self.height = event.height
# resize the canvas
self.config(width=self.width, height=self.height)
# rescale all the objects tagged with the "all" tag
self.scale("all",0,0,wscale,hscale)
class main():
def __init__(self, name = None):
self.pippo = Tk()
self.name = name # Filename
self.myframe = Frame(self.pippo)
self.myframe.pack(side = BOTTOM, expand=YES)
# myframe.pack(fill=BOTH, expand='TRUE')
self.cv = ResizingCanvas(self.myframe, width=850, height=400, bg="dark grey", highlightthickness=0)
self.cv.pack(fill=BOTH, expand=YES)
# sleep(2)
self.b = Button(self.myframe, text = 'Fix', command = self.fix)#.grid(row=1,column=1)
self.b.pack(side=TOP)
self.open_img()
# self.pippo.mainloop() ## use it if you eliminate def run
def draw_image(self, img, x = None, y = None):
""" Handles the drawing of the main image"""
self.img = ImageTk.PhotoImage(img)
# self.cv.create_image(self.pippo.winfo_screenwidth()/2,
# self.pippo.winfo_screenheight()/2, image=self.img, tags=('all'))
self.cv.create_image(self.pippo.winfo_width()/2,
self.pippo.winfo_reqheight()/2, image=self.img, tags=('all'))
def open_img(self, event=''):
self.pimg = Image.open(self.name)
self.pippo.state("normal")
self.draw_image(self.pimg)
def fix(self, event=''):
self.draw_image(self.pimg)
def run(self):
self.pippo.mainloop()
if __name__ == "__main__":
path = 'example.png'
app = main(path)
app.run()
don't know about your question though, but wanted to be sure your starting example works right. Let me know if it could be related to python/pillow/tkinter version or something else
Here my window image results before ad after pressing fix button :
At the end found out that your code does work as long as you use
self.root.attributes('-zoomed', True) instead of `self.root.state("zoomed")`
The problem is here. self.root.winfo_screenwidth()
Change it to self.cv.width. I don't know why.
def draw_image(self, img, x = None, y = None):
""" Handles the drawing of the main image"""
self.img = ImageTk.PhotoImage(img)
self.cv.create_image(self.root.winfo_screenwidth()/2,
self.root.winfo_screenheight()/2, image=self.img, tags=('all'))
Change the last line to
self.cv.create_image(self.cv.width/2,
self.cv.height/2, image=self.img, tags=('all'))
Fixes the issue.
Tk.winfo_screenwidth() according to https://tkdocs.com/shipman/universal.html returns the width of the screen, indepedant of the size of the window, so even if you have a small window on a 1920x1080 display, this function will return 1920.
self.cv.width returns the width of the canvas object.
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()
(Python 2.7). I have a Tkinter canvas with two images that are both the height and width of the canvas, so they cover the whole window. One image is on top of the other. I want to, using the mouse, be able to erase part of the top image wherever I want, thus exposing the bottom image. Is this possible?
I'm curious in how to implement the Home.erase method below which is bound to a Tkinter motion event.
# -*- coding: utf-8 -*-
import io
from PIL import Image, ImageTk
import Tkinter as tk
#Image 2 is on top of image 1.
IMAGE1_DIR = "C:/path_to_image/image1.png"
IMAGE2_DIR = "C:/path_to_image/image2.png"
def create_image(filename, width=0, height=0):
"""
Returns a PIL.Image object from filename - sized
according to width and height parameters.
filename: str.
width: int, desired image width.
height: int, desired image height.
1) If neither width nor height is given, image will be returned as is.
2) If both width and height are given, image will resized accordingly.
3) If only width or only height is given, image will be scaled so specified
parameter is satisfied while keeping image's original aspect ratio the same.
"""
with open(filename, "rb") as f:
fh = io.BytesIO(f.read())
#Create a PIL image from the data
img = Image.open(fh, mode="r")
#Resize if necessary.
if not width and not height:
return img
elif width and height:
return img.resize((int(width), int(height)), Image.ANTIALIAS)
else: #Keep aspect ratio.
w, h = img.size
scale = width/float(w) if width else height/float(h)
return img.resize((int(w*scale), int(h*scale)), Image.ANTIALIAS)
class Home(object):
"""
master: tk.Tk window.
screen: tuple, (width, height).
"""
def __init__(self, master, screen):
self.screen = w, h = screen
self.master = master
self.frame = tk.Frame(self.master)
self.frame.pack()
self.can = tk.Canvas(self.frame, width=w, height=h)
self.can.pack()
#Photos will be as large as the screen.
p1 = ImageTk.PhotoImage(image=create_image(IMAGE1_DIR, w, h))
p2 = ImageTk.PhotoImage(image=create_image(IMAGE2_DIR, w, h))
## Place photos in center of screen.
## Create label to retain a reference to image so it doesn't dissapear.
self.photo1 = self.can.create_image((w//2, h//2), image=p1)
label1 = tk.Label(image=p1)
label1.image = p1
#On top.
self.photo2 = self.can.create_image((w//2, h//2), image=p2)
label2 = tk.Label(image=p2)
label2.image = p2
#Key bindings.
self.master.bind("<Return>", self.reset)
self.master.bind("<Motion>", self.erase)
#### Key Bindings ####
def reset(self, event):
""" Enter/Return key. """
self.frame.destroy()
self.__init__(self.master, self.screen)
def erase(self, event):
"""
Mouse motion binding.
Erase part of top image (self.photo2) at location (event.x, event.y),
consequently exposing part of the bottom image (self.photo1).
"""
pass
def main(screen=(500, 500)):
root = tk.Tk()
root.resizable(0, 0)
Home(root, screen)
#Place window in center of screen.
root.eval('tk::PlaceWindow %s center'%root.winfo_pathname(root.winfo_id()))
root.mainloop()
if __name__ == '__main__':
main()
Thanks to #martineau for the suggestions! Here is the working code.
from PIL import Image, ImageTk
import Tkinter as tk
#Image 2 is on top of image 1.
IMAGE1_DIR = "C:/path/image1.PNG"
IMAGE2_DIR = "C:/path/image2.PNG"
#Brush size in pixels.
BRUSH = 5
#Actual size is 2*BRUSH
def create_image(filename, width=0, height=0):
"""
Returns a PIL.Image object from filename - sized
according to width and height parameters.
filename: str.
width: int, desired image width.
height: int, desired image height.
1) If neither width nor height is given, image will be returned as is.
2) If both width and height are given, image will resized accordingly.
3) If only width or only height is given, image will be scaled so specified
parameter is satisfied while keeping image's original aspect ratio the same.
"""
#Create a PIL image from the file.
img = Image.open(filename, mode="r")
#Resize if necessary.
if not width and not height:
return img
elif width and height:
return img.resize((int(width), int(height)), Image.ANTIALIAS)
else: #Keep aspect ratio.
w, h = img.size
scale = width/float(w) if width else height/float(h)
return img.resize((int(w*scale), int(h*scale)), Image.ANTIALIAS)
class Home(object):
"""
master: tk.Tk window.
screen: tuple, (width, height).
"""
def __init__(self, master, screen):
self.screen = w, h = screen
self.master = master
self.frame = tk.Frame(self.master)
self.frame.pack()
self.can = tk.Canvas(self.frame, width=w, height=h)
self.can.pack()
self.image1 = create_image(IMAGE1_DIR, w, h)
self.image2 = create_image(IMAGE2_DIR, w, h)
#Center of screen.
self.center = w//2, h//2
#Start with no photo on the screen.
self.photo = False
#Draw photo on screen.
self.draw()
#Key bindings.
self.master.bind("<Return>", self.reset)
self.master.bind("<Motion>", self.erase)
def draw(self):
"""
If there is a photo on the canvas, destroy it.
Draw self.image2 on the canvas.
"""
if self.photo:
self.can.delete(self.photo)
self.label.destroy()
p = ImageTk.PhotoImage(image=self.image2)
self.photo = self.can.create_image(self.center, image=p)
self.label = tk.Label(image=p)
self.label.image = p
#### Key Bindings ####
def reset(self, event):
""" Enter/Return key. """
self.frame.destroy()
self.__init__(self.master, self.screen)
def erase(self, event):
"""
Mouse motion binding.
Erase part of top image (self.photo2) at location (event.x, event.y),
consequently exposing part of the bottom image (self.photo1).
"""
for x in xrange(event.x-BRUSH, event.x+BRUSH+1):
for y in xrange(event.y-BRUSH, event.y+BRUSH+1):
try:
p = self.image1.getpixel((x, y))
self.image2.putpixel((x, y), p)
except IndexError:
pass
self.draw()
def main(screen=(500, 500)):
root = tk.Tk()
root.resizable(0, 0)
Home(root, screen)
#Place window in center of screen.
root.eval('tk::PlaceWindow %s center'%root.winfo_pathname(root.winfo_id()))
root.mainloop()
if __name__ == '__main__':
main()
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