[krita/kazakov/svg-loading] libs: Implement auto-fill function for Krita markers

Dmitry Kazakov null at kde.org
Wed Feb 15 18:07:12 UTC 2017


Git commit d2266b464fb97462f06d4e7bfd898724fa95862b by Dmitry Kazakov.
Committed on 15/02/2017 at 18:06.
Pushed by dkazakov into branch 'kazakov/svg-loading'.

Implement auto-fill function for Krita markers

Now we define a custom SVG attribute "krita:marker-fill-method=auto",
which means that all the markers should have the same fill (including
gradient and patterns) as the shape's stroke.

Ideally we should make a GUI switch, so the user would be able to choose
whether he wants to auto-fill the markers or not. But that can wait till
later.

CC:kimageshop at kde.org

M  +84   -10   libs/flake/KoMarker.cpp
M  +4    -0    libs/flake/KoMarker.h
M  +18   -9    libs/flake/KoPathShape.cpp
M  +3    -0    libs/flake/KoPathShape.h
M  +1    -3    libs/flake/KoPathShape_p.h
M  +16   -3    libs/flake/KoShapeStroke.cpp
M  +9    -1    libs/flake/commands/KoPathShapeMarkerCommand.cpp
M  +2    -0    libs/flake/svg/SvgGraphicContext.cpp
M  +2    -0    libs/flake/svg/SvgGraphicContext.h
M  +2    -0    libs/flake/svg/SvgParser.cpp
M  +8    -2    libs/flake/svg/SvgStyleParser.cpp
M  +4    -1    libs/flake/svg/SvgStyleWriter.cpp
M  +101  -0    libs/flake/tests/TestSvgParser.cpp
M  +4    -0    libs/flake/tests/TestSvgParser.h
A  +-    --    libs/flake/tests/data/svg_render/load_markers_scaled_fill_as_shape.png
M  +0    -14   libs/global/kis_algebra_2d.cpp
M  +17   -1    libs/global/kis_algebra_2d.h
M  +11   -5    libs/ui/widgets/KoStrokeConfigWidget.cpp
M  +44   -6    libs/widgets/KoMarkerModel.cpp
M  +12   -1    libs/widgets/KoMarkerModel.h
M  +5    -0    libs/widgets/KoMarkerSelector.cpp

https://commits.kde.org/krita/d2266b464fb97462f06d4e7bfd898724fa95862b

diff --git a/libs/flake/KoMarker.cpp b/libs/flake/KoMarker.cpp
index 450267aea21..0945e51471b 100644
--- a/libs/flake/KoMarker.cpp
+++ b/libs/flake/KoMarker.cpp
@@ -30,6 +30,10 @@
 #include "KoOdfWorkaround.h"
 #include "KoShapePainter.h"
 #include "KoViewConverter.h"
+#include <KoShapeStroke.h>
+#include <KoGradientBackground.h>
+#include <KoColorBackground.h>
+
 
 #include <QString>
 #include <QUrl>
@@ -39,6 +43,7 @@
 #include "kis_global.h"
 #include "kis_algebra_2d.h"
 
+
 class Q_DECL_HIDDEN KoMarker::Private
 {
 public:
@@ -105,7 +110,7 @@ public:
     }
 
     QTransform markerTransform(qreal strokeWidth, qreal nodeAngle, const QPointF &pos = QPointF()) {
-        const QTransform translate = QTransform::fromTranslate(referencePoint.x(), referencePoint.y());
+        const QTransform translate = QTransform::fromTranslate(-referencePoint.x(), -referencePoint.y());
 
         QTransform t = translate;
 
@@ -239,16 +244,8 @@ void KoMarker::paintAtPosition(QPainter *painter, const QPointF &pos, qreal stro
     KoShapePainter p;
     p.setShapes(d->shapes);
 
-    painter->translate(pos);
-
-    const qreal angle = d->hasAutoOrientation ? nodeAngle : d->explicitOrientation;
-    painter->rotate(kisRadiansToDegrees(angle));
+    painter->setTransform(d->markerTransform(strokeWidth, nodeAngle, pos), true);
 
-    if (d->coordinateSystem == StrokeWidth) {
-        painter->scale(strokeWidth, strokeWidth);
-    }
-
-    painter->translate(-d->referencePoint);
     p.paint(*painter, converter);
 
     painter->setTransform(oldTransform);
@@ -334,3 +331,80 @@ void KoMarker::drawPreview(QPainter *painter, const QRectF &previewRect, const Q
 
     painter->restore();
 }
+
+void KoMarker::applyShapeStroke(KoShape *parentShape, KoShapeStroke *stroke, const QPointF &pos, qreal strokeWidth, qreal nodeAngle)
+{
+    const QGradient *originalGradient = stroke->lineBrush().gradient();
+
+    if (!originalGradient) {
+        QList<KoShape*> linearizedShapes = KoShape::linearizeSubtree(d->shapes);
+        Q_FOREACH(KoShape *shape, linearizedShapes) {
+            // update the stroke
+            KoShapeStrokeSP shapeStroke = shape->stroke() ?
+                        qSharedPointerDynamicCast<KoShapeStroke>(shape->stroke()) :
+                        KoShapeStrokeSP();
+
+            if (shapeStroke) {
+                shapeStroke = toQShared(new KoShapeStroke(*shapeStroke));
+
+                shapeStroke->setLineBrush(QBrush());
+                shapeStroke->setColor(stroke->color());
+
+                shape->setStroke(shapeStroke);
+            }
+
+            // update the background
+            if (shape->background()) {
+                QSharedPointer<KoColorBackground> bg(new KoColorBackground(stroke->color()));
+                shape->setBackground(bg);
+            }
+        }
+    } else {
+        QScopedPointer<QGradient> g(KoFlake::cloneGradient(originalGradient));
+        KIS_ASSERT_RECOVER_RETURN(g);
+
+        const QTransform markerTransformInverted =
+                d->markerTransform(strokeWidth, nodeAngle, pos).inverted();
+
+        QTransform gradientToUser;
+
+        // Unwrap the gradient to work in global mode
+        if (g->coordinateMode() == QGradient::ObjectBoundingMode) {
+            const QRectF boundingRect =
+                    KisAlgebra2D::ensureRectNotSmaller(parentShape->outline().boundingRect(), QSizeF(1.0, 1.0));
+
+            gradientToUser = QTransform(boundingRect.width(), 0, 0, boundingRect.height(),
+                                        boundingRect.x(), boundingRect.y());
+
+            g->setCoordinateMode(QGradient::LogicalMode);
+        }
+
+        QList<KoShape*> linearizedShapes = KoShape::linearizeSubtree(d->shapes);
+        Q_FOREACH(KoShape *shape, linearizedShapes) {
+            // shape-unwinding transform
+            QTransform t = gradientToUser * markerTransformInverted * shape->absoluteTransformation(0).inverted();
+
+            // update the stroke
+            KoShapeStrokeSP shapeStroke = shape->stroke() ?
+                        qSharedPointerDynamicCast<KoShapeStroke>(shape->stroke()) :
+                        KoShapeStrokeSP();
+
+            if (shapeStroke) {
+                shapeStroke = toQShared(new KoShapeStroke(*shapeStroke));
+
+                QBrush brush(*g);
+                brush.setTransform(t);
+                shapeStroke->setLineBrush(brush);
+                shapeStroke->setColor(Qt::transparent);
+                shape->setStroke(shapeStroke);
+            }
+
+            // update the background
+            if (shape->background()) {
+
+                QSharedPointer<KoGradientBackground> bg(new KoGradientBackground(KoFlake::cloneGradient(g.data()), t));
+                shape->setBackground(bg);
+            }
+        }
+    }
+}
diff --git a/libs/flake/KoMarker.h b/libs/flake/KoMarker.h
index c1054feb6f4..a50bc27b4fa 100644
--- a/libs/flake/KoMarker.h
+++ b/libs/flake/KoMarker.h
@@ -33,6 +33,7 @@ class QString;
 class QPainterPath;
 class KoShape;
 class QPainter;
+class KoShapeStroke;
 
 class  KRITAFLAKE_EXPORT KoMarker : public QSharedData
 {
@@ -108,6 +109,9 @@ public:
     void drawPreview(QPainter *painter, const QRectF &previewRect,
                      const QPen &pen, KoFlake::MarkerPosition position);
 
+
+    void applyShapeStroke(KoShape *shape, KoShapeStroke *stroke, const QPointF &pos, qreal strokeWidth, qreal nodeAngle);
+
 private:
     class Private;
     Private * const d;
diff --git a/libs/flake/KoPathShape.cpp b/libs/flake/KoPathShape.cpp
index 5568428953c..e568fb0cb31 100644
--- a/libs/flake/KoPathShape.cpp
+++ b/libs/flake/KoPathShape.cpp
@@ -62,17 +62,15 @@ static const qreal DefaultMarkerWidth = 3.0;
 KoPathShapePrivate::KoPathShapePrivate(KoPathShape *q)
     : KoTosContainerPrivate(q),
     fillRule(Qt::OddEvenFill),
-    startMarker(KoMarkerData::MarkerStart),
-    endMarker(KoMarkerData::MarkerEnd)
+    autoFillMarkers(false)
 {
 }
 
 KoPathShapePrivate::KoPathShapePrivate(const KoPathShapePrivate &rhs, KoPathShape *q)
     : KoTosContainerPrivate(rhs, q),
       fillRule(rhs.fillRule),
-      startMarker(rhs.startMarker),
-      endMarker(rhs.endMarker),
-      markersNew(rhs.markersNew)
+      markersNew(rhs.markersNew),
+      autoFillMarkers(rhs.autoFillMarkers)
 {
     Q_FOREACH (KoSubpath *subPath, rhs.subpaths) {
         KoSubpath *clonedSubPath = new KoSubpath();
@@ -338,8 +336,8 @@ QString KoPathShape::saveStyle(KoGenStyle &style, KoShapeSavingContext &context)
     if (lineBorder) {
         lineWidth = lineBorder->lineWidth();
     }
-    d->startMarker.saveStyle(style, lineWidth, context);
-    d->endMarker.saveStyle(style, lineWidth, context);
+
+    Q_UNUSED(lineWidth)
 
     return KoTosContainer::saveStyle(style, context);
 }
@@ -368,8 +366,7 @@ void KoPathShape::loadStyle(const KoXmlElement & element, KoShapeLoadingContext
         lineWidth = lineBorder->lineWidth();
     }
 
-    d->startMarker.loadOdf(lineWidth, context);
-    d->endMarker.loadOdf(lineWidth, context);
+    Q_UNUSED(lineWidth);
 }
 
 QRect KoPathShape::loadOdfViewbox(const KoXmlElement & element)
@@ -1601,6 +1598,18 @@ bool KoPathShape::hasMarkers() const
     return !d->markersNew.isEmpty();
 }
 
+bool KoPathShape::autoFillMarkers() const
+{
+    Q_D(const KoPathShape);
+    return d->autoFillMarkers;
+}
+
+void KoPathShape::setAutoFillMarkers(bool value)
+{
+    Q_D(KoPathShape);
+    d->autoFillMarkers = value;
+}
+
 QPainterPath KoPathShape::pathStroke(const QPen &pen) const
 {
     Q_D(const KoPathShape);
diff --git a/libs/flake/KoPathShape.h b/libs/flake/KoPathShape.h
index a3c2b305c56..c0b5c6ace74 100644
--- a/libs/flake/KoPathShape.h
+++ b/libs/flake/KoPathShape.h
@@ -459,6 +459,9 @@ public:
     KoMarker* marker(KoFlake::MarkerPosition pos) const;
     bool hasMarkers() const;
 
+    bool autoFillMarkers() const;
+    void setAutoFillMarkers(bool value);
+
 private:
     /// constructor: to be used in cloneShape(), not in descendants!
     /// \internal
diff --git a/libs/flake/KoPathShape_p.h b/libs/flake/KoPathShape_p.h
index 065d57680e9..f34194a89d8 100644
--- a/libs/flake/KoPathShape_p.h
+++ b/libs/flake/KoPathShape_p.h
@@ -92,12 +92,10 @@ public:
 
     Q_DECLARE_PUBLIC(KoPathShape)
 
-    KoMarkerData startMarker;
-    KoMarkerData endMarker;
-
     KoSubpathList subpaths;
 
     QMap<KoFlake::MarkerPosition, QExplicitlySharedDataPointer<KoMarker>> markersNew;
+    bool autoFillMarkers;
 };
 
 #endif
diff --git a/libs/flake/KoShapeStroke.cpp b/libs/flake/KoShapeStroke.cpp
index 233539e5db6..da6951c629c 100644
--- a/libs/flake/KoShapeStroke.cpp
+++ b/libs/flake/KoShapeStroke.cpp
@@ -52,6 +52,9 @@
 class Q_DECL_HIDDEN KoShapeStroke::Private
 {
 public:
+    Private(KoShapeStroke *_q) : q(_q) {}
+    KoShapeStroke *q;
+
     void paintBorder(KoShape *shape, QPainter &painter, const QPen &pen) const;
     QColor color;
     QPen pen;
@@ -97,6 +100,7 @@ void KoShapeStroke::Private::paintBorder(KoShape *shape, QPainter &painter, cons
 
             if (!pathShape->hasMarkers()) return;
 
+            const bool autoFillMarkers = pathShape->autoFillMarkers();
             KoMarker *startMarker = pathShape->marker(KoFlake::StartMarker);
             KoMarker *midMarker = pathShape->marker(KoFlake::MidMarker);
             KoMarker *endMarker = pathShape->marker(KoFlake::EndMarker);
@@ -130,16 +134,25 @@ void KoShapeStroke::Private::paintBorder(KoShape *shape, QPainter &painter, cons
 
                     if (j == 0 && startMarker) {
                         const qreal angle = isClosedSubpath ? bisectorAngle(firstAngle, lastAngle) : firstAngle;
+                        if (autoFillMarkers) {
+                            startMarker->applyShapeStroke(shape, q, segment.first()->point(), pen.widthF(), angle);
+                        }
                         startMarker->paintAtPosition(&painter, segment.first()->point(), pen.widthF(), angle);
                     }
 
                     if (j > 0 && midMarker) {
                         const qreal angle = bisectorAngle(previousAngle, angle1);
+                        if (autoFillMarkers) {
+                            midMarker->applyShapeStroke(shape, q, segment.first()->point(), pen.widthF(), angle);
+                        }
                         midMarker->paintAtPosition(&painter, segment.first()->point(), pen.widthF(), angle);
                     }
 
                     if (j == numSubPoints - 2 && endMarker) {
                         const qreal angle = isClosedSubpath ? bisectorAngle(firstAngle, lastAngle) : lastAngle;
+                        if (autoFillMarkers) {
+                            endMarker->applyShapeStroke(shape, q, segment.second()->point(), pen.widthF(), angle);
+                        }
                         endMarker->paintAtPosition(&painter, segment.second()->point(), pen.widthF(), angle);
                     }
 
@@ -156,7 +169,7 @@ void KoShapeStroke::Private::paintBorder(KoShape *shape, QPainter &painter, cons
 
 
 KoShapeStroke::KoShapeStroke()
-        : d(new Private())
+        : d(new Private(this))
 {
     d->color = QColor(Qt::black);
     // we are not rendering stroke with zero width anymore
@@ -165,7 +178,7 @@ KoShapeStroke::KoShapeStroke()
 }
 
 KoShapeStroke::KoShapeStroke(const KoShapeStroke &other)
-        : KoShapeStrokeModel(), d(new Private())
+        : KoShapeStrokeModel(), d(new Private(this))
 {
     d->color = other.d->color;
     d->pen = other.d->pen;
@@ -173,7 +186,7 @@ KoShapeStroke::KoShapeStroke(const KoShapeStroke &other)
 }
 
 KoShapeStroke::KoShapeStroke(qreal lineWidth, const QColor &color)
-        : d(new Private())
+        : d(new Private(this))
 {
     d->pen.setWidthF(qMax(qreal(0.0), lineWidth));
     d->pen.setJoinStyle(Qt::MiterJoin);
diff --git a/libs/flake/commands/KoPathShapeMarkerCommand.cpp b/libs/flake/commands/KoPathShapeMarkerCommand.cpp
index f1845a6d177..770ce629e27 100644
--- a/libs/flake/commands/KoPathShapeMarkerCommand.cpp
+++ b/libs/flake/commands/KoPathShapeMarkerCommand.cpp
@@ -21,7 +21,7 @@
 #include "KoPathShapeMarkerCommand.h"
 #include "KoMarker.h"
 #include "KoPathShape.h"
-#include <QExplicitlySharedDataPointer>"
+#include <QExplicitlySharedDataPointer>
 
 #include "kis_command_ids.h"
 
@@ -34,6 +34,7 @@ public:
     QList<QExplicitlySharedDataPointer<KoMarker>> oldMarkers; ///< the old markers, one for each shape
     QExplicitlySharedDataPointer<KoMarker> marker; ///< the new marker to set
     KoFlake::MarkerPosition position;
+    QList<bool> oldAutoFillMarkers;
 };
 
 KoPathShapeMarkerCommand::KoPathShapeMarkerCommand(const QList<KoPathShape*> &shapes, KoMarker *marker, KoFlake::MarkerPosition position, KUndo2Command *parent)
@@ -47,6 +48,7 @@ KoPathShapeMarkerCommand::KoPathShapeMarkerCommand(const QList<KoPathShape*> &sh
     // save old markers
     Q_FOREACH (KoPathShape *shape, m_d->shapes) {
         m_d->oldMarkers.append(QExplicitlySharedDataPointer<KoMarker>(shape->marker(position)));
+        m_d->oldAutoFillMarkers.append(shape->autoFillMarkers());
     }
 }
 
@@ -60,6 +62,10 @@ void KoPathShapeMarkerCommand::redo()
     Q_FOREACH (KoPathShape *shape, m_d->shapes) {
         shape->update();
         shape->setMarker(m_d->marker.data(), m_d->position);
+
+        // we have no GUI for selection auto-filling yet! So just enable it!
+        shape->setAutoFillMarkers(true);
+
         shape->update();
     }
 }
@@ -68,9 +74,11 @@ void KoPathShapeMarkerCommand::undo()
 {
     KUndo2Command::undo();
     auto markerIt = m_d->oldMarkers.begin();
+    auto autoFillIt = m_d->oldAutoFillMarkers.begin();
     Q_FOREACH (KoPathShape *shape, m_d->shapes) {
         shape->update();
         shape->setMarker((*markerIt).data(), m_d->position);
+        shape->setAutoFillMarkers(*autoFillIt);
         shape->update();
         ++markerIt;
     }
diff --git a/libs/flake/svg/SvgGraphicContext.cpp b/libs/flake/svg/SvgGraphicContext.cpp
index 24a7af3c8b7..0182ac878a5 100644
--- a/libs/flake/svg/SvgGraphicContext.cpp
+++ b/libs/flake/svg/SvgGraphicContext.cpp
@@ -51,4 +51,6 @@ SvgGraphicsContext::SvgGraphicsContext()
     letterSpacing = 0.0;
     wordSpacing = 0.0;
     pixelsPerInch = 72.0;
+
+    autoFillMarkers = false;
 }
diff --git a/libs/flake/svg/SvgGraphicContext.h b/libs/flake/svg/SvgGraphicContext.h
index 012e6848b92..2e943db9c64 100644
--- a/libs/flake/svg/SvgGraphicContext.h
+++ b/libs/flake/svg/SvgGraphicContext.h
@@ -74,6 +74,8 @@ public:
     QString markerStartId;
     QString markerMidId;
     QString markerEndId;
+
+    bool autoFillMarkers;
 };
 
 #endif // SVGGRAPHICCONTEXT_H
diff --git a/libs/flake/svg/SvgParser.cpp b/libs/flake/svg/SvgParser.cpp
index 695b61f3c2f..65edc1438da 100644
--- a/libs/flake/svg/SvgParser.cpp
+++ b/libs/flake/svg/SvgParser.cpp
@@ -1013,6 +1013,8 @@ void SvgParser::applyMarkers(KoPathShape *shape)
     if (!gc->markerEndId.isEmpty() && m_markers.contains(gc->markerEndId)) {
         shape->setMarker(m_markers[gc->markerEndId].data(), KoFlake::EndMarker);
     }
+
+    shape->setAutoFillMarkers(gc->autoFillMarkers);
 }
 
 void SvgParser::applyClipping(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates)
diff --git a/libs/flake/svg/SvgStyleParser.cpp b/libs/flake/svg/SvgStyleParser.cpp
index 7ade60445c4..b5b7a241b91 100644
--- a/libs/flake/svg/SvgStyleParser.cpp
+++ b/libs/flake/svg/SvgStyleParser.cpp
@@ -47,7 +47,7 @@ public:
         styleAttributes << "stroke" << "stroke-width" << "stroke-linejoin" << "stroke-linecap";
         styleAttributes << "stroke-dasharray" << "stroke-dashoffset" << "stroke-opacity" << "stroke-miterlimit";
         styleAttributes << "opacity" << "filter" << "clip-path" << "clip-rule" << "mask";
-        styleAttributes << "marker" << "marker-start" << "marker-mid" << "marker-end";
+        styleAttributes << "marker" << "marker-start" << "marker-mid" << "marker-end" << "krita:marker-fill-method";
     }
 
     SvgLoadingContext &context;
@@ -327,7 +327,7 @@ void SvgStyleParser::parsePA(SvgGraphicsContext *gc, const QString &command, con
             unsigned int end = params.indexOf(')', start);
             gc->markerMidId = params.mid(start, end - start);
         }
-    }  else if (command == "marker") {
+    } else if (command == "marker") {
         if (params != "none" && params.startsWith("url(")) {
             unsigned int start = params.indexOf('#') + 1;
             unsigned int end = params.indexOf(')', start);
@@ -335,6 +335,8 @@ void SvgStyleParser::parsePA(SvgGraphicsContext *gc, const QString &command, con
             gc->markerMidId = gc->markerStartId;
             gc->markerEndId = gc->markerStartId;
         }
+    } else if (command == "krita:marker-fill-method") {
+        gc->autoFillMarkers = params == "auto";
     }
 
     gc->fillColor = fillcolor;
@@ -499,6 +501,10 @@ SvgStyles SvgStyleParser::collectStyles(const KoXmlElement &e)
                 continue;
             QString command = substyle[0].trimmed();
             QString params  = substyle[1].trimmed();
+
+            // toggle the namespace selector into the xml-like one
+            command.replace("|", ":");
+
             // only use style and font attributes
             if (d->styleAttributes.contains(command) || d->fontAttributes.contains(command))
                 styleMap[command] = params;
diff --git a/libs/flake/svg/SvgStyleWriter.cpp b/libs/flake/svg/SvgStyleWriter.cpp
index a4cdaeded3d..2cb7c6773e5 100644
--- a/libs/flake/svg/SvgStyleWriter.cpp
+++ b/libs/flake/svg/SvgStyleWriter.cpp
@@ -298,7 +298,6 @@ void tryEmbedMarker(const KoPathShape *pathShape,
 
 }
 
-
 void SvgStyleWriter::saveSvgMarkers(KoShape *shape, SvgSavingContext &context)
 {
     KoPathShape *pathShape = dynamic_cast<KoPathShape*>(shape);
@@ -308,6 +307,10 @@ void SvgStyleWriter::saveSvgMarkers(KoShape *shape, SvgSavingContext &context)
     tryEmbedMarker(pathShape, "marker-start", KoFlake::StartMarker, context);
     tryEmbedMarker(pathShape, "marker-mid", KoFlake::MidMarker, context);
     tryEmbedMarker(pathShape, "marker-end", KoFlake::EndMarker, context);
+
+    if (pathShape->autoFillMarkers()) {
+        context.shapeWriter().addAttribute("krita:marker-fill-method", "auto");
+    }
 }
 
 void SvgStyleWriter::saveSvgColorStops(const QGradientStops &colorStops, SvgSavingContext &context)
diff --git a/libs/flake/tests/TestSvgParser.cpp b/libs/flake/tests/TestSvgParser.cpp
index 5f7850bf309..66f287deed2 100644
--- a/libs/flake/tests/TestSvgParser.cpp
+++ b/libs/flake/tests/TestSvgParser.cpp
@@ -2997,5 +2997,106 @@ void TestSvgParser::testMarkersDifferent()
     t.test_standard_30px_72ppi("markers_different", false);
 }
 
+void TestSvgParser::testMarkersFillAsShape()
+{
+    const QString data =
+            "<svg width=\"30px\" height=\"30px\""
+            "    xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
+
+            "<defs>"
+            "    <linearGradient id=\"testGrad\" x1=\"0%\" y1=\"50%\" x2=\"100%\" y2=\"50%\">"
+            "        <stop offset=\"5%\" stop-color=\"#F60\" />"
+            "        <stop offset=\"80%\" stop-color=\"#FF6\" />"
+            "    </linearGradient>"
+
+            "    <marker id=\"SimpleRectMarker\""
+            "        orient=\"auto\" refY=\"12.5\" refX=\"12.5\" >"
+
+            "        <rect id=\"markerRect\" x=\"10\" y=\"10\" width=\"5\" height=\"5\""
+            "            fill=\"red\" stroke=\"none\"/>"
+            "        <rect id=\"markerRect\" x=\"14\" y=\"12\" width=\"1\" height=\"1\""
+            "            fill=\"yellow\" stroke=\"none\"/>"
+            "        <rect id=\"markerRect\" x=\"12\" y=\"12\" width=\"1\" height=\"1\""
+            "            fill=\"white\" stroke=\"none\"/>"
+            "    </marker>"
+
+            "</defs>"
+
+            "<path id=\"testRect\""
+            "    style=\"fill:none;stroke:url(#testGrad) magenta;stroke-width:2px;marker-start:url(#SimpleRectMarker);marker-end:url(#SimpleRectMarker);marker-mid:url(#SimpleRectMarker);krita|marker-fill-method:auto\""
+            "    d=\"M5,15 C5,5 25,5 25,15 L15,25\"/>"
+            //"    d=\"M5,15 L25,15\"/>"
+
+            "</svg>";
+
+    SvgRenderTester t (data);
+
+    t.test_standard_30px_72ppi("markers_scaled_fill_as_shape", false);
+}
+
+
+void TestSvgParser::testGradientRecoveringTrasnform()
+{
+    // used for experimenting purposes only!
+
+    QImage image(100,100,QImage::Format_ARGB32);
+    image.fill(0);
+    QPainter painter(&image);
+
+    painter.setPen(QPen(Qt::black, 0));
+
+
+    QLinearGradient gradient(0, 0.5, 1, 0.5);
+    gradient.setCoordinateMode(QGradient::ObjectBoundingMode);
+
+    //QLinearGradient gradient(0, 50, 100, 50);
+    //gradient.setCoordinateMode(QGradient::LogicalMode);
+
+    gradient.setColorAt(0.0, Qt::red);
+    gradient.setColorAt(1.0, Qt::blue);
+
+    QTransform gradientTrasnform;
+    gradientTrasnform.shear(0.2, 0);
+
+    {
+        QBrush brush(gradient);
+        brush.setTransform(gradientTrasnform);
+        painter.setBrush(brush);
+    }
+
+    QRect mainShape(3,3,94,94);
+    painter.drawRect(mainShape);
+
+    QTransform gradientToUser(mainShape.width(), 0, 0, mainShape.height(),
+                              mainShape.x(), mainShape.y());
+
+    QRect smallShape(0,0,20,20);
+    QTransform smallShapeTransform;
+
+    {
+        smallShapeTransform =
+            QTransform::fromTranslate(-smallShape.center().x(), -smallShape.center().y());
+
+        QTransform r; r.rotate(90);
+        smallShapeTransform *= r;
+
+        smallShapeTransform *=
+            QTransform::fromTranslate(mainShape.center().x(), mainShape.center().y());
+    }
+
+
+    {
+        gradient.setCoordinateMode(QGradient::LogicalMode);
+        QBrush brush(gradient);
+        brush.setTransform(gradientTrasnform * gradientToUser * smallShapeTransform.inverted());
+        painter.setBrush(brush);
+        painter.setPen(Qt::NoPen);
+    }
+
+    painter.setTransform(smallShapeTransform);
+    painter.drawRect(smallShape);
+
+    //image.save("gradient_recovering_transform.png");
+}
 
 QTEST_MAIN(TestSvgParser)
diff --git a/libs/flake/tests/TestSvgParser.h b/libs/flake/tests/TestSvgParser.h
index 6ce35837d4b..080b498eafa 100644
--- a/libs/flake/tests/TestSvgParser.h
+++ b/libs/flake/tests/TestSvgParser.h
@@ -156,6 +156,10 @@ private Q_SLOTS:
     void testMarkersCustomOrientation();
 
     void testMarkersDifferent();
+
+    void testMarkersFillAsShape();
+
+    void testGradientRecoveringTrasnform();
 };
 
 #endif // TESTSVGPARSER_H
diff --git a/libs/flake/tests/data/svg_render/load_markers_scaled_fill_as_shape.png b/libs/flake/tests/data/svg_render/load_markers_scaled_fill_as_shape.png
new file mode 100644
index 00000000000..48fa548444c
Binary files /dev/null and b/libs/flake/tests/data/svg_render/load_markers_scaled_fill_as_shape.png differ
diff --git a/libs/global/kis_algebra_2d.cpp b/libs/global/kis_algebra_2d.cpp
index 75c837a6808..1ba61509d0c 100644
--- a/libs/global/kis_algebra_2d.cpp
+++ b/libs/global/kis_algebra_2d.cpp
@@ -160,20 +160,6 @@ QPointF ensureInRect(QPointF pt, const QRectF &bounds)
     return ensureInRectImpl(pt, bounds);
 }
 
-QRect ensureRectNotSmaller(QRect rc, const QSize &size)
-{
-    if (rc.width() < size.width() ||
-        rc.height() < size.height()) {
-
-        int width = qMax(rc.width(), size.width());
-        int height = qMax(rc.height(), size.height());
-
-        rc = QRect(rc.topLeft(), QSize(width, height));
-    }
-
-    return rc;
-}
-
 bool intersectLineRect(QLineF &line, const QRect rect)
 {
     QPointF pt1 = QPointF(), pt2 = QPointF();
diff --git a/libs/global/kis_algebra_2d.h b/libs/global/kis_algebra_2d.h
index bc6575b70d8..a64f7874c4f 100644
--- a/libs/global/kis_algebra_2d.h
+++ b/libs/global/kis_algebra_2d.h
@@ -274,7 +274,23 @@ Rect blowRect(const Rect &rect, qreal coeff)
 QPoint KRITAGLOBAL_EXPORT ensureInRect(QPoint pt, const QRect &bounds);
 QPointF KRITAGLOBAL_EXPORT ensureInRect(QPointF pt, const QRectF &bounds);
 
-QRect KRITAGLOBAL_EXPORT ensureRectNotSmaller(QRect rc, const QSize &size);
+template <class Rect>
+Rect ensureRectNotSmaller(Rect rc, const decltype(Rect().size()) &size)
+{
+    typedef decltype(Rect().size()) Size;
+    typedef decltype(Rect().top()) ValueType;
+
+    if (rc.width() < size.width() ||
+        rc.height() < size.height()) {
+
+        ValueType width = qMax(rc.width(), size.width());
+        ValueType  height = qMax(rc.height(), size.height());
+
+        rc = Rect(rc.topLeft(), Size(width, height));
+    }
+
+    return rc;
+}
 
 template <class Size>
 Size ensureSizeNotSmaller(const Size &size, const Size &bounds)
diff --git a/libs/ui/widgets/KoStrokeConfigWidget.cpp b/libs/ui/widgets/KoStrokeConfigWidget.cpp
index d8a6d5fbc35..37813348f19 100644
--- a/libs/ui/widgets/KoStrokeConfigWidget.cpp
+++ b/libs/ui/widgets/KoStrokeConfigWidget.cpp
@@ -489,17 +489,23 @@ void KoStrokeConfigWidget::applyMarkerChanges(int rawPosition)
 {
     KoFlake::MarkerPosition position = KoFlake::MarkerPosition(rawPosition);
 
-    KoMarker *marker = 0;
+    QScopedPointer<KoMarker> marker;
 
     switch (position) {
     case KoFlake::StartMarker:
-        marker = d->startMarkerSelector->marker();
+        if (d->startMarkerSelector->marker()) {
+            marker.reset(new KoMarker(*d->startMarkerSelector->marker()));
+        }
         break;
     case KoFlake::MidMarker:
-        marker = d->midMarkerSelector->marker();
+        if (d->midMarkerSelector->marker()) {
+            marker.reset(new KoMarker(*d->midMarkerSelector->marker()));
+        }
         break;
     case KoFlake::EndMarker:
-        marker = d->endMarkerSelector->marker();
+        if (d->endMarkerSelector->marker()) {
+            marker.reset(new KoMarker(*d->endMarkerSelector->marker()));
+        }
         break;
     }
 
@@ -518,7 +524,7 @@ void KoStrokeConfigWidget::applyMarkerChanges(int rawPosition)
 
     if (pathShapes.isEmpty()) return;
 
-    KUndo2Command* command = new KoPathShapeMarkerCommand(pathShapes, marker, position);
+    KUndo2Command* command = new KoPathShapeMarkerCommand(pathShapes, marker.take(), position);
     canvasController->canvas()->addCommand(command);
 }
 
diff --git a/libs/widgets/KoMarkerModel.cpp b/libs/widgets/KoMarkerModel.cpp
index 6194eacbe98..36af2299191 100644
--- a/libs/widgets/KoMarkerModel.cpp
+++ b/libs/widgets/KoMarkerModel.cpp
@@ -26,10 +26,13 @@
 
 
 KoMarkerModel::KoMarkerModel(const QList<KoMarker*> markers, KoFlake::MarkerPosition position, QObject *parent)
-: QAbstractListModel(parent)
-, m_markers(markers)
-, m_markerPosition(position)
+    : QAbstractListModel(parent)
+    , m_markerPosition(position)
+    , m_temporaryMarkerPosition(-1)
 {
+    Q_FOREACH (KoMarker *marker, markers) {
+        m_markers.append(QExplicitlySharedDataPointer<KoMarker>(marker));
+    }
 }
 
 KoMarkerModel::~KoMarkerModel()
@@ -51,7 +54,7 @@ QVariant KoMarkerModel::data(const QModelIndex &index, int role) const
     switch(role) {
     case Qt::DecorationRole:
         if (index.row() < m_markers.size()) {
-            return QVariant::fromValue<KoMarker*>(m_markers.at(index.row()));
+            return QVariant::fromValue<KoMarker*>(m_markers.at(index.row()).data());
         }
         return QVariant();
     case Qt::SizeHintRole:
@@ -63,7 +66,42 @@ QVariant KoMarkerModel::data(const QModelIndex &index, int role) const
 
 int KoMarkerModel::markerIndex(KoMarker *marker) const
 {
-    return m_markers.indexOf(marker);
+    for (int i = 0; i < m_markers.size(); i++) {
+        if (m_markers[i] == marker) return i;
+        if (m_markers[i] && marker && *m_markers[i] == *marker) return i;
+    }
+
+    return false;
+}
+
+int KoMarkerModel::addTemporaryMarker(KoMarker *marker)
+{
+    if (m_temporaryMarkerPosition >= 0) {
+        removeTemporaryMarker();
+    }
+
+    m_temporaryMarkerPosition = m_markers.size() > 0 ? 1 : 0;
+    beginInsertRows(QModelIndex(), m_temporaryMarkerPosition, m_temporaryMarkerPosition);
+    m_markers.prepend(QExplicitlySharedDataPointer<KoMarker>(marker));
+
+    endInsertRows();
+
+    return m_temporaryMarkerPosition;
+}
+
+void KoMarkerModel::removeTemporaryMarker()
+{
+    if (m_temporaryMarkerPosition >= 0) {
+        beginRemoveRows(QModelIndex(), m_temporaryMarkerPosition, m_temporaryMarkerPosition);
+        m_markers.removeAt(m_temporaryMarkerPosition);
+        m_temporaryMarkerPosition = -1;
+        endRemoveRows();
+    }
+}
+
+int KoMarkerModel::temporaryMarkerPosition() const
+{
+    return m_temporaryMarkerPosition;
 }
 
 QVariant KoMarkerModel::marker(int index, int role) const
@@ -75,7 +113,7 @@ QVariant KoMarkerModel::marker(int index, int role) const
     switch(role) {
         case Qt::DecorationRole:
             if (index< m_markers.size()) {
-                return QVariant::fromValue<KoMarker*>(m_markers.at(index));
+                return QVariant::fromValue<KoMarker*>(m_markers.at(index).data());
             }
             return QVariant();
         case Qt::SizeHintRole:
diff --git a/libs/widgets/KoMarkerModel.h b/libs/widgets/KoMarkerModel.h
index da50b504f1e..46982e26081 100644
--- a/libs/widgets/KoMarkerModel.h
+++ b/libs/widgets/KoMarkerModel.h
@@ -22,6 +22,8 @@
 
 #include <KoFlake.h>
 #include <QAbstractListModel>
+#include <QExplicitlySharedDataPointer>
+
 
 class KoMarker;
 
@@ -35,12 +37,21 @@ public:
     QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
 
     int markerIndex(KoMarker *marker) const;
+
+    // returns index of the newly added temporary marker
+    int addTemporaryMarker(KoMarker *marker);
+    // removes a temporary marker added by \ref addTemporaryMarker
+    void removeTemporaryMarker();
+
+    int temporaryMarkerPosition() const;
+
     QVariant marker(int index, int role = Qt::UserRole) const;
     KoFlake::MarkerPosition position() const;
 
 private:
-    QList<KoMarker*> m_markers;
+    QList<QExplicitlySharedDataPointer<KoMarker>> m_markers;
     KoFlake::MarkerPosition m_markerPosition;
+    int m_temporaryMarkerPosition;
 };
 
 #endif /* KOMARKERMODEL_H */
diff --git a/libs/widgets/KoMarkerSelector.cpp b/libs/widgets/KoMarkerSelector.cpp
index 14c40dc9183..11235b5815a 100644
--- a/libs/widgets/KoMarkerSelector.cpp
+++ b/libs/widgets/KoMarkerSelector.cpp
@@ -82,6 +82,11 @@ void KoMarkerSelector::setMarker(KoMarker *marker)
     int index = d->model->markerIndex(marker);
     if (index >= 0) {
         setCurrentIndex(index);
+        if (index != d->model->temporaryMarkerPosition()) {
+            d->model->removeTemporaryMarker();
+        }
+    } else {
+        setCurrentIndex(d->model->addTemporaryMarker(marker));
     }
 }
 



More information about the kimageshop mailing list