[calligra/calligra/2.9] krita: [FEATURE] Implemented saving of layer styles into PSD

Dmitry Kazakov dimula73 at gmail.com
Thu May 21 14:38:52 UTC 2015


Git commit 427f6dbac3e36b9bc64261a0ac2e4ca3a00610a2 by Dmitry Kazakov.
Committed on 21/05/2015 at 13:24.
Pushed by dkazakov into branch 'calligra/2.9'.

[FEATURE] Implemented saving of layer styles into PSD

(including embedded patterns)

CC:kimageshop at kde.org

M  +13   -0    krita/image/kis_debug.cpp
M  +10   -0    krita/image/kis_debug.h
M  +21   -0    krita/image/kis_dom_utils.cpp
M  +5    -0    krita/image/kis_dom_utils.h
M  +3    -1    krita/libpsd/asl/kis_asl_patterns_writer.h
M  +75   -25   krita/libpsd/asl/kis_asl_writer.cpp
M  +1    -0    krita/libpsd/asl/kis_asl_writer.h
M  +60   -1    krita/plugins/formats/psd/psd_additional_layer_info_block.cpp
M  +6    -0    krita/plugins/formats/psd/psd_additional_layer_info_block.h
M  +23   -30   krita/plugins/formats/psd/psd_layer_record.cpp
M  +2    -2    krita/plugins/formats/psd/psd_layer_record.h
M  +176  -126  krita/plugins/formats/psd/psd_layer_section.cpp
M  +1    -1    krita/plugins/formats/psd/psd_layer_section.h
M  +54   -1    krita/plugins/formats/psd/tests/kis_psd_test.cpp
M  +2    -0    krita/plugins/formats/psd/tests/kis_psd_test.h
M  +52   -5    krita/ui/kis_asl_layer_style_serializer.cpp
M  +5    -1    krita/ui/kis_asl_layer_style_serializer.h

http://commits.kde.org/calligra/427f6dbac3e36b9bc64261a0ac2e4ca3a00610a2

diff --git a/krita/image/kis_debug.cpp b/krita/image/kis_debug.cpp
index fa83515..cb70be9 100644
--- a/krita/image/kis_debug.cpp
+++ b/krita/image/kis_debug.cpp
@@ -18,6 +18,8 @@
 
 #include "kis_debug.h"
 
+#include <string>
+
 #include <QRect>
 #include <QString>
 #include <QImage>
@@ -45,3 +47,14 @@ void kis_debug_save_device_incremental(KisPaintDeviceSP device,
     qDebug() << "Dumping:" << filename;
     device->convertToQImage(0, saveRect).save(filename);
 }
+
+const char* __methodName(const char *_prettyFunction)
+{
+    std::string prettyFunction(_prettyFunction);
+
+    size_t colons = prettyFunction.find("::");
+    size_t begin = prettyFunction.substr(0,colons).rfind(" ") + 1;
+    size_t end = prettyFunction.rfind("(") - begin;
+
+    return std::string(prettyFunction.substr(begin,end) + "()").c_str();
+}
diff --git a/krita/image/kis_debug.h b/krita/image/kis_debug.h
index 8e936ad..08d11c1 100644
--- a/krita/image/kis_debug.h
+++ b/krita/image/kis_debug.h
@@ -137,4 +137,14 @@ void KRITAIMAGE_EXPORT kis_debug_save_device_incremental(KisPaintDeviceSP device
     } while(0)
 
 
+
+#ifdef __GNUC__
+KRITAIMAGE_EXPORT const char* __methodName(const char *prettyFunction);
+#define __METHOD_NAME__ __methodName(__PRETTY_FUNCTION__)
+#else
+#define __METHOD_NAME__ "<unknown>:<unknown>"
+#endif
+
+#define PREPEND_METHOD(msg) QString("%1: %2").arg(__METHOD_NAME__).arg(msg)
+
 #include "kis_assert.h"
diff --git a/krita/image/kis_dom_utils.cpp b/krita/image/kis_dom_utils.cpp
index a1b73c8..88d7724 100644
--- a/krita/image/kis_dom_utils.cpp
+++ b/krita/image/kis_dom_utils.cpp
@@ -209,5 +209,26 @@ bool loadValue(const QDomElement &e, QTransform *t)
     return true;
 }
 
+QDomElement findElementByAttibute(QDomNode parent,
+                                  const QString &tag,
+                                  const QString &attribute,
+                                  const QString &key)
+{
+    QDomNode node = parent.firstChild();
+
+    while (!node.isNull()) {
+        QDomElement el = node.toElement();
+
+        if (!el.isNull() && el.tagName() == tag) {
+            QString value = el.attribute(attribute, "<undefined>");
+            if (value == key) return el;
+        }
+
+        node = node.nextSibling();
+    }
+
+    return QDomElement();
+}
+
 
 }
diff --git a/krita/image/kis_dom_utils.h b/krita/image/kis_dom_utils.h
index ae96827..2d29be4 100644
--- a/krita/image/kis_dom_utils.h
+++ b/krita/image/kis_dom_utils.h
@@ -225,6 +225,11 @@ bool loadValue(const QDomElement &parent, const QString &tag, T *value)
     return loadValue(e, value);
 }
 
+KRITAIMAGE_EXPORT QDomElement findElementByAttibute(QDomNode parent,
+                                                    const QString &tag,
+                                                    const QString &attribute,
+                                                    const QString &key);
+
 }
 
 #endif /* __KIS_DOM_UTILS_H */
diff --git a/krita/libpsd/asl/kis_asl_patterns_writer.h b/krita/libpsd/asl/kis_asl_patterns_writer.h
index 2a7a9ae..a3d7edf 100644
--- a/krita/libpsd/asl/kis_asl_patterns_writer.h
+++ b/krita/libpsd/asl/kis_asl_patterns_writer.h
@@ -19,13 +19,15 @@
 #ifndef __KIS_ASL_PATTERNS_WRITER_H
 #define __KIS_ASL_PATTERNS_WRITER_H
 
+#include "libkispsd_export.h"
+
 class QDomDocument;
 class QIODevice;
 
 class KoPattern;
 
 
-class KisAslPatternsWriter
+class LIBKISPSD_EXPORT KisAslPatternsWriter
 {
 public:
     KisAslPatternsWriter(const QDomDocument &doc, QIODevice *device);
diff --git a/krita/libpsd/asl/kis_asl_writer.cpp b/krita/libpsd/asl/kis_asl_writer.cpp
index b1aa4d8..9538646 100644
--- a/krita/libpsd/asl/kis_asl_writer.cpp
+++ b/krita/libpsd/asl/kis_asl_writer.cpp
@@ -143,6 +143,45 @@ int calculateNumStyles(const QDomElement &root)
     return numStyles;
 }
 
+void writeSingleStyle(QIODevice *device, QDomNode child)
+{
+    KisAslWriterUtils::OffsetStreamPusher<quint32> theOnlyStyleSizeField(device);
+
+    KIS_ASSERT_RECOVER_RETURN(!child.isNull());
+
+    {
+        quint32 stylesFormatVersion = 16;
+        SAFE_WRITE_EX(device, stylesFormatVersion);
+    }
+
+    while (!child.isNull()) {
+        QDomElement el = child.toElement();
+        QString key = el.attribute("key", "");
+
+        if (key != "Patterns") break;
+
+        child = child.nextSibling();
+    }
+
+    parseElement(child.toElement(), device);
+    child = child.nextSibling();
+
+    {
+        quint32 stylesFormatVersion = 16;
+        SAFE_WRITE_EX(device, stylesFormatVersion);
+    }
+
+    parseElement(child.toElement(), device);
+    child = child.nextSibling();
+
+    // ASL files' size should be 4-bytes aligned
+    const qint64 paddingSize = 4 - (device->pos() & 0x3);
+    if (paddingSize != 4) {
+        QByteArray padding(paddingSize, '\0');
+        device->write(padding);
+    }
+}
+
 void writeFileImpl(QIODevice *device, const QDomDocument &doc)
 {
     {
@@ -183,41 +222,47 @@ void writeFileImpl(QIODevice *device, const QDomDocument &doc)
     QDomNode child = root.firstChild();
 
     for (int styleIndex = 0; styleIndex < numStyles; styleIndex++) {
-        KisAslWriterUtils::OffsetStreamPusher<quint32> theOnlyStyleSizeField(device);
+        writeSingleStyle(device, child);
+    }
+}
 
-        KIS_ASSERT_RECOVER_RETURN(!child.isNull());
+void writePsdLfx2SectionImpl(QIODevice *device, const QDomDocument &doc)
+{
+    QDomElement root = doc.documentElement();
+    KIS_ASSERT_RECOVER_RETURN(root.tagName() == "asl");
 
-        {
-            quint32 stylesFormatVersion = 16;
-            SAFE_WRITE_EX(device, stylesFormatVersion);
-        }
+    int numStyles = calculateNumStyles(root);
+    KIS_ASSERT_RECOVER_RETURN(numStyles == 1);
 
-        while (!child.isNull()) {
-            QDomElement el = child.toElement();
-            QString key = el.attribute("key", "");
+    {
+        quint32 objectEffectsVersion = 0;
+        SAFE_WRITE_EX(device, objectEffectsVersion);
+    }
 
-            if (key != "Patterns") break;
+    {
+        quint32 descriptorVersion = 16;
+        SAFE_WRITE_EX(device, descriptorVersion);
+    }
 
-            child = child.nextSibling();
-        }
+    QDomNode child = root.firstChild();
 
-        parseElement(child.toElement(), device);
-        child = child.nextSibling();
+    while (!child.isNull()) {
+        QDomElement el = child.toElement();
+        QString key = el.attribute("key", "");
 
-        {
-            quint32 stylesFormatVersion = 16;
-            SAFE_WRITE_EX(device, stylesFormatVersion);
-        }
+        if (key != "Patterns") break;
 
-        parseElement(child.toElement(), device);
         child = child.nextSibling();
+    }
 
-        // ASL files' size should be 4-bytes aligned
-        const qint64 paddingSize = 4 - (device->pos() & 0x3);
-        if (paddingSize != 4) {
-            QByteArray padding(paddingSize, '\0');
-            device->write(padding);
-        }
+    parseElement(child.toElement(), device);
+    child = child.nextSibling();
+
+    // ASL files' size should be 4-bytes aligned
+    const qint64 paddingSize = 4 - (device->pos() & 0x3);
+    if (paddingSize != 4) {
+        QByteArray padding(paddingSize, '\0');
+        device->write(padding);
     }
 }
 
@@ -231,3 +276,8 @@ void KisAslWriter::writeFile(QIODevice *device, const QDomDocument &doc)
         qWarning() << "WARNING: ASL:" << e.what();
     }
 }
+
+void KisAslWriter::writePsdLfx2SectionEx(QIODevice *device, const QDomDocument &doc)
+{
+    Private::writePsdLfx2SectionImpl(device, doc);
+}
diff --git a/krita/libpsd/asl/kis_asl_writer.h b/krita/libpsd/asl/kis_asl_writer.h
index ed7ef0c..79e3e22 100644
--- a/krita/libpsd/asl/kis_asl_writer.h
+++ b/krita/libpsd/asl/kis_asl_writer.h
@@ -29,6 +29,7 @@ class LIBKISPSD_EXPORT KisAslWriter
 {
 public:
     void writeFile(QIODevice *device, const QDomDocument &doc);
+    void writePsdLfx2SectionEx(QIODevice *device, const QDomDocument &doc);
 };
 
 #endif /* __KIS_ASL_WRITER_H */
diff --git a/krita/plugins/formats/psd/psd_additional_layer_info_block.cpp b/krita/plugins/formats/psd/psd_additional_layer_info_block.cpp
index 5f5aa9e..1c08d09 100644
--- a/krita/plugins/formats/psd/psd_additional_layer_info_block.cpp
+++ b/krita/plugins/formats/psd/psd_additional_layer_info_block.cpp
@@ -24,6 +24,9 @@
 
 #include <asl/kis_asl_reader_utils.h>
 #include <asl/kis_asl_reader.h>
+#include <asl/kis_asl_writer_utils.h>
+#include <asl/kis_asl_writer.h>
+#include <asl/kis_asl_patterns_writer.h>
 
 
 PsdAdditionalLayerInfoBlock::PsdAdditionalLayerInfoBlock(const PSDHeader& header)
@@ -167,7 +170,6 @@ void PsdAdditionalLayerInfoBlock::readImpl(QIODevice* io)
         else if (key == "Patt" || key == "Pat2" || key == "Pat3") {
             KisAslReader reader;
             QDomDocument pattern = reader.readPsdSectionPattern(io, blockSize);
-
             embeddedPatterns << pattern;
         }
         else if (key == "Anno") {
@@ -338,3 +340,60 @@ bool PsdAdditionalLayerInfoBlock::valid()
 
     return true;
 }
+
+void PsdAdditionalLayerInfoBlock::writeLuniBlockEx(QIODevice* io, const QString &layerName)
+{
+    KisAslWriterUtils::writeFixedString("8BIM", io);
+    KisAslWriterUtils::writeFixedString("luni", io);
+    KisAslWriterUtils::OffsetStreamPusher<quint32> layerNameSizeTag(io, 2);
+    KisAslWriterUtils::writeUnicodeString(layerName, io);
+}
+
+void PsdAdditionalLayerInfoBlock::writeLsctBlockEx(QIODevice* io, psd_section_type sectionType, bool isPassThrough, const QString &blendModeKey)
+{
+    KisAslWriterUtils::writeFixedString("8BIM", io);
+    KisAslWriterUtils::writeFixedString("lsct", io);
+    KisAslWriterUtils::OffsetStreamPusher<quint32> sectionTypeSizeTag(io, 2);
+    SAFE_WRITE_EX(io, (quint32)sectionType);
+
+    QString realBlendModeKey = isPassThrough ? QString("pass") : blendModeKey;
+
+    KisAslWriterUtils::writeFixedString("8BIM", io);
+    KisAslWriterUtils::writeFixedString(realBlendModeKey, io);
+}
+
+void PsdAdditionalLayerInfoBlock::writeLfx2BlockEx(QIODevice* io, const QDomDocument &stylesXmlDoc)
+{
+    KisAslWriterUtils::writeFixedString("8BIM", io);
+    KisAslWriterUtils::writeFixedString("lfx2", io);
+    KisAslWriterUtils::OffsetStreamPusher<quint32> lfx2SizeTag(io, 2);
+
+    try {
+        KisAslWriter writer;
+        writer.writePsdLfx2SectionEx(io, stylesXmlDoc);
+
+    } catch (KisAslWriterUtils::ASLWriteException &e) {
+        qWarning() << "WARNING: Couldn't save layer style lfx2 block:" << PREPEND_METHOD(e.what());
+
+        // TODO: make this error recoverable!
+        throw e;
+    }
+}
+
+void PsdAdditionalLayerInfoBlock::writePattBlockEx(QIODevice* io, const QDomDocument &patternsXmlDoc)
+{
+    KisAslWriterUtils::writeFixedString("8BIM", io);
+    KisAslWriterUtils::writeFixedString("Patt", io);
+    KisAslWriterUtils::OffsetStreamPusher<quint32> pattSizeTag(io, 2);
+
+    try {
+        KisAslPatternsWriter writer(patternsXmlDoc, io);
+        writer.writePatterns();
+
+    } catch (KisAslWriterUtils::ASLWriteException &e) {
+        qWarning() << "WARNING: Couldn't save layer style patterns block:" << PREPEND_METHOD(e.what());
+
+        // TODO: make this error recoverable!
+        throw e;
+    }
+}
diff --git a/krita/plugins/formats/psd/psd_additional_layer_info_block.h b/krita/plugins/formats/psd/psd_additional_layer_info_block.h
index 9807bb6..dfa8e73 100644
--- a/krita/plugins/formats/psd/psd_additional_layer_info_block.h
+++ b/krita/plugins/formats/psd/psd_additional_layer_info_block.h
@@ -258,6 +258,12 @@ public:
     bool read(QIODevice* io);
     bool write(QIODevice* io, KisNodeSP node);
 
+    void writeLuniBlockEx(QIODevice* io, const QString &layerName);
+    void writeLsctBlockEx(QIODevice* io, psd_section_type sectionType, bool isPassThrough, const QString &blendModeKey);
+    void writeLfx2BlockEx(QIODevice* io, const QDomDocument &stylesXmlDoc);
+    void writePattBlockEx(QIODevice* io, const QDomDocument &patternsXmlDoc);
+
+
     bool valid();
 
     const PSDHeader &m_header;
diff --git a/krita/plugins/formats/psd/psd_layer_record.cpp b/krita/plugins/formats/psd/psd_layer_record.cpp
index 8b8ca67..6be6106 100644
--- a/krita/plugins/formats/psd/psd_layer_record.cpp
+++ b/krita/plugins/formats/psd/psd_layer_record.cpp
@@ -452,7 +452,12 @@ bool PSDLayerRecord::read(QIODevice* io)
     return valid();
 }
 
-bool PSDLayerRecord::write(QIODevice* io, KisPaintDeviceSP layerContentDevice, KisNodeSP onlyTransparencyMask, const QRect &maskRect, psd_section_type sectionType)
+void PSDLayerRecord::write(QIODevice* io,
+                           KisPaintDeviceSP layerContentDevice,
+                           KisNodeSP onlyTransparencyMask,
+                           const QRect &maskRect,
+                           psd_section_type sectionType,
+                           const QDomDocument &stylesXmlDoc)
 {
     dbgFile << "writing layer info record" << "at" << io->pos();
 
@@ -558,34 +563,25 @@ bool PSDLayerRecord::write(QIODevice* io, KisPaintDeviceSP layerContentDevice, K
             // layer name: Pascal string, padded to a multiple of 4 bytes.
             psdwrite_pascalstring(io, layerName, 4);
 
+
+            PsdAdditionalLayerInfoBlock additionalInfoBlock(m_header);
+
             // write 'luni' data block
-            {
-                KisAslWriterUtils::writeFixedString("8BIM", io);
-                KisAslWriterUtils::writeFixedString("luni", io);
-                KisAslWriterUtils::OffsetStreamPusher<quint32> layerNameSizeTag(io, 2);
-                KisAslWriterUtils::writeUnicodeString(layerName, io);
-            }
+            additionalInfoBlock.writeLuniBlockEx(io, layerName);
 
             // write 'lsct' data block
             if (sectionType != psd_other) {
-                KisAslWriterUtils::writeFixedString("8BIM", io);
-                KisAslWriterUtils::writeFixedString("lsct", io);
-                KisAslWriterUtils::OffsetStreamPusher<quint32> sectionTypeSizeTag(io, 2);
-                SAFE_WRITE_EX(io, (quint32)sectionType);
-
-                QString realBlendModeKey = isPassThrough ? QString("pass") : blendModeKey;
+                additionalInfoBlock.writeLsctBlockEx(io, sectionType, isPassThrough, blendModeKey);
+            }
 
-                KisAslWriterUtils::writeFixedString("8BIM", io);
-                KisAslWriterUtils::writeFixedString(realBlendModeKey, io);
+            // write 'lfx2' data block
+            if (!stylesXmlDoc.isNull()) {
+                additionalInfoBlock.writeLfx2BlockEx(io, stylesXmlDoc);
             }
         }
     } catch (KisAslWriterUtils::ASLWriteException &e) {
-        qWarning() << "WARNING: PSDLayerRecord:" << e.what();
-        return false;
+        throw KisAslWriterUtils::ASLWriteException(PREPEND_METHOD(e.what()));
     }
-
-
-    return true;
 }
 
 void writeChannelDataRLE(QIODevice *io, const quint8 *plane, const int channelSize, const QRect &rc, const qint64 sizeFieldOffset)
@@ -631,10 +627,8 @@ void PSDLayerRecord::writeTransparencyMaskPixelData(QIODevice *io)
     }
 }
 
-bool PSDLayerRecord::writePixelData(QIODevice *io)
+void PSDLayerRecord::writePixelData(QIODevice *io)
 {
-    bool retval = true;
-
     dbgFile << "writing pixel data for layer" << layerName << "at" << io->pos();
 
     KisPaintDeviceSP dev = m_layerContentDevice;
@@ -653,11 +647,10 @@ bool PSDLayerRecord::writePixelData(QIODevice *io)
             writeTransparencyMaskPixelData(io);
 
         } catch (KisAslWriterUtils::ASLWriteException &e) {
-            error = e.what();
-            retval = false;
+            throw KisAslWriterUtils::ASLWriteException(PREPEND_METHOD(e.what()));
         }
 
-        return retval;
+        return;
     }
 
     // now write all the channels in display order
@@ -721,14 +714,14 @@ bool PSDLayerRecord::writePixelData(QIODevice *io)
         writeTransparencyMaskPixelData(io);
 
     } catch (KisAslWriterUtils::ASLWriteException &e) {
-        error = e.what();
-        retval = false;
+        qDeleteAll(planes);
+        planes.clear();
+
+        throw KisAslWriterUtils::ASLWriteException(PREPEND_METHOD(e.what()));
     }
 
     qDeleteAll(planes);
     planes.clear();
-
-    return retval;
 }
 
 bool PSDLayerRecord::valid()
diff --git a/krita/plugins/formats/psd/psd_layer_record.h b/krita/plugins/formats/psd/psd_layer_record.h
index 518ce1f..fceb59d 100644
--- a/krita/plugins/formats/psd/psd_layer_record.h
+++ b/krita/plugins/formats/psd/psd_layer_record.h
@@ -94,8 +94,8 @@ public:
     bool readPixelData(QIODevice* io, KisPaintDeviceSP device);
     bool readMask(QIODevice* io, KisPaintDeviceSP dev, ChannelInfo *channel);
 
-    bool write(QIODevice* io, KisPaintDeviceSP layerContentDevice, KisNodeSP onlyTransparencyMask, const QRect &maskRect, psd_section_type sectionType);
-    bool writePixelData(QIODevice* io);
+    void write(QIODevice* io, KisPaintDeviceSP layerContentDevice, KisNodeSP onlyTransparencyMask, const QRect &maskRect, psd_section_type sectionType, const QDomDocument &stylesXmlDoc);
+    void writePixelData(QIODevice* io);
 
     bool valid();
 
diff --git a/krita/plugins/formats/psd/psd_layer_section.cpp b/krita/plugins/formats/psd/psd_layer_section.cpp
index d70b25a..8b0d343 100644
--- a/krita/plugins/formats/psd/psd_layer_section.cpp
+++ b/krita/plugins/formats/psd/psd_layer_section.cpp
@@ -27,6 +27,8 @@
 #include <kis_group_layer.h>
 #include <kis_effect_mask.h>
 
+#include "kis_dom_utils.h"
+
 #include "psd_header.h"
 #include "psd_utils.h"
 
@@ -34,6 +36,8 @@
 
 #include <asl/kis_offset_on_exit_verifier.h>
 #include <asl/kis_asl_reader_utils.h>
+#include <kis_asl_layer_style_serializer.h>
+#include <asl/kis_asl_writer_utils.h>
 
 
 PSDLayerMaskSection::PSDLayerMaskSection(const PSDHeader& header)
@@ -321,8 +325,63 @@ KisNodeSP findOnlyTransparencyMask(KisNodeSP node)
     return onlyMask->inherits("KisTransparencyMask") ? onlyMask : 0;
 }
 
+QDomDocument fetchLayerStyleXmlData(KisNodeSP node)
+{
+    const KisLayer *layer = qobject_cast<KisLayer*>(node.data());
+    KisPSDLayerStyleSP layerStyle = layer->layerStyle();
+
+    if (!layerStyle) return QDomDocument();
+
+    KisAslLayerStyleSerializer serializer;
+    serializer.setStyles(QVector<KisPSDLayerStyleSP>() << layerStyle);
+    return serializer.formPsdXmlDocument();
+}
+
+inline QDomNode findNodeByKey(const QString &key, QDomNode parent) {
+    return KisDomUtils::findElementByAttibute(parent, "node", "key", key);
+}
+
+void mergePatternsXMLSection(const QDomDocument &src, QDomDocument &dst)
+{
+    QDomNode srcPatternsNode = findNodeByKey("Patterns", src.documentElement());
+    QDomNode dstPatternsNode = findNodeByKey("Patterns", dst.documentElement());
+
+    if (srcPatternsNode.isNull()) return;
+    if (dstPatternsNode.isNull()) {
+        dst = src;
+        return;
+    }
+
+    KIS_ASSERT_RECOVER_RETURN(!srcPatternsNode.isNull());
+    KIS_ASSERT_RECOVER_RETURN(!dstPatternsNode.isNull());
+
+    QDomNode node = srcPatternsNode.firstChild();
+    while(!node.isNull()) {
+        QDomNode importedNode = dst.importNode(node, true);
+        KIS_ASSERT_RECOVER_RETURN(!importedNode.isNull());
+
+        dstPatternsNode.appendChild(importedNode);
+        node = node.nextSibling();
+    }
+
+}
+
 bool PSDLayerMaskSection::write(QIODevice* io, KisNodeSP rootLayer)
 {
+    bool retval = true;
+
+    try {
+        writeImpl(io, rootLayer);
+    } catch (KisAslWriterUtils::ASLWriteException &e) {
+        error = PREPEND_METHOD(e.what());
+        retval = false;
+    }
+
+    return retval;
+}
+
+void PSDLayerMaskSection::writeImpl(QIODevice* io, KisNodeSP rootLayer)
+{
     dbgFile << "Writing layer layer section";
 
     // Build the whole layer structure
@@ -330,157 +389,148 @@ bool PSDLayerMaskSection::write(QIODevice* io, KisNodeSP rootLayer)
     flattenNodes(rootLayer, nodes);
 
     if (nodes.isEmpty()) {
-        error = "Could not find paint layers to save";
-        return false;
+        throw KisAslWriterUtils::ASLWriteException("Could not find paint layers to save");
     }
 
-    quint64 layerMaskPos = io->pos();
-    // length of the layer info and mask information section
-    dbgFile << "Length of layer info and mask info section at" << layerMaskPos;
-    psdwrite(io, (quint32)0);
-
-    quint64 layerInfoPos = io->pos();
-    dbgFile << "length of the layer info section, rounded up to a multiple of two, at" << layerInfoPos;
-    psdwrite(io, (quint32)0);
-
-    // number of layers (negative, because krita always has alpha)
-    dbgFile << "number of layers" << -nodes.size() << "at" << io->pos();
-    psdwrite(io, (qint16)-nodes.size());
-
-    // Layer records section
-    foreach(const FlattenedNode &item, nodes) {
-        KisNodeSP node = item.node;
-
-        PSDLayerRecord *layerRecord = new PSDLayerRecord(m_header);
-        layers.append(layerRecord);
-
-        KisNodeSP onlyTransparencyMask = findOnlyTransparencyMask(node);
-        const QRect maskRect = onlyTransparencyMask ? onlyTransparencyMask->paintDevice()->exactBounds() : QRect();
-
-        const bool nodeVisible = node->visible();
-        const KoColorSpace *colorSpace = node->colorSpace();
-        const quint8 nodeOpacity = node->opacity();
-        const quint8 nodeClipping = 0;
-        const KisPaintLayer *paintLayer = qobject_cast<KisPaintLayer*>(node.data());
-        const bool alphaLocked = (paintLayer && paintLayer->alphaLocked());
-        const QString nodeCompositeOp = node->compositeOpId();
-
-        const KisGroupLayer *groupLayer = qobject_cast<KisGroupLayer*>(node.data());
-        const bool nodeIsPassThrough = groupLayer && groupLayer->passThroughMode();
-
-        bool nodeIrrelevant = false;
-        QString nodeName;
-        KisPaintDeviceSP layerContentDevice;
-        psd_section_type sectionType;
-
-        if (item.type == FlattenedNode::RASTER_LAYER) {
-            nodeIrrelevant = false;
-            nodeName = node->name();
-            layerContentDevice = onlyTransparencyMask ? node->original() : node->projection();
-            sectionType = psd_other;
-        } else {
-            nodeIrrelevant = true;
-            nodeName = item.type == FlattenedNode::SECTION_DIVIDER ?
-                QString("</Layer group>") :
-                node->name();
-            layerContentDevice = 0;
-            sectionType =
-                item.type == FlattenedNode::SECTION_DIVIDER ? psd_bounding_divider :
-                item.type == FlattenedNode::FOLDER_OPEN ? psd_open_folder :
-                psd_closed_folder;
-        }
+    {
+        KisAslWriterUtils::OffsetStreamPusher<quint32> layerAndMaskSectionSizeTag(io, 2);
+        QDomDocument mergedPatternsXmlDoc;
 
+        {
+            KisAslWriterUtils::OffsetStreamPusher<quint32> layerInfoSizeTag(io, 4);
 
-        // === no access to node anymore
+            {
+                // number of layers (negative, because krita always has alpha)
+                const qint16 layersSize = -nodes.size();
+                SAFE_WRITE_EX(io, layersSize);
 
-        QRect layerRect;
+                dbgFile << "Number of layers" << layersSize << "at" << io->pos();
+            }
 
-        if (layerContentDevice) {
-            QRect rc = layerContentDevice->extent();
-            rc = rc.normalized();
+            // Layer records section
+            foreach(const FlattenedNode &item, nodes) {
+                KisNodeSP node = item.node;
 
-            // keep to the max of photoshop's capabilities
-            if (rc.width() > 30000) rc.setWidth(30000);
-            if (rc.height() > 30000) rc.setHeight(30000);
+                PSDLayerRecord *layerRecord = new PSDLayerRecord(m_header);
+                layers.append(layerRecord);
 
-            layerRect = rc;
-        }
+                KisNodeSP onlyTransparencyMask = findOnlyTransparencyMask(node);
+                const QRect maskRect = onlyTransparencyMask ? onlyTransparencyMask->paintDevice()->exactBounds() : QRect();
 
-        layerRecord->top = layerRect.y();
-        layerRecord->left = layerRect.x();
-        layerRecord->bottom = layerRect.y() + layerRect.height();
-        layerRecord->right = layerRect.x() + layerRect.width();
+                const bool nodeVisible = node->visible();
+                const KoColorSpace *colorSpace = node->colorSpace();
+                const quint8 nodeOpacity = node->opacity();
+                const quint8 nodeClipping = 0;
+                const KisPaintLayer *paintLayer = qobject_cast<KisPaintLayer*>(node.data());
+                const bool alphaLocked = (paintLayer && paintLayer->alphaLocked());
+                const QString nodeCompositeOp = node->compositeOpId();
 
-        // colors + alpha channel
-        // note: transparency mask not included
-        layerRecord->nChannels = colorSpace->colorChannelCount() + 1;
+                const KisGroupLayer *groupLayer = qobject_cast<KisGroupLayer*>(node.data());
+                const bool nodeIsPassThrough = groupLayer && groupLayer->passThroughMode();
 
-        ChannelInfo *info = new ChannelInfo;
-        info->channelId = -1; // For the alpha channel, which we always have in Krita, and should be saved first in
-        layerRecord->channelInfoRecords << info;
+                QDomDocument stylesXmlDoc = fetchLayerStyleXmlData(node);
 
-        // the rest is in display order: rgb, cmyk, lab...
-        for (int i = 0; i < (int)colorSpace->colorChannelCount(); ++i) {
-            info = new ChannelInfo;
-            info->channelId = i; // 0 for red, 1 = green, etc
-            layerRecord->channelInfoRecords << info;
-        }
+                if (mergedPatternsXmlDoc.isNull() && !stylesXmlDoc.isNull()) {
+                    mergedPatternsXmlDoc = stylesXmlDoc;
+                } else if (!mergedPatternsXmlDoc.isNull() && !stylesXmlDoc.isNull()) {
+                    mergePatternsXMLSection(stylesXmlDoc, mergedPatternsXmlDoc);
+                }
 
-        layerRecord->blendModeKey = composite_op_to_psd_blendmode(nodeCompositeOp);
-        layerRecord->isPassThrough = nodeIsPassThrough;
-        layerRecord->opacity = nodeOpacity;
-        layerRecord->clipping = nodeClipping;
+                bool nodeIrrelevant = false;
+                QString nodeName;
+                KisPaintDeviceSP layerContentDevice;
+                psd_section_type sectionType;
+
+                if (item.type == FlattenedNode::RASTER_LAYER) {
+                    nodeIrrelevant = false;
+                    nodeName = node->name();
+                    layerContentDevice = onlyTransparencyMask ? node->original() : node->projection();
+                    sectionType = psd_other;
+                } else {
+                    nodeIrrelevant = true;
+                    nodeName = item.type == FlattenedNode::SECTION_DIVIDER ?
+                        QString("</Layer group>") :
+                        node->name();
+                    layerContentDevice = 0;
+                    sectionType =
+                        item.type == FlattenedNode::SECTION_DIVIDER ? psd_bounding_divider :
+                        item.type == FlattenedNode::FOLDER_OPEN ? psd_open_folder :
+                        psd_closed_folder;
+                }
 
-        layerRecord->transparencyProtected = alphaLocked;
-        layerRecord->visible = nodeVisible;
-        layerRecord->irrelevant = nodeIrrelevant;
 
-        layerRecord->layerName = nodeName;
+                // === no access to node anymore
 
-        if (!layerRecord->write(io, layerContentDevice, onlyTransparencyMask, maskRect, sectionType)) {
-            error = layerRecord->error;
-            return false;
-        }
-    }
+                QRect layerRect;
 
-    // Now save the pixel data
-    dbgFile << "start writing layer pixel data" << io->pos();
-    foreach(PSDLayerRecord *layerRecord, layers) {
-        if (!layerRecord->writePixelData(io)) {
-            error = layerRecord->error;
-            return false;
-        }
-    }
+                if (layerContentDevice) {
+                    QRect rc = layerContentDevice->extent();
+                    rc = rc.normalized();
 
-    // Write the final size of the block
-    dbgFile << "Final io pos after writing layer pixel data" << io->pos();
-    quint64 eof_pos = io->pos();
+                    // keep to the max of photoshop's capabilities
+                    if (rc.width() > 30000) rc.setWidth(30000);
+                    if (rc.height() > 30000) rc.setHeight(30000);
 
-    io->seek(layerInfoPos);
+                    layerRect = rc;
+                }
 
-    // length of the layer info information section
-    quint32 layerInfoSize = eof_pos - layerInfoPos - sizeof(qint32);
-    dbgFile << "Layer Info Section length" << layerInfoSize << "at"  << io->pos();
-    psdwrite(io, layerInfoSize);
+                layerRecord->top = layerRect.y();
+                layerRecord->left = layerRect.x();
+                layerRecord->bottom = layerRect.y() + layerRect.height();
+                layerRecord->right = layerRect.x() + layerRect.width();
 
-    io->seek(eof_pos);
+                // colors + alpha channel
+                // note: transparency mask not included
+                layerRecord->nChannels = colorSpace->colorChannelCount() + 1;
 
-    // Write the global layer mask info -- which is empty
-    psdwrite(io, (quint32)0);
+                ChannelInfo *info = new ChannelInfo;
+                info->channelId = -1; // For the alpha channel, which we always have in Krita, and should be saved first in
+                layerRecord->channelInfoRecords << info;
 
-    // Write the final size of the block
-    dbgFile << "Final io pos after writing layer pixel data" << io->pos();
-    eof_pos = io->pos();
+                // the rest is in display order: rgb, cmyk, lab...
+                for (int i = 0; i < (int)colorSpace->colorChannelCount(); ++i) {
+                    info = new ChannelInfo;
+                    info->channelId = i; // 0 for red, 1 = green, etc
+                    layerRecord->channelInfoRecords << info;
+                }
 
-    io->seek(layerInfoPos);
+                layerRecord->blendModeKey = composite_op_to_psd_blendmode(nodeCompositeOp);
+                layerRecord->isPassThrough = nodeIsPassThrough;
+                layerRecord->opacity = nodeOpacity;
+                layerRecord->clipping = nodeClipping;
 
-    // length of the layer and mask info section, rounded up to a multiple of two
-    io->seek(layerMaskPos);
-    quint32 layerMaskSize = eof_pos - layerMaskPos - sizeof(qint32);
-    dbgFile << "Layer and Mask information length" << layerMaskSize << "at" << io->pos();
-    psdwrite(io, layerMaskSize);
+                layerRecord->transparencyProtected = alphaLocked;
+                layerRecord->visible = nodeVisible;
+                layerRecord->irrelevant = nodeIrrelevant;
 
-    io->seek(eof_pos);
+                layerRecord->layerName = nodeName;
 
-    return true;
+                layerRecord->write(io,
+                                   layerContentDevice,
+                                   onlyTransparencyMask,
+                                   maskRect,
+                                   sectionType,
+                                   stylesXmlDoc);
+            }
+
+            dbgFile << "start writing layer pixel data" << io->pos();
+
+            // Now save the pixel data
+            foreach(PSDLayerRecord *layerRecord, layers) {
+                layerRecord->writePixelData(io);
+            }
+
+        }
+
+        {
+            // write the global layer mask info -- which is empty
+            const quint32 globalMaskSize = 0;
+            SAFE_WRITE_EX(io, globalMaskSize);
+        }
+
+        {
+            PsdAdditionalLayerInfoBlock globalInfoSection(m_header);
+            globalInfoSection.writePattBlockEx(io, mergedPatternsXmlDoc);
+        }
+    }
 }
diff --git a/krita/plugins/formats/psd/psd_layer_section.h b/krita/plugins/formats/psd/psd_layer_section.h
index dde8d57..f599f64 100644
--- a/krita/plugins/formats/psd/psd_layer_section.h
+++ b/krita/plugins/formats/psd/psd_layer_section.h
@@ -61,7 +61,7 @@ public:
 
 private:
     bool readImpl(QIODevice* io);
-
+    void writeImpl(QIODevice* io, KisNodeSP rootLayer);
 private:
 
     const PSDHeader m_header;
diff --git a/krita/plugins/formats/psd/tests/kis_psd_test.cpp b/krita/plugins/formats/psd/tests/kis_psd_test.cpp
index c0d3196..b833241 100644
--- a/krita/plugins/formats/psd/tests/kis_psd_test.cpp
+++ b/krita/plugins/formats/psd/tests/kis_psd_test.cpp
@@ -90,7 +90,7 @@ void KisPSDTest::testTransparencyMask()
     QVERIFY(retval);
 
     {
-        QSharedPointer<KisDocument> doc = openPsdDocument(sourceFileInfo);
+        QSharedPointer<KisDocument> doc = openPsdDocument(dstFileInfo);
         QVERIFY(doc->image());
 
         QImage result = doc->image()->projection()->convertToQImage(0, doc->image()->bounds());
@@ -184,6 +184,59 @@ void KisPSDTest::testOpenLayerStylesWithPatternMulti()
     QVERIFY(layer->layerStyle()->stroke()->pattern()->valid());
 }
 
+void KisPSDTest::testSaveLayerStylesWithPatternMulti()
+{
+    QFileInfo sourceFileInfo(QString(FILES_DATA_DIR) + QDir::separator() + "test_ls_pattern_multi.psd");
+
+    Q_ASSERT(sourceFileInfo.exists());
+
+    QSharedPointer<KisDocument> doc = openPsdDocument(sourceFileInfo);
+    QVERIFY(doc->image());
+
+    KisLayerSP layer = dynamic_cast<KisLayer*>(doc->image()->root()->lastChild().data());
+    QVERIFY(layer->layerStyle());
+
+    QVERIFY(layer->layerStyle()->patternOverlay());
+    QVERIFY(layer->layerStyle()->patternOverlay()->effectEnabled());
+    QVERIFY(layer->layerStyle()->patternOverlay()->pattern());
+    QVERIFY(layer->layerStyle()->patternOverlay()->pattern()->valid());
+
+    QVERIFY(layer->layerStyle()->stroke());
+    QVERIFY(layer->layerStyle()->stroke()->effectEnabled());
+    QVERIFY(layer->layerStyle()->stroke()->pattern());
+    QVERIFY(layer->layerStyle()->stroke()->pattern()->valid());
+
+
+    doc->setBackupFile(false);
+    doc->setOutputMimeType("image/vnd.adobe.photoshop");
+    QFileInfo dstFileInfo(QDir::currentPath() + QDir::separator() + "test_save_styles.psd");
+    bool retval = doc->saveAs(KUrl(dstFileInfo.absoluteFilePath()));
+    QVERIFY(retval);
+
+    {
+        QSharedPointer<KisDocument> doc = openPsdDocument(dstFileInfo);
+        QVERIFY(doc->image());
+
+        QImage result = doc->image()->projection()->convertToQImage(0, doc->image()->bounds());
+        //QVERIFY(TestUtil::checkQImageExternal(result, "psd_test", "transparency_masks", "kiki_single"));
+
+        KisLayerSP layer = dynamic_cast<KisLayer*>(doc->image()->root()->lastChild().data());
+        QVERIFY(layer->layerStyle());
+
+        QVERIFY(layer->layerStyle()->patternOverlay());
+        QVERIFY(layer->layerStyle()->patternOverlay()->effectEnabled());
+        QVERIFY(layer->layerStyle()->patternOverlay()->pattern());
+        QVERIFY(layer->layerStyle()->patternOverlay()->pattern()->valid());
+
+        QVERIFY(layer->layerStyle()->stroke());
+        QVERIFY(layer->layerStyle()->stroke()->effectEnabled());
+        QVERIFY(layer->layerStyle()->stroke()->pattern());
+        QVERIFY(layer->layerStyle()->stroke()->pattern()->valid());
+    }
+
+}
+
+
 
 QTEST_KDEMAIN(KisPSDTest, GUI)
 
diff --git a/krita/plugins/formats/psd/tests/kis_psd_test.h b/krita/plugins/formats/psd/tests/kis_psd_test.h
index 9b062c6..9e854aa 100644
--- a/krita/plugins/formats/psd/tests/kis_psd_test.h
+++ b/krita/plugins/formats/psd/tests/kis_psd_test.h
@@ -34,6 +34,8 @@ private Q_SLOTS:
 
     void testOpenLayerStylesWithPattern();
     void testOpenLayerStylesWithPatternMulti();
+
+    void testSaveLayerStylesWithPatternMulti();
 };
 
 #endif
diff --git a/krita/ui/kis_asl_layer_style_serializer.cpp b/krita/ui/kis_asl_layer_style_serializer.cpp
index 7b71110..dbf354b 100644
--- a/krita/ui/kis_asl_layer_style_serializer.cpp
+++ b/krita/ui/kis_asl_layer_style_serializer.cpp
@@ -29,6 +29,9 @@
 #include <KoStopGradient.h>
 #include <KoPattern.h>
 
+#include "kis_dom_utils.h"
+
+
 #include "psd.h"
 #include "kis_global.h"
 #include "kis_psd_layer_style.h"
@@ -236,7 +239,7 @@ QString strokeFillTypeToString(psd_fill_type position)
     return result;
 }
 
-QVector<KoPattern*> KisAslLayerStyleSerializer::fetchAllPatterns(KisPSDLayerStyle *style)
+QVector<KoPattern*> KisAslLayerStyleSerializer::fetchAllPatterns(KisPSDLayerStyle *style) const
 {
     QVector <KoPattern*> allPatterns;
 
@@ -269,10 +272,9 @@ QString fetchPatternUuidSafe(KoPattern *pattern, QHash<KoPattern*, QString> patt
     }
 }
 
-
-void KisAslLayerStyleSerializer::saveToDevice(QIODevice *device)
+QDomDocument KisAslLayerStyleSerializer::formXmlDocument() const
 {
-    KIS_ASSERT_RECOVER_RETURN(!m_stylesVector.isEmpty());
+    KIS_ASSERT_RECOVER(!m_stylesVector.isEmpty()) { return QDomDocument(); }
 
     QVector<KoPattern*> allPatterns;
 
@@ -676,8 +678,53 @@ void KisAslLayerStyleSerializer::saveToDevice(QIODevice *device)
         w.leaveDescriptor();
     }
 
+    return w.document();
+}
+
+inline QDomNode findNodeByClassId(const QString &classId, QDomNode parent) {
+    return KisDomUtils::findElementByAttibute(parent, "node", "classId", classId);
+}
+
+void replaceAllChildren(QDomNode src, QDomNode dst)
+{
+    QDomNode node;
+
+    do {
+        node = dst.lastChild();
+        dst.removeChild(node);
+
+    } while(!node.isNull());
+
+
+    node = src.firstChild();
+    while(!node.isNull()) {
+        dst.appendChild(node);
+        node = src.firstChild();
+    }
+
+    src.parentNode().removeChild(src);
+}
+
+QDomDocument KisAslLayerStyleSerializer::formPsdXmlDocument() const
+{
+    QDomDocument doc = formXmlDocument();
+
+    QDomNode nullNode = findNodeByClassId("null", doc.documentElement());
+    QDomNode stylNode = findNodeByClassId("Styl", doc.documentElement());
+    QDomNode lefxNode = findNodeByClassId("Lefx", stylNode);
+
+    replaceAllChildren(lefxNode, nullNode);
+
+    return doc;
+}
+
+void KisAslLayerStyleSerializer::saveToDevice(QIODevice *device)
+{
+    QDomDocument doc = formXmlDocument();
+    if (doc.isNull()) return ;
+
     KisAslWriter writer;
-    writer.writeFile(device, w.document());
+    writer.writeFile(device, doc);
 }
 
 void convertAndSetBlendMode(const QString &mode,
diff --git a/krita/ui/kis_asl_layer_style_serializer.h b/krita/ui/kis_asl_layer_style_serializer.h
index fca7dba..76be82d 100644
--- a/krita/ui/kis_asl_layer_style_serializer.h
+++ b/krita/ui/kis_asl_layer_style_serializer.h
@@ -43,6 +43,10 @@ public:
     void registerPSDPattern(const QDomDocument &doc);
     void readFromPSDXML(const QDomDocument &doc);
 
+    QDomDocument formXmlDocument() const;
+    QDomDocument formPsdXmlDocument() const;
+
+
 private:
     void registerPatternObject(const KoPattern *pattern);
 
@@ -50,7 +54,7 @@ private:
                              const QString &patternName,
                              boost::function<void (KoPattern *)> setPattern);
 
-    QVector<KoPattern*> fetchAllPatterns(KisPSDLayerStyle *style);
+    QVector<KoPattern*> fetchAllPatterns(KisPSDLayerStyle *style) const;
 
     void newStyleStarted(bool isPsdStructure);
     void connectCatcherToStyle(KisPSDLayerStyle *style, const QString &prefix);


More information about the kimageshop mailing list