How I added crossfading slide transitions to my digital picture frame using Pi3D

By February 11, 2019 May 11, 2019 DIY Instructions, Software

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 difference

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.

Taking the Raspberry Pi's graphical capabilities up a few notches

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 Raspberry Pi

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-get update && sudo apt-get upgrade
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 gamechanger in my digital picture 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.

#!/usr/bin/python

from __future__ import absolute_import, division, print_function, unicode_literals
"""This demo shows the use of special transition shaders on the Canvas 
shape for 2D drawing.
"""
import random, time, os
# if pi3d isn't installed as a module then you will need..
import demo # only needed if pi3d not 'installed'. Does the same as..
#import sys
#sys.path.insert(1, '/home/pi/pi3d')
import pi3d

########################################################################
# set the user variables here
########################################################################
PIC_DIR = '/home/pi/Pictures/ourphotos/' # for filtering subdirectories
               # and file names alter lines c. 52 and 56 below
TMDELAY = 200.0  # time between slides This needs to be big enough for
               # texture loading and fading
FPS = 20       # animation frames per second
FADE_TM = 10.0  # time for fading
TK = False     # set to true to run in tk window (have to start x server)
MIPMAP = False # whether to anti-alias map screen pixels to image pixels
               # set False if no scaling required
SHUFFLE = True # randomly shuffle the pictures
PPS = 1        # how many pictures to show before changing shader
CHKNUM = 700    # number of picture between re-loading file list
########################################################################

def tex_load(fname):
  ''' return a slide object
  '''
  slide = Slide()
  if not os.path.isfile(fname):
    return None
  tex = pi3d.Texture(fname, blend=True, mipmap=MIPMAP)
  xrat = DISPLAY.width/tex.ix
  yrat = DISPLAY.height/tex.iy
  if yrat < xrat:
    xrat = yrat
  wi, hi = tex.ix * xrat, tex.iy * xrat
  xi = (DISPLAY.width - wi)/2
  yi = (DISPLAY.height - hi)/2
  slide.tex = tex
  slide.dimensions = (wi, hi, xi, yi)
  return slide

def get_files():
  global SHUFFLE, PIC_DIR
  file_list = []
  extensions = ['.png','.jpg','.jpeg'] # can add to these
  for root, dirnames, filenames in os.walk(PIC_DIR):
      for filename in filenames:
          ext = os.path.splitext(filename)[1].lower()
          if ext in extensions and not '.AppleDouble' in root and not filename.startswith('.'):
              file_list.append(os.path.join(root, filename)) 
  if SHUFFLE:
    random.shuffle(file_list) # randomize pictures
  else:
    file_list.sort() # if not suffled; sort by name
  return file_list, len(file_list) # tuple of file list, number of pictures

class Slide(object):
  def __init__(self):
    self.tex = None
    self.dimensions = None

# Setup display and initialise pi3d
DISPLAY = pi3d.Display.create(background=(0.0, 0.0, 0.0, 1.0),
                                frames_per_second=FPS, tk=TK)
'if TK:
  win = DISPLAY.tkwin
  win.update()
else:
  mykeys = pi3d.Keyboard() # don't need this for picture frame but useful for testing'''
 
shader = [pi3d.Shader("shaders/blend_bump")]
num_sh = len(shader)

iFiles, nFi = get_files()
fade = 0.0
pic_num = nFi - 1

canvas = pi3d.Canvas()
canvas.set_shader(shader[0])

CAMERA = pi3d.Camera.instance()
CAMERA.was_moved = False #to save a tiny bit of work each loop
pictr = 0 # to allow shader changing every PPS slides
shnum = 0 # shader number
nexttm = 0.0 # force next on first loop
fade_step = 1.0 / (FPS * FADE_TM)

sbg = tex_load(iFiles[pic_num]) # initially load a background slide

while DISPLAY.loop_running():
  tm = time.time()
  if tm > nexttm: # load next image
    nexttm = tm + TMDELAY
    fade = 0.0 # reset fade to beginning
    sfg = sbg # foreground Slide set to old background
    pic_num = (pic_num + 1) % nFi # wraps to start
    if (pic_num % CHKNUM) == 0: # this will shuffle as well
      iFiles, nFi = get_files()
      pic_num = pic_num % nFi # just in case list is severly shortened
    tmp_slide = tex_load(iFiles[pic_num]) # background Slide load.
    if tmp_slide != None: # checking in case file deleted
      sbg = tmp_slide
    canvas.set_draw_details(canvas.shader,[sfg.tex, sbg.tex]) # reset two textures
    canvas.set_2d_size(sbg.dimensions[0], sbg.dimensions[1], sbg.dimensions[2], sbg.dimensions[3])
    canvas.unif[48:54] = canvas.unif[42:48] #need to pass shader dimensions for both textures
    canvas.set_2d_size(sfg.dimensions[0], sfg.dimensions[1], sfg.dimensions[2], sfg.dimensions[3])
    pictr += 1
    if pictr >= PPS:# shader change Pics Per Shader
      pictr = 0
      shnum = (shnum + 1) % num_sh
      canvas.set_shader(shader[shnum])

  if fade < 1.0:
    fade += fade_step # increment fade
    if fade > 1.0: # more efficient to test here than in pixel shader
      fade = 1.0
    canvas.unif[44] = fade # pass value to shader using unif list

  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 700A 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.

Automatically start the Pi3D slideshow at boot

Install the screen soft­ware
sudo apt-get install screen

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.