[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