0

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!

PiBer2
  • 159
  • 10

2 Answers2

1

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()
acw1668
  • 40,144
  • 5
  • 22
  • 34
  • That´s nice. I like how you copy() the base image so it´s not required to reload from disk every time. I can´t see how you can interrupt or manipulate the mainloop(). I need to process external info that´s generated in real time and goes into every image, not just update the current time. Thanks. – PiBer2 Dec 12 '20 at 17:55
  • Oh, I have to process the external info **inside** update_image(), right? – PiBer2 Dec 12 '20 at 17:59
  • Basically yes as long as it is not a time consuming work. – acw1668 Dec 12 '20 at 18:16
0

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.

PiBer2
  • 159
  • 10