tkinter 中的 Gif 动画,PILL 每隔一帧闪烁一次
Gif Animation in tkinter with PILL Flickering On Every Other Frame
我正在编写一个简单的小部件来使用 PILL 在 tkinter 中为 gif 动画,因为 tkinter 本身不支持它们。我遇到的问题是某些 gif 图像在闪烁。下面是这个效果的例子。
我正在尝试制作此 gif 动画:
可以找到此 gif 源 here。
然而,当我 运行 我的代码时,动画变成这样:
我把gif分开了,每隔一帧都是这样的:
经过一些研究,我认为这是一种压缩 gif 文件的方法,因此某些帧仅代表运动。然而,我不是 100% 的,我可能在那里是错的。如果是这种情况,我该如何改造图像以重现原始 gif 中的质量?
我已经能够创建一个简单的解决方法,它只是跳过每隔一帧,但这并不能解决实际问题,而且很可能不适用于像这样的每个 gif。
我怎样才能显示帧以使动画重现 gif 的原始质量。
实现:
import tkinter as tk
from PIL import Image, ImageTk, ImageSequence
class AnimatedGif:
def __init__(self, root, src=''):
self.root = root
# Load Frames
self.image = Image.open(src)
self.frames = []
self.duration = []
for frame in ImageSequence.Iterator(self.image):
self.duration.append(frame.info['duration'])
self.frames.append(ImageTk.PhotoImage(frame))
self.counter = 0
self.image = self.frames[self.counter]
# Create Label
self.label = tk.Label(self.root, image=self.image)
self.label.pack()
# Start Animation
self.__step_frame()
def __step_frame(self):
# Update Frame
self.label.config(image=self.frames[self.counter])
self.image = self.frames[self.counter]
# Loop Counter
self.counter += 1
if self.counter >= len(self.frames):
self.counter = 0
# Queue Frame Update
self.root.after(self.duration[self.counter], lambda: self.__step_frame())
def pack(self, **kwargs):
self.label.pack(**kwargs)
def grid(self, **kwargs):
self.label.grid(**kwargs)
if __name__ in '__main__':
root = tk.Tk()
gif = AnimatedGif(root, '144.gif')
gif.pack()
root.mainloop()
处理方法是我之前认为的问题的原因。部分帧的处理方法设置为 2,每隔一帧发生一次,因为其他帧设置为 1,如下所示:
1、2、1、2、1、2、1、2...
处理方法2以上一帧为背景,只改变当前帧的不透明像素。方法 1 会将整个图像复制到另一个图像上,同时保持透明度不变。
iv) Disposal Method - Indicates the way in which the graphic is to
be treated after being displayed.
Values : 0 - No disposal specified. The decoder is
not required to take any action.
1 - Do not dispose. The graphic is to be left
in place.
2 - Restore to background color. The area used by the
graphic must be restored to the background color.
3 - Restore to previous. The decoder is required to
restore the area overwritten by the graphic with
what was there prior to rendering the graphic.
4-7 - To be defined.
Source: www.w3.org
在下面的代码中,处理方法 1 的处理等同于方法 0。(因为它只获取当前帧而不是先将其粘贴到最后一帧)这样做是因为我正在晒黑部分帧上的颜色渗出,按照代码中的处理方式处理它可提供理想的结果。
此脚本中省略了方法 3+,因为它们很少见并且与此问题无关,因为此 gif 使用方法 0 和 1。
from PIL import Image, ImageSequence
def unpack_gif(src):
# Load Gif
image = Image.open(src)
# Get frames and disposal method for each frame
frames = []
disposal = []
for gifFrame in ImageSequence.Iterator(image):
disposal.append(gifFrame.disposal_method)
frames.append(gifFrame.convert('P'))
# Loop through frames, and edit them based on their disposal method
output = []
lastFrame = None
thisFrame = None
for i, loadedFrame in enumerate(frames):
# Update thisFrame
thisFrame = loadedFrame
# If the disposal method is 2
if disposal[i] == 2:
# Check that this is not the first frame
if i != 0:
# Pastes thisFrames opaque pixels over lastFrame and appends lastFrame to output
lastFrame.paste(thisFrame, mask=thisFrame.convert('RGBA'))
output.append(lastFrame)
else:
output.append(thisFrame)
# If the disposal method is 1 or 0
elif disposal[i] == 1 or disposal[i] == 0:
# Appends thisFrame to output
output.append(thisFrame)
# If disposal method if anything other than 2, 1, or 0
else:
raise ValueError('Disposal Methods other than 2:Restore to Background, 1:Do Not Dispose, and 0:No Disposal are supported at this time')
# Update lastFrame
lastFrame = loadedFrame
return output
此脚本returns 可以进一步修改以与 tkinter 或其他 GUI 框架一起使用的图像对象列表。
我正在编写一个简单的小部件来使用 PILL 在 tkinter 中为 gif 动画,因为 tkinter 本身不支持它们。我遇到的问题是某些 gif 图像在闪烁。下面是这个效果的例子。
我正在尝试制作此 gif 动画:
可以找到此 gif 源 here。
然而,当我 运行 我的代码时,动画变成这样:
我把gif分开了,每隔一帧都是这样的:
经过一些研究,我认为这是一种压缩 gif 文件的方法,因此某些帧仅代表运动。然而,我不是 100% 的,我可能在那里是错的。如果是这种情况,我该如何改造图像以重现原始 gif 中的质量?
我已经能够创建一个简单的解决方法,它只是跳过每隔一帧,但这并不能解决实际问题,而且很可能不适用于像这样的每个 gif。
我怎样才能显示帧以使动画重现 gif 的原始质量。
实现:
import tkinter as tk
from PIL import Image, ImageTk, ImageSequence
class AnimatedGif:
def __init__(self, root, src=''):
self.root = root
# Load Frames
self.image = Image.open(src)
self.frames = []
self.duration = []
for frame in ImageSequence.Iterator(self.image):
self.duration.append(frame.info['duration'])
self.frames.append(ImageTk.PhotoImage(frame))
self.counter = 0
self.image = self.frames[self.counter]
# Create Label
self.label = tk.Label(self.root, image=self.image)
self.label.pack()
# Start Animation
self.__step_frame()
def __step_frame(self):
# Update Frame
self.label.config(image=self.frames[self.counter])
self.image = self.frames[self.counter]
# Loop Counter
self.counter += 1
if self.counter >= len(self.frames):
self.counter = 0
# Queue Frame Update
self.root.after(self.duration[self.counter], lambda: self.__step_frame())
def pack(self, **kwargs):
self.label.pack(**kwargs)
def grid(self, **kwargs):
self.label.grid(**kwargs)
if __name__ in '__main__':
root = tk.Tk()
gif = AnimatedGif(root, '144.gif')
gif.pack()
root.mainloop()
处理方法是我之前认为的问题的原因。部分帧的处理方法设置为 2,每隔一帧发生一次,因为其他帧设置为 1,如下所示:
1、2、1、2、1、2、1、2...
处理方法2以上一帧为背景,只改变当前帧的不透明像素。方法 1 会将整个图像复制到另一个图像上,同时保持透明度不变。
iv) Disposal Method - Indicates the way in which the graphic is to be treated after being displayed.
Values : 0 - No disposal specified. The decoder is not required to take any action. 1 - Do not dispose. The graphic is to be left in place. 2 - Restore to background color. The area used by the graphic must be restored to the background color. 3 - Restore to previous. The decoder is required to restore the area overwritten by the graphic with what was there prior to rendering the graphic. 4-7 - To be defined.
Source: www.w3.org
在下面的代码中,处理方法 1 的处理等同于方法 0。(因为它只获取当前帧而不是先将其粘贴到最后一帧)这样做是因为我正在晒黑部分帧上的颜色渗出,按照代码中的处理方式处理它可提供理想的结果。
此脚本中省略了方法 3+,因为它们很少见并且与此问题无关,因为此 gif 使用方法 0 和 1。
from PIL import Image, ImageSequence
def unpack_gif(src):
# Load Gif
image = Image.open(src)
# Get frames and disposal method for each frame
frames = []
disposal = []
for gifFrame in ImageSequence.Iterator(image):
disposal.append(gifFrame.disposal_method)
frames.append(gifFrame.convert('P'))
# Loop through frames, and edit them based on their disposal method
output = []
lastFrame = None
thisFrame = None
for i, loadedFrame in enumerate(frames):
# Update thisFrame
thisFrame = loadedFrame
# If the disposal method is 2
if disposal[i] == 2:
# Check that this is not the first frame
if i != 0:
# Pastes thisFrames opaque pixels over lastFrame and appends lastFrame to output
lastFrame.paste(thisFrame, mask=thisFrame.convert('RGBA'))
output.append(lastFrame)
else:
output.append(thisFrame)
# If the disposal method is 1 or 0
elif disposal[i] == 1 or disposal[i] == 0:
# Appends thisFrame to output
output.append(thisFrame)
# If disposal method if anything other than 2, 1, or 0
else:
raise ValueError('Disposal Methods other than 2:Restore to Background, 1:Do Not Dispose, and 0:No Disposal are supported at this time')
# Update lastFrame
lastFrame = loadedFrame
return output
此脚本returns 可以进一步修改以与 tkinter 或其他 GUI 框架一起使用的图像对象列表。