File indexing completed on 2025-04-27 03:58:08

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-2011 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 "loadsavethread.h"
0017 
0018 // Local includes
0019 
0020 #include "metaengine_rotation.h"
0021 #include "dmetadata.h"
0022 #include "managedloadsavethread.h"
0023 #include "sharedloadsavethread.h"
0024 #include "loadsavetask.h"
0025 
0026 namespace Digikam
0027 {
0028 
0029 LoadSaveNotifier::LoadSaveNotifier()
0030 {
0031 }
0032 
0033 LoadSaveNotifier::~LoadSaveNotifier()
0034 {
0035 }
0036 
0037 // --------------------------------------------------------------------------------
0038 
0039 LoadSaveFileInfoProvider::LoadSaveFileInfoProvider()
0040 {
0041 }
0042 
0043 LoadSaveFileInfoProvider::~LoadSaveFileInfoProvider()
0044 {
0045 }
0046 
0047 // --------------------------------------------------------------------------------
0048 
0049 class Q_DECL_HIDDEN LoadSaveThread::Private
0050 {
0051 public:
0052 
0053     explicit Private()
0054       : running          (true),
0055         blockNotification(false),
0056         lastTask         (nullptr)
0057     {
0058     }
0059 
0060     bool                             running;
0061     bool                             blockNotification;
0062 
0063     QTime                            notificationTime;
0064 
0065     LoadSaveTask*                    lastTask;
0066 
0067     static LoadSaveFileInfoProvider* infoProvider;
0068 };
0069 
0070 LoadSaveFileInfoProvider* LoadSaveThread::Private::infoProvider = nullptr;
0071 
0072 //---------------------------------------------------------------------------------------------------
0073 
0074 LoadSaveThread::LoadSaveThread(QObject* const parent)
0075     : DynamicThread       (parent),
0076       m_currentTask       (nullptr),
0077       m_notificationPolicy(NotificationPolicyTimeLimited),
0078       d                   (new Private)
0079 {
0080 }
0081 
0082 LoadSaveThread::~LoadSaveThread()
0083 {
0084     shutDown();
0085     delete d;
0086 }
0087 
0088 void LoadSaveThread::setInfoProvider(LoadSaveFileInfoProvider* const infoProvider)
0089 {
0090     Private::infoProvider = infoProvider;
0091 }
0092 
0093 LoadSaveFileInfoProvider* LoadSaveThread::infoProvider()
0094 {
0095     return Private::infoProvider;
0096 }
0097 
0098 void LoadSaveThread::load(const LoadingDescription& description)
0099 {
0100     QMutexLocker lock(threadMutex());
0101     m_todo << new LoadingTask(this, description);
0102     start(lock);
0103 }
0104 
0105 void LoadSaveThread::save(const DImg& image, const QString& filePath, const QString& format)
0106 {
0107     QMutexLocker lock(threadMutex());
0108     m_todo << new SavingTask(this, image, filePath, format);
0109     start(lock);
0110 }
0111 
0112 void LoadSaveThread::run()
0113 {
0114     while (runningFlag())
0115     {
0116         {
0117             QMutexLocker lock(threadMutex());
0118 
0119             delete d->lastTask;
0120             d->lastTask   = nullptr;
0121             delete m_currentTask;
0122             m_currentTask = nullptr;
0123 
0124             if (!m_todo.isEmpty())
0125             {
0126                 m_currentTask = m_todo.takeFirst();
0127 
0128                 if (m_notificationPolicy == NotificationPolicyTimeLimited)
0129                 {
0130                     // set timing values so that first event is sent only
0131                     // after an initial time span.
0132 
0133                     d->notificationTime  = QTime::currentTime();
0134                     d->blockNotification = true;
0135                 }
0136             }
0137             else
0138             {
0139                 stop(lock);
0140             }
0141         }
0142 
0143         if (m_currentTask)
0144         {
0145             m_currentTask->execute();
0146         }
0147     }
0148 }
0149 
0150 void LoadSaveThread::taskHasFinished()
0151 {
0152     // This function is called by the tasks _before_ they send their _final_ message.
0153     // This is to guarantee the user of the API that at least the final message
0154     // is sent after load() has been called.
0155     // We set m_currentTask to 0 here. If a new task is appended, base classes usually check
0156     // that m_currentTask is not currently loading the same task.
0157     // Now it might happen that m_currentTask has already emitted its final signal,
0158     // but the new task is rejected afterwards when m_currentTask is still the task
0159     // that has actually already finished (execute() in the loop above is of course not under mutex).
0160     // So we set m_currentTask to 0 immediately before the final message is emitted,
0161     // so that anyone who finds this task running as m_current task will get a message.
0162 
0163     QMutexLocker lock(threadMutex());
0164     d->lastTask   = m_currentTask;
0165     m_currentTask = nullptr;
0166 }
0167 
0168 void LoadSaveThread::imageStartedLoading(const LoadingDescription& loadingDescription)
0169 {
0170     notificationReceived();
0171     Q_EMIT signalImageStartedLoading(loadingDescription);
0172 }
0173 
0174 void LoadSaveThread::loadingProgress(const LoadingDescription& loadingDescription, float progress)
0175 {
0176     notificationReceived();
0177     Q_EMIT signalLoadingProgress(loadingDescription, progress);
0178 }
0179 
0180 void LoadSaveThread::imageLoaded(const LoadingDescription& loadingDescription, const DImg& img)
0181 {
0182     notificationReceived();
0183     Q_EMIT signalImageLoaded(loadingDescription, img);
0184 }
0185 
0186 void LoadSaveThread::moreCompleteLoadingAvailable(const LoadingDescription& oldLoadingDescription,
0187                                                   const LoadingDescription& newLoadingDescription)
0188 {
0189     notificationReceived();
0190     Q_EMIT signalMoreCompleteLoadingAvailable(oldLoadingDescription, newLoadingDescription);
0191 }
0192 
0193 void LoadSaveThread::imageStartedSaving(const QString& filePath)
0194 {
0195     notificationReceived();
0196     Q_EMIT signalImageStartedSaving(filePath);
0197 }
0198 
0199 void LoadSaveThread::savingProgress(const QString& filePath, float progress)
0200 {
0201     notificationReceived();
0202     Q_EMIT signalSavingProgress(filePath, progress);
0203 }
0204 
0205 void LoadSaveThread::imageSaved(const QString& filePath, bool success)
0206 {
0207     notificationReceived();
0208     Q_EMIT signalImageSaved(filePath, success);
0209 }
0210 
0211 void LoadSaveThread::thumbnailLoaded(const LoadingDescription& loadingDescription, const QImage& img)
0212 {
0213     notificationReceived();
0214     Q_EMIT signalThumbnailLoaded(loadingDescription, img);
0215 }
0216 
0217 void LoadSaveThread::notificationReceived()
0218 {
0219     switch (m_notificationPolicy)
0220     {
0221         case NotificationPolicyDirect:
0222         {
0223             d->blockNotification = false;
0224             break;
0225         }
0226 
0227         case NotificationPolicyTimeLimited:
0228         {
0229             break;
0230         }
0231     }
0232 }
0233 
0234 void LoadSaveThread::setNotificationPolicy(NotificationPolicy notificationPolicy)
0235 {
0236     m_notificationPolicy = notificationPolicy;
0237     d->blockNotification = false;
0238 }
0239 
0240 bool LoadSaveThread::querySendNotifyEvent() const
0241 {
0242     // This function is called from the thread to ask for permission to send a notify event.
0243 
0244     switch (m_notificationPolicy)
0245     {
0246         case NotificationPolicyDirect:
0247         {
0248             // Note that m_blockNotification is not protected by a mutex. However, if there is a
0249             // race condition, the worst case is that one event is not sent, which is no problem.
0250 
0251             if (d->blockNotification)
0252             {
0253                 return false;
0254             }
0255             else
0256             {
0257                 d->blockNotification = true;
0258 
0259                 return true;
0260             }
0261 
0262             break;
0263         }
0264 
0265         case NotificationPolicyTimeLimited:
0266         {
0267             // Current default time value: 100 millisecs.
0268 
0269             if (d->blockNotification)
0270             {
0271                 d->blockNotification = d->notificationTime.msecsTo(QTime::currentTime()) < 100;
0272             }
0273 
0274             if (d->blockNotification)
0275             {
0276                 return false;
0277             }
0278             else
0279             {
0280                 d->notificationTime  = QTime::currentTime();
0281                 d->blockNotification = true;
0282 
0283                 return true;
0284             }
0285 
0286             break;
0287         }
0288     }
0289 
0290     return false;
0291 }
0292 
0293 int LoadSaveThread::exifOrientation(const QString& filePath,
0294                                     const DMetadata& metadata,
0295                                     bool isRaw,
0296                                     bool fromRawEmbeddedPreview)
0297 {
0298     int dbOrientation = MetaEngine::ORIENTATION_UNSPECIFIED;
0299 
0300     if (infoProvider())
0301     {
0302         dbOrientation = infoProvider()->orientationHint(filePath);
0303     }
0304 
0305     int exifOrientation = metadata.getItemOrientation();
0306 
0307     // Raw files are already rotated properly by Raw engine. Only perform auto-rotation with JPEG/PNG/TIFF file.
0308     // We don't have a feedback from Raw engine about auto-rotated RAW file during decoding.
0309 
0310     if (isRaw && !fromRawEmbeddedPreview)
0311     {
0312         // Did the user apply any additional rotation over the metadata flag?
0313 
0314         if (dbOrientation == MetaEngine::ORIENTATION_UNSPECIFIED || dbOrientation == exifOrientation)
0315         {
0316             return MetaEngine::ORIENTATION_NORMAL;
0317         }
0318 
0319         // Assume A is the orientation as from metadata, B is an additional operation applied by the user,
0320         // C is the current orientation in the database.
0321         // A*B = C and B = A_inv * C
0322 
0323         QTransform A     = MetaEngineRotation::toTransform((MetaEngine::ImageOrientation)exifOrientation);
0324         QTransform C     = MetaEngineRotation::toTransform((MetaEngine::ImageOrientation)dbOrientation);
0325         QTransform A_inv = A.inverted();
0326         QTransform B     = A_inv * C;
0327         MetaEngineRotation m(B.m11(), B.m12(), B.m21(), B.m22());
0328 
0329         return m.exifOrientation();
0330     }
0331 
0332     if (dbOrientation != MetaEngine::ORIENTATION_UNSPECIFIED)
0333     {
0334         return dbOrientation;
0335     }
0336 
0337     return exifOrientation;
0338 }
0339 
0340 } // namespace Digikam
0341 
0342 #include "moc_loadsavethread.cpp"