File indexing completed on 2025-01-26 04:07:39

0001 /*
0002  *  SPDX-FileCopyrightText: 2015 Dmitry Kazakov <dimula73@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "kis_asl_xml_writer.h"
0008 
0009 #include <QBuffer>
0010 #include <QColor>
0011 #include <QDomDocument>
0012 #include <QPointF>
0013 #include <QUuid>
0014 
0015 #include <resources/KoPattern.h>
0016 #include <resources/KoSegmentGradient.h>
0017 #include <resources/KoStopGradient.h>
0018 
0019 #include <cfloat>
0020 
0021 #include "kis_asl_writer_utils.h"
0022 #include "kis_dom_utils.h"
0023 
0024 struct KisAslXmlWriter::Private {
0025     QDomDocument document;
0026     QDomElement currentElement;
0027 };
0028 
0029 KisAslXmlWriter::KisAslXmlWriter()
0030     : m_d(new Private)
0031 {
0032     QDomElement el = m_d->document.createElement("asl");
0033     m_d->document.appendChild(el);
0034     m_d->currentElement = el;
0035 }
0036 
0037 KisAslXmlWriter::~KisAslXmlWriter()
0038 {
0039 }
0040 
0041 QDomDocument KisAslXmlWriter::document() const
0042 {
0043     if (m_d->document.documentElement() != m_d->currentElement) {
0044         warnKrita << "KisAslXmlWriter::document(): unbalanced enter/leave descriptor/array";
0045     }
0046 
0047     return m_d->document;
0048 }
0049 
0050 void KisAslXmlWriter::enterDescriptor(const QString &key, const QString &name, const QString &classId)
0051 {
0052     QDomElement el = m_d->document.createElement("node");
0053 
0054     if (!key.isEmpty()) {
0055         el.setAttribute("key", key);
0056     }
0057 
0058     el.setAttribute("type", "Descriptor");
0059     el.setAttribute("name", name);
0060     el.setAttribute("classId", classId);
0061 
0062     m_d->currentElement.appendChild(el);
0063     m_d->currentElement = el;
0064 }
0065 
0066 void KisAslXmlWriter::leaveDescriptor()
0067 {
0068     if (!m_d->currentElement.parentNode().toElement().isNull()) {
0069         m_d->currentElement = m_d->currentElement.parentNode().toElement();
0070     } else {
0071         warnKrita << "KisAslXmlWriter::leaveDescriptor(): unbalanced enter/leave descriptor";
0072     }
0073 }
0074 
0075 void KisAslXmlWriter::enterList(const QString &key)
0076 {
0077     QDomElement el = m_d->document.createElement("node");
0078 
0079     if (!key.isEmpty()) {
0080         el.setAttribute("key", key);
0081     }
0082 
0083     el.setAttribute("type", "List");
0084 
0085     m_d->currentElement.appendChild(el);
0086     m_d->currentElement = el;
0087 }
0088 
0089 void KisAslXmlWriter::leaveList()
0090 {
0091     if (!m_d->currentElement.parentNode().toElement().isNull()) {
0092         m_d->currentElement = m_d->currentElement.parentNode().toElement();
0093     } else {
0094         warnKrita << "KisAslXmlWriter::leaveList(): unbalanced enter/leave list";
0095     }
0096 }
0097 
0098 void KisAslXmlWriter::writeDouble(const QString &key, double value)
0099 {
0100     QDomElement el = m_d->document.createElement("node");
0101 
0102     if (!key.isEmpty()) {
0103         el.setAttribute("key", key);
0104     }
0105 
0106     el.setAttribute("type", "Double");
0107     el.setAttribute("value", KisDomUtils::toString(value));
0108 
0109     m_d->currentElement.appendChild(el);
0110 }
0111 
0112 void KisAslXmlWriter::writeInteger(const QString &key, int value)
0113 {
0114     QDomElement el = m_d->document.createElement("node");
0115 
0116     if (!key.isEmpty()) {
0117         el.setAttribute("key", key);
0118     }
0119 
0120     el.setAttribute("type", "Integer");
0121     el.setAttribute("value", KisDomUtils::toString(value));
0122 
0123     m_d->currentElement.appendChild(el);
0124 }
0125 
0126 void KisAslXmlWriter::writeEnum(const QString &key, const QString &typeId, const QString &value)
0127 {
0128     QDomElement el = m_d->document.createElement("node");
0129 
0130     if (!key.isEmpty()) {
0131         el.setAttribute("key", key);
0132     }
0133 
0134     el.setAttribute("type", "Enum");
0135     el.setAttribute("typeId", typeId);
0136     el.setAttribute("value", value);
0137 
0138     m_d->currentElement.appendChild(el);
0139 }
0140 
0141 void KisAslXmlWriter::writeUnitFloat(const QString &key, const QString &unit, double value)
0142 {
0143     QDomElement el = m_d->document.createElement("node");
0144 
0145     if (!key.isEmpty()) {
0146         el.setAttribute("key", key);
0147     }
0148 
0149     el.setAttribute("type", "UnitFloat");
0150     el.setAttribute("unit", unit);
0151     el.setAttribute("value", KisDomUtils::toString(value));
0152 
0153     m_d->currentElement.appendChild(el);
0154 }
0155 
0156 void KisAslXmlWriter::writeText(const QString &key, const QString &value)
0157 {
0158     QDomElement el = m_d->document.createElement("node");
0159 
0160     if (!key.isEmpty()) {
0161         el.setAttribute("key", key);
0162     }
0163 
0164     el.setAttribute("type", "Text");
0165     el.setAttribute("value", value);
0166 
0167     m_d->currentElement.appendChild(el);
0168 }
0169 
0170 void KisAslXmlWriter::writeBoolean(const QString &key, bool value)
0171 {
0172     QDomElement el = m_d->document.createElement("node");
0173 
0174     if (!key.isEmpty()) {
0175         el.setAttribute("key", key);
0176     }
0177 
0178     el.setAttribute("type", "Boolean");
0179     el.setAttribute("value", KisDomUtils::toString(value));
0180 
0181     m_d->currentElement.appendChild(el);
0182 }
0183 
0184 void KisAslXmlWriter::writeColor(const QString &key, const KoColor &value)
0185 {
0186     QDomDocument doc;
0187     QDomElement el = doc.createElement("color");
0188     value.toXML(doc, el);
0189     QDomElement colorEl = el.firstChildElement();
0190     if (value.colorSpace()->colorModelId() == RGBAColorModelID) {
0191         enterDescriptor(key, "", "RGBC");
0192 
0193         double v = qBound(0.0, KisDomUtils::toDouble(colorEl.attribute("r", "0.0")) * 255.0, 255.0);
0194         writeDouble("Rd  ", v);
0195         v = qBound(0.0, KisDomUtils::toDouble(colorEl.attribute("g", "0.0")) * 255.0, 255.0);
0196         writeDouble("Grn ", v);
0197         v = qBound(0.0, KisDomUtils::toDouble(colorEl.attribute("b", "0.0")) * 255.0, 255.0);
0198         writeDouble("Bl  ", v);
0199     } else if (value.colorSpace()->colorModelId() == CMYKAColorModelID) {
0200         enterDescriptor(key, "", "CMYC");
0201 
0202         double v = qBound(0.0, KisDomUtils::toDouble(colorEl.attribute("c", "0.0")) * 100.0, 100.0);
0203         writeDouble("Cyn ", v);
0204         v = qBound(0.0, KisDomUtils::toDouble(colorEl.attribute("m", "0.0")) * 100.0, 100.0);
0205         writeDouble("Mgnt", v);
0206         v = qBound(0.0, KisDomUtils::toDouble(colorEl.attribute("y", "0.0")) * 100.0, 100.0);
0207         writeDouble("Ylw ", v);
0208         v = qBound(0.0, KisDomUtils::toDouble(colorEl.attribute("k", "0.0")) * 100.0, 100.0);
0209         writeDouble("Blck", v);
0210     } else if (value.colorSpace()->colorModelId() == LABAColorModelID) {
0211         enterDescriptor(key, "", "LbCl");
0212 
0213         double v = KisDomUtils::toDouble(colorEl.attribute("L", "0.0"));
0214         writeDouble("Lmnc", v);
0215         v = KisDomUtils::toDouble(colorEl.attribute("a", "0.0"));
0216         writeDouble("A   ", v);
0217         v = KisDomUtils::toDouble(colorEl.attribute("b", "0.0"));
0218         writeDouble("B   ", v);
0219     } else if (value.colorSpace()->colorModelId() == GrayAColorModelID) {
0220         enterDescriptor(key, "", "Grsc");
0221 
0222         double v = qBound(0.0, KisDomUtils::toDouble(colorEl.attribute("g", "0.0")) * 100.0, 100.0);
0223         writeDouble("Gry ", v);
0224     } else { // default to sRGB
0225         enterDescriptor(key, "", "RGBC");
0226 
0227         writeDouble("Rd  ", value.toQColor().red());
0228         writeDouble("Grn ", value.toQColor().green());
0229         writeDouble("Bl  ", value.toQColor().blue());
0230     }
0231     if (value.metadata().keys().contains("psdSpotBook")) {
0232         QVariant v;
0233         v = value.metadata().value("spotName");
0234         if (v.isValid()) {
0235             writeText("Nm  ", v.toString());
0236         }
0237         v = value.metadata().value("psdSpotBook");
0238         if (v.isValid()) {
0239             writeText("Bk  ", v.toString());
0240         }
0241         bool ok;
0242         v = value.metadata().value("psdSpotBookId");
0243         int bookid = v.toInt(&ok);
0244         if (ok) {
0245             writeInteger("bookID", bookid);
0246         }
0247     }
0248 
0249     leaveDescriptor();
0250 }
0251 
0252 void KisAslXmlWriter::writePoint(const QString &key, const QPointF &value)
0253 {
0254     enterDescriptor(key, "", "CrPt");
0255 
0256     writeDouble("Hrzn", value.x());
0257     writeDouble("Vrtc", value.y());
0258 
0259     leaveDescriptor();
0260 }
0261 
0262 void KisAslXmlWriter::writePhasePoint(const QString &key, const QPointF &value)
0263 {
0264     enterDescriptor(key, "", "Pnt ");
0265 
0266     writeDouble("Hrzn", value.x());
0267     writeDouble("Vrtc", value.y());
0268 
0269     leaveDescriptor();
0270 }
0271 
0272 void KisAslXmlWriter::writeOffsetPoint(const QString &key, const QPointF &value)
0273 {
0274     enterDescriptor(key, "", "Pnt ");
0275 
0276     writeUnitFloat("Hrzn", "#Prc", value.x());
0277     writeUnitFloat("Vrtc", "#Prc", value.y());
0278 
0279     leaveDescriptor();
0280 }
0281 
0282 void KisAslXmlWriter::writeCurve(const QString &key, const QString &name, const QVector<QPointF> &points)
0283 {
0284     enterDescriptor(key, "", "ShpC");
0285 
0286     writeText("Nm  ", name);
0287 
0288     enterList("Crv ");
0289 
0290     Q_FOREACH (const QPointF &pt, points) {
0291         writePoint("", pt);
0292     }
0293 
0294     leaveList();
0295     leaveDescriptor();
0296 }
0297 
0298 QString KisAslXmlWriter::writePattern(const QString &key, const KoPatternSP pattern)
0299 {
0300     enterDescriptor(key, "", "KisPattern");
0301 
0302     writeText("Nm  ", pattern->name());
0303 
0304     QString uuid = KisAslWriterUtils::getPatternUuidLazy(pattern);
0305     writeText("Idnt", uuid);
0306 
0307     // Write pattern data
0308 
0309     QBuffer buffer;
0310     buffer.open(QIODevice::WriteOnly);
0311     pattern->savePatToDevice(&buffer);
0312 
0313     QDomCDATASection dataSection = m_d->document.createCDATASection(qCompress(buffer.buffer()).toBase64());
0314 
0315     QDomElement dataElement = m_d->document.createElement("node");
0316     dataElement.setAttribute("type", "KisPatternData");
0317     dataElement.setAttribute("key", "Data");
0318     dataElement.appendChild(dataSection);
0319 
0320     m_d->currentElement.appendChild(dataElement);
0321 
0322     leaveDescriptor();
0323 
0324     return uuid;
0325 }
0326 
0327 void KisAslXmlWriter::writePatternRef(const QString &key, const KoPatternSP pattern, const QString &uuid)
0328 {
0329     enterDescriptor(key, "", "Ptrn");
0330 
0331     writeText("Nm  ", pattern->name());
0332     writeText("Idnt", uuid);
0333 
0334     leaveDescriptor();
0335 }
0336 
0337 void KisAslXmlWriter::writeGradientImpl(const QString &key,
0338                                         const QString &name,
0339                                         QVector<KoColor> colors,
0340                                         QVector<qreal> transparencies,
0341                                         QVector<qreal> positions,
0342                                         QVector<QString> types,
0343                                         QVector<qreal> middleOffsets)
0344 {
0345     enterDescriptor(key, "Gradient", "Grdn");
0346 
0347     writeText("Nm  ", name);
0348     writeEnum("GrdF", "GrdF", "CstS");
0349     writeDouble("Intr", 4096);
0350 
0351     enterList("Clrs");
0352 
0353     for (int i = 0; i < colors.size(); i++) {
0354         enterDescriptor("", "", "Clrt");
0355 
0356         writeColor("Clr ", colors[i]);
0357         writeEnum("Type", "Clry", types[i]);
0358         writeInteger("Lctn", positions[i] * 4096.0);
0359         writeInteger("Mdpn", middleOffsets[i] * 100.0);
0360 
0361         leaveDescriptor();
0362     };
0363 
0364     leaveList();
0365 
0366     enterList("Trns");
0367 
0368     for (int i = 0; i < colors.size(); i++) {
0369         enterDescriptor("", "", "TrnS");
0370         writeUnitFloat("Opct", "#Prc", transparencies[i] * 100.0);
0371         writeInteger("Lctn", positions[i] * 4096.0);
0372         writeInteger("Mdpn", middleOffsets[i] * 100.0);
0373         leaveDescriptor();
0374     };
0375 
0376     leaveList();
0377 
0378     leaveDescriptor();
0379 }
0380 
0381 QString KisAslXmlWriter::getSegmentEndpointTypeString(KoGradientSegmentEndpointType segtype)
0382 {
0383     switch (segtype) {
0384     case COLOR_ENDPOINT:
0385         return "UsrS";
0386         break;
0387     case FOREGROUND_ENDPOINT:
0388     case FOREGROUND_TRANSPARENT_ENDPOINT:
0389         return "FrgC";
0390         break;
0391     case BACKGROUND_ENDPOINT:
0392     case BACKGROUND_TRANSPARENT_ENDPOINT:
0393         return "BckC";
0394         break;
0395     default:
0396         return "UsrS";
0397     }
0398 }
0399 
0400 void KisAslXmlWriter::writeSegmentGradient(const QString &key, const KoSegmentGradient &gradient)
0401 {
0402     const QList<KoGradientSegment *> &segments = gradient.segments();
0403     KIS_SAFE_ASSERT_RECOVER_RETURN(!segments.isEmpty());
0404 
0405     QVector<KoColor> colors;
0406     QVector<qreal> transparencies;
0407     QVector<qreal> positions;
0408     QVector<QString> types;
0409     QVector<qreal> middleOffsets;
0410 
0411     Q_FOREACH (const KoGradientSegment *seg, segments) {
0412         const qreal start = seg->startOffset();
0413         const qreal end = seg->endOffset();
0414         const qreal mid = (end - start) > DBL_EPSILON ? (seg->middleOffset() - start) / (end - start) : 0.5;
0415 
0416         KoColor color = seg->startColor();
0417         qreal transparency = color.opacityF();
0418         color.setOpacity(1.0);
0419 
0420         QString type = getSegmentEndpointTypeString(seg->startType());
0421 
0422         colors << color;
0423         transparencies << transparency;
0424         positions << start;
0425         types << type;
0426         middleOffsets << mid;
0427     }
0428 
0429     // last segment
0430 
0431     if (!segments.isEmpty()) {
0432         const KoGradientSegment *lastSeg = segments.last();
0433 
0434         KoColor color = lastSeg->endColor();
0435         qreal transparency = color.opacityF();
0436         color.setOpacity(1.0);
0437         QString type = getSegmentEndpointTypeString(lastSeg->endType());
0438 
0439         colors << color;
0440         transparencies << transparency;
0441         positions << lastSeg->endOffset();
0442         types << type;
0443         middleOffsets << 0.5;
0444     }
0445 
0446     writeGradientImpl(key, gradient.name(), colors, transparencies, positions, types, middleOffsets);
0447 }
0448 
0449 void KisAslXmlWriter::writeStopGradient(const QString &key, const KoStopGradient &gradient)
0450 {
0451     QVector<KoColor> colors;
0452     QVector<qreal> transparencies;
0453     QVector<qreal> positions;
0454     QVector<QString> types;
0455     QVector<qreal> middleOffsets;
0456 
0457     Q_FOREACH (const KoGradientStop &stop, gradient.stops()) {
0458         KoColor color = stop.color;
0459         qreal transparency = color.opacityF();
0460         color.setOpacity(1.0);
0461 
0462         QString type;
0463         switch (stop.type) {
0464         case COLORSTOP:
0465             type = "UsrS";
0466             break;
0467         case FOREGROUNDSTOP:
0468             type = "FrgC";
0469             break;
0470         case BACKGROUNDSTOP:
0471             type = "BckC";
0472             break;
0473         }
0474 
0475         colors << color;
0476         transparencies << transparency;
0477         positions << stop.position;
0478         types << type;
0479         middleOffsets << 0.5;
0480     }
0481 
0482     writeGradientImpl(key, gradient.name(), colors, transparencies, positions, types, middleOffsets);
0483 }