kis_kra_saver.cpp 15.2 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
/* This file is part of the KDE project
 * Copyright 2008 (C) Boudewijn Rempt <boud@valdyas.org>
 *
 * 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 "kis_kra_saver.h"

#include "kis_kra_tags.h"
#include "kis_kra_save_visitor.h"
23
#include "kis_kra_savexml_visitor.h"
24 25 26 27

#include <QDomDocument>
#include <QDomElement>
#include <QString>
28
#include <QStringList>
29

Boudewijn Rempt's avatar
Boudewijn Rempt committed
30
#include <QUrl>
31
#include <QBuffer>
32

33
#include <KoDocumentInfo.h>
34
#include <KoColorSpaceRegistry.h>
35 36
#include <KoColorSpace.h>
#include <KoColorProfile.h>
37
#include <KoColor.h>
38
#include <KoStore.h>
39
#include <KoStoreDevice.h>
40 41 42

#include <kis_annotation.h>
#include <kis_image.h>
43
#include <kis_image_animation_interface.h>
44 45 46
#include <kis_group_layer.h>
#include <kis_layer.h>
#include <kis_adjustment_layer.h>
Sven Langkamp's avatar
Sven Langkamp committed
47
#include <kis_layer_composition.h>
48
#include <kis_painting_assistants_decoration.h>
49
#include <kis_psd_layer_style_resource.h>
50
#include "kis_png_converter.h"
51
#include "kis_keyframe_channel.h"
52
#include <kis_time_range.h>
53
#include "KisDocument.h"
54
#include <string>
55
#include "kis_dom_utils.h"
56
#include "kis_grid_config.h"
57
#include "kis_guides_config.h"
58
#include "KisProofingConfiguration.h"
59 60 61 62


using namespace KRA;

63
struct KisKraSaver::Private
Boudewijn Rempt's avatar
Boudewijn Rempt committed
64
{
65
public:
66
    KisDocument* doc;
67
    QMap<const KisNode*, QString> nodeFileNames;
68
    QMap<const KisNode*, QString> keyframeFilenames;
69
    QString imageName;
70
    QStringList errorMessages;
71 72
};

73
KisKraSaver::KisKraSaver(KisDocument* document)
Boudewijn Rempt's avatar
Boudewijn Rempt committed
74
        : m_d(new Private)
75 76
{
    m_d->doc = document;
77 78

    m_d->imageName = m_d->doc->documentInfo()->aboutInfo("title");
79 80 81
    if (m_d->imageName.isEmpty()) {
        m_d->imageName = i18n("Unnamed");
    }
82 83 84 85
}

KisKraSaver::~KisKraSaver()
{
Boudewijn Rempt's avatar
Boudewijn Rempt committed
86
    delete m_d;
87 88
}

Boudewijn Rempt's avatar
Boudewijn Rempt committed
89
QDomElement KisKraSaver::saveXML(QDomDocument& doc,  KisImageWSP image)
90
{
Boudewijn Rempt's avatar
Boudewijn Rempt committed
91 92 93 94 95
    QDomElement imageElement = doc.createElement("IMAGE"); // Legacy!

    Q_ASSERT(image);
    imageElement.setAttribute(NAME, m_d->imageName);
    imageElement.setAttribute(MIME, NATIVE_MIMETYPE);
96 97
    imageElement.setAttribute(WIDTH, KisDomUtils::toString(image->width()));
    imageElement.setAttribute(HEIGHT, KisDomUtils::toString(image->height()));
Boudewijn Rempt's avatar
Boudewijn Rempt committed
98 99
    imageElement.setAttribute(COLORSPACE_NAME, image->colorSpace()->id());
    imageElement.setAttribute(DESCRIPTION, m_d->doc->documentInfo()->aboutInfo("comment"));
100
    // XXX: Save profile as blob inside the image, instead of the product name.
101
    if (image->profile() && image->profile()-> valid()) {
Boudewijn Rempt's avatar
Boudewijn Rempt committed
102
        imageElement.setAttribute(PROFILE, image->profile()->name());
103
    }
104 105
    imageElement.setAttribute(X_RESOLUTION, KisDomUtils::toString(image->xRes()*72.0));
    imageElement.setAttribute(Y_RESOLUTION, KisDomUtils::toString(image->yRes()*72.0));
106 107 108 109 110
    //now the proofing options:
    imageElement.setAttribute(PROOFINGPROFILENAME, KisDomUtils::toString(image->proofingConfiguration()->proofingProfile));
    imageElement.setAttribute(PROOFINGMODEL, KisDomUtils::toString(image->proofingConfiguration()->proofingModel));
    imageElement.setAttribute(PROOFINGDEPTH, KisDomUtils::toString(image->proofingConfiguration()->proofingDepth));
    imageElement.setAttribute(PROOFINGINTENT, KisDomUtils::toString(image->proofingConfiguration()->intent));
111
    imageElement.setAttribute(PROOFINGADAPTATIONSTATE, KisDomUtils::toString(image->proofingConfiguration()->adaptationState));
112

113
    quint32 count = 1; // We don't save the root layer, but it does count
114
    KisSaveXmlVisitor visitor(doc, imageElement, count, m_d->doc->url().toLocalFile(), true);
115
    visitor.setSelectedNodes(m_d->doc->activeNodes());
116

Boudewijn Rempt's avatar
Boudewijn Rempt committed
117
    image->rootLayer()->accept(visitor);
118 119
    m_d->errorMessages.append(visitor.errorMessages());

120
    m_d->nodeFileNames = visitor.nodeFileNames();
121
    m_d->keyframeFilenames = visitor.keyframeFileNames();
Sven Langkamp's avatar
Sven Langkamp committed
122

123
    saveBackgroundColor(doc, imageElement, image);
124
    saveWarningColor(doc, imageElement, image);
Sven Langkamp's avatar
Sven Langkamp committed
125
    saveCompositions(doc, imageElement, image);
126
    saveAssistantsList(doc,imageElement);
127
    saveGrid(doc,imageElement);
128
    saveGuides(doc,imageElement);
129 130 131

    QDomElement animationElement = doc.createElement("animation");
    KisDomUtils::saveValue(&animationElement, "framerate", image->animationInterface()->framerate());
132
    KisDomUtils::saveValue(&animationElement, "range", image->animationInterface()->fullClipRange());
133 134 135
    KisDomUtils::saveValue(&animationElement, "currentTime", image->animationInterface()->currentUITime());
    imageElement.appendChild(animationElement);

Boudewijn Rempt's avatar
Boudewijn Rempt committed
136
    return imageElement;
137 138
}

139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
bool KisKraSaver::saveKeyframes(KoStore *store, const QString &uri, bool external)
{
    QMap<const KisNode*, QString>::iterator it;

    for (it = m_d->keyframeFilenames.begin(); it != m_d->keyframeFilenames.end(); it++) {
        const KisNode *node = it.key();
        QString filename = it.value();

        QString location =
                (external ? QString() : uri)
                + m_d->imageName + LAYER_PATH + filename;

        if (!saveNodeKeyframes(store, location, node)) {
            return false;
        }
    }

    return true;
}

bool KisKraSaver::saveNodeKeyframes(KoStore *store, QString location, const KisNode *node)
{
    QDomDocument doc = KisDocument::createDomDocument("krita-keyframes", "keyframes", "1.0");
    QDomElement root = doc.documentElement();

    KisKeyframeChannel *channel;
165
    Q_FOREACH (channel, node->keyframeChannels()) {
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181
        QDomElement element = channel->toXML(doc, m_d->nodeFileNames[node]);
        root.appendChild(element);
    }

    if (store->open(location)) {
        QByteArray xml = doc.toByteArray();
        store->write(xml);
        store->close();
    } else {
        m_d->errorMessages << i18n("could not save keyframes");
        return false;
    }

    return true;
}

182
bool KisKraSaver::saveBinaryData(KoStore* store, KisImageWSP image, const QString & uri, bool external, bool autosave)
183 184 185 186
{
    QString location;

    // Save the layers data
187
    KisKraSaveVisitor visitor(store, m_d->imageName, m_d->nodeFileNames);
188 189 190 191

    if (external)
        visitor.setExternalUri(uri);

Boudewijn Rempt's avatar
Boudewijn Rempt committed
192
    image->rootLayer()->accept(visitor);
193 194 195 196 197 198

    m_d->errorMessages.append(visitor.errorMessages());
    if (!m_d->errorMessages.isEmpty()) {
        return false;
    }

199 200 201 202
    // saving annotations
    // XXX this only saves EXIF and ICC info. This would probably need
    // a redesign of the dtd of the krita file to do this more generally correct
    // e.g. have <ANNOTATION> tags or so.
Boudewijn Rempt's avatar
Boudewijn Rempt committed
203
    KisAnnotationSP annotation = image->annotation("exif");
204
    if (annotation) {
205
        location = external ? QString() : uri;
206
        location += m_d->imageName + EXIF_PATH;
207 208 209 210 211
        if (store->open(location)) {
            store->write(annotation->annotation());
            store->close();
        }
    }
Boudewijn Rempt's avatar
Boudewijn Rempt committed
212 213
    if (image->profile()) {
        const KoColorProfile *profile = image->profile();
214
        KisAnnotationSP annotation;
Cyrille Berger's avatar
Cyrille Berger committed
215 216 217 218 219 220 221 222 223
        if (profile) {
            QByteArray profileRawData = profile->rawData();
            if (!profileRawData.isEmpty()) {
                if (profile->type() == "icc") {
                    annotation = new KisAnnotation(ICC, profile->name(), profile->rawData());
                } else {
                    annotation = new KisAnnotation(PROFILE, profile->name(), profile->rawData());
                }
            }
224 225 226
        }

        if (annotation) {
227
            location = external ? QString() : uri;
228
            location += m_d->imageName + ICC_PATH;
229 230 231
            if (store->open(location)) {
                store->write(annotation->annotation());
                store->close();
232 233 234 235
            }
        }
    }

236
    //This'll embed the profile used for proofing into the kra file.
237 238 239 240 241 242 243 244
    if (image->proofingConfiguration()) {
        const KoColorProfile *proofingProfile = KoColorSpaceRegistry::instance()->profileByName(image->proofingConfiguration()->proofingProfile);
        if (proofingProfile && proofingProfile->valid()) {
            QByteArray proofingProfileRaw = proofingProfile->rawData();
            if (!proofingProfileRaw.isEmpty()) {
                annotation = new KisAnnotation(ICCPROOFINGPROFILE, proofingProfile->name(), proofingProfile->rawData());
            }
        }
245 246 247 248 249 250 251 252 253
        if (annotation) {
            location = external ? QString() : uri;
            location += m_d->imageName + ICC_PROOFING_PATH;
            if (store->open(location)) {
                store->write(annotation->annotation());
                store->close();
            }
        }

254 255
    }

256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271
    {
        KisPSDLayerStyleCollectionResource collection("not-nexists.asl");
        KIS_ASSERT_RECOVER_NOOP(!collection.valid());
        collection.collectAllLayerStyles(image->root());
        if (collection.valid()) {
            location = external ? QString() : uri;
            location += m_d->imageName + LAYER_STYLES_PATH;

            if (store->open(location)) {
                QBuffer aslBuffer;
                aslBuffer.open(QIODevice::WriteOnly);
                collection.saveToDevice(&aslBuffer);
                aslBuffer.close();

                store->write(aslBuffer.buffer());
                store->close();
272 273 274
            }
        }
    }
275

276
    if (!autosave) {
277
        KisPaintDeviceSP dev = image->projection();
278
        if (!KisPNGConverter::isColorSpaceSupported(dev->colorSpace())) {
279 280 281 282 283
            dev = new KisPaintDevice(*dev.data());
            KUndo2Command *cmd = dev->convertTo(KoColorSpaceRegistry::instance()->rgb8());
            delete cmd;
        }

284
        KisPNGConverter::saveDeviceToStore("mergedimage.png", image->bounds(), image->xRes(), image->yRes(), dev, store);
285 286
    }

287
    saveAssistants(store, uri,external);
288
    return true;
289
}
Sven Langkamp's avatar
Sven Langkamp committed
290

291 292 293 294 295
QStringList KisKraSaver::errorMessages() const
{
    return m_d->errorMessages;
}

296 297
void KisKraSaver::saveBackgroundColor(QDomDocument& doc, QDomElement& element, KisImageWSP image)
{
298
    QDomElement e = doc.createElement(CANVASPROJECTIONCOLOR);
299 300
    KoColor color = image->defaultProjectionColor();
    QByteArray colorData = QByteArray::fromRawData((const char*)color.data(), color.colorSpace()->pixelSize());
301
    e.setAttribute(COLORBYTEDATA, QString(colorData.toBase64()));
302 303 304
    element.appendChild(e);
}

305 306 307 308 309
void KisKraSaver::saveWarningColor(QDomDocument& doc, QDomElement& element, KisImageWSP image)
{
    if (image->proofingConfiguration()) {
        QDomElement e = doc.createElement(PROOFINGWARNINGCOLOR);
        KoColor color = image->proofingConfiguration()->warningColor;
310 311 312
        color.toXML(doc, e);
        //QByteArray colorData = QByteArray::fromRawData((const char*)color.data(), color.colorSpace()->pixelSize());
        //e.setAttribute("ColorData", QString(colorData.toBase64()));
313 314 315 316
        element.appendChild(e);
    }
}

Sven Langkamp's avatar
Sven Langkamp committed
317 318
void KisKraSaver::saveCompositions(QDomDocument& doc, QDomElement& element, KisImageWSP image)
{
319 320
    if (!image->compositions().isEmpty()) {
        QDomElement e = doc.createElement("compositions");
321
        Q_FOREACH (KisLayerComposition* composition, image->compositions()) {
322 323 324
            composition->save(doc, e);
        }
        element.appendChild(e);
Sven Langkamp's avatar
Sven Langkamp committed
325 326
    }
}
327

328
bool KisKraSaver::saveAssistants(KoStore* store, QString uri, bool external)
329 330
{
    QString location;
331 332
    QMap<QString, int> assistantcounters;
    QByteArray data;
333
    QList<KisPaintingAssistantSP> assistants =  m_d->doc->assistants();
334
    QMap<KisPaintingAssistantHandleSP, int> handlemap;
335
    if (!assistants.isEmpty()) {
336
        Q_FOREACH (KisPaintingAssistantSP assist, assistants){
337 338
            if (!assistantcounters.contains(assist->id())){
                assistantcounters.insert(assist->id(),0);
339
            }
340
            location = external ? QString() : uri;
341 342 343 344 345 346 347
            location += m_d->imageName + ASSISTANTS_PATH;
            location += QString(assist->id()+"%1.assistant").arg(assistantcounters[assist->id()]);
            data = assist->saveXml(handlemap);
            store->open(location);
            store->write(data);
            store->close();
            assistantcounters[assist->id()]++;
348
        }
349 350 351 352 353 354 355

    }
    return true;
}

bool KisKraSaver::saveAssistantsList(QDomDocument& doc, QDomElement& element)
{
356
    int count_ellipse = 0, count_perspective = 0, count_ruler = 0, count_vanishingpoint = 0,count_infiniteruler = 0, count_parallelruler = 0, count_concentricellipse = 0, count_fisheyepoint = 0, count_spline = 0;
357
    QList<KisPaintingAssistantSP> assistants =  m_d->doc->assistants();
358 359
    if (!assistants.isEmpty()) {
        QDomElement assistantsElement = doc.createElement("assistants");
360
        Q_FOREACH (KisPaintingAssistantSP assist, assistants){
361
            if (assist->id() == "ellipse"){
362 363
                assist->saveXmlList(doc, assistantsElement, count_ellipse);
                count_ellipse++;
364
            }
365
            else if (assist->id() == "spline"){
366 367
                assist->saveXmlList(doc, assistantsElement, count_spline);
                count_spline++;
368
            }
369
            else if (assist->id() == "perspective"){
370 371
                assist->saveXmlList(doc, assistantsElement, count_perspective);
                count_perspective++;
372
            }
373 374 375 376 377 378 379 380 381 382 383 384
            else if (assist->id() == "vanishing point"){
                assist->saveXmlList(doc, assistantsElement, count_vanishingpoint);
                count_vanishingpoint++;
            }
            else if (assist->id() == "infinite ruler"){
                assist->saveXmlList(doc, assistantsElement, count_infiniteruler);
                count_infiniteruler++;
            }
            else if (assist->id() == "parallel ruler"){
                assist->saveXmlList(doc, assistantsElement, count_parallelruler);
                count_parallelruler++;
            }
385 386 387 388
            else if (assist->id() == "concentric ellipse"){
                assist->saveXmlList(doc, assistantsElement, count_concentricellipse);
                count_concentricellipse++;
            }
389 390 391 392
            else if (assist->id() == "fisheye-point"){
                assist->saveXmlList(doc, assistantsElement, count_fisheyepoint);
                count_fisheyepoint++;
            }
393
            else if (assist->id() == "ruler"){
394 395
                assist->saveXmlList(doc, assistantsElement, count_ruler);
                count_ruler++;
396 397 398
            }
        }
        element.appendChild(assistantsElement);
399 400 401
    }
    return true;
}
402

403 404 405 406 407 408 409 410 411 412 413 414
bool KisKraSaver::saveGrid(QDomDocument& doc, QDomElement& element)
{
    KisGridConfig config = m_d->doc->gridConfig();

    if (!config.isDefault()) {
        QDomElement gridElement = config.saveDynamicDataToXml(doc, "grid");
        element.appendChild(gridElement);
    }

    return true;
}

415 416
bool KisKraSaver::saveGuides(QDomDocument& doc, QDomElement& element)
{
417
    KisGuidesConfig guides = m_d->doc->guidesConfig();
418

419 420 421
    if (guides.hasGuides()) {
        QDomElement guidesElement = guides.saveToXml(doc, "guides");
        element.appendChild(guidesElement);
422 423 424 425 426
    }

    return true;
}