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/libs/threadimageio/previewtask.cpp

259 lines
8.7 KiB

/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2006-12-26
* Description : Multithreaded loader for previews
*
* Copyright (C) 2006-2007 by Marcel Wiesweg <marcel.wiesweg@gmx.de>
* Copyright (C) 2006-2008 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.
*
* ============================================================ */
// C++ includes.
#include <cmath>
// TQt includes.
#include <tqapplication.h>
#include <tqimage.h>
#include <tqvariant.h>
#include <tqwmatrix.h>
// LibKDcraw includes.
#include <libkdcraw/kdcraw.h>
// Local includes.
#include "ddebug.h"
#include "dmetadata.h"
#include "jpegutils.h"
#include "previewloadthread.h"
#include "previewtask.h"
namespace Digikam
{
void PreviewLoadingTask::execute()
{
if (m_loadingTaskStatus == LoadingTaskStatusStopping)
return;
LoadingCache *cache = LoadingCache::cache();
{
LoadingCache::CacheLock lock(cache);
// find possible cached images
DImg *cachedImg = 0;
TQStringList lookupKeys = m_loadingDescription.lookupCacheKeys();
// lookupCacheKeys returns "best first". Prepend the cache key to make the list "fastest first":
// Scaling a full version takes longer!
lookupKeys.push_front(m_loadingDescription.cacheKey());
for ( TQStringList::Iterator it = lookupKeys.begin(); it != lookupKeys.end(); ++it ) {
if ( (cachedImg = cache->retrieveImage(*it)) )
break;
}
if (cachedImg)
{
// image is found in image cache, loading is successful
DImg img(*cachedImg);
// rotate if needed - images are unrotated in the cache,
// except for RAW images, which are already rotated by dcraw.
if (m_loadingDescription.previewParameters.exifRotate)
{
img = img.copy();
LoadSaveThread::exifRotate(img, m_loadingDescription.filePath);
}
TQApplication::postEvent(m_thread, new LoadedEvent(m_loadingDescription.filePath, img));
return;
}
else
{
// find possible running loading process
m_usedProcess = 0;
for ( TQStringList::Iterator it = lookupKeys.begin(); it != lookupKeys.end(); ++it ) {
if ( (m_usedProcess = cache->retrieveLoadingProcess(*it)) )
{
break;
}
}
// do not wait on other loading processes?
//m_usedProcess = cache->retrieveLoadingProcess(m_loadingDescription.cacheKey());
if (m_usedProcess)
{
// Other process is right now loading this image.
// Add this task to the list of listeners and
// attach this thread to the other thread, wait until loading
// has finished.
m_usedProcess->addListener(this);
// break loop when either the loading has completed, or this task is being stopped
while ( !m_usedProcess->completed() && m_loadingTaskStatus != LoadingTaskStatusStopping )
lock.timedWait();
// remove listener from process
m_usedProcess->removeListener(this);
// wake up the process which is waiting until all listeners have removed themselves
lock.wakeAll();
// set to 0, as checked in setStatus
m_usedProcess = 0;
return;
}
else
{
// Neither in cache, nor currently loading in different thread.
// Load it here and now, add this LoadingProcess to cache list.
cache->addLoadingProcess(this);
// Add this to the list of listeners
addListener(this);
// for use in setStatus
m_usedProcess = this;
// Notify other processes that we are now loading this image.
// They might be interested - see notifyNewLoadingProcess below
cache->notifyNewLoadingProcess(this, m_loadingDescription);
}
}
}
// load image
int size = m_loadingDescription.previewParameters.size;
DImg img;
TQImage qimage;
bool fromEmbeddedPreview = false;
// -- Get the image preview --------------------------------
// First the TQImage-dependent loading methods
// Trying to load with dcraw: RAW files.
if (KDcrawIface::KDcraw::loadEmbeddedPreview(qimage, m_loadingDescription.filePath))
fromEmbeddedPreview = true;
if (qimage.isNull())
{
//TODO: Use DImg based loader instead?
KDcrawIface::KDcraw::loadHalfPreview(qimage, m_loadingDescription.filePath);
}
// Try to extract Exif/Iptc preview.
if (qimage.isNull())
{
loadImagePreview(qimage, m_loadingDescription.filePath);
}
if (!qimage.isNull())
{
// convert from TQImage
img = DImg(qimage);
// mark as embedded preview (for exif rotation)
if (fromEmbeddedPreview)
img.setAttribute("fromRawEmbeddedPreview", true);
// free memory
qimage = TQImage();
}
// DImg-dependent loading methods
if (img.isNull())
{
// Set a hint to try to load a JPEG with the fast scale-before-decoding method
img.setAttribute("jpegScaledLoadingSize", size);
img.load(m_loadingDescription.filePath, this, m_loadingDescription.rawDecodingSettings);
}
if (img.isNull())
{
DWarning() << "Cannot extract preview for " << m_loadingDescription.filePath << endl;
}
img.convertToEightBit();
// Reduce size of image:
// - only scale down if size is considerably larger
// - only scale down, do not scale up
TQSize scaledSize = img.size();
if (needToScale(scaledSize, size))
{
scaledSize.scale(size, size, TQSize::ScaleMin);
img = img.smoothScale(scaledSize.width(), scaledSize.height());
}
// Scale if hinted, Store previews rotated in the cache (?)
if (m_loadingDescription.previewParameters.exifRotate)
LoadSaveThread::exifRotate(img, m_loadingDescription.filePath);
{
LoadingCache::CacheLock lock(cache);
// put (valid) image into cache of loaded images
if (!img.isNull())
cache->putImage(m_loadingDescription.cacheKey(), new DImg(img), m_loadingDescription.filePath);
// remove this from the list of loading processes in cache
cache->removeLoadingProcess(this);
}
// following the golden rule to avoid deadlocks, do this when CacheLock is not held
m_thread->taskHasFinished();
{
LoadingCache::CacheLock lock(cache);
// indicate that loading has finished so that listeners can stop waiting
m_completed = true;
// dispatch image to all listeners, including this
for (LoadingProcessListener *l = m_listeners.first(); l; l = m_listeners.next())
{
TQApplication::postEvent(l->eventReceiver(), new LoadedEvent(m_loadingDescription, img));
}
// remove myself from list of listeners
removeListener(this);
// wake all listeners waiting on cache condVar, so that they remove themselves
lock.wakeAll();
// wait until all listeners have removed themselves
while (m_listeners.count() != 0)
lock.timedWait();
// set to 0, as checked in setStatus
m_usedProcess = 0;
}
}
bool PreviewLoadingTask::needToScale(const TQSize &imageSize, int previewSize)
{
int maxSize = imageSize.width() > imageSize.height() ? imageSize.width() : imageSize.height();
int acceptableUpperSize = lround(1.25 * (double)previewSize);
return maxSize >= acceptableUpperSize;
}
// -- Exif/IPTC preview extraction using Exiv2 --------------------------------------------------------
bool PreviewLoadingTask::loadImagePreview(TQImage& image, const TQString& path)
{
DMetadata metadata(path);
if (metadata.getImagePreview(image))
{
DDebug() << "Use Exif/Iptc preview extraction. Size of image: "
<< image.width() << "x" << image.height() << endl;
return true;
}
return false;
}
} // namespace Digikam