How I added cross­fad­ing slide tran­si­tions to my dig­i­tal pic­ture frame using Pi3D

-

Build­ing a great dig­i­tal pic­ture frame is not just about putting togeth­er a mini­com­put­er, a dis­play, and a frame. The cru­cial part is the soft­ware, espe­cial­ly the image view­er.

The image view­er soft­ware con­trols the type of tran­si­tions between your pic­tures. And the kind of tran­si­tion makes the dif­fer­ence between a good and a bad dig­i­tal pic­ture frame.

I have tried many types of tran­si­tions until I final­ly found the one that I whole­heart­ed­ly rec­om­mend. It is based on soft­ware called Pi3D and in this arti­cle, I will describe in detail how you can install Pi3D to build an excel­lent dig­i­tal pic­ture frame.

A world of dif­fer­ence

The stan­dard image view­er on Lin­ux is a pack­age called feh. It is a light­weight image view­er that many tuto­ri­als on self-made dig­i­tal pic­ture frames sug­gest to use. I do not rec­om­mend this approach.

When I built my first dig­i­tal pic­ture frame, I ini­tial­ly used feh for the lack of some­thing bet­ter. But the prob­lem with feh is that it has offers no tran­si­tions between images. It can only do a hard cut between images.

This may be fine for out­door adver­tis­ing, but it is unsuit­ed for any social set­ting.

The hard cuts between pho­tos cause quite a dis­tur­bance, espe­cial­ly with a larg­er screen. There may be a sud­den change in bright­ness which is uncom­fort­able, some­thing which dis­tracts and doesn't feel right. And it gets bor­ing quick­ly.

I have test­ed many types of tran­si­tion effects for over 12 years now. Cross­fad­ing tran­si­tion effects with the right set­tings of tran­si­tion dura­tion are the key to dig­i­tal pic­ture frame heav­en.

You may say, a stan­dard image hard cut image view­er is good enough. But then you would be miss­ing out on some­thing mag­i­cal. A lack of tran­si­tions means that there is no emo­tion, noth­ing that touch­es you.

Here is an exam­ple of a 10 sec­onds cross­fade tran­si­tion effect (comes with soft piano music):

I will explain this in more detail in my arti­cle "5 essen­tial tips I learned from build­ing dig­i­tal pic­ture frames".

This is where Pi3D comes to res­cue.

Tak­ing the Rasp­berry Pi's graph­i­cal capa­bil­i­ties up a few notch­es

Back in 2014, I had post­ed in the Eng­lish Rasp­berry Pi forum that I was look­ing for an image tran­si­tion soft­ware.

One guy sug­gest­ed that I take a look at 2D/3D ren­der­ing soft­ware that came with many demo appli­ca­tions. There was one demo that got my atten­tion in par­tic­u­lar: Slide­Tran­si­tion. It built a slideshow of all files in a direc­to­ry with four tran­si­tion options.

Among them was cross­fad­ing. The one I was look­ing for.

The "guy" from the forum turned out to be Pad­dy Gaunt who togeth­er with Tim Skill­man and Tom Ritch­ford had writ­ten Pi3D, a Python Open GL ES 2.0 library for the Rasp­berry Pi.

The soft­ware dra­mat­i­cal­ly sim­pli­fies writ­ing 3D in Python while giv­ing access to the pow­er of the Rasp­berry Pi GPU. What that means in layman's terms is that this soft­ware ful­ly exploits the graph­i­cal capa­bil­i­ties of the small Rasp­berry Pi.

Pad­dy showed inter­est in my dig­i­tal pic­ture frame project and offered to write a Python script that would pro­duce beau­ti­ful and cus­tomiz­able image tran­si­tions for my exact pur­pose: PictureFrame.py was born.

Let's install Pi3D first and then go into the details of the script.

Installing Pi3D on your Rasp­berry Pi

Tested with: RASPBIAN STRETCH (MAY 2019)

The fol­low­ing instruc­tions assume that you have pre­pared your Rasp­berry Pi along with the instruc­tions in the arti­cle "How to set up your Rasp­berry Pi for your dig­i­tal pic­ture frame". If you haven't done yet, please read this arti­cle first.

Open Ter­mi­nal (or PuT­TY for Win­dows) and con­nect to your Rasp­berry Pi by typ­ing

ssh pi@IP-of-your-Raspberry-Pi (e.g. 192.178.134.79)

Then enter:

sudo apt update && sudo apt upgrade -y
sudo pip3 install pi3d

This may take a few sec­onds until you see a reac­tion.

wget https://github.com/pi3d/pi3d_demos/archive/master.zip
unzip master.zip
rm master.zip
mv pi3d_demos-master pi3d_demos

You're done. Pi3D, the most impor­tant pro­gram for your dig­i­tal pic­ture frame project, is now installed on your Rasp­berry Pi.

You can test the pro­gram by enter­ing (make sure you have a mon­i­tor con­nect­ed to your Rasp­berry Pi):

cd ~/pi3d_demos
python3 Earth.py

If you see a rotat­ing globe, your instal­la­tion has been suc­cess­ful.
Now we need to look at the Python script for our dig­i­tal pic­ture frame use case.

PictureFrame.py - The gamechang­er in my dig­i­tal pic­ture frame project

The script that con­trols the dis­play of your images is PictureFrame.py. It is a Python script which you will find in the pi3d_demos fold­er.

I have slight­ly mod­i­fied the orig­i­nal script to only use cross-fad­ing tran­si­tions and a few oth­er set­tings. You can down­load my Python script here and replace the one in the pi3d_demos fold­er.

  1. #!/usr/bin/python
  2.  
  3. from __future__ import absolute_import, division, print_function, unicode_literals
  4. """This demo shows the use of special transition shaders on the Canvas 
  5. shape for 2D drawing.
  6. """
  7. import random, time, os
  8. # if pi3d isn't installed as a module then you will need..
  9. import demo # only needed if pi3d not 'installed'. Does the same as..
  10. #import sys
  11. #sys.path.insert(1, '/home/pi/pi3d')
  12. import pi3d
  13.  
  14. ########################################################################
  15. # set the user variables here
  16. ########################################################################
  17. PIC_DIR = '/home/pi/Pictures/ourphotos/' # for filtering subdirectories
  18.                # and file names alter lines c. 52 and 56 below
  19. TMDELAY = 200.0  # time between slides This needs to be big enough for
  20.                # texture loading and fading
  21. FPS = 20       # animation frames per second
  22. FADE_TM = 10.0  # time for fading
  23. TK = False     # set to true to run in tk window (have to start x server)
  24. MIPMAP = False # whether to anti-alias map screen pixels to image pixels
  25.                # set False if no scaling required
  26. SHUFFLE = True # randomly shuffle the pictures
  27. PPS = 1        # how many pictures to show before changing shader
  28. CHKNUM = 700    # number of picture between re-loading file list
  29. ########################################################################
  30.  
  31. def tex_load(fname):
  32.   ''' return a slide object
  33.   '''
  34.   slide = Slide()
  35.   if not os.path.isfile(fname):
  36.     return None
  37.   tex = pi3d.Texture(fname, blend=True, mipmap=MIPMAP)
  38.   xrat = DISPLAY.width/tex.ix
  39.   yrat = DISPLAY.height/tex.iy
  40.   if yrat < xrat:
  41.     xrat = yrat
  42.   wi, hi = tex.ix * xrat, tex.iy * xrat
  43.   xi = (DISPLAY.width - wi)/2
  44.   yi = (DISPLAY.height - hi)/2
  45.   slide.tex = tex
  46.   slide.dimensions = (wi, hi, xi, yi)
  47.   return slide
  48.  
  49. def get_files():
  50.   global SHUFFLE, PIC_DIR
  51.   file_list = []
  52.   extensions = ['.png','.jpg','.jpeg'] # can add to these
  53.   for root, dirnames, filenames in os.walk(PIC_DIR):
  54.       for filename in filenames:
  55.           ext = os.path.splitext(filename)[1].lower()
  56.           if ext in extensions and not '.AppleDouble' in root and not filename.startswith('.'):
  57.               file_list.append(os.path.join(root, filename)) 
  58.   if SHUFFLE:
  59.     random.shuffle(file_list) # randomize pictures
  60.   else:
  61.     file_list.sort() # if not suffled; sort by name
  62.   return file_list, len(file_list) # tuple of file list, number of pictures
  63.  
  64. class Slide(object):
  65.   def __init__(self):
  66.     self.tex = None
  67.     self.dimensions = None
  68.  
  69. # Setup display and initialise pi3d
  70. DISPLAY = pi3d.Display.create(background=(0.0, 0.0, 0.0, 1.0),
  71.                                 frames_per_second=FPS, tk=TK)
  72. '''if TK:
  73.   win = DISPLAY.tkwin
  74.   win.update()
  75. else:
  76.   mykeys = pi3d.Keyboard() # don't need this for picture frame but useful for testing'''
  77.  
  78. shader = [pi3d.Shader("shaders/blend_bump")]
  79. num_sh = len(shader)
  80.  
  81. iFiles, nFi = get_files()
  82. fade = 0.0
  83. pic_num = nFi - 1
  84.  
  85. canvas = pi3d.Canvas()
  86. canvas.set_shader(shader[0])
  87.  
  88. CAMERA = pi3d.Camera.instance()
  89. CAMERA.was_moved = False #to save a tiny bit of work each loop
  90. pictr = 0 # to allow shader changing every PPS slides
  91. shnum = 0 # shader number
  92. nexttm = 0.0 # force next on first loop
  93. fade_step = 1.0 / (FPS * FADE_TM)
  94.  
  95. sbg = tex_load(iFiles[pic_num]) # initially load a background slide
  96.  
  97. while DISPLAY.loop_running():
  98.   tm = time.time()
  99.   if tm > nexttm: # load next image
  100.     nexttm = tm + TMDELAY
  101.     fade = 0.0 # reset fade to beginning
  102.     sfg = sbg # foreground Slide set to old background
  103.     pic_num = (pic_num + 1) % nFi # wraps to start
  104.     if (pic_num % CHKNUM) == 0: # this will shuffle as well
  105.       iFiles, nFi = get_files()
  106.       pic_num = pic_num % nFi # just in case list is severly shortened
  107.     tmp_slide = tex_load(iFiles[pic_num]) # background Slide load.
  108.     if tmp_slide != None: # checking in case file deleted
  109.       sbg = tmp_slide
  110.     canvas.set_draw_details(canvas.shader,[sfg.tex, sbg.tex]) # reset two textures
  111.     canvas.set_2d_size(sbg.dimensions[0], sbg.dimensions[1], sbg.dimensions[2], sbg.dimensions[3])
  112.     canvas.unif[48:54] = canvas.unif[42:48] #need to pass shader dimensions for both textures
  113.     canvas.set_2d_size(sfg.dimensions[0], sfg.dimensions[1], sfg.dimensions[2], sfg.dimensions[3])
  114.     pictr += 1
  115.     if pictr >= PPS:# shader change Pics Per Shader
  116.       pictr = 0
  117.       shnum = (shnum + 1) % num_sh
  118.       canvas.set_shader(shader[shnum])
  119.  
  120.   if fade < 1.0:
  121.     fade += fade_step # increment fade
  122.     if fade > 1.0: # more efficient to test here than in pixel shader
  123.       fade = 1.0
  124.     canvas.unif[44] = fade # pass value to shader using unif list
  125.  
  126.   canvas.draw() # then draw it

There are a few small mod­i­fi­ca­tions that you need to make.

Open the script in an edi­tor (e.g., Sub­lime Text).

The user vari­ables are defined under "# set the user vari­ables here" in line 17.

PIC_DIR: Enter your pic­tures direc­to­ry, e.g.

/home/pi/Pictures/ourphotos/

TMDELAY: Enter the delay between slides in sec­onds, e.g.,

200

FADE_TM: Enter fad­ing time in sec­onds: e.g.,

10

CHKNUM: This defines how often you refresh the list. I usu­al­ly enter a val­ue which is about 70% of the total num­ber of my images. So if you have 1,000 pho­tos in my direc­to­ry, you could enter

700

A high­er val­ue reduces the (ran­dom) chance of an image show­ing twice.

Don't for­get to mod­i­fy your image direc­to­ry which in my ver­sion is set to "/ourphotos".

Now upload a few images to your Rasp­berry Pi Pic­tures fold­er via file shar­ing and check if every­thing works.

In the ter­mi­nal win­dow enter:

cd pi3d_demos
python3 PictureFrame.py

The slideshow should start after a few sec­onds (Note: the first slide is imme­di­ate­ly fad­ed).

You can man­u­al­ly stop the slideshow by hit­ting Ctrl+C.

Auto­mat­i­cal­ly start the Pi3D slideshow at boot

Install the screen soft­ware

sudo apt install screen -y

Cre­ate a new file

nano start_slideshow.sh

Paste the fol­low­ing text into the file:

#!/bin/bash
cd pi3d_demos
screen -dmS PICFRAME python3 PictureFrame.py

Save it (CTRL+O), exit (CTRL-X) and make the file exe­cutable.

sudo chmod +x start_slideshow.sh

Open the crontab

crontab -e

and add this line

@reboot /home/pi/start_slideshow.sh

Reboot and Pi3D will now launch auto­mat­i­cal­ly at boot.

Pro­vid­ed every­thing works as expect­ed, you can now upload your full image col­lec­tion to your pho­to fold­er. Con­grat­u­la­tions!

Thanks again to Pad­dy and his col­leagues for such an out­stand­ing con­tri­bu­tion. I have yet to find a sim­i­lar pro­gram which makes such beau­ti­ful tran­si­tions on the Rasp­berry Pi pos­si­ble. You can read more about Pi3D here.