在Tkinter Python中延迟加载图像

时间:2019-06-26 08:02:09

标签: python image tkinter python-imaging-library

我正在尝试制作一个Windows样式的图像查看器,以便可以在tkinter中以图标或缩略图格式一次显示多个图像(例如,在Windows上执行“视图”>“布局”>“大图标”时)。但是,我要打开的文件夹包含数百张图像,这是一种缓慢加载所有图像的方法,并且无论如何可能会使用过多的内存。因此,我试图根据图像是否可见来延迟加载图像(即:如果用户向下滚动足以看到图像)。这是我到目前为止的内容:

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()

labels_in_view方法尝试仅返回显示的标签(包含图像)。每个标签最初都加载有一个虚拟的“加载”图像。您还可以看到我绑定了“ h”以显示一些额外的调试信息。

这是问题所在,过去的数十张图像完全挂掉了。如果要滚动到所有图像的末尾,它将尝试从上到下加载所有中间图像,而不仅是那些可见的图像。

这当然是因为我如何拦截滚动条回叫然后延迟加载,但是我无法弄清楚其他任何方式。有没有更好的办法?也许也是更好的库,tkinter相当慢,尽管我不想从头开始?

任何帮助将不胜感激!谢谢。

编辑:图像仅应加载一次,因为label.has_img标志在图像加载后设置为true。

0 个答案:

没有答案