diff options
Diffstat (limited to 'src/tdeioslave/digikamthumbnail.cpp')
-rw-r--r-- | src/tdeioslave/digikamthumbnail.cpp | 635 |
1 files changed, 635 insertions, 0 deletions
diff --git a/src/tdeioslave/digikamthumbnail.cpp b/src/tdeioslave/digikamthumbnail.cpp new file mode 100644 index 00000000..3dd4df28 --- /dev/null +++ b/src/tdeioslave/digikamthumbnail.cpp @@ -0,0 +1,635 @@ +/* ============================================================ + * + * 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; + } +} |