diff --git a/core/libs/threadimageio/fileio/loadingcache.cpp b/core/libs/threadimageio/fileio/loadingcache.cpp index 9bb9dd57ba..42b44ff135 100644 --- a/core/libs/threadimageio/fileio/loadingcache.cpp +++ b/core/libs/threadimageio/fileio/loadingcache.cpp @@ -1,522 +1,519 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2006-01-11 * Description : shared image loading and caching * * Copyright (C) 2005-2011 by Marcel Wiesweg * * 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. * * ============================================================ */ #include "loadingcache.h" // Qt includes #include #include #include #include // Local includes #include "digikam_debug.h" #include "iccsettings.h" #include "kmemoryinfo.h" #include "dmetadata.h" #include "thumbnailsize.h" namespace Digikam { class Q_DECL_HIDDEN LoadingCache::Private { public: explicit Private(LoadingCache* const q) : q(q) { // Note: Don't make the mutex recursive, we need to use a wait condition on it watch = 0; } void mapImageFilePath(const QString& filePath, const QString& cacheKey); void mapThumbnailFilePath(const QString& filePath, const QString& cacheKey); void cleanUpImageFilePathHash(); void cleanUpThumbnailFilePathHash(); LoadingCacheFileWatch* fileWatch() const; public: QCache imageCache; QCache thumbnailImageCache; QCache thumbnailPixmapCache; QMultiMap imageFilePathHash; QMultiMap thumbnailFilePathHash; QMap loadingDict; QMutex mutex; QWaitCondition condVar; LoadingCacheFileWatch* watch; LoadingCache* q; }; LoadingCacheFileWatch* LoadingCache::Private::fileWatch() const { // install default watch if no watch is set yet if (!watch) { q->setFileWatch(new ClassicLoadingCacheFileWatch); } return watch; } void LoadingCache::Private::mapImageFilePath(const QString& filePath, const QString& cacheKey) { if (imageFilePathHash.size() > 5*imageCache.size()) { cleanUpImageFilePathHash(); } imageFilePathHash.insert(filePath, cacheKey); } void LoadingCache::Private::mapThumbnailFilePath(const QString& filePath, const QString& cacheKey) { if (thumbnailFilePathHash.size() > 5*(thumbnailImageCache.size() + thumbnailPixmapCache.size())) { cleanUpThumbnailFilePathHash(); } thumbnailFilePathHash.insert(filePath, cacheKey); } void LoadingCache::Private::cleanUpImageFilePathHash() { // Remove all entries from hash whose value is no longer a key in the cache QSet keys = imageCache.keys().toSet(); QMultiMap::iterator it; for (it = imageFilePathHash.begin() ; it != imageFilePathHash.end() ; ) { if (!keys.contains(it.value())) { it = imageFilePathHash.erase(it); } else { ++it; } } } void LoadingCache::Private::cleanUpThumbnailFilePathHash() { QSet keys; keys += thumbnailImageCache.keys().toSet(); keys += thumbnailPixmapCache.keys().toSet(); QMultiMap::iterator it; for (it = thumbnailFilePathHash.begin() ; it != thumbnailFilePathHash.end() ; ) { if (!keys.contains(it.value())) { it = thumbnailFilePathHash.erase(it); } else { ++it; } } } LoadingCache* LoadingCache::m_instance = 0; LoadingCache* LoadingCache::cache() { if (!m_instance) { m_instance = new LoadingCache; } return m_instance; } void LoadingCache::cleanUp() { delete m_instance; } LoadingCache::LoadingCache() : d(new Private(this)) { KMemoryInfo memory = KMemoryInfo::currentInfo(); setCacheSize(qBound(60, int(memory.megabytes(KMemoryInfo::TotalRam)*0.05), 200)); setThumbnailCacheSize(5, 100); // the pixmap number should not be based on system memory, it's graphics memory // good place to call it here as LoadingCache is a singleton qRegisterMetaType("LoadingDescription"); qRegisterMetaType("DImg"); qRegisterMetaType("DMetadata"); connect(IccSettings::instance(), SIGNAL(settingsChanged(ICCSettingsContainer,ICCSettingsContainer)), this, SLOT(iccSettingsChanged(ICCSettingsContainer,ICCSettingsContainer))); } LoadingCache::~LoadingCache() { delete d->watch; delete d; m_instance = 0; } DImg* LoadingCache::retrieveImage(const QString& cacheKey) const { return d->imageCache[cacheKey]; } -bool LoadingCache::putImage(const QString& cacheKey, DImg* img, const QString& filePath) const +bool LoadingCache::putImage(const QString& cacheKey, const DImg& img, const QString& filePath) const { - bool successfulyInserted; - - int cost = img->numBytes(); - - successfulyInserted = d->imageCache.insert(cacheKey, img, cost); + int cost = img.numBytes(); + bool successfulyInserted = d->imageCache.insert(cacheKey, new DImg(img.copy()), cost); if (successfulyInserted && !filePath.isEmpty()) { d->mapImageFilePath(filePath, cacheKey); d->fileWatch()->addedImage(filePath); } return successfulyInserted; } void LoadingCache::removeImage(const QString& cacheKey) { d->imageCache.remove(cacheKey); } void LoadingCache::removeImages() { d->imageCache.clear(); } -bool LoadingCache::isCacheable(const DImg* img) const +bool LoadingCache::isCacheable(const DImg& img) const { // return whether image fits in cache - return (uint)d->imageCache.maxCost() >= img->numBytes(); + return (uint)d->imageCache.maxCost() >= img.numBytes(); } void LoadingCache::addLoadingProcess(LoadingProcess* const process) { d->loadingDict[process->cacheKey()] = process; } LoadingProcess* LoadingCache::retrieveLoadingProcess(const QString& cacheKey) const { return d->loadingDict.value(cacheKey); } void LoadingCache::removeLoadingProcess(LoadingProcess* const process) { d->loadingDict.remove(process->cacheKey()); } void LoadingCache::notifyNewLoadingProcess(LoadingProcess* const process, const LoadingDescription& description) { for (QMap::const_iterator it = d->loadingDict.constBegin() ; it != d->loadingDict.constEnd() ; ++it) { it.value()->notifyNewLoadingProcess(process, description); } } void LoadingCache::setCacheSize(int megabytes) { qCDebug(DIGIKAM_GENERAL_LOG) << "Allowing a cache size of" << megabytes << "MB"; d->imageCache.setMaxCost(megabytes * 1024 * 1024); } // --- Thumbnails ---- const QImage* LoadingCache::retrieveThumbnail(const QString& cacheKey) const { return d->thumbnailImageCache[cacheKey]; } const QPixmap* LoadingCache::retrieveThumbnailPixmap(const QString& cacheKey) const { return d->thumbnailPixmapCache[cacheKey]; } bool LoadingCache::hasThumbnailPixmap(const QString& cacheKey) const { return d->thumbnailPixmapCache.contains(cacheKey); } void LoadingCache::putThumbnail(const QString& cacheKey, const QImage& thumb, const QString& filePath) { int cost = thumb.byteCount(); if (d->thumbnailImageCache.insert(cacheKey, new QImage(thumb), cost)) { d->mapThumbnailFilePath(filePath, cacheKey); d->fileWatch()->addedThumbnail(filePath); } } void LoadingCache::putThumbnail(const QString& cacheKey, const QPixmap& thumb, const QString& filePath) { int cost = thumb.width() * thumb.height() * thumb.depth() / 8; if (d->thumbnailPixmapCache.insert(cacheKey, new QPixmap(thumb), cost)) { d->mapThumbnailFilePath(filePath, cacheKey); d->fileWatch()->addedThumbnail(filePath); } } void LoadingCache::removeThumbnail(const QString& cacheKey) { d->thumbnailImageCache.remove(cacheKey); d->thumbnailPixmapCache.remove(cacheKey); } void LoadingCache::removeThumbnails() { d->thumbnailImageCache.clear(); d->thumbnailPixmapCache.clear(); } void LoadingCache::setThumbnailCacheSize(int numberOfQImages, int numberOfQPixmaps) { d->thumbnailImageCache.setMaxCost(numberOfQImages * ThumbnailSize::maxThumbsSize() * ThumbnailSize::maxThumbsSize() * 4); d->thumbnailPixmapCache.setMaxCost(numberOfQPixmaps * ThumbnailSize::maxThumbsSize() * ThumbnailSize::maxThumbsSize() * QPixmap::defaultDepth() / 8); } void LoadingCache::setFileWatch(LoadingCacheFileWatch* const watch) { delete d->watch; d->watch = watch; d->watch->m_cache = this; } QStringList LoadingCache::imageFilePathsInCache() const { d->cleanUpImageFilePathHash(); return d->imageFilePathHash.uniqueKeys(); } QStringList LoadingCache::thumbnailFilePathsInCache() const { d->cleanUpThumbnailFilePathHash(); return d->thumbnailFilePathHash.uniqueKeys(); } void LoadingCache::notifyFileChanged(const QString& filePath, bool notify) { QList keys = d->imageFilePathHash.values(filePath); foreach (const QString& cacheKey, keys) { if (d->imageCache.remove(cacheKey) && notify) { emit fileChanged(filePath, cacheKey); } } keys = d->thumbnailFilePathHash.values(filePath); foreach (const QString& cacheKey, keys) { bool removedImage = d->thumbnailImageCache.remove(cacheKey); bool removedPixmap = d->thumbnailPixmapCache.remove(cacheKey); if ((removedImage || removedPixmap) && notify) { emit fileChanged(filePath, cacheKey); } } if (notify) { emit fileChanged(filePath); } } void LoadingCache::iccSettingsChanged(const ICCSettingsContainer& current, const ICCSettingsContainer& previous) { if (current.enableCM != previous.enableCM || current.useManagedPreviews != previous.useManagedPreviews || current.monitorProfile != previous.monitorProfile) { LoadingCache::CacheLock lock(this); removeImages(); removeThumbnails(); } } //--------------------------------------------------------------------------------------------------- LoadingCacheFileWatch::~LoadingCacheFileWatch() { if (m_cache) { LoadingCache::CacheLock lock(m_cache); if (m_cache->d->watch == this) { m_cache->d->watch = 0; } } } void LoadingCacheFileWatch::notifyFileChanged(const QString& filePath) { if (m_cache) { LoadingCache::CacheLock lock(m_cache); m_cache->notifyFileChanged(filePath); } } void LoadingCacheFileWatch::addedImage(const QString&) { // default: do nothing } void LoadingCacheFileWatch::addedThumbnail(const QString&) { // default: do nothing } //--------------------------------------------------------------------------------------------------- ClassicLoadingCacheFileWatch::ClassicLoadingCacheFileWatch() { if (thread() != QCoreApplication::instance()->thread()) { moveToThread(QCoreApplication::instance()->thread()); } m_watch = new QFileSystemWatcher; connect(m_watch, SIGNAL(fileChanged(QString)), this, SLOT(slotFileDirty(QString))); // Make sure the signal gets here directly from the event loop. // If putImage is called from the main thread, with CacheLock, // a deadlock would result (mutex is not recursive) connect(this, SIGNAL(signalUpdateDirWatch()), this, SLOT(slotUpdateDirWatch()), Qt::QueuedConnection); } ClassicLoadingCacheFileWatch::~ClassicLoadingCacheFileWatch() { delete m_watch; } void ClassicLoadingCacheFileWatch::addedImage(const QString& filePath) { Q_UNUSED(filePath) // schedule update of file watch // QFileSystemWatch can only be accessed from main thread! emit signalUpdateDirWatch(); } void ClassicLoadingCacheFileWatch::addedThumbnail(const QString& filePath) { Q_UNUSED(filePath); // ignore, we do not watch thumbnails } void ClassicLoadingCacheFileWatch::slotFileDirty(const QString& path) { // Signal comes from main thread qCDebug(DIGIKAM_GENERAL_LOG) << "LoadingCache slotFileDirty " << path; // This method acquires a lock itself notifyFileChanged(path); // No need for locking here, we are in main thread m_watch->removePath(path); m_watchedFiles.remove(path); } void ClassicLoadingCacheFileWatch::slotUpdateDirWatch() { // Event comes from main thread, we need to lock ourselves. LoadingCache::CacheLock lock(m_cache); // get a list of files in cache that need watch QSet toBeAdded; QSet toBeRemoved = m_watchedFiles; QList filePaths = m_cache->imageFilePathsInCache(); foreach (const QString& m_watchPath, filePaths) { if (!m_watchPath.isEmpty()) { if (!m_watchedFiles.contains(m_watchPath)) { toBeAdded.insert(m_watchPath); } toBeRemoved.remove(m_watchPath); } } foreach (const QString& watchedItem, toBeRemoved) { //qCDebug(DIGIKAM_GENERAL_LOG) << "removing watch for " << *it; m_watch->removePath(watchedItem); m_watchedFiles.remove(watchedItem); } foreach (const QString& watchedItem, toBeAdded) { //qCDebug(DIGIKAM_GENERAL_LOG) << "adding watch for " << *it; m_watch->addPath(watchedItem); m_watchedFiles.insert(watchedItem); } } //--------------------------------------------------------------------------------------------------- LoadingCache::CacheLock::CacheLock(LoadingCache* cache) : m_cache(cache) { m_cache->d->mutex.lock(); } LoadingCache::CacheLock::~CacheLock() { m_cache->d->mutex.unlock(); } void LoadingCache::CacheLock::wakeAll() { // obviously the mutex is locked when this function is called m_cache->d->condVar.wakeAll(); } void LoadingCache::CacheLock::timedWait() { // same as above, the mutex is certainly locked m_cache->d->condVar.wait(&m_cache->d->mutex, 1000); } } // namespace Digikam diff --git a/core/libs/threadimageio/fileio/loadingcache.h b/core/libs/threadimageio/fileio/loadingcache.h index e25e4be654..5bb798f990 100644 --- a/core/libs/threadimageio/fileio/loadingcache.h +++ b/core/libs/threadimageio/fileio/loadingcache.h @@ -1,313 +1,313 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2006-01-11 * Description : shared image loading and caching * * Copyright (C) 2005-2011 by Marcel Wiesweg * * 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. * * ============================================================ */ #ifndef DIGIKAM_LOADING_CACHE_H #define DIGIKAM_LOADING_CACHE_H // Qt includes #include #include #include // Local includes #include "dimg.h" #include "loadsavethread.h" #include "digikam_export.h" namespace Digikam { class ICCSettingsContainer; class LoadingProcessListener { public: virtual ~LoadingProcessListener() {}; virtual bool querySendNotifyEvent() const = 0; virtual void setResult(const LoadingDescription& loadingDescription, const DImg& img) = 0; virtual LoadSaveNotifier* loadSaveNotifier() const = 0; virtual LoadSaveThread::AccessMode accessMode() = 0; }; // -------------------------------------------------------------------------------------------------------------- class LoadingProcess { public: virtual ~LoadingProcess() {}; virtual bool completed() const volatile = 0; virtual QString filePath() const = 0; virtual QString cacheKey() const = 0; virtual void addListener(LoadingProcessListener* const listener) = 0; virtual void removeListener(LoadingProcessListener* const listener) = 0; virtual void notifyNewLoadingProcess(LoadingProcess* const process, const LoadingDescription& description) = 0; }; // -------------------------------------------------------------------------------------------------------------- class DIGIKAM_EXPORT LoadingCacheFileWatch { public: virtual ~LoadingCacheFileWatch(); /// Called by the thread when a new entry is added to the cache virtual void addedImage(const QString& filePath); virtual void addedThumbnail(const QString& filePath); protected: /** * Convenience method. * Call this to tell the cache to remove stored images for filePath from the cache. * Calling this method is fast, you do not need to check if the file is contained in the cache. * Do not hold the CacheLock when calling this method. */ void notifyFileChanged(const QString& filePath); protected: friend class LoadingCache; class LoadingCache* m_cache; }; // -------------------------------------------------------------------------------------------------------------- class DIGIKAM_EXPORT ClassicLoadingCacheFileWatch : public QObject, public LoadingCacheFileWatch { Q_OBJECT /** Reference implementation */ public: ClassicLoadingCacheFileWatch(); ~ClassicLoadingCacheFileWatch(); virtual void addedImage(const QString& filePath); virtual void addedThumbnail(const QString& filePath); private Q_SLOTS: void slotFileDirty(const QString& path); void slotUpdateDirWatch(); Q_SIGNALS: void signalUpdateDirWatch(); protected: QFileSystemWatcher* m_watch; QSet m_watchedFiles; }; // -------------------------------------------------------------------------------------------------------------- class DIGIKAM_EXPORT LoadingCache : public QObject { Q_OBJECT public: static LoadingCache* cache(); static void cleanUp(); virtual ~LoadingCache(); /// !! All methods of LoadingCache shall only be called when a CacheLock is held !! class DIGIKAM_EXPORT CacheLock { public: explicit CacheLock(LoadingCache* cache); ~CacheLock(); void wakeAll(); void timedWait(); private: LoadingCache* m_cache; }; /** * Retrieves an image for the given string from the cache, * or 0 if no image is found. */ DImg* retrieveImage(const QString& cacheKey) const; /// Returns whether the given DImg fits in the cache. - bool isCacheable(const DImg* img) const; + bool isCacheable(const DImg& img) const; /** Put image into for given string into the cache. * Returns true if image has been put in the cache, false otherwise. * Ownership of the DImg instance is passed to the cache. * When it cannot be put in the cache it is deleted. * The third parameter specifies a file path that will be watched. * If this file changes, the object will be removed from the cache. */ - bool putImage(const QString& cacheKey, DImg* img, const QString& filePath) const; + bool putImage(const QString& cacheKey, const DImg& img, const QString& filePath) const; /** * Remove entries for the given cacheKey from the cache */ void removeImage(const QString& cacheKey); /** * Remove all entries from the cache */ void removeImages(); // ------- Loading process management ----------------------------------- /** * Find the loading process for given cacheKey, or 0 if not found */ LoadingProcess* retrieveLoadingProcess(const QString& cacheKey) const; /** * Add a loading process to the list. Only one loading process * for the same cache key is registered at a time. */ void addLoadingProcess(LoadingProcess* process); /** * Remove loading process for given cache key */ void removeLoadingProcess(LoadingProcess* process); /** * Notify all currently registered loading processes */ void notifyNewLoadingProcess(LoadingProcess* process, const LoadingDescription& description); /** * Sets the cache size in megabytes. * The thumbnail cache is not affected and setThumbnailCacheSize takes the maximum number. */ void setCacheSize(int megabytes); // ------- Thumbnail cache ----------------------------------- /// The LoadingCache support both the caching of QImage and QPixmap objects. /// QPixmaps can only be accessed from the main thread, so the tasks cannot access this cache. /** * Retrieves a thumbnail for the given filePath from the thumbnail cache, * or a 0 if the thumbnail is not found. */ const QImage* retrieveThumbnail(const QString& cacheKey) const; const QPixmap* retrieveThumbnailPixmap(const QString& cacheKey) const; bool hasThumbnailPixmap(const QString& cacheKey) const; /** * Puts a thumbnail into the thumbnail cache. */ void putThumbnail(const QString& cacheKey, const QImage& thumb, const QString& filePath); void putThumbnail(const QString& cacheKey, const QPixmap& thumb, const QString& filePath); /** * Remove the thumbnail for the given file path from the thumbnail cache */ void removeThumbnail(const QString& cacheKey); /** * Remove all thumbnails */ void removeThumbnails(); /** * Sets the size of the thumbnail cache * @param numberOfQImages The maximum number of thumbnails of max possible size in QImage format that will be cached. If the size of the images is smaller, a larger number will be cached. * @param numberOfQPixmaps The maximum number of thumbnails of max possible size in QPixmap format that will be cached. If the size of the images is smaller, a larger number will be cached. * Note: The main cache is unaffected by this method, * and setCacheSize takes megabytes as parameter. * Note: A good caching strategy will be to set one of the numbers to 0 * Default values: (0, 100) */ void setThumbnailCacheSize(int numberOfQImages, int numberOfQPixmaps); // ------- File Watch Management ----------------------------------- /** * Sets a LoadingCacheFileWatch to watch the files contained in this cache. * Ownership of this object is transferred to the cache. */ void setFileWatch(LoadingCacheFileWatch* const watch); /** * Returns a list of all possible file paths in cache. */ QStringList imageFilePathsInCache() const; QStringList thumbnailFilePathsInCache() const; /** * Remove all entries from cache that were loaded from filePath. * Emits relevant signals if notify = true. */ void notifyFileChanged(const QString& filePath, bool notify = true); Q_SIGNALS: /** * This signal is emitted when the cache is notified that a file was changed. * There is no information in this signal if the file was ever contained in the cache. * The signal may be emitted under CacheLock. Strongly consider a queued connection. */ void fileChanged(const QString& filePath); /** * This signal is emitted when the cache is notified that a file was changed, * and the given cache key was removed from the cache. * The signal may be emitted under CacheLock. Strongly consider a queued connection. */ void fileChanged(const QString& filePath, const QString& cacheKey); private Q_SLOTS: void iccSettingsChanged(const ICCSettingsContainer& current, const ICCSettingsContainer& previous); private: LoadingCache(); friend class LoadingCacheFileWatch; friend class CacheLock; private: static LoadingCache* m_instance; class Private; Private* const d; }; } // namespace Digikam #endif // DIGIKAM_LOADING_CACHE_H diff --git a/core/libs/threadimageio/fileio/loadingcacheinterface.cpp b/core/libs/threadimageio/fileio/loadingcacheinterface.cpp index 94d36a30e1..a56f8e2b32 100644 --- a/core/libs/threadimageio/fileio/loadingcacheinterface.cpp +++ b/core/libs/threadimageio/fileio/loadingcacheinterface.cpp @@ -1,94 +1,92 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2006-02-06 * Description : shared image loading and caching * * Copyright (C) 2005-2011 by Marcel Wiesweg * * 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. * * ============================================================ */ #include "loadingcacheinterface.h" // Local includes #include "loadingcache.h" namespace Digikam { void LoadingCacheInterface::initialize() { LoadingCache::cache(); } void LoadingCacheInterface::cleanUp() { LoadingCache::cleanUp(); } void LoadingCacheInterface::fileChanged(const QString& filePath, bool notify) { LoadingCache* cache = LoadingCache::cache(); LoadingCache::CacheLock lock(cache); cache->notifyFileChanged(filePath, notify); } void LoadingCacheInterface::connectToSignalFileChanged(QObject* object, const char* slot) { LoadingCache* cache = LoadingCache::cache(); QObject::connect(cache, SIGNAL(fileChanged(QString)), object, slot, Qt::QueuedConnection); // make it a queued connection because the signal is emitted when the CacheLock is held! } void LoadingCacheInterface::cleanCache() { LoadingCache* cache = LoadingCache::cache(); LoadingCache::CacheLock lock(cache); cache->removeImages(); } void LoadingCacheInterface::cleanThumbnailCache() { LoadingCache* cache = LoadingCache::cache(); LoadingCache::CacheLock lock(cache); cache->removeThumbnails(); } void LoadingCacheInterface::putImage(const QString& filePath, const DImg& img) { LoadingCache* cache = LoadingCache::cache(); LoadingCache::CacheLock lock(cache); - if (cache->isCacheable(&img)) + if (cache->isCacheable(img)) { - DImg* copy = new DImg(img); - copy->detach(); - cache->putImage(filePath, copy, filePath); + cache->putImage(filePath, img, filePath); } } void LoadingCacheInterface::setCacheOptions(int cacheSize) { LoadingCache* cache = LoadingCache::cache(); LoadingCache::CacheLock lock(cache); cache->setCacheSize(cacheSize); } } // namespace Digikam diff --git a/core/libs/threadimageio/fileio/loadsavetask.cpp b/core/libs/threadimageio/fileio/loadsavetask.cpp index 8794ed849d..bc5169bc24 100644 --- a/core/libs/threadimageio/fileio/loadsavetask.cpp +++ b/core/libs/threadimageio/fileio/loadsavetask.cpp @@ -1,490 +1,490 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2005-12-17 * Description : image file IO threaded interface. * * Copyright (C) 2005-2013 by Marcel Wiesweg * Copyright (C) 2005-2019 by Gilles Caulier * * 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. * * ============================================================ */ #include "loadsavetask.h" // Qt includes // Local includes #include "digikam_debug.h" #include "iccmanager.h" #include "icctransform.h" #include "loadsavethread.h" #include "managedloadsavethread.h" #include "sharedloadsavethread.h" #include "loadingcache.h" namespace Digikam { void LoadingTask::execute() { if (m_loadingTaskStatus == LoadingTaskStatusStopping) { return; } DImg img(m_loadingDescription.filePath, this, m_loadingDescription.rawDecodingSettings); m_thread->taskHasFinished(); m_thread->imageLoaded(m_loadingDescription, img); } LoadingTask::TaskType LoadingTask::type() { return TaskTypeLoading; } void LoadingTask::progressInfo(DImg* const img, float progress) { Q_UNUSED(img); if (m_loadingTaskStatus == LoadingTaskStatusLoading) { if (m_thread && m_thread->querySendNotifyEvent()) { m_thread->loadingProgress(m_loadingDescription, progress); } } } bool LoadingTask::continueQuery(DImg* const img) { Q_UNUSED(img); return m_loadingTaskStatus != LoadingTaskStatusStopping; } void LoadingTask::setStatus(LoadingTaskStatus status) { m_loadingTaskStatus = status; } //--------------------------------------------------------------------------------------------------- SharedLoadingTask::SharedLoadingTask(LoadSaveThread* const thread, const LoadingDescription& description, LoadSaveThread::AccessMode mode, LoadingTaskStatus loadingTaskStatus) : LoadingTask(thread, description, loadingTaskStatus), m_completed(false), m_accessMode(mode), m_usedProcess(0), m_resultLoadingDescription(description) { if (m_accessMode == LoadSaveThread::AccessModeRead && needsPostProcessing()) { m_accessMode = LoadSaveThread::AccessModeReadWrite; } } void SharedLoadingTask::execute() { if (m_loadingTaskStatus == LoadingTaskStatusStopping) { return; } // send StartedLoadingEvent from each single Task, not via LoadingProcess list m_thread->imageStartedLoading(m_loadingDescription); LoadingCache* cache = LoadingCache::cache(); { LoadingCache::CacheLock lock(cache); // find possible cached images DImg* cachedImg = 0; QStringList lookupKeys = m_loadingDescription.lookupCacheKeys(); foreach (const QString& key, lookupKeys) { if ((cachedImg = cache->retrieveImage(key))) { if (m_loadingDescription.needCheckRawDecoding()) { if (cachedImg->rawDecodingSettings() == m_loadingDescription.rawDecodingSettings) { break; } else { cachedImg = 0; } } else { break; } } } if (cachedImg) { // image is found in image cache, loading is successful m_img = *cachedImg; } else { // find possible running loading process m_usedProcess = 0; for (QStringList::const_iterator it = lookupKeys.constBegin() ; it != lookupKeys.constEnd() ; ++it) { if ((m_usedProcess = cache->retrieveLoadingProcess(*it))) { break; } } 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_loadingTaskStatus != LoadingTaskStatusStopping && m_usedProcess && !m_usedProcess->completed()) { lock.timedWait(); } // remove listener from process if (m_usedProcess) { 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; // m_img is now set to the result } 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); } } } if (m_img.isNull()) { // load image m_img = DImg(m_loadingDescription.filePath, this, m_loadingDescription.rawDecodingSettings); LoadingCache::CacheLock lock(cache); // put (valid) image into cache of loaded images if (!m_img.isNull()) { - cache->putImage(m_loadingDescription.cacheKey(), new DImg(m_img.copy()), + cache->putImage(m_loadingDescription.cacheKey(), m_img, m_loadingDescription.filePath); } // remove this from the list of loading processes in cache cache->removeLoadingProcess(this); //qCDebug(DIGIKAM_GENERAL_LOG) << "SharedLoadingTask " << this << ": image loaded, " << img.isNull(); // indicate that loading has finished so that listeners can stop waiting m_completed = true; // dispatch image to all listeners, including this for (int i = 0 ; i < m_listeners.count() ; ++i) { LoadingProcessListener* const l = m_listeners[i]; if (l->accessMode() == LoadSaveThread::AccessModeReadWrite) { // If a listener requested ReadWrite access, it gets a deep copy. // DImg is explicitly shared. l->setResult(m_loadingDescription, m_img.copy()); } else { l->setResult(m_loadingDescription, m_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; } // following the golden rule to avoid deadlocks, do this when CacheLock is not held if (!m_img.isNull() && continueQuery(&m_img)) { if (accessMode() == LoadSaveThread::AccessModeReadWrite) { m_img.detach(); } postProcess(); } else if (continueQuery(&m_img)) { qCWarning(DIGIKAM_GENERAL_LOG) << "Cannot load image for" << m_loadingDescription.filePath; } else { m_img = DImg(); } m_thread->taskHasFinished(); m_thread->imageLoaded(m_loadingDescription, m_img); } void SharedLoadingTask::setResult(const LoadingDescription& loadingDescription, const DImg& img) { // this is called from another process's execute while this task is waiting on m_usedProcess. // Note that loadingDescription need not equal m_loadingDescription (may be superior) m_resultLoadingDescription = loadingDescription; // these are taken from our own description m_resultLoadingDescription.postProcessingParameters = m_loadingDescription.postProcessingParameters; m_img = img; } bool SharedLoadingTask::needsPostProcessing() const { return m_loadingDescription.postProcessingParameters.needsProcessing(); } void SharedLoadingTask::postProcess() { // to receive progress info again. Should be safe now, we are alone. addListener(this); // ---- Color management ---- // switch (m_loadingDescription.postProcessingParameters.colorManagement) { case LoadingDescription::NoColorConversion: break; case LoadingDescription::ApplyTransform: { IccTransform trans = m_loadingDescription.postProcessingParameters.transform(); trans.apply(m_img, this); m_img.setIccProfile(trans.outputProfile()); break; } case LoadingDescription::ConvertForEditor: { IccManager manager(m_img); manager.transformDefault(); break; } case LoadingDescription::ConvertToSRGB: { IccManager manager(m_img); manager.transformToSRGB(); break; } case LoadingDescription::ConvertForDisplay: { IccManager manager(m_img); manager.transformForDisplay(m_loadingDescription.postProcessingParameters.profile()); break; } case LoadingDescription::ConvertForOutput: { IccManager manager(m_img); manager.transformForOutput(m_loadingDescription.postProcessingParameters.profile()); break; } } removeListener(this); } void SharedLoadingTask::progressInfo(DImg* const img, float progress) { Q_UNUSED(img); if (m_loadingTaskStatus == LoadingTaskStatusLoading) { LoadingCache* cache = LoadingCache::cache(); LoadingCache::CacheLock lock(cache); for (int i = 0 ; i < m_listeners.size() ; ++i) { LoadingProcessListener* const l = m_listeners[i]; LoadSaveNotifier* const notifier = l->loadSaveNotifier(); if (notifier && l->querySendNotifyEvent()) { notifier->loadingProgress(m_loadingDescription, progress); } } } } bool SharedLoadingTask::continueQuery(DImg* const img) { Q_UNUSED(img); // If this is called, the thread is currently loading an image. // In shared loading, we cannot stop until all listeners have been removed as well return (m_loadingTaskStatus != LoadingTaskStatusStopping) || (m_listeners.count() != 0); } void SharedLoadingTask::setStatus(LoadingTaskStatus status) { m_loadingTaskStatus = status; if (m_loadingTaskStatus == LoadingTaskStatusStopping) { LoadingCache* cache = LoadingCache::cache(); LoadingCache::CacheLock lock(cache); // check for m_usedProcess, to avoid race condition that it has finished before if (m_usedProcess) { // remove this from list of listeners - check in continueQuery() of active thread m_usedProcess->removeListener(this); // set m_usedProcess to 0, signalling that we have detached already m_usedProcess = 0; // wake all listeners - particularly this - from waiting on cache condvar lock.wakeAll(); } } } bool SharedLoadingTask::completed() const volatile { return m_completed; } QString SharedLoadingTask::filePath() const { return m_loadingDescription.filePath; } QString SharedLoadingTask::cacheKey() const { return m_loadingDescription.cacheKey(); } void SharedLoadingTask::addListener(LoadingProcessListener* const listener) { m_listeners << listener; } void SharedLoadingTask::removeListener(LoadingProcessListener* const listener) { m_listeners.removeAll(listener); } void SharedLoadingTask::notifyNewLoadingProcess(LoadingProcess* const process, const LoadingDescription& description) { // Ok, we are notified that another task has been started in another thread. // We are of course only interested if the task loads the same file, // and we are right now loading a reduced version, and the other task is loading the full version. // In this case, we notify our own thread (a signal to the API user is emitted) of this. // The fact that we are receiving the method call shows that this task is registered with the LoadingCache, // somewhere in between the calls to addLoadingProcess(this) and removeLoadingProcess(this) above. if (process != this && m_loadingDescription.isReducedVersion() && m_loadingDescription.equalsIgnoreReducedVersion(description) && !description.isReducedVersion()) { for (int i = 0 ; i < m_listeners.size() ; ++i) { m_listeners[i]->loadSaveNotifier()->moreCompleteLoadingAvailable(m_loadingDescription, description); } } } bool SharedLoadingTask::querySendNotifyEvent() const { return m_thread && m_thread->querySendNotifyEvent(); } LoadSaveNotifier* SharedLoadingTask::loadSaveNotifier() const { return m_thread; } LoadSaveThread::AccessMode SharedLoadingTask::accessMode() { return m_accessMode; } //--------------------------------------------------------------------------------------------------- void SavingTask::execute() { m_thread->imageStartedSaving(m_filePath); bool success = m_img.save(m_filePath, m_format, this); m_thread->taskHasFinished(); m_thread->imageSaved(m_filePath, success); } LoadingTask::TaskType SavingTask::type() { return TaskTypeSaving; } void SavingTask::progressInfo(DImg* const img, float progress) { Q_UNUSED(img); if (m_thread->querySendNotifyEvent()) { m_thread->savingProgress(m_filePath, progress); } } bool SavingTask::continueQuery(DImg* const img) { Q_UNUSED(img); return (m_savingTaskStatus != SavingTaskStatusStopping); } void SavingTask::setStatus(SavingTaskStatus status) { m_savingTaskStatus = status; } } //namespace Digikam diff --git a/core/libs/threadimageio/preview/previewtask.cpp b/core/libs/threadimageio/preview/previewtask.cpp index edfbcd975e..6fa70ccfd8 100644 --- a/core/libs/threadimageio/preview/previewtask.cpp +++ b/core/libs/threadimageio/preview/previewtask.cpp @@ -1,552 +1,552 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2006-12-26 * Description : Multithreaded loader for previews * * Copyright (C) 2006-2011 by Marcel Wiesweg * Copyright (C) 2006-2019 by Gilles Caulier * * 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. * * ============================================================ */ #include "previewtask.h" // C ANSI includes #include // Qt includes #include #include #include // Local includes #include "drawdecoder.h" #include "digikam_debug.h" #include "dmetadata.h" #include "jpegutils.h" #include "metaenginesettings.h" #include "previewloadthread.h" namespace Digikam { void PreviewLoadingTask::execute() { if (m_loadingTaskStatus == LoadingTaskStatusStopping) { return; } // Check if preview is in cache first. LoadingCache* const cache = LoadingCache::cache(); { LoadingCache::CacheLock lock(cache); // find possible cached images DImg* cachedImg = 0; QStringList 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.prepend(m_loadingDescription.cacheKey()); foreach (const QString& key, lookupKeys) { if ((cachedImg = cache->retrieveImage(key))) { if (m_loadingDescription.needCheckRawDecoding()) { if (cachedImg->rawDecodingSettings() == m_loadingDescription.rawDecodingSettings) { break; } else { cachedImg = 0; } } else { break; } } } if (cachedImg) { // image is found in image cache, loading is successful m_img = *cachedImg; } else { // find possible running loading process m_usedProcess = 0; for (QStringList::const_iterator it = lookupKeys.constBegin() ; it != lookupKeys.constEnd() ; ++it) { if ((m_usedProcess = cache->retrieveLoadingProcess(*it))) { break; } } 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_loadingTaskStatus != LoadingTaskStatusStopping && m_usedProcess && !m_usedProcess->completed()) { lock.timedWait(); } // remove listener from process if (m_usedProcess) { 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; // m_img is now set to the result } 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); } } } if (m_img.isNull()) { // Preview is not in cache, we will load image from file. DImg::FORMAT format = DImg::fileFormat(m_loadingDescription.filePath); m_fromRawEmbeddedPreview = false; if (format == DImg::RAW) { MetaEnginePreviews previews(m_loadingDescription.filePath); // Check original image size using Exiv2. QSize originalSize = previews.originalSize(); // If not valid, get original size from LibRaw if (!originalSize.isValid()) { DRawInfo container; if (DRawDecoder::rawFileIdentify(container, m_loadingDescription.filePath)) { originalSize = container.imageSize; } } switch (m_loadingDescription.previewParameters.previewSettings.quality) { case PreviewSettings::FastPreview: case PreviewSettings::FastButLargePreview: { // Size calculations int sizeLimit = -1; int bestSize = qMax(originalSize.width(), originalSize.height()); // for RAWs, the alternative is the half preview, so best size is already originalSize / 2 bestSize /= 2; if (m_loadingDescription.previewParameters.previewSettings.quality == PreviewSettings::FastButLargePreview) { sizeLimit = qMin(m_loadingDescription.previewParameters.size, bestSize); } if (loadExiv2Preview(previews, sizeLimit)) { break; } if (loadLibRawPreview(sizeLimit)) { break; } loadHalfSizeRaw(); break; } case PreviewSettings::HighQualityPreview: { switch (m_loadingDescription.previewParameters.previewSettings.rawLoading) { case PreviewSettings::RawPreviewAutomatic: { // If we find a preview that is larger than half size (which is what we get from half-size original data), we take it int acceptableSize = qMax(lround(originalSize.width() * 0.48), lround(originalSize.height() * 0.48)); if (loadExiv2Preview(previews, acceptableSize)) { break; } if (loadLibRawPreview(acceptableSize)) { break; } loadHalfSizeRaw(); break; } case PreviewSettings::RawPreviewFromEmbeddedPreview: { if (loadExiv2Preview(previews)) { break; } if (loadLibRawPreview()) { break; } loadHalfSizeRaw(); break; } case PreviewSettings::RawPreviewFromRawHalfSize: { loadHalfSizeRaw(); break; } } } } // So far, everything loaded QImage. Convert to DImg. convertQImageToDImg(); } else // Non-RAW images { bool isFast = (m_loadingDescription.previewParameters.previewSettings.quality == PreviewSettings::FastPreview); switch (m_loadingDescription.previewParameters.previewSettings.quality) { case PreviewSettings::FastPreview: case PreviewSettings::FastButLargePreview: { if (isFast && loadImagePreview(m_loadingDescription.previewParameters.size)) { convertQImageToDImg(); break; } if (continueQuery(&m_img)) { // Set a hint to try to load a JPEG or PGF with the fast scale-before-decoding method if (isFast) { m_img.setAttribute(QLatin1String("scaledLoadingSize"), m_loadingDescription.previewParameters.size); } m_img.load(m_loadingDescription.filePath, this, m_loadingDescription.rawDecodingSettings); } break; } case PreviewSettings::HighQualityPreview: { if (continueQuery(&m_img)) { m_img.load(m_loadingDescription.filePath, this, m_loadingDescription.rawDecodingSettings); } break; } } } LoadingCache::CacheLock lock(cache); // Put valid image into cache of loaded images if (!m_img.isNull()) { - cache->putImage(m_loadingDescription.cacheKey(), new DImg(m_img.copy()), + cache->putImage(m_loadingDescription.cacheKey(), m_img, m_loadingDescription.filePath); } // remove this from the list of loading processes in cache cache->removeLoadingProcess(this); // indicate that loading has finished so that listeners can stop waiting m_completed = true; // dispatch image to all listeners, including this for (int i = 0 ; i < m_listeners.count() ; ++i) { LoadingProcessListener* const l = m_listeners[i]; LoadSaveNotifier* const notifier = l->loadSaveNotifier(); if (l->accessMode() == LoadSaveThread::AccessModeReadWrite) { // If a listener requested ReadWrite access, it gets a deep copy. // DImg is explicitly shared. l->setResult(m_loadingDescription, m_img.copy()); } else { l->setResult(m_loadingDescription, m_img); } if (notifier) { notifier->imageLoaded(m_loadingDescription, m_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; } // following the golden rule to avoid deadlocks, do this when CacheLock is not held if (!m_img.isNull() && continueQuery(&m_img)) { // The image from the cache may or may not be rotated and post processed. // exifRotate() and postProcess() will detect if work is needed. // We check before to find out if we need to provide a deep copy const bool needExifRotate = MetaEngineSettings::instance()->settings().exifRotate && !LoadSaveThread::wasExifRotated(m_img); const bool needImageScale = needToScale(); const bool needPostProcess = needsPostProcessing(); const bool needzoomOrgSize = !m_loadingDescription.previewParameters.previewSettings.zoomOrgSize; const bool needConvertToEightBit = m_loadingDescription.previewParameters.previewSettings.convertToEightBit; if (accessMode() == LoadSaveThread::AccessModeReadWrite && (needExifRotate || needImageScale || needPostProcess || needzoomOrgSize || needConvertToEightBit)) { m_img.detach(); } if (needImageScale) { QSize scaledSize = m_img.size(); scaledSize.scale(m_loadingDescription.previewParameters.size, m_loadingDescription.previewParameters.size, Qt::KeepAspectRatio); m_img = m_img.smoothScale(scaledSize.width(), scaledSize.height()); } // Set originalSize attribute to the m_img size, to disable zoom to the original image size if (needzoomOrgSize) { m_img.setAttribute(QLatin1String("originalSize"), m_img.size()); } if (needConvertToEightBit) { m_img.convertToEightBit(); } if (needExifRotate) { LoadSaveThread::exifRotate(m_img, m_loadingDescription.filePath); } if (needPostProcess) { postProcess(); } } else if (continueQuery(&m_img)) { qCWarning(DIGIKAM_GENERAL_LOG) << "Cannot extract preview for" << m_loadingDescription.filePath; } else { m_img = DImg(); } if (m_thread) { m_thread->taskHasFinished(); m_thread->imageLoaded(m_loadingDescription, m_img); } } bool PreviewLoadingTask::needToScale() { switch (m_loadingDescription.previewParameters.previewSettings.quality) { case PreviewSettings::FastPreview: if (m_loadingDescription.previewParameters.size > 0) { int maxSize = qMax(m_img.width(), m_img.height()); int acceptableUpperSize = lround(1.25 * (double)m_loadingDescription.previewParameters.size); return (maxSize >= acceptableUpperSize); } break; case PreviewSettings::FastButLargePreview: case PreviewSettings::HighQualityPreview: break; } return false; } // -- Exif/IPTC preview extraction using Exiv2 -------------------------------------------------------- bool PreviewLoadingTask::loadExiv2Preview(MetaEnginePreviews& previews, int sizeLimit) { if (previews.isEmpty() || !continueQuery(&m_img)) { return false; } if (sizeLimit == -1 || qMax(previews.width(), previews.height()) >= sizeLimit) { m_qimage = previews.image(); if (!m_qimage.isNull()) { m_fromRawEmbeddedPreview = true; return true; } } return false; } bool PreviewLoadingTask::loadLibRawPreview(int sizeLimit) { if (!continueQuery(&m_img)) { return false; } QImage rawPreview; DRawDecoder::loadEmbeddedPreview(rawPreview, m_loadingDescription.filePath); if (!rawPreview.isNull() && (sizeLimit == -1 || qMax(rawPreview.width(), rawPreview.height()) >= sizeLimit)) { m_qimage = rawPreview; m_fromRawEmbeddedPreview = true; return true; } return false; } bool PreviewLoadingTask::loadHalfSizeRaw() { if (!continueQuery(&m_img)) { return false; } DRawDecoder::loadHalfPreview(m_qimage, m_loadingDescription.filePath); return (!m_qimage.isNull()); } void PreviewLoadingTask::convertQImageToDImg() { if (!continueQuery(&m_img)) { return; } // convert from QImage m_img = DImg(m_qimage); DImg::FORMAT format = DImg::fileFormat(m_loadingDescription.filePath); m_img.setAttribute(QLatin1String("detectedFileFormat"), format); m_img.setAttribute(QLatin1String("originalFilePath"), m_loadingDescription.filePath); DMetadata metadata(m_loadingDescription.filePath); m_img.setAttribute(QLatin1String("originalSize"), metadata.getPixelSize()); m_img.setMetadata(metadata.data()); // mark as embedded preview (for Exif rotation) if (m_fromRawEmbeddedPreview) { m_img.setAttribute(QLatin1String("fromRawEmbeddedPreview"), true); // If we loaded the embedded preview, the Exif of the RAW indicates // the color space of the preview (see bug 195950 for NEF files) m_img.setIccProfile(metadata.getIccProfile()); } // free memory m_qimage = QImage(); } bool PreviewLoadingTask::loadImagePreview(int sizeLimit) { DMetadata metadata(m_loadingDescription.filePath); QImage previewImage; if (metadata.getItemPreview(previewImage)) { if (sizeLimit == -1 || qMax(previewImage.width(), previewImage.height()) > sizeLimit) { m_qimage = previewImage; return true; } } return false; } } // namespace Digikam