使用 PIL 在 Tkinter 中显示动画 GIF [英] Displaying animated GIFs in Tkinter using PIL

查看:33
本文介绍了使用 PIL 在 Tkinter 中显示动画 GIF的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试制作一个使用 Tkinter 显示动画 GIF 的程序.这是我最初使用的代码:

from __future__ import Division # 只是因为在 2.7.4 中除法不起作用从 Tkinter 导入 *从 PIL 导入 Image,ImageTk进口螺纹从时间导入睡眠def anim_gif(名称):## 返回 { 'frames', 'delay', 'loc', 'len' }im = Image.open(名称)gif = {'帧':[],'延迟':100,'位置':0,'连':0 }图片 = []尝试:为真:pics.append(im.copy())im.seek(len(图片))除了EOFError:通过temp = pics[0].convert('RGBA')gif['frames'] = [ImageTk.PhotoImage(temp)]温度 = 图片 [0]对于图片中的项目[1:]:临时粘贴(项目)gif['frames'].append(ImageTk.PhotoImage(temp.convert('RGBA')))尝试:gif['延迟'] = im.info['持续时间']除了:通过gif['len'] = len(gif['frames'])返回gif定义比率(a,b):如果 b  方法.

其次,对于如此简单的任务,您不需要线程的复杂性.tkinter 中有一个众所周知的动画模式:绘制一个框架,然后让该函数在将来使用 after 调用自身.

像这样:

def animate(self):如果 self._image_id 是 None:self._image_id = self.display.create_image(...)别的:self.itemconfig(self._image_id, image= the_new_image)self.display.after(self.gif["delay"], self.animate)

最后,除非有严格的理由使用画布,否则您可以通过使用标签小部件来稍微降低复杂性.

I'm trying to make a program to display an animated GIF using Tkinter. Here is the code that I originally used:

from __future__ import division # Just because division doesn't work right in 2.7.4
from Tkinter import *
from PIL import Image,ImageTk
import threading
from time import sleep

def anim_gif(name):
    ## Returns { 'frames', 'delay', 'loc', 'len' }
    im = Image.open(name)
    gif = { 'frames': [],
            'delay': 100,
            'loc' : 0,
            'len' : 0 }
    pics = []
    try:
        while True:
            pics.append(im.copy())
            im.seek(len(pics))
    except EOFError: pass

    temp = pics[0].convert('RGBA')
    gif['frames'] = [ImageTk.PhotoImage(temp)]
    temp = pics[0]
    for item in pics[1:]:
        temp.paste(item)
        gif['frames'].append(ImageTk.PhotoImage(temp.convert('RGBA')))

    try: gif['delay'] = im.info['duration']
    except: pass
    gif['len'] = len(gif['frames'])
    return gif

def ratio(a,b):
    if b < a: d,c = a,b
    else: c,d = a,b
    if b == a: return 1,1
    for i in reversed(xrange(2,int(round(a / 2)))):
        if a % i == 0 and b % i == 0:
            a /= i
            b /= i
    return (int(a),int(b))

class App(Frame):
    def show(self,image=None,event=None):
        self.display.create_image((0,0),anchor=NW,image=image)   

    def animate(self,event=None):
        self.show(image=self.gif['frames'][self.gif['loc']])
        self.gif['loc'] += 1
        if self.gif['loc'] == self.gif['len']:
            self.gif['loc'] = 0
        if self.cont:
            threading.Timer((self.gif['delay'] / 1000),self.animate).start()

    def kill(self,event=None):
        self.cont = False
        sleep(0.1)
        self.quit()

    def __init__(self,master):
        Frame.__init__(self,master)
        self.grid(row=0,sticky=N+E+S+W)
        self.rowconfigure(1,weight=2)
        self.rowconfigure(3,weight=1)
        self.columnconfigure(0,weight=1)
        self.title = Label(self,text='No title')
        self.title.grid(row=0,sticky=E+W)
        self.display = Canvas(self)
        self.display.grid(row=1,sticky=N+E+S+W)
        self.user = Label(self,text='Posted by No Username')
        self.user.grid(row=2,sticky=E+W)
        self.comment = Text(self,height=4,width=40,state=DISABLED)
        self.comment.grid(row=3,sticky=N+E+S+W)
        self.cont = True
        self.gif = anim_gif('test.gif')
        self.animate()

        root.protocol("WM_DELETE_WINDOW",self.kill)


root = Tk()
root.rowconfigure(0,weight=1)
root.columnconfigure(0,weight=1)
app = App(root)
app.mainloop()

try: root.destroy()
except: pass

test.gif is the following GIF:

This works fine, but the GIF quality is terrible. I tried changing it to what follows:

def anim_gif(name):
    ## Returns { 'frames', 'delay', 'loc', 'len' }
    im = Image.open(name)
    gif = { 'frames': [],
            'delay': 100,
            'loc' : 0,
            'len' : 0 }
    pics = []
    try:
        while True:
            gif['frames'].append(im.copy())
            im.seek(len(gif['frames']))
    except EOFError: pass

    try: gif['delay'] = im.info['duration']
    except: pass
    gif['len'] = len(gif['frames'])
    return gif

class App(Frame):
    def show(self,image=None,event=None):
        can_w = self.display['width']
        can_h = self.display['height']

        pic_w,pic_h = image.size
        rat_w,rat_h = ratio(pic_w,pic_h)

        while pic_w > int(can_w) or pic_h > int(can_h):
            pic_w -= rat_w
            pic_h -= rat_h

        resized = image.resize((pic_w,pic_h))
        resized = ImageTk.PhotoImage(resized)
        self.display.create_image((0,0),anchor=NW,image=resized)   

However, this will occasionally flash a picture. While the picture looks good, it's pretty useless as a program. What am I doing wrong?

解决方案

For one, you are creating a new canvas object for every frame. Eventually you will have thousands of images stacked on top of one another. This is highly inefficient; the canvas widget has performance issues when you start to have thousands of objects.

Instead of creating new image objects on the canvas, just reconfigure the existing object with the itemconfig method of the canvas.

Second, you don't need the complexities of threading for such a simple task. There is a well known pattern in tkinter for doing animations: draw a frame, then have that function use after to call itself in the future.

Something like this:

def animate(self):
    if self._image_id is None:
        self._image_id = self.display.create_image(...)
    else:
        self.itemconfig(self._image_id, image= the_new_image)
    self.display.after(self.gif["delay"], self.animate)

Finally, unless there's a strict reason to use a canvas, you can lower the complexity a little more by using a Label widget.

这篇关于使用 PIL 在 Tkinter 中显示动画 GIF的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆