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"