Python PIL Image in Label auto resize Python PIL Image in Label auto resize tkinter tkinter

Python PIL Image in Label auto resize


I believe I have now solved this, but it really needs a lot more testing with different parameters to ensure accurate results. The code I have use to test this is as follows:

from tkinter import tixfrom PIL import Image, ImageTkdef Resize_Image(image, maxsize):    r1 = image.size[0]/maxsize[0] # width ratio    r2 = image.size[1]/maxsize[1] # height ratio    ratio = max(r1, r2)    newsize = (int(image.size[0]/ratio), int(image.size[1]/ratio)) # keep image aspect ratio    image = image.resize(newsize, Image.ANTIALIAS)    return imageclass Pict_Frame(tix.Label):    def __init__(self, parent=None, picture=None, maxupdate=None, imagesize=None, **kwargs):        tix.Label.__init__(self, parent, **kwargs)        self.bind("<Configure>", self._resize_binding)        self.maxupdate = maxupdate        self.imagesize = imagesize        self.update_after_id = None # used for update rate limiting        self.photo = None # used to store raw image from file for later use        self.image = None # used for reference to the resized image        if imagesize:            self.photo=Image.new("RGB", (1,1)) # create empty image to insert            self.image=ImageTk.PhotoImage(self.photo) # create instance of image for PIL            self.configure(image=self.image)            self.configure(width=imagesize[0], height=imagesize[1]) # not label uses pixels for size, set size passed in        if picture:            self.set_picture(picture) # we have a picture so load it now    def _resize_binding(self, event):        if self.photo: # we have a picture            if not self.maxupdate: # no rate limiting                self.load_picture()            else:                if not self.update_after_id: # if we're not waiting then queue resize                    self.update_after_id = self.after(int(1000/self.maxupdate), self.load_picture)    def load_picture(self):        if self.photo:            if self.update_after_id:                self.update_after_id = None            if (self.winfo_width() > 1) and (self.winfo_height() > 1): # prevent updates before widget gets sized                bd = self.cget("bd") # get the border width                if type(bd) != int: # if there was no border set we get an object back                    pad = 4 # set this explicitly to avoid problems                else:                    pad = int(bd*2) # we have a border both sides, so double the retrieved value                newsize = (self.winfo_width()-pad, self.winfo_height()-pad)            elif self.imagesize: # only use the passed in image size if the widget has not rendered                newsize = self.imagesize            else:                return # widget not rendered yet and no size explicitly set, so break until rendered            self.image = ImageTk.PhotoImage(Resize_Image(self.photo, newsize))            self.configure(image=self.image)    def set_picture(self, filename):        with open(filename, mode="rb") as file:            self.photo = Image.open(file)            self.photo.load() # load image into memory to allow resizing later without file access        self.load_picture()

and my test cases were:

import os    path = "E:\imagefolder"    images = []    ind = 0    for item in os.listdir(path): # get a fully qualified list of images        if os.path.isdir(os.path.join(path, item)):            if os.path.isfile(os.path.join(path, item, "thumb.jpg")):                images.append(os.path.join(path, item, "thumb.jpg"))    def callback():        global ind        ind += 1        if ind >= len(images):            ind = 0        pict.set_picture(images[ind])    ignore_test_cases = []    if 1 not in ignore_test_cases:        print("test case 1: - no border no set size")        root = tix.Tk()        tix.Button(root, text="Next Image", command=callback).pack()        pict = Pict_Frame(parent=root, bg="grey",                          maxupdate=2, # allows problem to be easily seen                          picture=images[ind])        pict.pack(fill="both", expand=True)        tix.Button(root, text="Next Image", command=callback).pack()        root.mainloop()    if 2 not in ignore_test_cases:        print("test case 2: - small border no set size")        root = tix.Tk()        tix.Button(root, text="Next Image", command=callback).pack()        pict = Pict_Frame(parent=root, bg="grey", bd=2, relief="raised",                          maxupdate=2,                          picture=images[ind])        pict.pack(fill="both", expand=True)        tix.Button(root, text="Next Image", command=callback).pack()        root.mainloop()    if 3 not in ignore_test_cases:        print("test case 3: - large border no set size")        root = tix.Tk()        tix.Button(root, text="Next Image", command=callback).pack()        pict = Pict_Frame(parent=root, bg="grey", bd=10, relief="raised",                          maxupdate=2,                          picture=images[ind])        pict.pack(fill="both", expand=True)        tix.Button(root, text="Next Image", command=callback).pack()        root.mainloop()    if 4 not in ignore_test_cases:        print("test case 4: - no border with set size")        root = tix.Tk()        tix.Button(root, text="Next Image", command=callback).pack()        pict = Pict_Frame(parent=root, bg="grey",                          maxupdate=2,                          imagesize=(256,384),                          picture=images[ind])        pict.pack(fill="both", expand=True)        tix.Button(root, text="Next Image", command=callback).pack()        root.mainloop()    if 5 not in ignore_test_cases:        print("test case 5: - small border with set size")        root = tix.Tk()        tix.Button(root, text="Next Image", command=callback).pack()        pict = Pict_Frame(parent=root, bg="grey", bd=2, relief="raised",                          maxupdate=2,                          imagesize=(256,384),                          picture=images[ind])        pict.pack(fill="both", expand=True)        tix.Button(root, text="Next Image", command=callback).pack()        root.mainloop()    if 6 not in ignore_test_cases:        print("test case 6: - large border with set size")        root = tix.Tk()        tix.Button(root, text="Next Image", command=callback).pack()        pict = Pict_Frame(parent=root, bg="grey", bd=10, relief="raised",                          maxupdate=2,                          imagesize=(256,384),                          picture=images[ind])        pict.pack(fill="both", expand=True)        tix.Button(root, text="Next Image", command=callback).pack()        root.mainloop()    if 10 not in ignore_test_cases:        print("test case fullscreen: - small border no set size, in fullscreen window with expansion set up")        root = tix.Tk()        root.state("zoomed")        root.grid_columnconfigure(1, weight=2)        root.grid_columnconfigure(2, weight=1)        root.grid_rowconfigure(2, weight=1)        tix.Button(root, text="Next Image", command=callback).grid(column=2, row=1, sticky="nesw")        pict = Pict_Frame(parent=root, bg="grey",# bd=10, relief="raised",                          maxupdate=2,                          picture=images[ind])        pict.grid(column=2, row=2, sticky="nesw")        tix.Button(root, text="Next Image", command=callback).grid(column=2, row=3, sticky="nesw")        root.mainloop()    if 11 not in ignore_test_cases:        print("test case fullscreen: - small border no set size, in fullscreen window with expansion set up")        root = tix.Tk()        root.state("zoomed")        root.grid_columnconfigure(1, weight=2)        root.grid_columnconfigure(2, weight=1)        root.grid_rowconfigure(1, weight=1)        frame = tix.Frame(root)        frame.grid(column=2, row=1, sticky="nesw")        frame.grid_columnconfigure(1, weight=1)        frame.grid_rowconfigure(2, weight=1)        tix.Button(frame, text="Next Image", command=callback).grid(column=1, row=1, sticky="nesw")        pict = Pict_Frame(parent=frame, bg="grey",# bd=10, relief="raised",                          maxupdate=2,                          picture=images[ind])        pict.grid(column=1, row=2, sticky="nesw")        tix.Button(frame, text="Next Image", command=callback).grid(column=1, row=3, sticky="nesw")        root.mainloop()

The only issue I have had with this code is that when I am using the widget in a full-screen application the re-sizing doesn't work as intended, when using the grid method and setting the weight of the right column to 1 (with the pict widget) and the left column (empty) to 1, the right column ends up taking approx 2/3rds the width of the screen.

I suspect this is due to explicitly setting the size of the image, which then makes it wider, meaning the geometry manager wants to make it wider still (ad infinitum) until it reaches some equilibrium. But if anyone can shed any light on this (or even a solution) it would be appreciated.