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