Generate Tkinter Canvas In Loop with Image - python

I'm attempting to create a series of canvases, each displaying a different section of an image according to the coordinates present in a list. The number of different coordinates is dynamic so it must be done in a loop.
The issue I am having is even though I'm saving my references garbage collection is preventing all canvases except the last from displaying an image.
Here is what I have so far:
import tkinter as tk
import cv2
from PIL import Image, ImageTk
class App(object):
def __init__(self):
self.root = tk.Tk()
# Candidates holds the coordinates for the image sections, and is hardcoded for this example
candidates = [[0,100,100,100], [15,15,200,200], [30,30,200,200], [50,50,200,200], [100,100,200,200], [200,200,200,200]]
self.im = cv2.imread(r"....")
j = 0
i = 0
frames = []
images = []
refs = []
for candidate in candidates:
x,y,w,h = tuple(candidate)
self.img_tk = ImageTk.PhotoImage(Image.fromarray(self.im[y:y+h, x:x+w]))
Frame = tk.Canvas(self.root,bg='white', highlightthickness=1, highlightbackground="black", width=100, height= 100)
Frame.grid(row = i, column = j, sticky = 'w, e, n, s', padx=5, pady=5)
ref = Frame.create_image(0, 0, image=self.img_tk, anchor="nw")
images.append(self.img_tk)
refs.append(ref)
frames.append(Frame)
if j<2:
j+=1
else:
j=0
i+=1
app = App()
app.root.mainloop()
and the result:
As you can see, only the last frame in the loop has an image created. Any thoughts are greatly appreciated, thanks.

It is because images is a local variable, so it will be garbage collected after the function exits, so are the image references stored in it. The last image can be shown because self.img_tk saves the reference.
To solve it, change images to instance variable self.images.

Related

Why is this tkinter function not displaying any pictures

I have code to display 2x5 images and change them when I click on them. However, the code I wrote does not display any images in the tkinter windows. Why?
Some details:
the URLs are working fine
import tkinter as tk
from PIL import Image, ImageTk
# Create the main window
root = tk.Tk()
# Create a list of images to display
images = ['https://lh3.googleusercontent.com/SsEIJWka3_cYRXXSE8VD3XNOgtOxoZhqW1uB6UFj78eg8gq3G4jAqL4Z_5KwA12aD7Leqp27F653aBkYkRBkEQyeKxfaZPyDx0O8CzWg=s0',
'https://lh3.googleusercontent.com/Bawo7r1nPZV6sJ4OHZJHdKV_4Ky59vquAR7KoUXcNZgx9fqTaOW-QaOM9qoyYhOTAopzjt9OIfW06RMwa-9eJW9KjQw=s0',
'https://lh3.googleusercontent.com/tm1DbZrAP0uBM-OJhLwvKir1Le5LglRF_bvbaNi6m-F_pIyttsWQz040soRY9pWA9PgNEYFA_fBkg_keYixRXCAjz9Q=s0',
'https://lh3.googleusercontent.com/AyiKhdEWJ7XmtPXQbRg_kWqKn6mCV07bsuUB01hJHjVVP-ZQFmzjTWt7JIWiQFZbb9l5tKFhVOspmco4lMwqwWImfgg=s0',
'https://lh3.googleusercontent.com/FNNTrTASiUR0f49UVUY5bisIM-3RlAbf_AmktgnU_4ou1ZG0juh3pMT1-xpQmtN1R8At4Gq9B4ioSSi4TVrgbCZsmtY=s0',
'https://lh3.googleusercontent.com/mAyAjvYjIeAIlByhJx1Huctgeb58y7519XYP38oL1FXarhVlcXW7kxuwayOCFdnwtOp6B6F0HJmmws-Ceo5b_pNSSQs=s0',
'https://lh3.googleusercontent.com/gShVRyvLLbwVB8jeIPghCXgr96wxTHaM4zqfmxIWRsUpMhMn38PwuUU13o1mXQzLMt5HFqX761u8Tgo4L_JG1XLATvw=s0',
'https://lh3.googleusercontent.com/KA2hIo0BlMDmyQDEC3ixvp9WHgcyJnlAvWtVcZmExh9ocPoZdQGRJh7bZjE2Mx2OGC0Zi3QGHGP0LlmuFgRlIYs36Sgn5G2OD-0MaTo=s0',
'https://lh3.googleusercontent.com/N2m90mImdcoLacUybb_rxcktTwtr0LFhtuzxbSE9elIhElF6jpWngx96_uZ0L1TGNof5pNt4n_Ygb4KYlPTpA9o6788=s0',
'https://lh3.googleusercontent.com/1pTfYJlLwVTifKj4PlsWPyAg4PcIVBAiVvB8sameSnmm7HRd056abNUIRq33rgry7u9t-ju-eHOnbfqQpK4q_8IwzIXZ4WgrqZW9l7U=s0',
'https://lh3.googleusercontent.com/0bgOiMrBM2GuhW_pNeQW711GuB3kD7Gq7AILGHaJGeWKa1Fu1hUJGpOjvSpiP_XpgRlC4jVmH0Z1233PEPMJTfNRR7Q=s0',
'https://lh3.googleusercontent.com/x9NFmu-RbZ_9M5BK_hOzQRdVj4pu7p--y_IYwDK46lDPzQtTIO9AlBV_ObgQiY7GeWE0ZfNjMSyrCWgnwL4MCasQZQ=s0']
# Create a variable to keep track of the current image
current_image = [0,0,0,0,0,0,0,0,0,0]
# Create a grid of labels to display the images
image_grid = [[tk.Label(root) for _ in range(5)] for _ in range(2)]
# Function to change the image
def change_image(x,y):
global current_image
current_image[x*5+y] += 1
if current_image[x*5+y] >= len(images):
current_image[x*5+y] = 0
image = Image.open(BytesIO(requests.get(images[current_image[x*5+y]]).content))
image = image.resize((256,256))
print(image)
photo = ImageTk.PhotoImage(image)
image_grid[x][y].config(image=photo)
image_grid[x][y].image = photo
# Bind labels to the function
for i in range(2):
for j in range(5):
image_grid[i][j].bind("<Button-1>", lambda event, x=i, y=j: change_image(x,y))
image_grid[i][j].grid(row=i, column=j)
# Start the main loop
root.mainloop()
The Images will only display after you click on a label. And your labels are all very small when you initialize your window. Set a bigger window and use sticky in grid to get bigger cells (you can even set a border to the labels to see where exactly you're clicking):
import tkinter as tk
from PIL import Image, ImageTk
from io import BytesIO
import requests
# Create the main window
root = tk.Tk()
root.geometry('1280x720')
# Create a list of images to display
images = ['https://lh3.googleusercontent.com/SsEIJWka3_cYRXXSE8VD3XNOgtOxoZhqW1uB6UFj78eg8gq3G4jAqL4Z_5KwA12aD7Leqp27F653aBkYkRBkEQyeKxfaZPyDx0O8CzWg=s0',
'https://lh3.googleusercontent.com/Bawo7r1nPZV6sJ4OHZJHdKV_4Ky59vquAR7KoUXcNZgx9fqTaOW-QaOM9qoyYhOTAopzjt9OIfW06RMwa-9eJW9KjQw=s0',
'https://lh3.googleusercontent.com/tm1DbZrAP0uBM-OJhLwvKir1Le5LglRF_bvbaNi6m-F_pIyttsWQz040soRY9pWA9PgNEYFA_fBkg_keYixRXCAjz9Q=s0',
'https://lh3.googleusercontent.com/AyiKhdEWJ7XmtPXQbRg_kWqKn6mCV07bsuUB01hJHjVVP-ZQFmzjTWt7JIWiQFZbb9l5tKFhVOspmco4lMwqwWImfgg=s0',
'https://lh3.googleusercontent.com/FNNTrTASiUR0f49UVUY5bisIM-3RlAbf_AmktgnU_4ou1ZG0juh3pMT1-xpQmtN1R8At4Gq9B4ioSSi4TVrgbCZsmtY=s0',
'https://lh3.googleusercontent.com/mAyAjvYjIeAIlByhJx1Huctgeb58y7519XYP38oL1FXarhVlcXW7kxuwayOCFdnwtOp6B6F0HJmmws-Ceo5b_pNSSQs=s0',
'https://lh3.googleusercontent.com/gShVRyvLLbwVB8jeIPghCXgr96wxTHaM4zqfmxIWRsUpMhMn38PwuUU13o1mXQzLMt5HFqX761u8Tgo4L_JG1XLATvw=s0',
'https://lh3.googleusercontent.com/KA2hIo0BlMDmyQDEC3ixvp9WHgcyJnlAvWtVcZmExh9ocPoZdQGRJh7bZjE2Mx2OGC0Zi3QGHGP0LlmuFgRlIYs36Sgn5G2OD-0MaTo=s0',
'https://lh3.googleusercontent.com/N2m90mImdcoLacUybb_rxcktTwtr0LFhtuzxbSE9elIhElF6jpWngx96_uZ0L1TGNof5pNt4n_Ygb4KYlPTpA9o6788=s0',
'https://lh3.googleusercontent.com/1pTfYJlLwVTifKj4PlsWPyAg4PcIVBAiVvB8sameSnmm7HRd056abNUIRq33rgry7u9t-ju-eHOnbfqQpK4q_8IwzIXZ4WgrqZW9l7U=s0',
'https://lh3.googleusercontent.com/0bgOiMrBM2GuhW_pNeQW711GuB3kD7Gq7AILGHaJGeWKa1Fu1hUJGpOjvSpiP_XpgRlC4jVmH0Z1233PEPMJTfNRR7Q=s0',
'https://lh3.googleusercontent.com/x9NFmu-RbZ_9M5BK_hOzQRdVj4pu7p--y_IYwDK46lDPzQtTIO9AlBV_ObgQiY7GeWE0ZfNjMSyrCWgnwL4MCasQZQ=s0']
# Create a variable to keep track of the current image
current_image = [0,0,0,0,0,0,0,0,0,0]
# Create a grid of labels to display the images
image_grid = [[tk.Label(root, borderwidth=2, relief="groove") for _ in range(5)] for _ in range(2)]
# Function to change the image
def change_image(x,y):
global current_image
current_image[x*5+y] += 1
if current_image[x*5+y] >= len(images):
current_image[x*5+y] = 0
image = Image.open(BytesIO(requests.get(images[current_image[x*5+y]]).content))
image = image.resize((256,256))
print(image)
photo = ImageTk.PhotoImage(image)
image_grid[x][y].config(image=photo)
image_grid[x][y].image = photo
# Bind labels to the function
for i in range(2):
root.rowconfigure(i, weight=1)
for j in range(5):
root.columnconfigure(j, weight=1)
image_grid[i][j].bind("<Button-1>", lambda event, x=i, y=j: change_image(x,y))
image_grid[i][j].grid(row=i, column=j, sticky="nsew")
# Start the main loop
root.mainloop()
Note: setting rowconfigure/columnconfigure ensures that the cells have the same dimension, you might want to use it in change_image as well
your issue is that the labels start with a zero width and height because they are empty, you could set a placeholder image on startup to make the GUI fit exactly perfect around it.
import tkinter as tk
from PIL import Image, ImageTk
from io import BytesIO
import requests
# Create the main window
root = tk.Tk()
# Create a list of images to display
images = ['https://lh3.googleusercontent.com/SsEIJWka3_cYRXXSE8VD3XNOgtOxoZhqW1uB6UFj78eg8gq3G4jAqL4Z_5KwA12aD7Leqp27F653aBkYkRBkEQyeKxfaZPyDx0O8CzWg=s0',
'https://lh3.googleusercontent.com/Bawo7r1nPZV6sJ4OHZJHdKV_4Ky59vquAR7KoUXcNZgx9fqTaOW-QaOM9qoyYhOTAopzjt9OIfW06RMwa-9eJW9KjQw=s0',
'https://lh3.googleusercontent.com/tm1DbZrAP0uBM-OJhLwvKir1Le5LglRF_bvbaNi6m-F_pIyttsWQz040soRY9pWA9PgNEYFA_fBkg_keYixRXCAjz9Q=s0',
'https://lh3.googleusercontent.com/AyiKhdEWJ7XmtPXQbRg_kWqKn6mCV07bsuUB01hJHjVVP-ZQFmzjTWt7JIWiQFZbb9l5tKFhVOspmco4lMwqwWImfgg=s0',
'https://lh3.googleusercontent.com/FNNTrTASiUR0f49UVUY5bisIM-3RlAbf_AmktgnU_4ou1ZG0juh3pMT1-xpQmtN1R8At4Gq9B4ioSSi4TVrgbCZsmtY=s0',
'https://lh3.googleusercontent.com/mAyAjvYjIeAIlByhJx1Huctgeb58y7519XYP38oL1FXarhVlcXW7kxuwayOCFdnwtOp6B6F0HJmmws-Ceo5b_pNSSQs=s0',
'https://lh3.googleusercontent.com/gShVRyvLLbwVB8jeIPghCXgr96wxTHaM4zqfmxIWRsUpMhMn38PwuUU13o1mXQzLMt5HFqX761u8Tgo4L_JG1XLATvw=s0',
'https://lh3.googleusercontent.com/KA2hIo0BlMDmyQDEC3ixvp9WHgcyJnlAvWtVcZmExh9ocPoZdQGRJh7bZjE2Mx2OGC0Zi3QGHGP0LlmuFgRlIYs36Sgn5G2OD-0MaTo=s0',
'https://lh3.googleusercontent.com/N2m90mImdcoLacUybb_rxcktTwtr0LFhtuzxbSE9elIhElF6jpWngx96_uZ0L1TGNof5pNt4n_Ygb4KYlPTpA9o6788=s0',
'https://lh3.googleusercontent.com/1pTfYJlLwVTifKj4PlsWPyAg4PcIVBAiVvB8sameSnmm7HRd056abNUIRq33rgry7u9t-ju-eHOnbfqQpK4q_8IwzIXZ4WgrqZW9l7U=s0',
'https://lh3.googleusercontent.com/0bgOiMrBM2GuhW_pNeQW711GuB3kD7Gq7AILGHaJGeWKa1Fu1hUJGpOjvSpiP_XpgRlC4jVmH0Z1233PEPMJTfNRR7Q=s0',
'https://lh3.googleusercontent.com/x9NFmu-RbZ_9M5BK_hOzQRdVj4pu7p--y_IYwDK46lDPzQtTIO9AlBV_ObgQiY7GeWE0ZfNjMSyrCWgnwL4MCasQZQ=s0']
# Create a variable to keep track of the current image
current_image = [0,0,0,0,0,0,0,0,0,0]
# Create a grid of labels to display the images
image = Image.new('RGBA', (256, 256))
photo = ImageTk.PhotoImage(image)
image_grid = [[tk.Label(root, image=photo, borderwidth=2, relief="groove") for _ in range(5)] for _ in range(2)]
# Function to change the image
def change_image(x,y):
global current_image
current_image[x*5+y] += 1
if current_image[x*5+y] >= len(images):
current_image[x*5+y] = 0
image = Image.open(BytesIO(requests.get(images[current_image[x*5+y]]).content))
image = image.resize((256,256))
print(image)
photo = ImageTk.PhotoImage(image)
image_grid[x][y].config(image=photo)
image_grid[x][y].image = photo
# Bind labels to the function
for i in range(2):
root.rowconfigure(i, weight=1)
for j in range(5):
root.columnconfigure(j, weight=1)
image_grid[i][j].bind("<Button-1>", lambda event, x=i, y=j: change_image(x,y))
image_grid[i][j].grid(row=i, column=j)
# Start the main loop
root.mainloop()
As already mentioned, you used Label without a text and they were just not visible. I suggest you use Button instead. Your x and y in change_image update correctly, but images[current_image[x*5+y]] always pointed to the same image. I simplified your code and removed the global variable:
import tkinter as tk
from PIL import Image, ImageTk
import requests
from io import BytesIO
# Create the main window
root = tk.Tk()
# Create a list of images to display
images = ['https://lh3.googleusercontent.com/SsEIJWka3_cYRXXSE8VD3XNOgtOxoZhqW1uB6UFj78eg8gq3G4jAqL4Z_5KwA12aD7Leqp27F653aBkYkRBkEQyeKxfaZPyDx0O8CzWg=s0',
'https://lh3.googleusercontent.com/Bawo7r1nPZV6sJ4OHZJHdKV_4Ky59vquAR7KoUXcNZgx9fqTaOW-QaOM9qoyYhOTAopzjt9OIfW06RMwa-9eJW9KjQw=s0',
'https://lh3.googleusercontent.com/tm1DbZrAP0uBM-OJhLwvKir1Le5LglRF_bvbaNi6m-F_pIyttsWQz040soRY9pWA9PgNEYFA_fBkg_keYixRXCAjz9Q=s0',
'https://lh3.googleusercontent.com/AyiKhdEWJ7XmtPXQbRg_kWqKn6mCV07bsuUB01hJHjVVP-ZQFmzjTWt7JIWiQFZbb9l5tKFhVOspmco4lMwqwWImfgg=s0',
'https://lh3.googleusercontent.com/FNNTrTASiUR0f49UVUY5bisIM-3RlAbf_AmktgnU_4ou1ZG0juh3pMT1-xpQmtN1R8At4Gq9B4ioSSi4TVrgbCZsmtY=s0',
'https://lh3.googleusercontent.com/mAyAjvYjIeAIlByhJx1Huctgeb58y7519XYP38oL1FXarhVlcXW7kxuwayOCFdnwtOp6B6F0HJmmws-Ceo5b_pNSSQs=s0',
'https://lh3.googleusercontent.com/gShVRyvLLbwVB8jeIPghCXgr96wxTHaM4zqfmxIWRsUpMhMn38PwuUU13o1mXQzLMt5HFqX761u8Tgo4L_JG1XLATvw=s0',
'https://lh3.googleusercontent.com/KA2hIo0BlMDmyQDEC3ixvp9WHgcyJnlAvWtVcZmExh9ocPoZdQGRJh7bZjE2Mx2OGC0Zi3QGHGP0LlmuFgRlIYs36Sgn5G2OD-0MaTo=s0',
'https://lh3.googleusercontent.com/N2m90mImdcoLacUybb_rxcktTwtr0LFhtuzxbSE9elIhElF6jpWngx96_uZ0L1TGNof5pNt4n_Ygb4KYlPTpA9o6788=s0',
'https://lh3.googleusercontent.com/1pTfYJlLwVTifKj4PlsWPyAg4PcIVBAiVvB8sameSnmm7HRd056abNUIRq33rgry7u9t-ju-eHOnbfqQpK4q_8IwzIXZ4WgrqZW9l7U=s0',
'https://lh3.googleusercontent.com/0bgOiMrBM2GuhW_pNeQW711GuB3kD7Gq7AILGHaJGeWKa1Fu1hUJGpOjvSpiP_XpgRlC4jVmH0Z1233PEPMJTfNRR7Q=s0',
'https://lh3.googleusercontent.com/x9NFmu-RbZ_9M5BK_hOzQRdVj4pu7p--y_IYwDK46lDPzQtTIO9AlBV_ObgQiY7GeWE0ZfNjMSyrCWgnwL4MCasQZQ=s0']
# Create a grid of labels to display the images
image_grid = [[tk.Button(root, text=f"Button {x} {y}") for x in range(5)] for y in range(2)]
# Function to change the image
def change_image(x,y):
print(f"Changing image to: {x} {y} (index: {x*5+y}): {images[x*5+y]}")
image = Image.open(BytesIO(requests.get(images[x*5+y]).content))
image = image.resize((256, 256))
print(image)
photo = ImageTk.PhotoImage(image)
image_grid[x][y].config(image=photo)
image_grid[x][y].image = photo
# Bind labels to the function
for i in range(2):
for j in range(5):
image_grid[i][j].bind("<Button-1>", lambda event, x=i, y=j: change_image(x,y))
image_grid[i][j].grid(row=i, column=j)
# Start the main loop
root.mainloop()

unable to update image in tkinter using a function

I am trying to create a Tkinter to make a window that shows images using a label, then update the image using an update function, but the image that I am trying to show doesn't show up in the Tkinter window, instead, a black screen appears
I have two working code
one that shows an image on the Tkinter window
one what loops a GIF using an update function
I tried to combine them
the code I am working on that doesn't work
#import GUI
from tkinter import *
#change dir
import os
os.chdir("C:/Users/user/Desktop/test image folder/")
#add delay
import time
#import image
from PIL import Image, ImageTk
#set up the window
window = Tk()
#window.title("modify images")
#list of filename
filelist = []
#loop over all files in the working directory
for filename in os.listdir("."):
if not (filename.endswith('.png') or filename.endswith('.jpg')):
continue #skip non-image files and the logo file itself
filelist = filelist + [filename]
#list of filename
print(filelist)
#show first pic
imagefile = filelist[0]
photo = ImageTk.PhotoImage(Image.open(imagefile))
label1 = Label(window, image = photo)
label1.pack()
#update image
def update(ind):
imagefile = filelist[ind]
im = ImageTk.PhotoImage(Image.open(imagefile))
if ind < len(filelist):
ind += 1
else:
ind = 0
label1.configure(image=im)
window.after(2000, update, ind)
window.after(2000, update, 0)
#run the main loop
window.mainloop()
the other code I am trying to combine
1:the one that shows image
import tkinter as tk
from tkinter import *
from PIL import Image, ImageTk # Place this at the end (to avoid any conflicts/errors)
window = tk.Tk()
imagefile = "image.jpg"
img = ImageTk.PhotoImage(Image.open(imagefile))
lbl = tk.Label(window, image = img).pack()
window.mainloop()
print('hi')
2:updates gif
from tkinter import *
#change dir
import os
os.chdir("C:/Users/user/Desktop/Learn Python")
#add delay
import time
##### main:
window = Tk()
##### My Photo
photo1 = [PhotoImage(file="anime.gif", format="gif -index %i" %(i)) for i in range(85)]
#update image
def update(ind):
frame = photo1[ind]
if ind < 84:
ind += 1
else:
ind = 0
label.configure(image=frame)
window.after(80, update, ind)
label = Label(window, bg="black")
label.pack()
window.after(0, update, 0)
#####run the main loop
window.mainloop()
I expect it to show all images in the file one by one
it instead shows only the first image, then the window goes blank
You have problem because there is bug in PhotoImage. If you create it in function and assign to local variable then Garbage Collector removes image from memory and you see empty image. You have to create PhotoImages outside function or you have to assign it to some global variable.
Popular solution is to assign it to label which will display it.
label.im = im
Function:
def update(ind):
imagefile = filelist[ind]
im = ImageTk.PhotoImage(Image.open(imagefile))
if ind < len(filelist):
ind += 1
else:
ind = 0
label1.configure(image=im)
label1.im = im # <-- solution
window.after(2000, update, ind)
Doc: PhotoImage

Lazy loading images in Tkinter Python

I'm trying to make a windows style image viewer that can display multiple images at once in an icon or thumbnail format (like when you do View > Layout > Large Icons on windows) in tkinter. However, the folder I want to open contains a couple of hundred images and it's way to slow to load them all and will probably use too much memory anyway. Therefore I'm trying to lazy load images based on if they are visible (i.e: if the user has scrolled down enough to see it). Here's what I have so far:
from tkinter import *
from tkinter import filedialog
from PIL import Image
from PIL import ImageTk
import glob
import datetime
import os
class ImageListViewer(Frame):
def __init__(self, root, img_paths, *args, **kwargs):
self.img_paths = img_paths
self.root = root
self.canvas = Canvas(root)
self.scroll_y = Scrollbar(root, orient="vertical", command=self.canvas.yview)
self.frame = Frame(self.canvas, bg='red')
self.max_w, self.max_h = 200, 200
cols = 3
loading_img = Image.open('loading.png')
loading_img = self.fit_img(loading_img, self.max_w, self.max_h)
loading_photo = ImageTk.PhotoImage(loading_img)
# group of widgets
for i, path in enumerate(img_paths):
label = Label(self.frame, image=loading_photo, width=self.max_w, height=self.max_h, borderwidth=2, relief="sunken")
label.photo = loading_photo
# label = Label(self.frame, text=str(i), width=self.max_w, height=self.max_h, borderwidth=2, relief="sunken")
label.index = i
label.has_img = False
label.grid(row=i-i%cols, column=i%cols)
self.canvas.create_window(0, 0, anchor='nw', window=self.frame)
self.canvas.update_idletasks()
self.canvas.configure(scrollregion=self.canvas.bbox('all'),
yscrollcommand=self.scroll_intercepter)
self.canvas.pack(fill='both', expand=True, side='left')
self.scroll_y.pack(fill='y', side='right')
self.root.bind('h', self.debug)
super().__init__(root)
def scroll_intercepter(self, a, b):
for label in self.labels_in_view():
if not label.has_img:
self.lazy_load_img(label)
self.scroll_y.set(a, b)
def lazy_load_img(self, label):
img = Image.open(self.img_paths[label.index])
img = self.fit_img(img, self.max_w, self.max_h)
photo = ImageTk.PhotoImage(img)
label.config(image = photo)
label.photo = photo
# label.config(bg = 'red')
label.has_img = True
def labels_in_view(self, show=False):
if show:
print('Root:', self.root.winfo_width(), self.root.winfo_height())
for label in self.frame.winfo_children():
label_x = label.winfo_rootx() #- label.winfo_width()
label_y = label.winfo_rooty() #- label.winfo_height()
if show:
print(label.index, label_x, label_y)
x_in = 0 <= label_x < self.root.winfo_width()
y_in = 0 <= label_y < self.root.winfo_height()
if x_in and y_in:
yield label
def debug(self, event):
print([l.index for l in self.labels_in_view(show=True)], '\n')
#staticmethod
def fit_img(img, width, height, keep_aspect=True):
if keep_aspect:
w, h = img.size
ratio = min(width/w, height/h)
image = img.resize((int(w*ratio), int(h*ratio)))
else:
image = img.resize((width, height))
return image
if __name__ == "__main__":
root = Tk()
root.title("Image List Viewer")
root.geometry('400x400')
paths = glob.glob('*.JPG')
ImageListViewer(root, paths).pack()
root.mainloop()
The labels_in_view method tries to only return labels (which contain the images) that are displayed. Each label is originally loaded with a dummy 'loading' image. You can also see I've binded 'h' to display some extra debugging info.
Here's the problem, past tens of images it completely hangs. If one were to scroll to the end of all the images, it tries to load all the intermediate images from top to bottom and not only the ones that are viewable.
This is certainly because of how I intercept the scrollbar call back to then lazy load but I cannot figure out any other way of doing it. Is there a better way? Maybe a better library too, tkinter is rather slow although I'd rather not restart from scratch?
Any help is greatly appreciated! Thanks.
EDIT: Images should only be loaded once because of the label.has_img flag that is set to true once the image has been loaded.

Images do not get shown in a scroll view associated top-level window in python tkinter

I have been trying to make a top-level window view which collects and shows all the images present in a folder in columns of 10. If the images were more than the allocated size of the window I wanted it to be possible to scroll through the images. I followed the answer given to Scrollable Toplevel Window (tkinter)
to correctly add an image to a canvas and make it possible to scroll through them. But, in my case the entire popup window just comes out to be blank. Here is the code
import tkinter as tk
from tkinter import *
import glob
import os
from PIL import Image, ImageTk
def pop_up_window():
win = Toplevel()
vbar = tk.Scrollbar(win, orient = VERTICAL)
vbar.grid(row = 0, column = 1, sticky = "ns")
container = tk.Canvas(win, height=300, width=720, scrollregion=(0, 0, 300, 720))
container.grid(row = 0, column = 0, sticky = "nsew")
vbar.config(command=container.yview)
container.config(yscrollcommand=vbar.set)
path = "D:\\image_collection"
COLUMNS = 10
image_count = 0
for infile in glob.glob(os.path.join(path, '*.jpg')):
image_count += 1
r, c = divmod(image_count-1, COLUMNS)
im = Image.open(infile)
resized = im.resize((100, 100), Image.ANTIALIAS)
img_part = ImageTk.PhotoImage(Image.open(infile).resize((100, 100), Image.ANTIALIAS))
image_in_canvas = container.create_image(r, c, image = img_part)
win.rowconfigure(0, weight=1)
win.columnconfigure(0, weight=1)
root = Tk()
button = Button(root, text='Call Pop-up window', command = pop_up_window)
button.place(x = 0, y = 0)
root.mainloop()
What changes should I make?
You need to keep a reference to your images or it will be garbage collected by Python. A simple change can do it:
placeholder = []
def pop_up_window():
...
for infile in glob.glob(os.path.join(path, '*.jpg')):
image_count += 1
r, c = divmod(image_count-1, COLUMNS)
im = Image.open(infile)
img_part = ImageTk.PhotoImage(Image.open(infile).resize((100, 100), Image.ANTIALIAS))
placeholder.append(img_part)
image_in_canvas = container.create_image(r, c, image = img_part)
Also I want to point that the create_image method takes two coordinates as args. You are currently creating them as if they are grids, and it won't show up in the alignment you expected.
Your code works but it have few issues with it. I fixed all of them or maybe most of them, in case I forgot to notice it.
When creating an image in a function always create a reference to them as here you have many images so you can create a list to your container.
To keep updating the scrollregion depending upon the amount of images bind "" to the container with the callback function lambda e: scrollregion=container.bbox('all').
Here are the changes that I did to your pop_up_window function.
...
path = "D:\\image_collection"
COLUMNS = 7
container.img_list = []
column = 0
row = 0
for infile in glob.glob(os.path.join(path, '*.jpg')):
if column >= COLUMNS:
column = 0
row += 1
im = Image.open(infile).resize((100, 100), Image.ANTIALIAS)
img = ImageTk.PhotoImage(im)
container.img_list.append(img)
container.create_image(column*100+10, row*100+10, image = img, anchor='nw')
column += 1
container.bind('<Configure>',lambda e:container.configure(scrollregion=container.bbox('all')))
...

tkinter: why images are not dysplayed correctly

The code:
from tkinter import *
root = Tk()
f1=Frame(root)
for img,rlf in [ ('woman',RAISED),('mensetmanus',SOLID),
('terminal',SUNKEN), ('escherknot',FLAT),
('calculator',GROOVE),('letters',RIDGE)]:
filename = img + ".gif"
img1 = PhotoImage(file= filename)
Label(f1, image = img1, relief=rlf).pack(side=LEFT,
padx=5)
f1.pack()
root.mainloop()
Could you help me understand why this excerpt produces 5 empty places for images (though borders are drawn correctly according to what was meant), and 1 image. The last image (which is visible) is letters. And it seems to be cropped from than its actual size. My letters.gif contains letters from A to G, but this code displays only from half B to half F.
It does not work, i think, because img1 is overwritten in each loop. You need to keep references to image objects somewhere, so that garbage collector wont trash them:
from tkinter import *
root = Tk()
f1=Frame(root)
img_list = [] #<-- store references to images
for img,rlf in [ ('woman',RAISED),('mensetmanus',SOLID),
('terminal',SUNKEN), ('escherknot',FLAT),
('calculator',GROOVE),('letters',RIDGE)]:
filename = img + ".gif"
img1 = PhotoImage(file= filename)
img_list.append(img1) #<-- store references to images
Label(f1, image = img1, relief=rlf).pack(side=LEFT,
padx=5)
f1.pack()
root.mainloop()

Categories