Ленивая загрузка изображений в Tkinter Python - PullRequest
1 голос
/ 26 июня 2019

Я пытаюсь создать средство просмотра изображений в стиле 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, как только изображение было загружено.

...