File indexing completed on 2025-01-05 03:53:08

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2011-05-23
0007  * Description : a tool to create panorama by fusion of several images.
0008  *
0009  * SPDX-FileCopyrightText: 2011-2016 by Benjamin Girault <benjamin dot girault at gmail dot com>
0010  * SPDX-FileCopyrightText: 2009-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0011  * SPDX-FileCopyrightText: 2009-2011 by Johannes Wienke <languitar at semipol dot de>
0012  *
0013  * SPDX-License-Identifier: GPL-2.0-or-later
0014  *
0015  * ============================================================ */
0016 
0017 #include "panoactionthread.h"
0018 
0019 // Qt includes
0020 
0021 #include <QSharedPointer>
0022 #include <QTemporaryDir>
0023 #include <QString>
0024 
0025 // KDE includes
0026 
0027 #include <ThreadWeaver/Queue>
0028 #include <ThreadWeaver/QObjectDecorator>
0029 #include <threadweaver/debuggingaids.h>
0030 
0031 // Local includes
0032 
0033 #include "digikam_debug.h"
0034 #include "panotasks.h"
0035 
0036 using namespace ThreadWeaver;
0037 
0038 namespace DigikamGenericPanoramaPlugin
0039 {
0040 
0041 class Q_DECL_HIDDEN PanoActionThread::Private
0042 {
0043 public:
0044 
0045     explicit Private(QObject* const parent = nullptr)
0046         : threadQueue(new Queue(parent))
0047     {
0048         ThreadWeaver::setDebugLevel(true, 10);
0049     }
0050 
0051     ~Private()
0052     {
0053         threadQueue->dequeue();
0054         threadQueue->requestAbort();
0055         threadQueue->finish();
0056     }
0057 
0058     QSharedPointer<QTemporaryDir> preprocessingTmpDir;
0059     QString                       preprocessingTmpPath;
0060     QSharedPointer<Queue>         threadQueue;
0061 };
0062 
0063 PanoActionThread::PanoActionThread(QObject* const parent)
0064     : QObject(parent),
0065       d      (new Private(this))
0066 {
0067     // PanoActionThread init
0068 
0069     qRegisterMetaType<PanoActionData>("PanoActionData");
0070 
0071     d->threadQueue->setMaximumNumberOfThreads(qMax(QThread::idealThreadCount(), 1));
0072     qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "Starting Main Thread";
0073 }
0074 
0075 PanoActionThread::~PanoActionThread()
0076 {
0077     qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "Calling action thread destructor";
0078 
0079     delete d;
0080 }
0081 
0082 void PanoActionThread::cancel()
0083 {
0084     qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "Cancel (PanoAction Thread)";
0085     d->threadQueue->dequeue();
0086     d->threadQueue->requestAbort();
0087 }
0088 
0089 void PanoActionThread::finish()
0090 {
0091     qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "Finish (PanoAction Thread)";
0092 
0093     // Wait for all queued jobs to finish
0094 
0095     d->threadQueue->finish();
0096     d->threadQueue->resume();
0097 }
0098 
0099 void PanoActionThread::preProcessFiles(const QList<QUrl>& urlList,
0100                                        PanoramaItemUrlsMap& preProcessedMap,
0101                                        QUrl& baseUrl,
0102                                        QUrl& cpFindPtoUrl,
0103                                        QUrl& cpCleanPtoUrl,
0104                                        bool celeste,
0105                                        PanoramaFileType fileType,
0106                                        bool gPano,
0107                                        const QString& huginVersion,
0108                                        const QString& cpCleanPath,
0109                                        const QString& cpFindPath)
0110 {
0111     QString prefix          = QDir::tempPath() + QLatin1Char('/') +
0112                               QLatin1String("digiKam-panorama-tmp-XXXXXX");
0113 
0114     d->preprocessingTmpDir  = QSharedPointer<QTemporaryDir>(new QTemporaryDir(prefix));
0115     QDir().mkpath(d->preprocessingTmpDir->path() + QLatin1String("/work"));
0116     d->preprocessingTmpPath = d->preprocessingTmpDir->path() + QLatin1String("/work");
0117 
0118     QSharedPointer<Sequence> jobSeq(new Sequence());
0119 
0120     QSharedPointer<Collection> preprocessJobs(new Collection());
0121 
0122     int id                  = 0;
0123 
0124     for (const QUrl& file : urlList)
0125     {
0126         preProcessedMap.insert(file, PanoramaPreprocessedUrls());
0127 
0128         QObjectDecorator* const t = new QObjectDecorator(new PreProcessTask(d->preprocessingTmpPath,
0129                                                          id++,
0130                                                          preProcessedMap[file],
0131                                                          file));
0132 
0133         connect(t, SIGNAL(started(ThreadWeaver::JobPointer)),
0134                 this, SLOT(slotStarting(ThreadWeaver::JobPointer)));
0135 
0136         connect(t, SIGNAL(done(ThreadWeaver::JobPointer)),
0137                 this, SLOT(slotStepDone(ThreadWeaver::JobPointer)));
0138 
0139         (*preprocessJobs) << JobPointer(t);
0140     }
0141 
0142     (*jobSeq) << preprocessJobs;
0143 
0144     QObjectDecorator* const pto = new QObjectDecorator(new CreatePtoTask(d->preprocessingTmpPath,
0145                                                        fileType,
0146                                                        baseUrl,
0147                                                        urlList,
0148                                                        preProcessedMap,
0149                                                        gPano,
0150                                                        huginVersion));
0151 
0152     connect(pto, SIGNAL(started(ThreadWeaver::JobPointer)),
0153             this, SLOT(slotStarting(ThreadWeaver::JobPointer)));
0154 
0155     connect(pto, SIGNAL(done(ThreadWeaver::JobPointer)),
0156             this, SLOT(slotStepDone(ThreadWeaver::JobPointer)));
0157 
0158     (*jobSeq) << JobPointer(pto);
0159 
0160     QObjectDecorator* const cpFind = new QObjectDecorator(new CpFindTask(d->preprocessingTmpPath,
0161                                                           baseUrl,
0162                                                           cpFindPtoUrl,
0163                                                           celeste,
0164                                                           cpFindPath));
0165 
0166     connect(cpFind, SIGNAL(started(ThreadWeaver::JobPointer)),
0167             this, SLOT(slotStarting(ThreadWeaver::JobPointer)));
0168 
0169     connect(cpFind, SIGNAL(done(ThreadWeaver::JobPointer)),
0170             this, SLOT(slotStepDone(ThreadWeaver::JobPointer)));
0171 
0172     (*jobSeq) << JobPointer(cpFind);
0173 
0174     QObjectDecorator* const cpClean = new QObjectDecorator(new CpCleanTask(d->preprocessingTmpPath,
0175                                                            cpFindPtoUrl,
0176                                                            cpCleanPtoUrl,
0177                                                            cpCleanPath));
0178 
0179     connect(cpClean, SIGNAL(started(ThreadWeaver::JobPointer)),
0180             this, SLOT(slotStarting(ThreadWeaver::JobPointer)));
0181 
0182     connect(cpClean, SIGNAL(done(ThreadWeaver::JobPointer)),
0183             this, SLOT(slotDone(ThreadWeaver::JobPointer)));
0184 
0185     (*jobSeq) << JobPointer(cpClean);
0186 
0187     d->threadQueue->enqueue(jobSeq);
0188 }
0189 
0190 void PanoActionThread::optimizeProject(const QUrl& ptoUrl,
0191                                        QUrl& optimizePtoUrl,
0192                                        QUrl& viewCropPtoUrl,
0193                                        bool levelHorizon,
0194                                        bool buildGPano,
0195                                        const QString& autooptimiserPath,
0196                                        const QString& panoModifyPath)
0197 {
0198     QSharedPointer<Sequence> jobs(new Sequence());
0199 
0200     QObjectDecorator* const ot = new QObjectDecorator(new OptimisationTask(d->preprocessingTmpPath,
0201                                                       ptoUrl,
0202                                                       optimizePtoUrl,
0203                                                       levelHorizon,
0204                                                       buildGPano,
0205                                                       autooptimiserPath));
0206 
0207     connect(ot, SIGNAL(started(ThreadWeaver::JobPointer)),
0208             this, SLOT(slotStarting(ThreadWeaver::JobPointer)));
0209 
0210     connect(ot, SIGNAL(done(ThreadWeaver::JobPointer)),
0211             this, SLOT(slotStepDone(ThreadWeaver::JobPointer)));
0212 
0213     (*jobs) << ot;
0214 
0215     QObjectDecorator* const act = new QObjectDecorator(new AutoCropTask(d->preprocessingTmpPath,
0216                                                        optimizePtoUrl,
0217                                                        viewCropPtoUrl,
0218                                                        buildGPano,
0219                                                        panoModifyPath));
0220 
0221     connect(act, SIGNAL(started(ThreadWeaver::JobPointer)),
0222             this, SLOT(slotStarting(ThreadWeaver::JobPointer)));
0223 
0224     connect(act, SIGNAL(done(ThreadWeaver::JobPointer)),
0225             this, SLOT(slotDone(ThreadWeaver::JobPointer)));
0226 
0227     (*jobs) << act;
0228 
0229     d->threadQueue->enqueue(jobs);
0230 }
0231 
0232 void PanoActionThread::generatePanoramaPreview(QSharedPointer<const PTOType> ptoData,
0233                                                QUrl& previewPtoUrl,
0234                                                QUrl& previewMkUrl,
0235                                                QUrl& previewUrl,
0236                                                const PanoramaItemUrlsMap& preProcessedUrlsMap,
0237                                                const QString& makePath,
0238                                                const QString& pto2mkPath,
0239                                                const QString& huginExecutorPath,
0240                                                bool hugin2015,
0241                                                const QString& enblendPath,
0242                                                const QString& nonaPath)
0243 {
0244     QSharedPointer<Sequence> jobs(new Sequence());
0245 
0246     QObjectDecorator* const ptoTask = new QObjectDecorator(new CreatePreviewTask(d->preprocessingTmpPath,
0247                                                            ptoData,
0248                                                            previewPtoUrl,
0249                                                            preProcessedUrlsMap));
0250 
0251     connect(ptoTask, SIGNAL(started(ThreadWeaver::JobPointer)),
0252             this, SLOT(slotStarting(ThreadWeaver::JobPointer)));
0253 
0254     connect(ptoTask, SIGNAL(done(ThreadWeaver::JobPointer)),
0255             this, SLOT(slotStepDone(ThreadWeaver::JobPointer)));
0256 
0257     (*jobs) << ptoTask;
0258 
0259     if (!hugin2015)
0260     {
0261         appendStitchingJobs(jobs,
0262                             previewPtoUrl,
0263                             previewMkUrl,
0264                             previewUrl,
0265                             preProcessedUrlsMap,
0266                             JPEG,
0267                             makePath,
0268                             pto2mkPath,
0269                             enblendPath,
0270                             nonaPath,
0271                             true);
0272     }
0273     else
0274     {
0275         QObjectDecorator* const huginExecutorTask = new QObjectDecorator(new HuginExecutorTask(d->preprocessingTmpPath,
0276                                                                          previewPtoUrl,
0277                                                                          previewUrl,
0278                                                                          JPEG,
0279                                                                          huginExecutorPath,
0280                                                                          true));
0281 
0282         connect(huginExecutorTask, SIGNAL(started(ThreadWeaver::JobPointer)),
0283                 this, SLOT(slotStarting(ThreadWeaver::JobPointer)));
0284 
0285         connect(huginExecutorTask, SIGNAL(done(ThreadWeaver::JobPointer)),
0286                 this, SLOT(slotStepDone(ThreadWeaver::JobPointer)));
0287 
0288         (*jobs) << huginExecutorTask;
0289     }
0290 
0291     d->threadQueue->enqueue(jobs);
0292 }
0293 
0294 void PanoActionThread::compileProject(QSharedPointer<const PTOType> basePtoData,
0295                                       QUrl& panoPtoUrl,
0296                                       QUrl& mkUrl,
0297                                       QUrl& panoUrl,
0298                                       const PanoramaItemUrlsMap& preProcessedUrlsMap,
0299                                       PanoramaFileType fileType,
0300                                       const QRect& crop,
0301                                       const QString& makePath,
0302                                       const QString& pto2mkPath,
0303                                       const QString& huginExecutorPath,
0304                                       bool hugin2015,
0305                                       const QString& enblendPath,
0306                                       const QString& nonaPath)
0307 {
0308     QSharedPointer<Sequence> jobs(new Sequence());
0309 
0310     QObjectDecorator* const ptoTask = new QObjectDecorator(new CreateFinalPtoTask(d->preprocessingTmpPath,
0311                                                            basePtoData,
0312                                                            panoPtoUrl,
0313                                                            crop));
0314 
0315     connect(ptoTask, SIGNAL(started(ThreadWeaver::JobPointer)),
0316             this, SLOT(slotStarting(ThreadWeaver::JobPointer)));
0317 
0318     connect(ptoTask, SIGNAL(done(ThreadWeaver::JobPointer)),
0319             this, SLOT(slotStepDone(ThreadWeaver::JobPointer)));
0320 
0321     (*jobs) << ptoTask;
0322 
0323     if (!hugin2015)
0324     {
0325         appendStitchingJobs(jobs,
0326                             panoPtoUrl,
0327                             mkUrl,
0328                             panoUrl,
0329                             preProcessedUrlsMap,
0330                             fileType,
0331                             makePath,
0332                             pto2mkPath,
0333                             enblendPath,
0334                             nonaPath,
0335                             false);
0336     }
0337     else
0338     {
0339         QObjectDecorator* const huginExecutorTask = new QObjectDecorator(new HuginExecutorTask(d->preprocessingTmpPath,
0340                                                                          panoPtoUrl,
0341                                                                          panoUrl,
0342                                                                          fileType,
0343                                                                          huginExecutorPath,
0344                                                                          false));
0345 
0346         connect(huginExecutorTask, SIGNAL(started(ThreadWeaver::JobPointer)),
0347                 this, SLOT(slotStarting(ThreadWeaver::JobPointer)));
0348 
0349         connect(huginExecutorTask, SIGNAL(done(ThreadWeaver::JobPointer)),
0350                 this, SLOT(slotStepDone(ThreadWeaver::JobPointer)));
0351 
0352         (*jobs) << huginExecutorTask;
0353     }
0354 
0355     d->threadQueue->enqueue(jobs);
0356 }
0357 
0358 void PanoActionThread::copyFiles(const QUrl& ptoUrl,
0359                                  const QUrl& panoUrl,
0360                                  const QUrl& finalPanoUrl,
0361                                  const PanoramaItemUrlsMap& preProcessedUrlsMap,
0362                                  bool savePTO,
0363                                  bool addGPlusMetadata)
0364 {
0365     QObjectDecorator* const t = new QObjectDecorator(new CopyFilesTask(d->preprocessingTmpPath,
0366                                                      panoUrl,
0367                                                      finalPanoUrl,
0368                                                      ptoUrl,
0369                                                      preProcessedUrlsMap,
0370                                                      savePTO,
0371                                                      addGPlusMetadata));
0372 
0373     connect(t, SIGNAL(started(ThreadWeaver::JobPointer)),
0374             this, SLOT(slotStarting(ThreadWeaver::JobPointer)));
0375 
0376     connect(t, SIGNAL(done(ThreadWeaver::JobPointer)),
0377             this, SLOT(slotDone(ThreadWeaver::JobPointer)));
0378 
0379     d->threadQueue->enqueue(JobPointer(t));
0380 }
0381 
0382 void PanoActionThread::slotStarting(JobPointer j)
0383 {
0384     QSharedPointer<QObjectDecorator> dec = j.staticCast<QObjectDecorator>();
0385     PanoTask* const t                    = static_cast<PanoTask*>(dec->job());
0386 
0387     PanoActionData ad;
0388     ad.starting     = true;
0389     ad.action       = t->action;
0390     ad.id           = -1;
0391 
0392     qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "Starting (PanoAction Thread) (action):" << ad.action;
0393 
0394     if      (t->action == PANO_NONAFILE)
0395     {
0396         CompileMKStepTask* const c = static_cast<CompileMKStepTask*>(t);
0397         ad.id                      = c->id;
0398     }
0399     else if (t->action == PANO_PREPROCESS_INPUT)
0400     {
0401         PreProcessTask* const p = static_cast<PreProcessTask*>(t);
0402         ad.id                   = p->id;
0403     }
0404 
0405     Q_EMIT starting(ad);
0406 }
0407 
0408 void PanoActionThread::slotStepDone(JobPointer j)
0409 {
0410     QSharedPointer<QObjectDecorator> dec = j.staticCast<QObjectDecorator>();
0411     PanoTask* const t                    = static_cast<PanoTask*>(dec->job());
0412 
0413     PanoActionData ad;
0414     ad.starting     = false;
0415     ad.action       = t->action;
0416     ad.id           = -1;
0417     ad.success      = t->success();
0418     ad.message      = t->errString;
0419 
0420     qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "Step done (PanoAction Thread) (action, success):" << ad.action << ad.success;
0421 
0422     if      (t->action == PANO_NONAFILE)
0423     {
0424         CompileMKStepTask* const c = static_cast<CompileMKStepTask*>(t);
0425         ad.id                      = c->id;
0426     }
0427     else if (t->action == PANO_PREPROCESS_INPUT)
0428     {
0429         PreProcessTask* const p = static_cast<PreProcessTask*>(t);
0430         ad.id                   = p->id;
0431     }
0432 
0433     if (!ad.success)
0434     {
0435         d->threadQueue->dequeue();
0436     }
0437 
0438     Q_EMIT stepFinished(ad);
0439 }
0440 
0441 void PanoActionThread::slotDone(JobPointer j)
0442 {
0443     QSharedPointer<QObjectDecorator> dec = j.staticCast<QObjectDecorator>();
0444     PanoTask* const t                    = static_cast<PanoTask*>(dec->job());
0445 
0446     PanoActionData ad;
0447     ad.starting     = false;
0448     ad.action       = t->action;
0449     ad.id           = -1;
0450     ad.success      = t->success();
0451     ad.message      = t->errString;
0452 
0453     qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "Done (PanoAction Thread) (action, success):"
0454                                  << ad.action << ad.success;
0455 
0456     if      (t->action == PANO_NONAFILE)
0457     {
0458         CompileMKStepTask* const c = static_cast<CompileMKStepTask*>(t);
0459         ad.id                      = c->id;
0460     }
0461     else if (t->action == PANO_PREPROCESS_INPUT)
0462     {
0463         PreProcessTask* const p = static_cast<PreProcessTask*>(t);
0464         ad.id                   = p->id;
0465     }
0466 
0467     Q_EMIT jobCollectionFinished(ad);
0468 }
0469 
0470 void PanoActionThread::appendStitchingJobs(const QSharedPointer<Sequence>& js,
0471                                            const QUrl& ptoUrl,
0472                                            QUrl& mkUrl,
0473                                            QUrl& outputUrl,
0474                                            const PanoramaItemUrlsMap& preProcessedUrlsMap,
0475                                            PanoramaFileType fileType,
0476                                            const QString& makePath,
0477                                            const QString& pto2mkPath,
0478                                            const QString& enblendPath,
0479                                            const QString& nonaPath,
0480                                            bool preview)
0481 {
0482     QSharedPointer<Sequence> jobs(new Sequence());
0483 
0484     QObjectDecorator* const createMKTask = new QObjectDecorator(new CreateMKTask(d->preprocessingTmpPath,
0485                                                                 ptoUrl,
0486                                                                 mkUrl,
0487                                                                 outputUrl,
0488                                                                 fileType,
0489                                                                 pto2mkPath,
0490                                                                 preview));
0491 
0492     connect(createMKTask, SIGNAL(started(ThreadWeaver::JobPointer)),
0493             this, SLOT(slotStarting(ThreadWeaver::JobPointer)));
0494 
0495     connect(createMKTask, SIGNAL(done(ThreadWeaver::JobPointer)),
0496             this, SLOT(slotStepDone(ThreadWeaver::JobPointer)));
0497 
0498     (*jobs) << createMKTask;
0499 
0500     for (int i = 0 ; i < preProcessedUrlsMap.size() ; ++i)
0501     {
0502         QObjectDecorator* const t = new QObjectDecorator(new CompileMKStepTask(d->preprocessingTmpPath,
0503                                                          i,
0504                                                          mkUrl,
0505                                                          nonaPath,
0506                                                          enblendPath,
0507                                                          makePath,
0508                                                          preview));
0509 
0510         connect(t, SIGNAL(started(ThreadWeaver::JobPointer)),
0511                 this, SLOT(slotStarting(ThreadWeaver::JobPointer)));
0512 
0513         connect(t, SIGNAL(done(ThreadWeaver::JobPointer)),
0514                 this, SLOT(slotStepDone(ThreadWeaver::JobPointer)));
0515 
0516         (*jobs) << t;
0517     }
0518 
0519     QObjectDecorator* const compileMKTask = new QObjectDecorator(new CompileMKTask(d->preprocessingTmpPath,
0520                                                                  mkUrl,
0521                                                                  outputUrl,
0522                                                                  nonaPath,
0523                                                                  enblendPath,
0524                                                                  makePath,
0525                                                                  preview));
0526 
0527     connect(compileMKTask, SIGNAL(started(ThreadWeaver::JobPointer)),
0528             this, SLOT(slotStarting(ThreadWeaver::JobPointer)));
0529 
0530     connect(compileMKTask, SIGNAL(done(ThreadWeaver::JobPointer)),
0531             this, SLOT(slotDone(ThreadWeaver::JobPointer)));
0532 
0533     (*jobs) << compileMKTask;
0534 
0535     (*js) << jobs;
0536 }
0537 
0538 } // namespace DigikamGenericPanoramaPlugin
0539 
0540 #include "moc_panoactionthread.cpp"