Commit 7dcdcbd0 authored by Dmitry Kazakov's avatar Dmitry Kazakov

[FEATURE] Implemented saving of layer styles into PSD

(including embedded patterns)

CC:kimageshop@kde.org
parent 14766dab
......@@ -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();
}
......@@ -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"
......@@ -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();
}
}
......@@ -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 */
......@@ -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);
......
......@@ -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);
}
......@@ -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 */
......@@ -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;
}
}
......@@ -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;
......
......@@ -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()
......
......@@ -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();
......
......@@ -61,7 +61,7 @@ public:
private:
bool readImpl(QIODevice* io);
void writeImpl(QIODevice* io, KisNodeSP rootLayer);
private:
const PSDHeader m_header;
......
......@@ -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)
......
......@@ -34,6 +34,8 @@ private Q_SLOTS:
void testOpenLayerStylesWithPattern();
void testOpenLayerStylesWithPatternMulti();
void testSaveLayerStylesWithPatternMulti();
};
#endif
......@@ -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,
......
......@@ -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);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment