Fullscreen slideshow with images, text and video - python

I'm completely new at python, learned not much yet. My goal is, to build a slideshow app, which should display images, images with transparent text frames and also videos. Playing a video is my main problem at the moment.
A little test script with static content. Later, all content should be loaded from a web service.
import tkinter as tk
import tkvideo as tv
from PIL import Image
from PIL import ImageTk
import time
# fullscreen window
window = tk.Tk()
window.attributes('-fullscreen', True)
# dimensions
window_width = window.winfo_screenwidth()
window_height = window.winfo_screenheight()
text_width = int(window_width * 0.4)
text_x = int(window_width - text_width)
# transparent rectangle for text
imgs=[]
def create_rectangle(x,y,a,b,**options):
if 'alpha' in options:
# Calculate the alpha transparency for every color(RGB)
alpha = int(options.pop('alpha') * 255)
# Use the fill variable to fill the shape with transparent color
fill = options.pop('fill')
fill = window.winfo_rgb(fill) + (alpha,)
img = Image.new('RGBA', (a-x, b-y), fill)
imgs.append(ImageTk.PhotoImage(img, master=canvas))
canvas.create_image(x, y, image=imgs[-1], anchor='nw')
canvas.create_rectangle(x, y, a, b, **options)
# fullscreen canvas
canvas = tk.Canvas(window, bg="white", bd=0)
canvas.pack(fill=tk.BOTH, expand=True)
# only image slideshow
images = ['quiz1.jpg', 'quiz2.jpg', 'quiz3.jpg', 'quiz4.jpg']
for img in images:
image = Image.open("/home/jpm/Bilder/" + img)
newimage = image.resize((window_width, window_height))
photo = ImageTk.PhotoImage(newimage, master=canvas)
canvas.create_image(0, 0, anchor="nw", image=photo)
canvas.update()
time.sleep(5)
# image with text slideshow
images = ['1658724794aff.jpg', '1658724768kar.jpg']
headlines = ['Headline 1',
'Headline 2']
paragraphs = [['paragraph 1',
'paragraph 2',
'paragraph 3',
'paragraph 4'],
['paragraph 1',
'paragraph 2',
'paragraph 3',
'paragraph 4']]
i=0
for img in images:
image = Image.open("/home/jpm/Bilder/" + img)
newimage = image.resize((window_width, window_height))
photo = ImageTk.PhotoImage(newimage, master=canvas)
canvas.create_image(0, 0, anchor="nw", image=photo)
create_rectangle(text_x, 0, window_width, window_height, fill= "white", alpha=.80, width=0)
head = canvas.create_text(text_x+20, 20, text=headlines[i], fill="#72B765", font=('Helvetica 34 bold'), anchor='nw', width=text_width-20)
canvas.update()
x0, y0, x1, y1 = canvas.bbox(head)
for paragraph in paragraphs[i]:
time.sleep(4)
line = canvas.create_text(text_x+20, y1+10, text=paragraph, fill="#000000", font=('Helvetica 18 bold'), anchor='nw', width=text_width-20)
x0, y0, x1, y1 = canvas.bbox(line)
canvas.update()
canvas.update()
time.sleep(8)
i = i + 1
canvas.destroy()
my_label = tk.Label(window)
my_label.pack(fill='both', expand=True)
player = tv.tkvideo("/home/jpm/Bilder/docsite_promotion.mp4", my_label, loop = 0, size = (window_width,window_height))
player.play()
window.mainloop()
The image and image with text parts running fine. My problem is the video. First of all, it is only displayed at the end of the script. If I put the Label with the video between the two loops or in front of the first, nothing is shown. I also found out, that the program is not waiting till the video ends.
It's all too confusing for me, to explain it better. Maybe I don't understand the flow control of python programs at all. Maybe tkvideo is not the best option. I played also with ffpyplayer, but can't find out, how use it with tkinter.
I only want to display these three types of content in any order and finally also in an endless loop.
Hope you understand me and can give me some tips.

.play() doesn't wait for end of video (it runs code in separated thread) and next image may replace it (hide it) before you see video.
I tried to use sleep() to wait for end of video but it makes problem - it raises error because function doesn't run in main thread.
You would need to use .after(millisecond, my_function) to check periodically if there is end of video and later display images.
If in tkvideo you use self.thread instead of thread then you can check periodically self.thread.is_alive() to catch end of thread. But this need also some method to run next code which will change images. First idea is to create function which changes images and send it as callback to function which check end of video - and it would run this callback() when is_alive() is False. But all this makes problem when you would like to create something more complex.
Here example which uses window.after() to check self.thread.is_alive() and later it runs callback()
I uses tkvideo from my other answer - I added self.running to have method to stop video in any moment.
import time
import tkinter as tk
import threading
from time import perf_counter, sleep
import imageio
from PIL import Image, ImageTk
import os
class tkvideo():
def __init__(self, path, label, loop=False, size=(640,360), hz=0):
self.path = path
self.label = label
self.loop = loop
self.size = size
self.hz = hz
self.running = True # <-- variable to control loop
def load(self, path, label, loop, hz):
"""
Loads the video's frames recursively onto the selected label widget's image parameter.
Loop parameter controls whether the function will run in an infinite loop
or once.
"""
frame_data = imageio.get_reader(path)
if hz > 0:
frame_duration = float(1 / hz)
else:
frame_duration = float(0)
if loop:
while True:
before = perf_counter()
for image in frame_data.iter_data():
frame_image = ImageTk.PhotoImage(Image.fromarray(image).resize(self.size))
if not self.running: # <-- variable to control loop
return # exit function and stop thread
label.config(image=frame_image)
label.image = frame_image
diff = frame_duration + before
after = perf_counter()
diff = diff - after
if diff > 0:
sleep(diff)
before = perf_counter()
print('[load] end of loop')
else:
before = perf_counter()
for image in frame_data.iter_data():
frame_image = ImageTk.PhotoImage(Image.fromarray(image).resize(self.size))
if not self.running: # <-- variable to control loop
return # exit function and stop thread
label.config(image=frame_image)
label.image = frame_image
diff = frame_duration + before
after = perf_counter()
diff = diff - after
if diff > 0:
sleep(diff)
before = perf_counter()
print('[load] end of loop')
def play(self):
"""
Creates and starts a thread as a daemon that plays the video by rapidly going through
the video's frames.
"""
# uses `self.thread` instead of `thread` to have access to `self.thread.is_alive()`
self.thread = threading.Thread(target=self.load, args=(self.path, self.label, self.loop, self.hz))
self.thread.daemon = True
self.thread.start()
def show_video(filename, size, callback=None):
global player
global my_label
fullpath = os.path.join(folder, filename)
my_label = tk.Label(window)
my_label.pack(fill='both', expand=True)
player = tkvideo(fullpath, my_label, loop=False, size=size)
player.play()
# check after 5ms
window.after(5, check_end_video, callback)
def check_end_video(callback):
global my_label
if player.thread.is_alive(): # check if it end of thread
# check again after 5ms
window.after(5, check_end_video, callback)
else:
# remove label
print('The End')
my_label.destroy()
# run next function
if callback:
callback()
def show_image(filename, size, callback=None):
global my_label
fullpath = os.path.join(folder, filename)
frame_image = ImageTk.PhotoImage(Image.open(fullpath).resize(size))
my_label = tk.Label(window)
my_label.pack(fill='both', expand=True)
my_label['image'] = frame_image # show image on label
my_label.image = frame_image # solution for bug in PhotoImage
# check after 5000ms (5s)
window.after(5000, check_end_image, callback)
def check_end_image(callback):
global my_label
# remove label
print('The End')
my_label.destroy()
# run next function
if callback:
callback()
def other_function():
button = tk.Button(window, text='Close', command=window.destroy)
button.pack(fill='both', expand=True, padx=250, pady=250)
# --- main ---
folder = "/home/jpm/Bilder/"
window = tk.Tk()
window.update()
#window.attributes('-fullscreen', True)
#window_width = window.winfo_screenwidth()
#window_height = window.winfo_screenheight()
window.geometry('800x600+800+300')
window_width = 800
window_height = 600
# ---
size = (window_width, window_height)
# run video and it will later run `show_image`
show_video("docsite_promotion.mp4", size,
lambda:show_image('quiz1.jpg', size, other_function))
# ---
window.mainloop()

Related

How to display the whole image on this canvas?

The code provided here is:
import tkinter as tk
from PIL import Image, ImageTk
from pathlib import Path
class App(tk.Tk):
def __init__(self):
super().__init__()
self.geometry('600x600')
self.img_path = Path(r'D:\Python\Lena.jpg')
self.img = Image.open(self.img_path)
self.img_rgb = self.img.convert('RGB')
dim_x, dim_y = self.img_rgb.size
self.img_tk = ImageTk.PhotoImage(self.img_rgb.resize((dim_x, dim_y)))
self.canvas = tk.Canvas(self)
self.canvas.create_image(dim_x // 2, dim_y // 2, image=self.img_tk)
self.canvas.pack(expand=True, fill=tk.BOTH)
self.rgb_var = tk.StringVar(self, '0 0 0')
self.rgb_label = tk.Label(self, textvariable=self.rgb_var)
self.rgb_label.pack()
self.bind('<Motion>', lambda e: self.get_rgb(e))
def get_rgb(self, event):
x, y = event.x, event.y
try:
rgb = self.img_rgb.getpixel((x, y))
self.rgb_var.set(rgb)
except IndexError:
pass # ignore errors if the cursor is outside the image
if __name__ == '__main__':
app = App()
app.mainloop()
It displays an image with the RGB value of the pixel under the mouse pointer under the image (when the mouse pointer is over the image). The image used is this.
However, only the upper left quadrant of the image is displayed on the canvas. You can see that in the screenshot below.
How can I display the whole image and still have the RGB values of the pixel under the mouse pointer displayed (when the mouse pointer is over the image)?
I can see two possible solutions:
Expand image to fit window
Wrap window around image
To expand image to fit window
dim_x, dim_y = 600, 600
self.img_tk = ImageTk.PhotoImage(self.img_rgb.resize((dim_x, dim_y)))
OR
To wrap window around image
dim_x, dim_y = self.img_rgb.size
self.img_tk = ImageTk.PhotoImage(self.img_rgb)
Both approaches will display the entire image.
Here is the complete code with both options available via select flag.
import tkinter as tk
from PIL import Image, ImageTk
from pathlib import Path
class App(tk.Tk):
def __init__(self, select = True):
super().__init__()
self.img_path = Path('D:\Lenna.jpg')
self.img = Image.open(self.img_path)
self.img_rgb = self.img.convert('RGB')
if select:
# resize image to fit window
dim_x, dim_y = 600, 600
self.img_tk = ImageTk.PhotoImage(self.img_rgb.resize((dim_x, dim_y)))
else:
# resize window to fit image
dim_x, dim_y = self.img_rgb.size
self.img_tk = ImageTk.PhotoImage(self.img_rgb)
self.geometry(f'{dim_x}x{dim_y+21}')
self.canvas = tk.Canvas(self, borderwidth = 0, highlightthickness = 0)
self.canvas.create_image(0, 0, image = self.img_tk, anchor= tk.NW)
self.canvas.pack(expand=True, fill=tk.BOTH)
self.rgb_var = tk.StringVar(self, '0 0 0')
self.rgb_label = tk.Label(self, textvariable=self.rgb_var)
self.rgb_label.pack()
self.bind('<Motion>', lambda e: self.get_rgb(e))
def get_rgb(self, event):
x, y = event.x, event.y
try:
rgb = self.img_rgb.getpixel((x, y))
self.rgb_var.set(rgb)
except IndexError:
pass # ignore errors if the cursor is outside the image
if __name__ == '__main__':
app = App(False)
app.mainloop()
Everything works as expected when borderwidth and highlightthickness are removed.

Timer that prints elapsed time and resets with a button click

I have a GUI where the user can click a button named "next set" that allows them to move onto the next task. I wanted to add a timer that starts as soon as they start the application and run the timer until they press the button "next set". When clicked, I want the time elapsed to print and the timer to restart until they press "next set" button again. I would like the timer to start automatically when the code runs. Currently, the "next set" button has two actions, one is to retrieve the next set of images and the other action I am trying to include is to reset the timer and print time elapsed. I also only included part of the code that felt relevant because it is long.
import time
import tkinter as tk
import csv
from pathlib import Path
import PIL.Image
import PIL.ImageDraw
import PIL.ImageTk
MAX_HEIGHT = 500
IMAGES_PATH = Path("Images")
CSV_LABELS_KEY = "New Labels"
CSV_FILE_NAME_KEY = "FolderNum_SeriesNum"
CSV_BOUNDING_BOX_KEY = "correct_flip_bbox"
counter = 0
timer_id = None
class App(tk.Frame):
def __init__(self, master=None):
super().__init__(master) # python3 style
self.config_paths = ["config 1.yaml", "config 2.yaml", "config 3.yaml"]
self.config_index = 0
self.clickStatus = tk.StringVar()
self.loadedImages = dict()
self.loadedBoxes = dict() # this dictionary will keep track of all the boxes drawn on the images
self.master.title('Slideshow')
frame = tk.Frame(self)
tk.Button(frame, text=" Next set ", command=lambda:[self.get_next_image_set(), self.reset()]).pack(side=tk.RIGHT)
tk.Button(frame, text=" Exit ", command=self.destroy).pack(side=tk.RIGHT)
frame.pack(side=tk.TOP, fill=tk.BOTH)
self.canvas = tk.Canvas(self)
self.canvas.pack()
self._load_dataset()
self.reset()
self.start_timer = None
t = time()
t.start()
def start_timer(self, evt=None):
if self._start_timer is not None:
self._start_timer = time.perf_counter()
# global counter
# counter += 1
# label.config(text=str(counter))
# label.after(1000, count)
def reset(self):
if self._start_timer is None:
elapsed_time = time.perf_counter() - self._start_timer
self._start_timer = None
print('Time elapsed (hh:mm:ss.ms) {}'.format(elapsed_time))
def _load_dataset(self):
try:
config_path = self.config_paths[self.config_index]
self.config_index += 1
except IndexError:
return
image_data = loadData(config_path)
# drawing the image on the label
self.image_data = image_data
self.currentIndex = 0
# start from 0th image
self._load_image()
def _load_image(self):
imgName = self.image_data[self.currentIndex]['image_file']
if imgName not in self.loadedImages:
self.im = PIL.Image.open(self.image_data[self.currentIndex]['image_file'])
ratio = MAX_HEIGHT / self.im.height
# ratio divided by existing height -> to get constant amount
height, width = int(self.im.height * ratio), int(self.im.width * ratio)
# calculate the new h and w and then resize next
self.canvas.config(width=width, height=height)
self.im = self.im.resize((width, height))
if self.im.mode == "1":
self.img = PIL.ImageTk.BitmapImage(self.im, foreground="white")
else:
self.img = PIL.ImageTk.PhotoImage(self.im)
imgData = self.loadedImages.setdefault(self.image_data[self.currentIndex]['image_file'], dict())
imgData['image'] = self.img
imgData['shapes'] = self.image_data[self.currentIndex]['shapes']
# for next and previous so it loads the same image adn don't do calculations again
self.img = self.loadedImages[self.image_data[self.currentIndex]['image_file']]['image']
self.canvas.create_image(0, 0, anchor=tk.NW, image=self.img)
self.show_drag_box()
def loadData(fname):
with open(fname, mode='r') as f:
return yaml.load(f.read(), Loader=yaml.SafeLoader)
if __name__ == "__main__":
data = loadData('config 1.yaml')
app = App(data)
app.pack() # goes here
app.mainloop()
I have used datetime instead of time, as subtracting two datetime objects will give an output with hours and minutes included, whereas subtracting two time objects only gives seconds. However, both will work, you may just need to do more reformatting using time.
Read the current time when the application starts and store it. Each time you press the button, subtract your stored time from the current time which gives you your time elapsed. Then simply store your new current time until the next button press. The code below demonstrates this.
import tkinter as tk
import datetime as dt
class TimeButton(tk.Frame):
def __init__(self, parent):
super().__init__(parent)
# Start timer
self.current_time = dt.datetime.today()
# Button
self.next_set = tk.Button(self, text='Next Set', command = self.clicked)
self.next_set.pack()
def clicked(self):
now = dt.datetime.today()
time_elapsed = now - self.current_time
print(time_elapsed)
self.current_time = now
if __name__ == "__main__":
window = tk.Tk()
button = TimeButton(window)
button.pack()
window.mainloop()

How to create box around selected image on GUI

My GUI is a slideshow of images and there is a certain correct image on each page of the slideshow. The correct image is the top-left triangle, heart balloon, the circle, and the hippo. I am completely new to python so I have been struggling with how to create box around the selected image when a user is using the slideshow. This box forms around the correct or the incorrect image, whichever is pressed. To further explain, this includes inserting a box around an image based on where the user clicks but it does not say whether the image selected is the correct image or not. Thank you for your help
import PIL.Image
import PIL.ImageDraw
import tkinter as tk
import PIL.ImageTk
import csv
from PIL import Image
MAX_HEIGHT = 500
# height of the window (dimensions of the image)
class App(tk.Frame):
def __init__(self, imageData, master=None):
tk.Frame.__init__(self, master)
self.clickStatus = tk.StringVar()
self.loadedImages = dict()
self.master.title('Slideshow')
fram = tk.Frame(self)
tk.Button(fram, text="Previous Image", command=self.prev).pack(side=tk.LEFT)
tk.Button(fram, text=" Next Image ", command=self.next).pack(side=tk.LEFT)
tk.Label(fram, textvariable=self.clickStatus, font='Helvetica 18 bold').pack(side=tk.RIGHT)
# inside or outside
fram.pack(side=tk.TOP, fill=tk.BOTH)
self.imageLabel = tk.Label(self)
# drawing the image on the label
self.imageData = imageData
self.currentIndex = 0
# start from 0th image
self.__loadImage__()
self.imageLabel.bind("<Button-1>", self.clicked_evt)
# when you click button, it opens event of clicked_evt
self.imageLabel.pack()
self.pack()
def clicked_evt(self, evt):
x, y = evt.x, evt.y
imgData = self.loadedImages[self.imageData[self.currentIndex]['image_file']]
(l, t), (r,b) = imgData['lt'], imgData['rb']
if t<=y<=b and l<=x<=r:
##self.clickStatus.set('inside')
print('Inside')
else:
##self.clickStatus.set('outside')
print('Outside')
def __loadImage__(self):
if self.imageData[self.currentIndex]['image_file'] not in self.loadedImages:
self.im = PIL.Image.open(self.imageData[self.currentIndex]['image_file'])
ratio = MAX_HEIGHT/self.im.height
# ratio divided by existing height -> to get constant amount
height, width = int(self.im.height*ratio), int(self.im.width * ratio)
# calculate the new h and w and then resize next
self.im = self.im.resize((width, height))
lt = (int(self.imageData[self.currentIndex]['left']*ratio), int(self.imageData[self.currentIndex]['top']*ratio))
rb = (int(self.imageData[self.currentIndex]['right']*ratio), int(self.imageData[self.currentIndex]['bottom']*ratio))
# modifying new ratios with new height and width
#shape = [lt, rb]
# print(shape)
#img1 = PIL.ImageDraw.Draw(self.im)
#img1.rectangle(shape, outline ="red")
if self.im.mode == "1":
self.img = PIL.ImageTk.BitmapImage(self.im, foreground="white")
else:
self.img = PIL.ImageTk.PhotoImage(self.im)
imgData = self.loadedImages.setdefault(self.imageData[self.currentIndex]['image_file'], dict())
imgData['image'] = self.img
imgData['lt'] = lt
imgData['rb'] = rb
# for next and previous so it loads the same image adn don't do calculations again
self.img = self.loadedImages[self.imageData[self.currentIndex]['image_file']]['image']
self.imageLabel.config(image=self.img, width=self.img.width(), height=self.img.height())
def prev(self):
self.currentIndex = (self.currentIndex+len(self.imageData) - 1 ) % len(self.imageData)
self.__loadImage__()
# here if i go to the first one and press back, goes to last, round robbin
def next(self):
self.currentIndex = (self.currentIndex + 1) % len(self.imageData)
self.__loadImage__()
# here if i go to the last one and press next, goes to first, round robbin
def loadData(fname):
with open(fname, mode='r') as f:
reader = csv.DictReader(f)
data = [dict(row) for row in reader]
for row in data:
row['top'], row['bottom'], row['left'], row['right'] = int(row['top']),int(row['bottom']),int(row['left']),int(row['right'])
return data
if __name__ == "__main__":
data = loadData('bounding_box.csv')
app = App(data)
app.mainloop()
As far as I understand you simply want to draw a rectangle around an image when it is clicked (explanation in code comments):
from tkinter import Tk, Canvas
from PIL import Image, ImageTk
# below code is used to create images, you can also load them from a file if you need
mode = 'RGB'
size = (150, 150)
color_lst = ['red', 'green', 'blue', 'yellow']
# best to get all the images for the slide in a single list for further easier workings
img_lst = [Image.new(mode, size, color) for color in color_lst]
# selecting function that will be called when user clicks on image
def select(id_):
canvas.create_rectangle(canvas.bbox(id_), width=5)
# here you will put other code to be executed after
# user clicks on the image like going to the next frame or sth
# additionally you could use this if you want the other rectangles to disappear
# but kinda pointless if you will switch frames and stuff
# for canvas_id in canvas_images:
# if canvas_id == id_:
# continue
# canvas.create_rectangle(canvas.bbox(canvas_id), width=5, outline='white')
root = Tk()
# here create a photoimage list from the above image list
photo_lst = [ImageTk.PhotoImage(image) for image in img_lst]
# create canvas
canvas = Canvas(root, width=400, height=400)
canvas.pack()
# set the coordinates for the four images
coordinates = [(100, 100), (300, 100), (100, 300), (300, 300)]
# create the images and append their id to a list
canvas_images = [
canvas.create_image(pos[0], pos[1], image=photo) for pos, photo in zip(coordinates, photo_lst)
]
# bind the ids from `canvas_images` to being clicked and execute simple drawing method
for c_img in canvas_images:
canvas.tag_bind(
c_img, '<Button-1>', lambda e, i=c_img: select(i)
)
root.mainloop()

Setting a background image within a set Tkinter animation framework

I am currently working on a fruit ninja project for a class. Everything functionally works fine, however, when I try to put in a background image for the game runs extremely slow. In order for the game to look polished, I need everything to work smoothly while having the background of the game show. Other solutions I have come across and have tried to understand simply do not work or the file never ends up running.
FYI: I am working in python 2.7.
I have tried some other suggestions for adding a background, such as using a label function, however, when I try to implement it I get a variety of errors and it just does not seem to work in my animation framework.
def run(width=300, height=300):
def redrawAllWrapper(canvas, data):
canvas.delete(ALL)
canvas.create_rectangle(0, 0, data.width, data.height,
fill='white', width=0)
redrawAll(canvas, data)
canvas.update()
def mousePressedWrapper(event, canvas, data):
mousePressed(event, data)
redrawAllWrapper(canvas, data)
def keyPressedWrapper(event, canvas, data):
keyPressed(event, data)
redrawAllWrapper(canvas, data)
def timerFiredWrapper(canvas, data):
timerFired(data)
redrawAllWrapper(canvas, data)
# pause, then call timerFired again
canvas.after(data.timerDelay, timerFiredWrapper, canvas, data)
# Set up data and call init
class Struct(object): pass
data = Struct()
data.width = width
data.height = height
data.timerDelay = 10 # milliseconds
root = Tk()
root.resizable(width=False, height=False) # prevents resizing window
init(data)
# create the root and the canvas
canvas = Canvas(root, width=data.width, height=data.height)
canvas.configure(bd=0, highlightthickness=0)
canvas.pack()
# set up events
root.bind("<Button-1>", lambda event:
mousePressedWrapper(event, canvas, data))
root.bind("<Key>", lambda event:
keyPressedWrapper(event, canvas, data))
timerFiredWrapper(canvas, data)
# and launch the app
root.mainloop() # blocks until window is closed
print("bye!")
run(1200, 700)
with my current framework, I write all the necessary code within the init, timerFired, redrawAll, keyPressed, and mousePressed functions above this run function.
With my current implementation of the background. I use PhotoImage on a 1200 x 700 gif file and draw the image across the whole screen in the redrawAll function (which is called every 10 milliseconds). Without drawing this one image, my game runs very smoothly, however, upon drawing the image in redrawAll, the game lags significantly, so I do know the source of the lag is drawing the background image.
Here is the line of code that draws it in redrawAll:
canvas.create_image(data.width//2, data.height//2, image = data.background)
Is this only because I do it in redrawAll which continuously draws the image every time the function is called making it slow? Is simply having an image that large in Tkinter making it slow? What is the source?
This there a way to simply draw the image once on the background and have it never change? Or is there any way to not have lag? I just find it odd. Again, this is in python 2.7 on a Mac.
Thanks!
You don't have to remove and add again all elements to refresh screen. You can move elements and canvas will draw it correctly
This code create 1000 small rectangles and move them randomly on background.
Tested with Python 3.7 but on 2.7 should work too.
With 5_000 rectangles it slows down but it still works good (but not perfect). With 10_000 it slows down too much.
from Tkinter import *
from PIL import Image, ImageTk
import random
IMAGE_PATH = 'background.jpg'
class Struct(object):
pass
def run(width=300, height=300):
def init(data):
# create 1000 rectangles in random position
for _ in range(1000):
x = random.randint(0, data.width)
y = random.randint(0, data.height)
data.data.append(canvas.create_rectangle(x, y, x+10, y+10, fill='red'))
def mousePressedWrapper(event, canvas, data):
#mousePressed(event, data)
pass
def keyPressedWrapper(event, canvas, data):
#keyPressed(event, data)
pass
def timerFiredWrapper(canvas, data):
# move objects
for rect_id in data.data:
x = random.randint(-10, 10)
y = random.randint(-10, 10)
canvas.move(rect_id, x, y)
# pause, then call timerFired again
canvas.after(data.timerDelay, timerFiredWrapper, canvas, data)
# Set up data and call init
data = Struct()
data.width = width
data.height = height
data.timerDelay = 10 # milliseconds
data.data = [] # place for small red rectangles
root = Tk()
root.resizable(width=False, height=False) # prevents resizing window
# create the root and the canvas
canvas = Canvas(root, width=data.width, height=data.height)
canvas.configure(bd=0, highlightthickness=0)
canvas.pack()
#canvas.create_rectangle(0, 0, data.width, data.height, fill='white', width=0)
img = Image.open(IMAGE_PATH)
img = img.resize((data.width, data.height))
photo = ImageTk.PhotoImage(img)
canvas.create_image(0, 0, image=photo, anchor='nw')
init(data) # init after creating canvas because it create rectangles on canvas
# set up events
root.bind("<Button-1>", lambda event:
mousePressedWrapper(event, canvas, data))
root.bind("<Key>", lambda event:
keyPressedWrapper(event, canvas, data))
timerFiredWrapper(canvas, data)
# and launch the app
root.mainloop() # blocks until window is closed
print("bye!")
run(1200, 700)

Run Images in Loop Tkinter

I am creating an app that will allow users to scan a ticket and a message will be displayed. I have created a short GIF animation to play when the app starts to show users where to scan their ticket.
I am having trouble understanding how to play a GIF image using tkinter in Python 3. I have tried many solutions and I came across a piece of code where you select the folder and the images in that folder will play in a loop but it's not working.
I think I'm not understanding the code. Here is my code for my app:
from tkinter import *
from tkinter import messagebox
import tkinter.filedialog
from tkinter.filedialog import askdirectory
import requests
import simplejson as json
import os
#from json import JSONEncoder
#class MyEncoder(JSONEncoder):
#def default(self, o):
#return o.__dict__
#Connect to API function
def apiconnect(statusvar):
ticektid = e1.get()
def to_serializable(ticketid):
return str(ticketid)
url = "https://staging3.activitar.com/ticket_api/tickets"
data = {'ticket_id':e1.get(),'direction': 'up'}
headers = {'Content-Type': 'application/json','Authorization' :'J0XDvDqVRy9hMF9Fo7j5'}
r = requests.post(url,data=json.dumps(data), headers=headers)
requestpost = requests.post(url, headers=headers, json=data)
response_data = requestpost.json()
statusvar = (response_data["status"])
messagevar = (response_data["message"])
json.dumps(url,data)
# MyEncoder().encode(ticketid)
#'{"ticekt_id": "/foo/bar"}'
#19 February 2018
#def from_json(json_object):
# if 'ticket_id' in json_object:
# return FileItem(json_object['ticket_id'])
# ticketid = JSONDecoder(object_hook = from_json).decode('{"ticket_id": "/foo/bar"}')
#Including GPIO config
if statusvar == "failed":
messagebox.showinfo("Cape Point", messagevar)
else: statusvar == "successful"
#Run at full screen automatically:
#---------------Function & Class--------------------------------#
class FullScreenApp(object):
def __init__(self, master, **kwargs):
self.master=master
pad=3
self._geom='200x200+0+0'
master.geometry("{0}x{1}+0+0".format(
master.winfo_screenwidth()-pad, master.winfo_screenheight()-pad))
master.bind('<Escape>',self.toggle_geom)
def toggle_geom(self,event):
geom=self.master.winfo_geometry()
print(geom,self._geom)
self.master.geometry(self._geom)
self._geom=geom
#--------------------------------------------------------------------#
def next_img():
img_label.img = PhotoImage(file=next(imgs))
img_label.config(image=img_label.img)
#create a textbox on a form
root = Tk()
#-----Full Screen-------#
app = FullScreenApp(root)
root.title("Cape Point")
root.configure(background = 'White')
#________ this code below was the original that displayed a static image _____#
#titlepic = PhotoImage(file = "ScanPlease.gif")
#shownpic = titlepic
#filename = shownpic
#Label(root, image = filename).grid(row=0, sticky=W)
img_dir = askdirectory(parent=root, initialdir= "C:/Users/Nickitaes/Desktop", title='Where To Scan')
os.chdir(img_dir)
imgs = iter(os.listdir(img_dir))
img_label = Label(root)
img_label.bind("<Return>",next_img())
next_img()
e1 = Entry(root)
e1.grid(row=1, column=0)
e1.focus_set() #set cursor focus to textbox
e1.bind("<Return>", apiconnect) #Return function
root.mainloop( )
Thanks for the help!
Well..., it was not hard to find other questions about this on StackOverflow. Here are some: Play Animations in GIF with Tkinter and Play an Animated GIF in python with tkinter.
I have combined the answers to take care of different number of subpictures an also commented the code a bit more.
from tkinter import *
import time
root = Tk()
framelist = [] # List to hold all the frames
for ix in range(1000): # range > frames in largest GIF
part = 'gif -index {}'.format(ix)
try: frame = PhotoImage(file='giphy.gif', format=part)
except:
last = len(framelist) - 1 # Save index for last frame
break # Will break when GIF index is reached
framelist.append(frame)
def update(ix):
if ix > last: ix = 0 # Reset frame counter if too big
label.configure(image=framelist[ix]) # Display frame on label
ix += 1 # Increase framecounter
root.after(100, update, ix) # Run again after 100 ms.
label = Label(root)
label.pack()
root.after(0, update, 0) # Start update(0) after 0 ms.
root.mainloop()
Adjust the for-loop for the GIF size you use, or rewrite as a while-loop.
I don't know how to read the frame delay from the GIF. You'll have to try different values in after() until it looks good.

Categories