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 框架一起使用的图像对象列表。