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()
Related
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:
I trying to move OpenCV's frame to Tkinter
Code Following...
import tkinter as tk
import time
from PIL import ImageTk, Image
from cv2 import cv2
To show the video
class CamWindow:
def __init__(self, window, title:str, cam_plug = 0, delay = 15):
self.delay = delay
self.window = window
self.title = title
self.window.title(self.title)
self.cam = MyVideoCapture(cam_plug)
self.canvas = tk.Canvas(self.window, width = self.cam.width, height = self.cam.height)
self.canvas.pack()
self.photoButton = tk.Button(self.window, text="SnapShot", width=30, command=self.shot)
self.photoButton.pack(anchor=tk.CENTER, expand=True)
self.update()
self.window.mainloop()
def shot(self):
ret, frame = self.cam.getPic()
if ret:
cv2.imwrite(time.strftime("%d-%m-%Y-%H-%M-%S")+".jpg", cv2.cvtColor(frame, cv2.COLOR_RGB2BGR))
def update(self):
ret, frame = self.cam.getPic()
if ret:
self.photo = ImageTk.PhotoImage(image=Image.fromarray(frame))
self.canvas.create_image(0, 0, image = self.photo, anchor = tk.NW)
self.window.after(self.delay, self.update())
class MyVideoCapture:
def __init__(self, cam_plug = 0):
self.cam = cv2.VideoCapture(cam_plug, cv2.CAP_DSHOW)
if not self.cam.isOpened():
raise ValueError("cannot open camera at %d", cam_plug)
self.width = self.cam.get(cv2.CAP_PROP_FRAME_WIDTH)
self.height = self.cam.get(cv2.CAP_PROP_FRAME_HEIGHT)
def getPic(self):
if self.cam.isOpened():
ret, frame = self.cam.read()
if ret:
return ret, cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA)
else:
return ret, None
else:
return None, None
def __del__(self):
if self.cam.isOpened:
self.cam.release()
the terminal shows memory error, but everything seems to make sense.
and also I met "Attributeerror: 'photoimage' object has no attribute '_photoimage__photo'"
if there's any method or codes having problems please tell me as well
==========================edit===========================
my main file.py is:
from module.UI import CamWindow
def main():
CamWindow(tk.Tk(), title = "camera window", delay = 30)
if __name__ == '__main__':
main()
This is my code :
from PIL import Image, ImageSequence
import os
class MyImage:
def __init__(self,file_path):
self.image = Image.open(file_path)
def get_rewind(self):
iter = ImageSequence.Iterator(self.image)
index = 1
for frame in iter:
print("image [{}] : mode {}, size {}".format(index,frame.mode,frame.size))
frame.save("./rewind-result/frame{}.png".format(index))
index += 1
sequence = []
for f in ImageSequence.Iterator(self.image):
sequence.append(f.copy())
sequence.reverse()
sequence[0].save("./reverse_out.gif",save_all=True, append_images=sequence[1:])
if __name__ == '__main__':
image = MyImage("./test.gif")
image.get_rewind()
The gif is the reverse_out.gif, but it one loop once. So how to set the property of the loop?(Not by photoshop)
a non-healthy method:
from tkinter import *
class GIF(Tk):
def __init__(self):
super().__init__()
self.geometry("500x500")
self.x = 0
self.img = PhotoImage(file="filename.gif", format=f"gif -index {self.x}")
self.canvas = Canvas(self,width=1366, height=768,bd=0,highlightthickness=0)
self.canvas.pack(expand = 1, fill = BOTH)
self._img = self.canvas.create_image(0, 0, image=self.img,anchor = NW)
self.run_gif()
self.mainloop()
def run_gif(self):
try:
self.img = PhotoImage(file="filename.gif", format=f"gif -index {self.x}")
self.canvas.itemconfigure(self._img, image=self.img)
self.x += 1
except:
self.x = 0
self.canvas.after(10, self.run_gif)
GIF()
But there are those who are healthy!
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)
...
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