File indexing completed on 2025-03-09 03:58:50

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2008-11-24
0007  * Description : Batch Tool Container.
0008  *
0009  * SPDX-FileCopyrightText: 2008-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0010  *
0011  * SPDX-License-Identifier: GPL-2.0-or-later
0012  *
0013  * ============================================================ */
0014 
0015 #include "batchtool.h"
0016 
0017 // Qt includes
0018 
0019 #include <QLabel>
0020 #include <QWidget>
0021 #include <QPolygon>
0022 #include <QFileInfo>
0023 #include <QDateTime>
0024 #include <QDataStream>
0025 #include <QScopedPointer>
0026 
0027 // KDE includes
0028 
0029 #include <klocalizedstring.h>
0030 
0031 // Local includes
0032 
0033 #include "drawdecoder.h"
0034 #include "dimgloader.h"
0035 #include "digikam_debug.h"
0036 #include "dimgbuiltinfilter.h"
0037 #include "dimgloaderobserver.h"
0038 #include "dimgthreadedfilter.h"
0039 #include "filereadwritelock.h"
0040 #include "batchtoolutils.h"
0041 #include "dmetadata.h"
0042 #include "dpluginbqm.h"
0043 
0044 namespace Digikam
0045 {
0046 
0047 class BatchToolObserver;
0048 
0049 class Q_DECL_HIDDEN BatchTool::Private
0050 {
0051 
0052 public:
0053 
0054     explicit Private()
0055       : exifResetOrientation  (false),
0056         exifCanEditOrientation(true),
0057         saveAsNewVersion      (true),
0058         branchHistory         (true),
0059         cancel                (false),
0060         last                  (false),
0061         observer              (nullptr),
0062         toolGroup             (BaseTool),
0063         rawLoadingRule        (QueueSettings::DEMOSAICING),
0064         plugin                (nullptr)
0065     {
0066     }
0067 
0068     bool                          exifResetOrientation;
0069     bool                          exifCanEditOrientation;
0070     bool                          saveAsNewVersion;
0071     bool                          branchHistory;
0072     bool                          cancel;
0073     bool                          last;
0074 
0075     QString                       errorMessage;
0076     QString                       toolTitle;          ///< User friendly tool title.
0077     QString                       toolDescription;    ///< User friendly tool description.
0078     QIcon                         toolIcon;
0079 
0080     QUrl                          inputUrl;
0081     QUrl                          outputUrl;
0082     QUrl                          workingUrl;
0083 
0084     DImg                          image;
0085 
0086     ItemInfo                      imageinfo;
0087 
0088     DRawDecoderSettings           rawDecodingSettings;
0089 
0090     IOFileSettings                ioFileSettings;
0091 
0092     BatchToolSettings             settings;
0093 
0094     BatchToolObserver*            observer;
0095 
0096     BatchTool::BatchToolGroup     toolGroup;
0097 
0098     QueueSettings::RawLoadingRule rawLoadingRule;
0099 
0100     DPluginBqm*                   plugin;
0101 };
0102 
0103 class Q_DECL_HIDDEN BatchToolObserver : public DImgLoaderObserver
0104 {
0105 
0106 public:
0107 
0108     explicit BatchToolObserver(BatchTool::Private* const priv)
0109         : DImgLoaderObserver(),
0110           d                 (priv)
0111     {
0112     }
0113 
0114     ~BatchToolObserver() override
0115     {
0116     }
0117 
0118     bool continueQuery() override
0119     {
0120         return (!d->cancel);
0121     }
0122 
0123 private:
0124 
0125     BatchTool::Private* const d;
0126 };
0127 
0128 BatchTool::BatchTool(const QString& name, BatchToolGroup group, QObject* const parent)
0129     : QObject(parent),
0130       d      (new Private)
0131 {
0132     d->observer      = new BatchToolObserver(d);
0133     d->toolGroup     = group;
0134     m_settingsWidget = nullptr;
0135     setObjectName(name);
0136 }
0137 
0138 BatchTool::~BatchTool()
0139 {
0140     // NOTE: See bug #341566: no need to delete settings widget here.
0141     // We delete the settings widget now in the ToolSettingsView.
0142 
0143     delete d->observer;
0144     delete d;
0145 }
0146 
0147 void BatchTool::setPlugin(DPluginBqm* const plugin)
0148 {
0149     d->plugin = plugin;
0150     setToolTitle(d->plugin->name());
0151     setToolDescription(d->plugin->description());
0152     setToolIcon(d->plugin->icon());
0153 
0154     connect(d->plugin, SIGNAL(signalVisible(bool)),
0155             this, SIGNAL(signalVisible(bool)));
0156 }
0157 
0158 DPluginBqm* BatchTool::plugin() const
0159 {
0160     return d->plugin;
0161 }
0162 
0163 QString BatchTool::errorDescription() const
0164 {
0165     return d->errorMessage;
0166 }
0167 
0168 void BatchTool::setErrorDescription(const QString& errmsg)
0169 {
0170     d->errorMessage = errmsg;
0171 }
0172 
0173 BatchTool::BatchToolGroup BatchTool::toolGroup() const
0174 {
0175     return d->toolGroup;
0176 }
0177 
0178 QString BatchTool::toolGroupToString() const
0179 {
0180     switch (toolGroup())
0181     {
0182         case BaseTool:
0183         {
0184             return i18nc("@title: tool group", "Base");
0185         }
0186 
0187         case CustomTool:
0188         {
0189             return i18nc("@title: tool group", "Custom");
0190         }
0191 
0192         case ColorTool:
0193         {
0194             return i18nc("@title: tool group", "Colors");
0195         }
0196 
0197         case EnhanceTool:
0198         {
0199             return i18nc("@title: tool group", "Enhance");
0200         }
0201 
0202         case TransformTool:
0203         {
0204             return i18nc("@title: tool group", "Transform");
0205         }
0206 
0207         case DecorateTool:
0208         {
0209             return i18nc("@title: tool group", "Decorate");
0210         }
0211 
0212         case FiltersTool:
0213         {
0214             return i18nc("@title: tool group", "Filters");
0215         }
0216 
0217         case ConvertTool:
0218         {
0219             return i18nc("@title: tool group", "Convert");
0220         }
0221 
0222         case MetadataTool:
0223         {
0224             return i18nc("@title: tool group", "Metadata");
0225         }
0226 
0227         default:
0228         {
0229             break;
0230         }
0231     }
0232 
0233     return i18nc("@title: tool group", "Invalid");
0234 }
0235 
0236 void BatchTool::setToolTitle(const QString& toolTitle)
0237 {
0238     d->toolTitle = toolTitle;
0239 }
0240 
0241 QString BatchTool::toolTitle() const
0242 {
0243     return d->toolTitle;
0244 }
0245 
0246 void BatchTool::setToolDescription(const QString& toolDescription)
0247 {
0248     d->toolDescription = toolDescription;
0249 }
0250 
0251 QString BatchTool::toolDescription() const
0252 {
0253     return d->toolDescription;
0254 }
0255 
0256 void BatchTool::setToolIconName(const QString& iconName)
0257 {
0258     d->toolIcon = QIcon::fromTheme(iconName);
0259 }
0260 
0261 void BatchTool::setToolIcon(const QIcon& icon)
0262 {
0263     d->toolIcon = icon;
0264 }
0265 
0266 QIcon BatchTool::toolIcon() const
0267 {
0268     return d->toolIcon;
0269 }
0270 
0271 void BatchTool::slotResetSettingsToDefault()
0272 {
0273     slotSettingsChanged(defaultSettings());
0274 }
0275 
0276 void BatchTool::slotSettingsChanged(const BatchToolSettings& settings)
0277 {
0278     setSettings(settings);
0279     Q_EMIT signalSettingsChanged(d->settings);
0280 }
0281 
0282 void BatchTool::setSettings(const BatchToolSettings& settings)
0283 {
0284     d->settings = settings;
0285 
0286     Q_EMIT signalAssignSettings2Widget();
0287 }
0288 
0289 void BatchTool::setInputUrl(const QUrl& inputUrl)
0290 {
0291     d->inputUrl = inputUrl;
0292 }
0293 
0294 QUrl BatchTool::inputUrl() const
0295 {
0296     return d->inputUrl;
0297 }
0298 
0299 void BatchTool::setOutputUrl(const QUrl& outputUrl)
0300 {
0301     d->outputUrl = outputUrl;
0302 }
0303 
0304 QUrl BatchTool::outputUrl() const
0305 {
0306     return d->outputUrl;
0307 }
0308 
0309 QString BatchTool::outputSuffix() const
0310 {
0311     return QString();
0312 }
0313 
0314 void BatchTool::setImageData(const DImg& img)
0315 {
0316     d->image = img;
0317 }
0318 
0319 DImg BatchTool::imageData() const
0320 {
0321     return d->image;
0322 }
0323 
0324 void BatchTool::setItemInfo(const ItemInfo& info)
0325 {
0326     d->imageinfo = info;
0327 }
0328 
0329 ItemInfo BatchTool::imageInfo() const
0330 {
0331     return d->imageinfo;
0332 }
0333 
0334 void BatchTool::setNeedResetExifOrientation(bool set)
0335 {
0336     d->exifResetOrientation = set;
0337 }
0338 
0339 bool BatchTool::getNeedResetExifOrientation() const
0340 {
0341     return d->exifResetOrientation;
0342 }
0343 
0344 void BatchTool::setResetExifOrientationAllowed(bool set)
0345 {
0346     d->exifCanEditOrientation = set;
0347 }
0348 
0349 bool BatchTool::getResetExifOrientationAllowed() const
0350 {
0351     return d->exifCanEditOrientation;
0352 }
0353 
0354 void BatchTool::setRawLoadingRules(QueueSettings::RawLoadingRule rule)
0355 {
0356     d->rawLoadingRule = rule;
0357 }
0358 
0359 void BatchTool::setSaveAsNewVersion(bool fork)
0360 {
0361     d->saveAsNewVersion = fork;
0362 }
0363 
0364 void BatchTool::setBranchHistory(bool branch)
0365 {
0366     d->branchHistory = branch;
0367 }
0368 
0369 bool BatchTool::getBranchHistory() const
0370 {
0371     return d->branchHistory;
0372 }
0373 
0374 void BatchTool::setDRawDecoderSettings(const DRawDecoderSettings& settings)
0375 {
0376     d->rawDecodingSettings = settings;
0377 }
0378 
0379 DRawDecoderSettings BatchTool::rawDecodingSettings() const
0380 {
0381     return d->rawDecodingSettings;
0382 }
0383 
0384 void BatchTool::setIOFileSettings(const IOFileSettings& settings)
0385 {
0386     d->ioFileSettings = settings;
0387 }
0388 
0389 IOFileSettings BatchTool::ioFileSettings() const
0390 {
0391     return d->ioFileSettings;
0392 }
0393 
0394 void BatchTool::setWorkingUrl(const QUrl& workingUrl)
0395 {
0396     d->workingUrl = workingUrl;
0397 }
0398 
0399 QUrl BatchTool::workingUrl() const
0400 {
0401     return d->workingUrl;
0402 }
0403 
0404 void BatchTool::cancel()
0405 {
0406     d->cancel = true;
0407 }
0408 
0409 bool BatchTool::isCancelled() const
0410 {
0411     return d->cancel;
0412 }
0413 
0414 BatchToolSettings BatchTool::settings() const
0415 {
0416     return d->settings;
0417 }
0418 
0419 void BatchTool::setLastChainedTool(bool last)
0420 {
0421     d->last = last;
0422 }
0423 
0424 bool BatchTool::isLastChainedTool() const
0425 {
0426     return d->last;
0427 }
0428 
0429 void BatchTool::setOutputUrlFromInputUrl()
0430 {
0431     QString path(workingUrl().toLocalFile());
0432     QString suffix = outputSuffix();
0433 
0434     if (suffix.isEmpty())
0435     {
0436         QFileInfo fi(inputUrl().fileName());
0437         suffix = fi.suffix();
0438     }
0439 
0440     SafeTemporaryFile temp(path + QLatin1String("/BatchTool-XXXXXX"
0441                                                 ".digikamtempfile.") + suffix);
0442 
0443     temp.setAutoRemove(false);
0444     temp.open();
0445     qCDebug(DIGIKAM_GENERAL_LOG) << "path: " << temp.safeFilePath();
0446 
0447     setOutputUrl(QUrl::fromLocalFile(temp.safeFilePath()));
0448 }
0449 
0450 bool BatchTool::isRawFile(const QUrl& url) const
0451 {
0452     QString   rawFilesExt = DRawDecoder::rawFiles();
0453     QFileInfo fileInfo(url.toLocalFile());
0454 
0455     return (rawFilesExt.toUpper().contains(fileInfo.suffix().toUpper()));
0456 }
0457 
0458 bool BatchTool::loadToDImg() const
0459 {
0460     if (!d->image.isNull())
0461     {
0462         return true;
0463     }
0464 
0465     if ((d->rawLoadingRule == QueueSettings::USEEMBEDEDJPEG) && isRawFile(inputUrl()))
0466     {
0467         QImage img;
0468         bool   ret = DRawDecoder::loadRawPreview(img, inputUrl().toLocalFile());
0469         QScopedPointer<DMetadata> meta(new DMetadata(inputUrl().toLocalFile()));
0470         meta->setItemDimensions(QSize(img.width(), img.height()));
0471         d->image   = DImg(img);
0472         d->image.setMetadata(meta->data());
0473 
0474         return ret;
0475     }
0476 
0477     return (d->image.load(inputUrl().toLocalFile(), d->observer, DRawDecoding(rawDecodingSettings())));
0478 }
0479 
0480 bool BatchTool::savefromDImg() const
0481 {
0482     if (!isLastChainedTool() && outputSuffix().isEmpty())
0483     {
0484         return true;
0485     }
0486 
0487     DImg::FORMAT detectedFormat = d->image.detectedFormat();
0488     QString frm                 = outputSuffix().toUpper();
0489     bool resetOrientation       = getResetExifOrientationAllowed() &&
0490                                   (getNeedResetExifOrientation() || (detectedFormat == DImg::RAW));
0491 
0492     if (d->branchHistory)
0493     {
0494         // image has its original history
0495 
0496         d->image.setHistoryBranch(d->saveAsNewVersion);
0497     }
0498 
0499     if (frm.isEmpty())
0500     {
0501         // In case of output support is not set for ex. with all tool which do not convert to new format.
0502 
0503         if      (detectedFormat == DImg::JPEG)
0504         {
0505             d->image.setAttribute(QLatin1String("quality"),     DImgLoader::convertCompressionForLibJpeg(ioFileSettings().JPEGCompression));
0506             d->image.setAttribute(QLatin1String("subsampling"), ioFileSettings().JPEGSubSampling);
0507         }
0508         else if (detectedFormat == DImg::PNG)
0509         {
0510             d->image.setAttribute(QLatin1String("quality"),     DImgLoader::convertCompressionForLibPng(ioFileSettings().PNGCompression));
0511         }
0512         else if (detectedFormat == DImg::TIFF)
0513         {
0514             d->image.setAttribute(QLatin1String("compress"),    ioFileSettings().TIFFCompression);
0515         }
0516         else if (detectedFormat == DImg::JP2K)
0517         {
0518             d->image.setAttribute(QLatin1String("quality"),     ioFileSettings().JPEG2000LossLess ? 100 :
0519                                   ioFileSettings().JPEG2000Compression);
0520         }
0521         else if (detectedFormat == DImg::PGF)
0522         {
0523             d->image.setAttribute(QLatin1String("quality"),     ioFileSettings().PGFLossLess ? 0 :
0524                                   ioFileSettings().PGFCompression);
0525         }
0526 
0527         d->image.prepareMetadataToSave(outputUrl().toLocalFile(), DImg::formatToMimeType(detectedFormat), resetOrientation);
0528         bool b = d->image.save(outputUrl().toLocalFile(), detectedFormat, d->observer);
0529 
0530         return b;
0531     }
0532 
0533     d->image.prepareMetadataToSave(outputUrl().toLocalFile(), frm, resetOrientation);
0534     bool b   = d->image.save(outputUrl().toLocalFile(), frm, d->observer);
0535     d->image = DImg();
0536 
0537     return b;
0538 }
0539 
0540 DImg& BatchTool::image() const
0541 {
0542     return d->image;
0543 }
0544 
0545 bool BatchTool::apply()
0546 {
0547     d->cancel = false;
0548 
0549     qCDebug(DIGIKAM_GENERAL_LOG) << "Tool:       " << toolTitle();
0550     qCDebug(DIGIKAM_GENERAL_LOG) << "Input url:  " << inputUrl();
0551     qCDebug(DIGIKAM_GENERAL_LOG) << "Output url: " << outputUrl();
0552 /*
0553     qCDebug(DIGIKAM_GENERAL_LOG) << "Settings:   ";
0554 */
0555     BatchToolSettings prm = settings();
0556 
0557     for (BatchToolSettings::const_iterator it = prm.constBegin() ; it != prm.constEnd() ; ++it)
0558     {
0559         if (it.value().canConvert<QPolygon>())
0560         {
0561             QPolygon pol = it.value().value<QPolygon>();
0562             int     size = (pol.size() > 20) ? 20 : pol.size();
0563             QString tmp;
0564             tmp.append(QString::fromUtf8("[%1 items] : ").arg(pol.size()));
0565 
0566             for (int i = 0 ; i < size ; ++i)
0567             {
0568                 tmp.append(QLatin1String("("));
0569                 tmp.append(QString::number(pol.point(i).x()));
0570                 tmp.append(QLatin1String(", "));
0571                 tmp.append(QString::number(pol.point(i).y()));
0572                 tmp.append(QLatin1String(") "));
0573             }
0574 
0575             //qCDebug(DIGIKAM_GENERAL_LOG) << "   " << it.key() << ": " << tmp;
0576         }
0577         else
0578         {
0579             //qCDebug(DIGIKAM_GENERAL_LOG) << "   " << it.key() << ": " << it.value();
0580         }
0581     }
0582 
0583     return toolOperations();
0584 }
0585 
0586 void BatchTool::applyFilter(DImgThreadedFilter* const filter)
0587 {
0588     filter->startFilterDirectly();
0589 
0590     if (isCancelled())
0591     {
0592         return;
0593     }
0594 
0595     d->image.putImageData(filter->getTargetImage().bits());
0596     d->image.addFilterAction(filter->filterAction());
0597 }
0598 
0599 void BatchTool::applyFilterChangedProperties(DImgThreadedFilter* const filter)
0600 {
0601     filter->startFilterDirectly();
0602 
0603     if (isCancelled())
0604     {
0605         return;
0606     }
0607 
0608     DImg trg = filter->getTargetImage();
0609     d->image.putImageData(trg.width(), trg.height(), trg.sixteenBit(), trg.hasAlpha(), trg.bits());
0610     d->image.addFilterAction(filter->filterAction());
0611 }
0612 
0613 void BatchTool::applyFilter(DImgBuiltinFilter* const filter)
0614 {
0615     filter->apply(d->image);
0616     d->image.addFilterAction(filter->filterAction());
0617 }
0618 
0619 // -- Settings Widgets methods ---------------------------------------------------------------------------
0620 
0621 QWidget* BatchTool::settingsWidget() const
0622 {
0623     return m_settingsWidget;
0624 }
0625 
0626 void BatchTool::deleteSettingsWidget()
0627 {
0628     delete m_settingsWidget;
0629     m_settingsWidget = nullptr;
0630 }
0631 
0632 void BatchTool::registerSettingsWidget()
0633 {
0634     // NOTE: see bug #209225 : signal/slot connection used internally to prevent crash when settings
0635     // are assigned to settings widget by main thread to tool thread.
0636 
0637     connect(this, SIGNAL(signalAssignSettings2Widget()),
0638             this, SLOT(slotAssignSettings2Widget()));
0639 
0640     if (!m_settingsWidget)
0641     {
0642         QLabel* const label = new QLabel;
0643         label->setText(i18nc("@label", "No setting available"));
0644         label->setAlignment(Qt::AlignCenter);
0645         label->setWordWrap(true);
0646         m_settingsWidget    = label;
0647     }
0648 }
0649 
0650 } // namespace Digikam
0651 
0652 #include "moc_batchtool.cpp"