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