Python: auto slideshow with PIL/TkInter with no saving images - python

I´m designing a slideshow with no user intervention, where:
a. Every image is generated by the Python script itself
b. There´s no file saving, for performance reasons
c. Every image is shown in fullscreen for a certain time
d. It´s a loop that´s supposed to never end. There´s always going to be an image to show
So far, by adapting code found in a few pages, I have it running. But every image is shown for X time and then the desktop background appears for a second or so.
I´d like to have a smooth switching from one file to next, such as FEH does. As a matter of fact, I´m trying to replace FEH because I need finer control of the display of each file (for instance, changing the time it appears on screen).
Here´s my code:
from PIL import Image
from PIL import ImageFont
from PIL import ImageDraw
from PIL import ImageTk
import tkinter
def show_center(pil_image, msDelay):
root = tkinter.Tk()
w, h = root.winfo_screenwidth(), root.winfo_screenheight()
root.overrideredirect(1)
root.geometry("%dx%d+0+0" % (w, h))
root.focus_set()
root.attributes("-topmost", True)
canvas = tkinter.Canvas(root, width=w, height=h, highlightthickness=0)
canvas.pack()
canvas.configure(background='black')
image = ImageTk.PhotoImage(pil_image)
imagesprite = canvas.create_image(w / 2, h / 2, image=image)
root.after(msDelay, root.destroy)
root.mainloop()
### script body
while True:
# loads common background image
img = Image.open(baseImage)
draw = ImageDraw.Draw(img)
# here: image customization
draw.rectangle(....)
draw.text(....)
img.paste(....)
# shows this file
thisDelay = some Number Calculation
show_center(img, thisDelay)
Any ideas on how to avoid the desktop appearing between images? This will run in a headless Raspberry. I´m using Python3 on Raspbian.
Thanks in advance!

You can use after() instead of the while loop and simply use Label instead of Canvas:
import tkinter as tk
from PIL import Image, ImageTk, ImageDraw, ImageFont
import time
import random
def update_image():
# sample drawing
image = base_image.copy()
draw = ImageDraw.Draw(image)
draw.rectangle((100, 100, 500, 400), outline=random.choice(('red', 'green', 'blue', 'magenta', 'gold', 'orange')))
draw.text((120, 120), f"""{time.strftime("%F %T")}""")
# update image
tkimg = ImageTk.PhotoImage(image)
label.config(image=tkimg)
label.image = tkimg # save a reference to avoid garbage collected
ms_delay = random.randint(1000, 9000) # sample delay calculation
root.after(ms_delay, update_image)
root = tk.Tk()
root.attributes("-fullscreen", 1, "-topmost", 1)
base_image = Image.open("/path/to/base/image")
# label for showing image
label = tk.Label(root, bg="black")
label.pack(fill="both", expand=1)
update_image() # start the slide show
root.mainloop()

Well, it´s working quite well.
The solution required a bit of logic (maybe it makes sense not to destroy the object for every image) and a bit of good old trial & error.
The changes were:
Init the canvas only once, use global vars to make it persistent
For every image, call the display function and keep calling root.update() until the required timeout is reached
So, the prior function gets divided, and it looks like:
global canvas, root
global w, h
def init_image():
global canvas, root
global w, h
root = tkinter.Tk()
w, h = root.winfo_screenwidth(), root.winfo_screenheight()
root.overrideredirect(1)
root.geometry("%dx%d+0+0" % (w, h))
root.focus_set()
root.attributes("-topmost", True)
canvas = tkinter.Canvas(root, width=w, height=h, highlightthickness=0)
canvas.pack()
canvas.configure(background='black')
return
def show_center(pil_image, msDelay):
global canvas, root
global w, h
image = ImageTk.PhotoImage(pil_image)
imagesprite = canvas.create_image(w / 2, h / 2, image=image)
inicio = int(time.time() * 1000)
while 1:
root.update()
if (int(time.time() * 1000) - inicio) > msDelay:
break
return
Function init_image() is called once at beginning, and function show_center() is called for every image as the original post.
I hope this can be useful to anybody trying to accomplish the same.

Related

How to rotate a image in Python tkinter Window

I am working on a python messaging application using tkinter and i need a loading screen animation but none of them where fitting with my purpose so i Tried to rotate a image in python continusly which will look like a animation.
This is the image i want to ratate for some time in tkinter
The application code
i only need a simple tkinter window with the rotating screen
I tried google and i got this code to rote a image
from PIL import Image # Import Image class from the library.
image = Image.open("file.jpg") # Load the image.
rotated_image = image.rotate(180) # Rotate the image by 180 degrees.
rotated_image.save("file_rotated.jpg")
So i tried using this code like this:-
from tkinter import *
from PIL import ImageTk, Image
import os
root = Tk()
c = Canvas(root, width=700, height=700)
c.pack()
while True:
img = ImageTk.PhotoImage(Image.open(r"Loading_Icon.png"))
c.create_image(100, 100, image=img, anchor=NW)
image = Image.open(Loading_Icon.png") # Load the image.
rotated_image = image.rotate(30)
os.remove("Loading_Icon.png")
rotated_image.save("Loding_Icon.png")
root.mainloop()
c.delete("all")
Look at this:
from PIL import Image, ImageTk
import tkinter as tk
# Load the original image
pil_img = Image.open("pawn.black.png")
def loading_loop(i=0):
global tk_img
print(f"Loop {i}")
# If the prgram has loaded, stop the loop
if i == 13: # You can replace this with your loading condition
return
# Rotate the original image
rotated_pil_img = pil_img.rotate(30*i)
tk_img = ImageTk.PhotoImage(rotated_pil_img)
# put the rotated image inside the canvas
canvas.delete("all")
canvas.create_image(0, 0, image=tk_img, anchor="nw")
# Call `loading_loop(i+1)` after 200 milliseconds
root.after(200, loading_loop, i+1)
root = tk.Tk()
canvas = tk.Canvas(root)
canvas.pack()
# start the tkinter loop
loading_loop()
root.mainloop()
It loads the original image then it start a tkinter loop which runs loading_loop every 200 milliseconds. Inside loading_loop, I rotate the image 30*i degrees, where i is the iteration number. Then I put the image inside the canvas.
Notice how I only call .mainloop() once and there is no while True loop. Those are best practises for when using tkinter.
It's best to create only 1 image inside the canvas and just reconfigure it but that will make the code longer.

I need help displaying full screen images with tkinter

I am doing a people counter in raspberry pi. I want to display an one image if someone comes in, and another one if someone comes out. Right now i am using the code below (that i took from another question here xd) to change the image that tkinter is displaying. The problem with this is thay it only shows the picture cat.jpg for a second, and then it shows a black screen and nothing happends.
import sys
if sys.version_info[0] == 2: # the tkinter library changed it's name from Python 2 to 3.
import Tkinter
tkinter = Tkinter #I decided to use a library reference to avoid potential naming conflicts with people's programs.
else:
import tkinter
from PIL import Image, ImageTk
import time
def updateRoot(root,imagen):
pilImage = Image.open(imagen)
w, h = root.winfo_screenwidth(), root.winfo_screenheight()
root.overrideredirect(1)
root.geometry("%dx%d+0+0" % (w, h))
root.focus_set()
root.bind("<Escape>", lambda e: (e.widget.withdraw(), e.widget.quit()))
canvas = tkinter.Canvas(root,width=w,height=h)
canvas.pack()
canvas.configure(background='black')
imgWidth, imgHeight = pilImage.size
if imgWidth > w or imgHeight > h:
ratio = min(w/imgWidth, h/imgHeight)
imgWidth = int(imgWidth*ratio)
imgHeight = int(imgHeight*ratio)
pilImage = pilImage.resize((imgWidth,imgHeight), Image.ANTIALIAS)
image = ImageTk.PhotoImage(pilImage)
imagesprite = canvas.create_image(w/2,h/2,image=image)
root.update()
root = tkinter.Tk()
updateRoot(root,"Cat.jpg")
time.timesleep(5)
updateRoot(root,"Dog.jpg")
Before this I used this code
import tkinter
from PIL import Image, ImageTk
from tkinter import ttk
def updateRoot(root,imagen):
image1 = Image.open(imagen)
image2 = ImageTk. PhotoImage(image1)
image_label = ttk. Label(root , image =image2)
image_label.place(x = 0 , y = 0)
root.update()
That works fine, but it's not full screen.
First you should do the followings outside updateRoot():
make root window fullscreen (you can simply use root.attributes('-fullscreen', 1))
bind the <Escape> key
create the canvas and create_image() (you can use Label to do the same thing)
Then just update the image inside updateRoot().
Also you should use after() instead of time.sleep().
Below is an example:
try:
import Tkinter as tkinter
except:
import tkinter
from PIL import Image, ImageTk
def updateRoot(imagen):
# resize the image to fill the whole screen
pilImage = Image.open(imagen)
w, h = root.winfo_screenwidth(), root.winfo_screenheight()
image = ImageTk.PhotoImage(pilImage.resize((w,h)))
# update the image
canvas.itemconfig(imgbox, image=image)
# need to keep a reference of the image, otherwise it will be garbage collected
canvas.image = image
root = tkinter.Tk()
root.attributes('-fullscreen', 1)
root.bind('<Escape>', lambda _: root.destroy())
canvas = tkinter.Canvas(root, highlightthickness=0)
canvas.pack(fill=tkinter.BOTH, expand=1)
imgbox = canvas.create_image(0, 0, image=None, anchor='nw')
# show the first image
updateRoot('Cat.jpg')
# change the image 5 seconds later
root.after(5000, updateRoot, 'Dog.jpg')
root.mainloop()
Fixed your Black issue using labels, try this. i think you still need to resize image to fit screen
import sys
if sys.version_info[0] == 2: # the tkinter library changed it's name from Python 2 to 3.
import Tkinter
tkinter = Tkinter #I decided to use a library reference to avoid potential naming conflicts with people's programs.
else:
import tkinter
from PIL import Image, ImageTk
import time
from tkinter import *
import PIL.Image
def updateRoot(root,imagen):
w, h = root.winfo_screenwidth(), root.winfo_screenheight()
root.overrideredirect(1)
root.geometry("%dx%d+0+0" % (w, h))
root.focus_set()
root.bind("<Escape>", lambda e: (e.widget.withdraw(), e.widget.quit()))
img = PIL.Image.open(imagen)
root.tkimage = ImageTk.PhotoImage(img)
Label(root,image = root.tkimage).place(x=0, y=0, relwidth=1, relheight=1)
root.update()
root = tkinter.Tk()
updateRoot(root,"Cat.jpg")
time.sleep(3)
updateRoot(root,"Dog.jpg")
root.mainloop()

tkinter: move images with background

I'm looking for a way to move multiple images together with the background. Moving the background image works fine, but I can't figure out how to add two images on top (they disappear immediately) and then move together with the background. I guess there is an easy way to do that?
I appreciate any hint!
from tkinter import *
import time
tk = Tk()
w = 1000
h = 800
pos = 0
canvas = Canvas(tk, width=w, height=h)
canvas.pack()
tk.update()
background_image = PhotoImage(file="bg.gif")
background_label = Label(tk, image=background_image)
background_label.place(x=0, y=0)
tk.update()
def addImages(files):
for f in files:
image = PhotoImage(file=f)
label = Label(tk, image=image)
label.place(x=files[f][0],y=files[f][1])
tk.update()
def move(xPos):
pos = background_label.winfo_x() - xPos
while background_label.winfo_x() > pos:
background_label.place(x=background_label.winfo_x()-25)
tk.update()
time.sleep(0.001)
img = {"file1.gif": [10,10], "file2.gif": [50,50]}
addImages(img)
move(100)
tk.mainloop()
I'm having difficulty in understanding your code. Why create a canvas and then not use it? You have also littered your code with tk.update(), most of which are unnecessary. But, the described problem is because you create the labels inside a function and the association between label and image gets garbage collected when the function exits. You have to explicitly remember this association:
def addImages(files):
for f in files:
image = PhotoImage(file=f)
label = Label(tk, image=image)
label.image = image # Lets the label remember the image outside the function
label.place(x=files[f][0],y=files[f][1])
If you are then going to move these labels you might want to keep some kind of reference to them or you won't be able to address them.
Complete example
I changed tk to root because tk is the name usually used as alias for tkinter (eg. import tkinter as tk) which gets confusing.
I'm creating a image_list to hold references to the labels containing images. Later I use the list to loop through the labels and move them.
After I have built the GUI I wait 1000 milliseconds before starting the move function. Also I move the images just 1 pixel at a time to clearer see the action.
from tkinter import *
import time
root = Tk()
root.geometry('800x600') # Setting window size instead of usin canvas to do that
pos = 0
background_image = PhotoImage(file="bg.gif")
background_label = Label(root, image=background_image)
background_label.place(x=0, y=0)
image_list = [] # List for holding references to labels with images
def addImages(files):
for f in files:
image = PhotoImage(file=f)
label = Label(root, image=image)
label.image = image # Remember the image outside the function
label.place(x=files[f][0],y=files[f][1])
image_list.append(label) # Append created Label to the list
def move(xPos):
pos = background_label.winfo_x() - xPos
while background_label.winfo_x() > pos:
background_label.place(x=background_label.winfo_x()-1)
for image in image_list: # Loop over labels in list
image.place(x=image.winfo_x()-1) # Move the label
root.update()
time.sleep(0.001)
img = {"file1.gif": [10,10], "file2.gif": [50,50]}
addImages(img)
root.after(1000, move, 100) # Waits 1 second and then moves images
root.mainloop()
By the way; after is a function much preferred over sleep as sleep suspends the program until its finished, whereas after works by making a call after a time and the program runs meanwhile. But if you are ok with that the program freezes during the move, then why not.

PIL and Tkinter for drawing software

I am trying to write a code that would place a point/line/whatever at the mouse coordinates, aka Paint. I am using PIL and Tkinter. The problem is I can't understand how to realise canvas update.
window = Tk(className ='Window')
image = Image.new('RGB', (800,600),"#ffffff")
image_tk = PhotoImage(image)
canvas = Canvas(window,width = 800, height = 600)
canvas.create_image(400 ,300,image = image_tk)
canvas.pack()
draw = ImageDraw.Draw(image)
def mouseclick(event):
draw.point((event.x,event.y),fill=128)
print event.x,event.y
canvas.bind("<Button-1>", mouseclick)
mainloop()
What should be added? Maybe there are other better modules for doing it ?
That is going to be expensive, you need to create a new PhotoImage to reflect your modifications. Alternatively, consider drawing to the canvas without using an image. If you then need to save what was draw in the canvas you have the easy option to export it to postscript, or the harder option of storing what was drawn and reproducing it.
For the moment, here is an example that adjusts your code so it works as you intended (although I recommend the option of drawing in the canvas):
import Tkinter
from PIL import Image, ImageDraw, ImageTk
def paint_img(event, canvas):
x, y = event.x, event.y
image_draw.ellipse((x-5, y-5, x+5, y+5), fill='black')
canvas._image_tk = ImageTk.PhotoImage(image)
canvas.itemconfigure(canvas._image_id, image=canvas._image_tk)
root = Tkinter.Tk()
width, height = 800, 600
canvas = Tkinter.Canvas(width=width, height=height)
canvas.pack()
image = Image.new('RGB', (width, height), '#cdcdcd')
image_draw = ImageDraw.Draw(image)
canvas._image_tk = ImageTk.PhotoImage(image)
canvas._image_id = canvas.create_image(0, 0, image=canvas._image_tk, anchor='nw')
canvas.tag_bind(canvas._image_id, "<Button-1>", lambda e: paint_img(e, canvas))
root.mainloop()

Image resize under PhotoImage

I need to resize an image, but I want to avoid PIL, since I cannot make it work under OS X - don't ask me why...
Anyway since I am satisfied with gif/pgm/ppm, the PhotoImage class is ok for me:
photoImg = PhotoImage(file=imgfn)
images.append(photoImg)
text.image_create(INSERT, image=photoImg)
The problem is - how do I resize the image?
The following works only with PIL, which is the non-PIL equivalent?
img = Image.open(imgfn)
img = img.resize((w,h), Image.ANTIALIAS)
photoImg = ImageTk.PhotoImage(img)
images.append(photoImg)
text.image_create(INSERT, image=photoImg)
Thank you!
Because both zoom() and subsample() want integer as parameters, I used both.
I had to resize 320x320 image to 250x250, I ended up with
imgpath = '/path/to/img.png'
img = PhotoImage(file=imgpath)
img = img.zoom(25) #with 250, I ended up running out of memory
img = img.subsample(32) #mechanically, here it is adjusted to 32 instead of 320
panel = Label(root, image = img)
You have to either use the subsample() or the zoom() methods of the PhotoImage class. For the first option you first have to calculate the scale factors, simply explained in the following lines:
scale_w = new_width/old_width
scale_h = new_height/old_height
photoImg.zoom(scale_w, scale_h)
If you don't have PIL installed --> install it
(for Python3+ users --> use 'pip install pillow' in cmd)
from tkinter import *
import tkinter
import tkinter.messagebox
from PIL import Image
from PIL import ImageTk
master = Tk()
def callback():
print("click!")
width = 50
height = 50
img = Image.open("dir.png")
img = img.resize((width,height), Image.ANTIALIAS)
photoImg = ImageTk.PhotoImage(img)
b = Button(master,image=photoImg, command=callback, width=50)
b.pack()
mainloop()
I just had the same problem, and I found that #Memes' answer works rather well. Just make sure to reduce your ratio as much as possible, as subsample() takes a rather long time to run for some reason.
Basically, the image is zoomed out to the least common factor of the two sizes, and then being subsidized by the origonal size. This leaves you with the image of the desired size.
I had a requirement where I wanted to open an image, resize it, keeping the aspect ratio, save it under a new name, & display it in a tkinter window (using Linux Mint). After looking through dozens of forum questions, and dealing with some weird errors (semmingly involving the PIL to Pillow fork in Python 3.x), I was able to develop some code that works, using a predefined new maximum width or new maximum height (scaling up or down as necessary), and a Canvas object, where the image is displayed centered in the frame. Note that I did not include the file dialogs, just a hardcoded Image open & save for one file:
from tkinter import *
from PIL import ImageTk, Image
import shutil,os
from tkinter import filedialog as fd
maxwidth = 600
maxheight = 600
mainwindow = Tk()
picframe = Frame(mainwindow)
picframe.pack()
canvas = Canvas(picframe, width = maxwidth, height = maxheight)
canvas.pack()
img = Image.open("/home/user1/Pictures/abc.jpg")
width, height = img.size # Code to scale up or down as necessary to a given max height or width but keeping aspect ratio
if width > height:
scalingfactor = maxwidth/width
width = maxwidth
height = int(height*scalingfactor)
else:
scalingfactor = maxheight/height
height = maxheight
width = int(width*scalingfactor)
img = img.resize((width,height), Image.ANTIALIAS)
img.save("/home/user1/Pictures/Newabc.jpg")
img = ImageTk.PhotoImage(img) # Has to be after the resize
canvas.create_image(int(maxwidth/2)-int(width/2), int(maxheight/2)-int(height/2), anchor=NW, image=img) # No autocentering in frame, have to manually calculate with a new x, y coordinate based on a NW anchor (upper left)

Categories

Resources