Python 使用PIL在Tkinter中显示动画GIF
我正在尝试制作一个程序,用Tkinter显示动画GIF。以下是我最初使用的代码:Python 使用PIL在Tkinter中显示动画GIF,python,tkinter,python-imaging-library,animated-gif,Python,Tkinter,Python Imaging Library,Animated Gif,我正在尝试制作一个程序,用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): ## Retur
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
然而,这会偶尔闪现一张图片。虽然图片看起来不错,但作为一个程序,它却毫无用处。我做错了什么 首先,为每一帧创建一个新的画布对象。最终你会有成千上万的图像堆叠在一起。这是非常低效的;当您开始拥有数千个对象时,canvas小部件会出现性能问题 与其在画布上创建新的图像对象,不如使用画布的方法重新配置现有对象 其次,对于这样一个简单的任务,您不需要线程的复杂性。tkinter中有一个众所周知的模式用于制作动画:绘制一个帧,然后让该函数在之后使用在将来调用自己 大概是这样的:
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)
for i, item in enumerate(pics[1:]):
temp.paste(item)
temp.save('temp{}.png'.format(i))
gif['frames'].append(ImageTk.PhotoImage(temp.convert('RGBA')))
最后,除非有严格的理由使用画布,否则您可以通过使用标签小部件稍微降低复杂性。您的问题与Tkinter无关。(据我所知,您可能也有Tk问题,但在使用Tk之前,您的图像已经很糟糕了。) 我测试这一点的方法是修改
anim\u gif
函数,将帧作为单独的图像文件写入,方法是更改pics[1:][/code>循环中项目的:
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)
for i, item in enumerate(pics[1:]):
temp.paste(item)
temp.save('temp{}.png'.format(i))
gif['frames'].append(ImageTk.PhotoImage(temp.convert('RGBA')))
第一个文件,temp0.png
,已经搞砸了,没有调用与Tk相关的代码
事实上,您可以更轻松地测试相同的内容:
from PIL import Image
im = Image.open('test.gif')
temp = im.copy()
im.seek(1)
temp.paste(im.copy())
temp.save('test.png')
问题是,您正在将第1帧的像素粘贴到第0帧的像素之上,但保留了第0帧的调色板
有两种简单的方法可以解决这个问题
首先,使用RGBA转换帧而不是调色板颜色帧:
temp = pics[0].convert('RGBA')
gif['frames'] = [ImageTk.PhotoImage(temp)]
for item in pics[1:]:
frame = item.convert('RGBA')
temp.paste(frame)
gif['frames'].append(ImageTk.PhotoImage(temp))
第二,根本不使用复制和粘贴;只需将每个帧复制为独立图像:
gif['frames'] = [ImageTk.PhotoImage(frame.convert('RGBA')) for frame in pics]
首先要做的是找出问题所在。每次通过循环尝试将temp
保存到一个新的.PNG文件中。他们已经搞砸了吗?如果是这样,那么Tkinter与问题无关,这意味着您可以编写更小的代码;试图修复Tkinter的东西是在错误的树上追逐一只雁。相关报道:不管谁投了反对票,想解释一下原因吗?这解释了OP的代码不工作的原因,以及如何修复它。当然,用更聪明的方式重写整件事,同时碰巧没有犯同样的错误也解决了问题,但这并不能解释OP到底做错了什么。