Dynamically Resize only the image in tkinter GUI

Question:

I have been attempting to create a GUI with sliders and buttons with an output image to the right (NOT background image). A requirement is for only the image to dynamically change size based on the window size.

In my attempt the window properly displays a resized image only when resizing horizontally, not vertically. and when the window is updated after a vertical resize it will improperly scale the image, resulting in part of the image being cut off.

I am not looking to maintain a window/screen ratio like:
Dynamically resize images in Tkinter

I have used information from:

And youtube videos from codemy like How To Resize A Window Dynamically – Python Tkinter GUI Tutorial #80

to get this code:

import tkinter as tk
from tkinter import *
from tkinter.filedialog import askopenfilename
from PIL import ImageTk, Image


def show_values():
    print(slider1.get())


window = tk.Tk()

filename = askopenfilename()  # show an "Open" dialog box and return the path to the selected file

image = ImageTk.PhotoImage(Image.open(filename))
imageWidth = image.width()
imageHeight = image.height()

canvas = Canvas(window, width=imageWidth, height=imageHeight)
canvas.pack(fill=BOTH, expand=True, side=RIGHT)
canvas.create_image(0, 0, image=image, anchor='nw')

slider1 = Scale(window, from_=0, to=42, orient='vertical')
slider1.pack(side=LEFT)


def resizer(e):
    global image1, resized_image, new_image
    image1 = Image.open(filename)
    resized_image = image1.resize((e.width, e.height), Image.Resampling.LANCZOS)
    new_image = ImageTk.PhotoImage(resized_image)
    canvas.create_image(0, 0, image=new_image, anchor='nw')


window.bind('<Configure>', resizer)

Button(window, text='Process Image', command=show_values).pack(side=BOTTOM)

window.mainloop()

I have also tried to use a weighted grid and the NSEW (N+S+E+W) method to no success.

Asked By: Luke-McDevitt

||

Answers:

When you bind to a root window, that binding is applied to all widgets. This is due to the fact that bindings are applied to binding tags, and the tag for the root window is added to the list of bind tags for every widget.

Because of this, your resizer is being called once for the root window, and once for the canvas, every time the window changes size. That means that e.width and e.height will be different since sometimes it refers to the width of the window and sometimes to the width of the canvas.

The simple solution is to bind to the <Configure> event of only the canvas. If you have your canvas properly configured to resize when the window resizes, then this will all work just fine.

Also, you shouldn’t be creating new image objects on the canvas. You’re just stacking one image on top of another. Your program would eventually crash because of the multitude of images.

Instead, you can reconfigure the existing image object to use your new widget.

...
image_id = canvas.create_image(0, 0, image=image, anchor='nw')
...
def resizer(e):
    global image1, resized_image, new_image
    image1 = Image.open(filename)
    resized_image = image1.resize((e.width, e.height), Image.Resampling.LANCZOS)
    new_image = ImageTk.PhotoImage(resized_image)
    canvas.itemconfigure(image_id, image=new_image)

canvas.bind('<Configure>', resizer)
...
Answered By: Bryan Oakley