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 }