diff --git a/core/libs/threadimageio/fileio/loadsavetask.cpp b/core/libs/threadimageio/fileio/loadsavetask.cpp index 0e0da570c3..ef603fb9b2 100644 --- a/core/libs/threadimageio/fileio/loadsavetask.cpp +++ b/core/libs/threadimageio/fileio/loadsavetask.cpp @@ -1,501 +1,493 @@ /* ============================================================ * * This file is a part of digiKam project * https://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(nullptr), - m_resultLoadingDescription(description) + m_usedProcess(nullptr) { 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* const cache = LoadingCache::cache(); { LoadingCache::CacheLock lock(cache); // find possible cached images DImg* cachedImg = nullptr; 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 = nullptr; } } else { break; } } } if (cachedImg) { // image is found in image cache, loading is successful m_img = DImg(*cachedImg); } else { // find possible running loading process m_usedProcess = nullptr; 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 = nullptr; // 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 (continueQuery(&m_img) && m_img.isNull()) { // load image m_img = DImg(m_loadingDescription.filePath, this, m_loadingDescription.rawDecodingSettings); if (continueQuery(&m_img)) { + LoadingCache::CacheLock lock(cache); + + // put valid image into cache of loaded images + if (!m_img.isNull()) { - LoadingCache::CacheLock lock(cache); + cache->putImage(m_loadingDescription.cacheKey(), m_img, + m_loadingDescription.filePath); + } - // put (valid) image into cache of loaded images - if (!m_img.isNull()) - { - cache->putImage(m_loadingDescription.cacheKey(), m_img, - m_loadingDescription.filePath); - } + // remove this from the list of loading processes in cache + cache->removeLoadingProcess(this); - // 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) { - LoadingCache::CacheLock lock(cache); + LoadingProcessListener* const l = m_listeners.at(i); - // 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) + if (l->accessMode() == LoadSaveThread::AccessModeReadWrite) { - LoadingProcessListener* const l = m_listeners.at(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); - } + // If a listener requested ReadWrite access, it gets a deep copy. + // DImg is explicitly shared. + l->setResult(m_loadingDescription, m_img.copy()); } - - // 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) + else { - lock.timedWait(); + l->setResult(m_loadingDescription, m_img); } + } - // set to 0, as checked in setStatus - m_usedProcess = nullptr; + // 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 = nullptr; } } // following the golden rule to avoid deadlocks, do this when CacheLock is not held if (continueQuery(&m_img) && !m_img.isNull()) { 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; + LoadingDescription tempDescription = loadingDescription; + // these are taken from our own description - m_resultLoadingDescription.postProcessingParameters = m_loadingDescription.postProcessingParameters; - m_img = img; + tempDescription.postProcessingParameters = m_loadingDescription.postProcessingParameters; + m_loadingDescription = tempDescription; + m_img = img; } bool SharedLoadingTask::needsPostProcessing() const { return m_loadingDescription.postProcessingParameters.needsProcessing(); } void SharedLoadingTask::postProcess() { // ---- Color management ---- // switch (m_loadingDescription.postProcessingParameters.colorManagement) { case LoadingDescription::NoColorConversion: break; case LoadingDescription::ApplyTransform: { IccTransform trans = m_loadingDescription.postProcessingParameters.transform(); trans.apply(m_img); 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; } } } void SharedLoadingTask::progressInfo(DImg* const img, float progress) { Q_UNUSED(img); if (m_loadingTaskStatus == LoadingTaskStatusLoading) { LoadingCache* const cache = LoadingCache::cache(); LoadingCache::CacheLock lock(cache); for (int i = 0 ; i < m_listeners.size() ; ++i) { LoadingProcessListener* const l = m_listeners.at(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); + return ((m_loadingTaskStatus != LoadingTaskStatusStopping) || (m_listeners.count() != 0)); } void SharedLoadingTask::setStatus(LoadingTaskStatus status) { - if (status == LoadingTaskStatusStopping) + m_loadingTaskStatus = status; + + if (m_loadingTaskStatus == LoadingTaskStatusStopping) { LoadingCache* const cache = LoadingCache::cache(); LoadingCache::CacheLock lock(cache); - m_loadingTaskStatus = status; - // check for m_usedProcess, to avoid race condition that it has finished before if (m_usedProcess) { // remove this from the list of loading processes in cache cache->removeLoadingProcess(this); // 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 = nullptr; // wake all listeners - particularly this - from waiting on cache condvar lock.wakeAll(); } } - else - { - m_loadingTaskStatus = status; - } } bool SharedLoadingTask::completed() const { 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 != static_cast(this) && m_loadingDescription.isReducedVersion() && m_loadingDescription.equalsIgnoreReducedVersion(description) && !description.isReducedVersion()) { for (int i = 0 ; i < m_listeners.size() ; ++i) { m_listeners.at(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/fileio/loadsavetask.h b/core/libs/threadimageio/fileio/loadsavetask.h index eb5bd014c1..3d2a606b43 100644 --- a/core/libs/threadimageio/fileio/loadsavetask.h +++ b/core/libs/threadimageio/fileio/loadsavetask.h @@ -1,238 +1,237 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2006-01-20 * 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. * * ============================================================ */ #ifndef DIGIKAM_LOAD_SAVE_TASK_H #define DIGIKAM_LOAD_SAVE_TASK_H // Qt includes #include #include // Local includes #include "dimg.h" #include "dimgloaderobserver.h" #include "loadingdescription.h" #include "loadingcache.h" namespace Digikam { class LoadSaveThread; class LoadSaveTask { public: enum TaskType { TaskTypeLoading, TaskTypeSaving }; public: explicit LoadSaveTask(LoadSaveThread* const thread) : m_thread(thread) { } virtual ~LoadSaveTask() { } virtual void execute() = 0; virtual TaskType type() = 0; virtual void progressInfo(DImg* const img, float progress) = 0; virtual bool continueQuery(DImg* const img) = 0; protected: LoadSaveThread* m_thread; }; //--------------------------------------------------------------------------------------------------- class LoadingTask : public LoadSaveTask, public DImgLoaderObserver { public: enum LoadingTaskStatus { LoadingTaskStatusLoading, LoadingTaskStatusPreloading, LoadingTaskStatusStopping }; public: explicit LoadingTask(LoadSaveThread* const thread, const LoadingDescription& description, LoadingTaskStatus loadingTaskStatus = LoadingTaskStatusLoading) : LoadSaveTask(thread), m_loadingDescription(description), m_loadingTaskStatus(loadingTaskStatus) { } LoadingTaskStatus status() const { return m_loadingTaskStatus; } QString filePath() const { return m_loadingDescription.filePath; } const LoadingDescription& loadingDescription() const { return m_loadingDescription; } // LoadSaveTask virtual void execute() override; virtual TaskType type() override; // DImgLoaderObserver virtual void progressInfo(DImg* const img, float progress) override; virtual bool continueQuery(DImg* const img) override; virtual void setStatus(LoadingTaskStatus status); protected: LoadingDescription m_loadingDescription; volatile LoadingTaskStatus m_loadingTaskStatus; }; //--------------------------------------------------------------------------------------------------- class SharedLoadingTask : public LoadingTask, public LoadingProcess, public LoadingProcessListener { public: explicit SharedLoadingTask(LoadSaveThread* const thread, const LoadingDescription& description, LoadSaveThread::AccessMode mode = LoadSaveThread::AccessModeReadWrite, LoadingTaskStatus loadingTaskStatus = LoadingTaskStatusLoading); virtual void execute() override; virtual void progressInfo(DImg* const img, float progress) override; virtual bool continueQuery(DImg* const img) override; virtual void setStatus(LoadingTaskStatus status) override; bool needsPostProcessing() const; virtual void postProcess(); // LoadingProcess virtual bool completed() const override; virtual QString filePath() const override; virtual QString cacheKey() const override; virtual void addListener(LoadingProcessListener* const listener) override; virtual void removeListener(LoadingProcessListener* const listener) override; virtual void notifyNewLoadingProcess(LoadingProcess* const process, const LoadingDescription& description) override; // LoadingProcessListener virtual bool querySendNotifyEvent() const override; virtual void setResult(const LoadingDescription& loadingDescription, const DImg& img) override; virtual LoadSaveNotifier* loadSaveNotifier() const override; virtual LoadSaveThread::AccessMode accessMode() override; DImg img() { return m_img; } protected: volatile bool m_completed; LoadSaveThread::AccessMode m_accessMode; - LoadingProcess* volatile m_usedProcess; + LoadingProcess* m_usedProcess; QList m_listeners; DImg m_img; - LoadingDescription m_resultLoadingDescription; }; //--------------------------------------------------------------------------------------------------- class SavingTask : public LoadSaveTask, public DImgLoaderObserver { public: enum SavingTaskStatus { SavingTaskStatusSaving, SavingTaskStatusStopping }; public: explicit SavingTask(LoadSaveThread* const thread, const DImg& img, const QString& filePath, const QString& format) : LoadSaveTask(thread), m_filePath(filePath), m_format(format), m_img(img), m_savingTaskStatus(SavingTaskStatusSaving) { } SavingTaskStatus status() const { return m_savingTaskStatus; } QString filePath() const { return m_filePath; } public: void execute() override; TaskType type() override; void progressInfo(DImg* const img, float progress) override; bool continueQuery(DImg* const img) override; void setStatus(SavingTaskStatus status); private: QString m_filePath; QString m_format; DImg m_img; SavingTaskStatus m_savingTaskStatus; }; } // namespace Digikam #endif // DIGIKAM_LOAD_SAVE_TASK_H diff --git a/core/libs/threadimageio/preview/previewtask.cpp b/core/libs/threadimageio/preview/previewtask.cpp index b9fb980d18..8dec6dd205 100644 --- a/core/libs/threadimageio/preview/previewtask.cpp +++ b/core/libs/threadimageio/preview/previewtask.cpp @@ -1,581 +1,573 @@ /* ============================================================ * * This file is a part of digiKam project * https://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" // Qt includes #include #include #include // Local includes #include "dimgloader.h" #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 = nullptr; 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 = nullptr; } } else { break; } } } if (cachedImg) { // image is found in image cache, loading is successful m_img = DImg(*cachedImg); } else { // find possible running loading process m_usedProcess = nullptr; 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 = nullptr; // 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 (continueQuery(&m_img) && 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 { qCDebug(DIGIKAM_GENERAL_LOG) << "Try to get preview from" << m_loadingDescription.filePath; qCDebug(DIGIKAM_GENERAL_LOG) << "Preview quality: " << m_loadingDescription.previewParameters.previewSettings.quality; 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; } // 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: { m_img.load(m_loadingDescription.filePath, this, m_loadingDescription.rawDecodingSettings); break; } } } if (continueQuery(&m_img)) { if (!m_img.isNull() && MetaEngineSettings::instance()->settings().exifRotate) { m_img.exifRotate(m_loadingDescription.filePath); } - { - LoadingCache::CacheLock lock(cache); - - // Put valid image into cache of loaded images - if (!m_img.isNull()) - { - cache->putImage(m_loadingDescription.cacheKey(), m_img, - m_loadingDescription.filePath); - } + LoadingCache::CacheLock lock(cache); - // remove this from the list of loading processes in cache - cache->removeLoadingProcess(this); + // put valid image into cache of loaded images + if (!m_img.isNull()) + { + cache->putImage(m_loadingDescription.cacheKey(), m_img, + m_loadingDescription.filePath); } - { - LoadingCache::CacheLock lock(cache); + // 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; + // indicate that loading has finished so that listeners can stop waiting + m_completed = true; - // dispatch image to all listeners, including this + // dispatch image to all listeners, including this + for (int i = 0 ; i < m_listeners.count() ; ++i) + { + LoadingProcessListener* const l = m_listeners.at(i); - for (int i = 0 ; i < m_listeners.count() ; ++i) + if (l->accessMode() == LoadSaveThread::AccessModeReadWrite) { - LoadingProcessListener* const l = m_listeners.at(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); - } + // If a listener requested ReadWrite access, it gets a deep copy. + // DImg is explicitly shared. + l->setResult(m_loadingDescription, m_img.copy()); } - - // 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) + else { - lock.timedWait(); + l->setResult(m_loadingDescription, m_img); } + } - // set to 0, as checked in setStatus - m_usedProcess = nullptr; + // 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 = nullptr; } } // following the golden rule to avoid deadlocks, do this when CacheLock is not held if (continueQuery(&m_img) && !m_img.isNull()) { // 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 && !m_img.wasExifRotated(); const bool needImageScale = needToScale(); const bool needPostProcess = needsPostProcessing(); const bool needConvertToEightBit = m_loadingDescription.previewParameters.previewSettings.convertToEightBit; if (accessMode() == LoadSaveThread::AccessModeReadWrite || needExifRotate || needImageScale || needPostProcess || 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()); } if (needConvertToEightBit) { m_img.convertToEightBit(); } if (needExifRotate) { m_img.exifRotate(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); QSize orgSize = metadata.getPixelSize(); if (format == DImg::RAW && LoadSaveThread::infoProvider()) { orgSize = LoadSaveThread::infoProvider()->dimensionsHint(m_loadingDescription.filePath); } // Set the ratio of width and height of the // original size to the same ratio of the loaded image. // Because a half RAW preview was probably already rotated. if (format == DImg::RAW && !m_fromRawEmbeddedPreview) { if ((m_img.width() < m_img.height() && orgSize.width() > orgSize.height()) || (m_img.width() > m_img.height() && orgSize.width() < orgSize.height())) { orgSize.transpose(); } } m_img.setAttribute(QLatin1String("originalSize"), orgSize); 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; } } qDebug(DIGIKAM_GENERAL_LOG) << "Try to load DImg preview from:" << m_loadingDescription.filePath; DImg img; DImgLoader::LoadFlags loadFlags = DImgLoader::LoadItemInfo | DImgLoader::LoadMetadata | DImgLoader::LoadICCData | DImgLoader::LoadPreview; if (img.load(m_loadingDescription.filePath, loadFlags, this)) { if (sizeLimit == -1 || qMax(img.width(), img.height()) > (uint)sizeLimit) { m_qimage = img.copyQImage(); return true; } } return false; } } // namespace Digikam diff --git a/core/libs/threadimageio/thumb/thumbnailtask.cpp b/core/libs/threadimageio/thumb/thumbnailtask.cpp index 8993df1583..5cd883494b 100644 --- a/core/libs/threadimageio/thumb/thumbnailtask.cpp +++ b/core/libs/threadimageio/thumb/thumbnailtask.cpp @@ -1,290 +1,285 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2007-06-05 * Description : Multithreaded loader for thumbnails * * Copyright (C) 2006-2008 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 "thumbnailtask.h" // C++ includes #include // Qt includes #include #include #include #include // Local includes #include "drawdecoder.h" #include "digikam_debug.h" #include "dmetadata.h" #include "iccmanager.h" #include "jpegutils.h" #include "metaenginesettings.h" #include "thumbnailloadthread.h" #include "thumbnailcreator.h" namespace Digikam { ThumbnailLoadingTask::ThumbnailLoadingTask(LoadSaveThread* const thread, const LoadingDescription& description) : SharedLoadingTask(thread, description, LoadSaveThread::AccessModeRead, LoadingTaskStatusLoading) { // Thread must be a ThumbnailLoadThread, crashes otherwise. // Not a clean but pragmatic solution. ThumbnailLoadThread* const thumbThread = static_cast(thread); m_creator = thumbThread->thumbnailCreator(); } void ThumbnailLoadingTask::execute() { if (m_loadingTaskStatus == LoadingTaskStatusStopping) { return; } if (m_loadingDescription.previewParameters.onlyPregenerate()) { setupCreator(); switch (m_loadingDescription.previewParameters.type) { case LoadingDescription::PreviewParameters::Thumbnail: m_creator->pregenerate(m_loadingDescription.thumbnailIdentifier()); break; case LoadingDescription::PreviewParameters::DetailThumbnail: m_creator->pregenerateDetail(m_loadingDescription.thumbnailIdentifier(), m_loadingDescription.previewParameters.extraParameter.toRect()); break; default: break; } m_thread->taskHasFinished(); // do not emit any signal return; } LoadingCache* const cache = LoadingCache::cache(); { LoadingCache::CacheLock lock(cache); // find possible cached images const QImage* const cachedImage = cache->retrieveThumbnail(m_loadingDescription.cacheKey()); if (cachedImage) { m_qimage = QImage(*cachedImage); } if (m_qimage.isNull()) { // find possible running loading process // 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_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 = nullptr; } 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 (continueQuery(nullptr) && m_qimage.isNull()) { // Load or create thumbnail setupCreator(); switch (m_loadingDescription.previewParameters.type) { case LoadingDescription::PreviewParameters::Thumbnail: m_qimage = m_creator->load(m_loadingDescription.thumbnailIdentifier()); break; case LoadingDescription::PreviewParameters::DetailThumbnail: m_qimage = m_creator->loadDetail(m_loadingDescription.thumbnailIdentifier(), m_loadingDescription.previewParameters.extraParameter.toRect()); break; default: break; } if (continueQuery(nullptr)) { + LoadingCache::CacheLock lock(cache); + + // put valid image into cache of loaded images + if (!m_qimage.isNull()) { - LoadingCache::CacheLock lock(cache); + cache->putThumbnail(m_loadingDescription.cacheKey(), m_qimage, + m_loadingDescription.filePath); + } - // put (valid) image into cache of loaded images - if (!m_qimage.isNull()) - { - cache->putThumbnail(m_loadingDescription.cacheKey(), m_qimage, - m_loadingDescription.filePath); - } + // remove this from the list of loading processes in cache + cache->removeLoadingProcess(this); - // 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 + for (int i = 0 ; i < m_listeners.count() ; ++i) { - LoadingCache::CacheLock lock(cache); + ThumbnailLoadingTask* const task = dynamic_cast(m_listeners.at(i)); - // 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) + if (task) { - ThumbnailLoadingTask* const task = dynamic_cast(m_listeners.at(i)); - - if (task) - { - task->setThumbResult(m_loadingDescription, m_qimage); - } + task->setThumbResult(m_loadingDescription, m_qimage); } + } - // remove myself from list of listeners - removeListener(this); + // remove myself from list of listeners + removeListener(this); - // wake all listeners waiting on cache condVar, so that they remove themselves - lock.wakeAll(); + // 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 = nullptr; + // wait until all listeners have removed themselves + while (m_listeners.count() != 0) + { + lock.timedWait(); } + + // set to 0, as checked in setStatus + m_usedProcess = nullptr; } } // following the golden rule to avoid deadlocks, do this when CacheLock is not held if (continueQuery(nullptr) && !m_qimage.isNull()) { postProcess(); } else { m_qimage = QImage(); } m_thread->taskHasFinished(); m_thread->thumbnailLoaded(m_loadingDescription, m_qimage); } void ThumbnailLoadingTask::setupCreator() { m_creator->setThumbnailSize(m_loadingDescription.previewParameters.size); m_creator->setExifRotate(MetaEngineSettings::instance()->settings().exifRotate); m_creator->setLoadingProperties(this, m_loadingDescription.rawDecodingSettings); } void ThumbnailLoadingTask::setThumbResult(const LoadingDescription& loadingDescription, const QImage& qimage) { // 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; + LoadingDescription tempDescription = loadingDescription; // these are taken from our own description - m_resultLoadingDescription.postProcessingParameters = m_loadingDescription.postProcessingParameters; - m_qimage = qimage; + tempDescription.postProcessingParameters = m_loadingDescription.postProcessingParameters; + m_loadingDescription = tempDescription; + m_qimage = qimage; } void ThumbnailLoadingTask::postProcess() { m_loadingDescription.postProcessingParameters.profile().description(); switch (m_loadingDescription.postProcessingParameters.colorManagement) { case LoadingDescription::NoColorConversion: { break; } case LoadingDescription::ConvertToSRGB: { // Thumbnails are stored in sRGB break; } case LoadingDescription::ConvertForDisplay: { IccManager::transformForDisplay(m_qimage, m_loadingDescription.postProcessingParameters.profile()); break; } default: { qCDebug(DIGIKAM_GENERAL_LOG) << "Unsupported postprocessing parameter for thumbnail loading:" << m_loadingDescription.postProcessingParameters.colorManagement; break; } } } } // namespace Digikam