Ticket #5176: kenburns3.patch

File kenburns3.patch, 19.3 KB (added by jshoor, 12 years ago)

Updated patch with improved panning/zooming and facial recognition.

  • mythgallery/glsingleview.cpp

     
    3434#include <qdir.h>
    3535#include <qpainter.h>
    3636
     37#include <qbuffer.h>
     38
    3739// MythTV plugin headers
    3840#include <mythtv/mythcontext.h>
    3941#include <mythtv/util.h>
    4042
    4143// MythGallery headers
     44#include "config.h"
    4245#include "glsingleview.h"
    4346#include "galleryutil.h"
    4447
     48#ifdef FDLIB_SUPPORT
     49#include <fdlib.h>
     50#endif
     51
    4552#define LOC QString("GLView: ")
    4653#define LOC_ERR QString("GLView, Error: ")
    4754
     
    93100      // Unshared effect state variables
    94101      m_effect_cube_xrot(0.0f),
    95102      m_effect_cube_yrot(0.0f),
    96       m_effect_cube_zrot(0.0f)
     103      m_effect_cube_zrot(0.0f),
     104      m_effect_kenBurns_image_ready(true),
     105      m_effect_kenBurns_initialized(false),
     106      m_effect_kenBurns_new_image_started(true)
    97107{
    98108    m_scaleMax = (gContext->GetNumSetting("GalleryScaleMax", 0) > 0);
    99109
     
    661671    m_effect_map.insert("slide (gl)",      "EffectSlide");
    662672    m_effect_map.insert("flutter (gl)",    "EffectFlutter");
    663673    m_effect_map.insert("cube (gl)",       "EffectCube");
     674    m_effect_map.insert("Ken Burns (gl)",  "EffectKenBurns");
    664675}
    665676
    666677void GLSingleView::RunEffect(const QString &effect)
     
    683694        EffectFlutter();
    684695    else if (effect == "EffectCube")
    685696        EffectCube();
     697    else if (effect == "EffectKenBurns")
     698        EffectKenBurns();
    686699    else //if (effect == "EffectNone")
    687700        EffectNone();
    688701}
     
    11661179    m_effect_current_frame++;
    11671180}
    11681181
     1182void GLSingleView::EffectKenBurns(void)
     1183{
     1184
     1185    float single_image_pct = 0.75;
     1186    float trans_pct = 1.0 - single_image_pct;
     1187    float scale_max, x_loc, y_loc;
     1188    float scale_factor = 0;
     1189
     1190    //initialize effect   
     1191    if (!m_effect_kenBurns_initialized)
     1192    {
     1193               
     1194        m_effect_kenBurns_initialized = !m_effect_kenBurns_initialized;
     1195        m_effect_kenBurns_item = NULL;
     1196        // Need to load images in the background to keep effect smooth
     1197        m_effect_kenBurns_imageLoadThread = new KenBurnsImageLoader(this, m_itemList, m_texSize, m_screenSize);
     1198        //Since total image time is longer/different than effect time, create image timers
     1199        m_effect_kenBurns_image_time[m_texCur ? 0 : 1].restart();
     1200        // Pan image to a random location
     1201        FindRandXY(m_effect_kenBurns_location_x[0], m_effect_kenBurns_location_y[0]);
     1202        // Since first two images are preloaded, hardcode  them to zoom in
     1203        m_effect_kenBurns_projection[0] = 1;
     1204        m_effect_kenBurns_projection[1] = 1;
     1205        m_effect_kenBurns_image_timeout = m_effect_transition_timeout +
     1206                (m_effect_transition_timeout * trans_pct);
     1207        m_effect_kenBurns_face_x = 0.0;
     1208        m_effect_kenBurns_face_y = 0.0;
     1209    }
     1210
     1211    if (m_effect_frame_time.elapsed() >= m_effect_transition_timeout)
     1212    {
     1213        // Effect timed out, move new image to old image but don't load new image yet...
     1214        m_tex1First = !m_tex1First;
     1215        m_texCur      = (m_texCur) ? 0 : 1;
     1216        m_effect_current_frame  = 0;
     1217        m_effect_frame_time.restart();
     1218
     1219        m_effect_kenBurns_image_ready = false;
     1220        m_effect_kenBurns_face_x = 0.0;
     1221        m_effect_kenBurns_face_y = 0.0;
     1222
     1223        // Find next image to be loaded
     1224        int oldpos = m_pos;
     1225
     1226        while (true)
     1227        {
     1228            m_pos = m_slideshow_sequence->next();
     1229            m_effect_kenBurns_item = m_itemList.at(m_pos);
     1230            if (m_effect_kenBurns_item)
     1231            {
     1232                // Skip movies
     1233                if (QFile::exists(m_effect_kenBurns_item->GetPath()) && !GalleryUtil::isMovie(m_effect_kenBurns_item->GetPath()))
     1234                {
     1235                    break;
     1236                }
     1237            }
     1238            if (m_pos == oldpos)
     1239            {
     1240                // No valid items!!!
     1241                close();
     1242            }
     1243        }
     1244        m_effect_kenBurns_imageLoadThread->Initialize(m_pos);
     1245        m_effect_kenBurns_imageLoadThread->start();
     1246    }
     1247
     1248    float t[2], elapsed[2], s[2], effect_pct;
     1249    elapsed[m_texCur] = m_effect_kenBurns_image_time[m_texCur].elapsed();
     1250    elapsed[m_texCur ? 0 : 1] = m_effect_kenBurns_image_time[m_texCur ? 0 : 1].elapsed();
     1251    //progress linearly
     1252    t[m_texCur] = elapsed[m_texCur] / m_effect_kenBurns_image_timeout;
     1253    t[m_texCur ? 0 : 1] = elapsed[m_texCur ? 0 : 1] / m_effect_kenBurns_image_timeout;
     1254    //progress faster initially then slowing down- this is needed to ensure images zoom faster than they pan and
     1255    //therefore stay completely on the screen
     1256    s[m_texCur] = sqrt(elapsed[m_texCur]) / sqrt(m_effect_kenBurns_image_timeout);
     1257    s[m_texCur ? 0 : 1] = sqrt(elapsed[m_texCur ? 0 : 1]) / sqrt(m_effect_kenBurns_image_timeout);
     1258   
     1259    effect_pct = m_effect_frame_time.elapsed() *  m_effect_transition_timeout_inv;
     1260
     1261    // Load new image if its ready
     1262    if (effect_pct > single_image_pct && m_effect_kenBurns_image_ready)
     1263    {
     1264        if (!m_effect_kenBurns_new_image_started)
     1265        {                       
     1266            if (m_effect_kenBurns_item) //Do not create textures for first two images, since they are preloaded
     1267            {
     1268                m_texItem[!m_tex1First].SetItem(m_effect_kenBurns_item, m_effect_kenBurns_orig_image_size);
     1269                m_texItem[!m_tex1First].ScaleTo(m_screenSize, m_scaleMax);
     1270                m_texItem[!m_tex1First].Init(m_effect_kenBurns_image);
     1271                UpdateLCD(m_effect_kenBurns_item);
     1272               
     1273                // If there is no face in this image
     1274                if ((m_effect_kenBurns_face_x == 0.0) && (m_effect_kenBurns_face_x == 0.0))
     1275                {
     1276                    //choose the location and projection (zoom in or out) randomly
     1277                    FindRandXY(m_effect_kenBurns_location_x[m_texCur], m_effect_kenBurns_location_y[m_texCur]);
     1278                    m_effect_kenBurns_projection[m_texCur] = 1 + (int)((2.0f * rand() / (RAND_MAX + 1.0f)));
     1279                }
     1280                //else if the face is  close to center
     1281                else if ((m_effect_kenBurns_face_x < 0.25) && (m_effect_kenBurns_face_x > -0.25) &&
     1282                        (m_effect_kenBurns_face_y < 0.25) && (m_effect_kenBurns_face_y > -0.25))
     1283                {                   
     1284                    //start at random location and zoom out to face in center
     1285                    FindRandXY(m_effect_kenBurns_location_x[m_texCur], m_effect_kenBurns_location_y[m_texCur]);
     1286                    m_effect_kenBurns_projection[m_texCur] = 0;
     1287                }
     1288                else
     1289                {
     1290                    //start in center and zoom in to face random location
     1291                    m_effect_kenBurns_location_x[m_texCur] = m_effect_kenBurns_face_x;
     1292                    m_effect_kenBurns_location_y[m_texCur] = m_effect_kenBurns_face_y;
     1293                    m_effect_kenBurns_projection[m_texCur] = 1;               
     1294                }
     1295            }
     1296            else  //No item, must be 1 of the first two preloaded items
     1297            {
     1298                //start at random location and zoom out to face in center
     1299                FindRandXY(m_effect_kenBurns_location_x[m_texCur], m_effect_kenBurns_location_y[m_texCur]);
     1300                m_effect_kenBurns_projection[m_texCur] = 1;
     1301            }
     1302
     1303            m_effect_kenBurns_image_time[m_texCur].restart();
     1304            m_effect_kenBurns_new_image_started = true;
     1305        }
     1306        if (m_effect_kenBurns_projection[m_texCur] == 1) // Zoom in image
     1307        {
     1308            // Start in center and pan out
     1309            x_loc = m_effect_kenBurns_location_x[m_texCur] * t[m_texCur];
     1310            y_loc = m_effect_kenBurns_location_y[m_texCur] * t[m_texCur];               
     1311            scale_max = FindMaxScale(x_loc,y_loc);
     1312            scale_factor =      1.0f + (scale_max * s[m_texCur]);
     1313        }
     1314        else // Zoom out image
     1315        {
     1316            // Start at random location and pan to center
     1317            x_loc = m_effect_kenBurns_location_x[m_texCur] -  m_effect_kenBurns_location_x[m_texCur] * t[m_texCur];
     1318            y_loc = m_effect_kenBurns_location_y[m_texCur] -  m_effect_kenBurns_location_y[m_texCur] * t[m_texCur];
     1319            scale_max = FindMaxScale(x_loc,y_loc);
     1320            scale_factor =      1.0f + scale_max -  (scale_max * t[m_texCur]);
     1321        }
     1322
     1323        glMatrixMode(GL_MODELVIEW);
     1324        glLoadIdentity();
     1325        glTranslatef(x_loc, y_loc, 0.0f);
     1326
     1327        m_texItem[m_texCur].MakeQuad((effect_pct-single_image_pct)*4, scale_factor);
     1328    }
     1329   
     1330    //Load old picture
     1331    if (m_effect_kenBurns_projection[m_texCur ? 0 : 1] == 1)// Zoom in image
     1332    {
     1333        x_loc = m_effect_kenBurns_location_x[m_texCur ? 0 : 1] * t[m_texCur ? 0 : 1];
     1334        y_loc = m_effect_kenBurns_location_y[m_texCur ? 0 : 1] * t[m_texCur ? 0 : 1];
     1335        scale_max = FindMaxScale(x_loc,y_loc);
     1336        scale_factor =  1.0f + (scale_max * s[m_texCur ? 0 : 1]);
     1337    }
     1338    else // Zoom out image
     1339    {
     1340        x_loc = m_effect_kenBurns_location_x[m_texCur ? 0 : 1] - 
     1341            m_effect_kenBurns_location_x[m_texCur ? 0 : 1] * t[m_texCur ? 0 : 1];
     1342        y_loc = m_effect_kenBurns_location_y[m_texCur ? 0 : 1] - 
     1343            m_effect_kenBurns_location_y[m_texCur ? 0 : 1] * t[m_texCur ? 0 : 1];
     1344        scale_max = FindMaxScale(x_loc,y_loc);
     1345        scale_factor =  1.0f + scale_max -  (scale_max * t[m_texCur ? 0 : 1]);
     1346    }
     1347
     1348    glMatrixMode(GL_MODELVIEW);
     1349    glLoadIdentity();
     1350    glTranslatef(x_loc, y_loc, 0.0f);
     1351
     1352    if (effect_pct<= single_image_pct)
     1353    {
     1354        m_effect_kenBurns_new_image_started=false;
     1355        m_texItem[m_texCur ? 0 : 1].MakeQuad(1.0f, scale_factor); //
     1356    }
     1357    else // Fade out image
     1358    {
     1359        m_texItem[m_texCur ? 0 : 1].MakeQuad(1.0f - ((effect_pct-single_image_pct)*4), scale_factor);
     1360
     1361    }
     1362   
     1363    m_effect_current_frame++;
     1364}
     1365
    11691366void GLSingleView::SlideTimeout(void)
    11701367{
    11711368    bool wasMovie = false, isMovie = false;
     
    12691466    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    12701467    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    12711468}
     1469
     1470void GLSingleView::LoadImage(QImage image, QSize origSize, Face *face)
     1471{
     1472    m_effect_kenBurns_image = image;
     1473    m_effect_kenBurns_orig_image_size = origSize;
     1474    if (face)
     1475    {
     1476        VERBOSE(VB_IMPORTANT, QString("Found face, set location x '%1' y '%2'").arg(face->getX()).arg(face->getY()));
     1477        m_effect_kenBurns_face_x = face->getX();
     1478        m_effect_kenBurns_face_y = face->getY();
     1479    }
     1480}
     1481
     1482float GLSingleView::FindMaxScale(float x_loc, float y_loc)
     1483{
     1484    // Zoom big enough to keep the entire image on screen when we pan
     1485    if (abs(x_loc) > abs(y_loc))
     1486        return abs(x_loc) * 2;
     1487    else
     1488        return abs(y_loc) * 2;
     1489}
     1490
     1491void GLSingleView::FindRandXY(float &x_loc, float &y_loc)
     1492{
     1493    x_loc = (0.5 * rand() / (RAND_MAX + 1.0f)) + 0.25;  //Random number between .25 and .75
     1494    if ((int)(2.0 * rand() / (RAND_MAX + 1.0f)) == 0)
     1495        x_loc = -1 * x_loc;
     1496    y_loc = (0.5 * rand() / (RAND_MAX + 1.0f)) + 0.25;  //Random number between .25 and .75
     1497    if ((int)(2.0 * rand() / (RAND_MAX + 1.0f)) == 0)
     1498        y_loc = -1 * y_loc;   
     1499}
     1500
     1501KenBurnsImageLoader::KenBurnsImageLoader(GLSingleView *singleView, ThumbList &itemList, QSize texSize, QSize screenSize)
     1502{
     1503    m_singleView = singleView;
     1504    m_itemList = itemList;
     1505    m_texSize = texSize;
     1506    m_screenSize = screenSize;
     1507}
     1508
     1509void KenBurnsImageLoader::Initialize(int pos)
     1510{
     1511    m_pos = pos;
     1512}
     1513
     1514void KenBurnsImageLoader::run()
     1515{
     1516    ThumbItem *item = m_itemList.at(m_pos);
     1517    if (!item)
     1518    {
     1519        VERBOSE(VB_IMPORTANT, LOC_ERR + "No item at "<<m_pos);
     1520        return;
     1521    }
     1522    QImage image(item->GetPath());
     1523    if (image.isNull())
     1524        return;
     1525   
     1526#ifdef FDLIB_SUPPORT
     1527   
     1528    int i, n, x[256], y[256], size[256], w, h, threshold;
     1529    float loc_x, loc_y;
     1530    unsigned char *bgrdata, *graydata;
     1531   
     1532    w = image.width();
     1533    h = image.height();
     1534    bgrdata = image.bits();
     1535 
     1536    graydata = new unsigned char[w*h];
     1537    for (i=0; i<w*h; i++)
     1538        graydata[i] = (unsigned char) ((.11*bgrdata[4*i] + .59*bgrdata[4*i+1] + .3*bgrdata[4*i+2]));
     1539   
     1540   
     1541    threshold = 0;
     1542    fdlib_detectfaces(graydata, w, h, threshold);   
     1543    delete[] graydata;       
     1544    n = fdlib_getndetections();
     1545    if (n==0)
     1546    {
     1547        VERBOSE(VB_IMPORTANT, QString("%1 face found.").arg(n));
     1548        m_singleView->LoadImage(QGLWidget::convertToGLFormat(image.smoothScale(m_texSize)), image.size());
     1549    }
     1550    else if (n==1)
     1551    {
     1552        VERBOSE(VB_IMPORTANT, QString("%1 faces found.").arg(n));
     1553
     1554        fdlib_getdetection(0, x, y, size);
     1555        loc_x = (x[0]-(float)w/2)/((float)w/2)*(-1);
     1556        loc_y = (y[0]-(float)h/2)/((float)h/2)*(-1);
     1557        VERBOSE(VB_IMPORTANT, QString("x:%1 y:%2 size:%3").arg(x[0]).arg(y[0]).arg(size[0]));
     1558        m_singleView->LoadImage(QGLWidget::convertToGLFormat(image.smoothScale(m_texSize)), image.size(), new Face(loc_x, loc_y));
     1559    }
     1560    else
     1561    {
     1562        VERBOSE(VB_IMPORTANT, QString("%1 faces found.").arg(n));
     1563        QPtrList<Face> faces;
     1564        int faceIndex;
     1565   
     1566        for (i=0; i<n; i++)
     1567        {
     1568            fdlib_getdetection(i, x+i, y+i, size+i);
     1569            loc_x = (x[i]-(float)w/2)/((float)w/2)*(-1);
     1570            loc_y = (y[i]-(float)h/2)/((float)h/2)*(-1);
     1571            VERBOSE(VB_IMPORTANT, QString("x:%1 y:%2 size:%3").arg(x[i]).arg(y[i]).arg(size[i]));
     1572            faces.append(new Face(loc_x, loc_y));
     1573        }
     1574        if (faces.count() > 1)
     1575            faceIndex = (int) ((faces.count()+1) * rand() / (RAND_MAX + 1.0f)); //
     1576        else
     1577            faceIndex = 0;
     1578           
     1579        m_singleView->LoadImage(QGLWidget::convertToGLFormat(image.smoothScale(m_texSize)), image.size(), faces.at(faceIndex));
     1580    }
     1581   
     1582#else  //END FDLIB_SUPPORT
     1583    m_singleView->LoadImage(QGLWidget::convertToGLFormat(image.smoothScale(m_texSize)), image.size());   
     1584#endif
     1585    m_singleView->Ready();
     1586
     1587}
     1588
     1589Face::Face(float x, float y)
     1590{
     1591    m_x = x;
     1592    m_y = y;
     1593}
  • mythgallery/glsingleview.h

     
    2828#include <qmap.h>
    2929#include <qsize.h>
    3030
     31
    3132// MythTV plugin headers
    3233#include <mythtv/util.h>
    3334
     
    4142class QTimer;
    4243
    4344class GLSingleView;
     45class KenBurnsImageLoader;
     46class Face;
    4447
    4548class GLSDialog : public MythDialog
    4649{
     
    6669    ~GLSingleView();
    6770
    6871    void CleanUp(void);
     72    void Ready(){m_effect_kenBurns_image_ready = true;}
     73    void LoadImage(QImage image, QSize origSize, Face *face = NULL);
     74   
    6975
    7076  protected:
    7177    void initializeGL(void);
     
    103109    void EffectSlide(void);
    104110    void EffectFlutter(void);
    105111    void EffectCube(void);
    106 
     112    void EffectKenBurns(void);
     113 
     114  private:
     115        float FindMaxScale(float x_loc, float y_loc);
     116        void FindRandXY(float &x_loc, float &y_loc);
     117   
    107118  private slots:
    108119    void SlideTimeout(void);
    109120
     
    134145    float         m_effect_cube_xrot;
    135146    float         m_effect_cube_yrot;
    136147    float         m_effect_cube_zrot;
     148    float         m_effect_kenBurns_location_x[2];
     149    float         m_effect_kenBurns_location_y[2];
     150    int           m_effect_kenBurns_projection[2];
     151    MythTimer     m_effect_kenBurns_image_time[2];
     152    float         m_effect_kenBurns_image_timeout;
     153    KenBurnsImageLoader *m_effect_kenBurns_imageLoadThread;
     154    bool          m_effect_kenBurns_image_ready;
     155    QImage        m_effect_kenBurns_image;
     156    QSize         m_effect_kenBurns_orig_image_size;
     157    float         m_effect_kenBurns_face_x;
     158    float         m_effect_kenBurns_face_y;
     159    ThumbItem     *m_effect_kenBurns_item;
     160    bool          m_effect_kenBurns_initialized;
     161    bool          m_effect_kenBurns_new_image_started;
     162   
    137163};
    138164
     165class KenBurnsImageLoader : public QThread
     166{
     167public:
     168    KenBurnsImageLoader(GLSingleView *singleView, ThumbList &itemList, QSize m_texSize, QSize m_screenSize);
     169    void Initialize(int pos);
     170    void run();
     171private:
     172        GLSingleView *m_singleView;
     173    ThumbList     m_itemList;
     174    int           m_pos;
     175    bool          m_tex1First;
     176    QSize         m_screenSize;
     177    QSize         m_texSize;
     178
     179};
     180
     181class Face
     182{
     183  public:
     184    Face(float x, float y);
     185    float getX() { return m_x; }
     186    float getY() { return m_y; }
     187  private:
     188      float m_x;
     189      float m_y;
     190};
     191
    139192#endif // USING_OPENGL
    140193#endif // GLSINGLEVIEW_H
  • mythgallery/imageview.cpp

     
    136136{
    137137    QMap<QString,QString> tmpMap = m_effect_map;
    138138    tmpMap.remove("none");
     139    tmpMap.remove("Ken Burns (gl)");
    139140    QStringList t = tmpMap.keys();
    140141    int i = (int) ( (float)(t.count()) * rand() / (RAND_MAX + 1.0f) );
    141142    return tmpMap[t[i]];
  • mythgallery/gallerysettings.cpp

     
    105105    gc->addSelection("flutter (gl)");
    106106    gc->addSelection("cube (gl)");
    107107    gc->addSelection("random (gl)");
     108    gc->addSelection("Ken Burns (gl)");
    108109    gc->setHelpText(QObject::tr("This is the type of OpenGL transition used "
    109110                    "between pictures in slideshow mode."));
    110111    return gc;
     
    113114static HostSpinBox *SlideshowOpenGLTransitionLength()
    114115{
    115116    HostSpinBox *gc = new HostSpinBox(
    116         "SlideshowOpenGLTransitionLength", 500, 10000, 500);
     117        "SlideshowOpenGLTransitionLength", 500, 30000, 500);
    117118    gc->setLabel(QObject::tr("Duration of OpenGL Transition (milliseconds)"));
    118119    gc->setValue(2000);
    119120    return gc;
     
    159160
    160161static HostSpinBox *SlideshowDelay()
    161162{
    162     HostSpinBox *gc = new HostSpinBox("SlideshowDelay", 1, 600, 1);
     163    HostSpinBox *gc = new HostSpinBox("SlideshowDelay", 0, 600, 1);
    163164    gc->setLabel(QObject::tr("Slideshow Delay"));
    164165    gc->setValue(5);
    165166    gc->setHelpText(QObject::tr("This is the number of seconds to display each "
  • README

     
    3737Current, EXIF support only consists of auto-rotating images if the camera
    3838sets the orientation tag (My Canon S400 does)
    3939
     40You can also enable fdlib support using
     41./configure --enable-fdlib
     42fdlib - is a facial recognition library that is used as part of the ken
     43burns slide show effect. It tries to ensure that slide shows pan/zoom
     44to peoples faces rather than their feet or off to the background.
     45fdlib can be downloaded from:
     46http://www.kyb.mpg.de/bs/people/kienzle/fdlib/fdlib.htm
     47It will require the gcc compatibility packages. Once downloaded
     48fdlib.h should be installed in your headers directory (/usr/include)
     49libfd.so should be installed in your libs directory (/usr/lib)
     50fdlib.dat should be installed in your current working directory when
     51mythfrontend is started for me it was (/home/myth). Please note that
     52this is not probably not the same directory where mythfrontend actually
     53resides. If fdlib does not find fdlib.dat, it will fail silently and just
     54not find any faces.
     55
    40562) Next type 'qmake mythgallery.pro' then 'make' in the
    4157main distribution directory.
    4258