3

I want to display an animated gif with Tkinter. I tried using the solutions proposed here.

The problem is that in my gif, only the first frame is complete. All other frame consist of a mostly transparent area where only the few pixels that change w.r.t. the preceding frame are non-transparent.

Hoe can I tell Tkinter that the background of those frames is supposed to be transparent?

from Tkinter import * 
from PIL import Image, ImageTk


class MyLabel(Label):
    def __init__(self, master, filename):
        im = Image.open(filename)
        seq =  []
        try:
            while 1:
                seq.append(im.copy())
                im.seek(len(seq)) # skip to next frame
        except EOFError:
            pass # we're done

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

        first = seq[0].convert('RGBA')
        self.frames = [ImageTk.PhotoImage(first)]

        Label.__init__(self, master, image=self.frames[0])

        temp = seq[0]
        for image in seq[1:]:
            temp.paste(image)
            frame = temp.convert('RGBA')
            self.frames.append(ImageTk.PhotoImage(frame))

        self.idx = 0

        self.cancel = self.after(self.delay, self.play)

    def play(self):
        self.config(image=self.frames[self.idx])
        self.idx += 1
        if self.idx == len(self.frames):
            self.idx = 0
        self.cancel = self.after(self.delay, self.play)        


root = Tk()
anim = MyLabel(root, 'animated.gif')
anim.pack()

def stop_it():
    anim.after_cancel(anim.cancel)

Button(root, text='stop', command=stop_it).pack()

root.mainloop()
Community
  • 1
  • 1
  • Don't understand what you are asking. At one point you say that everything but the changed pixels is transparent, and then you ask how to make the background transparent. How does this fit together? Also, could you link to an animated, transparent gif for testing, so we do not have to search for one ourselves? – tobias_k Dec 04 '13 at 09:14
  • Take this gif, for example: http://25.media.tumblr.com/tumblr_m3i10ma4fI1qe0eclo1_r9_500.gif The pixels are supposed to be transparent. But when I load the individual frames with TKinter, the transparent pixels are simply black. I guess I have to tell Tkinter at some point that all pixels of a certain color of a frame are supposed to be transparent. – user3064653 Dec 04 '13 at 09:21
  • Just tried it myself. With your GIF I see the same problem, except the "transparent" pixels are not black, but the `bg` of the label, so it seems the "main" frame is overwritten in this place (but kept at the borders). Also, when I created a simple animated GIF with GIMP (a simple rotating cross with transparent background) it worked just fine, but with a non-transparent first frame (like your example) I had the same problem again. My guess is that instead of stacking the images on top of each other, Tkinter is just replacing the new sections, i.e. no "cumulative" mode. – tobias_k Dec 04 '13 at 09:51
  • Yes, I'm thinking the same. It might be a limitation of the Panel widget. I'm trying to do it with a Canvas widget right now, but so far unsuccessfully. – user3064653 Dec 04 '13 at 14:38
  • Maybe you can draw the first frame on the canvas, and then the other frames on top of that, i.e., without replacing the first frame. – tobias_k Dec 04 '13 at 14:43

1 Answers1

1

Looks like you need to supply a mask for the paste operation:

from Tkinter import * 
from PIL import Image, ImageTk


class MyLabel(Label):
    def __init__(self, master, filename):
        im = Image.open(filename)
        seq =  []
        try:
            while 1:
                seq.append(im.copy())
                im.seek(len(seq)) # skip to next frame
        except EOFError:
            pass # we're done

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

        first = seq[0].convert('RGBA')
        self.frames = [ImageTk.PhotoImage(first)]

        Label.__init__(self, master, image=self.frames[0])

        lut = [1] * 256
        lut[im.info["transparency"]] = 0

        temp = seq[0]
        for image in seq[1:]:
            mask = image.point(lut, "1")
            # point() is used to map image pixels into mask pixels
            # via the lookup table (lut), creating a mask
            # with value 0 at transparent pixels and
            # 1 elsewhere
            temp.paste(image, None, mask) #paste with mask
            frame = temp.convert('RGBA')
            self.frames.append(ImageTk.PhotoImage(frame))

        self.idx = 0

        self.cancel = self.after(1000, self.play)

    def play(self):
        self.config(image=self.frames[self.idx])
        self.idx += 1
        if self.idx == len(self.frames):
            self.idx = 0
        self.cancel = self.after(self.delay, self.play)        


root = Tk()
anim = MyLabel(root, 'tumblr_m3i10ma4fI1qe0eclo1_r9_500.gif')
anim.pack()

def stop_it():
    anim.after_cancel(anim.cancel)

Button(root, text='stop', command=stop_it).pack()

root.mainloop()
Oblivion
  • 1,669
  • 14
  • 14