From ee5ed7909903082aea69138fefbebea58bc9f69e Mon Sep 17 00:00:00 2001
From: Gavin Hurlbut <gjhurlbu@gmail.com>
Date: Tue, 13 Jul 2010 11:31:15 -0700
Subject: [PATCH 1/2] Added Ken Burns Effect for mythgallery

Patches taken from #5176

diff --git a/mythplugins/configure b/mythplugins/configure
index 2d6543d..029eebe 100755
--- a/mythplugins/configure
+++ b/mythplugins/configure
@@ -117,6 +117,7 @@ fftw
 sdl
 exif
 newexif
+fdlib
 "
 
 enable $PLUGIN_LIST $CONFIG_LIST
@@ -160,6 +161,7 @@ MythBrowser related options:
 
 MythGallery related options:
   --enable-mythgallery     build the mythgallery plugin [$gallery]
+  --enable-fdlib           enable face recognition libary [$fdlib]
   --enable-exif            enable reading of EXIF headers [$exif]
   --enable-new-exif        use libexif > version 0.6.9 [$newexif]
 
@@ -247,6 +249,10 @@ for opt do
   ;;
   --disable-new-exif) disable newexif
   ;;
+  --enable-fdlib) enable fdlib
+  ;;
+  --disable-fdlib) disable fdlib
+  ;;  
   --enable-all)
       enable $PLUGIN_LIST $CONFIG_LIST
   ;;
@@ -453,6 +459,15 @@ if ! disabled weather; then
     check_pl_lib "SOAP::Lite"  || disable_weather "SOAP::Lite"
 fi
 
+if test "$fdlib" != "no" ; then
+    fdlib="no"
+    if has_library libfd ; then
+        if has_header fdlib.h ; then
+            fdlib="yes"
+        fi
+    fi
+fi
+
 if test "$music" != "no" ; then
     vorbis="no"
     if has_header ogg/ogg.h && has_header vorbis/vorbisenc.h && has_header vorbis/vorbisfile.h && has_header vorbis/codec.h && has_library libogg && has_library libvorbisfile && has_library libvorbisenc && has_library libvorbis ; then
@@ -702,6 +717,16 @@ if test "$gallery" = "yes" ; then
     if test "$exif" = "no" ; then
         echo "        EXIF           support will not be included in MythGallery"
     fi
+    
+    if test "$fdlib" = "yes" ; then
+        echo "        fdlib          support will be included in MythGallery"
+        echo "#define FDLIB_SUPPORT 1" >> ./mythgallery/mythgallery/config.h
+        echo "LIBS += -lfd" >> ./mythgallery/mythgallery/config.pro
+    fi
+
+    if test "$fdlib" = "no" ; then
+        echo "        fdlib          support will not be included in MythGallery"
+    fi
 fi
 
 ###########################################################
diff --git a/mythplugins/mythgallery/README b/mythplugins/mythgallery/README
index 6b0fc9c..4f30e3a 100644
--- a/mythplugins/mythgallery/README
+++ b/mythplugins/mythgallery/README
@@ -35,6 +35,22 @@ packages from your favorite package repository usually provides this.
 Current, EXIF support only consists of auto-rotating images if the camera
 sets the orientation tag (My Canon S400 does)
 
+You can also enable fdlib support using
+./configure --enable-fdlib
+fdlib - is a facial recognition library that is used as part of the ken
+burns slide show effect. It tries to ensure that slide shows pan/zoom
+to peoples faces rather than their feet or off to the background.
+fdlib can be downloaded from:
+http://www.kyb.mpg.de/bs/people/kienzle/fdlib/fdlib.htm 
+It will require the gcc compatibility packages. Once downloaded
+fdlib.h should be installed in your headers directory (/usr/include)
+libfd.so should be installed in your libs directory (/usr/lib)
+fdlib.dat should be installed in your current working directory when
+mythfrontend is started for me it was (/home/myth). Please note that 
+this is not probably not the same directory where mythfrontend actually 
+resides. If fdlib does not find fdlib.dat, it will fail silently and just
+not find any faces.
+
 2) Next type 'qmake mythgallery.pro' then 'make' in the
 main distribution directory.
 
diff --git a/mythplugins/mythgallery/mythgallery/gallerysettings.cpp b/mythplugins/mythgallery/mythgallery/gallerysettings.cpp
index 1da908c..d8faab7 100644
--- a/mythplugins/mythgallery/mythgallery/gallerysettings.cpp
+++ b/mythplugins/mythgallery/mythgallery/gallerysettings.cpp
@@ -105,6 +105,7 @@ static HostComboBox *SlideshowOpenGLTransition()
     gc->addSelection("flutter (gl)");
     gc->addSelection("cube (gl)");
     gc->addSelection("random (gl)");
+    gc->addSelection("Ken Burns (gl)");
     gc->setHelpText(QObject::tr("This is the type of OpenGL transition used "
                     "between pictures in slideshow mode."));
     return gc;
@@ -113,7 +114,7 @@ static HostComboBox *SlideshowOpenGLTransition()
 static HostSpinBox *SlideshowOpenGLTransitionLength()
 {
     HostSpinBox *gc = new HostSpinBox(
-        "SlideshowOpenGLTransitionLength", 500, 10000, 500);
+        "SlideshowOpenGLTransitionLength", 500, 30000, 500);
     gc->setLabel(QObject::tr("Duration of OpenGL Transition (milliseconds)"));
     gc->setValue(2000);
     return gc;
@@ -159,7 +160,7 @@ static HostComboBox *SlideshowBackground()
 
 static HostSpinBox *SlideshowDelay()
 {
-    HostSpinBox *gc = new HostSpinBox("SlideshowDelay", 1, 600, 1);
+    HostSpinBox *gc = new HostSpinBox("SlideshowDelay", 0, 600, 1);
     gc->setLabel(QObject::tr("Slideshow Delay"));
     gc->setValue(5);
     gc->setHelpText(QObject::tr("This is the number of seconds to display each "
diff --git a/mythplugins/mythgallery/mythgallery/glsingleview.cpp b/mythplugins/mythgallery/mythgallery/glsingleview.cpp
index f237d6f..ff4c596 100644
--- a/mythplugins/mythgallery/mythgallery/glsingleview.cpp
+++ b/mythplugins/mythgallery/mythgallery/glsingleview.cpp
@@ -31,6 +31,7 @@ using namespace std;
 #include <QImage>
 #include <QDir>
 #include <QPainter>
+#include <qbuffer.h>
 
 // MythTV plugin headers
 #include <mythcontext.h>
@@ -38,9 +39,14 @@ using namespace std;
 #include <mythuihelper.h>
 
 // MythGallery headers
+#include "config.h"
 #include "glsingleview.h"
 #include "galleryutil.h"
 
+#ifdef FDLIB_SUPPORT
+#include <fdlib.h>
+#endif
+
 #define LOC QString("GLView: ")
 #define LOC_ERR QString("GLView, Error: ")
 
@@ -95,7 +101,10 @@ GLSingleView::GLSingleView(ThumbList itemList, int *pos, int slideShow,
       // Unshared effect state variables
       m_effect_cube_xrot(0.0f),
       m_effect_cube_yrot(0.0f),
-      m_effect_cube_zrot(0.0f)
+      m_effect_cube_zrot(0.0f),
+      m_effect_kenBurns_image_ready(true),
+      m_effect_kenBurns_initialized(false),
+      m_effect_kenBurns_new_image_started(true)
 {
     m_scaleMax = (ScaleMax) gCoreContext->GetNumSetting("GalleryScaleMax", 0);
 
@@ -707,6 +716,7 @@ void GLSingleView::RegisterEffects(void)
     m_effect_map.insert("slide (gl)",      "EffectSlide");
     m_effect_map.insert("flutter (gl)",    "EffectFlutter");
     m_effect_map.insert("cube (gl)",       "EffectCube");
+    m_effect_map.insert("Ken Burns (gl)",  "EffectKenBurns");
 }
 
 void GLSingleView::RunEffect(const QString &effect)
@@ -729,6 +739,8 @@ void GLSingleView::RunEffect(const QString &effect)
         EffectFlutter();
     else if (effect == "EffectCube")
         EffectCube();
+    else if (effect == "EffectKenBurns")
+        EffectKenBurns();
     else //if (effect == "EffectNone")
         EffectNone();
 }
@@ -1212,6 +1224,190 @@ void GLSingleView::EffectCube(void)
     m_effect_current_frame++;
 }
 
+void GLSingleView::EffectKenBurns(void)
+{
+
+    float single_image_pct = 0.75;
+    float trans_pct = 1.0 - single_image_pct;
+    float scale_max, x_loc, y_loc;
+    float scale_factor = 0;
+
+    //initialize effect   
+    if (!m_effect_kenBurns_initialized)
+    {
+                
+        m_effect_kenBurns_initialized = !m_effect_kenBurns_initialized;
+        m_effect_kenBurns_item = NULL;
+        // Need to load images in the background to keep effect smooth
+        m_effect_kenBurns_imageLoadThread = new KenBurnsImageLoader(this, m_itemList, m_texSize, m_screenSize);
+        //Since total image time is longer/different than effect time, create image timers
+        m_effect_kenBurns_image_time[m_texCur ? 0 : 1].restart();
+        // Pan image to a random location
+        FindRandXY(m_effect_kenBurns_location_x[0], m_effect_kenBurns_location_y[0]);
+        // Since first two images are preloaded, hardcode  them to zoom in
+        m_effect_kenBurns_projection[0] = 1;
+        m_effect_kenBurns_projection[1] = 1;
+        m_effect_kenBurns_image_timeout = m_effect_transition_timeout + 
+                (m_effect_transition_timeout * trans_pct);
+        m_effect_kenBurns_face_x = 0.0;
+        m_effect_kenBurns_face_y = 0.0;
+    }
+
+    if (m_effect_frame_time.elapsed() >= m_effect_transition_timeout)
+    {
+        // Effect timed out, move new image to old image but don't load new image yet...
+        m_tex1First = !m_tex1First;
+        m_texCur      = (m_texCur) ? 0 : 1;
+        m_effect_current_frame  = 0;
+        m_effect_frame_time.restart(); 
+
+        m_effect_kenBurns_image_ready = false;
+        m_effect_kenBurns_face_x = 0.0;
+        m_effect_kenBurns_face_y = 0.0;
+
+        // Find next image to be loaded
+        int oldpos = m_pos;
+
+        while (true)
+        {
+            m_pos = m_slideshow_sequence->next();
+            m_effect_kenBurns_item = m_itemList.at(m_pos);
+            if (m_effect_kenBurns_item)
+            {
+                // Skip movies
+                if (QFile::exists(m_effect_kenBurns_item->GetPath()) && !GalleryUtil::IsMovie(m_effect_kenBurns_item->GetPath()))
+                {
+                    break;
+                }
+            }
+            if (m_pos == oldpos)
+            {
+                // No valid items!!!
+                close();
+            }
+        }
+        m_effect_kenBurns_imageLoadThread->Initialize(m_pos);
+        m_effect_kenBurns_imageLoadThread->start();
+    }
+
+    float t[2], elapsed[2], s[2], effect_pct;
+    elapsed[m_texCur] = m_effect_kenBurns_image_time[m_texCur].elapsed();
+    elapsed[m_texCur ? 0 : 1] = m_effect_kenBurns_image_time[m_texCur ? 0 : 1].elapsed();
+    //progress linearly
+    t[m_texCur] = elapsed[m_texCur] / m_effect_kenBurns_image_timeout;
+    t[m_texCur ? 0 : 1] = elapsed[m_texCur ? 0 : 1] / m_effect_kenBurns_image_timeout;
+    //progress faster initially then slowing down- this is needed to ensure images zoom faster than they pan and
+    //therefore stay completely on the screen
+    s[m_texCur] = sqrt(elapsed[m_texCur]) / sqrt(m_effect_kenBurns_image_timeout);
+    s[m_texCur ? 0 : 1] = sqrt(elapsed[m_texCur ? 0 : 1]) / sqrt(m_effect_kenBurns_image_timeout); 
+    
+    effect_pct = m_effect_frame_time.elapsed() *  m_effect_transition_timeout_inv;
+
+    // Load new image if its ready
+    if (effect_pct > single_image_pct && m_effect_kenBurns_image_ready) 
+    {
+        if (!m_effect_kenBurns_new_image_started)
+        { 			
+            if (m_effect_kenBurns_item) //Do not create textures for first two images, since they are preloaded
+            {
+                m_texItem[!m_tex1First].SetItem(m_effect_kenBurns_item, m_effect_kenBurns_orig_image_size);
+                m_texItem[!m_tex1First].ScaleTo(m_screenSize, m_scaleMax);
+                m_texItem[!m_tex1First].Init(m_effect_kenBurns_image);
+                UpdateLCD(m_effect_kenBurns_item);
+                
+                // If there is no face in this image
+                if ((m_effect_kenBurns_face_x == 0.0) && (m_effect_kenBurns_face_x == 0.0))
+                {
+                    //choose the location and projection (zoom in or out) randomly
+                    FindRandXY(m_effect_kenBurns_location_x[m_texCur], m_effect_kenBurns_location_y[m_texCur]);
+                    m_effect_kenBurns_projection[m_texCur] = 1 + (int)((2.0f * rand() / (RAND_MAX + 1.0f))); 
+                }
+                //else if the face is  close to center
+                else if ((m_effect_kenBurns_face_x < 0.25) && (m_effect_kenBurns_face_x > -0.25) &&
+                        (m_effect_kenBurns_face_y < 0.25) && (m_effect_kenBurns_face_y > -0.25))
+                {                   
+                    //start at random location and zoom out to face in center
+                    FindRandXY(m_effect_kenBurns_location_x[m_texCur], m_effect_kenBurns_location_y[m_texCur]);
+                    m_effect_kenBurns_projection[m_texCur] = 0;
+                }
+                else 
+                {
+                    //start in center and zoom in to face random location
+                    m_effect_kenBurns_location_x[m_texCur] = m_effect_kenBurns_face_x; 
+                    m_effect_kenBurns_location_y[m_texCur] = m_effect_kenBurns_face_y; 
+                    m_effect_kenBurns_projection[m_texCur] = 1;                
+                }
+            } 
+            else  //No item, must be 1 of the first two preloaded items
+            {
+                //start at random location and zoom out to face in center
+                FindRandXY(m_effect_kenBurns_location_x[m_texCur], m_effect_kenBurns_location_y[m_texCur]);
+                m_effect_kenBurns_projection[m_texCur] = 1; 
+            }
+
+            m_effect_kenBurns_image_time[m_texCur].restart();
+            m_effect_kenBurns_new_image_started = true;
+        }
+        if (m_effect_kenBurns_projection[m_texCur] == 1) // Zoom in image
+        {
+            // Start in center and pan out
+            x_loc = m_effect_kenBurns_location_x[m_texCur] * t[m_texCur]; 
+            y_loc = m_effect_kenBurns_location_y[m_texCur] * t[m_texCur]; 		
+            scale_max = FindMaxScale(x_loc,y_loc);
+            scale_factor = 	1.0f + (scale_max * s[m_texCur]); 
+        }
+        else // Zoom out image
+        {
+            // Start at random location and pan to center
+            x_loc = m_effect_kenBurns_location_x[m_texCur] -  m_effect_kenBurns_location_x[m_texCur] * t[m_texCur]; 
+            y_loc = m_effect_kenBurns_location_y[m_texCur] -  m_effect_kenBurns_location_y[m_texCur] * t[m_texCur];
+            scale_max = FindMaxScale(x_loc,y_loc);
+            scale_factor = 	1.0f + scale_max -  (scale_max * t[m_texCur]);
+        } 
+
+        glMatrixMode(GL_MODELVIEW);
+        glLoadIdentity();
+        glTranslatef(x_loc, y_loc, 0.0f);
+
+        m_texItem[m_texCur].MakeQuad((effect_pct-single_image_pct)*4, scale_factor); 
+    }
+   
+    //Load old picture
+    if (m_effect_kenBurns_projection[m_texCur ? 0 : 1] == 1)// Zoom in image
+    {
+        x_loc = m_effect_kenBurns_location_x[m_texCur ? 0 : 1] * t[m_texCur ? 0 : 1]; 
+        y_loc = m_effect_kenBurns_location_y[m_texCur ? 0 : 1] * t[m_texCur ? 0 : 1];
+        scale_max = FindMaxScale(x_loc,y_loc);
+        scale_factor = 	1.0f + (scale_max * s[m_texCur ? 0 : 1]);
+    }
+    else // Zoom out image
+    {
+        x_loc = m_effect_kenBurns_location_x[m_texCur ? 0 : 1] -  
+            m_effect_kenBurns_location_x[m_texCur ? 0 : 1] * t[m_texCur ? 0 : 1]; 
+        y_loc = m_effect_kenBurns_location_y[m_texCur ? 0 : 1] -  
+            m_effect_kenBurns_location_y[m_texCur ? 0 : 1] * t[m_texCur ? 0 : 1];
+        scale_max = FindMaxScale(x_loc,y_loc);
+        scale_factor = 	1.0f + scale_max -  (scale_max * t[m_texCur ? 0 : 1]);
+    } 
+
+    glMatrixMode(GL_MODELVIEW);
+    glLoadIdentity();
+    glTranslatef(x_loc, y_loc, 0.0f);
+
+    if (effect_pct<= single_image_pct) 
+    {
+        m_effect_kenBurns_new_image_started=false;
+        m_texItem[m_texCur ? 0 : 1].MakeQuad(1.0f, scale_factor); //
+    }
+    else // Fade out image
+    {
+        m_texItem[m_texCur ? 0 : 1].MakeQuad(1.0f - ((effect_pct-single_image_pct)*4), scale_factor); 
+
+    }
+    
+    m_effect_current_frame++;
+}
+
 void GLSingleView::SlideTimeout(void)
 {
     bool wasMovie = false, isMovie = false;
@@ -1318,3 +1514,128 @@ void GLSingleView::createTexInfo(void)
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
 }
+
+void GLSingleView::LoadImage(QImage image, QSize origSize, Face *face)
+{
+    m_effect_kenBurns_image = image;
+    m_effect_kenBurns_orig_image_size = origSize;
+    if (face) 
+    {
+        VERBOSE(VB_IMPORTANT, QString("Found face, set location x '%1' y '%2'").arg(face->getX()).arg(face->getY()));
+        m_effect_kenBurns_face_x = face->getX();
+        m_effect_kenBurns_face_y = face->getY();
+    }
+}
+
+float GLSingleView::FindMaxScale(float x_loc, float y_loc)
+{
+    // Zoom big enough to keep the entire image on screen when we pan
+    if (abs(x_loc) > abs(y_loc))
+        return abs(x_loc) * 2;
+    else
+        return abs(y_loc) * 2;
+}
+
+void GLSingleView::FindRandXY(float &x_loc, float &y_loc)
+{
+    x_loc = (0.5 * rand() / (RAND_MAX + 1.0f)) + 0.25;  //Random number between .25 and .75
+    if ((int)(2.0 * rand() / (RAND_MAX + 1.0f)) == 0)
+        x_loc = -1 * x_loc;
+    y_loc = (0.5 * rand() / (RAND_MAX + 1.0f)) + 0.25;  //Random number between .25 and .75
+    if ((int)(2.0 * rand() / (RAND_MAX + 1.0f)) == 0)
+        y_loc = -1 * y_loc;   
+}
+
+KenBurnsImageLoader::KenBurnsImageLoader(GLSingleView *singleView, ThumbList &itemList, QSize texSize, QSize screenSize)
+{
+    m_singleView = singleView;
+    m_itemList = itemList;
+    m_texSize = texSize;
+    m_screenSize = screenSize;
+}
+
+void KenBurnsImageLoader::Initialize(int pos)
+{
+    m_pos = pos;
+}
+
+void KenBurnsImageLoader::run() 
+{
+    ThumbItem *item = m_itemList.at(m_pos);
+    if (!item)
+    {
+        VERBOSE(VB_IMPORTANT, LOC_ERR + "No item at "<<m_pos);
+        return;
+    }
+    QImage image(item->GetPath());
+    if (image.isNull())
+        return;
+    
+#ifdef FDLIB_SUPPORT 
+    
+    int i, n, x[256], y[256], size[256], w, h, threshold;
+    float loc_x, loc_y;
+    unsigned char *bgrdata, *graydata;
+    
+    w = image.width();
+    h = image.height();
+    bgrdata = image.bits(); 
+  
+    graydata = new unsigned char[w*h];
+    for (i=0; i<w*h; i++)
+        graydata[i] = (unsigned char) ((.11*bgrdata[4*i] + .59*bgrdata[4*i+1] + .3*bgrdata[4*i+2]));
+    
+    
+    threshold = 0;
+    fdlib_detectfaces(graydata, w, h, threshold);   
+    delete[] graydata;       
+    n = fdlib_getndetections();
+    if (n==0) 
+    {
+        VERBOSE(VB_IMPORTANT, QString("%1 face found.").arg(n));
+        m_singleView->LoadImage(QGLWidget::convertToGLFormat(image.scaled(m_texSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)), image.size());
+    }
+    else if (n==1)
+    {
+        VERBOSE(VB_IMPORTANT, QString("%1 faces found.").arg(n));
+
+        fdlib_getdetection(0, x, y, size);
+        loc_x = (x[0]-(float)w/2)/((float)w/2)*(-1);
+        loc_y = (y[0]-(float)h/2)/((float)h/2)*(-1);
+        VERBOSE(VB_IMPORTANT, QString("x:%1 y:%2 size:%3").arg(x[0]).arg(y[0]).arg(size[0]));
+        m_singleView->LoadImage(QGLWidget::convertToGLFormat(image.scaled(m_texSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)), image.size(), new Face(loc_x, loc_y));
+    }
+    else 
+    {
+        VERBOSE(VB_IMPORTANT, QString("%1 faces found.").arg(n));
+        QList<Face*> faces;
+        int faceIndex;
+    
+        for (i=0; i<n; i++)
+        {
+            fdlib_getdetection(i, x+i, y+i, size+i);
+            loc_x = (x[i]-(float)w/2)/((float)w/2)*(-1);
+            loc_y = (y[i]-(float)h/2)/((float)h/2)*(-1);
+            VERBOSE(VB_IMPORTANT, QString("x:%1 y:%2 size:%3").arg(x[i]).arg(y[i]).arg(size[i]));
+            faces.append(new Face(loc_x, loc_y));
+        }
+        if (faces.count() > 1)
+            faceIndex = (int) ((faces.count()+1) * rand() / (RAND_MAX + 1.0f)); //
+        else
+            faceIndex = 0;
+            
+        m_singleView->LoadImage(QGLWidget::convertToGLFormat(image.scaled(m_texSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)), image.size(), faces.at(faceIndex));
+    }
+    
+#else  //END FDLIB_SUPPORT
+    m_singleView->LoadImage(QGLWidget::convertToGLFormat(image.scaled(m_texSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)), image.size());   
+#endif
+    m_singleView->Ready();
+
+}
+
+Face::Face(float x, float y)
+{
+    m_x = x;
+    m_y = y;
+}
diff --git a/mythplugins/mythgallery/mythgallery/glsingleview.h b/mythplugins/mythgallery/mythgallery/glsingleview.h
index c8c6fe6..54acdd3 100644
--- a/mythplugins/mythgallery/mythgallery/glsingleview.h
+++ b/mythplugins/mythgallery/mythgallery/glsingleview.h
@@ -23,6 +23,7 @@
 #define GLSINGLEVIEW_H
 #ifdef USING_OPENGL
 
+
 // MythTV plugin headers
 #include <util.h>
 #include <mythdialogs.h>
@@ -37,6 +38,8 @@ class QImage;
 class QTimer;
 
 class GLSingleView;
+class KenBurnsImageLoader;
+class Face;
 
 class GLSDialog : public MythDialog
 {
@@ -62,6 +65,9 @@ class GLSingleView : public QGLWidget, public ImageView
     ~GLSingleView();
 
     void CleanUp(void);
+    void Ready(){m_effect_kenBurns_image_ready = true;}
+    void LoadImage(QImage image, QSize origSize, Face *face = NULL);
+    
 
   protected:
     void initializeGL(void);
@@ -99,7 +105,12 @@ class GLSingleView : public QGLWidget, public ImageView
     void EffectSlide(void);
     void EffectFlutter(void);
     void EffectCube(void);
-
+    void EffectKenBurns(void);
+  
+  private:
+	float FindMaxScale(float x_loc, float y_loc);
+	void FindRandXY(float &x_loc, float &y_loc);
+    
   private slots:
     void SlideTimeout(void);
 
@@ -130,6 +141,48 @@ class GLSingleView : public QGLWidget, public ImageView
     float         m_effect_cube_xrot;
     float         m_effect_cube_yrot;
     float         m_effect_cube_zrot;
+    float         m_effect_kenBurns_location_x[2];
+    float         m_effect_kenBurns_location_y[2];
+    int           m_effect_kenBurns_projection[2];
+    MythTimer 	  m_effect_kenBurns_image_time[2];
+    float         m_effect_kenBurns_image_timeout;
+    KenBurnsImageLoader *m_effect_kenBurns_imageLoadThread;
+    bool          m_effect_kenBurns_image_ready;
+    QImage        m_effect_kenBurns_image;
+    QSize         m_effect_kenBurns_orig_image_size;
+    float         m_effect_kenBurns_face_x;
+    float         m_effect_kenBurns_face_y;
+    ThumbItem     *m_effect_kenBurns_item;
+    bool          m_effect_kenBurns_initialized;
+    bool          m_effect_kenBurns_new_image_started;
+    
+};
+
+class KenBurnsImageLoader : public QThread
+{
+public:
+    KenBurnsImageLoader(GLSingleView *singleView, ThumbList &itemList, QSize m_texSize, QSize m_screenSize);
+    void Initialize(int pos);
+    void run();
+private:
+	GLSingleView *m_singleView;
+    ThumbList     m_itemList;
+    int           m_pos;
+    bool          m_tex1First;
+    QSize         m_screenSize;
+    QSize         m_texSize;
+
+};
+
+class Face
+{
+  public: 
+    Face(float x, float y);
+    float getX() { return m_x; }
+    float getY() { return m_y; }
+  private:
+      float m_x;
+      float m_y;
 };
 
 #endif // USING_OPENGL
diff --git a/mythplugins/mythgallery/mythgallery/imageview.cpp b/mythplugins/mythgallery/mythgallery/imageview.cpp
index fa847ea..b9d17f3 100644
--- a/mythplugins/mythgallery/mythgallery/imageview.cpp
+++ b/mythplugins/mythgallery/mythgallery/imageview.cpp
@@ -139,6 +139,7 @@ QString ImageView::GetRandomEffect(void) const
 {
     QMap<QString,QString> tmpMap = m_effect_map;
     tmpMap.remove("none");
+    tmpMap.remove("Ken Burns (gl)");
     QStringList t = tmpMap.keys();
     int i = (int) ( (float)(t.count()) * rand() / (RAND_MAX + 1.0f) );
     return tmpMap[t[i]];
-- 
1.7.0.4

