diff --git a/krita/libpsd/asl/kis_asl_reader.cpp b/krita/libpsd/asl/kis_asl_reader.cpp index 2f8f509afb..951dae5563 100644 --- a/krita/libpsd/asl/kis_asl_reader.cpp +++ b/krita/libpsd/asl/kis_asl_reader.cpp @@ -1,658 +1,658 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_asl_reader.h" #include "kis_dom_utils.h" #include #include #include #include #include #include "psd_utils.h" #include "psd.h" #include "compression.h" #include "kis_offset_on_exit_verifier.h" #include "kis_asl_writer_utils.h" #include "kis_asl_reader_utils.h" namespace Private { /** * Numerical fetch functions * * We read numbers and convert them to strings to be able to store * them in XML. */ QString readDoubleAsString(QIODevice *device) { double value = 0.0; SAFE_READ_EX(device, value); return KisDomUtils::Private::numberToString(value); } QString readIntAsString(QIODevice *device) { quint32 value = 0.0; SAFE_READ_EX(device, value); return KisDomUtils::Private::numberToString(value); } QString readBoolAsString(QIODevice *device) { quint8 value = 0.0; SAFE_READ_EX(device, value); return KisDomUtils::Private::numberToString(value); } /** * XML generation functions * * Add a node and fill the corresponding attributes */ QDomElement appendXMLNodeCommon(const QString &key, const QString &value, const QString &type, QDomElement *parent, QDomDocument *doc) { QDomElement el = doc->createElement("node"); if (!key.isEmpty()) { el.setAttribute("key", key); } el.setAttribute("type", type); el.setAttribute("value", value); parent->appendChild(el); return el; } QDomElement appendXMLNodeCommonNoValue(const QString &key, const QString &type, QDomElement *parent, QDomDocument *doc) { QDomElement el = doc->createElement("node"); if (!key.isEmpty()) { el.setAttribute("key", key); } el.setAttribute("type", type); parent->appendChild(el); return el; } void appendIntegerXMLNode(const QString &key, const QString &value, QDomElement *parent, QDomDocument *doc) { appendXMLNodeCommon(key, value, "Integer", parent, doc); } void appendDoubleXMLNode(const QString &key, const QString &value, QDomElement *parent, QDomDocument *doc) { appendXMLNodeCommon(key, value, "Double", parent, doc); } void appendTextXMLNode(const QString &key, const QString &value, QDomElement *parent, QDomDocument *doc) { appendXMLNodeCommon(key, value, "Text", parent, doc); } void appendPointXMLNode(const QString &key, const QPointF &pt, QDomElement *parent, QDomDocument *doc) { QDomElement el = appendXMLNodeCommonNoValue(key, "Descriptor", parent, doc); el.setAttribute("classId", "CrPt"); el.setAttribute("name", ""); appendDoubleXMLNode("Hrzn", KisDomUtils::Private::numberToString(pt.x()), &el, doc); appendDoubleXMLNode("Vrtc", KisDomUtils::Private::numberToString(pt.x()), &el, doc); } /** * ASL -> XML parsing functions */ void readDescriptor(QIODevice *device, const QString &key, QDomElement *parent, QDomDocument *doc); void readChildObject(QIODevice *device, QDomElement *parent, QDomDocument *doc, bool skipKey = false) { using namespace KisAslReaderUtils; QString key; if (!skipKey) { key = readVarString(device); } QString OSType = readFixedString(device); //qDebug() << "Child" << ppVar(key) << ppVar(OSType); if (OSType == "obj ") { qFatal("no implemented"); } else if (OSType == "Objc" || OSType == "GlbO") { readDescriptor(device, key, parent, doc); } else if (OSType == "VlLs") { quint32 numItems = GARBAGE_VALUE_MARK; SAFE_READ_EX(device, numItems); QDomElement el = appendXMLNodeCommonNoValue(key, "List", parent, doc); for (quint32 i = 0; i < numItems; i++) { readChildObject(device, &el, doc, true); } } else if (OSType == "doub") { appendDoubleXMLNode(key, readDoubleAsString(device), parent, doc); } else if (OSType == "UntF") { const QString unit = readFixedString(device); const QString value = readDoubleAsString(device); QDomElement el = appendXMLNodeCommon(key, value, "UnitFloat", parent, doc); el.setAttribute("unit", unit); } else if (OSType == "TEXT") { QString unicodeString = readUnicodeString(device); appendTextXMLNode(key, unicodeString, parent, doc); } else if (OSType == "enum") { const QString typeId = readVarString(device); const QString value = readVarString(device); QDomElement el = appendXMLNodeCommon(key, value, "Enum", parent, doc); el.setAttribute("typeId", typeId); } else if (OSType == "long") { appendIntegerXMLNode(key, readIntAsString(device), parent, doc); } else if (OSType == "bool") { const QString value = readBoolAsString(device); appendXMLNodeCommon(key, value, "Boolean", parent, doc); } else if (OSType == "type") { qFatal("no implemented"); } else if (OSType == "GlbC") { qFatal("no implemented"); } else if (OSType == "alis") { qFatal("no implemented"); } else if (OSType == "tdta") { qFatal("no implemented"); } } void readDescriptor(QIODevice *device, const QString &key, QDomElement *parent, QDomDocument *doc) { using namespace KisAslReaderUtils; QString name = readUnicodeString(device); QString classId = readVarString(device); quint32 numChildren = GARBAGE_VALUE_MARK; SAFE_READ_EX(device, numChildren); QDomElement el = appendXMLNodeCommonNoValue(key, "Descriptor", parent, doc); el.setAttribute("classId", classId); el.setAttribute("name", name); //qDebug() << "Descriptor" << ppVar(key) << ppVar(classId) << ppVar(numChildren); for (quint32 i = 0; i < numChildren; i++) { readChildObject(device, &el, doc); } } QImage readVirtualArrayList(QIODevice *device, int numPlanes) { using namespace KisAslReaderUtils; quint32 arrayVersion = GARBAGE_VALUE_MARK; SAFE_READ_EX(device, arrayVersion); if (arrayVersion != 3) { throw ASLParseException("VAList version is not '3'!"); } quint32 arrayLength = GARBAGE_VALUE_MARK; SAFE_READ_EX(device, arrayLength); SETUP_OFFSET_VERIFIER(vaEndVerifier, device, arrayLength, 100); quint32 x0, y0, x1, y1; SAFE_READ_EX(device, y0); SAFE_READ_EX(device, x0); SAFE_READ_EX(device, y1); SAFE_READ_EX(device, x1); QRect arrayRect(x0, y0, x1 - x0, y1 - y0); quint32 numberOfChannels = GARBAGE_VALUE_MARK; SAFE_READ_EX(device, numberOfChannels); if (numberOfChannels != 24) { throw ASLParseException("VAList: Krita doesn't support ASL files with 'numberOfChannels' flag not equal to 24 (it is not documented)!"); } // qDebug() << ppVar(arrayVersion); // qDebug() << ppVar(arrayLength); // qDebug() << ppVar(arrayRect); // qDebug() << ppVar(numberOfChannels); if (numPlanes != 1 && numPlanes != 3) { throw ASLParseException("VAList: unsupported number of planes!"); } QVector dataPlanes; dataPlanes.resize(3); for (int i = 0; i < numPlanes; i++) { quint32 arrayWritten = GARBAGE_VALUE_MARK; if (!psdread(device, &arrayWritten) || !arrayWritten) { throw ASLParseException("VAList plane has not-written flag set!"); } quint32 arrayPlaneLength = GARBAGE_VALUE_MARK; if (!psdread(device, &arrayPlaneLength) || !arrayPlaneLength) { throw ASLParseException("VAList has plane length set to zero!"); } SETUP_OFFSET_VERIFIER(planeEndVerifier, device, arrayPlaneLength, 0); qint64 nextPos = device->pos() + arrayPlaneLength; quint32 pixelDepth1 = GARBAGE_VALUE_MARK; SAFE_READ_EX(device, pixelDepth1); quint32 x0, y0, x1, y1; SAFE_READ_EX(device, y0); SAFE_READ_EX(device, x0); SAFE_READ_EX(device, y1); SAFE_READ_EX(device, x1); QRect planeRect(x0, y0, x1 - x0, y1 - y0); if (planeRect != arrayRect) { throw ASLParseException("VAList: planes are not uniform. Not supported yet!"); } quint16 pixelDepth2 = GARBAGE_VALUE_MARK; SAFE_READ_EX(device, pixelDepth2); quint8 useCompression = 9; SAFE_READ_EX(device, useCompression); // qDebug() << "plane index:" << ppVar(i); // qDebug() << ppVar(arrayWritten); // qDebug() << ppVar(arrayPlaneLength); // qDebug() << ppVar(pixelDepth1); // qDebug() << ppVar(planeRect); // qDebug() << ppVar(pixelDepth2); // qDebug() << ppVar(useCompression); if (pixelDepth1 != pixelDepth2) { throw ASLParseException("VAList: two pixel depths of the plane are not equal (it is not documented)!"); } if (pixelDepth1 != 8) { throw ASLParseException("VAList: supported pixel depth of the plane in 8 only!"); } const int dataLength = planeRect.width() * planeRect.height(); if (useCompression == Compression::Uncompressed) { dataPlanes[i] = device->read(dataLength); } else if (useCompression == Compression::RLE) { const int numRows = planeRect.height(); QVector rowSizes; rowSizes.resize(numRows); for (int row = 0; row < numRows; row++) { quint16 rowSize = GARBAGE_VALUE_MARK; SAFE_READ_EX(device, rowSize); rowSizes[row] = rowSize; } for (int row = 0; row < numRows; row++) { const quint16 rowSize = rowSizes[row]; QByteArray compressedData = device->read(rowSize); if (compressedData.size() != rowSize) { throw ASLParseException("VAList: failed to read compressed data!"); } QByteArray uncompressedData = Compression::uncompress(planeRect.width(), compressedData, Compression::RLE); if (uncompressedData.size() != planeRect.width()) { throw ASLParseException("VAList: failed to decompress data!"); } dataPlanes[i].append(uncompressedData); } } else { throw ASLParseException("VAList: ZIP compression is not implemented yet!"); } if (dataPlanes[i].size() != dataLength) { throw ASLParseException("VAList: failed to read/uncompress data plane!"); } device->seek(nextPos); } QImage image(arrayRect.size(), QImage::Format_ARGB32); const int dataLength = arrayRect.width() * arrayRect.height(); quint8 *dstPtr = image.bits(); for (int i = 0; i < dataLength; i++) { for (int j = 2; j >= 0; j--) { int plane = qMin(numPlanes, j); *dstPtr++ = dataPlanes[plane][i]; } *dstPtr++ = 0xFF; } #if 0 static int i = -1; i++; QString filename = QString("pattern_image_%1.png").arg(i); qDebug() << "### dumping pattern image" << ppVar(filename); image.save(filename); #endif return image; } qint64 readPattern(QIODevice *device, QDomElement *parent, QDomDocument *doc) { using namespace KisAslReaderUtils; quint32 patternSize = GARBAGE_VALUE_MARK; SAFE_READ_EX(device, patternSize); // patterns are always aligned by 4 bytes patternSize = KisAslWriterUtils::alignOffsetCeil(patternSize, 4); SETUP_OFFSET_VERIFIER(patternEndVerifier, device, patternSize, 0); quint32 patternVersion = GARBAGE_VALUE_MARK; SAFE_READ_EX(device, patternVersion); if (patternVersion != 1) { throw ASLParseException("Pattern version is not \'1\'"); } quint32 patternImageMode = GARBAGE_VALUE_MARK; SAFE_READ_EX(device, patternImageMode); quint16 patternHeight = GARBAGE_VALUE_MARK; SAFE_READ_EX(device, patternHeight); quint16 patternWidth = GARBAGE_VALUE_MARK; SAFE_READ_EX(device, patternWidth); QString patternName; psdread_unicodestring(device, patternName); QString patternUuid = readPascalString(device); // qDebug() << "--"; // qDebug() << ppVar(patternSize); // qDebug() << ppVar(patternImageMode); // qDebug() << ppVar(patternHeight); // qDebug() << ppVar(patternWidth); // qDebug() << ppVar(patternName); // qDebug() << ppVar(patternUuid); int numPlanes = 0; psd_color_mode mode = static_cast(patternImageMode); switch (mode) { case MultiChannel: case Grayscale: numPlanes = 1; break; case RGB: numPlanes = 3; break; default: { QString msg = QString("Unsupported image mode: %1!").arg(mode); throw ASLParseException(msg); } } /** * Create XML data */ QDomElement pat = doc->createElement("node"); pat.setAttribute("classId", "KisPattern"); pat.setAttribute("type", "Descriptor"); pat.setAttribute("name", ""); QBuffer patternBuf; patternBuf.open(QIODevice::WriteOnly); { // ensure we don't keep resources for too long QString fileName = QString("%1.pat").arg(patternUuid); QImage patternImage = readVirtualArrayList(device, numPlanes); KoPattern realPattern(patternImage, patternName, fileName); - realPattern.saveToDevice(&patternBuf); + realPattern.savePatToDevice(&patternBuf); } /** * We are loading the pattern and convert it into ARGB right away, * so we need not store real image mode and size of the pattern * externally. */ appendTextXMLNode("Nm ", patternName, &pat, doc); appendTextXMLNode("Idnt", patternUuid, &pat, doc); QDomCDATASection dataSection = doc->createCDATASection(qCompress(patternBuf.buffer()).toBase64()); QDomElement dataElement = doc->createElement("node"); dataElement.setAttribute("type", "KisPatternData"); dataElement.setAttribute("key", "Data"); dataElement.appendChild(dataSection); pat.appendChild(dataElement); parent->appendChild(pat); return sizeof(patternSize) + patternSize; } QDomDocument readFileImpl(QIODevice *device) { using namespace KisAslReaderUtils; QDomDocument doc; QDomElement root = doc.createElement("asl"); doc.appendChild(root); { quint16 stylesVersion = GARBAGE_VALUE_MARK; SAFE_READ_SIGNATURE_EX(device, stylesVersion, 2); } { quint32 aslSignature = GARBAGE_VALUE_MARK; const quint32 refSignature = 0x3842534c; // '8BSL' in little-endian SAFE_READ_SIGNATURE_EX(device, aslSignature, refSignature); } { quint16 patternsVersion = GARBAGE_VALUE_MARK; SAFE_READ_SIGNATURE_EX(device, patternsVersion, 3); } // Patterns { quint32 patternsSize = GARBAGE_VALUE_MARK; SAFE_READ_EX(device, patternsSize); if (patternsSize > 0) { SETUP_OFFSET_VERIFIER(patternsSectionVerifier, device, patternsSize, 0); QDomElement patternsRoot = doc.createElement("node"); patternsRoot.setAttribute("type", "List"); patternsRoot.setAttribute("key", "Patterns"); root.appendChild(patternsRoot); try { qint64 bytesRead = 0; while (bytesRead < patternsSize) { qint64 chunk = readPattern(device, &patternsRoot, &doc); bytesRead += chunk; } } catch (ASLParseException &e) { qWarning() << "WARNING: ASL (emb. pattern):" << e.what(); } } } // Styles quint32 numStyles = GARBAGE_VALUE_MARK; SAFE_READ_EX(device, numStyles); for (int i = 0; i < (int)numStyles; i++) { quint32 bytesToRead = GARBAGE_VALUE_MARK; SAFE_READ_EX(device, bytesToRead); SETUP_OFFSET_VERIFIER(singleStyleSectionVerifier, device, bytesToRead, 0); { quint32 stylesFormatVersion = GARBAGE_VALUE_MARK; SAFE_READ_SIGNATURE_EX(device, stylesFormatVersion, 16); } readDescriptor(device, "", &root, &doc); { quint32 stylesFormatVersion = GARBAGE_VALUE_MARK; SAFE_READ_SIGNATURE_EX(device, stylesFormatVersion, 16); } readDescriptor(device, "", &root, &doc); } return doc; } } // namespace QDomDocument KisAslReader::readFile(QIODevice *device) { QDomDocument doc; if (device->isSequential()) { qWarning() << "WARNING: *** KisAslReader::readFile: the supplied" << "IO device is sequential. Chances are that" << "the layer style will *not* be loaded correctly!"; } try { doc = Private::readFileImpl(device); } catch (KisAslReaderUtils::ASLParseException &e) { qWarning() << "WARNING: ASL:" << e.what(); } return doc; } QDomDocument KisAslReader::readLfx2PsdSection(QIODevice *device) { QDomDocument doc; if (device->isSequential()) { qWarning() << "WARNING: *** KisAslReader::readLfx2PsdSection: the supplied" << "IO device is sequential. Chances are that" << "the layer style will *not* be loaded correctly!"; } try { { quint32 objectEffectsVersion = GARBAGE_VALUE_MARK; const quint32 ref = 0x00; SAFE_READ_SIGNATURE_EX(device, objectEffectsVersion, ref); } { quint32 descriptorVersion = GARBAGE_VALUE_MARK; const quint32 ref = 0x10; SAFE_READ_SIGNATURE_EX(device, descriptorVersion, ref); } QDomElement root = doc.createElement("asl"); doc.appendChild(root); Private::readDescriptor(device, "", &root, &doc); } catch (KisAslReaderUtils::ASLParseException &e) { qWarning() << "WARNING: PSD: lfx2 section:" << e.what(); } return doc; } QDomDocument KisAslReader::readPsdSectionPattern(QIODevice *device, qint64 bytesLeft) { QDomDocument doc; QDomElement root = doc.createElement("asl"); doc.appendChild(root); QDomElement pat = doc.createElement("node"); root.appendChild(pat); pat.setAttribute("classId", "Patterns"); pat.setAttribute("type", "Descriptor"); pat.setAttribute("name", ""); try { qint64 bytesRead = 0; while (bytesRead < bytesLeft) { qint64 chunk = Private::readPattern(device, &pat, &doc); bytesRead += chunk; } } catch (KisAslReaderUtils::ASLParseException &e) { qWarning() << "WARNING: PSD (emb. pattern):" << e.what(); } return doc; } diff --git a/krita/libpsd/asl/kis_asl_xml_parser.cpp b/krita/libpsd/asl/kis_asl_xml_parser.cpp index 3baabbffb0..30b93aba9d 100644 --- a/krita/libpsd/asl/kis_asl_xml_parser.cpp +++ b/krita/libpsd/asl/kis_asl_xml_parser.cpp @@ -1,556 +1,556 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_asl_xml_parser.h" #include #include #include #include #include #include #include #include #include #include #include "kis_dom_utils.h" #include "kis_debug.h" #include "psd_utils.h" #include "psd.h" #include "compression.h" #include "kis_asl_object_catcher.h" namespace Private { void parseElement(const QDomElement &el, const QString &parentPath, KisAslObjectCatcher &catcher); class CurveObjectCatcher : public KisAslObjectCatcher { public: void addText(const QString &path, const QString &value) { if (path == "/Nm ") { m_name = value; } else { qWarning() << "XML (ASL): failed to parse curve object" << path << value; } } void addPoint(const QString &path, const QPointF &value) { if (!m_arrayMode) { qWarning() << "XML (ASL): failed to parse curve object (array fault)" << path << value << ppVar(m_arrayMode); } m_points.append(value); } public: QVector m_points; QString m_name; }; QColor parseRGBColorObject(QDomElement parent) { QColor color(Qt::black); QDomNode child = parent.firstChild(); while (!child.isNull()) { QDomElement childEl = child.toElement(); QString type = childEl.attribute("type", ""); QString key = childEl.attribute("key", ""); if (type != "Double") { qWarning() << "Unknown color component type:" << ppVar(type) << ppVar(key); return Qt::red; } double value = KisDomUtils::Private::stringToDouble(childEl.attribute("value", "0")); if (key == "Rd ") { color.setRed(value); } else if (key == "Grn ") { color.setGreen(value); } else if (key == "Bl ") { color.setBlue(value); } else { qWarning() << "Unknown color key value:" << ppVar(key); return Qt::red; } child = child.nextSibling(); } return color; } void parseColorStopsList(QDomElement parent, QVector &startLocations, QVector &middleOffsets, QVector &colors) { QDomNode child = parent.firstChild(); while (!child.isNull()) { QDomElement childEl = child.toElement(); QString type = childEl.attribute("type", ""); QString key = childEl.attribute("key", ""); QString classId = childEl.attribute("classId", ""); if (type == "Descriptor" && classId == "Clrt") { // sorry for naming... QDomNode child = childEl.firstChild(); while (!child.isNull()) { QDomElement childEl = child.toElement(); QString type = childEl.attribute("type", ""); QString key = childEl.attribute("key", ""); QString classId = childEl.attribute("classId", ""); if (type == "Integer" && key == "Lctn") { int value = KisDomUtils::Private::stringToInt(childEl.attribute("value", "0")); startLocations.append(qreal(value) / 4096.0); } else if (type == "Integer" && key == "Mdpn") { int value = KisDomUtils::Private::stringToInt(childEl.attribute("value", "0")); middleOffsets.append(qreal(value) / 100.0); } else if (type == "Descriptor" && key == "Clr ") { colors.append(parseRGBColorObject(childEl)); } else if (type == "Enum" && key == "Type") { QString typeId = childEl.attribute("typeId", ""); if (typeId != "Clry") { qWarning() << "WARNING: Invalid typeId of a greadient stop type" << typeId; } QString value = childEl.attribute("value", ""); if (value == "BckC" || value == "FrgC") { qWarning() << "WARNING: Using foreground/background colors in ASL gradients is not yet supported"; } } child = child.nextSibling(); } } else { qWarning() << "WARNING: Unrecognized object in color stops list" << ppVar(type) << ppVar(key) << ppVar(classId); } child = child.nextSibling(); } } void parseTransparencyStopsList(QDomElement parent, QVector &startLocations, QVector &middleOffsets, QVector &transparencies) { QDomNode child = parent.firstChild(); while (!child.isNull()) { QDomElement childEl = child.toElement(); QString type = childEl.attribute("type", ""); QString key = childEl.attribute("key", ""); QString classId = childEl.attribute("classId", ""); if (type == "Descriptor" && classId == "TrnS") { // sorry for naming again... QDomNode child = childEl.firstChild(); while (!child.isNull()) { QDomElement childEl = child.toElement(); QString type = childEl.attribute("type", ""); QString key = childEl.attribute("key", ""); if (type == "Integer" && key == "Lctn") { int value = KisDomUtils::Private::stringToInt(childEl.attribute("value", "0")); startLocations.append(qreal(value) / 4096.0); } else if (type == "Integer" && key == "Mdpn") { int value = KisDomUtils::Private::stringToInt(childEl.attribute("value", "0")); middleOffsets.append(qreal(value) / 100.0); } else if (type == "UnitFloat" && key == "Opct") { QString unit = childEl.attribute("unit", ""); if (unit != "#Prc") { qWarning() << "WARNING: Invalid unit of a greadient stop transparency" << unit; } qreal value = KisDomUtils::Private::stringToDouble(childEl.attribute("value", "100")); transparencies.append(value / 100.0); } child = child.nextSibling(); } } else { qWarning() << "WARNING: Unrecognized object in transparency stops list" << ppVar(type) << ppVar(key) << ppVar(classId); } child = child.nextSibling(); } } inline QString buildPath(const QString &parent, const QString &key) { return parent + "/" + key; } bool tryParseDescriptor(const QDomElement &el, const QString &path, const QString &classId, KisAslObjectCatcher &catcher) { bool retval = true; if (classId == "null") { catcher.newStyleStarted(); // here we just notify that a new style is started, we haven't // processed the whole block yet, so return false. retval = false; } else if (classId == "RGBC") { catcher.addColor(path, parseRGBColorObject(el)); } else if (classId == "ShpC") { CurveObjectCatcher curveCatcher; QDomNode child = el.firstChild(); while (!child.isNull()) { parseElement(child.toElement(), "", curveCatcher); child = child.nextSibling(); } catcher.addCurve(path, curveCatcher.m_name, curveCatcher.m_points); } else if (classId == "CrPt") { QPointF point; QDomNode child = el.firstChild(); while (!child.isNull()) { QDomElement childEl = child.toElement(); QString type = childEl.attribute("type", ""); QString key = childEl.attribute("key", ""); if (type == "Boolean" && key == "Cnty") { qWarning() << "WARNING: tryParseDescriptor: The points of the curve object contain \'Cnty\' flag which is unsupported by Krita"; qWarning() << " " << ppVar(type) << ppVar(key) << ppVar(path); child = child.nextSibling(); continue; } if (type != "Double") { qWarning() << "Unknown point component type:" << ppVar(type) << ppVar(key) << ppVar(path); return false; } double value = KisDomUtils::Private::stringToDouble(childEl.attribute("value", "0")); if (key == "Hrzn") { point.setX(value); } else if (key == "Vrtc") { point.setY(value); } else { qWarning() << "Unknown point key value:" << ppVar(key) << ppVar(path); return false; } child = child.nextSibling(); } catcher.addPoint(path, point); } else if (classId == "Pnt ") { QPointF point; QDomNode child = el.firstChild(); while (!child.isNull()) { QDomElement childEl = child.toElement(); QString type = childEl.attribute("type", ""); QString key = childEl.attribute("key", ""); QString unit = childEl.attribute("unit", ""); if (type != "Double" && !(type == "UnitFloat" && unit == "#Prc")) { qWarning() << "Unknown point component type:" << ppVar(unit) << ppVar(type) << ppVar(key) << ppVar(path); return false; } double value = KisDomUtils::Private::stringToDouble(childEl.attribute("value", "0")); if (key == "Hrzn") { point.setX(value); } else if (key == "Vrtc") { point.setY(value); } else { qWarning() << "Unknown point key value:" << ppVar(key) << ppVar(path); return false; } child = child.nextSibling(); } catcher.addPoint(path, point); } else if (classId == "KisPattern") { QByteArray patternData; QString patternUuid; QDomNode child = el.firstChild(); while (!child.isNull()) { QDomElement childEl = child.toElement(); QString type = childEl.attribute("type", ""); QString key = childEl.attribute("key", ""); if (type == "Text" && key == "Idnt") { patternUuid = childEl.attribute("value", ""); } if (type == "KisPatternData" && key == "Data") { QDomNode dataNode = child.firstChild(); if (!dataNode.isCDATASection()) { qWarning() << "WARNING: failed to parse KisPatternData XML section!"; continue; } QDomCDATASection dataSection = dataNode.toCDATASection(); QByteArray data = dataSection.data().toAscii(); data = QByteArray::fromBase64(data); data = qUncompress(data); if (data.isEmpty()) { qWarning() << "WARNING: failed to parse KisPatternData XML section!"; continue; } patternData = data; } child = child.nextSibling(); } if (!patternUuid.isEmpty() && !patternData.isEmpty()) { QString fileName = QString("%1.pat").arg(patternUuid); QScopedPointer pattern(new KoPattern(fileName)); QBuffer buffer(&patternData); buffer.open(QIODevice::ReadOnly); - pattern->loadFromDevice(&buffer); + pattern->loadPatFromDevice(&buffer); catcher.addPattern(path, pattern.data()); } else { qWarning() << "WARNING: failed to load KisPattern XML section!" << ppVar(patternUuid); } } else if (classId == "Ptrn") { // reference to an existing pattern QString patternUuid; QString patternName; QDomNode child = el.firstChild(); while (!child.isNull()) { QDomElement childEl = child.toElement(); QString type = childEl.attribute("type", ""); QString key = childEl.attribute("key", ""); if (type == "Text" && key == "Idnt") { patternUuid = childEl.attribute("value", ""); } else if (type == "Text" && key == "Nm ") { patternName = childEl.attribute("value", ""); } else { qWarning() << "WARNING: unrecognized pattern-ref section key:" << ppVar(type) << ppVar(key); } child = child.nextSibling(); } catcher.addPatternRef(path, patternUuid, patternName); } else if (classId == "Grdn") { QString gradientName; qreal gradientSmoothness = 100.0; QVector startLocations; QVector middleOffsets; QVector colors; QVector transpStartLocations; QVector transpMiddleOffsets; QVector transparencies; QDomNode child = el.firstChild(); while (!child.isNull()) { QDomElement childEl = child.toElement(); QString type = childEl.attribute("type", ""); QString key = childEl.attribute("key", ""); if (type == "Text" && key == "Nm ") { gradientName = childEl.attribute("value", ""); } else if (type == "Enum" && key == "GrdF") { QString typeId = childEl.attribute("typeId", ""); QString value = childEl.attribute("value", ""); if (typeId != "GrdF" || value != "CstS") { qWarning() << "WARNING: Unsupported gradient type (porbably, noise-based):" << value; return true; } } else if (type == "Double" && key == "Intr") { double value = KisDomUtils::Private::stringToDouble(childEl.attribute("value", "4096")); gradientSmoothness = 100.0 * value / 4096.0; } else if (type == "List" && key == "Clrs") { parseColorStopsList(childEl, startLocations, middleOffsets, colors); } else if (type == "List" && key == "Trns") { parseTransparencyStopsList(childEl, transpStartLocations, transpMiddleOffsets, transparencies); } child = child.nextSibling(); } if (colors.size() < 2) { qWarning() << "WARNING: ASL gradient has too few stops" << ppVar(colors.size()); } if (colors.size() != transparencies.size()) { qWarning() << "WARNING: ASL gradient has inconsistent number of transparency stops. Dropping transparency..." << ppVar(colors.size()) << ppVar(transparencies.size()); transparencies.resize(colors.size()); for (int i = 0; i < colors.size(); i++) { transparencies[i] = 1.0; } } QString fileName = gradientName + ".ggr"; QSharedPointer gradient(new KoSegmentGradient(fileName)); Q_UNUSED(gradientSmoothness); gradient->setName(gradientName); for (int i = 1; i < colors.size(); i++) { QColor startColor = colors[i-1]; QColor endColor = colors[i]; startColor.setAlphaF(transparencies[i-1]); endColor.setAlphaF(transparencies[i]); qreal start = startLocations[i-1]; qreal end = startLocations[i]; qreal middle = start + middleOffsets[i-1] * (end - start); gradient->createSegment(INTERP_LINEAR, COLOR_INTERP_RGB, start, end, middle, startColor, endColor); } gradient->setValid(true); catcher.addGradient(path, gradient); } else { retval = false; } return retval; } void parseElement(const QDomElement &el, const QString &parentPath, KisAslObjectCatcher &catcher) { KIS_ASSERT_RECOVER_RETURN(el.tagName() == "node"); QString type = el.attribute("type", ""); QString key = el.attribute("key", ""); if (type == "Descriptor") { QString classId = el.attribute("classId", ""); QString containerName = key.isEmpty() ? classId : key; QString containerPath = buildPath(parentPath, containerName); if (!tryParseDescriptor(el, containerPath, classId, catcher)) { QDomNode child = el.firstChild(); while (!child.isNull()) { parseElement(child.toElement(), containerPath, catcher); child = child.nextSibling(); } } } else if (type == "List") { catcher.setArrayMode(true); QString containerName = key; QString containerPath = buildPath(parentPath, containerName); QDomNode child = el.firstChild(); while (!child.isNull()) { parseElement(child.toElement(), containerPath, catcher); child = child.nextSibling(); } catcher.setArrayMode(false); } else if (type == "Double") { double v = KisDomUtils::Private::stringToDouble(el.attribute("value", "0")); catcher.addDouble(buildPath(parentPath, key), v); } else if (type == "UnitFloat") { QString unit = el.attribute("unit", ""); double v = KisDomUtils::Private::stringToDouble(el.attribute("value", "0")); catcher.addUnitFloat(buildPath(parentPath, key), unit, v); } else if (type == "Text") { QString v = el.attribute("value", ""); catcher.addText(buildPath(parentPath, key), v); } else if (type == "Enum") { QString v = el.attribute("value", ""); QString typeId = el.attribute("typeId", ""); catcher.addEnum(buildPath(parentPath, key), typeId, v); } else if (type == "Integer") { int v = KisDomUtils::Private::stringToInt(el.attribute("value", "0")); catcher.addInteger(buildPath(parentPath, key), v); } else if (type == "Boolean") { int v = KisDomUtils::Private::stringToInt(el.attribute("value", "0")); catcher.addBoolean(buildPath(parentPath, key), v); } else { qWarning() << "WARNING: XML (ASL) Unknown element type:" << type << ppVar(parentPath) << ppVar(key); } } } // namespace void KisAslXmlParser::parseXML(const QDomDocument &doc, KisAslObjectCatcher &catcher) { QDomElement root = doc.documentElement(); if (root.tagName() != "asl") { return; } QDomNode child = root.firstChild(); while (!child.isNull()) { Private::parseElement(child.toElement(), "", catcher); child = child.nextSibling(); } } diff --git a/krita/libpsd/asl/kis_asl_xml_writer.cpp b/krita/libpsd/asl/kis_asl_xml_writer.cpp index 57b8e02082..44296edc82 100644 --- a/krita/libpsd/asl/kis_asl_xml_writer.cpp +++ b/krita/libpsd/asl/kis_asl_xml_writer.cpp @@ -1,397 +1,397 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_asl_xml_writer.h" #include #include #include #include #include #include #include #include #include #include "kis_dom_utils.h" #include "kis_asl_writer_utils.h" struct KisAslXmlWriter::Private { QDomDocument document; QDomElement currentElement; }; KisAslXmlWriter::KisAslXmlWriter() : m_d(new Private) { QDomElement el = m_d->document.createElement("asl"); m_d->document.appendChild(el); m_d->currentElement = el; } KisAslXmlWriter::~KisAslXmlWriter() { } QDomDocument KisAslXmlWriter::document() const { if (m_d->document.documentElement() != m_d->currentElement) { qWarning() << "KisAslXmlWriter::document(): unbalanced enter/leave descriptor/array"; } return m_d->document; } void KisAslXmlWriter::enterDescriptor(const QString &key, const QString &name, const QString &classId) { QDomElement el = m_d->document.createElement("node"); if (!key.isEmpty()) { el.setAttribute("key", key); } el.setAttribute("type", "Descriptor"); el.setAttribute("name", name); el.setAttribute("classId", classId); m_d->currentElement.appendChild(el); m_d->currentElement = el; } void KisAslXmlWriter::leaveDescriptor() { if (!m_d->currentElement.parentNode().toElement().isNull()) { m_d->currentElement = m_d->currentElement.parentNode().toElement(); } else { qWarning() << "KisAslXmlWriter::leaveDescriptor(): unbalanced enter/leave descriptor"; } } void KisAslXmlWriter::enterList(const QString &key) { QDomElement el = m_d->document.createElement("node"); if (!key.isEmpty()) { el.setAttribute("key", key); } el.setAttribute("type", "List"); m_d->currentElement.appendChild(el); m_d->currentElement = el; } void KisAslXmlWriter::leaveList() { if (!m_d->currentElement.parentNode().toElement().isNull()) { m_d->currentElement = m_d->currentElement.parentNode().toElement(); } else { qWarning() << "KisAslXmlWriter::leaveList(): unbalanced enter/leave list"; } } void KisAslXmlWriter::writeDouble(const QString &key, double value) { QDomElement el = m_d->document.createElement("node"); if (!key.isEmpty()) { el.setAttribute("key", key); } el.setAttribute("type", "Double"); el.setAttribute("value", KisDomUtils::Private::numberToString(value)); m_d->currentElement.appendChild(el); } void KisAslXmlWriter::writeInteger(const QString &key, int value) { QDomElement el = m_d->document.createElement("node"); if (!key.isEmpty()) { el.setAttribute("key", key); } el.setAttribute("type", "Integer"); el.setAttribute("value", KisDomUtils::Private::numberToString(value)); m_d->currentElement.appendChild(el); } void KisAslXmlWriter::writeEnum(const QString &key, const QString &typeId, const QString &value) { QDomElement el = m_d->document.createElement("node"); if (!key.isEmpty()) { el.setAttribute("key", key); } el.setAttribute("type", "Enum"); el.setAttribute("typeId", typeId); el.setAttribute("value", value); m_d->currentElement.appendChild(el); } void KisAslXmlWriter::writeUnitFloat(const QString &key, const QString &unit, double value) { QDomElement el = m_d->document.createElement("node"); if (!key.isEmpty()) { el.setAttribute("key", key); } el.setAttribute("type", "UnitFloat"); el.setAttribute("unit", unit); el.setAttribute("value", KisDomUtils::Private::numberToString(value)); m_d->currentElement.appendChild(el); } void KisAslXmlWriter::writeText(const QString &key, const QString &value) { QDomElement el = m_d->document.createElement("node"); if (!key.isEmpty()) { el.setAttribute("key", key); } el.setAttribute("type", "Text"); el.setAttribute("value", value); m_d->currentElement.appendChild(el); } void KisAslXmlWriter::writeBoolean(const QString &key, bool value) { QDomElement el = m_d->document.createElement("node"); if (!key.isEmpty()) { el.setAttribute("key", key); } el.setAttribute("type", "Boolean"); el.setAttribute("value", KisDomUtils::Private::numberToString(value)); m_d->currentElement.appendChild(el); } void KisAslXmlWriter::writeColor(const QString &key, const QColor &value) { enterDescriptor(key, "", "RGBC"); writeDouble("Rd ", value.red()); writeDouble("Grn ", value.green()); writeDouble("Bl ", value.blue()); leaveDescriptor(); } void KisAslXmlWriter::writePoint(const QString &key, const QPointF &value) { enterDescriptor(key, "", "CrPt"); writeDouble("Hrzn", value.x()); writeDouble("Vrtc", value.y()); leaveDescriptor(); } void KisAslXmlWriter::writePhasePoint(const QString &key, const QPointF &value) { enterDescriptor(key, "", "Pnt "); writeDouble("Hrzn", value.x()); writeDouble("Vrtc", value.y()); leaveDescriptor(); } void KisAslXmlWriter::writeOffsetPoint(const QString &key, const QPointF &value) { enterDescriptor(key, "", "Pnt "); writeUnitFloat("Hrzn", "#Prc", value.x()); writeUnitFloat("Vrtc", "#Prc", value.y()); leaveDescriptor(); } void KisAslXmlWriter::writeCurve(const QString &key, const QString &name, const QVector &points) { enterDescriptor(key, "", "ShpC"); writeText("Nm ", name); enterList("Crv "); foreach (const QPointF &pt, points) { writePoint("", pt); } leaveList(); leaveDescriptor(); } QString KisAslXmlWriter::writePattern(const QString &key, const KoPattern *pattern) { enterDescriptor(key, "", "KisPattern"); writeText("Nm ", pattern->name()); QString uuid = KisAslWriterUtils::getPatternUuidLazy(pattern); writeText("Idnt", uuid); // Write pattern data QBuffer buffer; buffer.open(QIODevice::WriteOnly); - pattern->saveToDevice(&buffer); + pattern->savePatToDevice(&buffer); QDomCDATASection dataSection = m_d->document.createCDATASection(qCompress(buffer.buffer()).toBase64()); QDomElement dataElement = m_d->document.createElement("node"); dataElement.setAttribute("type", "KisPatternData"); dataElement.setAttribute("key", "Data"); dataElement.appendChild(dataSection); m_d->currentElement.appendChild(dataElement); leaveDescriptor(); return uuid; } void KisAslXmlWriter::writePatternRef(const QString &key, const KoPattern *pattern, const QString &uuid) { enterDescriptor(key, "", "Ptrn"); writeText("Nm ", pattern->name()); writeText("Idnt", uuid); leaveDescriptor(); } void KisAslXmlWriter::writeGradientImpl(const QString &key, const QString &name, QVector colors, QVector transparencies, QVector positions, QVector middleOffsets) { enterDescriptor(key, "Gradient", "Grdn"); writeText("Nm ", name); writeEnum("GrdF", "GrdF", "CstS"); writeDouble("Intr", 4096); enterList("Clrs"); for (int i = 0; i < colors.size(); i++) { enterDescriptor("", "", "Clrt"); writeColor("Clr ", colors[i]); writeEnum("Type", "Clry", "UsrS"); // NOTE: we do not support BG/FG color tags writeInteger("Lctn", positions[i] * 4096.0); writeInteger("Mdpn", middleOffsets[i] * 100.0); leaveDescriptor(); }; leaveList(); enterList("Trns"); for (int i = 0; i < colors.size(); i++) { enterDescriptor("", "", "TrnS"); writeUnitFloat("Opct", "#Prc", transparencies[i] * 100.0); writeInteger("Lctn", positions[i] * 4096.0); writeInteger("Mdpn", middleOffsets[i] * 100.0); leaveDescriptor(); }; leaveList(); leaveDescriptor(); } void KisAslXmlWriter::writeSegmentGradient(const QString &key, const KoSegmentGradient *gradient) { const QList&segments = gradient->segments(); QVector colors; QVector transparencies; QVector positions; QVector middleOffsets; foreach (const KoGradientSegment *seg, segments) { const qreal start = seg->startOffset(); const qreal end = seg->endOffset(); const qreal mid = (end - start) > DBL_EPSILON ? (seg->middleOffset() - start) / (end - start) : 0.5; QColor color = seg->startColor().toQColor(); qreal transparency = color.alphaF(); color.setAlphaF(1.0); colors << color; transparencies << transparency; positions << start; middleOffsets << mid; } // last segment { const KoGradientSegment *lastSeg = segments.last(); QColor color = lastSeg->endColor().toQColor(); qreal transparency = color.alphaF(); color.setAlphaF(1.0); colors << color; transparencies << transparency; positions << lastSeg->endOffset(); middleOffsets << 0.5; } writeGradientImpl(key, gradient->name(), colors, transparencies, positions, middleOffsets); } void KisAslXmlWriter::writeStopGradient(const QString &key, const KoStopGradient *gradient) { QVector colors; QVector transparencies; QVector positions; QVector middleOffsets; foreach (const KoGradientStop &stop, gradient->stops()) { QColor color = stop.second.toQColor(); qreal transparency = color.alphaF(); color.setAlphaF(1.0); colors << color; transparencies << transparency; positions << stop.first; middleOffsets << 0.5; } writeGradientImpl(key, gradient->name(), colors, transparencies, positions, middleOffsets); } diff --git a/libs/pigment/resources/KoPattern.cpp b/libs/pigment/resources/KoPattern.cpp index 0e3ec1634a..1c1f521d55 100644 --- a/libs/pigment/resources/KoPattern.cpp +++ b/libs/pigment/resources/KoPattern.cpp @@ -1,402 +1,412 @@ /* This file is part of the KDE project Copyright (c) 2000 Matthias Elter Copyright (c) 2004 Boudewijn Rempt This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "KoPattern.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { struct GimpPatternHeader { quint32 header_size; /* header_size = sizeof (PatternHeader) + brush name */ quint32 version; /* pattern file version # */ quint32 width; /* width of pattern */ quint32 height; /* height of pattern */ quint32 bytes; /* depth of pattern in bytes : 1, 2, 3 or 4*/ quint32 magic_number; /* GIMP brush magic number */ }; // Yes! This is _NOT_ what my pat.txt file says. It's really not 'GIMP', but 'GPAT' quint32 const GimpPatternMagic = (('G' << 24) + ('P' << 16) + ('A' << 8) + ('T' << 0)); } KoPattern::KoPattern(const QString& file) : KoResource(file) { } KoPattern::KoPattern(const QImage &image, const QString &name, const QString &folderName) : KoResource(QString()) { setPatternImage(image); setName(name); QFileInfo fileInfo(folderName + QDir::separator() + name + defaultFileExtension()); int i = 1; while (fileInfo.exists()) { fileInfo.setFile(folderName + QDir::separator() + name + QString("%1").arg(i) + defaultFileExtension()); i++; } setFilename(fileInfo.filePath()); } KoPattern::~KoPattern() { } bool KoPattern::load() { QFile file(filename()); if (file.size() == 0) return false; bool result; if (!file.open(QIODevice::ReadOnly)) { kWarning() << "Can't open file " << filename(); return false; } result = loadFromDevice(&file); file.close(); return result; } +bool KoPattern::loadPatFromDevice(QIODevice *dev) +{ + QByteArray data = dev->readAll(); + return init(data); +} + +bool KoPattern::savePatToDevice(QIODevice* dev) const +{ + // Header: header_size (24+name length),version,width,height,colordepth of brush,magic,name + // depth: 1 = greyscale, 2 = greyscale + A, 3 = RGB, 4 = RGBA + // magic = "GPAT", as a single uint32, the docs are wrong here! + // name is UTF-8 (\0-terminated! The docs say nothing about this!) + // _All_ data in network order, it seems! (not mentioned in gimp-2.2.8/devel-docs/pat.txt!!) + // We only save RGBA at the moment + // Version is 1 for now... + + GimpPatternHeader ph; + QByteArray utf8Name = name().toUtf8(); + char const* name = utf8Name.data(); + int nameLength = qstrlen(name); + + ph.header_size = htonl(sizeof(GimpPatternHeader) + nameLength + 1); // trailing 0 + ph.version = htonl(1); + ph.width = htonl(width()); + ph.height = htonl(height()); + ph.bytes = htonl(4); + ph.magic_number = htonl(GimpPatternMagic); + + QByteArray bytes = QByteArray::fromRawData(reinterpret_cast(&ph), sizeof(GimpPatternHeader)); + int wrote = dev->write(bytes); + bytes.clear(); + + if (wrote == -1) + return false; + + wrote = dev->write(name, nameLength + 1); // Trailing 0 apparantly! + if (wrote == -1) + return false; + + int k = 0; + bytes.resize(width() * height() * 4); + for (qint32 y = 0; y < height(); ++y) { + for (qint32 x = 0; x < width(); ++x) { + // RGBA only + QRgb pixel = m_pattern.pixel(x, y); + bytes[k++] = static_cast(qRed(pixel)); + bytes[k++] = static_cast(qGreen(pixel)); + bytes[k++] = static_cast(qBlue(pixel)); + bytes[k++] = static_cast(qAlpha(pixel)); + } + } + + wrote = dev->write(bytes); + if (wrote == -1) + return false; + + return true; +} + bool KoPattern::loadFromDevice(QIODevice *dev) { QString fileExtension; int index = filename().lastIndexOf('.'); if (index != -1) fileExtension = filename().mid(index + 1).toLower(); bool result; if (fileExtension == "pat") { - QByteArray data = dev->readAll(); - result = init(data); + result = loadPatFromDevice(dev); } else { QImage image; result = image.load(dev, fileExtension.toUpper().toLatin1()); setPatternImage(image); } return result; } bool KoPattern::save() { QFile file(filename()); file.open(QIODevice::WriteOnly | QIODevice::Truncate); bool res = saveToDevice(&file); file.close(); return res; } bool KoPattern::saveToDevice(QIODevice *dev) const { QString fileExtension; int index = filename().lastIndexOf('.'); if (index != -1) fileExtension = filename().mid(index + 1).toLower(); if (fileExtension == "pat") { - - // Header: header_size (24+name length),version,width,height,colordepth of brush,magic,name - // depth: 1 = greyscale, 2 = greyscale + A, 3 = RGB, 4 = RGBA - // magic = "GPAT", as a single uint32, the docs are wrong here! - // name is UTF-8 (\0-terminated! The docs say nothing about this!) - // _All_ data in network order, it seems! (not mentioned in gimp-2.2.8/devel-docs/pat.txt!!) - // We only save RGBA at the moment - // Version is 1 for now... - - GimpPatternHeader ph; - QByteArray utf8Name = name().toUtf8(); - char const* name = utf8Name.data(); - int nameLength = qstrlen(name); - - ph.header_size = htonl(sizeof(GimpPatternHeader) + nameLength + 1); // trailing 0 - ph.version = htonl(1); - ph.width = htonl(width()); - ph.height = htonl(height()); - ph.bytes = htonl(4); - ph.magic_number = htonl(GimpPatternMagic); - - QByteArray bytes = QByteArray::fromRawData(reinterpret_cast(&ph), sizeof(GimpPatternHeader)); - int wrote = dev->write(bytes); - bytes.clear(); - - if (wrote == -1) - return false; - - wrote = dev->write(name, nameLength + 1); // Trailing 0 apparantly! - if (wrote == -1) - return false; - - int k = 0; - bytes.resize(width() * height() * 4); - for (qint32 y = 0; y < height(); ++y) { - for (qint32 x = 0; x < width(); ++x) { - // RGBA only - QRgb pixel = m_pattern.pixel(x, y); - bytes[k++] = static_cast(qRed(pixel)); - bytes[k++] = static_cast(qGreen(pixel)); - bytes[k++] = static_cast(qBlue(pixel)); - bytes[k++] = static_cast(qAlpha(pixel)); - } - } - - wrote = dev->write(bytes); - if (wrote == -1) - return false; - + return savePatToDevice(dev); } else { return m_pattern.save(dev, fileExtension.toUpper().toLatin1()); } return true; } bool KoPattern::init(QByteArray& bytes) { int dataSize = bytes.size(); const char* data = bytes.constData(); // load Gimp patterns GimpPatternHeader bh; qint32 k; char* name; if ((int)sizeof(GimpPatternHeader) > dataSize) { return false; } memcpy(&bh, data, sizeof(GimpPatternHeader)); bh.header_size = ntohl(bh.header_size); bh.version = ntohl(bh.version); bh.width = ntohl(bh.width); bh.height = ntohl(bh.height); bh.bytes = ntohl(bh.bytes); bh.magic_number = ntohl(bh.magic_number); if ((int)bh.header_size > dataSize || bh.header_size == 0) { return false; } int size = bh.header_size - sizeof(GimpPatternHeader); name = new char[size]; memcpy(name, data + sizeof(GimpPatternHeader), size); if (name[size - 1]) { delete[] name; return false; } // size -1 so we don't add the end 0 to the QString... setName(QString::fromLatin1(name, size -1)); delete[] name; if (bh.width == 0 || bh.height == 0) { return false; } QImage::Format imageFormat; if (bh.bytes == 1 || bh.bytes == 3) { imageFormat = QImage::Format_RGB32; } else { imageFormat = QImage::Format_ARGB32; } QImage pattern = QImage(bh.width, bh.height, imageFormat); if (pattern.isNull()) { return false; } k = bh.header_size; if (bh.bytes == 1) { // Grayscale qint32 val; for (quint32 y = 0; y < bh.height; ++y) { QRgb* pixels = reinterpret_cast( pattern.scanLine(y) ); for (quint32 x = 0; x < bh.width; ++x, ++k) { if (k > dataSize) { kWarning(30009) << "failed in gray"; return false; } val = data[k]; pixels[x] = qRgb(val, val, val); } } // It was grayscale, so make the pattern as small as possible // by converting it to Indexed8 pattern = pattern.convertToFormat(QImage::Format_Indexed8); } else if (bh.bytes == 2) { // Grayscale + A qint32 val; qint32 alpha; for (quint32 y = 0; y < bh.height; ++y) { QRgb* pixels = reinterpret_cast( pattern.scanLine(y) ); for (quint32 x = 0; x < bh.width; ++x, ++k) { if (k + 2 > dataSize) { kWarning(30009) << "failed in grayA"; return false; } val = data[k]; alpha = data[k++]; pixels[x] = qRgba(val, val, val, alpha); } } } else if (bh.bytes == 3) { // RGB without alpha for (quint32 y = 0; y < bh.height; ++y) { QRgb* pixels = reinterpret_cast( pattern.scanLine(y) ); for (quint32 x = 0; x < bh.width; ++x) { if (k + 3 > dataSize) { kWarning(30009) << "failed in RGB"; return false; } pixels[x] = qRgb(data[k], data[k + 1], data[k + 2]); k += 3; } } } else if (bh.bytes == 4) { // Has alpha for (quint32 y = 0; y < bh.height; ++y) { QRgb* pixels = reinterpret_cast( pattern.scanLine(y) ); for (quint32 x = 0; x < bh.width; ++x) { if (k + 4 > dataSize) { kWarning(30009) << "failed in RGBA"; return false; } pixels[x] = qRgba(data[k], data[k + 1], data[k + 2], data[k + 3]); k += 4; } } } else { return false; } if (pattern.isNull()) { return false; } setPatternImage(pattern); setValid(true); return true; } qint32 KoPattern::width() const { return m_pattern.width(); } qint32 KoPattern::height() const { return m_pattern.height(); } void KoPattern::setPatternImage(const QImage& image) { m_pattern = image; setImage(image); setValid(true); } KoPattern& KoPattern::operator=(const KoPattern & pattern) { setFilename(pattern.filename()); setPatternImage(pattern.pattern()); setValid(true); return *this; } QString KoPattern::defaultFileExtension() const { return QString(".pat"); } KoPattern* KoPattern::clone() const { KoPattern* pat = new KoPattern(filename()); pat->setPatternImage(pattern()); pat->setName(name()); return pat; } QImage KoPattern::pattern() const { return m_pattern; } QByteArray KoPattern::generateMD5() const { if (!pattern().isNull()) { QImage im = m_pattern.convertToFormat(QImage::Format_ARGB32); #if QT_VERSION >= 0x040700 QByteArray ba = QByteArray::fromRawData((const char*)im.constBits(), im.byteCount()); #else QByteArray ba = QByteArray::fromRawData((const char*)im.bits(), im.byteCount()); #endif QCryptographicHash md5(QCryptographicHash::Md5); md5.addData(ba); return md5.result(); } return QByteArray(); } diff --git a/libs/pigment/resources/KoPattern.h b/libs/pigment/resources/KoPattern.h index 105cc6b56c..31acba45d1 100644 --- a/libs/pigment/resources/KoPattern.h +++ b/libs/pigment/resources/KoPattern.h @@ -1,81 +1,84 @@ /* Copyright (c) 2000 Matthias Elter This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef KOPATTERN_H #define KOPATTERN_H #include "KoResource.h" #include #include /// Write API docs here class PIGMENTCMS_EXPORT KoPattern : public KoResource { public: /** * Creates a new KoPattern object using @p filename. No file is opened * in the constructor, you have to call load. * * @param filename the file name to save and load from. */ explicit KoPattern(const QString &filename); KoPattern(const QImage &image, const QString &name, const QString &folderName); virtual ~KoPattern(); public: virtual bool load(); virtual bool loadFromDevice(QIODevice *dev); virtual bool save(); virtual bool saveToDevice(QIODevice* dev) const; + bool loadPatFromDevice(QIODevice *dev); + bool savePatToDevice(QIODevice* dev) const; + qint32 width() const; qint32 height() const; QString defaultFileExtension() const; KoPattern& operator=(const KoPattern& pattern); KoPattern* clone() const; /** * @brief pattern the actual pattern image * @return a valid QImage. There are no guarantees to the image format. */ QImage pattern() const; protected: virtual QByteArray generateMD5() const; private: bool init(QByteArray& data); void setPatternImage(const QImage& image); private: QImage m_pattern; mutable QByteArray m_md5; }; Q_DECLARE_METATYPE(KoPattern*) #endif // KOPATTERN_H