diff --git a/core/libs/threadimageio/fileio/loadsavetask.cpp b/core/libs/threadimageio/fileio/loadsavetask.cpp index ddaa34fd5d..ee5d53ca0a 100644 --- a/core/libs/threadimageio/fileio/loadsavetask.cpp +++ b/core/libs/threadimageio/fileio/loadsavetask.cpp @@ -1,486 +1,486 @@ /* ============================================================ * * 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-2020 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(float progress) { if (m_loadingTaskStatus == LoadingTaskStatusLoading) { if (m_thread && m_thread->querySendNotifyEvent()) { m_thread->loadingProgress(m_loadingDescription, progress); } } } bool LoadingTask::continueQuery() { 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) { 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); } // set to 0, as checked in setStatus m_usedProcess = nullptr; // wake up the process which is waiting until all listeners have removed themselves lock.wakeAll(); // 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.isNull()) { // load image m_img = DImg(m_loadingDescription.filePath, this, m_loadingDescription.rawDecodingSettings); if (continueQuery()) { 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); } // 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.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); } } // remove myself from list of listeners removeListener(this); + // indicate that loading has finished so that listeners can stop waiting + m_completed = true; + // 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.isNull()) { if (accessMode() == LoadSaveThread::AccessModeReadWrite) { m_img.detach(); } postProcess(); } else if (continueQuery()) { 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) LoadingDescription tempDescription = loadingDescription; // these are taken from our own description 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(float progress) { 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() { // 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* const 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 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(); } } } 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() const { 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(float progress) { if (m_thread->querySendNotifyEvent()) { m_thread->savingProgress(m_filePath, progress); } } bool SavingTask::continueQuery() { 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 639375b421..6b152eb740 100644 --- a/core/libs/threadimageio/preview/previewtask.cpp +++ b/core/libs/threadimageio/preview/previewtask.cpp @@ -1,584 +1,584 @@ /* ============================================================ * * 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-2020 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); } // set to 0, as checked in setStatus m_usedProcess = nullptr; // wake up the process which is waiting until all listeners have removed themselves lock.wakeAll(); // 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.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()) { 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); } // 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.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); } } // remove myself from list of listeners removeListener(this); + // indicate that loading has finished so that listeners can stop waiting + m_completed = true; + // 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.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()) { 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()) { 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()) { 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()) { return false; } DRawDecoder::loadHalfPreview(m_qimage, m_loadingDescription.filePath); return (!m_qimage.isNull()); } void PreviewLoadingTask::convertQImageToDImg() { if (!continueQuery()) { 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); } // In case we don't get the original size from the metadata. if (orgSize.isNull()) { orgSize = QSize(m_img.width(), m_img.height()); } // 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 115d3ab0d6..499cef49f8 100644 --- a/core/libs/threadimageio/thumb/thumbnailtask.cpp +++ b/core/libs/threadimageio/thumb/thumbnailtask.cpp @@ -1,285 +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-2020 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); } // set to 0, as checked in setStatus m_usedProcess = nullptr; // wake up the process which is waiting until all listeners have removed themselves lock.wakeAll(); } 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_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()) { LoadingCache::CacheLock lock(cache); // 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); - // 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) { ThumbnailLoadingTask* const task = dynamic_cast(m_listeners.at(i)); if (task) { task->setThumbResult(m_loadingDescription, m_qimage); } } // remove myself from list of listeners removeListener(this); + // indicate that loading has finished so that listeners can stop waiting + m_completed = true; + // 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_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) LoadingDescription tempDescription = loadingDescription; // these are taken from our own description 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