1

我正在编写一个简单的图像查看器,它让用户可以非常快速地翻阅数万张图像,一次大约 100 张。图像是磁盘上的文件。

为了让查看器发挥作用,它必须在用户当前的图像之前不断地预加载图像(否则查看器会变得无法使用)。

我用来在 Tkinter 标签网格中显示图像的基本配方如下(已经过测试并且有效):

def load_image(fn):
    image = Image.open(fn)
    print "Before photoimage"
    img = ImageTk.PhotoImage(image)
    print "After photoimage"

label.config(image=load_image("some_image.png")

我需要 ImageTk.PhotoImage 实例来在标签上显示图像。我已经实现了两种不同的方法,每种方法都有一个相关的问题。

第一种方法: 启动一个单独的线程来预加载图像:

def load_ahead():
    for fn in images:
        cache[fn] = load_image()
threading.Thread(target=load_ahead).start()
top.mainloop()

这在我的 Linux 机器上运行良好。但是,在另一台机器上(恰好运行 Windows,并使用 pyinstaller 编译),似乎发生了死锁。“在 Photoimage 之前”被打印,然后程序冻结,这表明加载程序线程在创建 ImageTk.PhotoImage 对象时卡住了。ImageTk.PhotoImage 对象的创建必须发生在主(Tkinter 主循环)线程中吗?PhotoImage 的创建在计算上是昂贵的,还是与实际从磁盘加载图像相比可以忽略不计?

第二种方法: 为了避免从 Tkiner 的 mainloop 线程中创建 PhotoImage 对象的这种可能要求,我求助于 Tk.after:

def load_some_images():
    #load only 10 images. function must return quickly to prevent freezing GUI
    for i in xrange(10): 
        fn = get_next_image()
        cache[fn] = load_image(fn)
    top.after_idle(load_some_images)

top.after_idle(load_some_images)

这样做的问题是,除了创建额外的开销(即图像加载过程必须分解成非常小的块,因为它与 GUI 竞争)之外,它会在调用期间定期冻结 GUI,并且它似乎消耗了执行期间发生的任何键盘事件。

第三种方法 有没有一种方法可以检测待处理的用户事件?我怎样才能完成这样的事情?

def load_some_images():
    while True:
        try: top.pending_gui_events.get_nowait()
        except: break
        #user is still idle! continuing caching of images
        fn = get_next_image()
        cache[fn] = load_image(fn)
    top.after_idle(load_some_images)

top.after(5,load_some_images)

编辑:我尝试使用 top.tk.call('after','info') 检查待处理的键盘事件。这并不总是可靠的,并且界面仍然缓慢/无响应。

提前感谢您的任何想法

4

1 回答 1

2

我建议创建一个load_one_image函数而不是一个load_some_images函数。它不太可能干扰事件循环。

此外,根据经验,调用 via 的函数after_idle不应使用after_idle. 原因是after_idle它将阻塞,直到空闲事件队列被耗尽。如果您在处理队列时继续向队列中添加内容,则它永远不会完全耗尽。这可能是您的 GUI 在第二种方法中似乎偶尔挂起的原因。

尝试after(5, ...)而不是after_idle(...)。如果您的系统可以在不到 5 毫秒的时间内创建一个图像,那么您可以在大约半秒内处理 100 个图像,这可能足够快,可以提供一个非常漂亮的界面。您可以调整延迟以查看它如何影响应用程序的整体感觉。

于 2013-09-04T17:38:45.447 回答