You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
digikam/digikam/tdeioslave/digikamthumbnail.cpp

636 lines
17 KiB

/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2003-01-15
* Description : digiKam TDEIO slave to get image thumbnails.
* This tdeio-slave support this freedesktop
* specification about thumbnails mamagement:
* http://jens.triq.net/thumbnail-spec
*
* Copyright (C) 2003-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
* Copyright (C) 2003-2009 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#define XMD_H
#define PNG_BYTES_TO_CHECK 4
// C++ includes.
#include <cstdlib>
#include <cstdio>
#include <cstring>
// TQt includes.
#include <tqcstring.h>
#include <tqstring.h>
#include <tqimage.h>
#include <tqdatastream.h>
#include <tqfile.h>
#include <tqfileinfo.h>
#include <tqdir.h>
#include <tqwmatrix.h>
#include <tqregexp.h>
#include <tqapplication.h>
// KDE includes.
#include <kdebug.h>
#include <kurl.h>
#include <kinstance.h>
#include <kimageio.h>
#include <tdelocale.h>
#include <tdeglobal.h>
#include <kstandarddirs.h>
#include <kmdcodec.h>
#include <tdetempfile.h>
#include <ktrader.h>
#include <klibloader.h>
#include <kmimetype.h>
#include <kprocess.h>
#include <tdeio/global.h>
#include <tdeio/thumbcreator.h>
#include <tdefilemetainfo.h>
// LibKDcraw includes.
#include <libkdcraw/version.h>
#include <libkdcraw/kdcraw.h>
#if KDCRAW_VERSION < 0x000106
#include <libkdcraw/dcrawbinary.h>
#endif
// Local includes.
#include "dimg.h"
#include "dmetadata.h"
#include "jpegutils.h"
#include "digikamthumbnail.h"
#include "digikam_export.h"
// C Ansi includes.
extern "C"
{
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/time.h>
#include <png.h>
}
using namespace TDEIO;
using namespace Digikam;
tdeio_digikamthumbnailProtocol::tdeio_digikamthumbnailProtocol(int argc, char** argv)
: SlaveBase("tdeio_digikamthumbnail", argv[2], argv[3])
{
argc_ = argc;
argv_ = argv;
app_ = 0;
digiKamFingerPrint = TQString("Digikam Thumbnail Generator");
createThumbnailDirs();
}
tdeio_digikamthumbnailProtocol::~tdeio_digikamthumbnailProtocol()
{
}
void tdeio_digikamthumbnailProtocol::get(const KURL& url)
{
int size = metaData("size").toInt();
bool exif = (metaData("exif") == "yes");
cachedSize_ = (size <= 128) ? 128 : 256;
if (cachedSize_ <= 0)
{
error(TDEIO::ERR_INTERNAL, i18n("No or invalid size specified"));
kdWarning() << "No or invalid size specified" << endl;
return;
}
// generate the thumbnail path
TQString uri = KURL(url.path()).url(); // "file://" uri
KMD5 md5( TQFile::encodeName(uri).data() );
TQString thumbPath = (cachedSize_ == 128) ? smallThumbPath_ : bigThumbPath_;
thumbPath += TQFile::encodeName( md5.hexDigest() + ".png" ).data();
TQImage img;
bool regenerate = true;
// stat the original file
struct stat st;
if (::stat(TQFile::encodeName(url.path(-1)), &st) != 0)
{
error(TDEIO::ERR_INTERNAL, i18n("File does not exist"));
return;
}
// NOTE: if thumbnail have not been generated by digiKam (konqueror for example),
// force to recompute it, else we use it.
img = loadPNG(thumbPath);
if (!img.isNull())
{
if (img.text("Thumb::MTime") == TQString::number(st.st_mtime) &&
img.text("Software") == digiKamFingerPrint)
regenerate = false;
}
if (regenerate)
{
// To speed-up thumb extraction, we trying to load image using the file extension.
if ( !loadByExtension(img, url.path()) )
{
// Try JPEG loading : JPEG files without using Exif Thumb.
if ( !loadJPEG(img, url.path()) )
{
// Try to load with dcraw : RAW files.
if (!KDcrawIface::KDcraw::loadDcrawPreview(img, url.path()) )
{
// Try to load with DImg : TIFF, PNG, etc.
if (!loadDImg(img, url.path()) )
{
// Try to load with KDE thumbcreators : video files and others stuff.
loadKDEThumbCreator(img, url.path());
}
}
}
}
if (img.isNull())
{
error(TDEIO::ERR_INTERNAL, i18n("Cannot create thumbnail for %1")
.arg(url.prettyURL()));
kdWarning() << "Cannot create thumbnail for " << url.path() << endl;
return;
}
if (TQMAX(img.width(),img.height()) != cachedSize_)
img = img.smoothScale(cachedSize_, cachedSize_, TQImage::ScaleMin);
if (img.depth() != 32)
img = img.convertDepth(32);
if (exif)
exifRotate(url.path(), img);
img.setText(TQString("Thumb::URI").latin1(), 0, uri);
img.setText(TQString("Thumb::MTime").latin1(), 0, TQString::number(st.st_mtime));
img.setText(TQString("Software").latin1(), 0, digiKamFingerPrint);
KTempFile temp(thumbPath + "-digikam-", ".png");
if (temp.status() == 0)
{
img.save(temp.name(), "PNG", 0);
::rename(TQFile::encodeName(temp.name()),
TQFile::encodeName(thumbPath));
}
}
img = img.smoothScale(size, size, TQImage::ScaleMin);
if (img.isNull())
{
error(TDEIO::ERR_INTERNAL, "Thumbnail is null");
return;
}
TQByteArray imgData;
TQDataStream stream( imgData, IO_WriteOnly );
const TQString shmid = metaData("shmid");
if (shmid.isEmpty())
{
stream << img;
}
else
{
void *shmaddr = shmat(shmid.toInt(), 0, 0);
if (shmaddr == (void *)-1)
{
error(TDEIO::ERR_INTERNAL, "Failed to attach to shared memory segment " + shmid);
kdWarning() << "Failed to attach to shared memory segment " << shmid << endl;
return;
}
if (img.width() * img.height() > cachedSize_ * cachedSize_)
{
error(TDEIO::ERR_INTERNAL, "Image is too big for the shared memory segment");
kdWarning() << "Image is too big for the shared memory segment" << endl;
shmdt((char*)shmaddr);
return;
}
stream << img.width() << img.height() << img.depth();
memcpy(shmaddr, img.bits(), img.numBytes());
shmdt((char*)shmaddr);
}
data(imgData);
finished();
}
bool tdeio_digikamthumbnailProtocol::loadByExtension(TQImage& image, const TQString& path)
{
TQFileInfo fileInfo(path);
if (!fileInfo.exists())
return false;
// Try to use embedded preview image from metadata.
DMetadata metadata(path);
if (metadata.getImagePreview(image))
{
kdDebug() << "Use Exif/Iptc preview extraction. Size of image: "
<< image.width() << "x" << image.height() << endl;
return true;
}
// Else, use the right way depending of image file extension.
TQString ext = fileInfo.extension(false).upper();
#if KDCRAW_VERSION < 0x000106
TQString rawFilesExt(KDcrawIface::DcrawBinary::instance()->rawFiles());
#else
TQString rawFilesExt(KDcrawIface::KDcraw::rawFiles());
#endif
if (!ext.isEmpty())
{
if (ext == TQString("JPEG") || ext == TQString("JPG") || ext == TQString("JPE"))
return (loadJPEG(image, path));
else if (ext == TQString("PNG"))
return (loadDImg(image, path));
else if (ext == TQString("TIFF") || ext == TQString("TIF"))
return (loadDImg(image, path));
else if (rawFilesExt.upper().contains(ext))
return (KDcrawIface::KDcraw::loadDcrawPreview(image, path));
}
return false;
}
bool tdeio_digikamthumbnailProtocol::loadJPEG(TQImage& image, const TQString& path)
{
return Digikam::loadJPEGScaled(image, path, cachedSize_);
}
void tdeio_digikamthumbnailProtocol::exifRotate(const TQString& filePath, TQImage& thumb)
{
// Rotate thumbnail based on metadata orientation information
DMetadata metadata(filePath);
DMetadata::ImageOrientation orientation = metadata.getImageOrientation();
if (orientation == DMetadata::ORIENTATION_NORMAL ||
orientation == DMetadata::ORIENTATION_UNSPECIFIED)
return;
TQWMatrix matrix;
switch (orientation)
{
case DMetadata::ORIENTATION_NORMAL:
case DMetadata::ORIENTATION_UNSPECIFIED:
break;
case DMetadata::ORIENTATION_HFLIP:
matrix.scale(-1, 1);
break;
case DMetadata::ORIENTATION_ROT_180:
matrix.rotate(180);
break;
case DMetadata::ORIENTATION_VFLIP:
matrix.scale(1, -1);
break;
case DMetadata::ORIENTATION_ROT_90_HFLIP:
matrix.scale(-1, 1);
matrix.rotate(90);
break;
case DMetadata::ORIENTATION_ROT_90:
matrix.rotate(90);
break;
case DMetadata::ORIENTATION_ROT_90_VFLIP:
matrix.scale(1, -1);
matrix.rotate(90);
break;
case DMetadata::ORIENTATION_ROT_270:
matrix.rotate(270);
break;
}
// transform accordingly
thumb = thumb.xForm(matrix);
}
TQImage tdeio_digikamthumbnailProtocol::loadPNG(const TQString& path)
{
png_uint_32 w32, h32;
int w, h;
bool has_alpha;
bool has_grey;
FILE *f;
png_structp png_ptr = NULL;
png_infop info_ptr = NULL;
int bit_depth, color_type, interlace_type;
has_alpha = 0;
has_grey = 0;
TQImage qimage;
f = fopen(path.latin1(), "rb");
if (!f)
return qimage;
unsigned char buf[PNG_BYTES_TO_CHECK];
fread(buf, 1, PNG_BYTES_TO_CHECK, f);
if (png_sig_cmp(buf, 0, PNG_BYTES_TO_CHECK))
{
fclose(f);
return qimage;
}
rewind(f);
png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (!png_ptr)
{
fclose(f);
return qimage;
}
info_ptr = png_create_info_struct(png_ptr);
if (!info_ptr)
{
png_destroy_read_struct(&png_ptr, NULL, NULL);
fclose(f);
return qimage;
}
if (setjmp(png_jmpbuf(png_ptr)))
{
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
fclose(f);
return qimage;
}
png_init_io(png_ptr, f);
png_read_info(png_ptr, info_ptr);
png_get_IHDR(png_ptr, info_ptr, (png_uint_32 *) (&w32),
(png_uint_32 *) (&h32), &bit_depth, &color_type,
&interlace_type, NULL, NULL);
w = w32;
h = h32;
qimage.create(w, h, 32);
if (color_type == PNG_COLOR_TYPE_PALETTE)
png_set_expand(png_ptr);
if (color_type == PNG_COLOR_TYPE_RGB_ALPHA)
has_alpha = 1;
if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
{
has_alpha = 1;
has_grey = 1;
}
if (color_type == PNG_COLOR_TYPE_GRAY)
has_grey = 1;
unsigned char **lines;
int i;
if (has_alpha)
png_set_expand(png_ptr);
if (TQImage::systemByteOrder() == TQImage::LittleEndian)
{
png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER);
png_set_bgr(png_ptr);
}
else
{
png_set_swap_alpha(png_ptr);
png_set_filler(png_ptr, 0xff, PNG_FILLER_BEFORE);
}
/* 16bit color -> 8bit color */
if ( bit_depth == 16 )
png_set_strip_16(png_ptr);
/* pack all pixels to byte boundaires */
png_set_packing(png_ptr);
if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
png_set_expand(png_ptr);
lines = (unsigned char **)malloc(h * sizeof(unsigned char *));
if (!lines)
{
png_read_end(png_ptr, info_ptr);
png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) NULL);
fclose(f);
return qimage;
}
if (has_grey)
{
png_set_gray_to_rgb(png_ptr);
if (png_get_bit_depth(png_ptr, info_ptr) < 8)
png_set_expand_gray_1_2_4_to_8(png_ptr);
}
int sizeOfUint = sizeof(unsigned int);
for (i = 0 ; i < h ; i++)
lines[i] = ((unsigned char *)(qimage.bits())) + (i * w * sizeOfUint);
png_read_image(png_ptr, lines);
free(lines);
png_textp text_ptr;
int num_text=0;
png_get_text(png_ptr,info_ptr,&text_ptr,&num_text);
while (num_text--)
{
qimage.setText(text_ptr->key,0,text_ptr->text);
text_ptr++;
}
png_read_end(png_ptr, info_ptr);
png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) NULL);
fclose(f);
return qimage;
}
// -- Load using DImg ---------------------------------------------------------------------
bool tdeio_digikamthumbnailProtocol::loadDImg(TQImage& image, const TQString& path)
{
Digikam::DImg dimg_im;
// to disable raw loader - does not work from ioslave
dimg_im.setAttribute("noeventloop", true);
if (!dimg_im.load(path))
{
return false;
}
image = dimg_im.copyTQImage();
org_width_ = image.width();
org_height_ = image.height();
if ( TQMAX(org_width_, org_height_) != cachedSize_ )
{
TQSize sz(dimg_im.width(), dimg_im.height());
sz.scale(cachedSize_, cachedSize_, TQSize::ScaleMin);
image.scale(sz.width(), sz.height());
}
new_width_ = image.width();
new_height_ = image.height();
image.setAlphaBuffer(true) ;
return true;
}
// -- Load using KDE API ---------------------------------------------------------------------
bool tdeio_digikamthumbnailProtocol::loadKDEThumbCreator(TQImage& image, const TQString& path)
{
// this sucks royally. some of the thumbcreators need an instance of
// app running so that they can use pixmap. till they get their
// code fixed, we will have to create a qapp instance.
if (!app_)
app_ = new TQApplication(argc_, argv_);
TQString mimeType = KMimeType::findByURL(path)->name();
if (mimeType.isEmpty())
{
kdDebug() << "Mimetype not found" << endl;
return false;
}
TQString mimeTypeAlt = mimeType.replace(TQRegExp("/.*"), "/*");
TQString plugin;
TDETrader::OfferList plugins = TDETrader::self()->query("ThumbCreator");
for (TDETrader::OfferList::ConstIterator it = plugins.begin(); it != plugins.end(); ++it)
{
TQStringList mimeTypes = (*it)->property("MimeTypes").toStringList();
for (TQStringList::ConstIterator mt = mimeTypes.begin(); mt != mimeTypes.end(); ++mt)
{
if ((*mt) == mimeType || (*mt) == mimeTypeAlt)
{
plugin=(*it)->library();
break;
}
}
if (!plugin.isEmpty())
break;
}
if (plugin.isEmpty())
{
kdDebug() << "No relevant plugin found " << endl;
return false;
}
KLibrary *library = KLibLoader::self()->library(TQFile::encodeName(plugin));
if (!library)
{
kdDebug() << "Plugin library not found " << plugin << endl;
return false;
}
ThumbCreator *creator = 0;
newCreator create = (newCreator)library->symbol("new_creator");
if (create)
creator = create();
if (!creator)
{
kdDebug() << "Cannot load ThumbCreator " << plugin << endl;
return false;
}
if (!creator->create(path, cachedSize_, cachedSize_, image))
{
kdDebug() << "Cannot create thumbnail for " << path << endl;
delete creator;
return false;
}
delete creator;
return true;
}
void tdeio_digikamthumbnailProtocol::createThumbnailDirs()
{
TQString path = TQDir::homeDirPath() + "/.thumbnails/";
smallThumbPath_ = path + "normal/";
bigThumbPath_ = path + "large/";
TDEStandardDirs::makeDir(smallThumbPath_, 0700);
TDEStandardDirs::makeDir(bigThumbPath_, 0700);
}
// -- TDEIO slave registration ---------------------------------------------------------------------
extern "C"
{
DIGIKAM_EXPORT int kdemain(int argc, char **argv)
{
TDELocale::setMainCatalogue("digikam");
TDEInstance instance( "tdeio_digikamthumbnail" );
( void ) TDEGlobal::locale();
if (argc != 4)
{
kdDebug() << "Usage: tdeio_digikamthumbnail protocol domain-socket1 domain-socket2"
<< endl;
exit(-1);
}
KImageIO::registerFormats();
tdeio_digikamthumbnailProtocol slave(argc, argv);
slave.dispatchLoop();
return 0;
}
}