Я пытаюсь создать средство просмотра изображений в стиле Windows, которое может отображать несколько изображений одновременно в виде значков или эскизов (например, когда вы делаете «Вид»> «Макет»> «Большие значки в окнах») в tkinter.Тем не менее, папка, которую я хочу открыть, содержит пару сотен изображений, поэтому их загрузка может быть медленной, и в любом случае, вероятно, будет использоваться слишком много памяти.Поэтому я пытаюсь лениво загружать изображения, основываясь на том, видны ли они (то есть: если пользователь прокрутил достаточно вниз, чтобы увидеть его).Вот что у меня есть:
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()
Метод label_in_view пытается вернуть только те метки (которые содержат изображения), которые отображаются.Каждая этикетка изначально загружена с фиктивным «загрузочным» изображением.Вы также можете видеть, что я связал 'h', чтобы отобразить некоторую дополнительную информацию об отладке.
Вот проблема: после десятков изображений она полностью зависает.Если прокрутить до конца все изображения, он пытается загрузить все промежуточные изображения сверху вниз, а не только те, которые доступны для просмотра.
Это, конечно, из-за того, как я перехватываю обратный вызов полосы прокрутки, чтобы потом лениво загрузить, но я не могу придумать какой-либо другой способ сделать это.Есть ли способ лучше?Может быть, и лучшая библиотека, tkinter довольно медленный, хотя я бы не стал перезапускать его с нуля?
Любая помощь с благодарностью!Спасибо.
РЕДАКТИРОВАТЬ: Изображения должны быть загружены только один раз из-за флага label.has_img, установленного в true, как только изображение было загружено.