使用tkinter在Python中播放动态GIF

8
我想用Python3和Tkinter创建一个虚拟宠物游戏。到目前为止,我已经有了主窗口,并开始添加标签,但我遇到的问题是播放动画gif。我在这里搜索并找到了一些答案,但它们不断出现错误。我找到的结果使用PhotoImage继续通过某个范围来确定gif的索引位置。
    # Loop through the index of the animated gif
frame2 = [PhotoImage(file='images/ball-1.gif', format = 'gif -index %i' %i) for i in range(100)]

def update(ind):

    frame = frame2[ind]
    ind += 1
    img.configure(image=frame)
    ms.after(100, update, ind)

img = Label(ms)
img.place(x=250, y=250, anchor="center")

ms.after(0, update, 0)
ms.mainloop()

当我在终端中使用"python3 main.py"运行时,出现以下错误:

_tkinter.TclError:此索引没有图像数据

我忽略了什么或完全遗漏了什么?

这是GitHub存储库的链接,以查看完整项目:VirtPet_Python

提前感谢!


2
你不应该检查 ind 是否超过100吗?也许可以使用 ind %= 100 - Alvaro Fuentes
4个回答

23

这个错误意味着你尝试加载100帧,但gif图片中的帧数不足。

在tkinter中使用动画gif通常效果很差。我写了很久以前的这段代码,你可以拿去使用,但是如果处理大的gif图片会出现卡顿问题:

import tkinter as tk
from PIL import Image, ImageTk
from itertools import count

class ImageLabel(tk.Label):
    """a label that displays images, and plays them if they are gifs"""
    def load(self, im):
        if isinstance(im, str):
            im = Image.open(im)
        self.loc = 0
        self.frames = []

        try:
            for i in count(1):
                self.frames.append(ImageTk.PhotoImage(im.copy()))
                im.seek(i)
        except EOFError:
            pass

        try:
            self.delay = im.info['duration']
        except:
            self.delay = 100

        if len(self.frames) == 1:
            self.config(image=self.frames[0])
        else:
            self.next_frame()

    def unload(self):
        self.config(image="")
        self.frames = None

    def next_frame(self):
        if self.frames:
            self.loc += 1
            self.loc %= len(self.frames)
            self.config(image=self.frames[self.loc])
            self.after(self.delay, self.next_frame)

root = tk.Tk()
lbl = ImageLabel(root)
lbl.pack()
lbl.load('ball-1.gif')
root.mainloop()

好的,我已经编辑了我的代码并将其推送到GitHub。现在它循环遍历13个帧,但完整的图像没有显示出来,然后当它通过所有帧时,我会得到一个索引超出范围的错误。 - nmoore146
1
我最初不使用它的原因是我在使用Python3时遇到了一些与PIL相关的问题,但是在我离开后似乎已经解决了。感谢您的帮助。我相信我会遇到更多问题,现在我对来到Stack Overflow感到更加放心。 - nmoore146
我把这个放在这里作为修复py37+透明度和闪烁的方法:添加seq=[]到开头,将self.frames.append改为seq.append。最后,在self.delay之后添加first = seq[0].convert('RGBA'); self.frames = [ImageTk.PhotoImage(first)]; self.config(image=self.frames[0]); for image in seq[1:]:; self.frames.append(ImageTk.PhotoImage(image.convert('RGBA')));(分号表示新行)。 - Scott Paterson
@ScottPaterson 谢谢,但那个修复方法相当慢,它会导致长时间的加载和减缓已经很慢的动画。出于性能原因,最好将gif文件转换为非压缩格式,并使用适当的背景颜色。 - Novel
@Novel 是的,它大致会使加载时间加倍。我有设置来控制背景颜色,因此将动画设置为纯色不太实际。但是对于帧数较低的小动画,仍然可以作为快速修复使用。在256x256的60-80帧中,加载时间没有明显差异(<0.2秒)。 - Scott Paterson
显示剩余3条评论

3

首先,你需要知道你的GIF文件最后一个范围是什么。通过改变i的不同值,你可以得到它。我的情况是31。 然后只需要放置条件即可。这样它就会无限地播放gif。

from tkinter import *

root = Tk()

frames = [
    PhotoImage(file="./images/play.gif", format="gif -index %i" % (i))
    for i in range(31)
]

def update(ind):
    frame = frames[ind]
    ind += 1
    print(ind)
    if ind > 30:  # With this condition it will play gif infinitely
        ind = 0
    label.configure(image=frame)
    root.after(100, update, ind)

label = Label(root)
label.pack()
root.after(0, update, 0)
root.mainloop()

0

一个非常简单的方法是使用多线程。

要在Tkinter窗口中无限运行GIF,您应该按照以下步骤进行:

  1. 创建一个运行GIF的函数。
  2. 将运行GIF的代码放在函数的while True内部。
  3. 创建一个线程来运行函数。
  4. 在程序的主线程中运行root.mainloop()
  5. 使用time.sleep()控制动画速度。

请参考下面的代码:

i=0
ph = ImageTk.PhotoImage(Image.fromarray(imageframes[i]))
imglabel=Label(f2,image=ph)
imglabel.grid(row=0,column=0)

def runthegif(root,i):
    
    while True:
        i = i + 7
        i= i % 150
        
        ph=ImageTk.PhotoImage(PhotoImage(file='images/ball.gif',format='gif -index %i' %i))
        imagelabel=Label(f2,image=ph)
        imagelabel.grid(row=0,column=0)
        time.sleep(0.1)
    


t1=threading.Thread(target=runthegif,args=(root,i))
t1.start()


root.mainloop()

我已经更改了你的代码块,以明确说明它是Python代码。此外,我删除了你的结尾句子,原因是我们倾向于不在SO帖子中留下“啰嗦”的感谢之类的话语。话虽如此,你总是可以查看帖子的历史记录,只需点击“edited ... ago”后面的链接,你就会进入一个修订页面像这个,在那里你可以看到所有更改的历史记录。 - β.εηοιτ.βε

-1
我已经更新了一个答案,这是完整的代码。
import tkinter as tk
from PIL import Image, ImageTk
from itertools import count

class ImageLabel(tk.Label):
    """a label that displays images, and plays them if they are gifs"""
    def load(self, im):
        if isinstance(im, str):
            im = Image.open(im)
        self.loc = 0
        self.frames = []

        try:
            for i in count(1):
                self.frames.append(ImageTk.PhotoImage(im.copy()))
                im.seek(i)
        except EOFError:
            pass

        try:
            self.delay = im.info['duration']
        except:
            self.delay = 100

        if len(self.frames) == 1:
            self.config(image=self.frames[0])
        else:
            self.next_frame()

    def unload(self):
        self.config(image="")
        self.frames = None

    def next_frame(self):
        self.update()
        if self.frames:
            self.loc += 1
            self.loc %= len(self.frames)
            self.config(image=self.frames[self.loc])
            self.after(self.delay, self.next_frame)

root = tk.Tk()

lbl = ImageLabel(root)

lbl.pack()

lbl.load('C:/AI/Picturse/Animation/idle_animation.gif')

root.mainloop()

这并没有回答问题。一旦你有足够的声望,你就可以评论任何帖子;相反,提供不需要提问者澄清的答案。- 来自评论 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接