diff --git a/libs/store/KoZipStore.cpp b/libs/store/KoZipStore.cpp
index 8446d9c9d0..68b867af34 100644
--- a/libs/store/KoZipStore.cpp
+++ b/libs/store/KoZipStore.cpp
@@ -1,242 +1,273 @@
/* This file is part of the KDE project
Copyright (C) 2000-2002 David Faure
Copyright (C) 2010 C. Boemann
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "KoZipStore.h"
#include "KoStore_p.h"
#include
#include
#include
#include
#include
#include
+class SaveZip : public KZip {
+public:
+ SaveZip(const QString &filename) : KZip(filename) {}
+ SaveZip(QIODevice *dev) : KZip(dev) {}
+ virtual ~SaveZip() {}
+ void resetDevice() {
+ closeArchive();
+ setDevice(0);
+ }
+};
+
KoZipStore::KoZipStore(const QString & _filename, Mode mode, const QByteArray & appIdentification,
bool writeMimetype)
: KoStore(mode, writeMimetype)
{
- debugStore << "KoZipStore Constructor filename =" << _filename
- << " mode = " << int(mode)
- << " mimetype = " << appIdentification << endl;
+// qDebug() << "KoZipStore Constructor filename =" << _filename
+// << " mode = " << int(mode)
+// << " mimetype = " << appIdentification;
Q_D(KoStore);
d->localFileName = _filename;
- m_pZip = new KZip(_filename);
+ m_pZip = new SaveZip(_filename);
init(appIdentification); // open the zip file and init some vars
}
KoZipStore::KoZipStore(QIODevice *dev, Mode mode, const QByteArray & appIdentification,
bool writeMimetype)
: KoStore(mode, writeMimetype)
{
- m_pZip = new KZip(dev);
+// qDebug() << "KoZipStore Constructor device =" << dev
+// << " mode = " << int(mode)
+// << " mimetype = " << appIdentification;
+
+ m_pZip = new SaveZip(dev);
init(appIdentification);
}
KoZipStore::KoZipStore(QWidget* window, const QUrl &_url, const QString & _filename, Mode mode,
const QByteArray & appIdentification, bool writeMimetype)
: KoStore(mode, writeMimetype)
{
debugStore << "KoZipStore Constructor url" << _url.url(QUrl::PreferLocalFile)
<< " filename = " << _filename
<< " mode = " << int(mode)
- << " mimetype = " << appIdentification << endl;
+ << " mimetype = " << appIdentification;
Q_D(KoStore);
d->url = _url;
d->window = window;
if (mode == KoStore::Read) {
d->localFileName = _filename;
} else {
QTemporaryFile f("kozip");
f.open();
d->localFileName = f.fileName();
f.close();
}
- m_pZip = new KZip(d->localFileName);
+ m_pZip = new SaveZip(d->localFileName);
init(appIdentification); // open the zip file and init some vars
}
KoZipStore::~KoZipStore()
{
Q_D(KoStore);
- debugStore << "KoZipStore::~KoZipStore";
- if (!d->finalized)
- finalize(); // ### no error checking when the app forgot to call finalize itself
+ bool sf = false;
+ if (m_pZip && m_pZip->device()) {
+ sf = true;
+ }
+
+// qDebug() << "KoZipStore::~KoZipStore" << d->localFileName << m_pZip << m_pZip->device() << "savefile" << sf;
+ if (m_pZip->device() && m_pZip->device()->inherits("QSaveFile")) {
+ m_pZip->resetDevice(); // otherwise, kzip's destructor will call close(), which aborts on a qsavefile
+ }
+ else {
+ if (!d->finalized) {
+ finalize(); // ### no error checking when the app forgot to call finalize itself
+ }
+ }
delete m_pZip;
// When writing, we write to a temp file that then gets copied over the original filename
if (d->mode == Write && (!d->localFileName.isEmpty() && !d->url.isEmpty())) {
QFile f(d->localFileName);
if (f.copy(d->url.toLocalFile())) {
f.remove();
}
}
}
void KoZipStore::init(const QByteArray& appIdentification)
{
Q_D(KoStore);
m_currentDir = 0;
d->good = m_pZip->open(d->mode == Write ? QIODevice::WriteOnly : QIODevice::ReadOnly);
if (!d->good)
return;
if (d->mode == Write) {
//debugStore <<"KoZipStore::init writing mimetype" << appIdentification;
m_pZip->setCompression(KZip::NoCompression);
m_pZip->setExtraField(KZip::NoExtraField);
// Write identification
if (d->writeMimetype) {
(void)m_pZip->writeFile(QLatin1String("mimetype"), appIdentification);
}
m_pZip->setCompression(KZip::DeflateCompression);
// We don't need the extra field in Krita - so we leave it as "no extra field".
} else {
d->good = m_pZip->directory() != 0;
}
}
void KoZipStore::setCompressionEnabled(bool e)
{
if (e) {
m_pZip->setCompression(KZip::DeflateCompression);
} else {
m_pZip->setCompression(KZip::NoCompression);
}
}
bool KoZipStore::doFinalize()
{
- return m_pZip->close();
+ if (m_pZip && m_pZip->device() && !m_pZip->device()->inherits("QSaveFile")) {
+ return m_pZip->close();
+ }
+ else {
+ return true;
+ }
}
bool KoZipStore::openWrite(const QString& name)
{
Q_D(KoStore);
d->stream = 0; // Don't use!
return m_pZip->prepareWriting(name, "", "" /*m_pZip->rootDir()->user(), m_pZip->rootDir()->group()*/, 0);
}
bool KoZipStore::openRead(const QString& name)
{
Q_D(KoStore);
const KArchiveEntry * entry = m_pZip->directory()->entry(name);
if (entry == 0) {
return false;
}
if (entry->isDirectory()) {
warnStore << name << " is a directory !";
return false;
}
// Must cast to KZipFileEntry, not only KArchiveFile, because device() isn't virtual!
const KZipFileEntry * f = static_cast(entry);
delete d->stream;
d->stream = f->createDevice();
d->size = f->size();
return true;
}
qint64 KoZipStore::write(const char* _data, qint64 _len)
{
Q_D(KoStore);
if (_len == 0) return 0;
//debugStore <<"KoZipStore::write" << _len;
if (!d->isOpen) {
errorStore << "KoStore: You must open before writing" << endl;
return 0;
}
if (d->mode != Write) {
errorStore << "KoStore: Can not write to store that is opened for reading" << endl;
return 0;
}
d->size += _len;
if (m_pZip->writeData(_data, _len)) // writeData returns a bool!
return _len;
return 0;
}
QStringList KoZipStore::directoryList() const
{
QStringList retval;
const KArchiveDirectory *directory = m_pZip->directory();
Q_FOREACH (const QString &name, directory->entries()) {
const KArchiveEntry* fileArchiveEntry = m_pZip->directory()->entry(name);
if (fileArchiveEntry->isDirectory()) {
retval << name;
}
}
return retval;
}
bool KoZipStore::closeWrite()
{
Q_D(KoStore);
debugStore << "Wrote file" << d->fileName << " into ZIP archive. size" << d->size;
return m_pZip->finishWriting(d->size);
}
bool KoZipStore::enterRelativeDirectory(const QString& dirName)
{
Q_D(KoStore);
if (d->mode == Read) {
if (!m_currentDir) {
m_currentDir = m_pZip->directory(); // initialize
Q_ASSERT(d->currentPath.isEmpty());
}
const KArchiveEntry *entry = m_currentDir->entry(dirName);
if (entry && entry->isDirectory()) {
m_currentDir = dynamic_cast(entry);
return m_currentDir != 0;
}
return false;
} else // Write, no checking here
return true;
}
bool KoZipStore::enterAbsoluteDirectory(const QString& path)
{
if (path.isEmpty()) {
m_currentDir = 0;
return true;
}
m_currentDir = dynamic_cast(m_pZip->directory()->entry(path));
Q_ASSERT(m_currentDir);
return m_currentDir != 0;
}
bool KoZipStore::fileExists(const QString& absPath) const
{
const KArchiveEntry *entry = m_pZip->directory()->entry(absPath);
return entry && entry->isFile();
}
diff --git a/libs/store/KoZipStore.h b/libs/store/KoZipStore.h
index 43579abd10..b45964d139 100644
--- a/libs/store/KoZipStore.h
+++ b/libs/store/KoZipStore.h
@@ -1,74 +1,73 @@
/* This file is part of the KDE project
Copyright (C) 2002 David Faure
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef koZipStore_h
#define koZipStore_h
#include "KoStore.h"
-class KZip;
+class SaveZip;
class KArchiveDirectory;
class QUrl;
class KoZipStore : public KoStore
{
public:
KoZipStore(const QString & _filename, Mode _mode, const QByteArray & appIdentification,
bool writeMimetype = true);
KoZipStore(QIODevice *dev, Mode mode, const QByteArray & appIdentification,
bool writeMimetype = true);
/**
* QUrl-constructor
* @todo saving not completely implemented (fixed temporary file)
*/
KoZipStore(QWidget* window, const QUrl &_url, const QString & _filename, Mode _mode,
const QByteArray & appIdentification, bool writeMimetype = true);
~KoZipStore();
virtual void setCompressionEnabled(bool e);
virtual qint64 write(const char* _data, qint64 _len);
virtual QStringList directoryList() const;
protected:
void init(const QByteArray& appIdentification);
virtual bool doFinalize();
virtual bool openWrite(const QString& name);
virtual bool openRead(const QString& name);
virtual bool closeWrite();
virtual bool closeRead() {
return true;
}
virtual bool enterRelativeDirectory(const QString& dirName);
virtual bool enterAbsoluteDirectory(const QString& path);
virtual bool fileExists(const QString& absPath) const;
private:
- /// The archive
- KZip * m_pZip;
+ // The archive
+ SaveZip * m_pZip;
- /** In "Read" mode this pointer is pointing to the
- current directory in the archive to speed up the verification process */
+ // In "Read" mode this pointer is pointing to the current directory in the archive to speed up the verification process
const KArchiveDirectory* m_currentDir;
Q_DECLARE_PRIVATE(KoStore)
};
#endif
diff --git a/libs/ui/KisDocument.cpp b/libs/ui/KisDocument.cpp
index b0993f5c86..0766a804fd 100644
--- a/libs/ui/KisDocument.cpp
+++ b/libs/ui/KisDocument.cpp
@@ -1,1777 +1,1734 @@
-/* This file is part of the Krita project
+/* This file is part of the Krita project
*
* Copyright (C) 2014 Boudewijn Rempt
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "KisMainWindow.h" // XXX: remove
#include // XXX: remove
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
// Krita Image
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
// Local
#include "KisViewManager.h"
#include "kis_clipboard.h"
#include "widgets/kis_custom_image_widget.h"
#include "canvas/kis_canvas2.h"
#include "flake/kis_shape_controller.h"
#include "kis_statusbar.h"
#include "widgets/kis_progress_widget.h"
#include "kis_canvas_resource_provider.h"
#include "kis_resource_server_provider.h"
#include "kis_node_manager.h"
#include "KisPart.h"
#include "KisApplication.h"
#include "KisDocument.h"
#include "KisImportExportManager.h"
#include "KisPart.h"
#include "KisView.h"
#include "kis_grid_config.h"
#include "kis_guides_config.h"
#include "kis_image_barrier_lock_adapter.h"
#include
// Define the protocol used here for embedded documents' URL
// This used to "store" but QUrl didn't like it,
// so let's simply make it "tar" !
#define STORE_PROTOCOL "tar"
// The internal path is a hack to make QUrl happy and for document children
#define INTERNAL_PROTOCOL "intern"
#define INTERNAL_PREFIX "intern:/"
// Warning, keep it sync in koStore.cc
#include
using namespace std;
/**********************************************************
*
* KisDocument
*
**********************************************************/
namespace {
class DocumentProgressProxy : public KoProgressProxy {
public:
KisMainWindow *m_mainWindow;
DocumentProgressProxy(KisMainWindow *mainWindow)
: m_mainWindow(mainWindow)
{
}
~DocumentProgressProxy() override {
// signal that the job is done
setValue(-1);
}
int maximum() const override {
return 100;
}
void setValue(int value) override {
if (m_mainWindow) {
m_mainWindow->slotProgress(value);
}
}
void setRange(int /*minimum*/, int /*maximum*/) override {
}
void setFormat(const QString &/*format*/) override {
}
};
}
//static
QString KisDocument::newObjectName()
{
static int s_docIFNumber = 0;
QString name; name.setNum(s_docIFNumber++); name.prepend("document_");
return name;
}
class UndoStack : public KUndo2Stack
{
public:
UndoStack(KisDocument *doc)
: m_doc(doc)
{
}
void setIndex(int idx) override {
KisImageWSP image = this->image();
image->requestStrokeCancellation();
if(image->tryBarrierLock()) {
KUndo2Stack::setIndex(idx);
image->unlock();
}
}
void notifySetIndexChangedOneCommand() override {
KisImageWSP image = this->image();
image->unlock();
/**
* Some very weird commands may emit blocking signals to
* the GUI (e.g. KisGuiContextCommand). Here is the best thing
* we can do to avoid the deadlock
*/
while(!image->tryBarrierLock()) {
QApplication::processEvents();
}
}
void undo() override {
KisImageWSP image = this->image();
image->requestUndoDuringStroke();
if (image->tryUndoUnfinishedLod0Stroke() == UNDO_OK) {
return;
}
if(image->tryBarrierLock()) {
KUndo2Stack::undo();
image->unlock();
}
}
void redo() override {
KisImageWSP image = this->image();
if(image->tryBarrierLock()) {
KUndo2Stack::redo();
image->unlock();
}
}
private:
KisImageWSP image() {
KisImageWSP currentImage = m_doc->image();
Q_ASSERT(currentImage);
return currentImage;
}
private:
KisDocument *m_doc;
};
class Q_DECL_HIDDEN KisDocument::Private
{
public:
Private() :
docInfo(0),
progressUpdater(0),
progressProxy(0),
importExportManager(0),
isImporting(false),
isExporting(false),
password(QString()),
modifiedAfterAutosave(false),
isAutosaving(false),
backupFile(true),
doNotSaveExtDoc(false),
undoStack(0),
m_saveOk(false),
m_waitForSave(false),
m_duringSaveAs(false),
m_bAutoDetectedMime(false),
modified(false),
readwrite(true),
disregardAutosaveFailure(false),
nserver(0),
macroNestDepth(0),
imageIdleWatcher(2000 /*ms*/),
suppressProgress(false),
fileProgressProxy(0),
savingLock(&savingMutex)
{
if (QLocale().measurementSystem() == QLocale::ImperialSystem) {
unit = KoUnit::Inch;
} else {
unit = KoUnit::Centimeter;
}
}
~Private() {
// Don't delete m_d->shapeController because it's in a QObject hierarchy.
delete nserver;
}
KoDocumentInfo *docInfo;
KoProgressUpdater *progressUpdater;
KoProgressProxy *progressProxy;
KoUnit unit;
KisImportExportManager *importExportManager; // The filter-manager to use when loading/saving [for the options]
QByteArray mimeType; // The actual mimetype of the document
QByteArray outputMimeType; // The mimetype to use when saving
bool isImporting;
bool isExporting; // File --> Import/Export vs File --> Open/Save
QString password; // The password used to encrypt an encrypted document
QTimer autoSaveTimer;
QString lastErrorMessage; // see openFile()
QString lastWarningMessage;
int autoSaveDelay {300}; // in seconds, 0 to disable.
bool modifiedAfterAutosave;
bool isAutosaving;
bool backupFile;
bool doNotSaveExtDoc; // makes it possible to save only internally stored child documents
KUndo2Stack *undoStack;
KisGuidesConfig guidesConfig;
QUrl m_originalURL; // for saveAs
QString m_originalFilePath; // for saveAs
bool m_saveOk;
bool m_waitForSave;
bool m_duringSaveAs;
bool m_bAutoDetectedMime; // whether the mimetype in the arguments was detected by the part itself
QUrl m_url; // local url - the one displayed to the user.
QString m_file; // Local file - the only one the part implementation should deal with.
QEventLoop m_eventLoop;
QMutex savingMutex;
bool modified;
bool readwrite;
QDateTime firstMod;
QDateTime lastMod;
bool disregardAutosaveFailure;
KisNameServer *nserver;
qint32 macroNestDepth;
KisImageSP image;
KisImageSP savingImage;
KisNodeSP preActivatedNode;
KisShapeController* shapeController;
KoShapeController* koShapeController;
KisIdleWatcher imageIdleWatcher;
QScopedPointer imageIdleConnection;
bool suppressProgress;
KoProgressProxy* fileProgressProxy;
QList assistants;
KisGridConfig gridConfig;
StdLockableWrapper savingLock;
void setImageAndInitIdleWatcher(KisImageSP _image) {
image = _image;
imageIdleWatcher.setTrackedImage(image);
if (image) {
imageIdleConnection.reset(
new KisSignalAutoConnection(
&imageIdleWatcher, SIGNAL(startedIdleMode()),
image.data(), SLOT(explicitRegenerateLevelOfDetail())));
}
}
class SafeSavingLocker;
};
class KisDocument::Private::SafeSavingLocker {
public:
SafeSavingLocker(KisDocument::Private *_d, KisDocument *document)
: d(_d)
, m_document(document)
, m_locked(false)
, m_imageLock(d->image, true)
{
const int realAutoSaveInterval = KisConfig().autoSaveInterval();
const int emergencyAutoSaveInterval = 10; // sec
/**
* Initial try to lock both objects. Locking the image guards
* us from any image composition threads running in the
* background, while savingMutex guards us from entering the
* saving code twice by autosave and main threads.
*
* Since we are trying to lock multiple objects, so we should
* do it in a safe manner.
*/
m_locked = std::try_lock(m_imageLock, d->savingLock) < 0;
if (!m_locked) {
if (d->isAutosaving) {
d->disregardAutosaveFailure = true;
if (realAutoSaveInterval) {
m_document->setAutoSaveDelay(emergencyAutoSaveInterval);
}
} else {
d->image->requestStrokeEnd();
QApplication::processEvents();
// one more try...
m_locked = std::try_lock(m_imageLock, d->savingLock) < 0;
}
}
if (m_locked) {
d->disregardAutosaveFailure = false;
}
}
~SafeSavingLocker() {
if (m_locked) {
m_imageLock.unlock();
d->savingLock.unlock();
const int realAutoSaveInterval = KisConfig().autoSaveInterval();
m_document->setAutoSaveDelay(realAutoSaveInterval);
}
}
bool successfullyLocked() const {
return m_locked;
}
private:
KisDocument::Private *d;
KisDocument *m_document;
bool m_locked;
KisImageBarrierLockAdapter m_imageLock;
};
KisDocument::KisDocument()
: d(new Private())
{
d->undoStack = new UndoStack(this);
d->undoStack->setParent(this);
d->importExportManager = new KisImportExportManager(this);
d->importExportManager->setProgresUpdater(d->progressUpdater);
connect(&d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave()));
KisConfig cfg;
setAutoSaveDelay(cfg.autoSaveInterval());
setObjectName(newObjectName());
d->docInfo = new KoDocumentInfo(this);
d->firstMod = QDateTime::currentDateTime();
d->lastMod = QDateTime::currentDateTime();
// preload the krita resources
KisResourceServerProvider::instance();
d->nserver = new KisNameServer(1);
d->shapeController = new KisShapeController(this, d->nserver);
d->koShapeController = new KoShapeController(0, d->shapeController);
undoStack()->setUndoLimit(KisConfig().undoStackLimit());
connect(d->undoStack, SIGNAL(indexChanged(int)), this, SLOT(slotUndoStackIndexChanged(int)));
setBackupFile(KisConfig().backupFile());
}
KisDocument::~KisDocument()
{
/**
* Push a timebomb, which will try to release the memory after
* the document has been deleted
*/
KisPaintDevice::createMemoryReleaseObject()->deleteLater();
d->autoSaveTimer.disconnect(this);
d->autoSaveTimer.stop();
delete d->importExportManager;
// Despite being QObject they needs to be deleted before the image
delete d->shapeController;
delete d->koShapeController;
if (d->image) {
d->image->notifyAboutToBeDeleted();
/**
* WARNING: We should wait for all the internal image jobs to
* finish before entering KisImage's destructor. The problem is,
* while execution of KisImage::~KisImage, all the weak shared
* pointers pointing to the image enter an inconsistent
* state(!). The shared counter is already zero and destruction
* has started, but the weak reference doesn't know about it,
* because KisShared::~KisShared hasn't been executed yet. So all
* the threads running in background and having weak pointers will
* enter the KisImage's destructor as well.
*/
d->image->requestStrokeCancellation();
d->image->waitForDone();
// clear undo commands that can still point to the image
d->undoStack->clear();
d->image->waitForDone();
KisImageWSP sanityCheckPointer = d->image;
Q_UNUSED(sanityCheckPointer);
// The following line trigger the deletion of the image
d->image.clear();
// check if the image has actually been deleted
KIS_SAFE_ASSERT_RECOVER_NOOP(!sanityCheckPointer.isValid());
}
delete d;
}
bool KisDocument::reload()
{
// XXX: reimplement!
return false;
}
bool KisDocument::exportDocument(const QUrl &_url, KisPropertiesConfigurationSP exportConfiguration)
{
//qDebug() << "exportDocument" << _url.toDisplayString() << "is autosaving" << d->isAutosaving;
bool ret;
d->isExporting = true;
//
// Preserve a lot of state here because we need to restore it in order to
// be able to fake a File --> Export. Can't do this in saveFile() because,
// for a start, KParts has already set url and m_file and because we need
// to restore the modified flag etc. and don't want to put a load on anyone
// reimplementing saveFile() (Note: importDocument() and exportDocument()
// will remain non-virtual).
//
QUrl oldURL = url();
QString oldFile = localFilePath();
//qDebug() << "\toldUrl" << oldURL << "oldFile" << oldFile << "export url" << _url;
bool wasModified = isModified();
// save...
ret = saveAs(_url, exportConfiguration);
//
// This is sooooo hacky :(
// Hopefully we will restore enough state.
//
dbgUI << "Restoring KisDocument state to before export";
// always restore url & m_file regardless of failure or success
//qDebug() << "\tafter saveAs: url" << url() << "local file path" << localFilePath();
setUrl(oldURL);
setLocalFilePath(oldFile);
//qDebug() << "\tafter restoring: url" << url() << "local file path" << localFilePath();
// on successful export we need to restore modified etc. too
// on failed export, mimetype/modified hasn't changed anyway
if (ret) {
setModified(wasModified);
}
d->isExporting = false;
return ret;
}
bool KisDocument::saveAs(const QUrl &url, KisPropertiesConfigurationSP exportConfiguration)
{
//qDebug() << "saveAs" << url;
if (!url.isValid() || !url.isLocalFile()) {
errKrita << "saveAs: Malformed URL " << url.url() << endl;
return false;
}
d->m_duringSaveAs = true;
d->m_originalURL = d->m_url;
d->m_originalFilePath = d->m_file;
d->m_url = url; // Store where to upload in saveToURL
d->m_file = d->m_url.toLocalFile();
bool result = save(exportConfiguration); // Save local file and upload local file
if (!result) {
d->m_url = d->m_originalURL;
d->m_file = d->m_originalFilePath;
d->m_duringSaveAs = false;
d->m_originalURL = QUrl();
d->m_originalFilePath.clear();
}
return result;
}
bool KisDocument::save(KisPropertiesConfigurationSP exportConfiguration)
{
//qDebug() << "save" << d->m_file << d->m_url << url() << localFilePath();
d->m_saveOk = false;
+
if (d->m_file.isEmpty()) { // document was created empty
d->m_file = d->m_url.toLocalFile();
}
updateEditingTime(true);
setFileProgressProxy();
setUrl(url());
bool ok = saveFile(localFilePath(), exportConfiguration);
clearFileProgressProxy();
if (ok) {
setModified( false );
emit completed();
d->m_saveOk = true;
d->m_duringSaveAs = false;
d->m_originalURL = QUrl();
d->m_originalFilePath.clear();
return true; // Nothing to do
}
else {
emit canceled(QString());
}
return false;
}
bool KisDocument::saveFile(const QString &filePath, KisPropertiesConfigurationSP exportConfiguration)
{
if (!prepareLocksForSaving()) {
return false;
}
// Unset the error message
setErrorMessage("");
// Save it to be able to restore it after a failed save
const bool wasModified = isModified();
+ bool ret = false;
+ bool suppressErrorDialog = fileBatchMode();
+ KisImportExportFilter::ConversionStatus status = KisImportExportFilter::OK;
- // The output format is set by KisMainWindow, and by openFile
- QByteArray outputMimeType = d->outputMimeType;
+ //qDebug() << "saveFile" << localFilePath() << QFileInfo(localFilePath()).exists() << !QFileInfo(localFilePath()).isWritable();
- if (outputMimeType.isEmpty()) {
- outputMimeType = d->outputMimeType = nativeFormatMimeType();
+ if (QFileInfo(localFilePath()).exists() && !QFileInfo(localFilePath()).isWritable()) {
+ setErrorMessage(i18n("%1 cannot be written to. Please save under a different name.", localFilePath()));
}
+ else {
- //qDebug() << "saveFile. Is Autosaving?" << isAutosaving() << "url" << filePath << d->outputMimeType;
-
-
- if (d->backupFile) {
- Q_ASSERT(url().isLocalFile());
- KBackup::backupFile(url().toLocalFile());
- }
+ // The output format is set by KisMainWindow, and by openFile
+ QByteArray outputMimeType = d->outputMimeType;
- qApp->processEvents();
+ if (outputMimeType.isEmpty()) {
+ outputMimeType = d->outputMimeType = nativeFormatMimeType();
+ }
- bool ret = false;
- bool suppressErrorDialog = false;
- KisImportExportFilter::ConversionStatus status = KisImportExportFilter::OK;
+ //qDebug() << "saveFile. Is Autosaving?" << isAutosaving() << "url" << filePath << d->outputMimeType;
- setFileProgressUpdater(i18n("Saving Document"));
- QFileInfo fi(filePath);
- QString tempororaryFileName;
- {
- QTemporaryFile tf(QDir::tempPath() + "/XXXXXX" + fi.baseName() + "." + fi.completeSuffix());
- tf.open();
- tempororaryFileName = tf.fileName();
- }
- Q_ASSERT(!tempororaryFileName.isEmpty());
+ if (d->backupFile) {
+ Q_ASSERT(url().isLocalFile());
+ KBackup::backupFile(url().toLocalFile());
+ }
- //qDebug() << "saving to tempory file" << tempororaryFileName;
- status = d->importExportManager->exportDocument(tempororaryFileName, filePath, outputMimeType, !d->isExporting , exportConfiguration);
+ qApp->processEvents();
- ret = (status == KisImportExportFilter::OK);
- suppressErrorDialog = (isAutosaving() || status == KisImportExportFilter::UserCancelled || status == KisImportExportFilter::BadConversionGraph);
- //qDebug() << "Export status was" << status;
+ setFileProgressUpdater(i18n("Saving Document"));
- if (ret) {
+ //qDebug() << "saving to tempory file" << tempororaryFileName;
+ status = d->importExportManager->exportDocument(localFilePath(), filePath, outputMimeType, !d->isExporting , exportConfiguration);
- //qDebug() << "copying temporary file" << tempororaryFileName << "to" << filePath;
+ ret = (status == KisImportExportFilter::OK);
+ suppressErrorDialog = (fileBatchMode() || isAutosaving() || status == KisImportExportFilter::UserCancelled || status == KisImportExportFilter::BadConversionGraph);
+ //qDebug() << "Export status was" << status;
- if (!d->isAutosaving && !d->suppressProgress) {
- QPointer updater = d->progressUpdater->startSubtask(1, "clear undo stack");
- updater->setProgress(0);
- d->undoStack->setClean();
- updater->setProgress(100);
- } else {
- d->undoStack->setClean();
- }
+ if (ret) {
- QFile tempFile(tempororaryFileName);
- QString s = filePath;
- QFile dstFile(s);
- while (QFileInfo(s).exists()) {
- s.append("_");
- }
- bool r;
- if (s != filePath) {
- r = dstFile.rename(s);
- if (!r) {
- setErrorMessage(i18n("Could not rename original file to %1: %2", dstFile.fileName(), dstFile. errorString()));
- ret = false;
+ if (!d->isAutosaving && !d->suppressProgress) {
+ QPointer updater = d->progressUpdater->startSubtask(1, "clear undo stack");
+ updater->setProgress(0);
+ d->undoStack->setClean();
+ updater->setProgress(100);
+ } else {
+ d->undoStack->setClean();
}
- }
- if (tempFile.exists()) {
- r = tempFile.copy(filePath);
- if (!r) {
- setErrorMessage(i18n("Copying the temporary file failed: %1 to %2: %3", tempFile.fileName(), dstFile.fileName(), tempFile.errorString()));
- ret = false;
+ if (errorMessage().isEmpty()) {
+ if (!isAutosaving()) {
+ removeAutoSaveFiles();
+ }
}
else {
- r = tempFile.remove();
- if (!r) {
- setErrorMessage(i18n("Could not remove temporary file %1: %2", tempFile.fileName(), tempFile.errorString()));
- ret = false;
- }
- else if (s != filePath) {
- r = dstFile.remove();
- if (!r) {
- setErrorMessage(i18n("Could not remove saved original file: %1", dstFile.errorString()));
- ret = false;
- }
- }
+ ret = false;
+ qWarning() << "Error while saving:" << errorMessage();
}
- }
- else {
- setErrorMessage(i18n("The temporary file %1 is gone before we could copy it!", tempFile.fileName()));
- ret = false;
- }
-
- if (errorMessage().isEmpty()) {
+ // Restart the autosave timer
+ // (we don't want to autosave again 2 seconds after a real save)
if (!isAutosaving()) {
- removeAutoSaveFiles();
+ setAutoSaveDelay(d->autoSaveDelay);
}
- }
- else {
- ret = false;
- qWarning() << "Error while saving:" << errorMessage();
- }
- // Restart the autosave timer
- // (we don't want to autosave again 2 seconds after a real save)
- if (!isAutosaving()) {
- setAutoSaveDelay(d->autoSaveDelay);
- }
- d->mimeType = outputMimeType;
+ d->mimeType = outputMimeType;
+ }
}
+
if (!ret) {
if (!suppressErrorDialog) {
if (errorMessage().isEmpty()) {
setErrorMessage(KisImportExportFilter::conversionStatusString(status));
}
if (errorMessage().isEmpty()) {
QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not save\n%1", filePath));
} else {
QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not save %1\nReason: %2", filePath, errorMessage()));
}
}
// couldn't save file so this new URL is invalid
// FIXME: we should restore the current document's true URL instead of
// setting it to nothing otherwise anything that depends on the URL
// being correct will not work (i.e. the document will be called
// "Untitled" which may not be true)
//
// Update: now the URL is restored in KisMainWindow but really, this
// should still be fixed in KisDocument/KParts (ditto for file).
// We still resetURL() here since we may or may not have been called
// by KisMainWindow - Clarence
resetURL();
// As we did not save, restore the "was modified" status
setModified(wasModified);
}
emit sigSavingFinished();
clearFileProgressUpdater();
unlockAfterSaving();
return ret;
}
QByteArray KisDocument::mimeType() const
{
return d->mimeType;
}
void KisDocument::setMimeType(const QByteArray & mimeType)
{
d->mimeType = mimeType;
}
void KisDocument::setOutputMimeType(const QByteArray & mimeType)
{
d->outputMimeType = mimeType;
}
QByteArray KisDocument::outputMimeType() const
{
return d->outputMimeType;
}
bool KisDocument::fileBatchMode() const
{
return d->importExportManager->batchMode();
}
void KisDocument::setFileBatchMode(const bool batchMode)
{
d->importExportManager->setBatchMode(batchMode);
}
bool KisDocument::isImporting() const
{
return d->isImporting;
}
bool KisDocument::isExporting() const
{
return d->isExporting;
}
void KisDocument::slotAutoSave()
{
//qDebug() << "slotAutoSave. Modified:" << d->modified << "modifiedAfterAutosave" << d->modified << "url" << url() << localFilePath();
if (!d->isAutosaving && d->modified && d->modifiedAfterAutosave) {
connect(this, SIGNAL(sigProgress(int)), KisPart::instance()->currentMainwindow(), SLOT(slotProgress(int)));
emit statusBarMessage(i18n("Autosaving..."));
d->isAutosaving = true;
QString autoSaveFileName = generateAutoSaveFileName(localFilePath());
QByteArray mimetype = d->outputMimeType;
d->outputMimeType = nativeFormatMimeType();
bool ret = exportDocument(QUrl::fromLocalFile(autoSaveFileName));
d->outputMimeType = mimetype;
if (ret) {
d->modifiedAfterAutosave = false;
d->autoSaveTimer.stop(); // until the next change
}
d->isAutosaving = false;
emit clearStatusBarMessage();
disconnect(this, SIGNAL(sigProgress(int)), KisPart::instance()->currentMainwindow(), SLOT(slotProgress(int)));
if (!ret && !d->disregardAutosaveFailure) {
emit statusBarMessage(i18n("Error during autosave! Partition full?"));
}
}
}
void KisDocument::setReadWrite(bool readwrite)
{
d->readwrite = readwrite;
setAutoSaveDelay(d->autoSaveDelay);
Q_FOREACH (KisMainWindow *mainWindow, KisPart::instance()->mainWindows()) {
mainWindow->setReadWrite(readwrite);
}
}
void KisDocument::setAutoSaveDelay(int delay)
{
//qDebug() << "setting autosave delay from" << d->autoSaveDelay << "to" << delay;
d->autoSaveDelay = delay;
if (isReadWrite() && d->autoSaveDelay > 0) {
d->autoSaveTimer.start(d->autoSaveDelay * 1000);
}
else {
d->autoSaveTimer.stop();
}
}
KoDocumentInfo *KisDocument::documentInfo() const
{
return d->docInfo;
}
bool KisDocument::isModified() const
{
return d->modified;
}
QPixmap KisDocument::generatePreview(const QSize& size)
{
KisImageSP image = d->image;
if (d->savingImage) image = d->savingImage;
if (image) {
QRect bounds = image->bounds();
QSize newSize = bounds.size();
newSize.scale(size, Qt::KeepAspectRatio);
QPixmap px = QPixmap::fromImage(image->convertToQImage(newSize, 0));
if (px.size() == QSize(0,0)) {
px = QPixmap(newSize);
QPainter gc(&px);
QBrush checkBrush = QBrush(KisCanvasWidgetBase::createCheckersImage(newSize.width() / 5));
gc.fillRect(px.rect(), checkBrush);
gc.end();
}
return px;
}
return QPixmap(size);
}
QString KisDocument::generateAutoSaveFileName(const QString & path) const
{
QString retval;
// Using the extension allows to avoid relying on the mime magic when opening
const QString extension (".kra");
if (path.isEmpty()) {
// Never saved?
#ifdef Q_OS_WIN
// On Windows, use the temp location (https://bugs.kde.org/show_bug.cgi?id=314921)
retval = QString("%1%2.%3-%4-%5-autosave%6").arg(QDir::tempPath()).arg(QDir::separator()).arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension);
#else
// On Linux, use a temp file in $HOME then. Mark it with the pid so two instances don't overwrite each other's autosave file
retval = QString("%1%2.%3-%4-%5-autosave%6").arg(QDir::homePath()).arg(QDir::separator()).arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension);
#endif
} else {
QFileInfo fi(path);
QString dir = fi.absolutePath();
QString filename = fi.fileName();
retval = QString("%1%2.%3-autosave%4").arg(dir).arg(QDir::separator()).arg(filename).arg(extension);
}
//qDebug() << "generateAutoSaveFileName() for path" << path << ":" << retval;
return retval;
}
bool KisDocument::importDocument(const QUrl &_url)
{
bool ret;
dbgUI << "url=" << _url.url();
d->isImporting = true;
// open...
ret = openUrl(_url);
// reset url & m_file (kindly? set by KisParts::openUrl()) to simulate a
// File --> Import
if (ret) {
dbgUI << "success, resetting url";
resetURL();
setTitleModified();
}
d->isImporting = false;
return ret;
}
bool KisDocument::openUrl(const QUrl &_url, KisDocument::OpenUrlFlags flags)
{
if (!_url.isLocalFile()) {
return false;
}
dbgUI << "url=" << _url.url();
d->lastErrorMessage.clear();
// Reimplemented, to add a check for autosave files and to improve error reporting
if (!_url.isValid()) {
d->lastErrorMessage = i18n("Malformed URL\n%1", _url.url()); // ## used anywhere ?
return false;
}
QUrl url(_url);
bool autosaveOpened = false;
if (url.isLocalFile() && !fileBatchMode()) {
QString file = url.toLocalFile();
QString asf = generateAutoSaveFileName(file);
if (QFile::exists(asf)) {
KisApplication *kisApp = static_cast(qApp);
kisApp->hideSplashScreen();
//dbgUI <<"asf=" << asf;
// ## TODO compare timestamps ?
int res = QMessageBox::warning(0,
i18nc("@title:window", "Krita"),
i18n("An autosaved file exists for this document.\nDo you want to open it instead?"),
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes);
switch (res) {
case QMessageBox::Yes :
url.setPath(asf);
autosaveOpened = true;
break;
case QMessageBox::No :
QFile::remove(asf);
break;
default: // Cancel
return false;
}
}
}
bool ret = openUrlInternal(url);
if (autosaveOpened) {
resetURL(); // Force save to act like 'Save As'
setReadWrite(true); // enable save button
setModified(true);
}
else {
if( !(flags & OPEN_URL_FLAG_DO_NOT_ADD_TO_RECENT_FILES) ) {
KisPart::instance()->addRecentURLToAllMainWindows(_url);
}
if (ret) {
// Detect readonly local-files; remote files are assumed to be writable
QFileInfo fi(url.toLocalFile());
setReadWrite(fi.isWritable());
}
}
return ret;
}
class DlgLoadMessages : public KoDialog {
public:
DlgLoadMessages(const QString &title, const QString &message, const QStringList &warnings) {
setWindowTitle(title);
setWindowIcon(KisIconUtils::loadIcon("dialog-warning"));
QWidget *page = new QWidget(this);
QVBoxLayout *layout = new QVBoxLayout(page);
QHBoxLayout *hlayout = new QHBoxLayout();
QLabel *labelWarning= new QLabel();
labelWarning->setPixmap(KisIconUtils::loadIcon("dialog-warning").pixmap(32, 32));
hlayout->addWidget(labelWarning);
hlayout->addWidget(new QLabel(message));
layout->addLayout(hlayout);
QTextBrowser *browser = new QTextBrowser();
QString warning = "";
if (warnings.size() == 1) {
warning += " Reason:
";
}
else {
warning += " Reasons:
";
}
warning += "";
Q_FOREACH(const QString &w, warnings) {
warning += "\n- " + w + "
";
}
warning += "
";
browser->setHtml(warning);
browser->setMinimumHeight(200);
browser->setMinimumWidth(400);
layout->addWidget(browser);
setMainWidget(page);
setButtons(KoDialog::Ok);
resize(minimumSize());
}
};
bool KisDocument::openFile()
{
//dbgUI <<"for" << localFilePath();
if (!QFile::exists(localFilePath())) {
QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("File %1 does not exist.", localFilePath()));
return false;
}
QString filename = localFilePath();
QString typeName = mimeType();
if (typeName.isEmpty()) {
typeName = KisMimeDatabase::mimeTypeForFile(filename);
}
//qDebug() << "mimetypes 4:" << typeName;
// Allow to open backup files, don't keep the mimetype application/x-trash.
if (typeName == "application/x-trash") {
QString path = filename;
while (path.length() > 0) {
path.chop(1);
typeName = KisMimeDatabase::mimeTypeForFile(path);
//qDebug() << "\t" << path << typeName;
if (!typeName.isEmpty()) {
break;
}
}
//qDebug() << "chopped" << filename << "to" << path << "Was trash, is" << typeName;
}
dbgUI << localFilePath() << "type:" << typeName;
setFileProgressUpdater(i18n("Opening Document"));
KisImportExportFilter::ConversionStatus status;
status = d->importExportManager->importDocument(localFilePath(), typeName);
if (status != KisImportExportFilter::OK) {
QString msg = KisImportExportFilter::conversionStatusString(status);
if (!msg.isEmpty()) {
DlgLoadMessages dlg(i18nc("@title:window", "Krita"),
i18n("Could not open %2.\nReason: %1.", msg, prettyPathOrUrl()),
errorMessage().split("\n") + warningMessage().split("\n"));
dlg.exec();
}
clearFileProgressUpdater();
return false;
}
else if (!warningMessage().isEmpty()) {
DlgLoadMessages dlg(i18nc("@title:window", "Krita"),
i18n("There were problems opening %1.", prettyPathOrUrl()),
warningMessage().split("\n"));
dlg.exec();
setUrl(QUrl());
}
setMimeTypeAfterLoading(typeName);
emit sigLoadingFinished();
if (!d->suppressProgress && d->progressUpdater) {
QPointer updater = d->progressUpdater->startSubtask(1, "clear undo stack");
updater->setProgress(0);
undoStack()->clear();
updater->setProgress(100);
clearFileProgressUpdater();
} else {
undoStack()->clear();
}
return true;
}
KoProgressUpdater *KisDocument::progressUpdater() const
{
return d->progressUpdater;
}
void KisDocument::setProgressProxy(KoProgressProxy *progressProxy)
{
d->progressProxy = progressProxy;
}
KoProgressProxy* KisDocument::progressProxy() const
{
if (!d->progressProxy) {
KisMainWindow *mainWindow = 0;
if (KisPart::instance()->mainwindowCount() > 0) {
mainWindow = KisPart::instance()->mainWindows()[0];
}
d->progressProxy = new DocumentProgressProxy(mainWindow);
}
return d->progressProxy;
}
// shared between openFile and koMainWindow's "create new empty document" code
void KisDocument::setMimeTypeAfterLoading(const QString& mimeType)
{
d->mimeType = mimeType.toLatin1();
d->outputMimeType = d->mimeType;
}
bool KisDocument::loadNativeFormat(const QString & file_)
{
return openUrl(QUrl::fromLocalFile(file_));
}
void KisDocument::setModified()
{
d->modified = true;
}
void KisDocument::setModified(bool mod)
{
if (mod) {
updateEditingTime(false);
}
if (d->isAutosaving) // ignore setModified calls due to autosaving
return;
if ( !d->readwrite && d->modified ) {
errKrita << "Can't set a read-only document to 'modified' !" << endl;
return;
}
//dbgUI<<" url:" << url.path();
//dbgUI<<" mod="<docInfo->aboutInfo("editing-time").toInt() + d->firstMod.secsTo(d->lastMod)));
d->firstMod = now;
} else if (firstModDelta > 60 || forceStoreElapsed) {
d->docInfo->setAboutInfo("editing-time", QString::number(d->docInfo->aboutInfo("editing-time").toInt() + firstModDelta));
d->firstMod = now;
}
d->lastMod = now;
}
QString KisDocument::prettyPathOrUrl() const
{
QString _url(url().toDisplayString());
#ifdef Q_OS_WIN
if (url().isLocalFile()) {
_url = QDir::toNativeSeparators(_url);
}
#endif
return _url;
}
// Get caption from document info (title(), in about page)
QString KisDocument::caption() const
{
QString c;
if (documentInfo()) {
c = documentInfo()->aboutInfo("title");
}
const QString _url(url().fileName());
if (!c.isEmpty() && !_url.isEmpty()) {
c = QString("%1 - %2").arg(c).arg(_url);
}
else if (c.isEmpty()) {
c = _url; // Fall back to document URL
}
return c;
}
void KisDocument::setTitleModified()
{
emit titleModified(caption(), isModified());
}
QDomDocument KisDocument::createDomDocument(const QString& tagName, const QString& version) const
{
return createDomDocument("krita", tagName, version);
}
//static
QDomDocument KisDocument::createDomDocument(const QString& appName, const QString& tagName, const QString& version)
{
QDomImplementation impl;
QString url = QString("http://www.calligra.org/DTD/%1-%2.dtd").arg(appName).arg(version);
QDomDocumentType dtype = impl.createDocumentType(tagName,
QString("-//KDE//DTD %1 %2//EN").arg(appName).arg(version),
url);
// The namespace URN doesn't need to include the version number.
QString namespaceURN = QString("http://www.calligra.org/DTD/%1").arg(appName);
QDomDocument doc = impl.createDocument(namespaceURN, tagName, dtype);
doc.insertBefore(doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\""), doc.documentElement());
return doc;
}
bool KisDocument::isNativeFormat(const QByteArray& mimetype) const
{
if (mimetype == nativeFormatMimeType())
return true;
return extraNativeMimeTypes().contains(mimetype);
}
void KisDocument::setErrorMessage(const QString& errMsg)
{
d->lastErrorMessage = errMsg;
}
QString KisDocument::errorMessage() const
{
return d->lastErrorMessage;
}
void KisDocument::setWarningMessage(const QString& warningMsg)
{
d->lastWarningMessage = warningMsg;
}
QString KisDocument::warningMessage() const
{
return d->lastWarningMessage;
}
void KisDocument::removeAutoSaveFiles()
{
//qDebug() << "removeAutoSaveFiles";
// Eliminate any auto-save file
QString asf = generateAutoSaveFileName(localFilePath()); // the one in the current dir
//qDebug() << "\tfilename:" << asf << "exists:" << QFile::exists(asf);
if (QFile::exists(asf)) {
//qDebug() << "\tremoving autosavefile" << asf;
QFile::remove(asf);
}
asf = generateAutoSaveFileName(QString()); // and the one in $HOME
//qDebug() << "Autsavefile in $home" << asf;
if (QFile::exists(asf)) {
//qDebug() << "\tremoving autsavefile 2" << asf;
QFile::remove(asf);
}
}
void KisDocument::setBackupFile(bool saveBackup)
{
d->backupFile = saveBackup;
}
KoUnit KisDocument::unit() const
{
return d->unit;
}
void KisDocument::setUnit(const KoUnit &unit)
{
if (d->unit != unit) {
d->unit = unit;
emit unitChanged(unit);
}
}
KUndo2Stack *KisDocument::undoStack()
{
return d->undoStack;
}
KisImportExportManager *KisDocument::importExportManager() const
{
return d->importExportManager;
}
void KisDocument::addCommand(KUndo2Command *command)
{
if (command)
d->undoStack->push(command);
}
void KisDocument::beginMacro(const KUndo2MagicString & text)
{
d->undoStack->beginMacro(text);
}
void KisDocument::endMacro()
{
d->undoStack->endMacro();
}
void KisDocument::slotUndoStackIndexChanged(int idx)
{
// even if the document was already modified, call setModified to re-start autosave timer
setModified(idx != d->undoStack->cleanIndex());
}
void KisDocument::clearUndoHistory()
{
d->undoStack->clear();
}
KisGridConfig KisDocument::gridConfig() const
{
return d->gridConfig;
}
void KisDocument::setGridConfig(const KisGridConfig &config)
{
d->gridConfig = config;
}
const KisGuidesConfig& KisDocument::guidesConfig() const
{
return d->guidesConfig;
}
void KisDocument::setGuidesConfig(const KisGuidesConfig &data)
{
if (d->guidesConfig == data) return;
d->guidesConfig = data;
emit sigGuidesConfigChanged(d->guidesConfig);
}
void KisDocument::resetURL() {
setUrl(QUrl());
setLocalFilePath(QString());
}
KoDocumentInfoDlg *KisDocument::createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const
{
return new KoDocumentInfoDlg(parent, docInfo);
}
bool KisDocument::isReadWrite() const
{
return d->readwrite;
}
QUrl KisDocument::url() const
{
return d->m_url;
}
bool KisDocument::closeUrl(bool promptToSave)
{
if (promptToSave) {
if ( isReadWrite() && isModified()) {
Q_FOREACH (KisView *view, KisPart::instance()->views()) {
if (view && view->document() == this) {
if (!view->queryClose()) {
return false;
}
}
}
}
}
// Not modified => ok and delete temp file.
d->mimeType = QByteArray();
// It always succeeds for a read-only part,
// but the return value exists for reimplementations
// (e.g. pressing cancel for a modified read-write part)
return true;
}
void KisDocument::setUrl(const QUrl &url)
{
d->m_url = url;
}
QString KisDocument::localFilePath() const
{
return d->m_file;
}
void KisDocument::setLocalFilePath( const QString &localFilePath )
{
d->m_file = localFilePath;
}
bool KisDocument::openUrlInternal(const QUrl &url)
{
if ( !url.isValid() )
return false;
if (d->m_bAutoDetectedMime) {
d->mimeType = QByteArray();
d->m_bAutoDetectedMime = false;
}
QByteArray mimetype = d->mimeType;
if ( !closeUrl() )
return false;
d->mimeType = mimetype;
setUrl(url);
d->m_file.clear();
if (d->m_url.isLocalFile()) {
d->m_file = d->m_url.toLocalFile();
bool ret;
// set the mimetype only if it was not already set (for example, by the host application)
if (d->mimeType.isEmpty()) {
// get the mimetype of the file
// using findByUrl() to avoid another string -> url conversion
QString mime = KisMimeDatabase::mimeTypeForFile(d->m_url.toLocalFile());
d->mimeType = mime.toLocal8Bit();
d->m_bAutoDetectedMime = true;
}
setFileProgressProxy();
setUrl(d->m_url);
ret = openFile();
clearFileProgressProxy();
if (ret) {
emit completed();
} else {
emit canceled(QString());
}
return ret;
}
return false;
}
bool KisDocument::newImage(const QString& name,
qint32 width, qint32 height,
const KoColorSpace* cs,
const KoColor &bgColor, bool backgroundAsLayer,
int numberOfLayers,
const QString &description, const double imageResolution)
{
Q_ASSERT(cs);
KisConfig cfg;
KisImageSP image;
KisPaintLayerSP layer;
if (!cs) return false;
QApplication::setOverrideCursor(Qt::BusyCursor);
image = new KisImage(createUndoStore(), width, height, cs, name);
Q_CHECK_PTR(image);
connect(image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection);
image->setResolution(imageResolution, imageResolution);
image->assignImageProfile(cs->profile());
documentInfo()->setAboutInfo("title", name);
if (name != i18n("Unnamed") && !name.isEmpty()) {
setUrl(QUrl::fromLocalFile(QDesktopServices::storageLocation(QDesktopServices::PicturesLocation) + '/' + name + ".kra"));
}
documentInfo()->setAboutInfo("abstract", description);
layer = new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, cs);
Q_CHECK_PTR(layer);
if (backgroundAsLayer) {
image->setDefaultProjectionColor(KoColor(cs));
if (bgColor.opacityU8() == OPACITY_OPAQUE_U8) {
layer->paintDevice()->setDefaultPixel(bgColor);
} else {
// Hack: with a semi-transparent background color, the projection isn't composited right if we just set the default pixel
KisFillPainter painter;
painter.begin(layer->paintDevice());
painter.fillRect(0, 0, width, height, bgColor, bgColor.opacityU8());
}
} else {
image->setDefaultProjectionColor(bgColor);
}
layer->setDirty(QRect(0, 0, width, height));
image->addNode(layer.data(), image->rootLayer().data());
setCurrentImage(image);
for(int i = 1; i < numberOfLayers; ++i) {
KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), OPACITY_OPAQUE_U8, cs);
image->addNode(layer, image->root(), i);
layer->setDirty(QRect(0, 0, width, height));
}
cfg.defImageWidth(width);
cfg.defImageHeight(height);
cfg.defImageResolution(imageResolution);
cfg.defColorModel(image->colorSpace()->colorModelId().id());
cfg.setDefaultColorDepth(image->colorSpace()->colorDepthId().id());
cfg.defColorProfile(image->colorSpace()->profile()->name());
QApplication::restoreOverrideCursor();
return true;
}
KoShapeBasedDocumentBase *KisDocument::shapeController() const
{
return d->shapeController;
}
KoShapeLayer* KisDocument::shapeForNode(KisNodeSP layer) const
{
return d->shapeController->shapeForNode(layer);
}
vKisNodeSP KisDocument::activeNodes() const
{
vKisNodeSP nodes;
Q_FOREACH (KisView *v, KisPart::instance()->views()) {
if (v->document() == this && v->viewManager()) {
KisNodeSP activeNode = v->viewManager()->activeNode();
if (activeNode && !nodes.contains(activeNode)) {
if (activeNode->inherits("KisMask")) {
activeNode = activeNode->parent();
}
nodes.append(activeNode);
}
}
}
return nodes;
}
QList KisDocument::assistants() const
{
return d->assistants;
}
void KisDocument::setAssistants(const QList value)
{
d->assistants = value;
}
void KisDocument::setPreActivatedNode(KisNodeSP activatedNode)
{
d->preActivatedNode = activatedNode;
}
KisNodeSP KisDocument::preActivatedNode() const
{
return d->preActivatedNode;
}
void KisDocument::setFileProgressUpdater(const QString &text)
{
d->suppressProgress = d->importExportManager->batchMode();
if (!d->suppressProgress) {
d->progressUpdater = new KoProgressUpdater(d->progressProxy, KoProgressUpdater::Unthreaded);
d->progressUpdater->start(100, text);
d->importExportManager->setProgresUpdater(d->progressUpdater);
connect(this, SIGNAL(sigProgress(int)), KisPart::instance()->currentMainwindow(), SLOT(slotProgress(int)));
connect(KisPart::instance()->currentMainwindow(), SIGNAL(sigProgressCanceled()), this, SIGNAL(sigProgressCanceled()));
}
}
void KisDocument::clearFileProgressUpdater()
{
if (!d->suppressProgress && d->progressUpdater) {
disconnect(KisPart::instance()->currentMainwindow(), SIGNAL(sigProgressCanceled()), this, SIGNAL(sigProgressCanceled()));
disconnect(this, SIGNAL(sigProgress(int)), KisPart::instance()->currentMainwindow(), SLOT(slotProgress(int)));
delete d->progressUpdater;
d->importExportManager->setProgresUpdater(0);
d->progressUpdater = 0;
}
}
void KisDocument::setFileProgressProxy()
{
if (!d->progressProxy && !d->importExportManager->batchMode()) {
d->fileProgressProxy = progressProxy();
} else {
d->fileProgressProxy = 0;
}
}
void KisDocument::clearFileProgressProxy()
{
if (d->fileProgressProxy) {
setProgressProxy(0);
delete d->fileProgressProxy;
d->fileProgressProxy = 0;
}
}
KisImageWSP KisDocument::image() const
{
return d->image;
}
KisImageSP KisDocument::savingImage() const
{
return d->savingImage;
}
void KisDocument::setCurrentImage(KisImageSP image)
{
if (d->image) {
// Disconnect existing sig/slot connections
d->image->disconnect(this);
d->shapeController->setImage(0);
d->image = 0;
}
if (!image) return;
d->setImageAndInitIdleWatcher(image);
d->shapeController->setImage(image);
setModified(false);
connect(d->image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection);
d->image->initialRefreshGraph();
setAutoSaveDelay(KisConfig().autoSaveInterval());
}
void KisDocument::setImageModified()
{
setModified(true);
}
KisUndoStore* KisDocument::createUndoStore()
{
return new KisDocumentUndoStore(this);
}
bool KisDocument::isAutosaving() const
{
return d->isAutosaving;
}
bool KisDocument::prepareLocksForSaving()
{
KisImageSP copiedImage;
// XXX: Restore this when
// a) cloning works correctly and
// b) doesn't take ages because it needs to refresh its entire graph and finally,
// c) we do use the saving image to save in the background.
{
Private::SafeSavingLocker locker(d, this);
if (locker.successfullyLocked()) {
copiedImage = d->image; //->clone(true);
}
else if (!isAutosaving()) {
// even though it is a recovery operation, we should ensure we do not enter saving twice!
std::unique_lock> l(d->savingLock, std::try_to_lock);
if (l.owns_lock()) {
d->lastErrorMessage = i18n("The image was still busy while saving. Your saved image might be incomplete.");
d->image->lock();
copiedImage = d->image; //->clone(true);
//copiedImage->initialRefreshGraph();
d->image->unlock();
}
}
}
bool result = false;
// ensure we do not enter saving twice
if (copiedImage && d->savingMutex.tryLock()) {
d->savingImage = copiedImage;
result = true;
} else {
qWarning() << "Could not lock the document for saving!";
d->lastErrorMessage = i18n("Could not lock the image for saving.");
}
return result;
}
void KisDocument::unlockAfterSaving()
{
d->savingImage = 0;
d->savingMutex.unlock();
}
diff --git a/libs/ui/KisImportExportFilter.h b/libs/ui/KisImportExportFilter.h
index 2cf439499c..0c163f7662 100644
--- a/libs/ui/KisImportExportFilter.h
+++ b/libs/ui/KisImportExportFilter.h
@@ -1,176 +1,179 @@
/* This file is part of the Calligra libraries
Copyright (C) 2001 Werner Trobin
2002 Werner Trobin
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef KIS_IMPORT_EXPORT_FILTER_H
#define KIS_IMPORT_EXPORT_FILTER_H
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
class KoUpdater;
class KisDocument;
class KisConfigWidget;
#include "kritaui_export.h"
/**
* @brief The base class for import and export filters.
*
* Derive your filter class from this base class and implement
* the @ref convert() method. Don't forget to specify the Q_OBJECT
* macro in your class even if you don't use signals or slots.
* This is needed as filters are created on the fly.
*
* @note Take care: The m_chain pointer is invalid while the constructor
* runs due to the implementation -- @em don't use it in the constructor.
* After the constructor, when running the @ref convert() method it's
* guaranteed to be valid, so no need to check against 0.
*
* @note If the code is compiled in debug mode, setting CALLIGRA_DEBUG_FILTERS
* environment variable to any value disables deletion of temporary files while
* importing/exporting. This is useful for testing purposes.
*
* @author Werner Trobin
* @todo the class has no constructor and therefore cannot initialize its private class
*/
class KRITAUI_EXPORT KisImportExportFilter : public QObject
{
Q_OBJECT
public:
/**
* This enum is used to signal the return state of your filter.
* Return OK in @ref convert() in case everything worked as expected.
* Feel free to add some more error conditions @em before the last item
* if it's needed.
*/
enum ConversionStatus { OK,
UsageError,
CreationError,
FileNotFound,
StorageCreationError,
BadMimeType,
BadConversionGraph,
WrongFormat,
NotImplemented,
ParsingError,
InternalError,
UserCancelled,
InvalidFormat,
FilterCreationError,
ProgressCancelled,
UnsupportedVersion,
JustInCaseSomeBrokenCompilerUsesLessThanAByte = 255
};
virtual ~KisImportExportFilter();
void setBatchMode(bool batchmode);
void setFilename(const QString &filename);
void setRealFilename(const QString &filename);
void setMimeType(const QString &mime);
void setUpdater(QPointer updater);
/**
* The filter chain calls this method to perform the actual conversion.
* The passed mimetypes should be a pair of those you specified in your
* .desktop file.
* You @em have to implement this method to make the filter work.
*
* @return The error status, see the @ref #ConversionStatus enum.
* KisImportExportFilter::OK means that everything is alright.
*/
virtual ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) = 0;
/**
* Get the text version of the status value
*/
static QString conversionStatusString(ConversionStatus status);
/**
* @brief defaultConfiguration defines the default settings for the given import export filter
* @param from The mimetype of the source file/document
* @param to The mimetype of the destination file/document
* @return a serializable KisPropertiesConfiguration object
*/
virtual KisPropertiesConfigurationSP defaultConfiguration(const QByteArray& from = "", const QByteArray& to = "") const;
/**
* @brief lastSavedConfiguration return the last saved configuration for this filter
* @param from The mimetype of the source file/document
* @param to The mimetype of the destination file/document
* @return a serializable KisPropertiesConfiguration object
*/
virtual KisPropertiesConfigurationSP lastSavedConfiguration(const QByteArray &from = "", const QByteArray &to = "") const;
/**
* @brief createConfigurationWidget creates a widget that can be used to define the settings for a given import/export filter
* @param parent the ownder of the widget; the caller is responsible for deleting
* @param from The mimetype of the source file/document
* @param to The mimetype of the destination file/document
*
* @return the widget
*/
virtual KisConfigWidget *createConfigurationWidget(QWidget *parent, const QByteArray& from = "", const QByteArray& to = "") const;
/**
* @brief generate and return the list of capabilities of this export filter. The list
* @return returns the list of capabilities of this export filter
*/
virtual QMap exportChecks();
+ /// Override and return false for the filters that use a library that cannot handle file handles, only file names.
+ virtual bool supportsIO() const { return true; }
+
protected:
/**
* This is the constructor your filter has to call, obviously.
*/
KisImportExportFilter(QObject *parent = 0);
QString filename() const;
QString realFilename() const;
bool batchMode() const;
QByteArray mimeType() const;
void setProgress(int value);
virtual void initializeCapabilities();
void addCapability(KisExportCheckBase *capability);
void addSupportedColorModels(QList > supportedColorModels, const QString &name, KisExportCheckBase::Level level = KisExportCheckBase::PARTIALLY);
private:
KisImportExportFilter(const KisImportExportFilter& rhs);
KisImportExportFilter& operator=(const KisImportExportFilter& rhs);
class Private;
Private *const d;
};
#endif
diff --git a/libs/ui/KisImportExportManager.cpp b/libs/ui/KisImportExportManager.cpp
index 06c2e67906..d91be7cb6f 100644
--- a/libs/ui/KisImportExportManager.cpp
+++ b/libs/ui/KisImportExportManager.cpp
@@ -1,458 +1,480 @@
/*
* Copyright (C) 2016 Boudewijn Rempt
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "KisImportExportManager.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
+#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "kis_config.h"
#include "KisImportExportFilter.h"
#include "KisDocument.h"
#include
#include
#include "kis_guides_config.h"
#include "kis_grid_config.h"
#include "kis_popup_button.h"
#include
// static cache for import and export mimetypes
QStringList KisImportExportManager::m_importMimeTypes;
QStringList KisImportExportManager::m_exportMimeTypes;
class Q_DECL_HIDDEN KisImportExportManager::Private
{
public:
bool batchMode {false};
QPointer progressUpdater {0};
};
KisImportExportManager::KisImportExportManager(KisDocument* document)
: m_document(document)
, d(new Private)
{
}
KisImportExportManager::~KisImportExportManager()
{
delete d;
}
KisImportExportFilter::ConversionStatus KisImportExportManager::importDocument(const QString& location, const QString& mimeType)
{
return convert(Import, location, location, mimeType, false, 0);
}
KisImportExportFilter::ConversionStatus KisImportExportManager::exportDocument(const QString& location, const QString& realLocation, QByteArray& mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
{
return convert(Export, location, realLocation, mimeType, showWarnings, exportConfiguration);
}
// The static method to figure out to which parts of the
// graph this mimetype has a connection to.
QStringList KisImportExportManager::mimeFilter(Direction direction)
{
// Find the right mimetype by the extension
QSet mimeTypes;
// mimeTypes << KisDocument::nativeFormatMimeType() << "application/x-krita-paintoppreset" << "image/openraster";
if (direction == KisImportExportManager::Import) {
if (m_importMimeTypes.isEmpty()) {
KoJsonTrader trader;
QListlist = trader.query("Krita/FileFilter", "");
Q_FOREACH(QPluginLoader *loader, list) {
QJsonObject json = loader->metaData().value("MetaData").toObject();
Q_FOREACH(const QString &mimetype, json.value("X-KDE-Import").toString().split(",", QString::SkipEmptyParts)) {
//qDebug() << "Adding import mimetype" << mimetype << KisMimeDatabase::descriptionForMimeType(mimetype) << "from plugin" << loader;
mimeTypes << mimetype;
}
}
qDeleteAll(list);
m_importMimeTypes = mimeTypes.toList();
}
return m_importMimeTypes;
}
else if (direction == KisImportExportManager::Export) {
if (m_exportMimeTypes.isEmpty()) {
KoJsonTrader trader;
QListlist = trader.query("Krita/FileFilter", "");
Q_FOREACH(QPluginLoader *loader, list) {
QJsonObject json = loader->metaData().value("MetaData").toObject();
Q_FOREACH(const QString &mimetype, json.value("X-KDE-Export").toString().split(",", QString::SkipEmptyParts)) {
//qDebug() << "Adding export mimetype" << mimetype << KisMimeDatabase::descriptionForMimeType(mimetype) << "from plugin" << loader;
mimeTypes << mimetype;
}
}
qDeleteAll(list);
m_exportMimeTypes = mimeTypes.toList();
}
return m_exportMimeTypes;
}
return QStringList();
}
KisImportExportFilter *KisImportExportManager::filterForMimeType(const QString &mimetype, KisImportExportManager::Direction direction)
{
int weight = -1;
KisImportExportFilter *filter = 0;
KoJsonTrader trader;
QListlist = trader.query("Krita/FileFilter", "");
Q_FOREACH(QPluginLoader *loader, list) {
QJsonObject json = loader->metaData().value("MetaData").toObject();
QString directionKey = direction == Export ? "X-KDE-Export" : "X-KDE-Import";
if (json.value(directionKey).toString().split(",", QString::SkipEmptyParts).contains(mimetype)) {
KLibFactory *factory = qobject_cast(loader->instance());
if (!factory) {
warnUI << loader->errorString();
continue;
}
QObject* obj = factory->create(0);
if (!obj || !obj->inherits("KisImportExportFilter")) {
delete obj;
continue;
}
KisImportExportFilter *f = qobject_cast(obj);
if (!f) {
delete obj;
continue;
}
int w = json.value("X-KDE-Weight").toInt();
if (w > weight) {
delete filter;
filter = f;
f->setObjectName(loader->fileName());
weight = w;
}
}
}
qDeleteAll(list);
if (filter) {
filter->setMimeType(mimetype);
}
return filter;
}
void KisImportExportManager::setBatchMode(const bool batch)
{
d->batchMode = batch;
}
bool KisImportExportManager::batchMode(void) const
{
return d->batchMode;
}
void KisImportExportManager::setProgresUpdater(KoProgressUpdater *updater)
{
d->progressUpdater = updater;
}
QString KisImportExportManager::askForAudioFileName(const QString &defaultDir, QWidget *parent)
{
KoFileDialog dialog(parent, KoFileDialog::ImportFiles, "ImportAudio");
if (!defaultDir.isEmpty()) {
dialog.setDefaultDir(defaultDir);
}
QStringList mimeTypes;
mimeTypes << "audio/mpeg";
mimeTypes << "audio/ogg";
mimeTypes << "audio/vorbis";
mimeTypes << "audio/vnd.wave";
mimeTypes << "audio/flac";
dialog.setMimeTypeFilters(mimeTypes);
dialog.setCaption(i18nc("@titile:window", "Open Audio"));
return dialog.filename();
}
KisImportExportFilter::ConversionStatus KisImportExportManager::convert(KisImportExportManager::Direction direction, const QString &location, const QString& realLocation, const QString &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
{
QString typeName = mimeType;
if (typeName.isEmpty()) {
typeName = KisMimeDatabase::mimeTypeForFile(location);
}
QSharedPointer filter(filterForMimeType(typeName, direction));
if (!filter) {
return KisImportExportFilter::FilterCreationError;
}
filter->setFilename(location);
filter->setRealFilename(realLocation);
filter->setBatchMode(batchMode());
filter->setMimeType(typeName);
if (d->progressUpdater) {
filter->setUpdater(d->progressUpdater->startSubtask());
}
QByteArray from, to;
if (direction == Export) {
from = m_document->nativeFormatMimeType();
to = mimeType.toLatin1();
}
else {
from = mimeType.toLatin1();
to = m_document->nativeFormatMimeType();
}
if (!exportConfiguration) {
exportConfiguration = filter->lastSavedConfiguration(from, to);
if (exportConfiguration) {
// Fill with some meta information about the image
KisImageWSP image = m_document->image();
KisPaintDeviceSP pd = image->projection();
bool isThereAlpha = false;
KisSequentialConstIterator it(pd, image->bounds());
const KoColorSpace* cs = pd->colorSpace();
do {
if (cs->opacityU8(it.oldRawData()) != OPACITY_OPAQUE_U8) {
isThereAlpha = true;
break;
}
} while (it.nextPixel());
exportConfiguration->setProperty("ImageContainsTransparency", isThereAlpha);
exportConfiguration->setProperty("ColorModelID", cs->colorModelId().id());
exportConfiguration->setProperty("ColorDepthID", cs->colorDepthId().id());
bool sRGB = (cs->profile()->name().contains(QLatin1String("srgb"), Qt::CaseInsensitive) && !cs->profile()->name().contains(QLatin1String("g10")));
exportConfiguration->setProperty("sRGB", sRGB);
}
}
KisPreExportChecker checker;
if (direction == Export) {
checker.check(m_document->image(), filter->exportChecks());
}
KisConfigWidget *wdg = filter->createConfigurationWidget(0, from, to);
bool alsoAsKra = false;
QStringList warnings = checker.warnings();
QStringList errors = checker.errors();
// Extra checks that cannot be done by the checker, because the checker only has access to the image.
if (!m_document->assistants().isEmpty() && typeName != m_document->nativeFormatMimeType()) {
warnings.append(i18nc("image conversion warning", "The image contains assistants. The assistants will not be saved."));
}
if (m_document->guidesConfig().hasGuides() && typeName != m_document->nativeFormatMimeType()) {
warnings.append(i18nc("image conversion warning", "The image contains guides. The guides will not be saved."));
}
if (!m_document->gridConfig().isDefault() && typeName != m_document->nativeFormatMimeType()) {
warnings.append(i18nc("image conversion warning", "The image contains a custom grid configuration. The configuration will not be saved."));
}
if (!batchMode() && !errors.isEmpty()) {
QString error = ""
+ i18n("Error: cannot save this image as a %1.", KisMimeDatabase::descriptionForMimeType(typeName))
+ " Reasons:
"
+ "";
Q_FOREACH(const QString &w, errors) {
error += "\n- " + w + "
";
}
error += "
";
QMessageBox::critical(KisPart::instance()->currentMainwindow(), i18nc("@title:window", "Krita: Export Error"), error);
return KisImportExportFilter::UserCancelled;
}
if (!batchMode() && (wdg || !warnings.isEmpty())) {
KoDialog dlg;
dlg.setButtons(KoDialog::Ok | KoDialog::Cancel);
dlg.setWindowTitle(KisMimeDatabase::descriptionForMimeType(mimeType));
QWidget *page = new QWidget(&dlg);
QVBoxLayout *layout = new QVBoxLayout(page);
if (!checker.warnings().isEmpty()) {
if (showWarnings) {
QHBoxLayout *hLayout = new QHBoxLayout();
QLabel *labelWarning = new QLabel();
labelWarning->setPixmap(KisIconUtils::loadIcon("dialog-warning").pixmap(32, 32));
hLayout->addWidget(labelWarning);
KisPopupButton *bn = new KisPopupButton(0);
bn->setText(i18nc("Keep the extra space at the end of the sentence, please", "Warning: saving as %1 will lose information from your image. ", KisMimeDatabase::descriptionForMimeType(mimeType)));
hLayout->addWidget(bn);
layout->addLayout(hLayout);
QTextBrowser *browser = new QTextBrowser();
browser->setMinimumWidth(bn->width());
bn->setPopupWidget(browser);
QString warning = ""
+ i18n("You will lose information when saving this image as a %1.", KisMimeDatabase::descriptionForMimeType(typeName));
if (warnings.size() == 1) {
warning += " Reason:
";
}
else {
warning += " Reasons:";
}
warning += "";
Q_FOREACH(const QString &w, warnings) {
warning += "\n- " + w + "
";
}
warning += "
";
browser->setHtml(warning);
}
}
if (wdg) {
QGroupBox *box = new QGroupBox(i18n("Options"));
QVBoxLayout *boxLayout = new QVBoxLayout(box);
wdg->setConfiguration(exportConfiguration);
boxLayout->addWidget(wdg);
layout->addWidget(box);
}
QCheckBox *chkAlsoAsKra = 0;
if (showWarnings) {
if (!checker.warnings().isEmpty()) {
chkAlsoAsKra = new QCheckBox(i18n("Also save your image as a Krita file."));
chkAlsoAsKra->setChecked(KisConfig().readEntry("AlsoSaveAsKra", false));
layout->addWidget(chkAlsoAsKra);
}
}
dlg.setMainWidget(page);
dlg.resize(dlg.minimumSize());
if (showWarnings || wdg) {
if (!dlg.exec()) {
return KisImportExportFilter::UserCancelled;
}
}
if (chkAlsoAsKra) {
KisConfig().writeEntry("AlsoSaveAsKra", chkAlsoAsKra->isChecked());
alsoAsKra = chkAlsoAsKra->isChecked();
}
if (wdg) {
exportConfiguration = wdg->configuration();
}
}
QFile io(location);
+ QSaveFile out(location);
if (direction == Import) {
if (!io.exists()) {
return KisImportExportFilter::FileNotFound;
}
- if (!io.open(QFile::ReadOnly)) {
+ if (!io.open(QFile::ReadOnly) ) {
return KisImportExportFilter::FileNotFound;
}
}
else if (direction == Export) {
- if (!io.open(QFile::WriteOnly)) {
- return KisImportExportFilter::CreationError;
+ if (filter->supportsIO()) {
+ out.setDirectWriteFallback(true);
+ if (!out.open(QFile::WriteOnly)) {
+ out.cancelWriting();
+ return KisImportExportFilter::CreationError;
+ }
}
}
else {
return KisImportExportFilter::BadConversionGraph;
}
if (!batchMode()) {
QApplication::setOverrideCursor(Qt::WaitCursor);
}
- KisImportExportFilter::ConversionStatus status = filter->convert(m_document, &io, exportConfiguration);
- io.close();
+ KisImportExportFilter::ConversionStatus status;
+ if (direction == Import || !filter->supportsIO()) {
+ status = filter->convert(m_document, &io, exportConfiguration);
+ if (io.isOpen()) io.close();
+ }
+ else {
+ status = filter->convert(m_document, &out, exportConfiguration);
+ if (status != KisImportExportFilter::OK) {
+ out.cancelWriting();
+ }
+ else {
+ out.commit();
+ }
+ }
if (exportConfiguration) {
KisConfig().setExportConfiguration(typeName, exportConfiguration);
}
if (alsoAsKra) {
QString l = location + ".kra";
QByteArray ba = m_document->nativeFormatMimeType();
KisImportExportFilter *filter = filterForMimeType(QString::fromLatin1(ba), Export);
- QFile f(l);
+ QSaveFile f(l);
f.open(QIODevice::WriteOnly);
if (filter) {
filter->setFilename(l);
- filter->convert(m_document, &f);
+ if (filter->convert(m_document, &f) == KisImportExportFilter::OK) {
+ f.commit();
+ }
+ else {
+ f.cancelWriting();
+ }
}
- f.close();
delete filter;
}
if (!batchMode()) {
QApplication::restoreOverrideCursor();
}
return status;
}
#include
diff --git a/libs/ui/kis_png_converter.cpp b/libs/ui/kis_png_converter.cpp
index 726d0a6f02..6117269b91 100644
--- a/libs/ui/kis_png_converter.cpp
+++ b/libs/ui/kis_png_converter.cpp
@@ -1,1287 +1,1286 @@
/*
* Copyright (c) 2005-2007 Cyrille Berger
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kis_png_converter.h"
// A big thank to Glenn Randers-Pehrson for his wonderful
// documentation of libpng available at
// http://www.libpng.org/pub/png/libpng-1.2.5-manual.html
#ifndef PNG_MAX_UINT // Removed in libpng 1.4
#define PNG_MAX_UINT PNG_UINT_31_MAX
#endif
#include // WORDS_BIGENDIAN
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "dialogs/kis_dlg_png_import.h"
#include "kis_clipboard.h"
namespace
{
int getColorTypeforColorSpace(const KoColorSpace * cs , bool alpha)
{
QString id = cs->id();
if (id == "GRAYA" || id == "GRAYAU16" || id == "GRAYA16") {
return alpha ? PNG_COLOR_TYPE_GRAY_ALPHA : PNG_COLOR_TYPE_GRAY;
}
if (id == "RGBA" || id == "RGBA16") {
return alpha ? PNG_COLOR_TYPE_RGB_ALPHA : PNG_COLOR_TYPE_RGB;
}
return -1;
}
bool colorSpaceIdSupported(const QString &id)
{
return id == "RGBA" || id == "RGBA16" ||
id == "GRAYA" || id == "GRAYAU16" || id == "GRAYA16";
}
QPair getColorSpaceForColorType(int color_type, int color_nb_bits)
{
QPair r;
if (color_type == PNG_COLOR_TYPE_PALETTE) {
r.first = RGBAColorModelID.id();
r.second = Integer8BitsColorDepthID.id();
} else {
if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
r.first = GrayAColorModelID.id();
} else if (color_type == PNG_COLOR_TYPE_RGB_ALPHA || color_type == PNG_COLOR_TYPE_RGB) {
r.first = RGBAColorModelID.id();
}
if (color_nb_bits == 16) {
r.second = Integer16BitsColorDepthID.id();
} else if (color_nb_bits <= 8) {
r.second = Integer8BitsColorDepthID.id();
}
}
return r;
}
void fillText(png_text* p_text, const char* key, QString& text)
{
p_text->compression = PNG_TEXT_COMPRESSION_zTXt;
p_text->key = const_cast(key);
char* textc = new char[text.length()+1];
strcpy(textc, text.toLatin1());
p_text->text = textc;
p_text->text_length = text.length() + 1;
}
long formatStringList(char *string, const size_t length, const char *format, va_list operands)
{
int n = vsnprintf(string, length, format, operands);
if (n < 0)
string[length-1] = '\0';
return((long) n);
}
long formatString(char *string, const size_t length, const char *format, ...)
{
long n;
va_list operands;
va_start(operands, format);
n = (long) formatStringList(string, length, format, operands);
va_end(operands);
return(n);
}
void writeRawProfile(png_struct *ping, png_info *ping_info, QString profile_type, QByteArray profile_data)
{
png_textp text;
png_uint_32 allocated_length, description_length;
const uchar hex[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
dbgFile << "Writing Raw profile: type=" << profile_type << ", length=" << profile_data.length() << endl;
text = (png_textp) png_malloc(ping, (png_uint_32) sizeof(png_text));
description_length = profile_type.length();
allocated_length = (png_uint_32)(profile_data.length() * 2 + (profile_data.length() >> 5) + 20 + description_length);
text[0].text = (png_charp) png_malloc(ping, allocated_length);
QString key = QLatin1Literal("Raw profile type ") + profile_type.toLatin1();
QByteArray keyData = key.toLatin1();
text[0].key = keyData.data();
uchar* sp = (uchar*)profile_data.data();
png_charp dp = text[0].text;
*dp++ = '\n';
memcpy(dp, profile_type.toLatin1().constData(), profile_type.length());
dp += description_length;
*dp++ = '\n';
formatString(dp, allocated_length - strlen(text[0].text), "%8lu ", profile_data.length());
dp += 8;
for (long i = 0; i < (long) profile_data.length(); i++) {
if (i % 36 == 0)
*dp++ = '\n';
*(dp++) = (char) hex[((*sp >> 4) & 0x0f)];
*(dp++) = (char) hex[((*sp++) & 0x0f)];
}
*dp++ = '\n';
*dp = '\0';
text[0].text_length = (png_size_t)(dp - text[0].text);
text[0].compression = -1;
if (text[0].text_length <= allocated_length)
png_set_text(ping, ping_info, text, 1);
png_free(ping, text[0].text);
png_free(ping, text);
}
QByteArray png_read_raw_profile(png_textp text)
{
QByteArray profile;
static const unsigned char unhex[103] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 11, 12,
13, 14, 15
};
png_charp sp = text[0].text + 1;
/* look for newline */
while (*sp != '\n')
sp++;
/* look for length */
while (*sp == '\0' || *sp == ' ' || *sp == '\n')
sp++;
png_uint_32 length = (png_uint_32) atol(sp);
while (*sp != ' ' && *sp != '\n')
sp++;
if (length == 0) {
return profile;
}
profile.resize(length);
/* copy profile, skipping white space and column 1 "=" signs */
unsigned char *dp = (unsigned char*)profile.data();
png_uint_32 nibbles = length * 2;
for (png_uint_32 i = 0; i < nibbles; i++) {
while (*sp < '0' || (*sp > '9' && *sp < 'a') || *sp > 'f') {
if (*sp == '\0') {
return QByteArray();
}
sp++;
}
if (i % 2 == 0)
*dp = (unsigned char)(16 * unhex[(int) *sp++]);
else
(*dp++) += unhex[(int) *sp++];
}
return profile;
}
void decode_meta_data(png_textp text, KisMetaData::Store* store, QString type, int headerSize)
{
dbgFile << "Decoding " << type << " " << text[0].key;
KisMetaData::IOBackend* exifIO = KisMetaData::IOBackendRegistry::instance()->value(type);
Q_ASSERT(exifIO);
QByteArray rawProfile = png_read_raw_profile(text);
if (headerSize > 0) {
rawProfile.remove(0, headerSize);
}
if (rawProfile.size() > 0) {
QBuffer buffer;
buffer.setData(rawProfile);
exifIO->loadFrom(store, &buffer);
} else {
dbgFile << "Decoding failed";
}
}
}
KisPNGConverter::KisPNGConverter(KisDocument *doc, bool batchMode)
{
// Q_ASSERT(doc);
// Q_ASSERT(adapter);
m_doc = doc;
m_stop = false;
m_max_row = 0;
m_image = 0;
m_batchMode = batchMode;
}
KisPNGConverter::~KisPNGConverter()
{
}
class KisPNGReadStream
{
public:
KisPNGReadStream(quint8* buf, quint32 depth) : m_posinc(8), m_depth(depth), m_buf(buf) {
}
int nextValue() {
if (m_posinc == 0) {
m_posinc = 8;
m_buf++;
}
m_posinc -= m_depth;
return (((*m_buf) >> (m_posinc)) & ((1 << m_depth) - 1));
}
private:
quint32 m_posinc, m_depth;
quint8* m_buf;
};
class KisPNGWriteStream
{
public:
KisPNGWriteStream(quint8* buf, quint32 depth) : m_posinc(8), m_depth(depth), m_buf(buf) {
*m_buf = 0;
}
void setNextValue(int v) {
if (m_posinc == 0) {
m_posinc = 8;
m_buf++;
*m_buf = 0;
}
m_posinc -= m_depth;
*m_buf = (v << m_posinc) | *m_buf;
}
private:
quint32 m_posinc, m_depth;
quint8* m_buf;
};
class KisPNGReaderAbstract
{
public:
KisPNGReaderAbstract(png_structp _png_ptr, int _width, int _height) : png_ptr(_png_ptr), width(_width), height(_height) {}
virtual ~KisPNGReaderAbstract() {}
virtual png_bytep readLine() = 0;
protected:
png_structp png_ptr;
int width, height;
};
class KisPNGReaderLineByLine : public KisPNGReaderAbstract
{
public:
KisPNGReaderLineByLine(png_structp _png_ptr, png_infop info_ptr, int _width, int _height) : KisPNGReaderAbstract(_png_ptr, _width, _height) {
png_uint_32 rowbytes = png_get_rowbytes(png_ptr, info_ptr);
row_pointer = new png_byte[rowbytes];
}
~KisPNGReaderLineByLine() override {
delete[] row_pointer;
}
png_bytep readLine() override {
png_read_row(png_ptr, row_pointer, 0);
return row_pointer;
}
private:
png_bytep row_pointer;
};
class KisPNGReaderFullImage : public KisPNGReaderAbstract
{
public:
KisPNGReaderFullImage(png_structp _png_ptr, png_infop info_ptr, int _width, int _height) : KisPNGReaderAbstract(_png_ptr, _width, _height), y(0) {
row_pointers = new png_bytep[height];
png_uint_32 rowbytes = png_get_rowbytes(png_ptr, info_ptr);
for (int i = 0; i < height; i++) {
row_pointers[i] = new png_byte[rowbytes];
}
png_read_image(png_ptr, row_pointers);
}
~KisPNGReaderFullImage() override {
for (int i = 0; i < height; i++) {
delete[] row_pointers[i];
}
delete[] row_pointers;
}
png_bytep readLine() override {
return row_pointers[y++];
}
private:
png_bytepp row_pointers;
int y;
};
static
void _read_fn(png_structp png_ptr, png_bytep data, png_size_t length)
{
QIODevice *in = (QIODevice *)png_get_io_ptr(png_ptr);
while (length) {
int nr = in->read((char*)data, length);
if (nr <= 0) {
png_error(png_ptr, "Read Error");
return;
}
length -= nr;
}
}
static
void _write_fn(png_structp png_ptr, png_bytep data, png_size_t length)
{
QIODevice* out = (QIODevice*)png_get_io_ptr(png_ptr);
uint nr = out->write((char*)data, length);
if (nr != length) {
png_error(png_ptr, "Write Error");
return;
}
}
static
void _flush_fn(png_structp png_ptr)
{
Q_UNUSED(png_ptr);
}
KisImageBuilder_Result KisPNGConverter::buildImage(QIODevice* iod)
{
dbgFile << "Start decoding PNG File";
png_byte signature[8];
iod->peek((char*)signature, 8);
#if PNG_LIBPNG_VER < 10400
if (!png_check_sig(signature, 8)) {
#else
if (png_sig_cmp(signature, 0, 8) != 0) {
#endif
iod->close();
return (KisImageBuilder_RESULT_BAD_FETCH);
}
// Initialize the internal structures
png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
if (!png_ptr) {
iod->close();
}
png_infop info_ptr = png_create_info_struct(png_ptr);
if (!info_ptr) {
png_destroy_read_struct(&png_ptr, (png_infopp)0, (png_infopp)0);
iod->close();
return (KisImageBuilder_RESULT_FAILURE);
}
png_infop end_info = png_create_info_struct(png_ptr);
if (!end_info) {
png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)0);
iod->close();
return (KisImageBuilder_RESULT_FAILURE);
}
// Catch errors
if (setjmp(png_jmpbuf(png_ptr))) {
png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
iod->close();
return (KisImageBuilder_RESULT_FAILURE);
}
// Initialize the special
png_set_read_fn(png_ptr, iod, _read_fn);
#if defined(PNG_SKIP_sRGB_CHECK_PROFILE) && defined(PNG_SET_OPTION_SUPPORTED)
png_set_option(png_ptr, PNG_SKIP_sRGB_CHECK_PROFILE, PNG_OPTION_ON);
#endif
// read all PNG info up to image data
png_read_info(png_ptr, info_ptr);
if (png_get_color_type(png_ptr, info_ptr) == PNG_COLOR_TYPE_GRAY && png_get_bit_depth(png_ptr, info_ptr) < 8) {
png_set_expand(png_ptr);
}
if (png_get_color_type(png_ptr, info_ptr) == PNG_COLOR_TYPE_PALETTE && png_get_bit_depth(png_ptr, info_ptr) < 8) {
png_set_packing(png_ptr);
}
if (png_get_color_type(png_ptr, info_ptr) != PNG_COLOR_TYPE_PALETTE &&
(png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))) {
png_set_expand(png_ptr);
}
png_read_update_info(png_ptr, info_ptr);
// Read information about the png
png_uint_32 width, height;
int color_nb_bits, color_type, interlace_type;
png_get_IHDR(png_ptr, info_ptr, &width, &height, &color_nb_bits, &color_type, &interlace_type, 0, 0);
dbgFile << "width = " << width << " height = " << height << " color_nb_bits = " << color_nb_bits << " color_type = " << color_type << " interlace_type = " << interlace_type << endl;
// swap byteorder on little endian machines.
#ifndef WORDS_BIGENDIAN
if (color_nb_bits > 8)
png_set_swap(png_ptr);
#endif
// Determine the colorspace
QPair csName = getColorSpaceForColorType(color_type, color_nb_bits);
if (csName.first.isEmpty()) {
png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
iod->close();
return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE;
}
bool hasalpha = (color_type == PNG_COLOR_TYPE_RGB_ALPHA || color_type == PNG_COLOR_TYPE_GRAY_ALPHA);
// Read image profile
png_charp profile_name;
#if PNG_LIBPNG_VER_MAJOR >= 1 && PNG_LIBPNG_VER_MINOR >= 5
png_bytep profile_data;
#else
png_charp profile_data;
#endif
int compression_type;
png_uint_32 proflen;
// Get the various optional chunks
// https://www.w3.org/TR/PNG/#11cHRM
#if defined(PNG_cHRM_SUPPORTED)
double whitePointX, whitePointY;
double redX, redY;
double greenX, greenY;
double blueX, blueY;
png_get_cHRM(png_ptr,info_ptr, &whitePointX, &whitePointY, &redX, &redY, &greenX, &greenY, &blueX, &blueY);
qDebug() << "cHRM:" << whitePointX << whitePointY << redX << redY << greenX << greenY << blueX << blueY;
#endif
// https://www.w3.org/TR/PNG/#11gAMA
#if defined(PNG_GAMMA_SUPPORTED)
double gamma;
png_get_gAMA(png_ptr, info_ptr, &gamma);
qDebug() << "gAMA" << gamma;
#endif
// https://www.w3.org/TR/PNG/#11sRGB
#if defined(PNG_sRGB_SUPPORTED)
int sRGBIntent;
png_get_sRGB(png_ptr, info_ptr, &sRGBIntent);
qDebug() << "sRGB" << sRGBIntent;
#endif
bool fromBlender = false;
png_text* text_ptr;
int num_comments;
png_get_text(png_ptr, info_ptr, &text_ptr, &num_comments);
for (int i = 0; i < num_comments; i++) {
QString key = QString(text_ptr[i].key).toLower();
if (key == "file") {
QString relatedFile = text_ptr[i].text;
if (relatedFile.contains(".blend", Qt::CaseInsensitive)){
fromBlender=true;
}
}
}
const KoColorProfile* profile = 0;
if (png_get_iCCP(png_ptr, info_ptr, &profile_name, &compression_type, &profile_data, &proflen)) {
QByteArray profile_rawdata;
// XXX: Hardcoded for icc type -- is that correct for us?
profile_rawdata.resize(proflen);
memcpy(profile_rawdata.data(), profile_data, proflen);
profile = KoColorSpaceRegistry::instance()->createColorProfile(csName.first, csName.second, profile_rawdata);
Q_CHECK_PTR(profile);
if (profile) {
// dbgFile << "profile name: " << profile->productName() << " profile description: " << profile->productDescription() << " information sur le produit: " << profile->productInfo();
if (!profile->isSuitableForOutput()) {
dbgFile << "the profile is not suitable for output and therefore cannot be used in krita, we need to convert the image to a standard profile"; // TODO: in ko2 popup a selection menu to inform the user
}
}
}
else {
dbgFile << "no embedded profile, will use the default profile";
if (color_nb_bits == 16 && !fromBlender && !qAppName().toLower().contains("test") && !m_batchMode) {
KisConfig cfg;
quint32 behaviour = cfg.pasteBehaviour();
if (behaviour == PASTE_ASK) {
KisDlgPngImport dlg(m_path, csName.first, csName.second);
QApplication::restoreOverrideCursor();
dlg.exec();
if (!dlg.profile().isEmpty()) {
QString s = KoColorSpaceRegistry::instance()->colorSpaceId(csName.first, csName.second);
const KoColorSpaceFactory * csf = KoColorSpaceRegistry::instance()->colorSpaceFactory(s);
if (csf) {
QList profileList = KoColorSpaceRegistry::instance()->profilesFor(csf);
Q_FOREACH (const KoColorProfile *p, profileList) {
if (p->name() == dlg.profile()) {
profile = p;
break;
}
}
}
}
QApplication::setOverrideCursor(Qt::WaitCursor);
}
}
dbgFile << "no embedded profile, will use the default profile";
}
// Check that the profile is used by the color space
if (profile && !KoColorSpaceRegistry::instance()->colorSpaceFactory(
KoColorSpaceRegistry::instance()->colorSpaceId(
csName.first, csName.second))->profileIsCompatible(profile)) {
warnFile << "The profile " << profile->name() << " is not compatible with the color space model " << csName.first << " " << csName.second;
profile = 0;
}
// Retrieve a pointer to the colorspace
const KoColorSpace* cs;
if (profile && profile->isSuitableForOutput()) {
dbgFile << "image has embedded profile: " << profile->name() << "\n";
cs = KoColorSpaceRegistry::instance()->colorSpace(csName.first, csName.second, profile);
}
else {
if (csName.first == RGBAColorModelID.id()) {
cs = KoColorSpaceRegistry::instance()->colorSpace(csName.first, csName.second, "sRGB-elle-V2-srgbtrc.icc");
}
else {
cs = KoColorSpaceRegistry::instance()->colorSpace(csName.first, csName.second, 0);
}
}
if (cs == 0) {
png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE;
}
//TODO: two fixes : one tell the user about the problem and ask for a solution, and two once the kocolorspace include KoColorTransformation, use that instead of hacking a lcms transformation
// Create the cmsTransform if needed
KoColorTransformation* transform = 0;
if (profile && !profile->isSuitableForOutput()) {
transform = KoColorSpaceRegistry::instance()->colorSpace(csName.first, csName.second, profile)->createColorConverter(cs, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags());
}
// Creating the KisImageWSP
if (m_image == 0) {
m_image = new KisImage(m_doc->createUndoStore(), width, height, cs, "built image");
Q_CHECK_PTR(m_image);
}
// Read resolution
int unit_type;
png_uint_32 x_resolution, y_resolution;
png_get_pHYs(png_ptr, info_ptr, &x_resolution, &y_resolution, &unit_type);
if (x_resolution > 0 && y_resolution > 0 && unit_type == PNG_RESOLUTION_METER) {
m_image->setResolution((double) POINT_TO_CM(x_resolution) / 100.0, (double) POINT_TO_CM(y_resolution) / 100.0); // It is the "invert" macro because we convert from pointer-per-inchs to points
}
double coeff = quint8_MAX / (double)(pow((double)2, color_nb_bits) - 1);
KisPaintLayerSP layer = new KisPaintLayer(m_image.data(), m_image -> nextLayerName(), UCHAR_MAX);
// Read comments/texts...
png_get_text(png_ptr, info_ptr, &text_ptr, &num_comments);
if (m_doc) {
KoDocumentInfo * info = m_doc->documentInfo();
dbgFile << "There are " << num_comments << " comments in the text";
for (int i = 0; i < num_comments; i++) {
QString key = QString(text_ptr[i].key).toLower();
dbgFile << "key is |" << text_ptr[i].key << "| containing " << text_ptr[i].text << " " << (key == "Raw profile type exif ");
if (key == "title") {
info->setAboutInfo("title", text_ptr[i].text);
} else if (key == "description") {
info->setAboutInfo("comment", text_ptr[i].text);
} else if (key == "author") {
qDebug()<<"Author:"<setAuthorInfo("creator", text_ptr[i].text);
} else if (key.contains("Raw profile type exif")) {
decode_meta_data(text_ptr + i, layer->metaData(), "exif", 6);
} else if (key.contains("Raw profile type iptc")) {
decode_meta_data(text_ptr + i, layer->metaData(), "iptc", 14);
} else if (key.contains("Raw profile type xmp")) {
decode_meta_data(text_ptr + i, layer->metaData(), "xmp", 0);
} else if (key == "version") {
m_image->addAnnotation(new KisAnnotation("kpp_version", "version", QByteArray(text_ptr[i].text)));
} else if (key == "preset") {
m_image->addAnnotation(new KisAnnotation("kpp_preset", "preset", QByteArray(text_ptr[i].text)));
}
}
}
// Read image data
KisPNGReaderAbstract* reader = 0;
try {
if (interlace_type == PNG_INTERLACE_ADAM7) {
reader = new KisPNGReaderFullImage(png_ptr, info_ptr, width, height);
} else {
reader = new KisPNGReaderLineByLine(png_ptr, info_ptr, width, height);
}
} catch (std::bad_alloc& e) {
// new png_byte[] may raise such an exception if the image
// is invalid / to large.
dbgFile << "bad alloc: " << e.what();
// Free only the already allocated png_byte instances.
png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
return (KisImageBuilder_RESULT_FAILURE);
}
// Read the palette if the file is indexed
png_colorp palette ;
int num_palette;
if (color_type == PNG_COLOR_TYPE_PALETTE) {
png_get_PLTE(png_ptr, info_ptr, &palette, &num_palette);
}
// Read the transparency palette
quint8 palette_alpha[256];
memset(palette_alpha, 255, 256);
if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
if (color_type == PNG_COLOR_TYPE_PALETTE) {
png_bytep alpha_ptr;
int num_alpha;
png_get_tRNS(png_ptr, info_ptr, &alpha_ptr, &num_alpha, 0);
for (int i = 0; i < num_alpha; ++i) {
palette_alpha[i] = alpha_ptr[i];
}
}
}
for (png_uint_32 y = 0; y < height; y++) {
KisHLineIteratorSP it = layer -> paintDevice() -> createHLineIteratorNG(0, y, width);
png_bytep row_pointer = reader->readLine();
switch (color_type) {
case PNG_COLOR_TYPE_GRAY:
case PNG_COLOR_TYPE_GRAY_ALPHA:
if (color_nb_bits == 16) {
quint16 *src = reinterpret_cast(row_pointer);
do {
quint16 *d = reinterpret_cast(it->rawData());
d[0] = *(src++);
if (transform) transform->transform(reinterpret_cast(d), reinterpret_cast(d), 1);
if (hasalpha) {
d[1] = *(src++);
} else {
d[1] = quint16_MAX;
}
} while (it->nextPixel());
} else {
KisPNGReadStream stream(row_pointer, color_nb_bits);
do {
quint8 *d = it->rawData();
d[0] = (quint8)(stream.nextValue() * coeff);
if (transform) transform->transform(d, d, 1);
if (hasalpha) {
d[1] = (quint8)(stream.nextValue() * coeff);
} else {
d[1] = UCHAR_MAX;
}
} while (it->nextPixel());
}
// FIXME:should be able to read 1 and 4 bits depth and scale them to 8 bits"
break;
case PNG_COLOR_TYPE_RGB:
case PNG_COLOR_TYPE_RGB_ALPHA:
if (color_nb_bits == 16) {
quint16 *src = reinterpret_cast(row_pointer);
do {
quint16 *d = reinterpret_cast(it->rawData());
d[2] = *(src++);
d[1] = *(src++);
d[0] = *(src++);
if (transform) transform->transform(reinterpret_cast(d), reinterpret_cast(d), 1);
if (hasalpha) d[3] = *(src++);
else d[3] = quint16_MAX;
} while (it->nextPixel());
} else {
KisPNGReadStream stream(row_pointer, color_nb_bits);
do {
quint8 *d = it->rawData();
d[2] = (quint8)(stream.nextValue() * coeff);
d[1] = (quint8)(stream.nextValue() * coeff);
d[0] = (quint8)(stream.nextValue() * coeff);
if (transform) transform->transform(d, d, 1);
if (hasalpha) d[3] = (quint8)(stream.nextValue() * coeff);
else d[3] = UCHAR_MAX;
} while (it->nextPixel());
}
break;
case PNG_COLOR_TYPE_PALETTE: {
KisPNGReadStream stream(row_pointer, color_nb_bits);
do {
quint8 *d = it->rawData();
quint8 index = stream.nextValue();
quint8 alpha = palette_alpha[ index ];
if (alpha == 0) {
memset(d, 0, 4);
} else {
png_color c = palette[ index ];
d[2] = c.red;
d[1] = c.green;
d[0] = c.blue;
d[3] = alpha;
}
} while (it->nextPixel());
}
break;
default:
return KisImageBuilder_RESULT_UNSUPPORTED;
}
}
m_image->addNode(layer.data(), m_image->rootLayer().data());
png_read_end(png_ptr, end_info);
iod->close();
// Freeing memory
png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
delete reader;
return KisImageBuilder_RESULT_OK;
}
KisImageBuilder_Result KisPNGConverter::buildImage(const QString &filename)
{
m_path = filename;
QFile fp(filename);
if (fp.exists()) {
if (!fp.open(QIODevice::ReadOnly)) {
dbgFile << "Failed to open PNG File";
return (KisImageBuilder_RESULT_FAILURE);
}
return buildImage(&fp);
}
return (KisImageBuilder_RESULT_NOT_EXIST);
}
KisImageSP KisPNGConverter::image()
{
return m_image;
}
bool KisPNGConverter::saveDeviceToStore(const QString &filename, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP dev, KoStore *store, KisMetaData::Store* metaData)
{
if (store->open(filename)) {
KoStoreDevice io(store);
if (!io.open(QIODevice::WriteOnly)) {
dbgFile << "Could not open for writing:" << filename;
return false;
}
KisPNGConverter pngconv(0);
vKisAnnotationSP_it annotIt = 0;
KisMetaData::Store* metaDataStore = 0;
if (metaData) {
metaDataStore = new KisMetaData::Store(*metaData);
}
KisPNGOptions options;
options.compression = 0;
options.interlace = false;
options.tryToSaveAsIndexed = false;
options.alpha = true;
options.saveSRGBProfile = false;
if (dev->colorSpace()->id() != "RGBA") {
dev = new KisPaintDevice(*dev.data());
KUndo2Command *cmd = dev->convertTo(KoColorSpaceRegistry::instance()->rgb8());
delete cmd;
}
bool success = pngconv.buildFile(&io, imageRect, xRes, yRes, dev, annotIt, annotIt, options, metaDataStore);
if (success != KisImageBuilder_RESULT_OK) {
dbgFile << "Saving PNG failed:" << filename;
delete metaDataStore;
return false;
}
delete metaDataStore;
io.close();
if (!store->close()) {
return false;
}
} else {
dbgFile << "Opening of data file failed :" << filename;
return false;
}
return true;
}
KisImageBuilder_Result KisPNGConverter::buildFile(const QString &filename, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP device, vKisAnnotationSP_it annotationsStart, vKisAnnotationSP_it annotationsEnd, KisPNGOptions options, KisMetaData::Store* metaData)
{
dbgFile << "Start writing PNG File " << filename;
// Open a QIODevice for writing
QFile fp (filename);
if (!fp.open(QIODevice::WriteOnly)) {
dbgFile << "Failed to open PNG File for writing";
return (KisImageBuilder_RESULT_FAILURE);
}
KisImageBuilder_Result result = buildFile(&fp, imageRect, xRes, yRes, device, annotationsStart, annotationsEnd, options, metaData);
return result;
}
KisImageBuilder_Result KisPNGConverter::buildFile(QIODevice* iodevice, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP device, vKisAnnotationSP_it annotationsStart, vKisAnnotationSP_it annotationsEnd, KisPNGOptions options, KisMetaData::Store* metaData)
{
if (!device)
return KisImageBuilder_RESULT_INVALID_ARG;
if (!options.alpha) {
KisPaintDeviceSP tmp = new KisPaintDevice(device->colorSpace());
KoColor c(options.transparencyFillColor, device->colorSpace());
tmp->fill(imageRect, c);
KisPainter gc(tmp);
gc.bitBlt(imageRect.topLeft(), device, imageRect);
gc.end();
device = tmp;
}
if (device->colorSpace()->colorDepthId() == Float16BitsColorDepthID
|| device->colorSpace()->colorDepthId() == Float32BitsColorDepthID
|| device->colorSpace()->colorDepthId() == Float64BitsColorDepthID) {
const KoColorSpace *dstcs = KoColorSpaceRegistry::instance()->colorSpace(device->colorSpace()->colorModelId().id(), Integer8BitsColorDepthID.id(), "");
KisPaintDeviceSP tmp = new KisPaintDevice(dstcs);
KisPainter gc(tmp);
gc.bitBlt(imageRect.topLeft(), device, imageRect);
gc.end();
device = tmp;
}
if (options.forceSRGB) {
const KoColorSpace* cs = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), device->colorSpace()->colorDepthId().id(), "sRGB built-in - (lcms internal)");
device = new KisPaintDevice(*device);
device->convertTo(cs);
}
// Initialize structures
png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
if (!png_ptr) {
return (KisImageBuilder_RESULT_FAILURE);
}
#if defined(PNG_SKIP_sRGB_CHECK_PROFILE) && defined(PNG_SET_OPTION_SUPPORTED)
png_set_option(png_ptr, PNG_SKIP_sRGB_CHECK_PROFILE, PNG_OPTION_ON);
#endif
#ifdef PNG_READ_CHECK_FOR_INVALID_INDEX_SUPPORTED
png_set_check_for_invalid_index(png_ptr, 0);
#endif
png_infop info_ptr = png_create_info_struct(png_ptr);
if (!info_ptr) {
png_destroy_write_struct(&png_ptr, (png_infopp)0);
return (KisImageBuilder_RESULT_FAILURE);
}
// If an error occurs during writing, libpng will jump here
if (setjmp(png_jmpbuf(png_ptr))) {
png_destroy_write_struct(&png_ptr, &info_ptr);
return (KisImageBuilder_RESULT_FAILURE);
}
// Initialize the writing
// png_init_io(png_ptr, fp);
// Setup the progress function
// XXX: Implement progress updating -- png_set_write_status_fn(png_ptr, progress);"
// setProgressTotalSteps(100/*height*/);
/* set the zlib compression level */
png_set_compression_level(png_ptr, options.compression);
png_set_write_fn(png_ptr, (void*)iodevice, _write_fn, _flush_fn);
/* set other zlib parameters */
png_set_compression_mem_level(png_ptr, 8);
png_set_compression_strategy(png_ptr, Z_DEFAULT_STRATEGY);
png_set_compression_window_bits(png_ptr, 15);
png_set_compression_method(png_ptr, 8);
png_set_compression_buffer_size(png_ptr, 8192);
int color_nb_bits = 8 * device->pixelSize() / device->channelCount();
int color_type = getColorTypeforColorSpace(device->colorSpace(), options.alpha);
Q_ASSERT(color_type > -1);
// Try to compute a table of color if the colorspace is RGB8f
png_colorp palette = 0;
int num_palette = 0;
if (!options.alpha && options.tryToSaveAsIndexed && KoID(device->colorSpace()->id()) == KoID("RGBA")) { // png doesn't handle indexed images and alpha, and only have indexed for RGB8
palette = new png_color[255];
KisSequentialIterator it(device, imageRect);
bool toomuchcolor = false;
do {
const quint8* c = it.oldRawData();
bool findit = false;
for (int i = 0; i < num_palette; i++) {
if (palette[i].red == c[2] &&
palette[i].green == c[1] &&
palette[i].blue == c[0]) {
findit = true;
break;
}
}
if (!findit) {
if (num_palette == 255) {
toomuchcolor = true;
break;
}
palette[num_palette].red = c[2];
palette[num_palette].green = c[1];
palette[num_palette].blue = c[0];
num_palette++;
}
} while (it.nextPixel());
if (!toomuchcolor) {
dbgFile << "Found a palette of " << num_palette << " colors";
color_type = PNG_COLOR_TYPE_PALETTE;
if (num_palette <= 2) {
color_nb_bits = 1;
} else if (num_palette <= 4) {
color_nb_bits = 2;
} else if (num_palette <= 16) {
color_nb_bits = 4;
} else {
color_nb_bits = 8;
}
} else {
delete [] palette;
}
}
int interlacetype = options.interlace ? PNG_INTERLACE_ADAM7 : PNG_INTERLACE_NONE;
png_set_IHDR(png_ptr, info_ptr,
imageRect.width(),
imageRect.height(),
color_nb_bits,
color_type, interlacetype,
PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
// set sRGB only if the profile is sRGB -- http://www.w3.org/TR/PNG/#11sRGB says sRGB and iCCP should not both be present
bool sRGB = device->colorSpace()->profile()->name().contains(QLatin1String("srgb"), Qt::CaseInsensitive);
/*
* This automatically writes the correct gamma and chroma chunks along with the sRGB chunk, but firefox's
* color management is bugged, so once you give it any incentive to start color managing an sRGB image it
* will turn, for example, a nice desaturated rusty red into bright poppy red. So this is disabled for now.
*/
/*if (!options.saveSRGBProfile && sRGB) {
png_set_sRGB_gAMA_and_cHRM(png_ptr, info_ptr, PNG_sRGB_INTENT_PERCEPTUAL);
}*/
// set the palette
if (color_type == PNG_COLOR_TYPE_PALETTE) {
png_set_PLTE(png_ptr, info_ptr, palette, num_palette);
}
// Save annotation
vKisAnnotationSP_it it = annotationsStart;
while (it != annotationsEnd) {
if (!(*it) || (*it)->type().isEmpty()) {
dbgFile << "Warning: empty annotation";
it++;
continue;
}
dbgFile << "Trying to store annotation of type " << (*it) -> type() << " of size " << (*it) -> annotation() . size();
if ((*it) -> type().startsWith(QString("krita_attribute:"))) { //
// Attribute
// XXX: it should be possible to save krita_attributes in the \"CHUNKs\""
dbgFile << "cannot save this annotation : " << (*it) -> type();
} else if ((*it)->type() == "kpp_version" || (*it)->type() == "kpp_preset" ) {
dbgFile << "Saving preset information " << (*it)->description();
png_textp text = (png_textp) png_malloc(png_ptr, (png_uint_32) sizeof(png_text));
QByteArray keyData = (*it)->description().toLatin1();
text[0].key = keyData.data();
text[0].text = (char*)(*it)->annotation().data();
text[0].text_length = (*it)->annotation().size();
text[0].compression = -1;
png_set_text(png_ptr, info_ptr, text, 1);
png_free(png_ptr, text);
}
it++;
}
// Save the color profile
const KoColorProfile* colorProfile = device->colorSpace()->profile();
QByteArray colorProfileData = colorProfile->rawData();
if (!sRGB || options.saveSRGBProfile) {
#if PNG_LIBPNG_VER_MAJOR >= 1 && PNG_LIBPNG_VER_MINOR >= 5
png_set_iCCP(png_ptr, info_ptr, (char*)"icc", PNG_COMPRESSION_TYPE_BASE, (const png_bytep)colorProfileData.constData(), colorProfileData . size());
#else
png_set_iCCP(png_ptr, info_ptr, (char*)"icc", PNG_COMPRESSION_TYPE_BASE, (char*)colorProfileData.constData(), colorProfileData . size());
#endif
}
// read comments from the document information
// warning: according to the official png spec, the keys need to be capitalised!
if (m_doc) {
png_text texts[3];
int nbtexts = 0;
KoDocumentInfo * info = m_doc->documentInfo();
QString title = info->aboutInfo("title");
if (!title.isEmpty()) {
fillText(texts + nbtexts, "Title", title);
nbtexts++;
}
QString abstract = info->aboutInfo("comment");
if (!abstract.isEmpty()) {
fillText(texts + nbtexts, "Description", abstract);
nbtexts++;
}
QString author = info->authorInfo("creator");
if (!author.isEmpty()) {
fillText(texts + nbtexts, "Author", author);
nbtexts++;
}
png_set_text(png_ptr, info_ptr, texts, nbtexts);
}
// Save metadata following imagemagick way
// Save exif
if (metaData && !metaData->empty()) {
if (options.exif) {
dbgFile << "Trying to save exif information";
KisMetaData::IOBackend* exifIO = KisMetaData::IOBackendRegistry::instance()->value("exif");
Q_ASSERT(exifIO);
QBuffer buffer;
exifIO->saveTo(metaData, &buffer, KisMetaData::IOBackend::JpegHeader);
writeRawProfile(png_ptr, info_ptr, "exif", buffer.data());
}
// Save IPTC
if (options.iptc) {
dbgFile << "Trying to save exif information";
KisMetaData::IOBackend* iptcIO = KisMetaData::IOBackendRegistry::instance()->value("iptc");
Q_ASSERT(iptcIO);
QBuffer buffer;
iptcIO->saveTo(metaData, &buffer, KisMetaData::IOBackend::JpegHeader);
dbgFile << "IPTC information size is" << buffer.data().size();
writeRawProfile(png_ptr, info_ptr, "iptc", buffer.data());
}
// Save XMP
if (options.xmp) {
dbgFile << "Trying to save XMP information";
KisMetaData::IOBackend* xmpIO = KisMetaData::IOBackendRegistry::instance()->value("xmp");
Q_ASSERT(xmpIO);
QBuffer buffer;
xmpIO->saveTo(metaData, &buffer, KisMetaData::IOBackend::NoHeader);
dbgFile << "XMP information size is" << buffer.data().size();
writeRawProfile(png_ptr, info_ptr, "xmp", buffer.data());
}
}
#if 0 // Unimplemented?
// Save resolution
int unit_type;
png_uint_32 x_resolution, y_resolution;
#endif
png_set_pHYs(png_ptr, info_ptr, CM_TO_POINT(xRes) * 100.0, CM_TO_POINT(yRes) * 100.0, PNG_RESOLUTION_METER); // It is the "invert" macro because we convert from pointer-per-inchs to points
// Save the information to the file
png_write_info(png_ptr, info_ptr);
png_write_flush(png_ptr);
// swap byteorder on little endian machines.
#ifndef WORDS_BIGENDIAN
if (color_nb_bits > 8)
png_set_swap(png_ptr);
#endif
// Write the PNG
// png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, 0);
// Fill the data structure
png_byte** row_pointers = new png_byte*[imageRect.height()];
int row = 0;
for (int y = imageRect.y(); y < imageRect.y() + imageRect.height(); y++, row++) {
KisHLineConstIteratorSP it = device->createHLineConstIteratorNG(imageRect.x(), y, imageRect.width());
row_pointers[row] = new png_byte[imageRect.width() * device->pixelSize()];
switch (color_type) {
case PNG_COLOR_TYPE_GRAY:
case PNG_COLOR_TYPE_GRAY_ALPHA:
if (color_nb_bits == 16) {
quint16 *dst = reinterpret_cast(row_pointers[row]);
do {
const quint16 *d = reinterpret_cast(it->oldRawData());
*(dst++) = d[0];
if (options.alpha) *(dst++) = d[1];
} while (it->nextPixel());
} else {
quint8 *dst = row_pointers[row];
do {
const quint8 *d = it->oldRawData();
*(dst++) = d[0];
if (options.alpha) *(dst++) = d[1];
} while (it->nextPixel());
}
break;
case PNG_COLOR_TYPE_RGB:
case PNG_COLOR_TYPE_RGB_ALPHA:
if (color_nb_bits == 16) {
quint16 *dst = reinterpret_cast(row_pointers[row]);
do {
const quint16 *d = reinterpret_cast(it->oldRawData());
*(dst++) = d[2];
*(dst++) = d[1];
*(dst++) = d[0];
if (options.alpha) *(dst++) = d[3];
} while (it->nextPixel());
} else {
quint8 *dst = row_pointers[row];
do {
const quint8 *d = it->oldRawData();
*(dst++) = d[2];
*(dst++) = d[1];
*(dst++) = d[0];
if (options.alpha) *(dst++) = d[3];
} while (it->nextPixel());
}
break;
case PNG_COLOR_TYPE_PALETTE: {
quint8 *dst = row_pointers[row];
KisPNGWriteStream writestream(dst, color_nb_bits);
do {
const quint8 *d = it->oldRawData();
int i;
for (i = 0; i < num_palette; i++) {
if (palette[i].red == d[2] &&
palette[i].green == d[1] &&
palette[i].blue == d[0]) {
break;
}
}
writestream.setNextValue(i);
} while (it->nextPixel());
}
break;
default:
delete[] row_pointers;
return KisImageBuilder_RESULT_UNSUPPORTED;
}
}
png_write_image(png_ptr, row_pointers);
// Writing is over
png_write_end(png_ptr, info_ptr);
// Free memory
png_destroy_write_struct(&png_ptr, &info_ptr);
for (int y = 0; y < imageRect.height(); y++) {
delete[] row_pointers[y];
}
delete[] row_pointers;
if (color_type == PNG_COLOR_TYPE_PALETTE) {
delete [] palette;
}
- iodevice->close();
return KisImageBuilder_RESULT_OK;
}
void KisPNGConverter::cancel()
{
m_stop = true;
}
void KisPNGConverter::progress(png_structp png_ptr, png_uint_32 row_number, int pass)
{
if (png_ptr == 0 || row_number > PNG_MAX_UINT || pass > 7) return;
// setProgress(row_number);
}
bool KisPNGConverter::isColorSpaceSupported(const KoColorSpace *cs)
{
return colorSpaceIdSupported(cs->id());
}
diff --git a/plugins/impex/brush/kis_brush_export.h b/plugins/impex/brush/kis_brush_export.h
index 2e3d262cbc..e901009b46 100644
--- a/plugins/impex/brush/kis_brush_export.h
+++ b/plugins/impex/brush/kis_brush_export.h
@@ -1,61 +1,61 @@
/*
* Copyright (c) 2016 Boudewijn Rempt
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_Brush_EXPORT_H_
#define _KIS_Brush_EXPORT_H_
#include
#include
#include
#include
#include
class KisWdgOptionsBrush : public KisConfigWidget, public Ui::WdgExportGih
{
Q_OBJECT
public:
KisWdgOptionsBrush(QWidget *parent)
: KisConfigWidget(parent)
{
setupUi(this);
}
void setConfiguration(const KisPropertiesConfigurationSP cfg);
KisPropertiesConfigurationSP configuration() const;
};
class KisBrushExport : public KisImportExportFilter
{
Q_OBJECT
public:
KisBrushExport(QObject *parent, const QVariantList &);
virtual ~KisBrushExport();
-public:
+ virtual bool supportsIO() const { return false; }
virtual KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0);
KisPropertiesConfigurationSP defaultConfiguration(const QByteArray& from = "", const QByteArray& to = "") const;
KisPropertiesConfigurationSP lastSavedConfiguration(const QByteArray &from = "", const QByteArray &to = "") const;
KisConfigWidget *createConfigurationWidget(QWidget *parent, const QByteArray& from = "", const QByteArray& to = "") const;
void initializeCapabilities();
};
#endif
diff --git a/plugins/impex/exr/exr_export.h b/plugins/impex/exr/exr_export.h
index a1950b9a9a..23cd869859 100644
--- a/plugins/impex/exr/exr_export.h
+++ b/plugins/impex/exr/exr_export.h
@@ -1,59 +1,60 @@
/*
* Copyright (c) 2010 Cyrille Berger
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef _EXR_EXPORT_H_
#define _EXR_EXPORT_H_
#include
#include
#include
#include "ui_exr_export_widget.h"
class KisWdgOptionsExr : public KisConfigWidget, public Ui::ExrExportWidget
{
Q_OBJECT
public:
KisWdgOptionsExr(QWidget *parent)
: KisConfigWidget(parent)
{
setupUi(this);
}
void setConfiguration(const KisPropertiesConfigurationSP cfg);
KisPropertiesConfigurationSP configuration() const;
};
class EXRExport : public KisImportExportFilter
{
Q_OBJECT
public:
EXRExport(QObject *parent, const QVariantList &);
virtual ~EXRExport();
+ virtual bool supportsIO() const { return false; }
virtual KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0);
KisPropertiesConfigurationSP defaultConfiguration(const QByteArray& from = "", const QByteArray& to = "") const;
KisPropertiesConfigurationSP lastSavedConfiguration(const QByteArray &from = "", const QByteArray &to = "") const;
KisConfigWidget *createConfigurationWidget(QWidget *parent, const QByteArray& from = "", const QByteArray& to = "") const;
void initializeCapabilities();
};
#endif
diff --git a/plugins/impex/exr/exr_import.h b/plugins/impex/exr/exr_import.h
index eb6ed2cb5f..52adab72b7 100644
--- a/plugins/impex/exr/exr_import.h
+++ b/plugins/impex/exr/exr_import.h
@@ -1,37 +1,37 @@
/*
* Copyright (c) 2010 Cyrille Berger
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef EXR_IMPORT_H_
#define EXR_IMPORT_H_
#include
#include
class exrImport : public KisImportExportFilter
{
Q_OBJECT
public:
exrImport(QObject *parent, const QVariantList &);
virtual ~exrImport();
-public:
+ virtual bool supportsIO() const { return false; }
virtual KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0);
};
#endif
diff --git a/plugins/impex/spriter/kis_spriter_export.h b/plugins/impex/spriter/kis_spriter_export.h
index d620f17493..04f9632a7d 100644
--- a/plugins/impex/spriter/kis_spriter_export.h
+++ b/plugins/impex/spriter/kis_spriter_export.h
@@ -1,135 +1,135 @@
/*
* Copyright (c) 2016 Boudewijn Rempt
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_SPRITER_EXPORT_H_
#define _KIS_SPRITER_EXPORT_H_
#include
#include
#include
#include
#include
struct SpriterFile {
qreal id;
QString name;
QString pathName;
QString baseName;
QString layerName;
qreal width;
qreal height;
qreal x;
qreal y;
};
struct Folder {
qreal id;
QString name;
QString pathName;
QString baseName;
QString groupName;
QList files;
};
struct Bone {
qreal id;
const Bone *parentBone;
QString name;
qreal x;
qreal y;
qreal width;
qreal height;
qreal localX;
qreal localY;
qreal localAngle;
qreal localScaleX;
qreal localScaleY;
qreal fixLocalX;
qreal fixLocalY;
qreal fixLocalAngle;
qreal fixLocalScaleX;
qreal fixLocalScaleY;
QList bones;
~Bone() {
qDeleteAll(bones);
bones.clear();;
}
};
struct SpriterSlot {
QString name;
bool defaultAttachmentFlag;
};
struct SpriterObject {
qreal id;
qreal folderId;
qreal fileId;
Bone *bone;
SpriterSlot *slot;
qreal x;
qreal y;
qreal localX;
qreal localY;
qreal localAngle;
qreal localScaleX;
qreal localScaleY;
qreal fixLocalX;
qreal fixLocalY;
qreal fixLocalAngle;
qreal fixLocalScaleX;
qreal fixLocalScaleY;
~SpriterObject() {
delete slot;
}
};
class KisSpriterExport : public KisImportExportFilter
{
Q_OBJECT
public:
KisSpriterExport(QObject *parent, const QVariantList &);
virtual ~KisSpriterExport();
-public:
+ virtual bool supportsIO() const { return false; }
virtual KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0);
void initializeCapabilities();
private:
bool savePaintDevice(KisPaintDeviceSP dev, const QString &fileName);
void parseFolder(KisGroupLayerSP parentGroup, const QString &folderName, const QString &basePath, int *folderId = 0);
Bone *parseBone(const Bone *parent, KisGroupLayerSP groupLayer);
void fixBone(Bone *bone);
void fillScml(QDomDocument &scml, const QString &entityName);
void writeBoneRef(const Bone *bone, QDomElement &mainline, QDomDocument &scml);
void writeBone(const Bone *bone, QDomElement &timeline, QDomDocument &scml);
KisImageSP m_image;
qreal m_timelineid;
QList m_folders;
Bone *m_rootBone;
QList m_objects;
KisGroupLayerSP m_rootLayer; // Not the image's root later, but the one that is named "root"
KisLayerSP m_boneLayer;
};
#endif
diff --git a/plugins/impex/tiff/kis_tiff_export.h b/plugins/impex/tiff/kis_tiff_export.h
index 4bd4517bbd..6def27cfac 100644
--- a/plugins/impex/tiff/kis_tiff_export.h
+++ b/plugins/impex/tiff/kis_tiff_export.h
@@ -1,42 +1,42 @@
/*
* Copyright (c) 2005 Cyrille Berger
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_TIFF_EXPORT_H_
#define _KIS_TIFF_EXPORT_H_
#include
#include
#include
class KisTIFFExport : public KisImportExportFilter
{
Q_OBJECT
public:
KisTIFFExport(QObject *parent, const QVariantList &);
virtual ~KisTIFFExport();
-public:
+ virtual bool supportsIO() const { return false; }
virtual KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0);
KisPropertiesConfigurationSP defaultConfiguration(const QByteArray& from = "", const QByteArray& to = "") const;
KisPropertiesConfigurationSP lastSavedConfiguration(const QByteArray &from = "", const QByteArray &to = "") const;
KisConfigWidget *createConfigurationWidget(QWidget *parent, const QByteArray& from = "", const QByteArray& to = "") const;
void initializeCapabilities();
};
#endif
diff --git a/plugins/impex/tiff/kis_tiff_import.h b/plugins/impex/tiff/kis_tiff_import.h
index 99e04c4a38..78b85bd6ef 100644
--- a/plugins/impex/tiff/kis_tiff_import.h
+++ b/plugins/impex/tiff/kis_tiff_import.h
@@ -1,36 +1,37 @@
/*
* Copyright (c) 2005 Cyrille Berger
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_TIFF_IMPORT_H_
#define _KIS_TIFF_IMPORT_H_
#include
#include
class KisTIFFImport : public KisImportExportFilter
{
Q_OBJECT
public:
KisTIFFImport(QObject *parent, const QVariantList &);
virtual ~KisTIFFImport();
+ virtual bool supportsIO() const { return false; }
public:
virtual KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0);
};
#endif
diff --git a/plugins/impex/video/kis_video_export.h b/plugins/impex/video/kis_video_export.h
index 4c1edf0c4a..6daadd5d97 100644
--- a/plugins/impex/video/kis_video_export.h
+++ b/plugins/impex/video/kis_video_export.h
@@ -1,42 +1,43 @@
/*
* Copyright (c) 2016 Dmitry Kazakov
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_VIDEO_EXPORT_H_
#define _KIS_VIDEO_EXPORT_H_
#include
#include
class KisVideoExport : public KisImportExportFilter
{
Q_OBJECT
public:
KisVideoExport(QObject *parent, const QVariantList &);
virtual ~KisVideoExport();
public:
+ virtual bool supportsIO() const { return false; }
virtual KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0);
KisPropertiesConfigurationSP defaultConfiguration(const QByteArray& from, const QByteArray& to) const;
KisPropertiesConfigurationSP lastSavedConfiguration(const QByteArray &from, const QByteArray &to) const;
KisConfigWidget *createConfigurationWidget(QWidget *parent, const QByteArray& from = "", const QByteArray& to = "") const;
};
#endif