File indexing completed on 2024-06-09 04:23:35
0001 /* 0002 SPDX-FileCopyrightText: 2005 Tim Beaulen <tbscope@gmail.org> 0003 SPDX-FileCopyrightText: 2007 Jan Hambrecht <jaham@gmx.net> 0004 SPDX-FileCopyrightText: 2007 Sven Langkamp <sven.langkamp@gmail.com> 0005 SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@amyspark.me> 0006 0007 SPDX-License-Identifier: LGPL-2.1-or-later 0008 */ 0009 0010 #include <resources/KoStopGradient.h> 0011 0012 #include <array> 0013 #include <cfloat> 0014 #include <cmath> 0015 0016 #include <QColor> 0017 #include <QFile> 0018 #include <QDomDocument> 0019 #include <QDomElement> 0020 #include <QBuffer> 0021 0022 #include <klocalizedstring.h> 0023 #include <DebugPigment.h> 0024 0025 #include "KoColorSpaceRegistry.h" 0026 #include <KoColorSpaceEngine.h> 0027 #include <KoColorProfile.h> 0028 #include "KoMixColorsOp.h" 0029 0030 #include "kis_dom_utils.h" 0031 0032 #include <KoColorModelStandardIds.h> 0033 #include <KoXmlNS.h> 0034 0035 #include <KoCanvasResourcesIds.h> 0036 #include <KoCanvasResourcesInterface.h> 0037 0038 0039 KoStopGradient::KoStopGradient(const QString& filename) 0040 : KoAbstractGradient(filename) 0041 { 0042 } 0043 0044 KoStopGradient::~KoStopGradient() 0045 { 0046 } 0047 0048 KoStopGradient::KoStopGradient(const KoStopGradient &rhs) 0049 : KoAbstractGradient(rhs) 0050 , m_stops(rhs.m_stops) 0051 , m_start(rhs.m_start) 0052 , m_stop(rhs.m_stop) 0053 , m_focalPoint(rhs.m_focalPoint) 0054 { 0055 } 0056 0057 bool KoStopGradient::operator==(const KoStopGradient& rhs) const 0058 { 0059 return 0060 *colorSpace() == *rhs.colorSpace() && 0061 spread() == rhs.spread() && 0062 type() == rhs.type() && 0063 m_start == rhs.m_start && 0064 m_stop == rhs.m_stop && 0065 m_focalPoint == rhs.m_focalPoint && 0066 m_stops == rhs.m_stops; 0067 } 0068 0069 KoResourceSP KoStopGradient::clone() const 0070 { 0071 return KoResourceSP(new KoStopGradient(*this)); 0072 } 0073 0074 bool KoStopGradient::loadFromDevice(QIODevice *dev, KisResourcesInterfaceSP resourcesInterface) 0075 { 0076 Q_UNUSED(resourcesInterface); 0077 loadSvgGradient(dev); 0078 if (m_stops.count() >= 2) { 0079 setValid(true); 0080 } 0081 updatePreview(); 0082 return true; 0083 } 0084 0085 QGradient* KoStopGradient::toQGradient() const 0086 { 0087 QGradient* gradient; 0088 0089 switch (type()) { 0090 case QGradient::LinearGradient: { 0091 gradient = new QLinearGradient(m_start, m_stop); 0092 break; 0093 } 0094 case QGradient::RadialGradient: { 0095 QPointF diff = m_stop - m_start; 0096 qreal radius = sqrt(diff.x() * diff.x() + diff.y() * diff.y()); 0097 gradient = new QRadialGradient(m_start, radius, m_focalPoint); 0098 break; 0099 } 0100 case QGradient::ConicalGradient: { 0101 qreal angle = atan2(m_start.y(), m_start.x()) * 180.0 / M_PI; 0102 if (angle < 0.0) 0103 angle += 360.0; 0104 gradient = new QConicalGradient(m_start, angle); 0105 break; 0106 } 0107 default: 0108 return 0; 0109 } 0110 QColor color; 0111 for (QList<KoGradientStop>::const_iterator i = m_stops.begin(); i != m_stops.end(); ++i) { 0112 i->color.toQColor(&color); 0113 gradient->setColorAt(i->position, color); 0114 } 0115 0116 gradient->setCoordinateMode(QGradient::ObjectBoundingMode); 0117 gradient->setSpread(this->spread()); 0118 0119 return gradient; 0120 } 0121 0122 bool KoStopGradient::stopsAt(KoGradientStop& leftStop, KoGradientStop& rightStop, qreal t) const 0123 { 0124 if (!m_stops.count()) 0125 return false; 0126 0127 KIS_SAFE_ASSERT_RECOVER(!qIsNaN(t)) { // if it's nan, it would crash in the last 'else' 0128 leftStop = m_stops.first(); 0129 rightStop = KoGradientStop(-std::numeric_limits<double>::infinity(), leftStop.color, leftStop.type); 0130 return true; 0131 } 0132 0133 if (t <= m_stops.first().position || m_stops.count() == 1) { 0134 // we have only one stop or t is before the first stop 0135 leftStop = m_stops.first(); 0136 rightStop = KoGradientStop(-std::numeric_limits<double>::infinity(), leftStop.color, leftStop.type); 0137 return true; 0138 } else if (t >= m_stops.last().position) { 0139 // t is after the last stop 0140 rightStop = m_stops.last(); 0141 leftStop = KoGradientStop(std::numeric_limits<double>::infinity(), rightStop.color, rightStop.type); 0142 return true; 0143 } else { 0144 // we have at least two color stops 0145 // -> find the two stops which frame our t 0146 auto it = std::lower_bound(m_stops.begin(), m_stops.end(), KoGradientStop(t, KoColor(), COLORSTOP), 0147 kismpl::mem_less(&KoGradientStop::position)); 0148 leftStop = *(it - 1); 0149 rightStop = *(it); 0150 return true; 0151 } 0152 } 0153 0154 void KoStopGradient::colorAt(KoColor& dst, qreal t) const 0155 { 0156 KoGradientStop leftStop, rightStop; 0157 if (!stopsAt(leftStop, rightStop, t)) return; 0158 0159 const KoColorSpace *mixSpace = dst.colorSpace(); 0160 0161 KoColor buffer(mixSpace); 0162 KoColor startDummy(leftStop.color, mixSpace); 0163 KoColor endDummy(rightStop.color, mixSpace); 0164 0165 const std::array<quint8 *, 2> colors = {{startDummy.data(), endDummy.data()}}; 0166 0167 qreal localT = NAN; 0168 qreal stopDistance = rightStop.position - leftStop.position; 0169 if (stopDistance < DBL_EPSILON) { 0170 localT = 0.5; 0171 } else { 0172 localT = (t - leftStop.position) / stopDistance; 0173 } 0174 std::array<qint16, 2> colorWeights {}; 0175 colorWeights[0] = std::lround((1.0 - localT) * qint16_MAX); 0176 colorWeights[1] = qint16_MAX - colorWeights[0]; 0177 0178 mixSpace->mixColorsOp()->mixColors(colors.data(), colorWeights.data(), 2, buffer.data(), qint16_MAX); 0179 0180 dst = buffer; 0181 } 0182 0183 QSharedPointer<KoStopGradient> KoStopGradient::fromQGradient(const QGradient *gradient) 0184 { 0185 if (!gradient) 0186 return QSharedPointer<KoStopGradient>(0); 0187 0188 QSharedPointer<KoStopGradient> newGradient(new KoStopGradient(QString())); 0189 newGradient->setType(gradient->type()); 0190 newGradient->setSpread(gradient->spread()); 0191 0192 switch (gradient->type()) { 0193 case QGradient::LinearGradient: { 0194 const QLinearGradient* g = static_cast<const QLinearGradient*>(gradient); 0195 newGradient->m_start = g->start(); 0196 newGradient->m_stop = g->finalStop(); 0197 newGradient->m_focalPoint = g->start(); 0198 break; 0199 } 0200 case QGradient::RadialGradient: { 0201 const QRadialGradient* g = static_cast<const QRadialGradient*>(gradient); 0202 newGradient->m_start = g->center(); 0203 newGradient->m_stop = g->center() + QPointF(g->radius(), 0); 0204 newGradient->m_focalPoint = g->focalPoint(); 0205 break; 0206 } 0207 case QGradient::ConicalGradient: { 0208 const QConicalGradient* g = static_cast<const QConicalGradient*>(gradient); 0209 qreal radian = g->angle() * M_PI / 180.0; 0210 newGradient->m_start = g->center(); 0211 newGradient->m_stop = QPointF(100.0 * cos(radian), 100.0 * sin(radian)); 0212 newGradient->m_focalPoint = g->center(); 0213 break; 0214 } 0215 default: 0216 return QSharedPointer<KoStopGradient>(0);; 0217 } 0218 0219 Q_FOREACH(const QGradientStop & stop, gradient->stops()) { 0220 KoColor color(newGradient->colorSpace()); 0221 color.fromQColor(stop.second); 0222 newGradient->m_stops.append(KoGradientStop(stop.first, color, COLORSTOP)); 0223 } 0224 0225 newGradient->setValid(true); 0226 0227 return newGradient; 0228 } 0229 0230 void KoStopGradient::setStops(QList< KoGradientStop > stops) 0231 { 0232 m_stops.clear(); 0233 m_hasVariableStops = false; 0234 KoColor color; 0235 Q_FOREACH(const KoGradientStop & stop, stops) { 0236 color = stop.color; 0237 m_stops.append(KoGradientStop(stop.position, color, stop.type)); 0238 if (stop.type != COLORSTOP) { 0239 m_hasVariableStops = true; 0240 } 0241 } 0242 if (m_stops.count() >= 2) { 0243 setValid(true); 0244 } else { 0245 setValid(false); 0246 } 0247 updatePreview(); 0248 } 0249 0250 QList<KoGradientStop> KoStopGradient::stops() const 0251 { 0252 return m_stops; 0253 } 0254 0255 QList<int> KoStopGradient::requiredCanvasResources() const 0256 { 0257 QList<int> result; 0258 0259 if (std::find_if_not(m_stops.begin(), m_stops.end(), 0260 kismpl::mem_equal_to(&KoGradientStop::type, COLORSTOP)) 0261 != m_stops.end()) { 0262 0263 result << KoCanvasResource::ForegroundColor << KoCanvasResource::BackgroundColor; 0264 } 0265 0266 return result; 0267 } 0268 0269 void KoStopGradient::bakeVariableColors(KoCanvasResourcesInterfaceSP canvasResourcesInterface) 0270 { 0271 const KoColor fgColor = canvasResourcesInterface->resource(KoCanvasResource::ForegroundColor).value<KoColor>(); 0272 const KoColor bgColor = canvasResourcesInterface->resource(KoCanvasResource::BackgroundColor).value<KoColor>(); 0273 0274 for (auto it = m_stops.begin(); it != m_stops.end(); ++it) { 0275 if (it->type == FOREGROUNDSTOP) { 0276 it->color = fgColor; 0277 it->type = COLORSTOP; 0278 } else if (it->type == BACKGROUNDSTOP) { 0279 it->color = bgColor; 0280 it->type = COLORSTOP; 0281 } 0282 } 0283 } 0284 0285 void KoStopGradient::updateVariableColors(KoCanvasResourcesInterfaceSP canvasResourcesInterface) 0286 { 0287 const KoColor fgColor = canvasResourcesInterface->resource(KoCanvasResource::ForegroundColor).value<KoColor>(); 0288 const KoColor bgColor = canvasResourcesInterface->resource(KoCanvasResource::BackgroundColor).value<KoColor>(); 0289 0290 for (auto it = m_stops.begin(); it != m_stops.end(); ++it) { 0291 if (it->type == FOREGROUNDSTOP) { 0292 it->color = fgColor; 0293 } else if (it->type == BACKGROUNDSTOP) { 0294 it->color = bgColor; 0295 } 0296 } 0297 } 0298 0299 void KoStopGradient::loadSvgGradient(QIODevice* file) 0300 { 0301 QDomDocument doc; 0302 0303 if (!(doc.setContent(file))) { 0304 file->close(); 0305 } else { 0306 QHash<QString, const KoColorProfile*> profiles; 0307 for (QDomElement e = doc.documentElement().firstChildElement("defs"); !e.isNull(); e = e.nextSiblingElement("defs")) { 0308 for (QDomElement profileEl = e.firstChildElement("color-profile"); !profileEl.isNull(); profileEl = profileEl.nextSiblingElement("color-profile")) { 0309 const QString href = profileEl.attribute("xlink:href"); 0310 const QByteArray uniqueId = QByteArray::fromHex(profileEl.attribute("local").toLatin1()); 0311 const QString name = profileEl.attribute("name"); 0312 0313 const KoColorProfile *profile = 0314 KoColorSpaceRegistry::instance()->profileByUniqueId(uniqueId); 0315 if (!profile) { 0316 QFile file(href); 0317 if (file.exists()) { 0318 KoColorSpaceEngine *engine = KoColorSpaceEngineRegistry::instance()->get("icc"); 0319 KIS_ASSERT(engine); 0320 file.open(QIODevice::ReadOnly); 0321 const QByteArray profileData = file.readAll(); 0322 if (!profileData.isEmpty()) { 0323 profile = engine->addProfile(href); 0324 } 0325 } 0326 } 0327 0328 0329 if (profile && !profiles.contains(name)) { 0330 profiles.insert(name, profile); 0331 } 0332 } 0333 } 0334 for (QDomNode n = doc.documentElement().firstChild(); !n.isNull(); n = n.nextSibling()) { 0335 QDomElement e = n.toElement(); 0336 0337 if (e.isNull()) continue; 0338 0339 if (e.tagName() == "linearGradient" || e.tagName() == "radialGradient") { 0340 parseSvgGradient(e, profiles); 0341 return; 0342 } 0343 // Inkscape gradients are in another defs 0344 if (e.tagName() == "defs") { 0345 0346 0347 for (QDomNode defnode = e.firstChild(); !defnode.isNull(); defnode = defnode.nextSibling()) { 0348 QDomElement defelement = defnode.toElement(); 0349 0350 if (defelement.isNull()) continue; 0351 0352 if (defelement.tagName() == "linearGradient" || defelement.tagName() == "radialGradient") { 0353 parseSvgGradient(defelement, profiles); 0354 return; 0355 } 0356 } 0357 } 0358 } 0359 } 0360 } 0361 0362 0363 void KoStopGradient::parseSvgGradient(const QDomElement& element, QHash<QString, const KoColorProfile *> profiles) 0364 { 0365 m_stops.clear(); 0366 m_hasVariableStops = false; 0367 setSpread(QGradient::PadSpread); 0368 0369 /*QString href = e.attribute( "xlink:href" ).mid( 1 ); 0370 if( !href.isEmpty() ) 0371 { 0372 }*/ 0373 setName(element.attribute("id", i18n("SVG Gradient"))); 0374 0375 bool bbox = element.attribute("gradientUnits") != "userSpaceOnUse"; 0376 0377 if (element.tagName() == "linearGradient") { 0378 0379 if (bbox) { 0380 QString s; 0381 0382 s = element.attribute("x1", "0%"); 0383 qreal xOrigin; 0384 if (s.endsWith('%')) 0385 xOrigin = s.remove('%').toDouble(); 0386 else 0387 xOrigin = s.toDouble() * 100.0; 0388 0389 s = element.attribute("y1", "0%"); 0390 qreal yOrigin; 0391 if (s.endsWith('%')) 0392 yOrigin = s.remove('%').toDouble(); 0393 else 0394 yOrigin = s.toDouble() * 100.0; 0395 0396 s = element.attribute("x2", "100%"); 0397 qreal xVector; 0398 if (s.endsWith('%')) 0399 xVector = s.remove('%').toDouble(); 0400 else 0401 xVector = s.toDouble() * 100.0; 0402 0403 s = element.attribute("y2", "0%"); 0404 qreal yVector; 0405 if (s.endsWith('%')) 0406 yVector = s.remove('%').toDouble(); 0407 else 0408 yVector = s.toDouble() * 100.0; 0409 0410 m_start = QPointF(xOrigin, yOrigin); 0411 m_stop = QPointF(xVector, yVector); 0412 } 0413 else { 0414 m_start = QPointF(element.attribute("x1").toDouble(), element.attribute("y1").toDouble()); 0415 m_stop = QPointF(element.attribute("x2").toDouble(), element.attribute("y2").toDouble()); 0416 } 0417 setType(QGradient::LinearGradient); 0418 } 0419 else { 0420 if (bbox) { 0421 QString s; 0422 0423 s = element.attribute("cx", "50%"); 0424 qreal xOrigin; 0425 if (s.endsWith('%')) 0426 xOrigin = s.remove('%').toDouble(); 0427 else 0428 xOrigin = s.toDouble() * 100.0; 0429 0430 s = element.attribute("cy", "50%"); 0431 qreal yOrigin; 0432 if (s.endsWith('%')) 0433 yOrigin = s.remove('%').toDouble(); 0434 else 0435 yOrigin = s.toDouble() * 100.0; 0436 0437 s = element.attribute("cx", "50%"); 0438 qreal xVector; 0439 if (s.endsWith('%')) 0440 xVector = s.remove('%').toDouble(); 0441 else 0442 xVector = s.toDouble() * 100.0; 0443 0444 s = element.attribute("r", "50%"); 0445 if (s.endsWith('%')) 0446 xVector += s.remove('%').toDouble(); 0447 else 0448 xVector += s.toDouble() * 100.0; 0449 0450 s = element.attribute("cy", "50%"); 0451 qreal yVector; 0452 if (s.endsWith('%')) 0453 yVector = s.remove('%').toDouble(); 0454 else 0455 yVector = s.toDouble() * 100.0; 0456 0457 s = element.attribute("fx", "50%"); 0458 qreal xFocal; 0459 if (s.endsWith('%')) 0460 xFocal = s.remove('%').toDouble(); 0461 else 0462 xFocal = s.toDouble() * 100.0; 0463 0464 s = element.attribute("fy", "50%"); 0465 qreal yFocal; 0466 if (s.endsWith('%')) 0467 yFocal = s.remove('%').toDouble(); 0468 else 0469 yFocal = s.toDouble() * 100.0; 0470 0471 m_start = QPointF(xOrigin, yOrigin); 0472 m_stop = QPointF(xVector, yVector); 0473 m_focalPoint = QPointF(xFocal, yFocal); 0474 } 0475 else { 0476 m_start = QPointF(element.attribute("cx").toDouble(), element.attribute("cy").toDouble()); 0477 m_stop = QPointF(element.attribute("cx").toDouble() + element.attribute("r").toDouble(), 0478 element.attribute("cy").toDouble()); 0479 m_focalPoint = QPointF(element.attribute("fx").toDouble(), element.attribute("fy").toDouble()); 0480 } 0481 setType(QGradient::RadialGradient); 0482 } 0483 // handle spread method 0484 QString spreadMethod = element.attribute("spreadMethod"); 0485 if (!spreadMethod.isEmpty()) { 0486 if (spreadMethod == "reflect") 0487 setSpread(QGradient::ReflectSpread); 0488 else if (spreadMethod == "repeat") 0489 setSpread(QGradient::RepeatSpread); 0490 } 0491 0492 for (QDomNode n = element.firstChild(); !n.isNull(); n = n.nextSibling()) { 0493 QDomElement colorstop = n.toElement(); 0494 if (colorstop.tagName() == "stop") { 0495 qreal opacity = 0.0; 0496 KoColor color; 0497 float off; 0498 QString temp = colorstop.attribute("offset"); 0499 if (temp.contains('%')) { 0500 temp = temp.left(temp.length() - 1); 0501 off = temp.toFloat() / 100.0; 0502 } 0503 else 0504 off = temp.toFloat(); 0505 0506 if (!colorstop.attribute("stop-color").isEmpty()) 0507 color = KoColor::fromSVG11(colorstop.attribute("stop-color"), profiles); 0508 else { 0509 // try style attr 0510 QString style = colorstop.attribute("style").simplified(); 0511 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) 0512 QStringList substyles = style.split(';', Qt::SkipEmptyParts); 0513 #else 0514 QStringList substyles = style.split(';', QString::SkipEmptyParts); 0515 #endif 0516 Q_FOREACH(const QString & s, substyles) { 0517 QStringList substyle = s.split(':'); 0518 QString command = substyle[0].trimmed(); 0519 QString params = substyle[1].trimmed(); 0520 if (command == "stop-color") 0521 color = KoColor::fromSVG11(params, profiles); 0522 if (command == "stop-opacity") 0523 opacity = params.toDouble(); 0524 } 0525 0526 } 0527 if (!colorstop.attribute("stop-opacity").isEmpty()) 0528 opacity = colorstop.attribute("stop-opacity").toDouble(); 0529 0530 color.setOpacity(static_cast<quint8>(std::lround(opacity * OPACITY_OPAQUE_U8))); 0531 QString stopTypeStr = colorstop.attribute("krita:stop-type", "color-stop"); 0532 KoGradientStopType stopType = KoGradientStop::typeFromString(stopTypeStr); 0533 if (stopType != COLORSTOP) { 0534 m_hasVariableStops = true; 0535 } 0536 //According to the SVG spec each gradient offset has to be equal to or greater than the previous one 0537 //if not it needs to be adjusted to be equal 0538 if (m_stops.count() > 0 && m_stops.last().position >= off) { 0539 off = m_stops.last().position; 0540 } 0541 m_stops.append(KoGradientStop(off, color, stopType)); 0542 } 0543 } 0544 if (m_stops.count() >= 2) { 0545 setValid(true); 0546 } else { 0547 setValid(false); 0548 } 0549 } 0550 0551 QString KoStopGradient::defaultFileExtension() const 0552 { 0553 return QString(".svg"); 0554 } 0555 0556 void KoStopGradient::toXML(QDomDocument& doc, QDomElement& gradientElt) const 0557 { 0558 gradientElt.setAttribute("type", "stop"); 0559 for (int s = 0; s < m_stops.size(); s++) { 0560 KoGradientStop stop = m_stops.at(s); 0561 QDomElement stopElt = doc.createElement("stop"); 0562 stopElt.setAttribute("offset", KisDomUtils::toString(stop.position)); 0563 stopElt.setAttribute("bitdepth", stop.color.colorSpace()->colorDepthId().id()); 0564 stopElt.setAttribute("alpha", KisDomUtils::toString(stop.color.opacityF())); 0565 stopElt.setAttribute("stoptype", KisDomUtils::toString(stop.type)); 0566 stop.color.toXML(doc, stopElt); 0567 gradientElt.appendChild(stopElt); 0568 } 0569 } 0570 0571 KoStopGradient KoStopGradient::fromXML(const QDomElement& elt) 0572 { 0573 KoStopGradient gradient; 0574 QList<KoGradientStop> stops; 0575 QDomElement stopElt = elt.firstChildElement("stop"); 0576 while (!stopElt.isNull()) { 0577 qreal offset = KisDomUtils::toDouble(stopElt.attribute("offset", "0.0")); 0578 QString bitDepth = stopElt.attribute("bitdepth", Integer8BitsColorDepthID.id()); 0579 KoColor color = KoColor::fromXML(stopElt.firstChildElement(), bitDepth); 0580 color.setOpacity(KisDomUtils::toDouble(stopElt.attribute("alpha", "1.0"))); 0581 KoGradientStopType stoptype = static_cast<KoGradientStopType>(KisDomUtils::toInt(stopElt.attribute("stoptype", "0"))); 0582 stops.append(KoGradientStop(offset, color, stoptype)); 0583 stopElt = stopElt.nextSiblingElement("stop"); 0584 } 0585 gradient.setStops(stops); 0586 return gradient; 0587 } 0588 0589 QString KoStopGradient::saveSvgGradient() const 0590 { 0591 QDomDocument doc; 0592 0593 doc.setContent(QString("<svg xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:krita=\"%1\" > </svg>").arg(KoXmlNS::krita)); 0594 0595 const QString spreadMethod[3] = { 0596 QString("pad"), 0597 QString("reflect"), 0598 QString("repeat") 0599 }; 0600 0601 QDomElement gradient = doc.createElement("linearGradient"); 0602 gradient.setAttribute("id", name()); 0603 gradient.setAttribute("gradientUnits", "objectBoundingBox"); 0604 gradient.setAttribute("spreadMethod", spreadMethod[spread()]); 0605 0606 QHash<QString, const KoColorProfile*> profiles; 0607 for(const KoGradientStop & stop: m_stops) { 0608 QDomElement stopEl = doc.createElement("stop"); 0609 stopEl.setAttribute("stop-color", stop.color.toSVG11(&profiles)); 0610 stopEl.setAttribute("offset", QString().setNum(stop.position)); 0611 stopEl.setAttribute("stop-opacity", stop.color.opacityF()); 0612 stopEl.setAttribute("krita:stop-type", stop.typeString()); 0613 gradient.appendChild(stopEl); 0614 } 0615 0616 if (profiles.size()>0) { 0617 QDomElement defs = doc.createElement("defs"); 0618 for (QString key: profiles.keys()) { 0619 const KoColorProfile * profile = profiles.value(key); 0620 0621 QDomElement profileEl = doc.createElement("color-profile"); 0622 profileEl.setAttribute("name", key); 0623 QString val = profile->uniqueId().toHex(); 0624 profileEl.setAttribute("local", val); 0625 profileEl.setAttribute("xlink:href", profile->fileName()); 0626 defs.appendChild(profileEl); 0627 } 0628 doc.documentElement().appendChild(defs); 0629 } 0630 0631 doc.documentElement().appendChild(gradient); 0632 0633 return doc.toString(); 0634 } 0635 0636 bool KoStopGradient::saveToDevice(QIODevice* dev) const 0637 { 0638 QTextStream stream(dev); 0639 stream.setCodec("UTF-8"); 0640 stream << saveSvgGradient(); 0641 0642 return true; 0643 }