Krita Source Code Documentation
Loading...
Searching...
No Matches
KisImportExportManager.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2016 Boudewijn Rempt <boud@valdyas.org>
3 *
4 * SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6
8
9#include <QDir>
10#include <QFile>
11#include <QLabel>
12#include <QVBoxLayout>
13#include <QList>
14#include <QApplication>
15#include <QByteArray>
16#include <QPluginLoader>
17#include <QFileInfo>
18#include <QMessageBox>
19#include <QJsonObject>
20#include <QTextBrowser>
21#include <QCheckBox>
22#include <QSaveFile>
23#include <QGroupBox>
24#include <QFuture>
25#include <QtConcurrentRun>
26#include <QTemporaryFile>
27
28#include <klocalizedstring.h>
29#include <ksqueezedtextlabel.h>
30#include <kpluginfactory.h>
31
32#include <KisMimeDatabase.h>
33#include <KisPart.h>
34#include <KisPopupButton.h>
35#include <KisPreExportChecker.h>
36#include <KisUsageLogger.h>
37#include <KoColorProfile.h>
39#include <KoDialog.h>
40#include <KoFileDialog.h>
41#include <KoJsonTrader.h>
42#include <KoProgressUpdater.h>
43#include <kis_assert.h>
44#include <kis_config_widget.h>
45#include <kis_debug.h>
46#include <kis_icon_utils.h>
47#include <kis_image.h>
48#include <kis_iterator_ng.h>
49#include <kis_layer_utils.h>
50#include <kis_paint_layer.h>
51#include <kis_painter.h>
52
53#include "KisDocument.h"
56#include "KisMainWindow.h"
60#include "kis_config.h"
61#include "kis_grid_config.h"
62#include "kis_guides_config.h"
64#include <kis_filter_mask.h>
65
67
68// static cache for import and export mimetypes
71
72namespace {
73struct SynchronousUserFeedbackInterface : KisImportUserFeedbackInterface
74{
75 SynchronousUserFeedbackInterface(QWidget *parent, bool batchMode)
76 : m_parent(parent)
77 , m_batchMode(batchMode)
78 {
79 }
80
81 Result askUser(AskCallback callback) override
82 {
83 if (m_batchMode) return SuppressedByBatchMode;
84 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(m_parent, SuppressedByBatchMode);
85
86 return callback(m_parent) ? Success : UserCancelled;
87 }
88
89 QWidget *m_parent {nullptr};
90 bool m_batchMode {false};
91};
92
93}
94
95
104
107 {
108 }
109
115
121
122 bool isAsync() const {
123 return m_isAsync;
124 }
125
127 // if the result is not async, then it means some failure happened,
128 // just return a cancelled future
130
131 return m_futureStatus;
132 }
133
135 return m_status;
136 }
137
141private:
142 bool m_isAsync = false;
145};
146
147
149 : m_document(document)
150 , d(new Private)
151{
152}
153
158
159KisImportExportErrorCode KisImportExportManager::importDocument(const QString& location, const QString& mimeType)
160{
161 ConversionResult result = convert(Import, location, location, mimeType, false, 0, false);
163
164 return result.status();
165}
166
167KisImportExportErrorCode KisImportExportManager::exportDocument(const QString& location, const QString& realLocation, const QByteArray& mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration, bool isAdvancedExporting)
168{
169 ConversionResult result = convert(Export, location, realLocation, mimeType, showWarnings, exportConfiguration, false, isAdvancedExporting);
171
172 return result.status();
173}
174
175QFuture<KisImportExportErrorCode> KisImportExportManager::exportDocumentAsync(const QString &location, const QString &realLocation, const QByteArray &mimeType,
176 KisImportExportErrorCode &status, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration, bool isAdvancedExporting)
177{
178 ConversionResult result = convert(Export, location, realLocation, mimeType, showWarnings, exportConfiguration, true, isAdvancedExporting);
181
182 status = result.status();
183 return result.futureStatus();
184}
185
186// The static method to figure out to which parts of the
187// graph this mimetype has a connection to.
189{
190 // Find the right mimetype by the extension
191 QSet<QString> mimeTypes;
192 // mimeTypes << KisDocument::nativeFormatMimeType() << "application/x-krita-paintoppreset" << "image/openraster";
193
194 if (direction == KisImportExportManager::Import) {
195 if (m_importMimeTypes.isEmpty()) {
196 QList<KoJsonTrader::Plugin> list = KoJsonTrader::instance()->query("Krita/FileFilter", "");
197 Q_FOREACH(const KoJsonTrader::Plugin &loader, list) {
198 QJsonObject json = loader.metaData().value("MetaData").toObject();
199 Q_FOREACH(const QString &mimetype, json.value("X-KDE-Import").toString().split(",", Qt::SkipEmptyParts)) {
200
201 //qDebug() << "Adding import mimetype" << mimetype << KisMimeDatabase::descriptionForMimeType(mimetype) << "from plugin" << loader;
202 mimeTypes << mimetype;
203 }
204 }
205 m_importMimeTypes = QList<QString>(mimeTypes.begin(), mimeTypes.end());
206 }
207 return m_importMimeTypes;
208 }
209 else if (direction == KisImportExportManager::Export) {
210 if (m_exportMimeTypes.isEmpty()) {
211 QList<KoJsonTrader::Plugin> list = KoJsonTrader::instance()->query("Krita/FileFilter", "");
212 Q_FOREACH(const KoJsonTrader::Plugin &loader, list) {
213 QJsonObject json = loader.metaData().value("MetaData").toObject();
214 Q_FOREACH(const QString &mimetype, json.value("X-KDE-Export").toString().split(",", Qt::SkipEmptyParts)) {
215
216 //qDebug() << "Adding export mimetype" << mimetype << KisMimeDatabase::descriptionForMimeType(mimetype) << "from plugin" << loader;
217 mimeTypes << mimetype;
218 }
219 }
220 m_exportMimeTypes = QList<QString>(mimeTypes.begin(), mimeTypes.end());
221 }
222 return m_exportMimeTypes;
223 }
224 return QStringList();
225}
226
228{
229 int weight = -1;
230 KisImportExportFilter *filter = 0;
231 QList<KoJsonTrader::Plugin>list = KoJsonTrader::instance()->query("Krita/FileFilter", "");
232
233 Q_FOREACH(const KoJsonTrader::Plugin &loader, list) {
234 QJsonObject json = loader.metaData().value("MetaData").toObject();
235 QString directionKey = direction == Export ? "X-KDE-Export" : "X-KDE-Import";
236
237 if (json.value(directionKey).toString().split(",", Qt::SkipEmptyParts).contains(mimetype)) {
238
239 KPluginFactory *factory = qobject_cast<KPluginFactory *>(loader.instance());
240
241 if (!factory) {
242 warnUI << loader.errorString();
243 continue;
244 }
245
246 QObject* obj = factory->create<KisImportExportFilter>(0);
247 if (!obj || !obj->inherits("KisImportExportFilter")) {
248 delete obj;
249 continue;
250 }
251
252 KisImportExportFilter *f = qobject_cast<KisImportExportFilter*>(obj);
253 if (!f) {
254 delete obj;
255 continue;
256 }
257
258 KIS_ASSERT_RECOVER_NOOP(json.value("X-KDE-Weight").isDouble());
259
260 int w = json.value("X-KDE-Weight").toInt();
261
262 if (w > weight) {
263 delete filter;
264 filter = f;
265 f->setObjectName(loader.fileName());
266 weight = w;
267 }
268 }
269 }
270
271 if (filter) {
272 filter->setMimeType(mimetype);
273 }
274 return filter;
275}
276
278{
279 return m_document->fileBatchMode();
280}
281
283{
284 d->updater = updater;
285}
286
287QString KisImportExportManager::askForAudioFileName(const QString &defaultDir, QWidget *parent)
288{
289 KoFileDialog dialog(parent, KoFileDialog::ImportFiles, "ImportAudio");
290
291 if (!defaultDir.isEmpty()) {
292 dialog.setDefaultDir(defaultDir);
293 }
294
295 QStringList mimeTypes;
296 mimeTypes << "audio/mpeg";
297 mimeTypes << "audio/ogg";
298 mimeTypes << "audio/vorbis";
299 mimeTypes << "audio/vnd.wave";
300 mimeTypes << "audio/flac";
301
302 dialog.setMimeTypeFilters(mimeTypes);
303 dialog.setCaption(i18nc("@title:window", "Open Audio"));
304
305 return dialog.filename();
306}
307
308QString KisImportExportManager::getUriForAdditionalFile(const QString &defaultUri, QWidget *parent)
309{
310 KoFileDialog dialog(parent, KoFileDialog::SaveFile, "Save Kra");
311
312 KIS_SAFE_ASSERT_RECOVER_NOOP(!defaultUri.isEmpty());
313
314 dialog.setDirectoryUrl(QUrl(defaultUri));
315 dialog.setMimeTypeFilters(QStringList("application/x-krita"));
316
317 return dialog.filename();
318}
319
320KisImportExportManager::ConversionResult KisImportExportManager::convert(KisImportExportManager::Direction direction, const QString &location, const QString& realLocation, const QString &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration, bool isAsync, bool isAdvancedExporting)
321{
322 // export configuration is supported for export only
323 KIS_SAFE_ASSERT_RECOVER_NOOP(direction == Export || !bool(exportConfiguration));
324
325 QString typeName = mimeType;
326 if (typeName.isEmpty()) {
327 typeName = KisMimeDatabase::mimeTypeForFile(location, direction == KisImportExportManager::Export ? false : true);
328 }
329
331
336 if (direction == KisImportExportManager::Export &&
337 d->cachedExportFilter &&
338 d->cachedExportFilterMimeType == typeName) {
339
340 filter = d->cachedExportFilter;
341 } else {
342
343 filter = toQShared(filterForMimeType(typeName, direction));
344
345 if (direction == Export) {
346 d->cachedExportFilter = filter;
347 d->cachedExportFilterMimeType = typeName;
348 }
349 }
350
351 if (!filter) {
353 }
354
355 filter->setFilename(location);
356 filter->setRealFilename(realLocation);
357 filter->setBatchMode(batchMode());
358 filter->setMimeType(typeName);
359
360 if (direction == Import) {
362 filter->setImportUserFeedBackInterface(new SynchronousUserFeedbackInterface(kisMain, batchMode()));
363 }
364
365 if (!d->updater.isNull()) {
366 // WARNING: The updater is not guaranteed to be persistent! If you ever want
367 // to add progress reporting to "Save also as .kra", make sure you create
368 // a separate KoProgressUpdater for that!
369
370 // WARNING2: the failsafe completion of the updater happens in the destructor
371 // the filter.
372
373 filter->setUpdater(d->updater);
374 }
375
376 QByteArray from, to;
377 if (direction == Export) {
379 to = typeName.toLatin1();
380 }
381 else {
382 from = typeName.toLatin1();
384 }
385
387 direction == Import || direction == Export,
389
391 if (direction == Import) {
392
393 KisUsageLogger::log(QString("Importing %1 to %2. Location: %3. Real location: %4. Batchmode: %5")
394 .arg(QString::fromLatin1(from), QString::fromLatin1(to), location,
395 realLocation, QString::number(batchMode())));
396
397 // async importing is not yet supported!
399
400 // 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"
401 if (0 && !batchMode()) {
402 KisAsyncActionFeedback f(i18n("Opening document..."), 0);
403 result = f.runAction(std::bind(&KisImportExportManager::doImport, this, location, filter));
404 } else {
405 result = doImport(location, filter);
406 }
407 if (result.status().isOk()) {
408 KisImageSP image = m_document->image().toStrongRef();
409 if (image) {
410 KIS_SAFE_ASSERT_RECOVER(image->colorSpace() != nullptr && image->colorSpace()->profile() != nullptr)
411 {
412 qWarning() << "Loaded a profile-less file without a fallback. Rejecting image "
413 "opening";
415 }
416 KisUsageLogger::log(QString("Loaded image from %1. Size: %2 * %3 pixels, %4 dpi. Color "
417 "model: %6 %5 (%7). Layers: %8")
418 .arg(QString::fromLatin1(from), QString::number(image->width()),
419 QString::number(image->height()), QString::number(image->xRes()),
420 image->colorSpace()->colorModelId().name(),
421 image->colorSpace()->colorDepthId().name(),
422 image->colorSpace()->profile()->name(),
423 QString::number(image->nlayers())));
424 } else {
425 qWarning() << "The filter returned OK, but there is no image";
426 }
427
428 }
429 else {
430 KisUsageLogger::log(QString("Failed to load image from %1").arg(QString::fromLatin1(from)));
431 }
432
433 }
434 else /* if (direction == Export) */ {
435 if (!exportConfiguration) {
436 exportConfiguration = filter->lastSavedConfiguration(from, to);
437 }
438
439 if (exportConfiguration) {
440 fillStaticExportConfigurationProperties(exportConfiguration);
441 }
442
443 bool alsoAsKra = false;
444 bool askUser = askUserAboutExportConfiguration(filter, exportConfiguration,
445 from, to,
446 batchMode(), showWarnings,
447 &alsoAsKra, isAdvancedExporting);
448
449
450 if (!batchMode() && !askUser) {
452 }
453
455 QString(
456 "Converting from %1 to %2. Location: %3. Real location: %4. Batchmode: %5. Configuration: %6")
457 .arg(QString::fromLatin1(from), QString::fromLatin1(to), location, realLocation,
458 QString::number(batchMode()),
459 (exportConfiguration ? exportConfiguration->toXML() : "none")));
460
461 const QString alsoAsKraLocation = alsoAsKra ? getAlsoAsKraLocation(location) : QString();
462 if (isAsync) {
463 result = QtConcurrent::run(std::bind(&KisImportExportManager::doExport, this, location, filter,
464 exportConfiguration, alsoAsKraLocation));
465
466 // we should explicitly report that the exporting has been initiated
468
469 } else if (!batchMode()) {
470 KisAsyncActionFeedback f(i18n("Saving document..."), 0);
471 result = f.runAction(std::bind(&KisImportExportManager::doExport, this, location, filter,
472 exportConfiguration, alsoAsKraLocation));
473 } else {
474 result = doExport(location, filter, exportConfiguration, alsoAsKraLocation);
475 }
476
477 if (exportConfiguration && !batchMode()) {
478 KisConfig(false).setExportConfiguration(typeName, exportConfiguration);
479 }
480 }
481 return result;
482}
483
485{
486 KisPaintDeviceSP dev = image->projection();
487 const KoColorSpace* cs = dev->colorSpace();
488 const bool isThereAlpha =
490
491 exportConfiguration->setProperty(KisImportExportFilter::ImageContainsTransparencyTag, isThereAlpha);
492 exportConfiguration->setProperty(KisImportExportFilter::ColorModelIDTag, cs->colorModelId().id());
493 exportConfiguration->setProperty(KisImportExportFilter::ColorDepthIDTag, cs->colorDepthId().id());
494
495 const bool sRGB =
496 (cs->profile()->name().contains(QLatin1String("srgb"), Qt::CaseInsensitive) &&
497 !cs->profile()->name().contains(QLatin1String("g10")));
498 exportConfiguration->setProperty(KisImportExportFilter::sRGBTag, sRGB);
499
500 ColorPrimaries primaries = cs->profile()->getColorPrimaries();
501 if (primaries >= PRIMARIES_ADOBE_RGB_1998) {
502 primaries = PRIMARIES_UNSPECIFIED;
503 }
504 TransferCharacteristics transferFunction =
506 if (transferFunction >= TRC_GAMMA_1_8) {
507 transferFunction = TRC_UNSPECIFIED;
508 }
509 exportConfiguration->setProperty(KisImportExportFilter::CICPPrimariesTag,
510 static_cast<int>(primaries));
511 exportConfiguration->setProperty(
513 static_cast<int>(transferFunction));
514 exportConfiguration->setProperty(KisImportExportFilter::HDRTag, cs->hasHighDynamicRange());
515}
516
517
522
525 KisPropertiesConfigurationSP exportConfiguration,
526 const QByteArray &from,
527 const QByteArray &to,
528 const bool batchMode,
529 const bool showWarnings,
530 bool *alsoAsKra,
531 bool isAdvancedExporting)
532{
533
534 // prevents the animation renderer from running this code
535
536
537 const QString mimeUserDescription = KisMimeDatabase::descriptionForMimeType(to);
538
539 QStringList warnings;
540 QStringList errors;
541
542 {
543 KisPreExportChecker checker;
544 checker.check(m_document->image(), filter->exportChecks());
545
546 warnings = checker.warnings();
547 errors = checker.errors();
548 }
549
550 KisConfigWidget *wdg = 0;
551
552 if (QThread::currentThread() == qApp->thread()) {
553 wdg = filter->createConfigurationWidget(0, from, to);
554
556 if (wdg && kisMain) {
557 KisViewManager *manager = kisMain->viewManager();
558 wdg->setView(manager);
559 }
560 }
561
562 // Extra checks that cannot be done by the checker, because the checker only has access to the image.
563 if (!m_document->assistants().isEmpty() && to != m_document->nativeFormatMimeType()) {
564 warnings.append(i18nc("image conversion warning", "The image contains <b>assistants</b>. The assistants will not be saved."));
565 }
567 warnings.append(i18nc("image conversion warning", "The image contains <b>reference images</b>. The reference images will not be saved."));
568 }
569 if (m_document->guidesConfig().hasGuides() && !filter->exportSupportsGuides()) {
570 warnings.append(i18nc("image conversion warning", "The image contains <b>guides</b>. The guides will not be saved."));
571 }
573 warnings.append(i18nc("image conversion warning", "The image contains a <b>custom grid configuration</b>. The configuration will not be saved."));
574 }
575
576 bool shouldFlattenTheImageBeforeScaling = false;
577
578 if (isAdvancedExporting) {
579 QMap<QString, KisExportCheckBase *> exportChecks = filter->exportChecks();
580
581 const bool filterSupportsMultilayerExport =
582 exportChecks.contains("MultiLayerCheck") &&
583 exportChecks["MultiLayerCheck"]->checkNeeded(m_document->image()) &&
584 exportChecks["MultiLayerCheck"]->check(m_document->image()) == KisExportCheckBase::SUPPORTED;
585
586 if (!filterSupportsMultilayerExport) {
587 shouldFlattenTheImageBeforeScaling = true;
588 } else {
589 if (KisLayerUtils::findNodeByType<KisAdjustmentLayer>(m_document->image()->root())) {
590 shouldFlattenTheImageBeforeScaling = true;
591 warnings.append(i18nc("image conversion warning", "Trying to perform an Advanced Export with the image containing a <b>filter layer</b>. The image will be <b>flattened</b> before resizing."));
592 }
593 if (KisLayerUtils::findNodeByType<KisFilterMask>(m_document->image()->root())) {
594 shouldFlattenTheImageBeforeScaling = true;
595 warnings.append(i18nc("image conversion warning", "Trying to perform an Advanced Export with the image containing a <b>filter mask</b>. The image will be <b>flattened</b> before resizing."));
596 }
597 bool hasLayerStyles =
599 [] (KisNodeSP node) {
600 KisLayer *layer = dynamic_cast<KisLayer*>(node.data());
601 return layer && layer->layerStyle();
602 });
603
604 if (hasLayerStyles) {
605 shouldFlattenTheImageBeforeScaling = true;
606 warnings.append(i18nc("image conversion warning", "Trying to perform an Advanced Export with the image containing a <b>layer style</b>. The image will be <b>flattened</b> before resizing."));
607 }
608 }
609 }
610
611 if (!batchMode && !errors.isEmpty()) {
612 QString error = "<html><body><p><b>"
613 + i18n("Error: cannot save this image as a %1.", mimeUserDescription)
614 + "</b> " + i18n("Reasons:") + "</p>"
615 + "<p/><ul>";
616 Q_FOREACH(const QString &w, errors) {
617 error += "\n<li>" + w + "</li>";
618 }
619
620 error += "</ul>";
621
622 QMessageBox::critical(KisPart::instance()->currentMainwindow(), i18nc("@title:window", "Krita: Export Error"), error);
623 return false;
624 }
625
626 if (!batchMode && (wdg || !warnings.isEmpty() || isAdvancedExporting)) {
627 QWidget *page = new QWidget();
628 QVBoxLayout *layout = new QVBoxLayout(page);
629
630 if (showWarnings && !warnings.isEmpty()) {
631 QHBoxLayout *hLayout = new QHBoxLayout();
632
633 QLabel *labelWarning = new QLabel();
634 labelWarning->setPixmap(KisIconUtils::loadIcon("dialog-warning").pixmap(48, 48));
635 hLayout->addWidget(labelWarning);
636
637 KisPopupButton *bn = new KisPopupButton(0);
638
639 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));
640 bn->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
641
642 hLayout->addWidget(bn);
643
644 layout->addLayout(hLayout);
645
646 QTextBrowser *browser = new QTextBrowser();
647 browser->setMinimumWidth(bn->width());
648 bn->setPopupWidget(browser);
649
650 QString warning = "<html><body><p><b>"
651 + i18n("You will lose information when saving this image as a %1.", mimeUserDescription);
652
653 if (warnings.size() == 1) {
654 warning += "</b> " + i18n("Reason:") + "</p>";
655 }
656 else {
657 warning += "</b> " + i18n("Reasons:") + "</p>";
658 }
659 warning += "<p/><ul>";
660
661 Q_FOREACH(const QString &w, warnings) {
662 warning += "\n<li>" + w + "</li>";
663 }
664
665 warning += "</ul>";
666 browser->setHtml(warning);
667 }
668
669 QTabWidget *box = new QTabWidget;
670 if (wdg) {
671 wdg->setConfiguration(exportConfiguration);
672 box->addTab(wdg,i18n("Options"));
673 }
674
675 WdgImageSize *wdgImageSize = nullptr;
676
677 if (isAdvancedExporting) {
678 wdgImageSize = new WdgImageSize(box, m_document->image()->width(), m_document->image()->height(), m_document->image()->yRes());
679
680 box->addTab(wdgImageSize,i18n("Resize"));
681 }
682 layout->addWidget(box);
683
684 QCheckBox *chkAlsoAsKra = 0;
685 if (showWarnings && !warnings.isEmpty()) {
686 chkAlsoAsKra = new QCheckBox(i18n("Also save your image as a Krita file."));
687 chkAlsoAsKra->setChecked(KisConfig(true).readEntry<bool>("AlsoSaveAsKra", false));
688 layout->addWidget(chkAlsoAsKra);
689 }
690
691 KoDialog dlg(qApp->activeWindow());
692 dlg.setMainWidget(page);
693 page->setParent(&dlg);
695 dlg.setWindowTitle(mimeUserDescription);
696
697 if (showWarnings || wdg || isAdvancedExporting) {
698 if (!dlg.exec()) {
699 return false;
700 }
701 }
702
703 *alsoAsKra = false;
704 if (chkAlsoAsKra) {
705 KisConfig(false).writeEntry<bool>("AlsoSaveAsKra", chkAlsoAsKra->isChecked());
706 *alsoAsKra = chkAlsoAsKra->isChecked();
707 }
708
709 KIS_SAFE_ASSERT_RECOVER_NOOP(bool(isAdvancedExporting) == bool(wdgImageSize));
710
711 if (isAdvancedExporting && wdgImageSize) {
712 if (shouldFlattenTheImageBeforeScaling) {
715 }
716
717 const QSize desiredSize(wdgImageSize->desiredWidth(), wdgImageSize->desiredHeight());
718 double res = wdgImageSize->desiredResolution();
719 m_document->savingImage()->scaleImage(desiredSize,res,res,wdgImageSize->filterType());
723 }
724
725 if (wdg) {
726 *exportConfiguration = *wdg->configuration();
727 }
728 }
729
730 return true;
731}
732
734{
735 QFile file(location);
736 if (!file.exists()) {
738 }
739
740 if (filter->supportsIO() && !file.open(QFile::ReadOnly)) {
742 }
743
744 KisImportExportErrorCode status = filter->convert(m_document, &file, KisPropertiesConfigurationSP());
745
746 if (file.isOpen()) {
747 file.close();
748 }
749
750 return status;
751}
752
755 KisPropertiesConfigurationSP exportConfiguration,
756 const QString alsoAsKraLocation)
757{
759 doExportImpl(location, filter, exportConfiguration);
760
761 if (!alsoAsKraLocation.isNull() && status.isOk()) {
762 QByteArray mime = m_document->nativeFormatMimeType();
764 filterForMimeType(QString::fromLatin1(mime), Export));
765
767
768 if (filter) {
769 filter->setFilename(alsoAsKraLocation);
770
771 KisPropertiesConfigurationSP kraExportConfiguration =
772 filter->lastSavedConfiguration(mime, mime);
773
774 status = doExportImpl(alsoAsKraLocation, filter, kraExportConfiguration);
775 } else {
777 }
778 }
779
780 return status;
781}
782
783// Temporary workaround until QTBUG-57299 is fixed.
784// 02-10-2019 update: the bug is closed, but we've still seen this issue.
785// and without using QSaveFile the issue can still occur
786// when QFile::copy fails because Dropbox/Google/OneDrive
787// locks the target file.
788// 02-24-2022 update: Added macOS since QSaveFile does not work on sandboxed krita
789// It can work if user gives access to the container dir, but
790// we cannot guarantee the user gave us permission.
791// 12-05-2025 update: Also Android because we gotta play in the sandbox.
792#if !(defined(Q_OS_WIN) || defined(Q_OS_MACOS) || defined(Q_OS_ANDROID))
793#define USE_QSAVEFILE
794#endif
795
796namespace {
797QFileDevice::FileError getFileOpenError(const QFileDevice &file)
798{
799 QFileDevice::FileError error = file.error();
806 if (error == QFileDevice::NoError) {
807 return QFileDevice::OpenError;
808 } else {
809 return error;
810 }
811}
812}
813
815{
816#ifdef USE_QSAVEFILE
817 QSaveFile file(location);
818 file.setDirectWriteFallback(true);
819 if (filter->supportsIO() && !file.open(QFile::WriteOnly)) {
820#else
821 QFileInfo fi(location);
822 QTemporaryFile file(QDir::tempPath() + "/.XXXXXX.kra");
823 if (filter->supportsIO() && !file.open()) {
824#endif
825 KisImportExportErrorCannotWrite result(getFileOpenError(file));
826#ifdef USE_QSAVEFILE
827 file.cancelWriting();
828#endif
829 return result;
830 }
831
832 KisImportExportErrorCode status = filter->convert(m_document, &file, exportConfiguration);
833
834 if (filter->supportsIO()) {
835 if (!status.isOk()) {
836#ifdef USE_QSAVEFILE
837 file.cancelWriting();
838#endif
839 } else {
840#ifdef USE_QSAVEFILE
841 if (!file.commit()) {
842 qWarning() << "Could not commit QSaveFile";
843 status = KisImportExportErrorCannotWrite(file.error());
844 }
845#elif defined(Q_OS_ANDROID)
846 // The Android file system is bananas, so it needs special handling.
847
848 // If the temporary file is still open, ensure it's fully written.
849 // If it got closed, open it again so that we can read from it.
850 if(file.isOpen()) {
851 if (!file.flush()) {
852 return KisImportExportErrorCannotWrite(file.error());
853 }
854 } else if (!file.open()) {
855 return KisImportExportErrorCannotWrite(getFileOpenError(file));
856 }
857
858 // Grab the size we're expecting to write for later verification.
859 qint64 expectedSize = file.size();
860 if (expectedSize < 0 || !file.seek(0)) {
861 return KisImportExportErrorCannotWrite(file.error());
862 }
863
864 // Open the target file. We have to explicitly tell the file to
865 // truncate itself because unlike on every other system it doesn't
866 // do that on its own when opening a file for writing, it just
867 // leaves the old content laying around and you start overwriting
868 // it, potentially leaving old garbage at the end of the file.
869 QFile target(location);
870 if (!target.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
871 return KisImportExportErrorCannotWrite(getFileOpenError(target));
872 }
873
874 // QFile::copy also doesn't work, so we gotta do it manually by
875 // alternately reading and writing BUFSIZ-sized chunks.
876 QByteArray buf;
877 buf.resize(BUFSIZ);
878 qint64 totalWritten = 0;
879 while (true) {
880 qint64 read = file.read(buf.data(), BUFSIZ);
881 if (read < 0) {
882 // Read error.
883 return KisImportExportErrorCannotWrite(file.error());
884 } else if (read == 0) {
885 // End of file.
886 break;
887 } else {
888 // Successful read, try to write it.
889 qint64 written = target.write(buf.constData(), read);
890 if (written < 0) {
891 // Write error.
893 }
894 // We may not have written as much as we read, but we handle
895 // that at the end.
896 totalWritten += written;
897 }
898 }
899
900 // Finish up and make sure what we wrote is out to storage.
901 file.close();
902 if (!target.flush()) {
904 }
905 target.close();
906
907 // Now check if we actually wrote as much as we wanted to. If not,
908 // raise an error. There's not much we can do about it though, since
909 // we already truncated the original file at this point and don't
910 // have permissions to create backup files in the sandbox.
911 if (totalWritten != expectedSize) {
912 return KisImportExportErrorCannotWrite(QFileDevice::CopyError);
913 }
914#else
915 file.flush();
916 file.close();
917 QFile target(location);
918 if (target.exists()) {
919 // There should already be a .kra~ backup
920 target.remove();
921 }
922 if (!file.copy(location)) {
923 file.setAutoRemove(false);
924 return KisImportExportErrorCannotWrite(file.error());
925 }
926#endif
927 }
928 }
929
930 if (status.isOk()) {
931 // Do some minimal verification
932 QString verificationResult = filter->verify(location);
933 if (!verificationResult.isEmpty()) {
935 m_document->setErrorMessage(verificationResult);
936 }
937 }
938
939 return status;
940
941}
942
943QString KisImportExportManager::getAlsoAsKraLocation(const QString location) const
944{
945#ifdef Q_OS_ANDROID
946 return getUriForAdditionalFile(location, nullptr);
947#else
948 return location + ".kra";
949#endif
950}
951
952#include <KisMimeDatabase.h>
float value(const T *src, size_t ch)
KisMagneticGraph::vertex_descriptor target(typename KisMagneticGraph::edge_descriptor e, KisMagneticGraph g)
QList< QString > QStringList
ColorPrimaries
The colorPrimaries enum Enum of colorants, follows ITU H.273 for values 0 to 255, and has extra known...
@ PRIMARIES_UNSPECIFIED
@ PRIMARIES_ADOBE_RGB_1998
TransferCharacteristics
The transferCharacteristics enum Enum of transfer characteristics, follows ITU H.273 for values 0 to ...
virtual void setView(KisViewManager *view)
virtual KisPropertiesConfigurationSP configuration() const =0
virtual void setConfiguration(const KisPropertiesConfigurationSP config)=0
void writeEntry(const QString &name, const T &value)
Definition kis_config.h:865
void setExportConfiguration(const QString &filterId, KisPropertiesConfigurationSP properties) const
KisSharedPtr< KisReferenceImagesLayer > referenceImagesLayer() const
KisImageSP image
void setErrorMessage(const QString &errMsg)
KisGridConfig gridConfig
static QByteArray nativeFormatMimeType()
KisImageSP savingImage
QList< KisPaintingAssistantSP > assistants
bool fileBatchMode() const
KisGuidesConfig guidesConfig
bool isDefault() const
void waitForDone()
const KoColorSpace * colorSpace() const
void flatten(KisNodeSP activeNode)
KisPaintDeviceSP projection() const
qint32 width() const
void scaleImage(const QSize &size, qreal xres, qreal yres, KisFilterStrategy *filterStrategy)
start asynchronous operation on scaling the image
double xRes() const
double yRes() const
qint32 height() const
qint32 nlayers() const
The base class for import and export filters.
void setMimeType(const QString &mime)
static const QString ColorDepthIDTag
static const QString ColorModelIDTag
static const QString CICPTransferCharacteristicsTag
static const QString CICPPrimariesTag
static const QString ImageContainsTransparencyTag
static const QString sRGBTag
static const QString HDRTag
The class managing all the filters.
KisImportExportErrorCode importDocument(const QString &location, const QString &mimeType)
ConversionResult convert(Direction direction, const QString &location, const QString &realLocation, const QString &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration, bool isAsync, bool isAdvancedExporting=false)
KisImportExportErrorCode doExport(const QString &location, QSharedPointer< KisImportExportFilter > filter, KisPropertiesConfigurationSP exportConfiguration, const QString alsoAsKraLocation)
void setUpdater(KoUpdaterPtr updater)
QString getAlsoAsKraLocation(const QString location) const
QFuture< KisImportExportErrorCode > exportDocumentAsync(const QString &location, const QString &realLocation, const QByteArray &mimeType, KisImportExportErrorCode &status, bool showWarnings=true, KisPropertiesConfigurationSP exportConfiguration=0, bool isAdvancedExporting=false)
KisImportExportManager(KisDocument *document)
static QString getUriForAdditionalFile(const QString &defaultUri, QWidget *parent)
static QStringList m_exportMimeTypes
static QString askForAudioFileName(const QString &defaultDir, QWidget *parent)
static KisImportExportFilter * filterForMimeType(const QString &mimetype, Direction direction)
filterForMimeType loads the relevant import/export plugin and returns it. The caller is responsible f...
KisImportExportErrorCode exportDocument(const QString &location, const QString &realLocation, const QByteArray &mimeType, bool showWarnings=true, KisPropertiesConfigurationSP exportConfiguration=0, bool isAdvancedExporting=false)
Exports the given file/document to the specified URL/mimetype.
bool askUserAboutExportConfiguration(QSharedPointer< KisImportExportFilter > filter, KisPropertiesConfigurationSP exportConfiguration, const QByteArray &from, const QByteArray &to, bool batchMode, const bool showWarnings, bool *alsoAsKra, bool isAdvancedExporting=false)
KisImportExportErrorCode doExportImpl(const QString &location, QSharedPointer< KisImportExportFilter > filter, KisPropertiesConfigurationSP exportConfiguration)
static void fillStaticExportConfigurationProperties(KisPropertiesConfigurationSP exportConfiguration, KisImageSP image)
KisImportExportErrorCode doImport(const QString &location, QSharedPointer< KisImportExportFilter > filter)
QSharedPointer< KisImportExportFilter > cachedExportFilter
static QStringList m_importMimeTypes
A static cache for the availability checks of filters.
static QStringList supportedMimeTypes(Direction direction)
Main window for Krita.
KisViewManager * viewManager
static QString mimeTypeForFile(const QString &file, bool checkExistingFiles=true)
Find the mimetype for the given filename. The filename must include a suffix.
static QString descriptionForMimeType(const QString &mimeType)
Find the user-readable description for the given mimetype.
const KoColorSpace * colorSpace() const
static bool checkDeviceHasTransparency(KisPaintDeviceSP dev)
static KisPart * instance()
Definition KisPart.cpp:131
KisMainWindow * currentMainwindow() const
Definition KisPart.cpp:459
void setPopupWidget(QWidget *widget)
bool check(KisImageSP image, QMap< QString, KisExportCheckBase * > filterChecks)
check checks the image against the capabilities of the export filter
QStringList errors() const
QStringList warnings() const
static void log(const QString &message)
Logs with date/time.
virtual bool hasHighDynamicRange() const =0
virtual KoID colorModelId() const =0
virtual KoID colorDepthId() const =0
virtual const KoColorProfile * profile() const =0
A dialog base class with standard buttons and predefined layouts.
Definition KoDialog.h:116
void setMainWidget(QWidget *widget)
Definition KoDialog.cpp:345
void setButtons(ButtonCodes buttonMask)
Definition KoDialog.cpp:195
@ Ok
Show Ok button. (this button accept()s the dialog; result set to QDialog::Accepted)
Definition KoDialog.h:127
@ Cancel
Show Cancel-button. (this button reject()s the dialog; result set to QDialog::Rejected)
Definition KoDialog.h:130
QString name() const
Definition KoID.cpp:68
QString id() const
Definition KoID.cpp:63
static KoJsonTrader * instance()
QList< Plugin > query(const QString &servicetype, const QString &mimetype)
KisFilterStrategy * filterType()
qint32 desiredWidth()
double desiredResolution()
qint32 desiredHeight()
#define KIS_ASSERT_RECOVER_RETURN_VALUE(cond, val)
Definition kis_assert.h:85
#define KIS_SAFE_ASSERT_RECOVER(cond)
Definition kis_assert.h:126
#define KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(cond, val)
Definition kis_assert.h:129
#define KIS_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:97
#define KIS_SAFE_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:130
#define warnUI
Definition kis_debug.h:94
QSharedPointer< T > toQShared(T *ptr)
KisPinnedSharedPtr< KisPropertiesConfiguration > KisPropertiesConfigurationSP
Definition kis_types.h:278
KisSharedPtr< KisNode > KisNodeSP
Definition kis_types.h:86
ChildIterator< value_type, is_const > parent(const ChildIterator< value_type, is_const > &it)
Definition KisForest.h:327
QIcon loadIcon(const QString &name)
KisNodeSP recursiveFindNode(KisNodeSP node, std::function< bool(KisNodeSP)> func)
void forceAllDelayedNodesUpdate(KisNodeSP root)
QFuture< KisImportExportErrorCode > futureStatus() const
QFuture< KisImportExportErrorCode > m_futureStatus
ConversionResult(KisImportExportErrorCode status)
ConversionResult(const QFuture< KisImportExportErrorCode > &futureStatus)
void setStatus(KisImportExportErrorCode value)
virtual ColorPrimaries getColorPrimaries() const
getColorPrimaries
virtual TransferCharacteristics getTransferCharacteristics() const
getTransferCharacteristics This function should be subclassed at some point so we can get the value f...
QObject * instance() const
QString fileName() const
QJsonObject metaData() const
QString errorString() const