[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