Krita Source Code Documentation
Loading...
Searching...
No Matches
kra_converter.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2016 Boudewijn Rempt <boud@valdyas.org>
3 *
4 * SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6
7#include "kra_converter.h"
8
9#include <QApplication>
10#include <QUrl>
11#include <QVersionNumber>
12
13#include <KoStore.h>
14#include <KoStoreDevice.h>
16#include <KoDocumentInfo.h>
17#include <KoXmlWriter.h>
18
19#include <KisDocument.h>
20#include <KritaVersionWrapper.h>
21#include <kis_clone_layer.h>
22#include <kis_group_layer.h>
23#include <kis_image.h>
24#include <kis_paint_layer.h>
25
26static const char CURRENT_DTD_VERSION[] = "2.0";
27
29 : m_doc(doc)
30 , m_image(doc->savingImage())
31{
32}
33
35 : m_doc(doc)
36 , m_image(doc->savingImage())
37 , m_updater(updater)
38{
39}
40
42{
43 delete m_store;
44 delete m_kraSaver;
45 delete m_kraLoader;
46}
47
49{
50 KisNodeSP first = root->firstChild();
51 KisNodeSP node = first;
52 while (!node.isNull()) {
53 if (node->inherits("KisCloneLayer")) {
54 KisCloneLayer* layer = dynamic_cast<KisCloneLayer*>(node.data());
55 if (layer && layer->copyFrom().isNull()) {
56 KisLayerSP reincarnation = layer->reincarnateAsPaintLayer();
57 image->addNode(reincarnation, node->parent(), node->prevSibling());
58 image->removeNode(node);
59 node = reincarnation;
60 }
61 } else if (node->childCount() > 0) {
62 fixCloneLayers(image, node);
63 }
64 node = node->nextSibling();
65 }
66}
67
69{
71
72 if (m_store->bad()) {
73 m_doc->setErrorMessage(i18n("Not a valid Krita file"));
75 }
76
77 bool success = false;
78 {
79 if (m_store->hasFile("root") || m_store->hasFile("maindoc.xml")) { // Fallback to "old" file format (maindoc.xml)
80 QDomDocument doc;
81
83 if (res.isOk())
84 res = loadXML(doc, m_store);
85 if (!res.isOk()) {
86 return res;
87 }
88
89 } else {
90 errUI << "ERROR: No maindoc.xml" << Qt::endl;
91 m_doc->setErrorMessage(i18n("Invalid document: no file 'maindoc.xml'."));
93 }
94
95 if (m_store->hasFile("documentinfo.xml")) {
96 QDomDocument doc;
97 KisImportExportErrorCode resultHere = oldLoadAndParse(m_store, "documentinfo.xml", doc);
98 if (resultHere.isOk()) {
99 m_doc->documentInfo()->load(doc);
100 }
101 }
102 success = completeLoading(m_store);
103 }
104
106
108}
109
111{
112 return m_image;
113}
114
119
124
129
134
135KisImportExportErrorCode KraConverter::buildFile(QIODevice *io, const QString &filename, bool addMergedImage)
136{
137 if (m_image->size().isEmpty()) {
139 }
140
141 setProgress(5);
143
144 bool success = true;
145
146 if (m_store->bad()) {
147 m_doc->setErrorMessage(i18n("Could not create the file for saving"));
149 }
150
151 setProgress(20);
152
153 m_kraSaver = new KisKraSaver(m_doc, filename, addMergedImage);
154
156
157 if (!resultCode.isOk()) {
158 return resultCode;
159 }
160
161 setProgress(40);
162 bool result;
163
164 result = m_kraSaver->saveKeyframes(m_store, m_doc->path(), true);
165 if (!result) {
166 success = false;
167 qWarning() << "saving key frames failed";
168 }
169 setProgress(60);
170 result = m_kraSaver->saveBinaryData(m_store, m_image, m_doc->path(), true, addMergedImage);
171 if (!result) {
172 success = false;
173 qWarning() << "saving binary data failed";
174 }
175 setProgress(70);
177 if (!result) {
178 success = false;
179 qWarning() << "saving resources data failed";
180 }
181
183 if (!result) {
184 success = false;
185 qWarning() << "Saving storyboard data failed";
186 }
187
189 if (!result) {
190 success = false;
191 qWarning() << "Saving animation metadata failed";
192 }
193
194 result = m_kraSaver->saveAudio(m_store);
195 if (!result) {
196 success = false;
197 qWarning() << "Saving audio data failed";
198 }
199
200 setProgress(80);
201
202 if (!m_store->finalize()) {
203 success = false;
204 }
205 if (!success || !m_kraSaver->errorMessages().isEmpty()) {
208 }
209
211
212 setProgress(90);
214}
215
217{
218 dbgFile << "Saving root";
219 if (store->open("root")) {
220 KoStoreDevice dev(store);
221 if (!saveToStream(&dev) || !store->close()) {
222 dbgUI << "saveToStream failed";
224 }
225 } else {
226 m_doc->setErrorMessage(i18n("Not able to write '%1'. Partition full?", QString("maindoc.xml")));
228 }
229
230 if (store->open("documentinfo.xml")) {
231 QDomDocument doc = KisDocument::createDomDocument("document-info"
232 /*DTD name*/, "document-info" /*tag name*/, "1.1");
233 doc = m_doc->documentInfo()->save(doc);
234 KoStoreDevice dev(store);
235 QByteArray s = doc.toByteArray(); // this is already Utf8!
236 bool success = dev.write(s.data(), s.size());
237 if (!success) {
239 }
240 store->close();
241 } else {
243 }
244
245 if (store->open("preview.png")) {
246 // ### TODO: missing error checking (The partition could be full!)
248 (void)store->close();
249 if (!result.isOk()) {
250 return result;
251 }
252 } else {
254 }
255
256 dbgUI << "Saving done of url:" << m_doc->path();
258}
259
260bool KraConverter::saveToStream(QIODevice *dev)
261{
262 QDomDocument doc = createDomDocument();
263 // Save to buffer
264 QByteArray s = doc.toByteArray(); // utf8 already
265 dev->open(QIODevice::WriteOnly);
266 int nwritten = dev->write(s.data(), s.size());
267 if (nwritten != (int)s.size()) {
268 warnUI << "wrote " << nwritten << "- expected" << s.size();
269 }
270 return nwritten == (int)s.size();
271}
272
274{
275 QDomDocument doc = m_doc->createDomDocument("DOC", CURRENT_DTD_VERSION);
276 QDomElement root = doc.documentElement();
277
278 root.setAttribute("editor", "Krita");
279 root.setAttribute("syntaxVersion", CURRENT_DTD_VERSION);
280 root.setAttribute("kritaVersion", KritaVersionWrapper::versionString(false));
281
282 root.appendChild(m_kraSaver->saveXML(doc, m_image));
283
284 if (!m_kraSaver->errorMessages().isEmpty()) {
286 }
287 return doc;
288}
289
291{
292 QPixmap pix = m_doc->generatePreview(QSize(256, 256));
293 QImage preview(pix.toImage().convertToFormat(QImage::Format_ARGB32, Qt::ColorOnly));
294 if (preview.size().isEmpty()) {
295 QSize newSize = m_doc->savingImage()->bounds().size();
296 // make sure dimensions are at least one pixel, because extreme aspect ratios may cause rounding to zero
297 newSize = newSize.scaled(QSize(256, 256), Qt::KeepAspectRatio).expandedTo({1, 1});
298 preview = QImage(newSize, QImage::Format_ARGB32);
299 preview.fill(QColor(0, 0, 0, 0));
300 }
301
302 KoStoreDevice io(store);
303 if (!io.open(QIODevice::WriteOnly)) {
305 }
306 bool ret = preview.save(&io, "PNG");
307 io.close();
309}
310
311
312KisImportExportErrorCode KraConverter::oldLoadAndParse(KoStore *store, const QString &filename, QDomDocument &xmldoc)
313{
314 //dbgUI <<"Trying to open" << filename;
315
316 if (!store->open(filename)) {
317 warnUI << "Entry " << filename << " not found!";
318 m_doc->setErrorMessage(i18n("Could not find %1", filename));
320 }
321 // Error variables for QDomDocument::setContent
322 QString errorMsg;
323 int errorLine, errorColumn;
324 bool ok = xmldoc.setContent(store->device(), &errorMsg, &errorLine, &errorColumn);
325 store->close();
326 if (!ok) {
327 errUI << "Parsing error in " << filename << "! Aborting!" << Qt::endl
328 << " In line: " << errorLine << ", column: " << errorColumn << Qt::endl
329 << " Error message: " << errorMsg << Qt::endl;
330 m_doc->setErrorMessage(i18n("Parsing error in %1 at line %2, column %3\nError message: %4",
331 filename, errorLine, errorColumn,
332 QCoreApplication::translate("QXml", errorMsg.toUtf8(), 0)));
334 }
335 dbgUI << "File" << filename << " loaded and parsed";
337}
338
340{
341 Q_UNUSED(store);
342
343 QDomElement root;
344 QDomNode node;
345
346 if (doc.doctype().name() != "DOC") {
347 errUI << "The format is not supported or the file is corrupted";
348 m_doc->setErrorMessage(i18n("The format is not supported or the file is corrupted"));
350 }
351 root = doc.documentElement();
352
353 QString versionTag = root.attribute("syntaxVersion", "3.0");
354 QVersionNumber parsedVersionNumber = QVersionNumber::fromString(versionTag);
355 const int syntaxVersion = parsedVersionNumber.isNull() ? 3 : parsedVersionNumber.majorVersion();
356
357 if (syntaxVersion > 2) {
358 errUI << "The file is too new for this version of Krita:" << syntaxVersion;
359 m_doc->setErrorMessage(i18n("The file is too new for this version of Krita (%1).", syntaxVersion));
361 }
362
363 if (!root.hasChildNodes()) {
364 errUI << "The file has no layers.";
365 m_doc->setErrorMessage(i18n("The file has no layers."));
367 }
368
369 QString kritaVersionTag = root.attribute("kritaVersion", "6.0");
370 QVersionNumber kritaVersionNumber = QVersionNumber::fromString(kritaVersionTag);
371 if (kritaVersionNumber.isNull()) {
372 kritaVersionNumber = QVersionNumber::fromString(KritaVersionWrapper::versionString(false));
373 }
374
375 m_kraLoader = new KisKraLoader(m_doc, syntaxVersion, kritaVersionNumber);
376
377 // reset the old image before loading the next one
378 m_doc->setCurrentImage(0, false);
379
380 for (node = root.firstChild(); !node.isNull(); node = node.nextSibling()) {
381 if (node.isElement()) {
382 if (node.nodeName() == "IMAGE") {
383 QDomElement elem = node.toElement();
384 if (!(m_image = m_kraLoader->loadXML(elem))) {
385
386 if (m_kraLoader->errorMessages().isEmpty()) {
387 errUI << "Unknown error while opening the .kra file.";
388 m_doc->setErrorMessage(i18n("Unknown error."));
389 }
390 else {
392 errUI << m_kraLoader->errorMessages().join("\n");
393 }
395 }
396
397 // HACK ALERT!
399
401 }
402 else {
403 if (m_kraLoader->errorMessages().isEmpty()) {
404 m_doc->setErrorMessage(i18n("The file does not contain an image."));
405 }
407 }
408 }
409 }
411}
412
414{
415 if (!m_image) {
416 if (m_kraLoader->errorMessages().isEmpty()) {
417 m_doc->setErrorMessage(i18n("Unknown error."));
418 }
419 else {
421 }
422 return false;
423 }
424
426
427 QString layerPathName = m_kraLoader->imageName();
428 if (!m_store->hasDirectory(layerPathName)) {
429 // We might be hitting an encoding problem. Get the only folder in the toplevel
430 Q_FOREACH (const QString &entry, m_store->directoryList()) {
431 if (entry.contains("/layers/")) {
432 layerPathName = entry.split("/layers/").first();
433 m_store->setSubstitution(m_kraLoader->imageName(), layerPathName);
434 break;
435 }
436 }
437 }
438
443 m_kraLoader->loadAudio(store, m_doc);
444
445 if (!m_kraLoader->errorMessages().isEmpty()) {
447 return false;
448 }
449
451
452 if (!m_kraLoader->warningMessages().isEmpty()) {
453 // warnings do not interrupt loading process, so we do not return here
455 }
456
461
462 return true;
463}
464
466{
467 m_stop = true;
468}
469
471{
472 if (m_updater) {
473 m_updater->setProgress(progress);
474 }
475}
476
KoDocumentInfo * documentInfo() const
void hackPreliminarySetImage(KisImageSP image)
QPixmap generatePreview(const QSize &size)
Generates a preview picture of the document.
QString localFilePath() const
void setErrorMessage(const QString &errMsg)
QDomDocument createDomDocument(const QString &tagName, const QString &version) const
void setWarningMessage(const QString &warningMsg)
static QByteArray nativeFormatMimeType()
QString path() const
KisImageSP savingImage
void setCurrentImage(KisImageSP image, bool forceInitialUpdate=true, KisNodeSP preActivatedNode=nullptr)
void disableDirtyRequests() override
void enableDirtyRequests() override
QSize size() const
Definition kis_image.h:547
QRect bounds() const override
KisImageSP loadXML(const QDomElement &imageElement)
void loadAnimationMetadata(KoStore *store, KisImageSP image)
QStringList errorMessages() const
if empty, loading didn't fail...
void loadStoryboards(KoStore *store, KisDocument *doc)
QString imageName() const
QStringList warningMessages() const
if not empty, loading didn't fail, but there are problems
void loadBinaryData(KoStore *store, KisImageSP image, const QString &uri, bool external)
vKisNodeSP selectedNodes() const
void loadResources(KoStore *store, KisDocument *doc)
StoryboardItemList storyboardItemList() const
QList< KisPaintingAssistantSP > assistants() const
StoryboardCommentList storyboardCommentList() const
void loadAudio(KoStore *store, KisDocument *kisDoc)
bool saveKeyframes(KoStore *store, const QString &uri, bool external)
QStringList errorMessages() const
QDomElement saveXML(QDomDocument &doc, KisImageSP image)
bool saveAnimationMetadata(KoStore *store, KisImageSP image, const QString &uri)
bool saveResources(KoStore *store, KisImageSP image, const QString &uri)
bool saveBinaryData(KoStore *store, KisImageSP image, const QString &uri, bool external, bool addMergedImage)
bool saveAudio(KoStore *store)
bool saveStoryboard(KoStore *store, KisImageSP image, const QString &uri)
QStringList warningMessages() const
bool isNull() const
bool load(const QDomDocument &doc)
QDomDocument save(QDomDocument &doc)
void close() override
bool open(OpenMode m) override
QIODevice * device() const
Definition KoStore.cpp:171
bool close()
Definition KoStore.cpp:156
@ Read
Definition KoStore.h:29
@ Write
Definition KoStore.h:29
void setSubstitution(const QString &name, const QString &substitution)
When reading, in the paths in the store where name occurs, substitution is used.
Definition KoStore.cpp:407
@ Zip
Definition KoStore.h:30
virtual QStringList directoryList() const
Definition KoStore.cpp:426
bool bad() const
Definition KoStore.cpp:414
static KoStore * createStore(const QString &fileName, Mode mode, const QByteArray &appIdentification=QByteArray(), Backend backend=Auto, bool writeMimetype=true)
Definition KoStore.cpp:39
bool finalize()
Definition KoStore.cpp:395
bool hasFile(const QString &fileName) const
Definition KoStore.cpp:384
bool open(const QString &name)
Definition KoStore.cpp:109
bool hasDirectory(const QString &directoryName)
Definition KoStore.cpp:390
StoryboardCommentList storyboardCommentList()
KisImportExportErrorCode buildImage(QIODevice *io)
bool completeLoading(KoStore *store)
KisImportExportErrorCode buildFile(QIODevice *io, const QString &filename, bool addMergedImage=true)
vKisNodeSP activeNodes()
QList< KisPaintingAssistantSP > assistants()
KraConverter(KisDocument *doc)
KisImportExportErrorCode saveRootDocuments(KoStore *store)
KisImageSP m_image
StoryboardCommentList m_storyboardCommentList
QList< KisPaintingAssistantSP > m_assistants
vKisNodeSP m_activeNodes
StoryboardItemList m_storyboardItemList
void setProgress(int progress)
~KraConverter() override
QPointer< KoUpdater > m_updater
KoStore * m_store
virtual void cancel()
QDomDocument createDomDocument()
KisDocument * m_doc
StoryboardItemList storyboardItemList()
KisKraLoader * m_kraLoader
KisImageSP image()
KisImportExportErrorCode oldLoadAndParse(KoStore *store, const QString &filename, QDomDocument &xmldoc)
bool saveToStream(QIODevice *dev)
KisImportExportErrorCode loadXML(const QDomDocument &doc, KoStore *store)
KisImportExportErrorCode savePreview(KoStore *store)
KisKraSaver * m_kraSaver
#define errUI
Definition kis_debug.h:114
#define dbgUI
Definition kis_debug.h:52
#define dbgFile
Definition kis_debug.h:53
#define warnUI
Definition kis_debug.h:94
typedef void(QOPENGLF_APIENTRYP PFNGLINVALIDATEBUFFERDATAPROC)(GLuint buffer)
void fixCloneLayers(KisImageSP image, KisNodeSP root)
static const char CURRENT_DTD_VERSION[]
KRITAVERSION_EXPORT QString versionString(bool checkGit=false)
KisLayerSP copyFrom
KisLayerSP reincarnateAsPaintLayer() const
bool addNode(KisNodeSP node, KisNodeSP parent=KisNodeSP(), KisNodeAdditionFlags flags=KisNodeAdditionFlag::None)
bool removeNode(KisNodeSP node)
KisNodeSP prevSibling() const
Definition kis_node.cpp:402
KisNodeSP firstChild() const
Definition kis_node.cpp:361
quint32 childCount() const
Definition kis_node.cpp:414
KisNodeWSP parent
Definition kis_node.cpp:86
KisNodeSP nextSibling() const
Definition kis_node.cpp:408