我正在尝试制作一个程序来使用Tkinter显示动画GIF。这是我最初使用的代码:
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是以下GIF:
这可以正常工作,但是GIF的质量很糟糕。我尝试将其更改为以下内容:
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)
但是,这偶尔会闪烁图像。虽然图片看起来不错,但是作为程序却毫无用处。我究竟做错了什么?
首先,您要为每个帧创建一个新的画布对象。最终,您将有成千上万的图像相互叠加。这是非常低效的;当您开始具有数千个对象时,canvas小部件会出现性能问题。
与其在画布上创建新的图像对象,不如使用画布的itemconfig方法重新配置现有对象。
其次,对于这样一个简单的任务,您不需要复杂的线程处理。在tkinter中有一个众所周知的用于制作动画的模式:绘制一帧,然后after在将来使用该函数进行调用。
after
像这样:
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)
最后,除非有严格的理由使用画布,否则可以通过使用Label小部件来进一步降低复杂性。