A basic graphics package for the LPC4088 Display Module.

Dependents:   lpc4088_displaymodule_demo_sphere sampleGUI sampleEmptyGUI lpc4088_displaymodule_fs_aid ... more

Fork of DMBasicGUI by EmbeddedArtists AB

Files at this revision

API Documentation at this revision

Comitter:
embeddedartists
Date:
Sun Dec 21 13:53:07 2014 +0100
Parent:
4:a73760d09423
Child:
6:7917b0894655
Commit message:
- Added support for RAW images
- Added SlideShow + App for it
- Moved App-specific stuff out from the AppLauncher and into main

Changed in this revision

Application/AppLauncher.cpp Show annotated file Show diff for this revision Revisions of this file
Application/AppLauncher.h Show annotated file Show diff for this revision Revisions of this file
Application/Image.cpp Show annotated file Show diff for this revision Revisions of this file
Application/Image.h Show annotated file Show diff for this revision Revisions of this file
SlideShow/AppSlideShow.cpp Show annotated file Show diff for this revision Revisions of this file
SlideShow/AppSlideShow.h Show annotated file Show diff for this revision Revisions of this file
SlideShow/Renderer.cpp Show annotated file Show diff for this revision Revisions of this file
SlideShow/Renderer.h Show annotated file Show diff for this revision Revisions of this file
SlideShow/SlideShow.cpp Show annotated file Show diff for this revision Revisions of this file
SlideShow/SlideShow.h Show annotated file Show diff for this revision Revisions of this file
--- a/Application/AppLauncher.cpp	Fri Dec 19 16:40:30 2014 +0100
+++ b/Application/AppLauncher.cpp	Sun Dec 21 13:53:07 2014 +0100
@@ -17,10 +17,6 @@
 
 #include "mbed.h"
 #include "AppLauncher.h"
-#include "AppSettings.h"
-#include "AppTouchCalibration.h"
-#include "AppColorPicker.h"
-#include "AppImageViewer.h"
 #include "lpc_swim_font.h"
 #include "Button.h"
 #include "ImageButton.h"
@@ -31,25 +27,27 @@
  
 #define APP_PREFIX  "[Launcher] "
 
+#define NO_APPLICATION  (-1)
 
-typedef enum {
-    NoApplication = -1,
-    SettingsApp   =  0,
-    ColorPicker,
-    TouchTestApp,
-    ImageViewerApp,
-    //SlideshowApp,
-    //TouchGFXApp,
-    //EmWinApp,
-    Placeholder    =  99,
-    CalibrationApp = 100,
-} AppID_t;
+
+//typedef enum {
+//    NoApplication = -1,
+//    SettingsApp   =  0,
+//    ColorPicker,
+//    TouchTestApp,
+//    ImageViewerApp,
+//    SlideshowApp,
+//    //TouchGFXApp,
+//    //EmWinApp,
+//    Placeholder    =  99,
+//    CalibrationApp = 100,
+//} AppID_t;
 
 /******************************************************************************
  * Private variables
  *****************************************************************************/
 
-static AppID_t appToLaunch = NoApplication;
+static int appToLaunch = NO_APPLICATION;
 
 /******************************************************************************
  * Private functions
@@ -57,43 +55,11 @@
 
 static void buttonClicked(uint32_t x)
 {
-    if (appToLaunch == NoApplication) {
-        appToLaunch = (AppID_t)x;
+    if (appToLaunch == NO_APPLICATION) {
+        appToLaunch = (int)x;
     }
 }
 
-void AppLauncher::addButton(uint32_t buttonID, const char* caption)
-{
-    int idx = _usedButtons++;
-    int xspace = ((_disp->width() - ButtonColumns * ButtonWidth) / (ButtonColumns + 1));
-    int yspace = ((_disp->height() - TitleHeight - ButtonRows * ButtonHeight) / (ButtonRows + 1));
-    
-    _buttons[idx] = new Button(caption, (COLOR_T*)_fb, 
-                              xspace + (ButtonWidth + xspace)*(idx%ButtonColumns), 
-                              TitleHeight + yspace + (ButtonHeight + yspace)*(idx/ButtonColumns), 
-                              ButtonWidth, ButtonHeight);
-    _buttons[idx]->setAction(buttonClicked, buttonID);
-    _buttons[idx]->draw();
-}
-
-void AppLauncher::addImageButton(uint32_t buttonID, const char* imgUp, const char* imgDown)
-{
-    int idx = _usedButtons++;
-    int xspace = ((_disp->width() - ButtonColumns * 64) / (ButtonColumns + 1));
-    int yspace = ((_disp->height() - TitleHeight - ButtonRows * 64) / (ButtonRows + 1));
-    
-    ImageButton* img =  new ImageButton((COLOR_T*)_fb, 
-                              xspace + (64 + xspace)*(idx%ButtonColumns), 
-                              TitleHeight + yspace + (64 + yspace)*(idx/ButtonColumns), 
-                              64, 64);
-    if (!img->loadImages(imgUp, imgDown)) {
-      DMBoard::instance().logger()->printf("Failed to load image for buttonID %u, %s[%s]\n", buttonID, imgUp, imgDown==NULL?"":imgDown);
-    }
-    _buttons[idx] = img;
-    _buttons[idx]->setAction(buttonClicked, buttonID);
-    _buttons[idx]->draw();
-}
-
 void AppLauncher::draw()
 {
     // Prepare fullscreen
@@ -105,41 +71,14 @@
                      WHITE, BLACK, BLACK);                    // colors: pen, backgr, forgr
     swim_set_title(_win, "Demo Program", BLACK);
 
-    // Add many buttons
-#if 0    
-    addButton(SettingsApp,  "Settings");
-    addButton(TouchTestApp, "Test Touch");
-    //addButton(SlideshowApp, "Slideshow");
-    //addButton(TouchGFXApp,  "TouchGFX");
-    //addButton(EmWinApp,     "emWin");
-    addButton(ColorPicker,  "Color Picker");
-    addButton(ImageViewerApp,  "Image Viewer");
-    //addButton(5, "Button 5");
-    //addButton(6, "Button 6");
-    //addButton(7, "Button 7");
-    //addButton(8, "Button 8");
-    //addButton(9, "Button 9");
-#else
-    addImageButton(SettingsApp,  "/usb/preferences-desktop-applications.png");
-    addImageButton(TouchTestApp, "/usb/bijiben.png");
-    //addImageButton(SlideshowApp, "Slideshow");
-    //addImageButton(TouchGFXApp,  "TouchGFX");
-    //addImageButton(EmWinApp,     "emWin");
-    addImageButton(ColorPicker,  "/usb/preferences-color.png");
-    addImageButton(ImageViewerApp,  "/usb/multimedia-photo-manager.png");
-    addImageButton(Placeholder,  "/usb/help-info.png");
-    addImageButton(Placeholder,  "/usb/unetbootin.png");
-    //addImageButton(5, "Button 5");
-    //addImageButton(6, "Button 6");
-    //addImageButton(7, "Button 7");
-    //addImageButton(8, "Button 8");
-    //addImageButton(9, "Button 9");
-#endif
-
     const char* msg = "(Press physical UserButton >2s to calibrate touch)";
     int w, h;
     swim_get_string_bounds(_win, msg, &w, &h);
     swim_put_text_xy(_win, msg, (_disp->width()-w)/2, _disp->height()-h*4);
+    
+    for (int i = 0; i < _usedButtons; i++) {
+        _buttons[i]->draw();
+    }
 }
 
 /******************************************************************************
@@ -208,7 +147,7 @@
         }
         
         // Check if the physical USER button on the board has been pressed
-        if (appToLaunch == NoApplication) {
+        if (appToLaunch == NO_APPLICATION) {
             if (board->buttonPressed()) {
                 if (buttonPressed) {
                     if (buttonTimer.read_ms() > 2000) {
@@ -234,23 +173,10 @@
             buttonPressed = false;
         }
         
-        if (appToLaunch != NoApplication) {
+        if (appToLaunch != NO_APPLICATION) {
             App* a = NULL;
-            switch (appToLaunch) {
-                case SettingsApp:
-                    a = new AppSettings();
-                    break;
-                case CalibrationApp:
-                    a = new AppTouchCalibration();
-                    break;
-                case ColorPicker:
-                    a = new AppColorPicker();
-                    break;
-                case ImageViewerApp:
-                    a = new AppImageViewer();
-                    break;
-                default:
-                    break;
+            if (_callback != NULL) {
+                a = _callback(appToLaunch);
             }
             if (a != NULL) {
                 if (a->setup()) {
@@ -259,8 +185,8 @@
                 }
                 delete a;
             }
-            appToLaunch = NoApplication;
-        }        
+            appToLaunch = NO_APPLICATION;
+        }
     }
 }
 
@@ -284,4 +210,64 @@
     return true;
 }
 
+void AppLauncher::setAppCreatorFunc(App*(*callback)(uint32_t buttonID))
+{
+    _callback = callback;
+}
 
+bool AppLauncher::addButton(uint32_t buttonID, const char* caption)
+{
+    int idx = _usedButtons++;
+    int xspace = ((_disp->width() - ButtonColumns * ButtonWidth) / (ButtonColumns + 1));
+    int yspace = ((_disp->height() - TitleHeight - ButtonRows * ButtonHeight) / (ButtonRows + 1));
+    
+    _buttons[idx] = new Button(caption, (COLOR_T*)_fb, 
+                              xspace + (ButtonWidth + xspace)*(idx%ButtonColumns), 
+                              TitleHeight + yspace + (ButtonHeight + yspace)*(idx/ButtonColumns), 
+                              ButtonWidth, ButtonHeight);
+    _buttons[idx]->setAction(buttonClicked, buttonID);
+    //_buttons[idx]->draw();
+    return true;
+}
+
+bool AppLauncher::addImageButton(uint32_t buttonID, const char* imgUp, const char* imgDown)
+{
+    int idx = _usedButtons++;
+    int xspace = ((_disp->width() - ButtonColumns * 64) / (ButtonColumns + 1));
+    int yspace = ((_disp->height() - TitleHeight - ButtonRows * 64) / (ButtonRows + 1));
+    
+    ImageButton* img =  new ImageButton((COLOR_T*)_fb, 
+                              xspace + (64 + xspace)*(idx%ButtonColumns), 
+                              TitleHeight + yspace + (64 + yspace)*(idx/ButtonColumns), 
+                              64, 64);
+    if (img->loadImages(imgUp, imgDown)) {
+      _buttons[idx] = img;
+      _buttons[idx]->setAction(buttonClicked, buttonID);
+      //_buttons[idx]->draw();
+      return true;
+    } else {
+      //DMBoard::instance().logger()->printf("Failed to load image for buttonID %u, %s[%s]\n", buttonID, imgUp, imgDown==NULL?"":imgDown);
+      return false;
+    }
+}
+
+bool AppLauncher::addImageButton(uint32_t buttonID, const unsigned char* imgUp, unsigned int imgUpSize, const unsigned char* imgDown, unsigned int imgDownSize)
+{
+    int idx = _usedButtons++;
+    int xspace = ((_disp->width() - ButtonColumns * 64) / (ButtonColumns + 1));
+    int yspace = ((_disp->height() - TitleHeight - ButtonRows * 64) / (ButtonRows + 1));
+    
+    ImageButton* img =  new ImageButton((COLOR_T*)_fb, 
+                              xspace + (64 + xspace)*(idx%ButtonColumns), 
+                              TitleHeight + yspace + (64 + yspace)*(idx/ButtonColumns), 
+                              64, 64);
+    if (img->loadImages(imgUp, imgUpSize, imgDown, imgDownSize)) {
+      _buttons[idx] = img;
+      _buttons[idx]->setAction(buttonClicked, buttonID);
+      //_buttons[idx]->draw();
+      return true;
+    } else {
+      //DMBoard::instance().logger()->printf("Failed to load image for buttonID %u, %s[%s]\n", buttonID, imgUp, imgDown==NULL?"":imgDown);
+      return false;
+    }
+}
--- a/Application/AppLauncher.h	Fri Dec 19 16:40:30 2014 +0100
+++ b/Application/AppLauncher.h	Sun Dec 21 13:53:07 2014 +0100
@@ -36,10 +36,20 @@
 	AppLauncher();
 	~AppLauncher();
 
+    enum CommonApplicationIDs {
+        CalibrationApp = 0xffff,
+    };
+
     virtual bool setup();
     virtual void runToCompletion();
     virtual bool teardown();
 
+    void setAppCreatorFunc(App*(*callback)(uint32_t buttonID));
+
+    bool addButton(uint32_t buttonID, const char* caption);
+    bool addImageButton(uint32_t buttonID, const char* imgUp, const char* imgDown = 0);
+    bool addImageButton(uint32_t buttonID, const unsigned char* imgUp, unsigned int imgUpSize, const unsigned char* imgDown = 0, unsigned int imgDownSize = 0);
+
 private:
     enum Constants {
        TitleHeight = 20,
@@ -55,10 +65,9 @@
     void* _fb;
     Clickable* _buttons[NumberOfButtons];
     int _usedButtons;
+    App*(*_callback)(uint32_t buttonID);
 
     void draw();
-    void addButton(uint32_t buttonID, const char* caption);
-    void addImageButton(uint32_t buttonID, const char* imgUp, const char* imgDown = 0);
 };
 
 #endif
--- a/Application/Image.cpp	Fri Dec 19 16:40:30 2014 +0100
+++ b/Application/Image.cpp	Sun Dec 21 13:53:07 2014 +0100
@@ -20,6 +20,13 @@
 #include "bmp.h"
 #include "lodepng.h"
 
+struct eaimg_header_t
+{
+  char prefix[6];
+  uint16_t width;
+  uint16_t height;
+} __attribute__ ((packed));
+
 int Image::decode(const unsigned char* pDataIn, unsigned int sizeIn, Resolution resolution, ImageData_t* pDataOut)
 {
   Image::Type type = imageType(pDataIn, sizeIn);
@@ -36,6 +43,7 @@
       } else {
         return -1;
       }
+      pDataOut->pointerToFree = pDataOut->pixels;
       if (pDataOut->pixels != NULL)
       {
         unsigned char error = BMP_Decode((void*)pDataIn, (unsigned char*)pDataOut->pixels, 
@@ -48,6 +56,7 @@
           return 0;
         }
         free(pDataOut->pixels);
+        pDataOut->pointerToFree = NULL;
       }
     }
     break;
@@ -68,6 +77,7 @@
         result = 0;
         if (resolution == RES_16BIT) {
             pDataOut->pixels = (uint16_t*)malloc(pDataOut->width * pDataOut->height * 2);
+            pDataOut->pointerToFree = pDataOut->pixels;
             if (pDataOut->pixels != NULL)
             {
                 uint16_t* pConverted = pDataOut->pixels;
@@ -88,6 +98,7 @@
         } else if (resolution == RES_24BIT) {
             uint32_t* pConverted = (uint32_t*)malloc(pDataOut->width * pDataOut->height * 4);
             pDataOut->pixels = (uint16_t*)pConverted;
+            pDataOut->pointerToFree = pDataOut->pixels;
             if (pDataOut->pixels != NULL)
             {
                 uint8_t* p = pTmp;
@@ -110,22 +121,50 @@
     }
     break;
     
+    case RAW:
+    {
+      eaimg_header_t* hdr = (eaimg_header_t*)pDataIn;
+      pDataOut->width = hdr->width;
+      pDataOut->height = hdr->height;
+      pDataOut->pointerToFree = malloc(sizeIn-sizeof(eaimg_header_t));
+      pDataOut->pixels = (uint16_t*)pDataOut->pointerToFree;
+      pDataOut->res = RES_16BIT;
+      if (pDataOut->pixels != NULL)
+      {
+        memcpy(pDataOut->pixels, pDataIn+sizeof(eaimg_header_t), sizeIn-sizeof(eaimg_header_t));
+        return 0;
+      }
+    }
+    break;
+
     default:
       break;
   }
   
   pDataOut->pixels = NULL;
+  pDataOut->pointerToFree = NULL;
   pDataOut->width = 0;
   pDataOut->height = 0;
+  pDataOut->res = resolution;
   return result;
 }
 
-int Image::decode(const char* filename, Resolution res, ImageData_t* pDataOut)
+int Image::decode(const char* filename, Resolution res, ImageData_t* pDataOut, Mutex* pLock)
 {
   FILE* fh = NULL;
   uint8_t* buff = NULL;
   int result = 1;
 
+  pDataOut->height = 0;
+  pDataOut->width = 0;
+  pDataOut->pixels = NULL;
+  pDataOut->pointerToFree = NULL;
+  pDataOut->res = res;
+
+  if (pLock != NULL) {
+      pLock->lock();
+  }
+
   do 
   {
     fh = fopen(filename, "r");
@@ -154,12 +193,25 @@
       break;
     }
     
-    if (Image::decode(buff, size, res, pDataOut) == 1) {
-      break;
+    fclose(fh);
+    if (pLock != NULL) {
+      pLock->unlock();
+    }
+    
+    Type type = imageType(buff, size);
+    if (type == RAW) {
+        pDataOut->width = ((eaimg_header_t*)buff)->width;
+        pDataOut->height = ((eaimg_header_t*)buff)->height;
+        pDataOut->pointerToFree = buff;
+        pDataOut->res = RES_16BIT;
+        pDataOut->pixels = (uint16_t*)(buff + sizeof(eaimg_header_t));
+    } else {
+        result = Image::decode(buff, size, res, pDataOut);
+        free(buff);
     }
     
     // success
-    result = 0;
+    return 0;
     
   } while (false);
   
@@ -169,6 +221,9 @@
   if (buff != NULL) {
     free(buff);
   }
+  if (pLock != NULL) {
+      pLock->unlock();
+  }
   return result;
 }
 
@@ -185,6 +240,14 @@
   {
     return BMP;
   }
+  if (sizeIn >= sizeof(eaimg_header_t))
+  {
+    eaimg_header_t* hdr = (eaimg_header_t*)pDataIn;
+    if (memcmp(hdr->prefix, "eaimg:", 6) == 0)
+    {
+      return RAW;
+    }
+  }
   return UNKNOWN;
 }
 
--- a/Application/Image.h	Fri Dec 19 16:40:30 2014 +0100
+++ b/Application/Image.h	Sun Dec 21 13:53:07 2014 +0100
@@ -18,6 +18,7 @@
 #define IMAGE_H
 
 #include "mbed.h"
+#include "rtos.h"
 
 /**
  * Image example
@@ -56,6 +57,7 @@
     enum Type {
         BMP = 0,
         PNG,
+        RAW,      /* Image prepared with the img_conv.jar tool */
         UNKNOWN
     };
     
@@ -69,6 +71,7 @@
         uint32_t   width;
         uint32_t   height;
         Resolution res;
+        void*      pointerToFree;
     } ImageData_t;
 
     /** Decodes the specified image data
@@ -97,12 +100,13 @@
      *  @param filename the file name and path
      *  @param res the format of the display
      *  @param pDataOut the decoded image (only valid if 0 is returned)
+     *  @param pLock an optional mutex to prevent multiple access to file operations
      *
      *  @returns
      *       0 on success
      *       1 on failure
      */
-    static int decode(const char* filename, Resolution res, ImageData_t* pDataOut);
+    static int decode(const char* filename, Resolution res, ImageData_t* pDataOut, Mutex* pLock=NULL);
     
 private:
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SlideShow/AppSlideShow.cpp	Sun Dec 21 13:53:07 2014 +0100
@@ -0,0 +1,118 @@
+/*
+ *  Copyright 2014 Embedded Artists AB
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+
+#include "mbed.h"
+#include "AppSlideShow.h"
+
+/******************************************************************************
+ * Defines and typedefs
+ *****************************************************************************/
+
+#define TICKER_RESOLUTION_IN_MS  10
+
+/******************************************************************************
+ * Global variables
+ *****************************************************************************/
+
+volatile uint32_t msTicks = 0;
+
+/******************************************************************************
+ * Private functions
+ *****************************************************************************/
+
+static void tRender(void const *args)
+{
+  Renderer* s = (Renderer*)args;
+  s->render();
+}
+
+static void ticker(void const *n) {
+  msTicks += TICKER_RESOLUTION_IN_MS;
+}
+
+/******************************************************************************
+ * Public functions
+ *****************************************************************************/
+
+AppSlideShow::AppSlideShow() : _fb(NULL), _disp(NULL), _show(NULL), _rend(NULL), _fileMutex()
+{
+}
+
+AppSlideShow::~AppSlideShow()
+{
+    teardown();
+}
+
+bool AppSlideShow::setup()
+{
+    SlideShow::SlideShowError err;
+    
+    _disp = DMBoard::instance().display();
+    
+    _fb = _disp->allocateFramebuffer();
+    if (_fb == NULL) {
+        err = SlideShow::OutOfMemory;
+    } else {
+        memset(_fb, 0xff, _disp->fbSize());
+        _rend = new Renderer();
+        _show = new SlideShow(_rend, "/mci/elec14", NULL, 150, 95, 1, &_fileMutex);
+        err = _show->prepare("/mci/elec14/ea_logo.txt");
+    }
+
+    return (err == SlideShow::Ok);
+}
+
+void AppSlideShow::runToCompletion()
+{
+    // Save existing frame buffer
+    void* oldFB = _disp->swapFramebuffer(_fb);
+    
+    // Alternative 1: use the calling thread's context to run in
+    Thread tr(tRender, _rend, osPriorityHigh);
+    _rend->setRenderThread(&tr);
+    
+    // Generate the millisecond ticks for the slideshow
+    RtosTimer rtosTimer(ticker, osTimerPeriodic);
+    rtosTimer.start(TICKER_RESOLUTION_IN_MS);    
+
+    // Wait for slideshow to complete
+    _show->run();
+    
+    tr.terminate();
+    
+    // Restore the original FB
+    _disp->swapFramebuffer(oldFB);
+}
+
+bool AppSlideShow::teardown()
+{
+    if (_show != NULL) {
+        free(_show);
+        _show = NULL;
+    }
+    if (_rend != NULL) {
+        free(_rend);
+        _rend = NULL;
+    }
+    if (_fb != NULL) {
+        free(_fb);
+        _fb = NULL;
+    }
+    return true;
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SlideShow/AppSlideShow.h	Sun Dec 21 13:53:07 2014 +0100
@@ -0,0 +1,48 @@
+/*
+ *  Copyright 2014 Embedded Artists AB
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+ 
+#ifndef APP_SLIDESHOW_H
+#define APP_SLIDESHOW_H
+
+#include "App.h"
+#include "DMBoard.h"
+#include "SlideShow.h"
+#include "rtos.h"
+
+/**
+ * An App example. Slideshow.
+ *
+ * The purpose of this example is to show how the SlideShow class can be used.
+ */
+class AppSlideShow : public App {
+public:
+
+	AppSlideShow();
+	~AppSlideShow();
+
+    virtual bool setup();
+    virtual void runToCompletion();
+    virtual bool teardown();
+
+private:
+    void* _fb;
+    Display* _disp;
+    SlideShow* _show;
+    Renderer* _rend;
+    Mutex _fileMutex;
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SlideShow/Renderer.cpp	Sun Dec 21 13:53:07 2014 +0100
@@ -0,0 +1,240 @@
+#include "mbed.h"
+#include "Renderer.h"
+#include "DMBoard.h"
+
+#if !defined(MIN)
+  #define MIN(__a, __b)  (((__a)<(__b))?(__a):(__b))
+#endif
+
+Renderer::Renderer(/*LcdController::Config* screen, EaLcdBoard* lcdBoard*/)
+{
+  memset(layers, 0, sizeof(layerinfo_t)*MaxNumLayers);
+  for (int i = 0; i < MaxNumLayers; i++) {
+    layers[i].used = false;
+    order[i] = NULL;
+  }
+
+  numRegisteredLayers = 0;
+  t = NULL;
+  activeBackBuffer = 0;
+  display = DMBoard::instance().display();
+
+  this->screenWidth = display->width();
+  this->screenHeight = display->height();
+  this->screenPixels = this->screenWidth * this->screenHeight;
+
+  // Assume screen->bpp == Bpp_16
+  this->screenBytes = display->bytesPerPixel() * this->screenPixels;
+
+  ImageBackBuffer[0] = (image_t)display->allocateFramebuffer();//malloc(this->screenBytes);
+  ImageBackBuffer[1] = (image_t)display->allocateFramebuffer();//malloc(this->screenBytes);
+  if ((ImageBackBuffer[0] == NULL) || (ImageBackBuffer[1] == NULL)) {
+    printf("Failed to allocate buffer(s) for Renderer. Halting...\n");
+    while(1) {
+      ;
+    }
+  }
+  memset(ImageBackBuffer[0], 0xff, screenBytes);
+  memset(ImageBackBuffer[1], 0xff, screenBytes);
+}
+
+Renderer::~Renderer()
+{
+  free(ImageBackBuffer[0]);
+  free(ImageBackBuffer[1]);
+  for (int i = 0; i < MaxNumLayers; i++) {
+    if (layers[i].used) {
+      free(layers[i].tmp);
+      delete(layers[i].lock);
+    }
+  }
+}
+
+uint32_t Renderer::registerUser(int layer, int xoff, int yoff, int width, int height)
+{
+  setupMutex.lock();
+  if (numRegisteredLayers < MaxNumLayers) {
+    for (int i = 0; i < MaxNumLayers; i++) {
+      if (!layers[i].used) {
+        // found a spot
+        layers[i].used = true;
+        layers[i].x0 = xoff;
+        layers[i].y0 = yoff;
+        layers[i].x1 = MIN(xoff+width, screenWidth);
+        layers[i].y1 = MIN(yoff+height, screenHeight);
+        layers[i].width = width;
+        layers[i].clippedHeight = layers[i].y1 - layers[i].y0;
+        layers[i].zorder = layer;
+        layers[i].signalId = 1;//(1<<i);
+        layers[i].lock = new Mutex();
+        layers[i].newData = NULL;
+        layers[i].activeData = NULL;
+        layers[i].fullscreen = false;
+        if ((xoff == 0) && (yoff == 0) && (width == screenWidth) && (height == screenHeight)) {
+          layers[i].fullscreen = true;
+        }
+
+        layers[i].tmp = (uint16_t*)malloc(width*layers[i].clippedHeight*2);
+        if (layers[i].tmp != NULL) {
+          memset(layers[i].tmp, 0, width*layers[i].clippedHeight*2);
+        }
+
+
+        // sort the order
+        for (int j = 0; j < MaxNumLayers; j++) {
+          if (order[j] == NULL) {
+            // no more layers so add the new one to the top
+            order[j] = &(layers[i]);
+            break;
+          }
+          if (order[j]->zorder > layer) {
+            // should insert the new layer here
+            for (int k = numRegisteredLayers; k > j; k--) {
+              order[k] = order[k-1];
+            }
+            order[j] = &(layers[i]);
+            break;
+          }
+        }
+
+        // Cause a repaint of all layers. It does not have to be immediate
+        // as for unregisterUser() - it is enough that it is done next time.
+        order[0]->activeData = NULL;
+
+        numRegisteredLayers++;
+
+        setupMutex.unlock();
+        return (uint32_t)(&(layers[i]));
+      }
+    }
+  }
+  setupMutex.unlock();
+  return 0;
+}
+
+uint32_t Renderer::registerFullscreenUser(int layer)
+{
+  return registerUser(layer, 0, 0, screenWidth, screenHeight);
+}
+
+void Renderer::unregisterUser(uint32_t handle)
+{
+  setupMutex.lock();
+  if (handle != 0) {
+    layerinfo_t* layer = (layerinfo_t*)handle;
+    for (int i = 0; i < MaxNumLayers; i++) {
+      if (order[i] == layer) {
+        layer->used = false;
+        free(layer->tmp);
+        delete layer->lock;
+
+        // move all layers "down" one step
+        for (int j = i+1; j<numRegisteredLayers; j++) {
+          order[j-1] = order[j];
+        }
+        order[numRegisteredLayers-1] = NULL;
+        numRegisteredLayers--;
+
+        // cause a repaint
+        if (order[0] != NULL) {
+          order[0]->activeData = NULL;
+
+          // Very important that the signal is not sent while the lock is held
+          // as it will cause the renderer thread be able to take it also
+          setupMutex.unlock();
+          t->signal_set(layer->signalId);
+          return;
+        } else {
+          // no longer anything to show, clear back buffers
+          memset(ImageBackBuffer[0], 0xff, screenBytes);
+          memset(ImageBackBuffer[1], 0xff, screenBytes);
+        }
+
+        break;
+      }
+    }
+  }
+  setupMutex.unlock();
+}
+
+void Renderer::setFramebuffer(uint32_t handle, const uint16_t* data)
+{
+  layerinfo_t* layer = (layerinfo_t*)handle;
+
+  // make sure that the render thread is not using the data when
+  // we change it
+  layer->lock->lock();
+  layer->newData = data;
+  layer->activeData = NULL;
+  layer->lock->unlock();
+
+  // notify the renderer that there is new data for our layer
+  t->signal_set(layer->signalId);
+}
+
+void Renderer::render()
+{
+  int mask = 1;//(1<<MaxNumLayers) - 1;
+  while(true)
+  {
+    osEvent ev = Thread::signal_wait(mask);
+    if (ev.status == osEventSignal) {
+      setupMutex.lock();
+      for (int i = 0; i < numRegisteredLayers; i++) {
+        if (order[i]->activeData != order[i]->newData) {
+        //if (order[i]->signalId & ev.value.signals) {
+
+          int n = (activeBackBuffer + 1) & 1;
+
+//          if (i == 0) {
+//            // TEMPORARY
+//            board->setFrameBuffer((uint32_t)ImageBackBuffer[n]);
+//            if (order[i]->activeData != NULL && order[i]->newData != NULL) {
+//              for (int y = 0; y < order[i]->clippedHeight; y++) {
+//                for (int x = 0; x < order[i]->width; x++) {
+//                  if (order[i]->activeData[y*order[i]->width+x] != order[i]->newData[y*order[i]->width+x]) {
+//                    printf("Difference in x,y {%d,%d} active is 0x%04x, new is 0x%04x\n", x, y,
+//                           order[i]->activeData[y*order[i]->width+x], order[i]->newData[y*order[i]->width+x]);
+//                  }
+//                }
+//              }
+//            }
+//          }
+
+          // Layer i have new data
+          // redraw this layer and all on top of it
+          for (int j = i; j < numRegisteredLayers; j++) {
+            layerinfo_t* layer = order[j];
+            layer->lock->lock();
+            layer->activeData = layer->newData;
+            memcpy(layer->tmp, layer->newData, layer->width*layer->clippedHeight*2);
+            layer->lock->unlock();
+            if (layer->activeData != NULL) {
+              if (layer->fullscreen) {
+                memcpy(ImageBackBuffer[n], layer->tmp, screenBytes);
+              } else {
+                for (int y = 0; y < layer->clippedHeight; y++) {
+                  memcpy(ImageBackBuffer[n] + ((y + layer->y0) * screenWidth) + layer->x0,
+                         layer->tmp + (y * layer->width),
+                         (layer->x1 - layer->x0) * 2);
+                }
+              }
+            }
+          }
+
+          //board->setFrameBuffer((uint32_t)ImageBackBuffer[n]);
+          display->setFramebuffer(ImageBackBuffer[n]);
+          if (i == (numRegisteredLayers - 1)) {
+            // only top layer changed, no need to copy entire back buffer
+          } else {
+            // more layers exist on top so the back buffer must be updated
+            memcpy(ImageBackBuffer[activeBackBuffer], ImageBackBuffer[n], screenBytes);
+          }
+          activeBackBuffer = n;
+          break;
+        }
+      }
+      setupMutex.unlock();
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SlideShow/Renderer.h	Sun Dec 21 13:53:07 2014 +0100
@@ -0,0 +1,104 @@
+#ifndef RENDERER_H
+#define RENDERER_H
+
+#include "rtos.h"
+//#include "LcdController.h"
+//#include "EaLcdBoard.h"
+#include "Display.h"
+
+class Renderer {
+public:
+
+    Renderer(/*LcdController::Config* screen, EaLcdBoard* lcdBoard*/);
+    ~Renderer();
+
+    /** Specifies a part of a layer
+     *
+     *  Returns a handle to pass when updating the framebuffer.
+     *
+     *  @param layer  0 is the bottom of the stack, higher number is on top
+     *  @param xoff   top left corner of the drawing rectangle
+     *  @param yoff   top left corner of the drawing rectangle
+     *  @param width  width of the drawing rectangle
+     *  @param height height of the drawing rectangle
+     *
+     *  @returns
+     *       handle to pass to setFrameBuffer function
+     *       0 on failure
+     */
+    uint32_t registerUser(int layer, int xoff, int yoff, int width, int height);
+    uint32_t registerFullscreenUser(int layer);
+
+    /** Removes the item from the renderer
+     *
+     *  @param handle the handle returned in the registerUser() call
+     */
+    void unregisterUser(uint32_t handle);
+
+    /** Informs the renderer that there is new data to use
+     *
+     * Blocks until the data has been register. After that point the
+     * data must not be modified until another call to setFramebuffer.
+     *
+     *  @param handle the handle returned in the registerUser() call
+     *  @param data   the image data
+     */
+    void setFramebuffer(uint32_t handle, const uint16_t* data);
+
+    void setRenderThread(Thread* renderThread) { t = renderThread; }
+
+    /** Run the renderer
+     *
+     * Should be called from a high priority thread.
+     */
+    void render();
+
+private:
+
+    enum Constants {
+      MaxNumLayers = 10,
+    };
+
+    typedef uint16_t* image_t;
+
+    typedef struct {
+      bool used;
+      int x0;
+      int y0;
+      int x1;
+      int y1;
+      int width;
+      int clippedHeight;
+      int zorder;
+      int signalId;
+      bool fullscreen;
+      const uint16_t* newData;
+      const uint16_t* activeData;
+      uint16_t* tmp;
+      Mutex* lock;
+    } layerinfo_t;
+
+    layerinfo_t layers[MaxNumLayers];
+    layerinfo_t* order[MaxNumLayers];
+
+    int numRegisteredLayers;
+
+    Thread* t;
+    Mutex setupMutex;
+
+    int screenWidth;
+    int screenHeight;
+    int screenPixels;
+    int screenBytes;
+    int activeBackBuffer;
+
+    image_t ImageBackBuffer[2];
+
+    //EaLcdBoard* board;
+    Display* display;
+};
+
+#endif
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SlideShow/SlideShow.cpp	Sun Dec 21 13:53:07 2014 +0100
@@ -0,0 +1,914 @@
+#include "SlideShow.h"
+#include "Image.h"
+#include "mbed_debug.h"
+#include "DMBoard.h"
+
+/******************************************************************************
+ * Defines and typedefs
+ *****************************************************************************/
+
+#define SLIDESHOW_DBG             0
+
+#define NO_SLOT   -12   /* Some constant to indicate that no slot is in use */
+
+/* Helper macros for the Fade transition */
+#define FADE_XRED(__in)   (((__in)>>11)&0x1f)
+#define FADE_XGREEN(__in) (((__in)>>6)&0x3f)
+#define FADE_XBLUE(__in)  ((__in)&0x1f)
+#define FADE_COMBINE(__old, __new, __mul) \
+        ( ((((FADE_XRED(__old)*(8-(__mul)))+(FADE_XRED(__new)*(__mul)))>>3)<<11) \
+        | ((((FADE_XGREEN(__old)*(8-(__mul)))+(FADE_XGREEN(__new)*(__mul)))>>3)<<5) \
+        | (((FADE_XBLUE(__old)*(8-(__mul)))+(FADE_XBLUE(__new)*(__mul)))>>3) )
+
+
+/******************************************************************************
+ * Global variables
+ *****************************************************************************/
+
+extern volatile uint32_t msTicks;
+
+/******************************************************************************
+ * Private Functions
+ *****************************************************************************/
+
+void SlideShow::Command::print()
+{
+    switch(type)
+    {
+        case Clear:
+            printf("CMD: Clear screen\n");
+            break;
+        case Goto:
+            printf("CMD: Goto command %d\n", information);
+            break;
+        case LoadImage:
+            printf("CMD: Load file %s into [%d]\n", fname, information);
+            break;
+        case Show:
+            printf("CMD: Show image [%d] with %s transition\n", information, transition->typeString());
+            break;
+        case Wait:
+            printf("CMD: Wait %d ms\n", information);
+            break;
+        case Callout:
+            printf("CMD: Callout %d\n", information);
+            break;
+        default:
+            printf("Unknown command\n");
+    }
+}
+
+SlideShow::SlideShowError SlideShow::Command::handle(SlideShow* ss, int* seqIdx, int* lastTime)
+{
+    SlideShowError result = Ok;
+
+    //printf("[%03d] ", *seqIdx); print();
+    switch (type)
+    {
+        case Clear:
+            // Use the 3rd back buffer as a fake image for the transition
+            Image::ImageData_t d;
+            d.height = ss->screenHeight;
+            d.width = ss->screenWidth;
+            d.pixels = &ss->ImageBackBuffer[ss->screenPixels*2];
+            d.pointerToFree = NULL;
+            memset(d.pixels, information, ss->screenBytes);
+            if (ss->CurrentSlot == NO_SLOT) {
+                result = transition->execute(ss, NULL, &d);
+            } else {
+                result = transition->execute(ss, &(ss->PreparedImages[ss->CurrentSlot]), &d);
+            }
+            *lastTime = msTicks;
+            *seqIdx+=1;
+            break;
+
+        case Goto:
+            *seqIdx = information;
+            break;
+
+        case LoadImage:
+            if ((result = ss->loadImage(fname, information)) != Ok)
+            {
+                printf("Failed to load image. Aborting...\n");
+                break;
+            }
+            *seqIdx+=1;
+            break;
+
+        case Show:
+            if (ss->CurrentSlot == NO_SLOT) {
+                result = transition->execute(ss, NULL, &(ss->PreparedImages[information]));
+            } else {
+                result = transition->execute(ss, &(ss->PreparedImages[ss->CurrentSlot]), &(ss->PreparedImages[information]));
+            }
+            if (result != Ok) {
+                printf("Failed to show image. Aborting...\n");
+                break;
+            }
+            ss->CurrentSlot = information;
+            *lastTime = msTicks;
+            *seqIdx+=1;
+            break;
+
+        case Wait:
+            ss->delay(*lastTime, information);
+            *lastTime = msTicks;
+            *seqIdx+=1;
+            break;
+
+        case Callout:
+            if (ss->callout != NULL) {
+                result = ss->callout(ss->calloutId, ss, information);
+            } else {
+                // Silently accept that no callout listener is registered
+            }
+            *seqIdx+=1;
+            break;
+
+        default:
+            printf("Found unknown command at index %d\n", *seqIdx);
+            result = InvalidScript;
+    }
+    return result;
+}
+
+SlideShow::SlideShowError SlideShow::Transition::execute(SlideShow* ss, Image::ImageData_t* CurrentImage, Image::ImageData_t* NewImage)
+{
+  SlideShowError result = Ok;
+
+  do {
+
+    // TODO: This would be a good place to handle rendering of differently sized images,
+    //       could unregister+register if NewImage is different from CurrentImage
+
+    // Register with the Renderer if needed.
+    if (ss->rendHnd == 0) {
+      if (ss->rend == NULL) {
+        printf("No registered renderer\n");
+        result = RuntimeError;
+        break;
+      }
+
+      // time to register with the renderer
+      ss->rendHnd = ss->rend->registerUser(ss->layer, ss->drawXoff, ss->drawYoff,
+                                           NewImage->width, NewImage->height);
+      if (ss->rendHnd == 0) {
+        printf("Failed to register with renderer\n");
+        result = RuntimeError;
+        break;
+      }
+    }
+
+    switch (t) {
+      case None:
+      {
+        ss->rend->setFramebuffer(ss->rendHnd, NewImage->pixels);
+      }
+      break;
+
+      case LeftRight:  // TODO: Note that this transition is only implemented for fullscreen mode
+      {
+        // Create a buffer with the old image
+        if (CurrentImage == NULL) {
+          memset(ss->ImageBackBuffer, 0, ss->screenBytes);
+        } else {
+          memcpy(ss->ImageBackBuffer, CurrentImage->pixels, ss->screenBytes);
+        }
+        int end = ss->screenWidth - LeftRight_PixelsToSkip;
+        for (int x = 0; x < end; x += LeftRight_PixelsToSkip)
+        {
+          int off = 0;
+          for (int y = 0; y < ss->screenHeight; y++)
+          {
+            memcpy(ss->ImageBackBuffer + (off+x), NewImage->pixels + (off+x),
+                   LeftRight_PixelsToSkip*sizeof(uint16_t));
+            off += ss->screenWidth;
+          }
+
+          // Show the updated image
+          ss->rend->setFramebuffer(ss->rendHnd, ss->ImageBackBuffer);
+
+          // Sleep and do over again
+          wait_ms(LeftRight_DelayMs);
+        }
+
+        // Show final image
+        ss->rend->setFramebuffer(ss->rendHnd, NewImage->pixels);
+      }
+      break;
+
+      case DownUp:  // TODO: Note that this transition is only implemented for fullscreen mode
+      {
+        // Create a buffer with the two images after each other, NewImage below
+        if (CurrentImage == NULL) {
+          memset(ss->ImageBackBuffer, 0, ss->screenBytes);
+        } else {
+          memcpy(ss->ImageBackBuffer, CurrentImage->pixels, ss->screenBytes);
+        }
+        memcpy(ss->ImageBackBuffer+ss->screenPixels, NewImage->pixels, ss->screenBytes);
+
+        // We will be using a back buffer
+        for (int i = DownUp_LineSkip/2; i < (ss->screenHeight-1); i+=DownUp_LineSkip)
+        {
+          // Show image by advancing what is shown one line at a time
+          ss->rend->setFramebuffer(ss->rendHnd, ss->ImageBackBuffer + i*ss->screenWidth);
+
+          // Sleep and do over again
+          wait_ms(DownUp_DelayMs);
+        }
+
+        // show final image
+        ss->rend->setFramebuffer(ss->rendHnd, NewImage->pixels);
+      }
+      break;
+
+      case TopDown:  // TODO: Note that this transition is only implemented for fullscreen mode
+      {
+        // Create a buffer with the two images after each other, NewImage above
+        if (CurrentImage == NULL) {
+          memset(ss->ImageBackBuffer+ss->screenPixels, 0, ss->screenBytes);
+        } else {
+          memcpy(ss->ImageBackBuffer+ss->screenPixels, CurrentImage->pixels, ss->screenBytes);
+        }
+        memcpy(ss->ImageBackBuffer, NewImage->pixels, ss->screenBytes);
+
+        // We will be using a back buffer
+        for (int i = ss->screenHeight - TopDown_LineSkip/2; i > 0; i-=TopDown_LineSkip)
+        {
+          // Show image by advancing what is shown one line at a time
+          ss->rend->setFramebuffer(ss->rendHnd, ss->ImageBackBuffer + i*ss->screenWidth);
+
+          // Sleep and do over again
+          wait_ms(TopDown_DelayMs);
+        }
+
+        // show final image
+        ss->rend->setFramebuffer(ss->rendHnd, NewImage->pixels);
+      }
+      break;
+
+      case Blinds:
+      {
+        int i;
+        int blockNumPixels = Blinds_LinesPerBlock * ss->screenWidth;
+        int blockNumBytes = blockNumPixels * sizeof(uint16_t);
+        image_t beamBlock = ss->ImageBackBuffer + ss->screenPixels;
+        image_t bkgBlock = beamBlock + blockNumPixels;
+
+        // Create a buffer with the old image
+        if (CurrentImage == NULL) {
+          memset(ss->ImageBackBuffer, 0, ss->screenBytes);
+        } else {
+          memcpy(ss->ImageBackBuffer, CurrentImage->pixels, ss->screenBytes);
+        }
+
+        // Create the two coloured blocks
+        memset(beamBlock, Blinds_BeamColor, blockNumBytes);
+        memset(bkgBlock, Blinds_BackColor, blockNumBytes);
+
+        for (i = 0; i < ss->screenPixels; i += blockNumPixels)
+        {
+          // Draw the moving beam, erasing the old image
+          memcpy(ss->ImageBackBuffer+i, beamBlock, blockNumBytes);
+
+          // Fill upp behind the beam with background color
+          if (i > 0) {
+            memcpy(ss->ImageBackBuffer+i-blockNumPixels, bkgBlock, blockNumBytes);
+          }
+
+          // Show the updated image
+          ss->rend->setFramebuffer(ss->rendHnd, ss->ImageBackBuffer);
+
+          // Sleep and do over again
+          wait_ms(Blinds_DelayMs);
+        }
+        memcpy(ss->ImageBackBuffer+i-blockNumPixels, bkgBlock, blockNumBytes);
+        ss->rend->setFramebuffer(ss->rendHnd, ss->ImageBackBuffer);
+        wait_ms(Blinds_DelayMs);
+
+        for (i = 0; i < ss->screenPixels; i += blockNumPixels)
+        {
+          // Draw the moving beam, erasing the old image
+          memcpy(ss->ImageBackBuffer+i, beamBlock, blockNumBytes);
+
+          // Fill upp behind the beam with the new image
+          if (i > 0) {
+            memcpy(ss->ImageBackBuffer+i-blockNumPixels, NewImage->pixels+i-blockNumPixels, blockNumBytes);
+          }
+
+          // Show image by advancing what is shown one line at a time
+          ss->rend->setFramebuffer(ss->rendHnd, ss->ImageBackBuffer);
+
+          // Sleep and do over again
+          wait_ms(Blinds_DelayMs);
+        }
+
+        // show final image
+        ss->rend->setFramebuffer(ss->rendHnd, NewImage->pixels);
+      }
+      break;
+
+      case Fade:
+      {
+        // Create a buffer with the old image
+        if (CurrentImage == NULL) {
+          memset(ss->ImageBackBuffer, 0, ss->screenBytes * 2); // use an extra backbuffer
+        } else {
+          memcpy(ss->ImageBackBuffer, CurrentImage->pixels, ss->screenBytes);
+        }
+
+        int firstY = 0;
+        int lastY = NewImage->height;
+        for (int y = 0, off=0; y < NewImage->height; y++) {
+          for (int x = 0; x < NewImage->width; x++) {
+            off++;
+            if (NewImage->pixels[off] != ss->ImageBackBuffer[off]) {
+              firstY = y;
+              y = NewImage->height;
+              break;
+            }
+          }
+        }
+        for (int y = NewImage->height-1, off=NewImage->height*NewImage->width-1; y > firstY; y--) {
+          for (int x = 0; x < NewImage->width; x++) {
+            off--;
+            if (NewImage->pixels[off] != ss->ImageBackBuffer[off]) {
+              lastY = y;
+              y = -1;
+              break;
+            }
+          }
+        }
+
+        // Gradually fade between the old and new images
+        for (int pass = 1; pass < 8; pass++)
+        {
+          uint16_t* oldImg = CurrentImage==NULL ? &ss->ImageBackBuffer[ss->screenPixels] : &CurrentImage->pixels[firstY*NewImage->width];
+          uint16_t* newImg = &NewImage->pixels[firstY*NewImage->width];
+          uint16_t* dstImg = &ss->ImageBackBuffer[firstY*NewImage->width];
+          for (int y = firstY; y <= lastY; y++)
+          {
+            for (int x = 0; x < NewImage->width; x++)
+            {
+              if (*oldImg != *newImg) {
+                *dstImg = FADE_COMBINE(*oldImg, *newImg, pass);
+              }
+              oldImg++;
+              newImg++;
+              dstImg++;
+            }
+          }
+          // Show the updated image
+          ss->rend->setFramebuffer(ss->rendHnd, ss->ImageBackBuffer);
+
+          // Sleep and do over again
+          wait_ms(Fade_DelayMs);
+        }
+
+        // show final image
+        ss->rend->setFramebuffer(ss->rendHnd, NewImage->pixels);
+      }
+      break;
+
+      case Unknown:
+      default:
+        result = RuntimeError;
+    }
+  } while(0);
+
+  return result;
+}
+
+SlideShow::SlideShowError SlideShow::loadFile(const char* path, uint8_t** pData, uint32_t* pSize)
+{
+    FILE* f = NULL;
+    uint32_t pos, size, num;
+    SlideShowError result = Ok;
+
+    *pData = NULL;
+    *pSize = 0;
+
+    if (fileMutex != NULL) {
+        fileMutex->lock();
+    }
+    do
+    {
+        f = fopen(path, "r");
+        if (f == NULL) {
+            printf("Failed to open file %s for reading\n", path);
+            result = FileError;
+            break;
+        }
+
+        // Determine file size
+        pos = ftell(f);
+        fseek(f, 0, SEEK_END);
+        size = ftell(f);
+        fseek(f, pos, SEEK_SET);
+
+        // Allocate memory to read into
+        *pData = (unsigned char*)malloc(size);
+        if (*pData == NULL) {
+            printf("Failed to allocate %u bytes to load %s into\n", size, path);
+            result = OutOfMemory;
+            break;
+        }
+
+        // Read entire file
+        *pSize = size;
+        pos = 0;
+        do {
+            num = fread(*pData + pos, 1, size, f);
+            if (num > 0) {
+                size -= num;
+                pos += num;
+            }
+        } while ((num > 0) && (size > 0));
+
+        if (size != 0) {
+            printf("Failed to read entire %s, got %u of %ul\n", path, pos, *pSize);
+            result = FileError;
+            break;
+        }
+
+        // All OK
+
+    } while(0);
+
+    if (f != NULL) {
+        fclose(f);
+    }
+    if (result != Ok) {
+        if (*pData != NULL) {
+            free(*pData);
+            *pData = NULL;
+            *pSize = 0;
+        }
+    }
+
+    if (fileMutex != NULL) {
+        fileMutex->unlock();
+    }
+
+    return result;
+}
+
+
+// pBuf in, pOffset in/out, pLine out
+// returns 0 as long as a token is found
+int SlideShow::getNextLine(char* pBuf, int* pOffset, char** ppLine)
+{
+    int pos = *pOffset;
+    int result = -1;
+
+    // trim whitespace from start of line
+    while ((pBuf[pos] == ' ') || (pBuf[pos] == '\t'))
+    {
+        pos++;
+    }
+    *ppLine = &(pBuf[pos]);
+
+    while (pBuf[pos] != '\0')
+    {
+        if ((pBuf[pos] == '\r') || (pBuf[pos] == '\n'))
+        {
+            // found the next end of line
+            pBuf[pos++] = '\0';
+            result = 0;
+
+            // move past all end-of-line characters
+            while ((pBuf[pos] == '\r') || (pBuf[pos] == '\n'))
+            {
+                pos++;
+            }
+            break;
+        }
+        pos++;
+    }
+
+    *pOffset = pos;
+    return result;
+}
+
+// pLine in, ppPart1 out, ppPart2 out, ppPart3 out
+// returns number of found parts
+int SlideShow::splitLine(char* pLine, char** ppPart1, char** ppPart2, char** ppPart3)
+{
+    int pos = 0;
+    int found = 0;
+
+    *ppPart1 = NULL;
+    *ppPart2 = NULL;
+    *ppPart3 = NULL;
+
+    if (*pLine != '\0')
+    {
+        *ppPart1 = &(pLine[0]);
+        found++;
+
+        while (pLine[pos] != '\0')
+        {
+            if (pLine[pos] == ' ')
+            {
+                // found the next token separator
+                pLine[pos++] = '\0';
+                found++;
+
+                // move past all token separator characters
+                while (pLine[pos] == ' ')
+                {
+                    pos++;
+                }
+
+                // start looking for end of next token
+                if (found == 2)
+                {
+                    *ppPart2 = &(pLine[pos]);
+                }
+                else if (found == 3)
+                {
+                    *ppPart3 = &(pLine[pos]);
+                }
+            }
+            pos++;
+        }
+    }
+
+    return found;
+}
+
+// returns index of pLabel or -1 if it doesn't exist
+int SlideShow::findLabel(LabelInfo* pLabels, int numLabels, const char* pLabel)
+{
+    int i;
+    for (i = 0; i < numLabels; i++)
+    {
+        if (strcmp(pLabels[i].pLabel, pLabel) == 0)
+        {
+            return pLabels[i].index;
+        }
+    }
+    return -1;
+}
+
+void SlideShow::freeSequence(void)
+{
+    if (allocatedSequenceItems > 0) {
+        for (int i = 0; i < usedSequenceItems; i++) {
+            delete Sequence[i];
+        }
+        free(Sequence);
+        Sequence = NULL;
+        allocatedSequenceItems = 0;
+        usedSequenceItems = 0;
+    }
+}
+
+SlideShow::SlideShowError SlideShow::expandSequence()
+{
+    int newSize = allocatedSequenceItems + 20;
+    Command** newPtr = (Command**)realloc(Sequence, newSize * sizeof(Command*));
+    if (newPtr != NULL) {
+        Sequence = newPtr;
+        allocatedSequenceItems = newSize;
+        return Ok;
+    } else {
+        return OutOfMemory;
+    }
+}
+
+SlideShow::SlideShowError SlideShow::parseScript(char* pBuf)
+{
+    char* pLine = NULL;
+    int offset = 0;
+    LabelInfo Labels[10] = {0};
+    int numLabels = 0;
+    LabelInfo UnresolvedGotos[10] = {0};
+    int numUnresolvedGotos = 0;
+    int i;
+    SlideShowError result;
+
+    // cleanup old sequences
+    freeSequence();
+
+    // prepare the new one
+    result = expandSequence();
+    if (result != Ok) {
+        return result;
+    }
+
+    // start parsing the new sequence
+    while (getNextLine(pBuf, &offset, &pLine) == 0)
+    {
+        if (*pLine == '#')
+        {
+            // found a comment line
+        }
+        else
+        {
+            char* pCommand;
+            char* pArg1;
+            char* pArg2;
+            int num = splitLine(pLine, &pCommand, &pArg1, &pArg2);
+
+            if ((num >= 1) && (num <= 3) && (strcmp(pCommand, "clear") == 0))
+            {
+                if (num == 1) {
+                    Sequence[usedSequenceItems] = new Command(Command::Clear, 0xff, new Transition("none"));
+                } else if (num == 2) {
+                    Sequence[usedSequenceItems] = new Command(Command::Clear, strtol(pArg1, NULL, 16), new Transition("none"));
+                } else {
+                    Transition* t = new Transition(pArg2);
+                    if (t->type() == Transition::Unknown) {
+                        printf("Found invalid transition '%s'. Aborting...\n", pArg2);
+                        result = InvalidScript;
+                        break;
+                    }
+                    Sequence[usedSequenceItems] = new Command(Command::Clear, strtol(pArg1, NULL, 16), t);
+                }
+            }
+            else if ((num == 3) && (strcmp(pCommand, "show") == 0))
+            {
+                Transition* t = new Transition(pArg2);
+                if (t->type() == Transition::Unknown) {
+                    printf("Found invalid transition '%s'. Aborting...\n", pArg2);
+                    result = InvalidScript;
+                    break;
+                }
+                Sequence[usedSequenceItems] = new Command(Command::Show, atoi(pArg1), t);
+            }
+            else if ((num == 2) && (strcmp(pCommand, "wait") == 0))
+            {
+                Sequence[usedSequenceItems] = new Command(Command::Wait, atoi(pArg1));
+            }
+            else if ((num == 2) && (strcmp(pCommand, "callout") == 0))
+            {
+                Sequence[usedSequenceItems] = new Command(Command::Callout, atoi(pArg1));
+            }
+            else if ((num == 2) && (strcmp(pCommand, "label") == 0))
+            {
+                int index = findLabel(&(Labels[0]), numLabels, pArg1);
+                if (index == -1)
+                {
+                    // found a new label
+                    Labels[numLabels].index = usedSequenceItems;
+                    Labels[numLabels].pLabel = pArg1;
+                    numLabels++;
+
+                    // A label doesn't occupy a slot in the sequence
+                    usedSequenceItems--;
+                }
+                else
+                {
+                    // label already declared
+                    printf("Found a second declaration of label '%s'. Aborting...\n", pArg1);
+                    result = InvalidScript;
+                    break;
+                }
+            }
+            else if ((num == 2) && (strcmp(pCommand, "goto") == 0))
+            {
+                int index = findLabel(&(Labels[0]), numLabels, pArg1);
+                if (index == -1)
+                {
+                    // couldn't find the label we are looking for so we
+                    // wait for now
+                    UnresolvedGotos[numUnresolvedGotos].index = usedSequenceItems;
+                    UnresolvedGotos[numUnresolvedGotos].pLabel = pArg1;
+                    numUnresolvedGotos++;
+                }
+
+                // Create the command
+                Sequence[usedSequenceItems] = new Command(Command::Goto, index);
+            }
+            else if ((num == 3) && (strcmp(pCommand, "load") == 0))
+            {
+                Sequence[usedSequenceItems] = new Command(Command::LoadImage, atoi(pArg2), NULL, pArg1, pathPrefix);
+                if (Sequence[usedSequenceItems]->info() >= MaxNumPreparedImages) {
+                    printf("Attempting to load into invalid slot %d, have 0..%d. Aborting...\n", Sequence[usedSequenceItems]->info(), MaxNumPreparedImages);
+                    result = InvalidScript;
+                    break;
+                }
+            }
+            else
+            {
+                // unknown command
+                printf("Found unknown command '%s'. Aborting...\n", pCommand);
+                result = InvalidScript;
+                break;
+            }
+
+            // start looking for next part in the sequence
+            usedSequenceItems++;
+
+            // assure we don't pass memory limit
+            if (usedSequenceItems >= allocatedSequenceItems)
+            {
+                result = expandSequence();
+                if (result != Ok) {
+                    printf("Failed to allocate memory to hold sequence. Aborting...\n");
+                    break;
+                }
+            }
+        }
+    }
+
+    // Resolve any unresolved gotos. Happens when the label is on
+    // a line with a higher line number than the goto statement.
+    for (i = 0; i < numUnresolvedGotos && result==Ok; i++)
+    {
+        int index = findLabel(&(Labels[0]), numLabels, UnresolvedGotos[i].pLabel);
+        if (index == -1)
+        {
+            printf("Unable to find label '%s' used in goto statement. Aborting...\n", UnresolvedGotos[i].pLabel);
+            result = InvalidScript;
+        }
+        else
+        {
+            // Update the goto element with the correct index of the label
+            Sequence[UnresolvedGotos[i].index]->updateInfo(index);
+        }
+    }
+
+    if (result==Ok && usedSequenceItems == 0)
+    {
+        printf("Found no sequence. Aborting...\n");
+        result = InvalidScript;
+    }
+    return result;
+}
+
+SlideShow::SlideShowError SlideShow::loadImage(const char* pFileName, int slot)
+{
+    SlideShowError result = Ok;
+
+    if (PreparedImages[slot].pointerToFree != NULL) {
+        free(PreparedImages[slot].pointerToFree);
+        PreparedImages[slot].pointerToFree = NULL;
+    }
+
+    if (Image::decode(pFileName, Image::RES_16BIT, &(PreparedImages[slot]), fileMutex) != 0) {
+        printf("Failed to decode file %s as image\n", pFileName);
+        result = FileError;
+    }
+
+    return result;
+}
+void SlideShow::delay(int lastTime, int millis)
+{
+    int timeToWait = (lastTime + millis) - msTicks;
+    if (timeToWait > 0) {
+        wait_ms(timeToWait);
+    }
+}
+
+SlideShow::SlideShowError SlideShow::runScript()
+{
+    SlideShowError result = Ok;
+    int seqIndex = 0;
+    int lastTime = 0;
+
+    while ((result == Ok) && (seqIndex < this->usedSequenceItems))
+    {
+        result = Sequence[seqIndex]->handle(this, &seqIndex, &lastTime);
+
+        if (abortBeforeNextStep) {
+            break;
+        }
+    }
+
+//    if (*pAbort)
+//    {
+//    return SLIDE_USER_ABORT;
+//    }
+//    else
+//    {
+//    return SLIDE_SCRIPT_END;
+//    }
+    return Ok;
+}
+
+
+/******************************************************************************
+ * Public Functions
+ *****************************************************************************/
+
+SlideShow::SlideShow(Renderer* r, /*LcdController::Config* screen,*/ const char* pathPrefix, uint8_t* bkg, int xoff, int yoff, int layer, Mutex* fileMutex)
+{
+    Display* disp = DMBoard::instance().display();
+    this->screenWidth = disp->width();
+    this->screenHeight = disp->height();
+    this->screenPixels = this->screenWidth * this->screenHeight;
+    this->drawXoff = xoff;
+    this->drawYoff = yoff;
+
+    // Assume screen->bpp == Bpp_16
+    this->screenBytes = 2 * this->screenPixels;
+
+    this->ImageBackBuffer = NULL;
+    this->Sequence = NULL;
+    this->allocatedSequenceItems = 0;
+    this->usedSequenceItems = 0;
+
+    this->pathPrefix = pathPrefix;
+
+    this->CurrentSlot = NO_SLOT;
+
+    memset(PreparedImages, 0, MaxNumPreparedImages * sizeof(Image::ImageData_t));
+
+    this->fileMutex = fileMutex;
+
+    this->rend = r;
+    this->rendHnd = 0;
+    this->layer = layer;
+
+    this->callout = NULL;
+
+    this->abortBeforeNextStep = false;
+}
+
+SlideShow::~SlideShow()
+{
+    if (ImageBackBuffer != NULL) {
+        free(ImageBackBuffer);
+        ImageBackBuffer = NULL;
+    }
+    for (int i = 0; i < MaxNumPreparedImages; i++) {
+        if (PreparedImages[i].pointerToFree != NULL) {
+            free(PreparedImages[i].pointerToFree);
+        }
+    }
+
+    freeSequence();
+
+    if ((rendHnd != 0) && (rend != NULL)) {
+      rend->unregisterUser(rendHnd);
+    }
+
+    //memset(PreparedImages, 0, MaxNumPreparedImages * sizeof(Image::ImageData_t));
+}
+
+SlideShow::SlideShowError SlideShow::prepare(const char* scriptFile)
+{
+    uint8_t* pBuf = NULL;
+    uint32_t size = 0;
+    SlideShowError result = InvalidScript;
+
+    do
+    {
+        if (ImageBackBuffer == NULL) {
+            // Back buffer will be able to hold three images
+            ImageBackBuffer = (image_t)malloc(screenBytes * 3);
+            if (ImageBackBuffer == NULL) {
+                result = OutOfMemory;
+                break;
+            }
+        }
+
+        // Read the contents of the file into a newly allocated buffer
+        result = loadFile(scriptFile, &pBuf, &size);
+        if (result != Ok)
+        {
+            break;
+        }
+
+        //printf("Parsing buffer...\n");
+
+        // Parse buffer to create the script sequence
+        result = parseScript((char*)pBuf);
+
+    } while (0);
+
+    // Release resources
+    if (pBuf != NULL)
+    {
+        free(pBuf);
+        pBuf = NULL;
+    }
+    if (result != Ok) {
+        freeSequence();
+    }
+
+    return result;
+}
+
+SlideShow::SlideShowError SlideShow::run()
+{
+  //printf("Executing script...\n");
+  this->abortBeforeNextStep = false;
+  return runScript();
+}
+
+void SlideShow::setCalloutHandler(calloutFunc func, int calloutId)
+{
+  this->callout = func;
+  this->calloutId = calloutId;
+}
+
+void SlideShow::releaseScreen(void)
+{
+  if ((rendHnd != 0) && (rend != NULL)) {
+    rend->unregisterUser(rendHnd);
+  }
+  rendHnd = 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SlideShow/SlideShow.h	Sun Dec 21 13:53:07 2014 +0100
@@ -0,0 +1,212 @@
+
+#ifndef SLIDESHOW_H
+#define SLIDESHOW_H
+
+#include "mbed.h"
+#include "rtos.h"
+//#include "LcdController.h"
+#include "Image.h"
+#include "Renderer.h"
+
+/**
+ */
+class SlideShow {
+public:
+
+    enum SlideShowError {
+        Ok,
+        InvalidScript,
+        RuntimeError,
+        UserAbort,
+        ScriptEnd,
+        OutOfMemory,
+        FileError,
+    };
+
+    typedef SlideShowError (*calloutFunc)(int calloutId, SlideShow* ss, int identifier);
+
+    SlideShow(Renderer* r, /*LcdController::Config* screen,*/ const char* pathPrefix=NULL, uint8_t* bkg=NULL, int xoff=0, int yoff=0, int layer=0, Mutex* fileMutex=NULL);
+    ~SlideShow();
+    SlideShowError prepare(const char* scriptFile);
+    SlideShowError run();
+    void setCalloutHandler(calloutFunc func, int calloutId);
+    void releaseScreen(void);
+    void stop() { abortBeforeNextStep = true; }
+
+private:
+
+    typedef uint16_t* image_t;
+
+    class Transition {
+    public:
+        typedef enum
+        {
+            None,
+            LeftRight,
+            DownUp,
+            TopDown,
+            Blinds,
+            Fade,
+            Unknown,
+        } Type;
+
+        Transition(const char* typeStr) {
+            if (typeStr == NULL) {
+                t = Unknown;
+            } else if (strcmp("none", typeStr) == 0) {
+                t = None;
+            } else if (strcmp("left-right", typeStr) == 0) {
+                t = LeftRight;
+            } else if (strcmp("down-up", typeStr) == 0) {
+                t = DownUp;
+            } else if (strcmp("top-down", typeStr) == 0) {
+                t = TopDown;
+            } else if (strcmp("blinds", typeStr) == 0) {
+                t = Blinds;
+            } else if (strcmp("fade", typeStr) == 0) {
+                t = Fade;
+            } else {
+                t = Unknown;
+            }
+        };
+        ~Transition();
+        SlideShowError execute(SlideShow* ss, Image::ImageData_t* CurrentImage, Image::ImageData_t* NewImage);
+        Type type() { return this->t; }
+        const char* typeString() {
+            switch(t) {
+                case None: return "No";
+                case LeftRight: return "Left to Right";
+                case TopDown: return "Top to Bottom";
+                case Blinds: return "Blinds";
+                case Unknown:
+                default:
+                    return "Unknown";
+            }
+        };
+
+    private:
+        Type t;
+
+        enum Constants {
+          LeftRight_DelayMs      = 100,
+          LeftRight_PixelsToSkip =  20,
+          DownUp_DelayMs         = 100,
+          DownUp_LineSkip        =  20,
+          TopDown_DelayMs        = 100,
+          TopDown_LineSkip       =  20,
+          Blinds_LinesPerBlock   =   8,
+          Blinds_DelayMs         =  20,
+          Blinds_BeamColor       = 0xffff,
+          Blinds_BackColor       = 0x0000,
+          Fade_DelayMs           =  80,
+        };
+    };
+
+    class Command {
+    public:
+        typedef enum
+        {
+            Clear,
+            Goto,
+            LoadImage,
+            Show,
+            Wait,
+            Callout,
+        } Type;
+
+        Command(Type cmd, int info=0, Transition* t=NULL, const char* filename=NULL, const char* prefix=NULL) {
+            type = cmd;
+            information = info;
+            transition = t;
+            if (filename == NULL) {
+                fname = NULL;
+            } else {
+                fname = (char*)malloc(strlen(filename) + strlen(prefix) + 1); //+1 for null termination
+                if (fname != NULL) {
+                    fname[0] = '\0';
+                    strcat(fname, prefix);
+                    strcat(fname, filename);
+                }
+            }
+        };
+        ~Command() {
+            if (fname != NULL) {
+                free(fname);
+                fname = NULL;
+            }
+        };
+        void updateInfo(int newInfo) { information = newInfo; }
+        int info() { return information; }
+        void print();
+        SlideShowError handle(SlideShow* ss, int* seqIdx, int* lastTime);
+    private:
+        Type        type;
+        Transition* transition;
+        int         information;
+        char*       fname;
+    };
+
+
+    typedef enum
+    {
+        Bmp,
+        Raw
+    } FileFormat;
+
+    typedef struct
+    {
+      const char* pLabel;
+      int index;
+    } LabelInfo;
+
+    enum {
+        MaxNumPreparedImages = 100,
+    } Constants;
+
+    int screenWidth;
+    int screenHeight;
+    int screenPixels;
+    int screenBytes;
+    int drawXoff;
+    int drawYoff;
+
+    image_t ImageBackBuffer;
+    Command** Sequence;
+    int allocatedSequenceItems;
+    int usedSequenceItems;
+
+    const char* pathPrefix;
+
+    int CurrentSlot;
+    Image::ImageData_t PreparedImages[MaxNumPreparedImages];
+
+    Mutex* fileMutex;
+
+    Renderer* rend;
+    uint32_t  rendHnd;
+    int layer;
+
+    calloutFunc callout;
+    int calloutId;
+
+    bool abortBeforeNextStep;
+
+    // Utilities
+    SlideShowError loadFile(const char* path, uint8_t** pData, uint32_t* pSize);
+
+    // Parsing functions
+    int getNextLine(char* pBuf, int* pOffset, char** ppLine);
+    int splitLine(char* pLine, char** ppPart1, char** ppPart2, char** ppPart3);
+    int findLabel(LabelInfo* pLabels, int numLabels, const char* pLabel);
+    void freeSequence(void);
+    SlideShowError expandSequence();
+    SlideShowError parseScript(char* pBuf);
+
+    // Command related functions
+    SlideShowError loadImage(const char* pFileName, int slot);
+    void delay(int lastTime, int millis);
+    SlideShowError runScript();
+
+};
+
+#endif