Skip to content

Commit

Permalink
Make RAW image thumbnails much faster
Browse files Browse the repository at this point in the history
Turns out that nearly all supported RAW image formats actually have embedded
JPG thumbnails in them.  Rather than decode to RAW then reencode, just load
the thumbnail from it if it's there.  Also, rotate the image based on the
EXIF orientation in the thumbnail.

Now creating a thumbnail from a supported RAW format takes avout 200ms instead
of 10-15s.

Refs #10185
  • Loading branch information
Beirdo committed Jan 3, 2012
1 parent 3b83dbd commit 9cfa8a1
Show file tree
Hide file tree
Showing 10 changed files with 169 additions and 71 deletions.
5 changes: 5 additions & 0 deletions mythplugins/configure
Expand Up @@ -742,6 +742,11 @@ if test "$gallery" = "yes" ; then
echo " Automatically generated by configure - do not modify" >> ./mythgallery/dcrawplugin/config.h
echo "*/" >> ./mythgallery/dcrawplugin/config.h

if test "$exif" = "yes" ; then
echo "#define EXIF_SUPPORT 1" >> ./mythgallery/dcrawplugin/config.h

fi

if test x"$icc" != x ; then
echo " ICC profile $icc"
# Quote C escape characters.
Expand Down
2 changes: 1 addition & 1 deletion mythplugins/mythgallery/dcrawplugin/dcrawformats.cpp
Expand Up @@ -45,7 +45,7 @@ QStringList DcrawFormats::getFilters()
{
QSet<QString> formats(getFormats());
QStringList filters;
for (QSet<QString>::const_iterator i(formats.begin()); i != formats.end(); ++i)
for (QSet<QString>::iterator i = formats.begin(); i != formats.end(); ++i)
filters << ("*." + *i);
return filters;
}
Expand Down
36 changes: 36 additions & 0 deletions mythplugins/mythgallery/dcrawplugin/dcrawhandler.cpp
Expand Up @@ -9,8 +9,11 @@
#include <QImage>
#include <QIODevice>
#include <QString>

#include "mythsystem.h"
#include "exitcodes.h"
#include "../mythgallery/galleryutil.h"
#include "mythlogging.h"

namespace
{
Expand Down Expand Up @@ -74,3 +77,36 @@ bool DcrawHandler::read(QImage *image)
return loaded;
}

int DcrawHandler::loadThumbnail(QImage *image, QString fileName)
{
QStringList arguments;
arguments << "-e" << "-c";
arguments << "'" + fileName + "'";

uint flags = kMSRunShell | kMSStdOut | kMSBuffered;
MythSystem ms("dcraw", arguments, flags);
ms.Run();
if (ms.Wait() != GENERIC_EXIT_OK)
return -1;

QByteArray buffer = ms.ReadAll();
if (buffer.isEmpty())
return -1;

if (!image->loadFromData(buffer, "JPG"))
return -1;

int rotateAngle = 0;

#ifdef EXIF_SUPPORT
const unsigned char *buf = (const unsigned char *)buffer.constData();
int size = buffer.size();
rotateAngle = GalleryUtil::GetNaturalRotation(buf, size);
#endif

return rotateAngle;
}

/*
* vim:ts=4:sw=4:ai:et:si:sts=4
*/
7 changes: 3 additions & 4 deletions mythplugins/mythgallery/dcrawplugin/dcrawhandler.h
@@ -1,13 +1,12 @@
#include <QImage>
#include <QImageIOHandler>
#include <QString>

class DcrawHandler : public QImageIOHandler
{

public:

public:
bool canRead() const;
bool read(QImage *image);

static int loadThumbnail(QImage *image, QString fileName);
};

1 change: 0 additions & 1 deletion mythplugins/mythgallery/dcrawplugin/dcrawplugin.h
Expand Up @@ -15,6 +15,5 @@ class DcrawPlugin : public QImageIOPlugin
QStringList keys() const;
Capabilities capabilities(QIODevice *device, const QByteArray &format) const;
QImageIOHandler *create(QIODevice *device, const QByteArray &format) const;

};

1 change: 1 addition & 0 deletions mythplugins/mythgallery/dcrawplugin/dcrawplugin.pro
Expand Up @@ -15,3 +15,4 @@ SOURCES += dcrawformats.cpp
SOURCES += dcrawhandler.cpp
SOURCES += dcrawplugin.cpp

QT += sql
145 changes: 90 additions & 55 deletions mythplugins/mythgallery/mythgallery/galleryutil.cpp
Expand Up @@ -39,7 +39,6 @@
#ifdef EXIF_SUPPORT
#include <libexif/exif-data.h>
#include <libexif/exif-entry.h>
// include "exif.hpp"
#endif // EXIF_SUPPORT

#define LOC QString("GalleryUtil:")
Expand Down Expand Up @@ -122,87 +121,119 @@ bool GalleryUtil::IsMovie(const QString &filePath)
return false;
}

long GalleryUtil::GetNaturalRotation(const unsigned char *buffer, int size)
{
long rotateAngle = 0;

#ifdef EXIF_SUPPORT
try
{
ExifData *data = exif_data_new_from_data(buffer, size);
if (data)
{
rotateAngle = GetNaturalRotation(data);
exif_data_free(data);
}
else
{
LOG(VB_FILE, LOG_ERR, LOC + "Could not load exif data from buffer");
}
}
catch (...)
{
LOG(VB_GENERAL, LOG_ERR, LOC +
"Failed to extract EXIF headers from buffer");
}
#else
// Shut the compiler up about the unused argument
(void)buffer;
(void)length;
#endif

return rotateAngle;
}

long GalleryUtil::GetNaturalRotation(const QString &filePathString)
{
long rotateAngle = 0;

#ifdef EXIF_SUPPORT
QByteArray filePathBA = filePathString.toLocal8Bit();
const char *filePath = filePathBA.constData();

try
{
ExifData *data = exif_data_new_from_file (filePath);
ExifData *data = exif_data_new_from_file(filePath);
if (data)
{
for (int i = 0; i < EXIF_IFD_COUNT; i++)
{
ExifEntry *entry = exif_content_get_entry (data->ifd[i],
EXIF_TAG_ORIENTATION);
ExifByteOrder byteorder = exif_data_get_byte_order (data);

if (entry)
{
ExifShort v_short = exif_get_short (entry->data, byteorder);
LOG(VB_GENERAL, LOG_DEBUG,
QString("Exif entry=%1").arg(v_short));

/* See http://sylvana.net/jpegcrop/exif_orientation.html*/
if (v_short == 8)
{
rotateAngle = -90;
}
else if (v_short == 6)
{
rotateAngle = 90;
}
break;
}
}
rotateAngle = GetNaturalRotation(data);
exif_data_free(data);
}
else
{
LOG(VB_FILE, LOG_ERR, LOC +
QString("Could not load exif data from '%1'") .arg(filePath));
}

#if 0
Exiv2::ExifData exifData;
int rc = exifData.read(filePath);
if (!rc)
{
Exiv2::ExifKey key = Exiv2::ExifKey("Exif.Image.Orientation");
Exiv2::ExifData::iterator pos = exifData.findKey(key);
if (pos != exifData.end())
{
long orientation = pos->toLong();
switch (orientation)
{
case 6:
rotateAngle = 90;
break;
case 8:
rotateAngle = -90;
break;
default:
rotateAngle = 0;
break;
}
}
}
#endif
}
catch (...)
{
LOG(VB_GENERAL, LOG_ERR, LOC +
QString("Failed to extract EXIF headers from '%1'")
.arg(filePathString));
QString("Failed to extract EXIF headers from '%1'") .arg(filePath));
}

#else
// Shut the compiler up about the unused argument
(void)filePathString;
#endif

return rotateAngle;
}

long GalleryUtil::GetNaturalRotation(void *exifData)
{
long rotateAngle = 0;

#ifdef EXIF_SUPPORT
ExifData *data = (ExifData *)exifData;

if (!data)
return 0;

for (int i = 0; i < EXIF_IFD_COUNT; i++)
{
ExifEntry *entry = exif_content_get_entry(data->ifd[i],
EXIF_TAG_ORIENTATION);
ExifByteOrder byteorder = exif_data_get_byte_order(data);

if (entry)
{
ExifShort v_short = exif_get_short(entry->data, byteorder);
LOG(VB_GENERAL, LOG_DEBUG,
QString("Exif entry=%1").arg(v_short));

/* See http://sylvana.net/jpegcrop/exif_orientation.html*/
switch (v_short)
{
case 3:
rotateAngle = 180;
break;
case 6:
rotateAngle = 90;
break;
case 8:
rotateAngle = -90;
break;
default:
rotateAngle = 0;
break;
}
break;
}
}
#else
// Shut the compiler up about the unused argument
(void)exifData;
#endif // EXIF_SUPPORT

return rotateAngle;
}

Expand Down Expand Up @@ -776,3 +807,7 @@ static bool FileDelete(const QFileInfo &file)

return true;
}

/*
* vim:ts=4:sw=4:ai:et:si:sts=4
*/
2 changes: 2 additions & 0 deletions mythplugins/mythgallery/mythgallery/galleryutil.h
Expand Up @@ -40,6 +40,7 @@ class GalleryUtil

static bool IsImage(const QString &filePath);
static bool IsMovie(const QString &filePath);
static long GetNaturalRotation(const unsigned char *buffer, int size);
static long GetNaturalRotation(const QString &filePath);

static QString GetCaption(const QString &filePath);
Expand All @@ -65,6 +66,7 @@ class GalleryUtil
static bool DeleteDirectory(const QFileInfo &dir);
static bool RenameDirectory(const QString &currDir, const QString &oldName,
const QString &newName);
static long GetNaturalRotation(void *exifData);
};

#endif /* GALLERYUTIL_H */
10 changes: 0 additions & 10 deletions mythplugins/mythgallery/mythgallery/iconview.cpp
Expand Up @@ -334,16 +334,6 @@ void IconView::LoadThumbnail(ThumbItem *item)
.arg(ThumbGenerator::getThumbcacheDir(m_currDir))
.arg(item->GetName());

// int rotateAngle = 0;
//
// rotateAngle = item->GetRotationAngle();
//
// if (rotateAngle != 0)
// {
// QMatrix matrix;
// matrix.rotate(rotateAngle);
// image = image.xForm(matrix);
// }
item->SetImageFilename(imagePath);
}

Expand Down
31 changes: 31 additions & 0 deletions mythplugins/mythgallery/mythgallery/thumbgenerator.cpp
Expand Up @@ -26,6 +26,7 @@
#include <QDir>
#include <QEvent>
#include <QImageReader>
#include <QSet>

// myth
#include <mythuihelper.h>
Expand All @@ -42,6 +43,11 @@
#include "exitcodes.h"
#include "mythlogging.h"

#ifdef DCRAW_SUPPORT
#include "../dcrawplugin/dcrawformats.h"
#include "../dcrawplugin/dcrawhandler.h"
#endif // DCRAW_SUPPORT

#ifdef EXIF_SUPPORT
#include <libexif/exif-data.h>
#include <libexif/exif-entry.h>
Expand Down Expand Up @@ -335,6 +341,27 @@ void ThumbGenerator::loadFile(QImage& image, const QFileInfo& fi)
return;
#endif

#ifdef DCRAW_SUPPORT
QString extension = fi.suffix();
QSet<QString> dcrawFormats = DcrawFormats::getFormats();
int rotateAngle;

if (dcrawFormats.contains(extension) &&
(rotateAngle = DcrawHandler::loadThumbnail(&image,
fi.absoluteFilePath())) != -1 &&
image.width() > m_width && image.height() > m_height)
{
if (rotateAngle != 0)
{
QMatrix matrix;
matrix.rotate(rotateAngle);
image = image.transformed(matrix);
}

return;
}
#endif

image.load(fi.absoluteFilePath());
}
}
Expand Down Expand Up @@ -373,3 +400,7 @@ QString ThumbGenerator::getThumbcacheDir(const QString& inDir)

return aPath;
}

/*
* vim:ts=4:sw=4:ai:et:si:sts=4
*/

0 comments on commit 9cfa8a1

Please sign in to comment.