OpenCV Video with Tkinter and threading Crash - python

I'm trying to make a multithreaded program with Python, OpenCV, and Tkinter.
My program has some general point.
Load Video from file
Create 2 thread
1st thread to fetch frames from capture object and put it to python Queue
2nd thread to get the frames from Queue
At last, if possible, start and stop the capture object
However, my script seems to behave weirdly. Sometimes it can finish playing video until the end, but sometimes it also crash at some point of the video. Here is what I've got so far.
from Tkinter import Tk, Text
from Tkinter import PhotoImage
from ttk import Frame, Scrollbar, Button, Label
from PIL import Image, ImageTk
import cv
import time
import Queue
import threading
def writeToLog(log, msg):
numlines = log.index('end - 1 line').split('.')[0]
if log.index('end-1c')!='1.0': log.insert('end', '\n')
log.insert('end', msg)
log.see('end')
def GetIplImageMode(img):
orientation = 1 if img.origin == 0 else -1
mode_list = {(1, cv.IPL_DEPTH_8U) : ("L", "L", 1),\
(3, cv.IPL_DEPTH_8U) : ("BGR", "RGB", 3),\
(1, cv.IPL_DEPTH_32F) : ("F", "F", 4)}
key = (img.nChannels, img.depth)
modes = mode_list[key]
return [modes[0], modes[1], orientation]
def IplImage2PIL(img, mode):
return Image.fromstring(mode[1], (img.width, img.height),\
img.tostring(), "raw", mode[0],\
img.width * img.channels,\
mode[2])
def ResizePILImage(pil, width = 260, height = 180):
return pil.resize((width, height), Image.ANTIALIAS)
def PIL2TkImage(pil):
return ImageTk.PhotoImage(pil)
def setImageLabelFromIplImage(label, img_ipl):
mode = GetIplImageMode(img_ipl)
img_pil = IplImage2PIL(img_ipl, mode)
img_resized = ResizePILImage(img_pil)
img_tk = PIL2TkImage(img_resized)
label.configure(image = img_tk)
label.image = img_tk
def setImageLabelFromFile(label, szFileName):
img_ipl = cv.LoadImage(szFileName)
setImageLabelFromIplImage(label, img_ipl)
def mat_from_ipl(ipl):
return cv.GetMat(ipl)
def ipl_from_mat(mat):
ipl = cv.CreateImageHeader((mat.width, mat.height),\
cv.IPL_DEPTH_8U, mat.channels)
cv.SetData(ipl, mat.tostring())
return ipl
class asdf(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.pack(fill='both', expand=True)
self.parent = parent
self.variables()
self.ui()
def variables(self):
self.ctr = 0
self.fps = 0
self.video = None
self.image = None
self.putProc = None
self.getProc = None
self.isRunning = False
self.queue = Queue.Queue()
def ui(self):
f1 = Frame(self)
frm1 = Frame(f1)
self.lbl1 = Label(frm1, image=None)
setImageLabelFromFile(self.lbl1, '../image.bmp')
self.txt1 = Text(frm1, width=30, height=8)
sb1 = Scrollbar(frm1, orient='vertical', command=self.txt1.yview)
self.txt1.configure(yscrollcommand = sb1.set)
self.lbl1.pack()
self.txt1.pack(side='left')
sb1.pack(side='left', fill='y')
frm1.pack(side='left')
frm2 = Frame(f1)
self.lbl2 = Label(frm2, image=None)
setImageLabelFromFile(self.lbl2, '../image.bmp')
self.txt2 = Text(frm2, width=30, height=8)
sb2 = Scrollbar(frm2, orient='vertical', command=self.txt2.yview)
self.txt2.configure(yscrollcommand = sb2.set)
self.lbl2.pack()
self.txt2.pack(side='left')
sb2.pack(side='left', fill='y')
frm2.pack(side='left')
f1.pack()
f2 = Frame(self)
Button(f2, text='Run', command=self.run).pack(side='left')
Button(f2, text='Stop', command=self.stop).pack(side='left')
f2.pack()
def put_to_queue(self):
while self.isRunning:
self.ctr = self.ctr + 1
self.image = cv.QueryFrame(self.video)
time.sleep(1 / self.fps)
try:
writeToLog(self.txt1, '\nPut to queue .. %d' % (self.ctr))
temp1 = cv.CloneImage(self.image)
setImageLabelFromIplImage(self.lbl1, temp1)
temp2 = mat_from_ipl(temp1)
self.queue.put([self.ctr, temp2])
except:
writeToLog(self.txt1, '\nReach end of video ..')
break
def get_from_queue(self):
while self.isRunning:
from_queue = self.queue.get()
self.ctr_fr = from_queue[0]
if self.ctr_fr == self.ctr: time.sleep(30 / self.fps)
temp1 = ipl_from_mat(from_queue[1])
setImageLabelFromIplImage(self.lbl2, temp1)
writeToLog(self.txt2, '\nGet from queue .. %d' % (self.ctr_fr))
time.sleep(1 / self.fps)
def run(self):
self.isRunning = True
self.video = cv.CreateFileCapture('../video.avi')
self.fps = cv.GetCaptureProperty(self.video, cv.CV_CAP_PROP_FPS)
writeToLog(self.txt1, '\nStart put_queue ..')
self.putProc = threading.Thread(target=self.put_to_queue)
self.putProc.start()
time.sleep(1)
writeToLog(self.txt2, '\nStart get_queue ..')
self.getProc = threading.Thread(target=self.get_from_queue)
self.getProc.start()
def stop(self):
self.isRunning = False
if self.putProc.isAlive():
self.putProc._Thread__stop()
writeToLog(self.txt1, '\nputProc still alive, stopping ..')
self.putProc = None
if self.getProc.isAlive():
self.getProc._Thread__stop()
writeToLog(self.txt2, '\ngetProc still alive, stopping ..')
self.getProc = None
self.ctr_fr = 0
self.ctr = 0
if __name__ == '__main__':
root = Tk()
c = asdf(root)
root.mainloop()
Am I doing it wrong?
Any ideas will be very appreciated.
Thanks

Related

Python: parallel processing and tkinter

I am trying to implement a piano game. The rules are simple: a note is played and after 5 seconds the answer is shown. The problem is that I want the user to be able to play the notes while the program waits for those 5 seconds. Right now the program creates a new GUI window when the the gamemode's process starts.
Gamemode class:
from multiprocessing import Process
import random
import time
class Practice(Process):
def __init__(self, keys, rounds = 5):
super(Process, self).__init__()
self.rounds = rounds
self.keys = keys
def run(self):
for i in range(3):
answer = self.__random_key()
answer.play()
time.sleep(3)
print("woke up")
self.show_answer()
def show_answer(self, answer):
print(f"Answer is: {answer.name}")
def __random_key(self):
return self.keys[random.randint(0, len(self.keys) - 1)]
piano key class:
import asyncio
import copy
import time
from tkinter import Button
from pygame import mixer
class PianoKey():
def __init__(self, master, note = None, octave = None, color = None):
self.master = master
self.note = note
self.color = color
self.octave = octave
self.name = f"{note}{octave}"
self.button = Button(self.master, bg = color, command = lambda : self.play())
def get_piano_button(self):
return self.button
def empty_copy(self):
button = self.button
self.button = None
result = copy.copy(self)
self.button = button
def change_color(self, color):
self.button.config(bg = color)
def play(self):
path = "PATH_OF_NOTES_FOLDER"
print(f"played {self.name}")
# mixer.music.load("B3.mp3")
# mixer.music.play()
Piano class and subclasses:
from core.GUI.gui_piece import GUIPiece, GUITypes
from core.GUI.piano_key import PianoKey
from core.game_modes.Practice import Practice
class Piano(GUIPiece):
def __init__(self, master, relx, rely, relwidth, relheight, background = "White", octaves = 3):
super().__init__(master, relx, rely, relwidth, relheight, GUITypes.FRAME, background)
self.keys = []
self.initiate_keys(octaves)
self.mode = Practice(self.keys)
def get_piano(self):
return self.gui
def initiate_keys(self, octaves):
white_width = 1/(octaves * 7)
self.initiate_key(octaves=octaves, keys=["C", "D", "E", "F", "G", "A", "B"], color="White", positions=[0,1,2,3,4,5,6], width = white_width, relxwidth = white_width, minus_starting = 0, relh = 1)
black_width = white_width * 0.7
self.initiate_key(octaves=octaves, keys=["Cb", "Db", "Fb", "Gb", "Ab"], color="Black", positions=[1,2,4,5,6], width = black_width, relxwidth = white_width, minus_starting= black_width/2, relh = 0.6)
def start_practice(self):
self.mode.start()
# t = threading.Thread(target=self.mode.play())
# print("now going in for loop")
# t.start()
def empty_keys_copy(self):
return [key.empty_copy() for key in self.keys]
def initiate_key(self, octaves, keys, color, positions, width, relxwidth, minus_starting, relh):
for o in range(octaves):
octave = o * 7
for index in range(len(positions)):
relx = relxwidth * (octave + positions[index])- minus_starting
key = PianoKey(self.get_piano(), keys[index], o, color)
self.keys.append(key)
key.get_piano_button().place(relx=relx, rely=0, relwidth= width, relheight = relh)
def show_answer(self):
print(f"Answer is: {self.answer.name}")
self.answer.change_color('Red')
class GUITypes(Enum):
LABEL = 0,
FRAME = 1,
CHECKBUTTON = 2,
BUTTON = 3
class GUIPiece():
def __init__(self, master, relx, rely, relwidth, relheight, type, background = 'Grey', parent = None, text = ""):
self.master = master
self.relx = relx
self.rely = rely
self.relwidth = relwidth
self.relheight = relheight
if type == GUITypes.LABEL:
self.gui = Label(bg = background)
elif type == GUITypes.FRAME:
self.gui = Frame(bg=background)
elif type == GUITypes.CHECKBUTTON:
self.gui = Checkbutton(bg=background)
elif type == GUITypes.BUTTON:
self.gui = Button(bg=background)
else:
raise AttributeError("Please select a GUIType.")
if text != "":
self.set_text(text)
#adapt the relative sizes
if parent != None:
self.relx *= parent.relwidth
self.rely *= parent.relheight
self.relwidth = relwidth * parent.relwidth
self.relheight = relheight * parent.relheight
def get_gui(self):
return self.gui
def set_text(self, text):
self.get_gui().config(text = text)
def set_gui(self, gui):
self.gui = gui
def place(self):
self.gui.place(relx = self.relx, rely = self.rely, relwidth = self.relwidth, relheight = self.relheight)
def get_placements(self):
return self.relx, self.rely, self.relwidth, self.relheight
class GUIButton(GUIPiece):
def __init__(self, master, relx, rely, relwidth, relheight, background = "White", parent = None, text = ""):
super().__init__(master, relx, rely, relwidth, relheight, GUITypes.BUTTON, background, parent = parent, text = text)
main:
from tkinterdnd2 import *
from tkinter import *
from core.GUI.pian import Piano
from core.GUI.top_menu import TopMenu
from core.GUI.top_menu_parts.top_menu_pieces import GUIButton
from core.configurations import PIANO_RELX, PIANO_RELY, PIANO_RELWIDTH, PIANO_RELHEIGHT, TOPMENU_RELHEIGHT
from core.game_modes.Practice import Practice
root = Tk()
# set window title
root.wm_title("Tkinter window")
root.geometry("1600x700")
p = Piano(root, relx=PIANO_RELX, rely=PIANO_RELY, relwidth=PIANO_RELWIDTH, relheight=PIANO_RELHEIGHT, background="White")
p.place()
# t = TopMenu(root, relx=0, rely=0, relwidth=1, relheight=TOPMENU_RELHEIGHT, background="Pink")
start = GUIButton(root, relx=0, rely=0, relwidth=1, relheight=TOPMENU_RELHEIGHT, text="practice")
start.place()
# t.place()
mode = Practice(p.keys)
start.get_gui().config(command = lambda: p.start_practice())
root.mainloop()
Use the root.after() method. It can delay like time.sleep() but can also call a function when the timeout finishes. In this way, the user can play the piano notes while the program is waiting. Here is a tutorial regarding root.after().

Adding GIF or Image to tkinter's ScrolledText widget. (Or another implementation of chat with animated emotes like BTTV)

i'm working on creating a chat, like on a twitch, in my project using Tkinter. Is it possible to make a chat widget with animated (and not only) emojis in messages?
This is how it looks.
code:
def print_mess(self, mess):
self.console.configure(state='normal')
self.console.insert(END, mess.formated_time() + " ", 'timesign')
self.console.tag_config('timesign', foreground='#C0C0C0')
self.console.insert(END, mess.username, mess.username)
self.console.tag_config(mess.username, foreground=mess.usercolor)
self.console.insert(END, ": ", 'mess')
self.console.insert(END, mess.message + "\n", 'mess')
self.console.tag_config('mess', foreground='#FFFFFF')
self.console.yview(END) # autoscroll
self.console.configure(state='disabled')
There are multiple ways one way is to use a label and add it to the Text widget using window_create()
Example: (drag and drop a gif. click on the gif to play, Modify this to suit your need).
import tkinter as tk
from PIL import Image, ImageTk
import windnd
class Text(tk.Text):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.gif = {}
self.index = 0
self.delay = 20
self.currentlyPlaying = None
self.currentId = None
def insert_gif(self, path):
gif = GifMaker(path)
label = tk.Label(image=gif.frames[0], bd=3)
label.bind('<1>', lambda event: self.playGif(label))
self.window_create("end", window=label)
self.gif[label] = gif
self.playGif(label)
def playGif(self, label):
if self.currentlyPlaying is None:
self.currentlyPlaying = label
if self.currentlyPlaying != label: # makes sure only one gif is played at any time
self.index = 0
self.currentlyPlaying.configure(image=self.gif[self.currentlyPlaying].frames[0])
self.currentlyPlaying = label
self.after_cancel(self.currentId)
self.index += 1
if self.index == self.gif[self.currentlyPlaying].n_frames-1:
self.index = 0
self.currentlyPlaying.configure(image=self.gif[self.currentlyPlaying].frames[self.index])
if self.index != 0:
self.currentId = self.after(self.delay, self.playGif, self.currentlyPlaying)
class GifMaker:
def __init__(self, path):
self.path = path
self.image = Image.open(path)
self.n_frames = self.image.n_frames # number of frames
self.frames = []
self.duration = 0 # total duration of the gif
for x in range(self.n_frames):
img = ImageTk.PhotoImage(self.image.copy())
self.duration += self.image.info['duration']
self.frames.append(img)
self.image.seek(x)
self.delay = self.duration//self.n_frames
def dropped(file):
text.insert_gif(file[0])
root = tk.Tk()
text = Text()
text.pack(fill='both', expand=True)
windnd.hook_dropfiles(root, func=dropped)
root.mainloop()
other way is to use .image_create() to create the image and use .image_configure() to update the image. (Recommended)
import tkinter as tk
from PIL import Image, ImageTk
import windnd
class Text(tk.Text):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.gif = {}
self.frame_index = 0
self.delay = 10
def changeCursor(self, event):
if event.type == '7':
self.configure(cursor="hand2")
else:
self.configure(cursor="xterm")
def insert_gif(self, path):
gif = GifMaker(path)
index = self.image_create("end", image=gif.frames[0])
self.tag_add(f"{index}", 'end-2c')
self.gif[index] = gif
self.tag_bind(f"{index}", "<1>", lambda event: self.playGif(index))
self.tag_bind(f"{index}", "<Enter>", self.changeCursor)
self.tag_bind(f"{index}", "<Leave>", self.changeCursor)
self.playGif(index)
def playGif(self, label):
self.frame_index += 1
self.image_configure(label, image=self.gif[label].frames[self.frame_index])
if self.frame_index == self.gif[label].n_frames-1:
self.frame_index = 0
self.image_configure(label, image=self.gif[label].frames[0])
if self.frame_index != 0:
#self.after(self.gif[label].delay, self.playGif, label)
self.after(self.delay, self.playGif, label)
class GifMaker:
def __init__(self, path):
self.path = path
self.image = Image.open(path)
self.n_frames = self.image.n_frames # number of frames
self.frames = []
self.duration = 0 # total duration of the gif
for x in range(self.n_frames):
img = ImageTk.PhotoImage(self.image.copy())
self.duration += self.image.info['duration']
self.frames.append(img)
self.image.seek(x)
self.delay = self.duration//self.n_frames
def dropped(file):
text.insert_gif(file[0])
root = tk.Tk()
text = Text()
text.pack(fill='both', expand=True)
windnd.hook_dropfiles(root, func=dropped)
root.mainloop()
(make sure only one gif is played at any instance by adding conditions like the above method)
Output:

Tkinter: image loop slows down

I wrote a Tkinter GUI that is supposed to show display 3D images by looping through the 2D slices of each 3D image. I read up on how to implement such an image loop, and it was recommended to use the after() function with recursion. This is, in principle, my implementation of that:
def load_image(self):
self.stack = read_3D_image(path)
slice = self.stack[self.slice_no]
im = Image.fromarray(slice)
self.photo = ImageTk.PhotoImage(image=im)
self.canvas.create_image(0, 0, image=self.photo, anchor=tk.NW)
if self.forward is True:
self.slice_no += 1
if self.slice_no == 21:
self.forward = False
if self.forward is False:
self.slice_no -= 1
if self.slice_no == 0:
self.forward = True
root.after(10, self.load_image)
This works well for some time, but after a couple of minutes, the loop notably slows down. I guess that is because of the high number of iterations. Is there a way to fix this?
Edit: I noticed this: when the program runs, the image loop will slow down to about half the original frequency after about 10 minutes. When I run a second instance, its loop runs equally slow. Then when I close the first instance, the second instance loop immediately runs faster. I launch from Eclipse.
Updated full code
import glob
import os.path
import tkinter as tk
from PIL import Image, ImageTk
import numpy as np
import helpers
class App():
def __init__(self, master):
super().__init__()
self.frame = tk.Frame(master)
master.bind("<KP_1>", lambda e: self.is_cell())
master.bind("<KP_2>", lambda e: self.is_not_cell())
self.frame.pack()
self.goto_button = tk.Button(
self.frame, text="Go to:", command=self.goto)
self.goto_button.pack(side=tk.TOP)
self.goto_entry = tk.Entry(self.frame, width=5)
self.goto_entry.pack(side=tk.TOP)
self.img_side_length = 100
self.canvas = tk.Canvas(
master=self.frame, width=self.img_side_length, height=self.img_side_length)
self.canvas.pack()
self.img_label = tk.Label(self.frame, text="Bla")
self.img_label.pack(side=tk.TOP)
self.no_cell_button = tk.Button(
self.frame, text="2: Not cell!", command=self.is_not_cell)
self.no_cell_button.pack(side=tk.RIGHT)
self.cell_button = tk.Button(
self.frame, text="1: Cell!", command=self.is_cell)
self.cell_button.pack(side=tk.RIGHT)
self.img_path = "/storage/images/"
self.img_list = glob.glob(os.path.join(self.img_path, "*"))
self.img_list.sort()
self.slice_no = 0
self.img_no = 0
self.forward = True
self.no_of_imgs = len(self.img_list)
self.stack = []
self.image_id = self.canvas.create_image(0, 0, anchor=tk.NW)
self.stack = helpers.read_image_sequence(self.img_list[self.img_no])
self.classifications = np.zeros(self.no_of_imgs)
self.out_path = "/dev/null"
self.loop_image()
def loop_image(self):
data = self.stack[self.slice_no]
im = Image.fromarray(data)
im = im.resize((self.img_side_length, self.img_side_length))
self.photo = ImageTk.PhotoImage(image=im)
self.canvas.itemconfigure(self.image_id, image=self.photo)
if self.forward is True:
self.slice_no += 1
if self.slice_no == 21:
self.forward = False
if self.forward is False:
self.slice_no -= 1
if self.slice_no == 0:
self.forward = True
root.after(10, self.loop_image)
def next_image(self):
self.img_no += 1
self.stack = helpers.read_image_sequence(self.img_list[self.img_no])
self.img_label['text'] = self.img_list[self.img_no].split("/")[-1]
def previous_image(self):
self.img_no -= 1
self.stack = helpers.read_image_sequence(self.img_list[self.img_no])
self.img_label['text'] = self.img_list[self.img_no].split("/")[-1]
def is_cell(self):
self.classifications[self.img_no] = 1
with open(self.out_path, "a") as f:
f.write(str(self.img_no) + "," + str(1) + "\n")
print(self.classifications)
self.next_image()
def is_not_cell(self):
self.classifications[self.img_no] = 2
with open(self.out_path, "a") as f:
f.write(str(self.img_no) + "," + str(2) + "\n")
print(self.classifications)
self.next_image()
def goto(self):
self.img_no = int(self.goto_entry.get())
root = tk.Tk()
app = App(root)
root.mainloop()
You are creating 100 images per second and are stacking them on top of each other. After 10 minutes, that's 60,000 images stacked on top of each other. The canvas has performance issues when it has tens of thousands of items on it, even if all but one is invisible.
Instead of creating more and more images on the canvas, just modify the existing image:
def __init__(self):
...
self.canvas = tk.Canvas(...)
...
# create the image object which will display the image
self.image_id = self.canvas.create_image(0, 0, anchor=tk.NW)
def load_image(self):
...
self.photo = ImageTk.PhotoImage(image=im)
# reconfigure the canvas item to show the new image
canvas.itemconfigure(self.image_id, image=self.photo)
...

tkinter and multiple threads

I am trying to start a function with a new thread and everytime i start the function, the tkinter application opens up again. For me to see the function running i need to quit the new application that opened and then it starts. Is there something in my code that is calling the application again and not allowing my startReceive function to run instantly when i start the thread?
import tkinter as tk
from udpTransfer1 import UdpClass
from threading import Thread
from tkinter import messagebox
import multiprocessing, time, signal
class Application(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.alert = ''
self.pack()
self.alive = False
self.create_widgets()
self.c = UdpClass()
self.threadExists= False
self.st = None
def create_widgets(self):
# 1 button
self.hi_there = tk.Button(self)
self.hi_there["text"] = "Save UDP"
self.hi_there["command"] = self.run
self.hi_there.pack(side="top")
# 2 button
self.b2 = tk.Button(self)
self.b2["text"] = "Stop UDP"
self.b2["command"] = self.b2Action
self.b2.pack(side="top")
# 3 button
self.quit = tk.Button(self, text="QUIT", fg="red")
self.quit["command"] = self.kill
self.quit.pack(side="bottom")
def kill(self):
if self.threadExists == True:
# Alert Box
# self.alert = messagebox.showinfo("ALERT").capitalize()
self.alert = messagebox._show(None,'UDP has to be stopped first!')
else:
root.destroy()
def run(self):
# self.st = Thread(target=self.c.startReceive)
# self.st = multiprocessing.Process(target=self.c.startReceive)
self.st = multiprocessing.Process(target=startReceive)
self.st.start()
if (self.st.is_alive()):
print("Thread open")
self.threadExists = True
def b2Action(self):
if self.threadExists:
# self.c.terminate()
terminate()
self.st.terminate()
time.sleep(.1)
if not(self.st.is_alive()):
print("Thread Killed")
self.threadExists = False
else :
self.alert = messagebox._show(None, 'No Detection of UDP Running!')
def startReceive():
print('started')
try:
isRunning = True
# csvfile = open(self.filename, 'w')
# spamwriter = csv.writer(csvfile, delimiter=',', quotechar='|', quoting=csv.QUOTE_MINIMAL)
# start_time = time.time()
while isRunning:
print('done')
time.sleep(1)
except:
print('out')
def terminate():
# self.socket.close()
# isRunning = False
print('stopped')
root = tk.Tk()
app = Application(master=root)
app.master.title("UDP Saving")
app.master.geometry('200x200')
app.master.maxsize(200, 500)
app.mainloop()
Not 100% sure, but this might be the issue:
def run(self):
# self.st = Thread(target=self.c.startReceive)
# self.st = multiprocessing.Process(target=self.c.startReceive)
self.st = multiprocessing.Process(target=startReceive)
self.st.start()
if (self.st.is_alive()):
print("Thread open")
self.threadExists = True
I think You should put self.threadExists = True inside previous if.
def run(self):
# self.st = Thread(target=self.c.startReceive)
# self.st = multiprocessing.Process(target=self.c.startReceive)
self.st = multiprocessing.Process(target=startReceive)
self.st.start()
if (self.st.is_alive()):
print("Thread open")
self.threadExists = True

No transparency in animated gif with Tkinter

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()
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()

Categories