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 }