File indexing completed on 2024-05-26 04:33:33

0001 /* This file is part of the KDE project
0002  * Copyright 2008 (C) Boudewijn Rempt <boud@valdyas.org>
0003  *
0004  * SPDX-License-Identifier: LGPL-2.0-or-later
0005  */
0006 #include "kis_kra_saver.h"
0007 
0008 #include "kis_kra_tags.h"
0009 #include "kis_kra_save_visitor.h"
0010 #include "kis_kra_savexml_visitor.h"
0011 
0012 #include <QApplication>
0013 #include <QMessageBox>
0014 #include <QDomDocument>
0015 #include <QDomElement>
0016 #include <QString>
0017 #include <QStringList>
0018 #include <QScopedPointer>
0019 
0020 #include <QUrl>
0021 #include <QBuffer>
0022 
0023 #include <KoDocumentInfo.h>
0024 #include <KoColorSpaceRegistry.h>
0025 #include <KoColorSpace.h>
0026 #include <KoColorProfile.h>
0027 #include <KoColor.h>
0028 #include <KoColorSet.h>
0029 #include <KoStore.h>
0030 #include <KoStoreDevice.h>
0031 #include <KisResourceTypes.h>
0032 #include <KisResourceModel.h>
0033 #include <kis_annotation.h>
0034 #include <kis_image.h>
0035 #include <kis_image_animation_interface.h>
0036 #include <KisImportExportManager.h>
0037 #include <kis_group_layer.h>
0038 #include <kis_layer.h>
0039 #include <kis_adjustment_layer.h>
0040 #include <kis_layer_composition.h>
0041 #include <kis_painting_assistants_decoration.h>
0042 #include "kis_png_converter.h"
0043 #include "kis_keyframe_channel.h"
0044 #include <kis_time_span.h>
0045 #include "KisDocument.h"
0046 #include <string>
0047 #include "kis_dom_utils.h"
0048 #include "kis_grid_config.h"
0049 #include "kis_guides_config.h"
0050 #include "KisProofingConfiguration.h"
0051 #include "kis_asl_layer_style_serializer.h"
0052 
0053 #include <KisMirrorAxisConfig.h>
0054 
0055 #include <QFileInfo>
0056 #include <QDir>
0057 
0058 
0059 using namespace KRA;
0060 
0061 struct KisKraSaver::Private
0062 {
0063     KisDocument* doc {nullptr};
0064     QMap<const KisNode*, QString> nodeFileNames;
0065     QMap<const KisNode*, QString> keyframeFilenames;
0066     QString imageName;
0067     QString filename;
0068     QStringList errorMessages;
0069     QStringList warningMessages;
0070     QStringList specialAnnotations;
0071     bool addMergedImage {false};
0072     QList<KoResourceLoadResult> linkedDocumentResources;
0073 
0074     Private() {
0075         specialAnnotations << "exif" << "icc";
0076     }
0077 
0078 };
0079 
0080 KisKraSaver::KisKraSaver(KisDocument* document, const QString &filename, bool addMergedImage)
0081     : m_d(new Private)
0082 {
0083     m_d->doc = document;
0084     m_d->filename = filename;
0085     m_d->addMergedImage = addMergedImage;
0086     m_d->linkedDocumentResources = document->linkedDocumentResources();
0087 
0088     m_d->imageName = m_d->doc->documentInfo()->aboutInfo("title");
0089     if (m_d->imageName.isEmpty()) {
0090         m_d->imageName = i18n("Unnamed");
0091     }
0092 }
0093 
0094 KisKraSaver::~KisKraSaver()
0095 {
0096     delete m_d;
0097 }
0098 
0099 QDomElement KisKraSaver::saveXML(QDomDocument& doc,  KisImageSP image)
0100 {
0101     QDomElement imageElement = doc.createElement("IMAGE");
0102 
0103     Q_ASSERT(image);
0104     imageElement.setAttribute(NAME, m_d->imageName);
0105     imageElement.setAttribute(MIME, NATIVE_MIMETYPE);
0106     imageElement.setAttribute(WIDTH, KisDomUtils::toString(image->width()));
0107     imageElement.setAttribute(HEIGHT, KisDomUtils::toString(image->height()));
0108     imageElement.setAttribute(COLORSPACE_NAME, image->colorSpace()->id());
0109     imageElement.setAttribute(DESCRIPTION, m_d->doc->documentInfo()->aboutInfo("comment"));
0110     // XXX: Save profile as blob inside the image, instead of the product name.
0111     if (image->profile() && image->profile()-> valid()) {
0112         imageElement.setAttribute(PROFILE, image->profile()->name());
0113     }
0114     imageElement.setAttribute(X_RESOLUTION, KisDomUtils::toString(image->xRes()*72.0));
0115     imageElement.setAttribute(Y_RESOLUTION, KisDomUtils::toString(image->yRes()*72.0));
0116     //now the proofing options:
0117     if (image->proofingConfiguration()) {
0118         if (image->proofingConfiguration()->storeSoftproofingInsideImage) {
0119             imageElement.setAttribute(PROOFINGPROFILENAME, KisDomUtils::toString(image->proofingConfiguration()->proofingProfile));
0120             imageElement.setAttribute(PROOFINGMODEL, KisDomUtils::toString(image->proofingConfiguration()->proofingModel));
0121             imageElement.setAttribute(PROOFINGDEPTH, KisDomUtils::toString(image->proofingConfiguration()->proofingDepth));
0122             imageElement.setAttribute(PROOFINGINTENT, KisDomUtils::toString(image->proofingConfiguration()->intent));
0123             imageElement.setAttribute(PROOFINGADAPTATIONSTATE, KisDomUtils::toString(image->proofingConfiguration()->adaptationState));
0124         }
0125     }
0126 
0127     quint32 count = 1; // We don't save the root layer, but it does count
0128     KisSaveXmlVisitor visitor(doc, imageElement, count, m_d->filename, true);
0129     visitor.setSelectedNodes({m_d->doc->preActivatedNode()});
0130 
0131     image->rootLayer()->accept(visitor);
0132     m_d->errorMessages.append(visitor.errorMessages());
0133 
0134     m_d->nodeFileNames = visitor.nodeFileNames();
0135     m_d->keyframeFilenames = visitor.keyframeFileNames();
0136 
0137     saveBackgroundColor(doc, imageElement, image);
0138     saveAssistantsGlobalColor(doc, imageElement);
0139     saveWarningColor(doc, imageElement, image);
0140     saveCompositions(doc, imageElement, image);
0141     saveAssistantsList(doc, imageElement);
0142     saveGrid(doc, imageElement);
0143     saveGuides(doc, imageElement);
0144     saveMirrorAxis(doc, imageElement);
0145     saveResourcesToXML(doc, imageElement);
0146 
0147     // Redundancy -- Save animation metadata in XML to prevent data loss for the time being...
0148     QDomElement animationElement = doc.createElement("animation");
0149     KisDomUtils::saveValue(&animationElement, "framerate", image->animationInterface()->framerate());
0150     KisDomUtils::saveValue(&animationElement, "range", image->animationInterface()->documentPlaybackRange());
0151     KisDomUtils::saveValue(&animationElement, "currentTime", image->animationInterface()->currentUITime());
0152     imageElement.appendChild(animationElement);
0153 
0154     vKisAnnotationSP_it beginIt = image->beginAnnotations();
0155     vKisAnnotationSP_it endIt = image->endAnnotations();
0156 
0157     if (beginIt != endIt) {
0158         QDomElement annotationsElement = doc.createElement(ANNOTATIONS);
0159         vKisAnnotationSP_it it = beginIt;
0160         while (it != endIt) {
0161             if (!(*it) || (*it)->type().isEmpty()) {
0162                 it++;
0163                 continue;
0164             }
0165             QString type = (*it)->type();
0166 
0167             if (!m_d->specialAnnotations.contains(type)) {
0168 
0169                 QString description = (*it)->description();
0170                 QDomElement annotationElement = doc.createElement(ANNOTATION);
0171                 annotationsElement.appendChild(annotationElement);
0172                 annotationElement.setAttribute("type", type);
0173                 annotationElement.setAttribute("description", description);
0174             }
0175             it++;
0176         }
0177         imageElement.appendChild(annotationsElement);
0178     }
0179 
0180 
0181     return imageElement;
0182 }
0183 
0184 bool KisKraSaver::saveResources(KoStore *store, KisImageSP image, const QString &uri)
0185 {
0186     Q_UNUSED(image);
0187     Q_UNUSED(uri);
0188 
0189     QList<KoResourceLoadResult> embeddedResources = m_d->linkedDocumentResources;
0190 
0191     Q_FOREACH (const KoResourceLoadResult &result, embeddedResources) {
0192         KIS_SAFE_ASSERT_RECOVER(result.type() != KoResourceLoadResult::ExistingResource) { continue; }
0193 
0194         if (result.type() == KoResourceLoadResult::FailedLink) {
0195             m_d->warningMessages << i18nc("Error message when saving a .kra file", "Could not export resource for embedding: %1", result.signature().filename);
0196             continue;
0197         }
0198 
0199         KoEmbeddedResource resource = result.embeddedResource();
0200 
0201         QString path = RESOURCE_PATH + "/" + resource.signature().type;
0202 
0203         if (resource.signature().type == ResourceType::Palettes) {
0204             path = m_d->imageName + PALETTE_PATH;
0205         }
0206 
0207         const QString fileName = resource.signature().filename;
0208 
0209         if (!store->open(path  + '/' + fileName)) {
0210             m_d->warningMessages << i18nc("Error message when saving a .kra file", "Could not write resource: %1", result.signature().filename);
0211             continue;
0212         }
0213 
0214         // we first read into a buffer to make sure the save operation is transactional,
0215         // that is, either resource is saves correctly, or the file is left empty.
0216         QByteArray ba = resource.data();
0217 
0218         qint64 nwritten = 0;
0219         if (!ba.isEmpty()) {
0220             nwritten = store->write(ba);
0221         } else {
0222             m_d->warningMessages << i18nc("Error message when saving a .kra file", "Written resource is empty: %1", result.signature().filename);
0223         }
0224 
0225         store->close();
0226 
0227         if (nwritten != ba.size()) {
0228             m_d->warningMessages << i18nc("Error message when saving a .kra file", "Written resource is incomplete: %1", result.signature().filename);
0229         }
0230     }
0231 
0232     return true;
0233 }
0234 
0235 bool KisKraSaver::saveStoryboard(KoStore *store, KisImageSP image, const QString &uri)
0236 {
0237     Q_UNUSED(image);
0238     Q_UNUSED(uri);
0239 
0240     bool success = true;
0241     if (m_d->doc->getStoryboardItemList().count() == 0) {
0242         return true;
0243     } else {
0244         if (!store->open(m_d->imageName + STORYBOARD_PATH + "index.xml")) {
0245             m_d->errorMessages << i18nc("Error message when saving a .kra file", "Could not save storyboards.");
0246             return false;
0247         }
0248 
0249         QDomDocument storyboardDocument = m_d->doc->createDomDocument("storyboard-info", "1.1");
0250         QDomElement root = storyboardDocument.documentElement();
0251         saveStoryboardToXML(storyboardDocument, root);
0252 
0253         QByteArray ba = storyboardDocument.toByteArray();
0254         qint64 nwritten = 0;
0255         if (!ba.isEmpty()) {
0256             nwritten = store->write(ba);
0257         } else {
0258             success = false;
0259             qWarning() << "Could not save storyboard data to a byte array!";
0260         }
0261 
0262         bool r = store->close();
0263         success = success && r && (nwritten == ba.size());
0264     }
0265 
0266     if (!success) {
0267         m_d->errorMessages << i18nc("Error message when saving a .kra file", "Could not save storyboards.");
0268         return false;
0269     }
0270 
0271     return success;
0272 }
0273 
0274 bool KisKraSaver::saveAnimationMetadata(KoStore *store, KisImageSP image, const QString &uri)
0275 {
0276     Q_UNUSED(uri);
0277 
0278     if (!store->open(m_d->imageName + ANIMATION_METADATA_PATH + "index.xml")) {
0279         m_d->errorMessages << i18nc("Error message when saving a .kra file", "Could not save animation meta data.");
0280         return false;
0281     }
0282 
0283     QDomDocument animationDocument = m_d->doc->createDomDocument("animation-metadata", "1.1");
0284     QDomElement root = animationDocument.documentElement();
0285     saveAnimationMetadataToXML(animationDocument, root, image);
0286 
0287     bool success = true;
0288 
0289     QByteArray ba = animationDocument.toByteArray();
0290     qint64 nwritten = 0;
0291     if (!ba.isEmpty()) {
0292         nwritten = store->write(ba);
0293     } else {
0294         qWarning() << "Could not save animation meta data to a byte array!";
0295         success = false;
0296     }
0297 
0298     bool r = store->close();
0299 
0300     success = success && r && (nwritten == ba.size());
0301 
0302     if (!success) {
0303         m_d->errorMessages << i18nc("Error message when saving a .kra file", "Could not save animation meta data.");
0304         return false;
0305     }
0306 
0307     return true;
0308 }
0309 
0310 bool KisKraSaver::saveAudio(KoStore *store)
0311 {
0312     if (m_d->doc->getAudioTracks().isEmpty())
0313         return true;
0314 
0315     if (!store->open(m_d->imageName + AUDIO_PATH + "index.xml")) {
0316         m_d->errorMessages << i18nc("Error message when saving a .kra file", "Could not save audio meta data.");
0317         return false;
0318     }
0319 
0320     QDomDocument audioDocument = m_d->doc->createDomDocument("audio-info", "1.1");
0321     QDomElement root = audioDocument.documentElement();
0322     saveAudioXML(audioDocument, root);
0323 
0324     bool success = true;
0325     QByteArray byteArray = audioDocument.toByteArray();
0326     qint64 bytesWriteCount = 0;
0327     if (!byteArray.isEmpty()) {
0328         bytesWriteCount = store->write(byteArray);
0329     } else {
0330         qWarning() << "Could not save audio data to a byte array!";
0331         success = false;
0332     }
0333 
0334     bool closeOK = store->close();
0335 
0336     success = success && closeOK && (bytesWriteCount == byteArray.size());
0337 
0338     if (!success) {
0339         m_d->errorMessages << i18nc("Error message when saving a .kra file", "Could not save audio meta data.");
0340         return false;
0341     }
0342 
0343     return true;
0344 }
0345 
0346 void KisKraSaver::saveResourcesToXML(QDomDocument &doc, QDomElement &element)
0347 {
0348     QDomElement ePalette = doc.createElement(PALETTES);
0349     QDomElement eResources = doc.createElement(RESOURCES);
0350 
0351     Q_FOREACH (const KoResourceLoadResult resource, m_d->linkedDocumentResources) {
0352         // all warnings will be issued in KisKraSaver::saveResources()
0353         if (resource.type() != KoResourceLoadResult::EmbeddedResource) continue;
0354 
0355         KoResourceSignature sig = resource.signature();
0356 
0357         QDomElement eResource = doc.createElement("resource");
0358         eResource.setAttribute("type", sig.type);
0359         eResource.setAttribute("name", sig.name);
0360         eResource.setAttribute("filename", sig.filename);
0361         eResource.setAttribute("md5sum", sig.md5sum);
0362 
0363         if (sig.type == ResourceType::Palettes) {
0364             ePalette.appendChild(eResource);
0365         }
0366         else {
0367             eResources.appendChild(eResource);
0368 
0369         }
0370     }
0371     element.appendChild(ePalette);
0372     element.appendChild(eResources);
0373 }
0374 
0375 void KisKraSaver::saveStoryboardToXML(QDomDocument& doc, QDomElement &element)
0376 {
0377     //saving storyboard comments
0378     QDomElement eCommentList = doc.createElement("StoryboardCommentList");
0379     for (StoryboardComment comment: m_d->doc->getStoryboardCommentsList()) {
0380         QDomElement commentElement = doc.createElement("storyboardcomment");
0381         commentElement.setAttribute("name", comment.name);
0382         commentElement.setAttribute("visibility", comment.visibility);
0383         eCommentList.appendChild(commentElement);
0384     }
0385     element.appendChild(eCommentList);
0386 
0387     //saving storyboard items
0388     QDomElement eItemList = doc.createElement("StoryboardItemList");
0389     for (StoryboardItemSP item : m_d->doc->getStoryboardItemList()) {
0390         QDomElement eItem =  item->toXML(doc);
0391         eItemList.appendChild(eItem);
0392     }
0393     element.appendChild(eItemList);
0394 }
0395 
0396 void KisKraSaver::saveAnimationMetadataToXML(QDomDocument &doc, QDomElement &element, KisImageSP image)
0397 {
0398     KisDomUtils::saveValue(&element, "framerate", image->animationInterface()->framerate());
0399     KisDomUtils::saveValue(&element, "range", image->animationInterface()->documentPlaybackRange());
0400     KisDomUtils::saveValue(&element, "currentTime", image->animationInterface()->currentUITime());
0401 
0402     {
0403         QDomElement exportItemElem = doc.createElement("export-settings");
0404         KisDomUtils::saveValue(&exportItemElem, "sequenceFilePath", image->animationInterface()->exportSequenceFilePath());
0405         KisDomUtils::saveValue(&exportItemElem, "sequenceBaseName", image->animationInterface()->exportSequenceBaseName());
0406         KisDomUtils::saveValue(&exportItemElem, "sequenceInitialFrameNumber", image->animationInterface()->exportInitialFrameNumber());
0407         element.appendChild(exportItemElem);
0408     }
0409 }
0410 
0411 bool KisKraSaver::saveKeyframes(KoStore *store, const QString &uri, bool external)
0412 {
0413     QMap<const KisNode*, QString>::iterator it;
0414 
0415     for (it = m_d->keyframeFilenames.begin(); it != m_d->keyframeFilenames.end(); it++) {
0416         const KisNode *node = it.key();
0417         QString filename = it.value();
0418 
0419         QString location =
0420                 (external ? QString() : uri)
0421                 + m_d->imageName + LAYER_PATH + filename;
0422 
0423         if (!saveNodeKeyframes(store, location, node)) {
0424             return false;
0425         }
0426     }
0427 
0428     return true;
0429 }
0430 
0431 bool KisKraSaver::saveNodeKeyframes(KoStore *store, QString location, const KisNode *node)
0432 {
0433     QDomDocument doc = KisDocument::createDomDocument("krita-keyframes", "keyframes", "1.0");
0434     QDomElement root = doc.documentElement();
0435 
0436     KisKeyframeChannel *channel;
0437     Q_FOREACH (channel, node->keyframeChannels()) {
0438         QDomElement element = channel->toXML(doc, m_d->nodeFileNames[node]);
0439         root.appendChild(element);
0440     }
0441 
0442     bool success = true;
0443     if (store->open(location)) {
0444         QByteArray xml = doc.toByteArray();
0445         qint64 nwritten = store->write(xml);
0446         bool r = store->close();
0447         success = r && (nwritten == xml.size());
0448     } else {
0449         success = false;
0450     }
0451     if (!success) {
0452         m_d->errorMessages << i18nc("Error message on saving a .kra file", "Could not save keyframes.");
0453         return false;
0454     }
0455 
0456     return true;
0457 }
0458 
0459 bool KisKraSaver::saveBinaryData(KoStore* store, KisImageSP image, const QString &uri, bool external, bool addMergedImage)
0460 {
0461     QString location;
0462 
0463     // Save the layers data
0464     KisKraSaveVisitor visitor(store, m_d->imageName, m_d->nodeFileNames);
0465 
0466     if (external)
0467         visitor.setExternalUri(uri);
0468 
0469     image->rootLayer()->accept(visitor);
0470 
0471     m_d->errorMessages.append(visitor.errorMessages());
0472     if (!m_d->errorMessages.isEmpty()) {
0473         return false;
0474     }
0475 
0476     bool success = true;
0477     bool r = true;
0478     qint64 nwritten = 0;
0479 
0480     // saving annotations
0481     bool savingAnnotationsSuccess = true;
0482     KisAnnotationSP annotation = image->annotation("exif");
0483     if (annotation) {
0484         location = external ? QString() : uri;
0485         location += m_d->imageName + EXIF_PATH;
0486         if (store->open(location)) {
0487             nwritten = store->write(annotation->annotation());
0488             r = store->close();
0489             savingAnnotationsSuccess = savingAnnotationsSuccess && (nwritten == annotation->annotation().size()) && r;
0490         } else {
0491             savingAnnotationsSuccess = false;
0492         }
0493     }
0494 
0495     if (!savingAnnotationsSuccess) {
0496         m_d->errorMessages.append(i18nc("Saving .kra file error message", "Could not save annotations."));
0497     }
0498 
0499     success = success && savingAnnotationsSuccess;
0500 
0501     bool savingImageProfileSuccess = true;
0502     if (image->profile()) {
0503         const KoColorProfile *profile = image->profile();
0504         KisAnnotationSP annotation;
0505         if (profile) {
0506             QByteArray profileRawData = profile->rawData();
0507             if (!profileRawData.isEmpty()) {
0508                 if (profile->type() == "icc") {
0509                     annotation = new KisAnnotation(ICC, profile->name(), profile->rawData());
0510                 } else {
0511                     annotation = new KisAnnotation(PROFILE, profile->name(), profile->rawData());
0512                 }
0513             }
0514         }
0515 
0516         if (annotation) {
0517             location = external ? QString() : uri;
0518             location += m_d->imageName + ICC_PATH;
0519             if (store->open(location)) {
0520                 nwritten = store->write(annotation->annotation());
0521                 r = store->close();
0522                 savingImageProfileSuccess = savingImageProfileSuccess && (nwritten == annotation->annotation().size()) && r;
0523             } else {
0524                 savingImageProfileSuccess = false;
0525             }
0526         }
0527     }
0528 
0529     if (!savingImageProfileSuccess) {
0530         m_d->errorMessages.append(i18nc("Saving .kra file error message", "Could not save image profile."));
0531     }
0532     success = success && savingImageProfileSuccess;
0533 
0534     //This'll embed the profile used for proofing into the kra file.
0535     bool savingSoftproofingProfileSuccess = true;
0536     if (image->proofingConfiguration()) {
0537         if (image->proofingConfiguration()->storeSoftproofingInsideImage) {
0538             const KoColorProfile *proofingProfile = KoColorSpaceRegistry::instance()->profileByName(image->proofingConfiguration()->proofingProfile);
0539             if (proofingProfile && proofingProfile->valid()) {
0540                 QByteArray proofingProfileRaw = proofingProfile->rawData();
0541                 if (!proofingProfileRaw.isEmpty()) {
0542                     annotation = new KisAnnotation(ICCPROOFINGPROFILE, proofingProfile->name(), proofingProfile->rawData());
0543                 }
0544             }
0545             if (annotation) {
0546                 location = external ? QString() : uri;
0547                 location += m_d->imageName + ICC_PROOFING_PATH;
0548                 if (store->open(location)) {
0549                     nwritten = store->write(annotation->annotation());
0550                     r = store->close();
0551                     savingSoftproofingProfileSuccess = savingSoftproofingProfileSuccess && (nwritten == annotation->annotation().size()) && r;
0552                 } else {
0553                     savingSoftproofingProfileSuccess = false;
0554                 }
0555             }
0556         }
0557     }
0558 
0559     if (!savingSoftproofingProfileSuccess) {
0560         m_d->errorMessages.append(i18nc("Saving .kra file error message", "Could not save softproofing color profile."));
0561     }
0562 
0563     success = success && savingSoftproofingProfileSuccess;
0564 
0565     // Save the remaining annotations
0566     vKisAnnotationSP_it beginIt = image->beginAnnotations();
0567     vKisAnnotationSP_it endIt = image->endAnnotations();
0568 
0569     bool savingRemainingAnnotationsSuccess = true;
0570     if (beginIt != endIt) {
0571         vKisAnnotationSP_it it = beginIt;
0572         while (it != endIt) {
0573             if (!(*it) || (*it)->type().isEmpty()) {
0574                 it++;
0575                 continue;
0576             }
0577             QString type = (*it)->type();
0578 
0579             if (!m_d->specialAnnotations.contains(type)) {
0580                 location = external ? QString() : uri;
0581                 location += m_d->imageName + ANNOTATIONS_PATH + type;
0582                 if (store->open(location)) {
0583                     nwritten = store->write((*it)->annotation());
0584                     r = store->close();
0585                     savingRemainingAnnotationsSuccess = savingRemainingAnnotationsSuccess && (nwritten == (*it)->annotation().size()) && r;
0586                 } else {
0587                     savingRemainingAnnotationsSuccess = false;
0588                 }
0589             }
0590             it++;
0591         }
0592     }
0593 
0594     if (!savingRemainingAnnotationsSuccess) {
0595         m_d->errorMessages.append(i18nc("Saving .kra file error message", "Could not save additional annotations."));
0596     }
0597 
0598     success = success && savingRemainingAnnotationsSuccess;
0599 
0600     bool savingLayerStylesSuccess = true;
0601     {
0602         KisAslLayerStyleSerializer serializer;
0603         QVector<KisPSDLayerStyleSP> stylesClones = serializer.collectAllLayerStyles(image->root());
0604         if (stylesClones.size() > 0) {
0605             location = external ? QString() : uri;
0606             location += m_d->imageName + LAYER_STYLES_PATH;
0607 
0608             if (store->open(location)) {
0609                 QBuffer aslBuffer;
0610                 if (aslBuffer.open(QIODevice::WriteOnly)) {
0611                     serializer.setStyles(stylesClones);
0612                     serializer.saveToDevice(aslBuffer);
0613                     aslBuffer.close();
0614                     nwritten = store->write(aslBuffer.buffer());
0615                     savingLayerStylesSuccess = savingLayerStylesSuccess && (nwritten == aslBuffer.buffer().size());
0616                 } else {
0617                     savingLayerStylesSuccess = false;
0618                 }
0619                 r = store->close();
0620                 savingLayerStylesSuccess = savingLayerStylesSuccess && r;
0621             } else {
0622                 savingLayerStylesSuccess = false;
0623             }
0624         }
0625     }
0626 
0627     if (!savingLayerStylesSuccess) {
0628         m_d->errorMessages.append(i18nc("Saving .kra file error message", "Could not save layer styles."));
0629     }
0630 
0631     success = success && savingLayerStylesSuccess;
0632 
0633     bool savingMergedImageSuccess = true;
0634     if (addMergedImage) {
0635         KisPaintDeviceSP dev = image->projection();
0636         store->setCompressionEnabled(false);
0637         r = KisPNGConverter::saveDeviceToStore("mergedimage.png", image->bounds(), image->xRes(), image->yRes(), dev, store);
0638         savingMergedImageSuccess = savingMergedImageSuccess && r;
0639         store->setCompressionEnabled(KisConfig(true).compressKra());
0640     }
0641 
0642     if (!savingMergedImageSuccess) {
0643         m_d->errorMessages.append(i18nc("Saving .kra file error message", "Could not save merged image."));
0644     }
0645 
0646     success = success && savingMergedImageSuccess;
0647 
0648     r = saveAssistants(store, uri,external);
0649     success = success && r;
0650 
0651     return success;
0652 }
0653 
0654 
0655 
0656 QStringList KisKraSaver::errorMessages() const
0657 {
0658     return m_d->errorMessages;
0659 }
0660 
0661 QStringList KisKraSaver::warningMessages() const
0662 {
0663     return m_d->warningMessages;
0664 }
0665 
0666 void KisKraSaver::saveBackgroundColor(QDomDocument& doc, QDomElement& element, KisImageSP image)
0667 {
0668     QDomElement e = doc.createElement(CANVASPROJECTIONCOLOR);
0669     KoColor color = image->defaultProjectionColor();
0670     QByteArray colorData = QByteArray::fromRawData((const char*)color.data(), color.colorSpace()->pixelSize());
0671     e.setAttribute(COLORBYTEDATA, QString(colorData.toBase64()));
0672     element.appendChild(e);
0673 }
0674 
0675 void KisKraSaver::saveAssistantsGlobalColor(QDomDocument& doc, QDomElement& element)
0676 {
0677     QDomElement e = doc.createElement(GLOBALASSISTANTSCOLOR);
0678     QString colorString = KisDomUtils::qColorToQString(m_d->doc->assistantsGlobalColor());
0679     e.setAttribute(SIMPLECOLORDATA, QString(colorString));
0680     element.appendChild(e);
0681 }
0682 
0683 void KisKraSaver::saveWarningColor(QDomDocument& doc, QDomElement& element, KisImageSP image)
0684 {
0685     if (image->proofingConfiguration()) {
0686         if (image->proofingConfiguration()->storeSoftproofingInsideImage) {
0687             QDomElement e = doc.createElement(PROOFINGWARNINGCOLOR);
0688             KoColor color = image->proofingConfiguration()->warningColor;
0689             color.toXML(doc, e);
0690             element.appendChild(e);
0691         }
0692     }
0693 }
0694 
0695 void KisKraSaver::saveCompositions(QDomDocument& doc, QDomElement& element, KisImageSP image)
0696 {
0697     if (!image->compositions().isEmpty()) {
0698         QDomElement e = doc.createElement("compositions");
0699         Q_FOREACH (KisLayerCompositionSP composition, image->compositions()) {
0700             composition->save(doc, e);
0701         }
0702         element.appendChild(e);
0703     }
0704 }
0705 
0706 bool KisKraSaver::saveAssistants(KoStore* store, QString uri, bool external)
0707 {
0708     QString location;
0709     QMap<QString, int> assistantcounters;
0710     QByteArray data;
0711 
0712     QList<KisPaintingAssistantSP> assistants =  m_d->doc->assistants();
0713     QMap<KisPaintingAssistantHandleSP, int> handlemap;
0714 
0715     bool success = true;
0716     if (!assistants.isEmpty()) {
0717 
0718         Q_FOREACH (KisPaintingAssistantSP assist, assistants){
0719             if (!assistantcounters.contains(assist->id())){
0720                 assistantcounters.insert(assist->id(),0);
0721             }
0722             location = external ? QString() : uri;
0723             location += m_d->imageName + ASSISTANTS_PATH;
0724             location += QString(assist->id()+"%1.assistant").arg(assistantcounters[assist->id()]);
0725 
0726             data = assist->saveXml(handlemap);
0727             if (store->open(location)) {
0728                 qint64 nwritten = store->write(data);
0729                 bool r = store->close();
0730                 success = success && r && (nwritten == data.size());
0731             } else {
0732                 success = false;
0733             }
0734             assistantcounters[assist->id()]++;
0735         }
0736     }
0737     if (!success) {
0738         m_d->errorMessages.append(i18nc("Saving .kra file error message", "Could not save assistants."));
0739     }
0740     return true;
0741 }
0742 
0743 bool KisKraSaver::saveAssistantsList(QDomDocument& doc, QDomElement& element)
0744 {
0745     int count_ellipse = 0,
0746         count_twopoint = 0,
0747         count_perspective = 0,
0748         count_ruler = 0,
0749         count_vanishingpoint = 0,
0750         count_infiniteruler = 0,
0751         count_parallelruler = 0,
0752         count_concentricellipse = 0,
0753         count_fisheyepoint = 0,
0754         count_spline = 0,
0755         count_perspectiveellipse = 0,
0756         count_curvilinearperspective = 0;
0757     QList<KisPaintingAssistantSP> assistants =  m_d->doc->assistants();
0758     if (!assistants.isEmpty()) {
0759         QDomElement assistantsElement = doc.createElement("assistants");
0760         Q_FOREACH (KisPaintingAssistantSP assist, assistants){
0761             if (assist->id() == "ellipse"){
0762                 assist->saveXmlList(doc, assistantsElement, count_ellipse);
0763                 count_ellipse++;
0764             }
0765             else if (assist->id() == "spline"){
0766                 assist->saveXmlList(doc, assistantsElement, count_spline);
0767                 count_spline++;
0768             }
0769             else if (assist->id() == "perspective"){
0770                 assist->saveXmlList(doc, assistantsElement, count_perspective);
0771                 count_perspective++;
0772             }
0773             else if (assist->id() == "vanishing point"){
0774                 assist->saveXmlList(doc, assistantsElement, count_vanishingpoint);
0775                 count_vanishingpoint++;
0776             }
0777             else if (assist->id() == "infinite ruler"){
0778                 assist->saveXmlList(doc, assistantsElement, count_infiniteruler);
0779                 count_infiniteruler++;
0780             }
0781             else if (assist->id() == "parallel ruler"){
0782                 assist->saveXmlList(doc, assistantsElement, count_parallelruler);
0783                 count_parallelruler++;
0784             }
0785             else if (assist->id() == "concentric ellipse"){
0786                 assist->saveXmlList(doc, assistantsElement, count_concentricellipse);
0787                 count_concentricellipse++;
0788             }
0789             else if (assist->id() == "fisheye-point"){
0790                 assist->saveXmlList(doc, assistantsElement, count_fisheyepoint);
0791                 count_fisheyepoint++;
0792             }
0793             else if (assist->id() == "two point"){
0794                 assist->saveXmlList(doc, assistantsElement, count_twopoint);
0795                 count_twopoint++;
0796             }
0797             else if (assist->id() == "ruler"){
0798                 assist->saveXmlList(doc, assistantsElement, count_ruler);
0799                 count_ruler++;
0800             }
0801             else if (assist->id() == "perspective ellipse"){
0802                 assist->saveXmlList(doc, assistantsElement, count_perspectiveellipse);
0803                 count_perspectiveellipse++;
0804             }
0805             else if (assist->id() == "curvilinear-perspective"){
0806                 assist->saveXmlList(doc, assistantsElement, count_curvilinearperspective);
0807                 count_curvilinearperspective++;
0808             }
0809         }
0810         element.appendChild(assistantsElement);
0811     }
0812     return true;
0813 }
0814 
0815 bool KisKraSaver::saveGrid(QDomDocument& doc, QDomElement& element)
0816 {
0817     KisGridConfig config = m_d->doc->gridConfig();
0818 
0819     if (!config.isDefault()) {
0820         QDomElement gridElement = config.saveDynamicDataToXml(doc, "grid");
0821         element.appendChild(gridElement);
0822     }
0823 
0824     return true;
0825 }
0826 
0827 bool KisKraSaver::saveGuides(QDomDocument& doc, QDomElement& element)
0828 {
0829     KisGuidesConfig guides = m_d->doc->guidesConfig();
0830 
0831     if (!guides.isDefault()) {
0832         QDomElement guidesElement = guides.saveToXml(doc, "guides");
0833         element.appendChild(guidesElement);
0834     }
0835 
0836     return true;
0837 }
0838 
0839 bool KisKraSaver::saveMirrorAxis(QDomDocument &doc, QDomElement &element)
0840 {
0841     KisMirrorAxisConfig mirrorAxisConfig = m_d->doc->mirrorAxisConfig();
0842 
0843     if (!mirrorAxisConfig.isDefault()) {
0844         QDomElement mirrorAxisElement = mirrorAxisConfig.saveToXml(doc, MIRROR_AXIS);
0845         element.appendChild(mirrorAxisElement);
0846     }
0847 
0848     return true;
0849 }
0850 
0851 bool KisKraSaver::saveAudioXML(QDomDocument& doc, QDomElement& element)
0852 {
0853     QVector<QFileInfo> clips = m_d->doc->getAudioTracks();
0854     const qreal volume = m_d->doc->getAudioLevel();
0855 
0856     if (!clips.isEmpty()) {
0857         QDomElement audioClips = doc.createElement("audioClips");
0858         Q_FOREACH(const QFileInfo& file, clips) {
0859             QDomElement clip = doc.createElement(QString("Clip"));
0860             clip.setAttribute("filePath", file.absoluteFilePath());
0861             clip.setAttribute("volume", volume);
0862             audioClips.appendChild(clip);
0863         }
0864         element.appendChild(audioClips);
0865     }
0866 
0867     return true;
0868 }
0869