File indexing completed on 2025-12-07 04:08:03

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2005-12-17
0007  * Description : image file IO threaded interface.
0008  *
0009  * SPDX-FileCopyrightText: 2005-2013 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
0010  * SPDX-FileCopyrightText: 2005-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0011  *
0012  * SPDX-License-Identifier: GPL-2.0-or-later
0013  *
0014  * ============================================================ */
0015 
0016 #include "loadsavetask.h"
0017 
0018 // Local includes
0019 
0020 #include "digikam_debug.h"
0021 #include "iccmanager.h"
0022 #include "icctransform.h"
0023 #include "loadsavethread.h"
0024 #include "managedloadsavethread.h"
0025 #include "sharedloadsavethread.h"
0026 
0027 namespace Digikam
0028 {
0029 
0030 LoadSaveTask::LoadSaveTask(LoadSaveThread* const thread)
0031     : m_thread(thread)
0032 {
0033 }
0034 
0035 LoadSaveTask::~LoadSaveTask()
0036 {
0037 }
0038 
0039 // --------------------------------------------------------------------------------------------
0040 
0041 LoadingTask::LoadingTask(LoadSaveThread* const thread,
0042                          const LoadingDescription& description,
0043                          LoadingTaskStatus loadingTaskStatus)
0044     : LoadSaveTask        (thread),
0045       m_loadingDescription(description),
0046       m_loadingTaskStatus (loadingTaskStatus)
0047 {
0048 }
0049 
0050 LoadingTask::~LoadingTask()
0051 {
0052 }
0053 
0054 LoadingTask::LoadingTaskStatus LoadingTask::status() const
0055 {
0056     return m_loadingTaskStatus;
0057 }
0058 
0059 QString LoadingTask::filePath() const
0060 {
0061     return m_loadingDescription.filePath;
0062 }
0063 
0064 const LoadingDescription& LoadingTask::loadingDescription() const
0065 {
0066     return m_loadingDescription;
0067 }
0068 
0069 void LoadingTask::execute()
0070 {
0071     if (m_loadingTaskStatus == LoadingTaskStatusStopping)
0072     {
0073         m_thread->taskHasFinished();
0074 
0075         return;
0076     }
0077 
0078     DImg img(m_loadingDescription.filePath, this, m_loadingDescription.rawDecodingSettings);
0079     m_thread->taskHasFinished();
0080     m_thread->imageLoaded(m_loadingDescription, img);
0081 }
0082 
0083 LoadingTask::TaskType LoadingTask::type()
0084 {
0085     return TaskTypeLoading;
0086 }
0087 
0088 void LoadingTask::progressInfo(float progress)
0089 {
0090     if (m_loadingTaskStatus == LoadingTaskStatusLoading)
0091     {
0092         if (m_thread && m_thread->querySendNotifyEvent())
0093         {
0094             m_thread->loadingProgress(m_loadingDescription, progress);
0095         }
0096     }
0097 }
0098 
0099 bool LoadingTask::continueQuery()
0100 {
0101     return (m_loadingTaskStatus != LoadingTaskStatusStopping);
0102 }
0103 
0104 void LoadingTask::setStatus(LoadingTaskStatus status)
0105 {
0106     m_loadingTaskStatus = status;
0107 }
0108 
0109 //---------------------------------------------------------------------------------------------------
0110 
0111 SharedLoadingTask::SharedLoadingTask(LoadSaveThread* const thread, const LoadingDescription& description,
0112                                      LoadSaveThread::AccessMode mode, LoadingTaskStatus loadingTaskStatus)
0113     : LoadingTask  (thread, description, loadingTaskStatus),
0114       m_completed  (false),
0115       m_accessMode (mode)
0116 {
0117     if (m_accessMode == LoadSaveThread::AccessModeRead && needsPostProcessing())
0118     {
0119         m_accessMode = LoadSaveThread::AccessModeReadWrite;
0120     }
0121 }
0122 
0123 void SharedLoadingTask::execute()
0124 {
0125     if (m_loadingTaskStatus == LoadingTaskStatusStopping)
0126     {
0127         m_thread->taskHasFinished();
0128 
0129         return;
0130     }
0131 
0132     // send StartedLoadingEvent from each single Task, not via LoadingProcess list
0133 
0134     m_thread->imageStartedLoading(m_loadingDescription);
0135 
0136     LoadingCache* const cache = LoadingCache::cache();
0137     {
0138         LoadingCache::CacheLock lock(cache);
0139 
0140         // find possible cached images
0141 
0142         DImg* cachedImg        = nullptr;
0143         QStringList lookupKeys = m_loadingDescription.lookupCacheKeys();
0144 
0145         Q_FOREACH (const QString& key, lookupKeys)
0146         {
0147             if ((cachedImg = cache->retrieveImage(key)))
0148             {
0149                 if (m_loadingDescription.needCheckRawDecoding())
0150                 {
0151                     if (cachedImg->rawDecodingSettings() == m_loadingDescription.rawDecodingSettings)
0152                     {
0153                         break;
0154                     }
0155                     else
0156                     {
0157                         cachedImg = nullptr;
0158                     }
0159                 }
0160                 else
0161                 {
0162                     break;
0163                 }
0164             }
0165         }
0166 
0167         if (cachedImg)
0168         {
0169             // image is found in image cache, loading is successful
0170 
0171             m_img = *cachedImg;
0172         }
0173         else
0174         {
0175             // find possible running loading process
0176 
0177             LoadingProcess* usedProcess = nullptr;
0178 
0179             for (QStringList::const_iterator it = lookupKeys.constBegin() ; it != lookupKeys.constEnd() ; ++it)
0180             {
0181                 if ((usedProcess = cache->retrieveLoadingProcess(*it)))
0182                 {
0183                     break;
0184                 }
0185             }
0186 
0187             if (usedProcess)
0188             {
0189                 // Other process is right now loading this image.
0190                 // Add this task to the list of listeners and
0191                 // attach this thread to the other thread, wait until loading
0192                 // has finished.
0193 
0194                 usedProcess->addListener(this);
0195 
0196                 // break loop when either the loading has completed, or this task is being stopped
0197 
0198                 // cppcheck-suppress knownConditionTrueFalse
0199                 while ((m_loadingTaskStatus != LoadingTaskStatusStopping) && !usedProcess->completed())
0200                 {
0201                     lock.timedWait();
0202                 }
0203 
0204                 // remove listener from process
0205 
0206                 usedProcess->removeListener(this);
0207 
0208                 // wake up the process which is waiting until all listeners have removed themselves
0209 
0210                 lock.wakeAll();
0211 
0212                 // m_img is now set to the result
0213             }
0214         }
0215     }
0216 
0217     if (continueQuery() && m_img.isNull())
0218     {
0219         {
0220             LoadingCache::CacheLock lock(cache);
0221 
0222             // Neither in cache, nor currently loading in different thread.
0223             // Load it here and now, add this LoadingProcess to cache list.
0224 
0225             cache->addLoadingProcess(this);
0226 
0227             // Notify other processes that we are now loading this image.
0228             // They might be interested - see notifyNewLoadingProcess below
0229 
0230             cache->notifyNewLoadingProcess(this, m_loadingDescription);
0231         }
0232 
0233         // load image
0234 
0235         m_img = DImg(m_loadingDescription.filePath, this, m_loadingDescription.rawDecodingSettings);
0236 
0237         if (continueQuery() && !m_img.isNull())
0238         {
0239             postProcess();
0240         }
0241 
0242         {
0243             LoadingCache::CacheLock lock(cache);
0244 
0245             // remove this from the list of loading processes in cache
0246 
0247             cache->removeLoadingProcess(this);
0248 
0249             // put valid image into cache of loaded images
0250 
0251             if (continueQuery() && !m_img.isNull())
0252             {
0253                 cache->putImage(m_loadingDescription.cacheKey(), m_img,
0254                                 m_loadingDescription.filePath);
0255 
0256                 // dispatch image to all listeners
0257 
0258                 for (int i = 0 ; i < m_listeners.count() ; ++i)
0259                 {
0260                     LoadingProcessListener* const l = m_listeners.at(i);
0261 
0262                     if (l->accessMode() == LoadSaveThread::AccessModeReadWrite)
0263                     {
0264                         // If a listener requested ReadWrite access, it gets a deep copy.
0265                         // DImg is explicitly shared.
0266 
0267                         l->setResult(m_loadingDescription, m_img.copy());
0268                     }
0269                     else
0270                     {
0271                             l->setResult(m_loadingDescription, m_img);
0272                     }
0273                 }
0274             }
0275 
0276             // indicate that loading has finished so that listeners can stop waiting
0277 
0278             m_completed = true;
0279 
0280             // wake all listeners waiting on cache condVar, so that they remove themselves
0281 
0282             lock.wakeAll();
0283 
0284             // wait until all listeners have removed themselves
0285 
0286             while (m_listeners.count() != 0)
0287             {
0288                 lock.timedWait();
0289             }
0290         }
0291     }
0292 
0293     // following the golden rule to avoid deadlocks, do this when CacheLock is not held
0294 
0295     if      (continueQuery() && !m_img.isNull())
0296     {
0297         if (accessMode() == LoadSaveThread::AccessModeReadWrite)
0298         {
0299             m_img.detach();
0300         }
0301     }
0302     else if (continueQuery())
0303     {
0304         qCWarning(DIGIKAM_GENERAL_LOG) << "Cannot load image for" << m_loadingDescription.filePath;
0305     }
0306     else
0307     {
0308         m_img = DImg();
0309     }
0310 
0311     m_thread->taskHasFinished();
0312     m_thread->imageLoaded(m_loadingDescription, m_img);
0313 }
0314 
0315 void SharedLoadingTask::setResult(const LoadingDescription& loadingDescription, const DImg& img)
0316 {
0317     // This is called from another process's execute while this task is waiting on usedProcess.
0318     // Note that loadingDescription need not equal m_loadingDescription (may be superior)
0319 
0320     LoadingDescription tempDescription       = loadingDescription;
0321 
0322     // these are taken from our own description
0323 
0324     tempDescription.postProcessingParameters = m_loadingDescription.postProcessingParameters;
0325     m_loadingDescription                     = tempDescription;
0326     m_img                                    = img;
0327 }
0328 
0329 bool SharedLoadingTask::needsPostProcessing() const
0330 {
0331     return m_loadingDescription.postProcessingParameters.needsProcessing();
0332 }
0333 
0334 void SharedLoadingTask::postProcess()
0335 {
0336     // ---- Color management ---- //
0337 
0338     switch (m_loadingDescription.postProcessingParameters.colorManagement)
0339     {
0340         case LoadingDescription::NoColorConversion:
0341             break;
0342 
0343         case LoadingDescription::ApplyTransform:
0344         {
0345             IccTransform trans = m_loadingDescription.postProcessingParameters.transform();
0346             trans.apply(m_img);
0347             m_img.setIccProfile(trans.outputProfile());
0348             break;
0349         }
0350 
0351         case LoadingDescription::ConvertForEditor:
0352         {
0353             IccManager manager(m_img);
0354             manager.transformDefault();
0355             break;
0356         }
0357 
0358         case LoadingDescription::ConvertToSRGB:
0359         {
0360             IccManager manager(m_img);
0361             manager.transformToSRGB();
0362             break;
0363         }
0364 
0365         case LoadingDescription::ConvertForDisplay:
0366         {
0367             IccManager manager(m_img);
0368             manager.transformForDisplay(m_loadingDescription.postProcessingParameters.profile());
0369             break;
0370         }
0371 
0372         case LoadingDescription::ConvertForOutput:
0373         {
0374             IccManager manager(m_img);
0375             manager.transformForOutput(m_loadingDescription.postProcessingParameters.profile());
0376             break;
0377         }
0378     }
0379 }
0380 
0381 void SharedLoadingTask::progressInfo(float progress)
0382 {
0383     if (m_loadingTaskStatus == LoadingTaskStatusLoading)
0384     {
0385         LoadingCache* const cache = LoadingCache::cache();
0386         LoadingCache::CacheLock lock(cache);
0387 
0388         for (int i = 0 ; i < m_listeners.size() ; ++i)
0389         {
0390             LoadingProcessListener* const l  = m_listeners.at(i);
0391             LoadSaveNotifier* const notifier = l->loadSaveNotifier();
0392 
0393             if (notifier && l->querySendNotifyEvent())
0394             {
0395                 notifier->loadingProgress(m_loadingDescription, progress);
0396             }
0397         }
0398     }
0399 }
0400 
0401 bool SharedLoadingTask::completed() const
0402 {
0403     return m_completed;
0404 }
0405 
0406 QString SharedLoadingTask::cacheKey() const
0407 {
0408     return m_loadingDescription.cacheKey();
0409 }
0410 
0411 void SharedLoadingTask::addListener(LoadingProcessListener* const listener)
0412 {
0413     m_listeners << listener;
0414 }
0415 
0416 void SharedLoadingTask::removeListener(LoadingProcessListener* const listener)
0417 {
0418     m_listeners.removeOne(listener);
0419 }
0420 
0421 void SharedLoadingTask::notifyNewLoadingProcess(LoadingProcess* const process,
0422                                                 const LoadingDescription& description)
0423 {
0424     // Ok, we are notified that another task has been started in another thread.
0425     // We are of course only interested if the task loads the same file,
0426     // and we are right now loading a reduced version, and the other task is loading the full version.
0427     // In this case, we notify our own thread (a signal to the API user is emitted) of this.
0428     // The fact that we are receiving the method call shows that this task is registered with the LoadingCache,
0429     // somewhere in between the calls to addLoadingProcess(this) and removeLoadingProcess(this) above.
0430 
0431     if ((process != static_cast<LoadingProcess*>(this))              &&
0432         m_loadingDescription.isReducedVersion()                      &&
0433         m_loadingDescription.equalsIgnoreReducedVersion(description) &&
0434         !description.isReducedVersion())
0435     {
0436         for (int i = 0 ; i < m_listeners.size() ; ++i)
0437         {
0438             if (m_listeners.at(i)->loadSaveNotifier())
0439             {
0440                 m_listeners.at(i)->loadSaveNotifier()->
0441                     moreCompleteLoadingAvailable(m_loadingDescription, description);
0442             }
0443         }
0444     }
0445 }
0446 
0447 bool SharedLoadingTask::querySendNotifyEvent() const
0448 {
0449     return m_thread && m_thread->querySendNotifyEvent();
0450 }
0451 
0452 LoadSaveNotifier* SharedLoadingTask::loadSaveNotifier() const
0453 {
0454     return m_thread;
0455 }
0456 
0457 LoadSaveThread::AccessMode SharedLoadingTask::accessMode() const
0458 {
0459     return m_accessMode;
0460 }
0461 
0462 DImg SharedLoadingTask::img() const
0463 {
0464     return m_img;
0465 }
0466 
0467 //---------------------------------------------------------------------------------------------------
0468 
0469 SavingTask::SavingTask(LoadSaveThread* const thread,
0470                        const DImg& img,
0471                        const QString& filePath,
0472                        const QString& format)
0473     : LoadSaveTask(thread),
0474         m_filePath(filePath),
0475         m_format(format),
0476         m_img(img),
0477         m_savingTaskStatus(SavingTaskStatusSaving)
0478 {
0479 }
0480 
0481 SavingTask::SavingTaskStatus SavingTask::status() const
0482 {
0483     return m_savingTaskStatus;
0484 }
0485 
0486 QString SavingTask::filePath() const
0487 {
0488     return m_filePath;
0489 }
0490 
0491 void SavingTask::execute()
0492 {
0493     m_thread->imageStartedSaving(m_filePath);
0494     bool success = m_img.save(m_filePath, m_format, this);
0495     m_thread->taskHasFinished();
0496     m_thread->imageSaved(m_filePath, success);
0497 }
0498 
0499 LoadingTask::TaskType SavingTask::type()
0500 {
0501     return TaskTypeSaving;
0502 }
0503 
0504 void SavingTask::progressInfo(float progress)
0505 {
0506     if (m_thread->querySendNotifyEvent())
0507     {
0508         m_thread->savingProgress(m_filePath, progress);
0509     }
0510 }
0511 
0512 bool SavingTask::continueQuery()
0513 {
0514     return (m_savingTaskStatus != SavingTaskStatusStopping);
0515 }
0516 
0517 void SavingTask::setStatus(SavingTaskStatus status)
0518 {
0519     m_savingTaskStatus = status;
0520 }
0521 
0522 }   //namespace Digikam