File indexing completed on 2024-05-19 04:29:13
0001 /* 0002 * SPDX-FileCopyrightText: 2016 Boudewijn Rempt <boud@valdyas.org> 0003 * 0004 * SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "KisImportExportManager.h" 0008 0009 #include <QDir> 0010 #include <QFile> 0011 #include <QLabel> 0012 #include <QVBoxLayout> 0013 #include <QList> 0014 #include <QApplication> 0015 #include <QByteArray> 0016 #include <QPluginLoader> 0017 #include <QFileInfo> 0018 #include <QMessageBox> 0019 #include <QJsonObject> 0020 #include <QTextBrowser> 0021 #include <QCheckBox> 0022 #include <QSaveFile> 0023 #include <QGroupBox> 0024 #include <QFuture> 0025 #include <QtConcurrent> 0026 0027 #include <klocalizedstring.h> 0028 #include <ksqueezedtextlabel.h> 0029 #include <kpluginfactory.h> 0030 0031 #include <KisMimeDatabase.h> 0032 #include <KisPart.h> 0033 #include <KisPopupButton.h> 0034 #include <KisPreExportChecker.h> 0035 #include <KisUsageLogger.h> 0036 #include <KoColorProfile.h> 0037 #include <KoColorProfileConstants.h> 0038 #include <KoDialog.h> 0039 #include <KoFileDialog.h> 0040 #include <KoJsonTrader.h> 0041 #include <KoProgressUpdater.h> 0042 #include <kis_assert.h> 0043 #include <kis_config_widget.h> 0044 #include <kis_debug.h> 0045 #include <kis_icon_utils.h> 0046 #include <kis_image.h> 0047 #include <kis_iterator_ng.h> 0048 #include <kis_layer_utils.h> 0049 #include <kis_paint_layer.h> 0050 #include <kis_painter.h> 0051 0052 #include "KisDocument.h" 0053 #include "KisImportExportErrorCode.h" 0054 #include "KisImportExportFilter.h" 0055 #include "KisMainWindow.h" 0056 #include "KisReferenceImagesLayer.h" 0057 #include "imagesize/dlg_imagesize.h" 0058 #include "kis_async_action_feedback.h" 0059 #include "kis_config.h" 0060 #include "kis_grid_config.h" 0061 #include "kis_guides_config.h" 0062 0063 #include <KisImportUserFeedbackInterface.h> 0064 0065 // static cache for import and export mimetypes 0066 QStringList KisImportExportManager::m_importMimeTypes; 0067 QStringList KisImportExportManager::m_exportMimeTypes; 0068 0069 namespace { 0070 struct SynchronousUserFeedbackInterface : KisImportUserFeedbackInterface 0071 { 0072 SynchronousUserFeedbackInterface(QWidget *parent, bool batchMode) 0073 : m_parent(parent) 0074 , m_batchMode(batchMode) 0075 { 0076 } 0077 0078 Result askUser(AskCallback callback) override 0079 { 0080 if (m_batchMode) return SuppressedByBatchMode; 0081 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(m_parent, SuppressedByBatchMode); 0082 0083 return callback(m_parent) ? Success : UserCancelled; 0084 } 0085 0086 QWidget *m_parent {nullptr}; 0087 bool m_batchMode {false}; 0088 }; 0089 0090 } 0091 0092 0093 class Q_DECL_HIDDEN KisImportExportManager::Private 0094 { 0095 public: 0096 KoUpdaterPtr updater; 0097 0098 QString cachedExportFilterMimeType; 0099 QSharedPointer<KisImportExportFilter> cachedExportFilter; 0100 }; 0101 0102 struct KisImportExportManager::ConversionResult { 0103 ConversionResult() 0104 { 0105 } 0106 0107 ConversionResult(const QFuture<KisImportExportErrorCode> &futureStatus) 0108 : m_isAsync(true), 0109 m_futureStatus(futureStatus) 0110 { 0111 } 0112 0113 ConversionResult(KisImportExportErrorCode status) 0114 : m_isAsync(false), 0115 m_status(status) 0116 { 0117 } 0118 0119 bool isAsync() const { 0120 return m_isAsync; 0121 } 0122 0123 QFuture<KisImportExportErrorCode> futureStatus() const { 0124 // if the result is not async, then it means some failure happened, 0125 // just return a cancelled future 0126 KIS_SAFE_ASSERT_RECOVER_NOOP(m_isAsync || !m_status.isOk()); 0127 0128 return m_futureStatus; 0129 } 0130 0131 KisImportExportErrorCode status() const { 0132 return m_status; 0133 } 0134 0135 void setStatus(KisImportExportErrorCode value) { 0136 m_status = value; 0137 } 0138 private: 0139 bool m_isAsync = false; 0140 QFuture<KisImportExportErrorCode> m_futureStatus; 0141 KisImportExportErrorCode m_status = ImportExportCodes::InternalError; 0142 }; 0143 0144 0145 KisImportExportManager::KisImportExportManager(KisDocument* document) 0146 : m_document(document) 0147 , d(new Private) 0148 { 0149 } 0150 0151 KisImportExportManager::~KisImportExportManager() 0152 { 0153 delete d; 0154 } 0155 0156 KisImportExportErrorCode KisImportExportManager::importDocument(const QString& location, const QString& mimeType) 0157 { 0158 ConversionResult result = convert(Import, location, location, mimeType, false, 0, false); 0159 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!result.isAsync(), ImportExportCodes::InternalError); 0160 0161 return result.status(); 0162 } 0163 0164 KisImportExportErrorCode KisImportExportManager::exportDocument(const QString& location, const QString& realLocation, const QByteArray& mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration, bool isAdvancedExporting) 0165 { 0166 ConversionResult result = convert(Export, location, realLocation, mimeType, showWarnings, exportConfiguration, false, isAdvancedExporting); 0167 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!result.isAsync(), ImportExportCodes::InternalError); 0168 0169 return result.status(); 0170 } 0171 0172 QFuture<KisImportExportErrorCode> KisImportExportManager::exportDocumentAsync(const QString &location, const QString &realLocation, const QByteArray &mimeType, 0173 KisImportExportErrorCode &status, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration, bool isAdvancedExporting) 0174 { 0175 ConversionResult result = convert(Export, location, realLocation, mimeType, showWarnings, exportConfiguration, true, isAdvancedExporting); 0176 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(result.isAsync() || 0177 !result.status().isOk(), QFuture<KisImportExportErrorCode>()); 0178 0179 status = result.status(); 0180 return result.futureStatus(); 0181 } 0182 0183 // The static method to figure out to which parts of the 0184 // graph this mimetype has a connection to. 0185 QStringList KisImportExportManager::supportedMimeTypes(Direction direction) 0186 { 0187 // Find the right mimetype by the extension 0188 QSet<QString> mimeTypes; 0189 // mimeTypes << KisDocument::nativeFormatMimeType() << "application/x-krita-paintoppreset" << "image/openraster"; 0190 0191 if (direction == KisImportExportManager::Import) { 0192 if (m_importMimeTypes.isEmpty()) { 0193 QList<KoJsonTrader::Plugin> list = KoJsonTrader::instance()->query("Krita/FileFilter", ""); 0194 Q_FOREACH(const KoJsonTrader::Plugin &loader, list) { 0195 QJsonObject json = loader.metaData().value("MetaData").toObject(); 0196 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) 0197 Q_FOREACH(const QString &mimetype, json.value("X-KDE-Import").toString().split(",", Qt::SkipEmptyParts)) { 0198 #else 0199 Q_FOREACH(const QString &mimetype, json.value("X-KDE-Import").toString().split(",", QString::SkipEmptyParts)) { 0200 #endif 0201 0202 //qDebug() << "Adding import mimetype" << mimetype << KisMimeDatabase::descriptionForMimeType(mimetype) << "from plugin" << loader; 0203 mimeTypes << mimetype; 0204 } 0205 } 0206 #if QT_VERSION >= QT_VERSION_CHECK(5,14,0) 0207 m_importMimeTypes = QList<QString>(mimeTypes.begin(), mimeTypes.end()); 0208 #else 0209 m_importMimeTypes = QList<QString>::fromSet(mimeTypes); 0210 #endif 0211 } 0212 return m_importMimeTypes; 0213 } 0214 else if (direction == KisImportExportManager::Export) { 0215 if (m_exportMimeTypes.isEmpty()) { 0216 QList<KoJsonTrader::Plugin> list = KoJsonTrader::instance()->query("Krita/FileFilter", ""); 0217 Q_FOREACH(const KoJsonTrader::Plugin &loader, list) { 0218 QJsonObject json = loader.metaData().value("MetaData").toObject(); 0219 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) 0220 Q_FOREACH(const QString &mimetype, json.value("X-KDE-Export").toString().split(",", Qt::SkipEmptyParts)) { 0221 #else 0222 Q_FOREACH(const QString &mimetype, json.value("X-KDE-Export").toString().split(",", QString::SkipEmptyParts)) { 0223 #endif 0224 0225 //qDebug() << "Adding export mimetype" << mimetype << KisMimeDatabase::descriptionForMimeType(mimetype) << "from plugin" << loader; 0226 mimeTypes << mimetype; 0227 } 0228 } 0229 #if QT_VERSION >= QT_VERSION_CHECK(5,14,0) 0230 m_exportMimeTypes = QList<QString>(mimeTypes.begin(), mimeTypes.end()); 0231 #else 0232 m_exportMimeTypes = QList<QString>::fromSet(mimeTypes); 0233 #endif 0234 0235 } 0236 return m_exportMimeTypes; 0237 } 0238 return QStringList(); 0239 } 0240 0241 KisImportExportFilter *KisImportExportManager::filterForMimeType(const QString &mimetype, KisImportExportManager::Direction direction) 0242 { 0243 int weight = -1; 0244 KisImportExportFilter *filter = 0; 0245 QList<KoJsonTrader::Plugin>list = KoJsonTrader::instance()->query("Krita/FileFilter", ""); 0246 0247 Q_FOREACH(const KoJsonTrader::Plugin &loader, list) { 0248 QJsonObject json = loader.metaData().value("MetaData").toObject(); 0249 QString directionKey = direction == Export ? "X-KDE-Export" : "X-KDE-Import"; 0250 0251 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) 0252 if (json.value(directionKey).toString().split(",", Qt::SkipEmptyParts).contains(mimetype)) { 0253 #else 0254 if (json.value(directionKey).toString().split(",", QString::SkipEmptyParts).contains(mimetype)) { 0255 #endif 0256 0257 KLibFactory *factory = qobject_cast<KLibFactory *>(loader.instance()); 0258 0259 if (!factory) { 0260 warnUI << loader.errorString(); 0261 continue; 0262 } 0263 0264 QObject* obj = factory->create<KisImportExportFilter>(0); 0265 if (!obj || !obj->inherits("KisImportExportFilter")) { 0266 delete obj; 0267 continue; 0268 } 0269 0270 KisImportExportFilter *f = qobject_cast<KisImportExportFilter*>(obj); 0271 if (!f) { 0272 delete obj; 0273 continue; 0274 } 0275 0276 KIS_ASSERT_RECOVER_NOOP(json.value("X-KDE-Weight").isDouble()); 0277 0278 int w = json.value("X-KDE-Weight").toInt(); 0279 0280 if (w > weight) { 0281 delete filter; 0282 filter = f; 0283 f->setObjectName(loader.fileName()); 0284 weight = w; 0285 } 0286 } 0287 } 0288 0289 if (filter) { 0290 filter->setMimeType(mimetype); 0291 } 0292 return filter; 0293 } 0294 0295 bool KisImportExportManager::batchMode(void) const 0296 { 0297 return m_document->fileBatchMode(); 0298 } 0299 0300 void KisImportExportManager::setUpdater(KoUpdaterPtr updater) 0301 { 0302 d->updater = updater; 0303 } 0304 0305 QString KisImportExportManager::askForAudioFileName(const QString &defaultDir, QWidget *parent) 0306 { 0307 KoFileDialog dialog(parent, KoFileDialog::ImportFiles, "ImportAudio"); 0308 0309 if (!defaultDir.isEmpty()) { 0310 dialog.setDefaultDir(defaultDir); 0311 } 0312 0313 QStringList mimeTypes; 0314 mimeTypes << "audio/mpeg"; 0315 mimeTypes << "audio/ogg"; 0316 mimeTypes << "audio/vorbis"; 0317 mimeTypes << "audio/vnd.wave"; 0318 mimeTypes << "audio/flac"; 0319 0320 dialog.setMimeTypeFilters(mimeTypes); 0321 dialog.setCaption(i18nc("@title:window", "Open Audio")); 0322 0323 return dialog.filename(); 0324 } 0325 0326 QString KisImportExportManager::getUriForAdditionalFile(const QString &defaultUri, QWidget *parent) 0327 { 0328 KoFileDialog dialog(parent, KoFileDialog::SaveFile, "Save Kra"); 0329 0330 KIS_SAFE_ASSERT_RECOVER_NOOP(!defaultUri.isEmpty()); 0331 0332 dialog.setDirectoryUrl(QUrl(defaultUri)); 0333 dialog.setMimeTypeFilters(QStringList("application/x-krita")); 0334 0335 return dialog.filename(); 0336 } 0337 0338 KisImportExportManager::ConversionResult KisImportExportManager::convert(KisImportExportManager::Direction direction, const QString &location, const QString& realLocation, const QString &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration, bool isAsync, bool isAdvancedExporting) 0339 { 0340 // export configuration is supported for export only 0341 KIS_SAFE_ASSERT_RECOVER_NOOP(direction == Export || !bool(exportConfiguration)); 0342 0343 QString typeName = mimeType; 0344 if (typeName.isEmpty()) { 0345 typeName = KisMimeDatabase::mimeTypeForFile(location, direction == KisImportExportManager::Export ? false : true); 0346 } 0347 0348 QSharedPointer<KisImportExportFilter> filter; 0349 0350 /** 0351 * Fetching a filter from the registry is a really expensive operation, 0352 * because it blocks all the threads. Cache the filter if possible. 0353 */ 0354 if (direction == KisImportExportManager::Export && 0355 d->cachedExportFilter && 0356 d->cachedExportFilterMimeType == typeName) { 0357 0358 filter = d->cachedExportFilter; 0359 } else { 0360 0361 filter = toQShared(filterForMimeType(typeName, direction)); 0362 0363 if (direction == Export) { 0364 d->cachedExportFilter = filter; 0365 d->cachedExportFilterMimeType = typeName; 0366 } 0367 } 0368 0369 if (!filter) { 0370 return KisImportExportErrorCode(ImportExportCodes::FileFormatNotSupported); 0371 } 0372 0373 filter->setFilename(location); 0374 filter->setRealFilename(realLocation); 0375 filter->setBatchMode(batchMode()); 0376 filter->setMimeType(typeName); 0377 0378 if (direction == Import) { 0379 KisMainWindow *kisMain = KisPart::instance()->currentMainwindow(); 0380 filter->setImportUserFeedBackInterface(new SynchronousUserFeedbackInterface(kisMain, batchMode())); 0381 } 0382 0383 if (!d->updater.isNull()) { 0384 // WARNING: The updater is not guaranteed to be persistent! If you ever want 0385 // to add progress reporting to "Save also as .kra", make sure you create 0386 // a separate KoProgressUpdater for that! 0387 0388 // WARNING2: the failsafe completion of the updater happens in the destructor 0389 // the filter. 0390 0391 filter->setUpdater(d->updater); 0392 } 0393 0394 QByteArray from, to; 0395 if (direction == Export) { 0396 from = m_document->nativeFormatMimeType(); 0397 to = typeName.toLatin1(); 0398 } 0399 else { 0400 from = typeName.toLatin1(); 0401 to = m_document->nativeFormatMimeType(); 0402 } 0403 0404 KIS_ASSERT_RECOVER_RETURN_VALUE( 0405 direction == Import || direction == Export, 0406 KisImportExportErrorCode(ImportExportCodes::InternalError)); // "bad conversion graph" 0407 0408 ConversionResult result = KisImportExportErrorCode(ImportExportCodes::OK); 0409 if (direction == Import) { 0410 0411 KisUsageLogger::log(QString("Importing %1 to %2. Location: %3. Real location: %4. Batchmode: %5") 0412 .arg(QString::fromLatin1(from), QString::fromLatin1(to), location, 0413 realLocation, QString::number(batchMode()))); 0414 0415 // async importing is not yet supported! 0416 KIS_SAFE_ASSERT_RECOVER_NOOP(!isAsync); 0417 0418 // FIXME: Dmitry says "this progress reporting code never worked. Initial idea was to implement it his way, but I stopped and didn't finish it" 0419 if (0 && !batchMode()) { 0420 KisAsyncActionFeedback f(i18n("Opening document..."), 0); 0421 result = f.runAction(std::bind(&KisImportExportManager::doImport, this, location, filter)); 0422 } else { 0423 result = doImport(location, filter); 0424 } 0425 if (result.status().isOk()) { 0426 KisImageSP image = m_document->image().toStrongRef(); 0427 if (image) { 0428 KIS_SAFE_ASSERT_RECOVER(image->colorSpace() != nullptr && image->colorSpace()->profile() != nullptr) 0429 { 0430 qWarning() << "Loaded a profile-less file without a fallback. Rejecting image " 0431 "opening"; 0432 return KisImportExportErrorCode(ImportExportCodes::InternalError); 0433 } 0434 KisUsageLogger::log(QString("Loaded image from %1. Size: %2 * %3 pixels, %4 dpi. Color " 0435 "model: %6 %5 (%7). Layers: %8") 0436 .arg(QString::fromLatin1(from), QString::number(image->width()), 0437 QString::number(image->height()), QString::number(image->xRes()), 0438 image->colorSpace()->colorModelId().name(), 0439 image->colorSpace()->colorDepthId().name(), 0440 image->colorSpace()->profile()->name(), 0441 QString::number(image->nlayers()))); 0442 } else { 0443 qWarning() << "The filter returned OK, but there is no image"; 0444 } 0445 0446 } 0447 else { 0448 KisUsageLogger::log(QString("Failed to load image from %1").arg(QString::fromLatin1(from))); 0449 } 0450 0451 } 0452 else /* if (direction == Export) */ { 0453 if (!exportConfiguration) { 0454 exportConfiguration = filter->lastSavedConfiguration(from, to); 0455 } 0456 0457 if (exportConfiguration) { 0458 fillStaticExportConfigurationProperties(exportConfiguration); 0459 } 0460 0461 bool alsoAsKra = false; 0462 bool askUser = askUserAboutExportConfiguration(filter, exportConfiguration, 0463 from, to, 0464 batchMode(), showWarnings, 0465 &alsoAsKra, isAdvancedExporting); 0466 0467 0468 if (!batchMode() && !askUser) { 0469 return KisImportExportErrorCode(ImportExportCodes::Cancelled); 0470 } 0471 0472 KisUsageLogger::log( 0473 QString( 0474 "Converting from %1 to %2. Location: %3. Real location: %4. Batchmode: %5. Configuration: %6") 0475 .arg(QString::fromLatin1(from), QString::fromLatin1(to), location, realLocation, 0476 QString::number(batchMode()), 0477 (exportConfiguration ? exportConfiguration->toXML() : "none"))); 0478 0479 const QString alsoAsKraLocation = alsoAsKra ? getAlsoAsKraLocation(location) : QString(); 0480 if (isAsync) { 0481 result = QtConcurrent::run(std::bind(&KisImportExportManager::doExport, this, location, filter, 0482 exportConfiguration, alsoAsKraLocation)); 0483 0484 // we should explicitly report that the exporting has been initiated 0485 result.setStatus(ImportExportCodes::OK); 0486 0487 } else if (!batchMode()) { 0488 KisAsyncActionFeedback f(i18n("Saving document..."), 0); 0489 result = f.runAction(std::bind(&KisImportExportManager::doExport, this, location, filter, 0490 exportConfiguration, alsoAsKraLocation)); 0491 } else { 0492 result = doExport(location, filter, exportConfiguration, alsoAsKraLocation); 0493 } 0494 0495 if (exportConfiguration && !batchMode()) { 0496 KisConfig(false).setExportConfiguration(typeName, exportConfiguration); 0497 } 0498 } 0499 return result; 0500 } 0501 0502 void KisImportExportManager::fillStaticExportConfigurationProperties(KisPropertiesConfigurationSP exportConfiguration, KisImageSP image) 0503 { 0504 KisPaintDeviceSP dev = image->projection(); 0505 const KoColorSpace* cs = dev->colorSpace(); 0506 const bool isThereAlpha = 0507 KisPainter::checkDeviceHasTransparency(image->projection()); 0508 0509 exportConfiguration->setProperty(KisImportExportFilter::ImageContainsTransparencyTag, isThereAlpha); 0510 exportConfiguration->setProperty(KisImportExportFilter::ColorModelIDTag, cs->colorModelId().id()); 0511 exportConfiguration->setProperty(KisImportExportFilter::ColorDepthIDTag, cs->colorDepthId().id()); 0512 0513 const bool sRGB = 0514 (cs->profile()->name().contains(QLatin1String("srgb"), Qt::CaseInsensitive) && 0515 !cs->profile()->name().contains(QLatin1String("g10"))); 0516 exportConfiguration->setProperty(KisImportExportFilter::sRGBTag, sRGB); 0517 0518 ColorPrimaries primaries = cs->profile()->getColorPrimaries(); 0519 if (primaries >= PRIMARIES_ADOBE_RGB_1998) { 0520 primaries = PRIMARIES_UNSPECIFIED; 0521 } 0522 TransferCharacteristics transferFunction = 0523 cs->profile()->getTransferCharacteristics(); 0524 if (transferFunction >= TRC_GAMMA_1_8) { 0525 transferFunction = TRC_UNSPECIFIED; 0526 } 0527 exportConfiguration->setProperty(KisImportExportFilter::CICPPrimariesTag, 0528 static_cast<int>(primaries)); 0529 exportConfiguration->setProperty( 0530 KisImportExportFilter::CICPTransferCharacteristicsTag, 0531 static_cast<int>(transferFunction)); 0532 exportConfiguration->setProperty(KisImportExportFilter::HDRTag, cs->hasHighDynamicRange()); 0533 } 0534 0535 0536 void KisImportExportManager::fillStaticExportConfigurationProperties(KisPropertiesConfigurationSP exportConfiguration) 0537 { 0538 return fillStaticExportConfigurationProperties(exportConfiguration, m_document->image()); 0539 } 0540 0541 bool KisImportExportManager::askUserAboutExportConfiguration( 0542 QSharedPointer<KisImportExportFilter> filter, 0543 KisPropertiesConfigurationSP exportConfiguration, 0544 const QByteArray &from, 0545 const QByteArray &to, 0546 const bool batchMode, 0547 const bool showWarnings, 0548 bool *alsoAsKra, 0549 bool isAdvancedExporting) 0550 { 0551 0552 // prevents the animation renderer from running this code 0553 0554 0555 const QString mimeUserDescription = KisMimeDatabase::descriptionForMimeType(to); 0556 0557 QStringList warnings; 0558 QStringList errors; 0559 0560 { 0561 KisPreExportChecker checker; 0562 checker.check(m_document->image(), filter->exportChecks()); 0563 0564 warnings = checker.warnings(); 0565 errors = checker.errors(); 0566 } 0567 0568 KisConfigWidget *wdg = 0; 0569 0570 if (QThread::currentThread() == qApp->thread()) { 0571 wdg = filter->createConfigurationWidget(0, from, to); 0572 0573 KisMainWindow *kisMain = KisPart::instance()->currentMainwindow(); 0574 if (wdg && kisMain) { 0575 KisViewManager *manager = kisMain->viewManager(); 0576 wdg->setView(manager); 0577 } 0578 } 0579 0580 // Extra checks that cannot be done by the checker, because the checker only has access to the image. 0581 if (!m_document->assistants().isEmpty() && to != m_document->nativeFormatMimeType()) { 0582 warnings.append(i18nc("image conversion warning", "The image contains <b>assistants</b>. The assistants will not be saved.")); 0583 } 0584 if (m_document->referenceImagesLayer() && m_document->referenceImagesLayer()->shapeCount() > 0 && to != m_document->nativeFormatMimeType()) { 0585 warnings.append(i18nc("image conversion warning", "The image contains <b>reference images</b>. The reference images will not be saved.")); 0586 } 0587 if (m_document->guidesConfig().hasGuides() && to != m_document->nativeFormatMimeType()) { 0588 warnings.append(i18nc("image conversion warning", "The image contains <b>guides</b>. The guides will not be saved.")); 0589 } 0590 if (!m_document->gridConfig().isDefault() && to != m_document->nativeFormatMimeType()) { 0591 warnings.append(i18nc("image conversion warning", "The image contains a <b>custom grid configuration</b>. The configuration will not be saved.")); 0592 } 0593 0594 if (!batchMode && !errors.isEmpty()) { 0595 QString error = "<html><body><p><b>" 0596 + i18n("Error: cannot save this image as a %1.", mimeUserDescription) 0597 + "</b> " + i18n("Reasons:") + "</p>" 0598 + "<p/><ul>"; 0599 Q_FOREACH(const QString &w, errors) { 0600 error += "\n<li>" + w + "</li>"; 0601 } 0602 0603 error += "</ul>"; 0604 0605 QMessageBox::critical(KisPart::instance()->currentMainwindow(), i18nc("@title:window", "Krita: Export Error"), error); 0606 return false; 0607 } 0608 0609 if (!batchMode && (wdg || !warnings.isEmpty() || isAdvancedExporting)) { 0610 QWidget *page = new QWidget(); 0611 QVBoxLayout *layout = new QVBoxLayout(page); 0612 0613 if (showWarnings && !warnings.isEmpty()) { 0614 QHBoxLayout *hLayout = new QHBoxLayout(); 0615 0616 QLabel *labelWarning = new QLabel(); 0617 labelWarning->setPixmap(KisIconUtils::loadIcon("dialog-warning").pixmap(48, 48)); 0618 hLayout->addWidget(labelWarning); 0619 0620 KisPopupButton *bn = new KisPopupButton(0); 0621 0622 bn->setText(i18nc("Keep the extra space at the end of the sentence, please", "Warning: saving as a %1 will lose information from your image. ", mimeUserDescription)); 0623 bn->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); 0624 0625 hLayout->addWidget(bn); 0626 0627 layout->addLayout(hLayout); 0628 0629 QTextBrowser *browser = new QTextBrowser(); 0630 browser->setMinimumWidth(bn->width()); 0631 bn->setPopupWidget(browser); 0632 0633 QString warning = "<html><body><p><b>" 0634 + i18n("You will lose information when saving this image as a %1.", mimeUserDescription); 0635 0636 if (warnings.size() == 1) { 0637 warning += "</b> " + i18n("Reason:") + "</p>"; 0638 } 0639 else { 0640 warning += "</b> " + i18n("Reasons:") + "</p>"; 0641 } 0642 warning += "<p/><ul>"; 0643 0644 Q_FOREACH(const QString &w, warnings) { 0645 warning += "\n<li>" + w + "</li>"; 0646 } 0647 0648 warning += "</ul>"; 0649 browser->setHtml(warning); 0650 } 0651 0652 QTabWidget *box = new QTabWidget; 0653 if (wdg) { 0654 wdg->setConfiguration(exportConfiguration); 0655 box->addTab(wdg,i18n("Options")); 0656 } 0657 0658 auto dlgImageSize = new DlgImageSize(box, m_document->image()->width(), m_document->image()->height(), m_document->image()->yRes()); 0659 dlgImageSize->setButtons(KoDialog::None); 0660 0661 if(isAdvancedExporting) 0662 { 0663 box->addTab(dlgImageSize,i18n("Resize")); 0664 } 0665 layout->addWidget(box); 0666 0667 QCheckBox *chkAlsoAsKra = 0; 0668 if (showWarnings && !warnings.isEmpty()) { 0669 chkAlsoAsKra = new QCheckBox(i18n("Also save your image as a Krita file.")); 0670 chkAlsoAsKra->setChecked(KisConfig(true).readEntry<bool>("AlsoSaveAsKra", false)); 0671 layout->addWidget(chkAlsoAsKra); 0672 } 0673 0674 KoDialog dlg(qApp->activeWindow()); 0675 dlg.setMainWidget(page); 0676 page->setParent(&dlg); 0677 dlg.setButtons(KoDialog::Ok | KoDialog::Cancel); 0678 dlg.setWindowTitle(mimeUserDescription); 0679 0680 if (showWarnings || wdg || isAdvancedExporting) { 0681 if (!dlg.exec()) { 0682 return false; 0683 } 0684 } 0685 0686 *alsoAsKra = false; 0687 if (chkAlsoAsKra) { 0688 KisConfig(false).writeEntry<bool>("AlsoSaveAsKra", chkAlsoAsKra->isChecked()); 0689 *alsoAsKra = chkAlsoAsKra->isChecked(); 0690 } 0691 0692 if(isAdvancedExporting) { 0693 const QSize desiredSize(dlgImageSize->desiredWidth(), dlgImageSize->desiredHeight()); 0694 double res = dlgImageSize->desiredResolution(); 0695 m_document->savingImage()->scaleImage(desiredSize,res,res,dlgImageSize->filterType()); 0696 m_document->savingImage()->waitForDone(); 0697 KisLayerUtils::forceAllDelayedNodesUpdate(m_document->savingImage()->root()); 0698 m_document->savingImage()->waitForDone(); 0699 } 0700 0701 if (wdg) { 0702 *exportConfiguration = *wdg->configuration(); 0703 } 0704 } 0705 0706 return true; 0707 } 0708 0709 KisImportExportErrorCode KisImportExportManager::doImport(const QString &location, QSharedPointer<KisImportExportFilter> filter) 0710 { 0711 QFile file(location); 0712 if (!file.exists()) { 0713 return ImportExportCodes::FileNotExist; 0714 } 0715 0716 if (filter->supportsIO() && !file.open(QFile::ReadOnly)) { 0717 return KisImportExportErrorCode(KisImportExportErrorCannotRead(file.error())); 0718 } 0719 0720 KisImportExportErrorCode status = filter->convert(m_document, &file, KisPropertiesConfigurationSP()); 0721 0722 if (file.isOpen()) { 0723 file.close(); 0724 } 0725 0726 return status; 0727 } 0728 0729 KisImportExportErrorCode KisImportExportManager::doExport(const QString &location, 0730 QSharedPointer<KisImportExportFilter> filter, 0731 KisPropertiesConfigurationSP exportConfiguration, 0732 const QString alsoAsKraLocation) 0733 { 0734 KisImportExportErrorCode status = 0735 doExportImpl(location, filter, exportConfiguration); 0736 0737 if (!alsoAsKraLocation.isNull() && status.isOk()) { 0738 QByteArray mime = m_document->nativeFormatMimeType(); 0739 QSharedPointer<KisImportExportFilter> filter( 0740 filterForMimeType(QString::fromLatin1(mime), Export)); 0741 0742 KIS_SAFE_ASSERT_RECOVER_NOOP(filter); 0743 0744 if (filter) { 0745 filter->setFilename(alsoAsKraLocation); 0746 0747 KisPropertiesConfigurationSP kraExportConfiguration = 0748 filter->lastSavedConfiguration(mime, mime); 0749 0750 status = doExportImpl(alsoAsKraLocation, filter, kraExportConfiguration); 0751 } else { 0752 status = ImportExportCodes::FileFormatIncorrect; 0753 } 0754 } 0755 0756 return status; 0757 } 0758 0759 // Temporary workaround until QTBUG-57299 is fixed. 0760 // 02-10-2019 update: the bug is closed, but we've still seen this issue. 0761 // and without using QSaveFile the issue can still occur 0762 // when QFile::copy fails because Dropbox/Google/OneDrive 0763 // locks the target file. 0764 // 02-24-2022 update: Added macOS since QSaveFile does not work on sandboxed krita 0765 // It can work if user gives access to the container dir, but 0766 // we cannot guarantee the user gave us permission. 0767 #if !(defined(Q_OS_WIN) || defined(Q_OS_MACOS)) 0768 #define USE_QSAVEFILE 0769 #endif 0770 0771 KisImportExportErrorCode KisImportExportManager::doExportImpl(const QString &location, QSharedPointer<KisImportExportFilter> filter, KisPropertiesConfigurationSP exportConfiguration) 0772 { 0773 #ifdef USE_QSAVEFILE 0774 QSaveFile file(location); 0775 file.setDirectWriteFallback(true); 0776 if (filter->supportsIO() && !file.open(QFile::WriteOnly)) { 0777 #else 0778 QFileInfo fi(location); 0779 QTemporaryFile file(QDir::tempPath() + "/.XXXXXX.kra"); 0780 if (filter->supportsIO() && !file.open()) { 0781 #endif 0782 KisImportExportErrorCannotWrite result(file.error()); 0783 #ifdef USE_QSAVEFILE 0784 file.cancelWriting(); 0785 #endif 0786 return result; 0787 } 0788 0789 KisImportExportErrorCode status = filter->convert(m_document, &file, exportConfiguration); 0790 0791 if (filter->supportsIO()) { 0792 if (!status.isOk()) { 0793 #ifdef USE_QSAVEFILE 0794 file.cancelWriting(); 0795 #endif 0796 } else { 0797 #ifdef USE_QSAVEFILE 0798 if (!file.commit()) { 0799 qWarning() << "Could not commit QSaveFile"; 0800 status = KisImportExportErrorCannotWrite(file.error()); 0801 } 0802 #else 0803 file.flush(); 0804 file.close(); 0805 QFile target(location); 0806 if (target.exists()) { 0807 // There should already be a .kra~ backup 0808 target.remove(); 0809 } 0810 if (!file.copy(location)) { 0811 file.setAutoRemove(false); 0812 return KisImportExportErrorCannotWrite(file.error()); 0813 } 0814 #endif 0815 } 0816 } 0817 0818 if (status.isOk()) { 0819 // Do some minimal verification 0820 QString verificationResult = filter->verify(location); 0821 if (!verificationResult.isEmpty()) { 0822 status = KisImportExportErrorCode(ImportExportCodes::ErrorWhileWriting); 0823 m_document->setErrorMessage(verificationResult); 0824 } 0825 } 0826 0827 return status; 0828 0829 } 0830 0831 QString KisImportExportManager::getAlsoAsKraLocation(const QString location) const 0832 { 0833 #ifdef Q_OS_ANDROID 0834 return getUriForAdditionalFile(location, nullptr); 0835 #else 0836 return location + ".kra"; 0837 #endif 0838 } 0839 0840 #include <KisMimeDatabase.h>