Improvements to curve renderer
This contains numerous updates/improvements to the curve renderer and the manual test. Most notably, it replaces the Delaunay triangulator with a more robust algorithm which uses qTriangulate() for the internal hull (like the basic code path) and a separate triangulation for curves which adds triangles on the outside and calculates texture coordinates accordingly. This gives antialiasing on straight lines as well. It also has multiple improvements to the cubic-to-quad algorithm, for instance circles now look as they should. Currently, the QTriangulatingStroker is used for solid strokes (causing jagged edges there) and the QPainterPath stroker is used for dash strokes (causing some issues with antialiasing). Pick-to: 6.6 Task-number: QTBUG-104122 Change-Id: Ic195aa874dc73c62359a93764ef38a09efb012e3 Reviewed-by: Paul Olav Tvete <paul.tvete@qt.io> Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Eirik Aavitsland <eirik.aavitsland@qt.io> Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io>
This commit is contained in:
parent
2b9aa54610
commit
4a1e5d8e74
|
@ -21,7 +21,6 @@ qt_internal_add_qml_module(QuickShapesPrivate
|
||||||
qquickshapegenericrenderer.cpp qquickshapegenericrenderer_p.h
|
qquickshapegenericrenderer.cpp qquickshapegenericrenderer_p.h
|
||||||
qquickshapesglobal.h qquickshapesglobal_p.h
|
qquickshapesglobal.h qquickshapesglobal_p.h
|
||||||
qquickshapecurverenderer.cpp qquickshapecurverenderer_p.h qquickshapecurverenderer_p_p.h
|
qquickshapecurverenderer.cpp qquickshapecurverenderer_p.h qquickshapecurverenderer_p_p.h
|
||||||
qt_delaunay_triangulator.cpp
|
|
||||||
qt_quadratic_bezier.cpp
|
qt_quadratic_bezier.cpp
|
||||||
qquickshapesoftwarerenderer.cpp qquickshapesoftwarerenderer_p.h
|
qquickshapesoftwarerenderer.cpp qquickshapesoftwarerenderer_p.h
|
||||||
PUBLIC_LIBRARIES
|
PUBLIC_LIBRARIES
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -59,6 +59,7 @@ public:
|
||||||
static QuadPath fromPainterPath(const QPainterPath &path);
|
static QuadPath fromPainterPath(const QPainterPath &path);
|
||||||
QPainterPath toPainterPath() const;
|
QPainterPath toPainterPath() const;
|
||||||
QuadPath subPathsClosed() const;
|
QuadPath subPathsClosed() const;
|
||||||
|
QuadPath flattened() const;
|
||||||
|
|
||||||
class Element
|
class Element
|
||||||
{
|
{
|
||||||
|
@ -131,6 +132,8 @@ public:
|
||||||
return QVector2D(-tan.y(), tan.x());
|
return QVector2D(-tan.y(), tan.x());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float extent() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int intersectionsAtY(float y, float *fractions) const;
|
int intersectionsAtY(float y, float *fractions) const;
|
||||||
|
|
||||||
|
@ -306,6 +309,7 @@ private:
|
||||||
QPainterPath fillPath;
|
QPainterPath fillPath;
|
||||||
QPainterPath originalPath;
|
QPainterPath originalPath;
|
||||||
QuadPath path;
|
QuadPath path;
|
||||||
|
QuadPath qPath; // TODO: better name
|
||||||
QColor fillColor;
|
QColor fillColor;
|
||||||
Qt::FillRule fillRule = Qt::OddEvenFill;
|
Qt::FillRule fillRule = Qt::OddEvenFill;
|
||||||
QPen pen;
|
QPen pen;
|
||||||
|
@ -321,8 +325,9 @@ private:
|
||||||
void deleteAndClear(NodeList *nodeList);
|
void deleteAndClear(NodeList *nodeList);
|
||||||
|
|
||||||
QVector<QSGGeometryNode *> addPathNodesBasic(const PathData &pathData, NodeList *debugNodes);
|
QVector<QSGGeometryNode *> addPathNodesBasic(const PathData &pathData, NodeList *debugNodes);
|
||||||
|
QVector<QSGGeometryNode *> addPathNodesLineShader(const PathData &pathData, NodeList *debugNodes);
|
||||||
QVector<QSGGeometryNode *> addPathNodesDelaunayTest(const PathData &pathData, NodeList *debugNodes);
|
QVector<QSGGeometryNode *> addStrokeNodes(const PathData &pathData, NodeList *debugNodes);
|
||||||
|
QVector<QSGGeometryNode *> addNodesStrokeShader(const PathData &pathData, NodeList *debugNodes);
|
||||||
|
|
||||||
QSGGeometryNode *addLoopBlinnNodes(const QTriangleSet &triangles,
|
QSGGeometryNode *addLoopBlinnNodes(const QTriangleSet &triangles,
|
||||||
const QVarLengthArray<quint32> &extraIndices,
|
const QVarLengthArray<quint32> &extraIndices,
|
||||||
|
|
|
@ -25,52 +25,9 @@ QT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
Q_DECLARE_LOGGING_CATEGORY(lcShapeCurveRenderer);
|
Q_DECLARE_LOGGING_CATEGORY(lcShapeCurveRenderer);
|
||||||
|
|
||||||
struct QtPathVertex
|
|
||||||
{
|
|
||||||
QVector2D point;
|
|
||||||
int id;
|
|
||||||
quint32 binX = 0;
|
|
||||||
quint32 binY = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct QtPathEdge
|
|
||||||
{
|
|
||||||
quint32 startIndex;
|
|
||||||
quint32 endIndex;
|
|
||||||
int id;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct QtPathTriangle
|
|
||||||
{
|
|
||||||
QtPathTriangle(quint32 i1, quint32 i2, quint32 i3, int d) : v1Index(i1), v2Index(i2), v3Index(i3), id(d) {}
|
|
||||||
quint32 v1Index;
|
|
||||||
quint32 v2Index;
|
|
||||||
quint32 v3Index;
|
|
||||||
|
|
||||||
quint32 adjacentTriangle1 = quint32(-1); // Adjacent to v1-v2
|
|
||||||
quint32 adjacentTriangle2 = quint32(-1); // Adjacent to v2-v3
|
|
||||||
quint32 adjacentTriangle3 = quint32(-1); // Adjacent to v3-v1
|
|
||||||
|
|
||||||
// Used by triangulator
|
|
||||||
quint32 lastSeenVertex = quint32(-1);
|
|
||||||
|
|
||||||
// Should this triangle be rendered? Set to false for triangles connecting to super-triangle
|
|
||||||
bool isValid = true;
|
|
||||||
|
|
||||||
int id;
|
|
||||||
};
|
|
||||||
|
|
||||||
constexpr bool operator==(const QtPathTriangle& lhs, const QtPathTriangle& rhs)
|
|
||||||
{
|
|
||||||
return lhs.id == rhs.id
|
|
||||||
&& lhs.v1Index == rhs.v1Index
|
|
||||||
&& lhs.v2Index == rhs.v2Index
|
|
||||||
&& lhs.v3Index == rhs.v3Index;
|
|
||||||
}
|
|
||||||
|
|
||||||
class QBezier;
|
class QBezier;
|
||||||
Q_QUICKSHAPES_PRIVATE_EXPORT QPolygonF qt_toQuadratics(const QBezier &b, qreal errorLimit = 0.2);
|
Q_QUICKSHAPES_PRIVATE_EXPORT void qt_toQuadratics(const QBezier &b, QPolygonF *out,
|
||||||
Q_QUICKSHAPES_PRIVATE_EXPORT QList<QtPathTriangle> qtDelaunayTriangulator(const QList<QtPathVertex> &vertices, const QList<QtPathEdge> &edges, const QPainterPath &path);
|
qreal errorLimit = 0.01);
|
||||||
|
|
||||||
QT_END_NAMESPACE
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -8,38 +8,81 @@
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
#if 0
|
|
||||||
static bool qt_isQuadratic(const QBezier &b)
|
|
||||||
{
|
|
||||||
const qreal f = 3.0 / 2.0;
|
|
||||||
const QPointF c1 = b.pt1() + f * (b.pt2() - b.pt1());
|
|
||||||
const QPointF c2 = b.pt4() + f * (b.pt3() - b.pt4());
|
|
||||||
return c1 == c2;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static qreal qt_scoreQuadratic(const QBezier &b, QPointF qcp)
|
static qreal qt_scoreQuadratic(const QBezier &b, QPointF qcp)
|
||||||
{
|
{
|
||||||
// Construct a cubic from the quadratic, and compare its control points to the originals'
|
static bool init = false;
|
||||||
const QRectF bounds = b.bounds();
|
const int numSteps = 21;
|
||||||
qreal dim = QLineF(bounds.topLeft(), bounds.bottomRight()).length();
|
Q_STATIC_ASSERT(numSteps % 2 == 1); // numTries must be odd
|
||||||
if (qFuzzyIsNull(dim))
|
static qreal t2s[numSteps];
|
||||||
return 0;
|
static qreal tmts[numSteps];
|
||||||
const qreal f = 2.0 / 3;
|
if (!init) {
|
||||||
const QPointF cp1 = b.pt1() + f * (qcp - b.pt1());
|
// Precompute bezier factors
|
||||||
const QPointF cp2 = b.pt4() + f * (qcp - b.pt4());
|
qreal t = 0.20;
|
||||||
const QLineF d1(b.pt2(), cp1);
|
const qreal step = (1 - (2 * t)) / (numSteps - 1);
|
||||||
const QLineF d2(b.pt3(), cp2);
|
for (int i = 0; i < numSteps; i++) {
|
||||||
return qMax(d1.length(), d2.length()) / dim;
|
t2s[i] = t * t;
|
||||||
|
tmts[i] = 2 * t * (1 - t);
|
||||||
|
t += step;
|
||||||
|
}
|
||||||
|
init = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QPointF midPoint = b.midPoint();
|
||||||
|
auto distForIndex = [&](int i) -> qreal {
|
||||||
|
QPointF qp = (t2s[numSteps - 1 - i] * b.pt1()) + (tmts[i] * qcp) + (t2s[i] * b.pt4());
|
||||||
|
QPointF d = midPoint - qp;
|
||||||
|
return QPointF::dotProduct(d, d);
|
||||||
|
};
|
||||||
|
|
||||||
|
const int halfSteps = (numSteps - 1) / 2;
|
||||||
|
bool foundIt = false;
|
||||||
|
const qreal centerDist = distForIndex(halfSteps);
|
||||||
|
qreal minDist = centerDist;
|
||||||
|
// Search for the minimum in right half
|
||||||
|
for (int i = 0; i < halfSteps; i++) {
|
||||||
|
qreal tDist = distForIndex(halfSteps + 1 + i);
|
||||||
|
if (tDist < minDist) {
|
||||||
|
minDist = tDist;
|
||||||
|
} else {
|
||||||
|
foundIt = (i > 0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!foundIt) {
|
||||||
|
// Search in left half
|
||||||
|
minDist = centerDist;
|
||||||
|
for (int i = 0; i < halfSteps; i++) {
|
||||||
|
qreal tDist = distForIndex(halfSteps - 1 - i);
|
||||||
|
if (tDist < minDist) {
|
||||||
|
minDist = tDist;
|
||||||
|
} else {
|
||||||
|
foundIt = (i > 0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return foundIt ? minDist : centerDist;
|
||||||
}
|
}
|
||||||
|
|
||||||
static qreal qt_quadraticForCubic(const QBezier &b, QPointF *qcp)
|
static QPointF qt_quadraticForCubic(const QBezier &b)
|
||||||
{
|
{
|
||||||
const QLineF st = b.startTangent();
|
const QLineF st = b.startTangent();
|
||||||
const QLineF et = b.endTangent();
|
const QLineF et = b.endTangent();
|
||||||
if (st.intersects(et, qcp) == QLineF::NoIntersection)
|
const QPointF midPoint = b.midPoint();
|
||||||
*qcp = b.midPoint();
|
bool valid = true;
|
||||||
return qt_scoreQuadratic(b, *qcp);
|
QPointF quadControlPoint;
|
||||||
|
if (st.intersects(et, &quadControlPoint) == QLineF::NoIntersection) {
|
||||||
|
valid = false;
|
||||||
|
} else {
|
||||||
|
// Check if intersection is on wrong side
|
||||||
|
const QPointF bl = b.pt4() - b.pt1();
|
||||||
|
const QPointF ml = midPoint - b.pt1();
|
||||||
|
const QPointF ql = quadControlPoint - b.pt1();
|
||||||
|
qreal cx1 = (ml.x() * bl.y()) - (ml.y() * bl.x());
|
||||||
|
qreal cx2 = (ql.x() * bl.y()) - (ql.y() * bl.x());
|
||||||
|
valid = (std::signbit(cx1) == std::signbit(cx2));
|
||||||
|
}
|
||||||
|
return valid ? quadControlPoint : midPoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int qt_getInflectionPoints(const QBezier &orig, qreal *tpoints)
|
static int qt_getInflectionPoints(const QBezier &orig, qreal *tpoints)
|
||||||
|
@ -57,22 +100,34 @@ static int qt_getInflectionPoints(const QBezier &orig, qreal *tpoints)
|
||||||
const QBezier n = orig.mapBy(xf);
|
const QBezier n = orig.mapBy(xf);
|
||||||
Q_ASSERT(n.pt1() == QPoint() && qFuzzyIsNull(float(n.pt4().y())));
|
Q_ASSERT(n.pt1() == QPoint() && qFuzzyIsNull(float(n.pt4().y())));
|
||||||
|
|
||||||
const qreal p = n.pt3().x() * n.pt2().y();
|
const qreal x2 = n.pt2().x();
|
||||||
const qreal q = n.pt4().x() * n.pt2().y();
|
const qreal x3 = n.pt3().x();
|
||||||
const qreal r = n.pt2().x() * n.pt3().y();
|
const qreal x4 = n.pt4().x();
|
||||||
const qreal s = n.pt4().x() * n.pt3().y();
|
const qreal y2 = n.pt2().y();
|
||||||
|
const qreal y3 = n.pt3().y();
|
||||||
|
|
||||||
const qreal a = 36 * ((-3 * p) + (2 * q) + (3 * r) - s);
|
const qreal p = x3 * y2;
|
||||||
if (!a)
|
const qreal q = x4 * y2;
|
||||||
return 0;
|
const qreal r = x2 * y3;
|
||||||
const qreal b = -18 * (((3 * p) - q) - (3 * r));
|
const qreal s = x4 * y3;
|
||||||
|
|
||||||
|
const qreal a = 18 * ((-3 * p) + (2 * q) + (3 * r) - s);
|
||||||
|
if (qFuzzyIsNull(float(a))) {
|
||||||
|
if (std::signbit(y2) != std::signbit(y3) && qFuzzyCompare(float(x4 - x3), float(x2))) {
|
||||||
|
tpoints[0] = 0.5; // approx
|
||||||
|
return 1;
|
||||||
|
} else if (!a) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const qreal b = 18 * (((3 * p) - q) - (3 * r));
|
||||||
const qreal c = 18 * (r - p);
|
const qreal c = 18 * (r - p);
|
||||||
const qreal rad = (b * b) - (2 * a * c);
|
const qreal rad = (b * b) - (4 * a * c);
|
||||||
if (rad < 0)
|
if (rad < 0)
|
||||||
return 0;
|
return 0;
|
||||||
const qreal sqr = qSqrt(rad);
|
const qreal sqr = qSqrt(rad);
|
||||||
const qreal root1 = (b + sqr) / a;
|
const qreal root1 = (-b + sqr) / (2 * a);
|
||||||
const qreal root2 = (b - sqr) / a;
|
const qreal root2 = (-b - sqr) / (2 * a);
|
||||||
|
|
||||||
int res = 0;
|
int res = 0;
|
||||||
if (isValidRoot(root1))
|
if (isValidRoot(root1))
|
||||||
|
@ -86,54 +141,53 @@ static int qt_getInflectionPoints(const QBezier &orig, qreal *tpoints)
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void qt_addToQuadratics(const QBezier &b, QPolygonF *p, qreal spanlength, qreal errorLimit)
|
static void qt_addToQuadratics(const QBezier &b, QPolygonF *p, int maxSplits, qreal maxDiff)
|
||||||
{
|
{
|
||||||
Q_ASSERT((spanlength > 0) && !(spanlength > 1));
|
QPointF qcp = qt_quadraticForCubic(b);
|
||||||
|
if (maxSplits <= 0 || qt_scoreQuadratic(b, qcp) < maxDiff) {
|
||||||
QPointF qcp;
|
|
||||||
bool isOk = (qt_quadraticForCubic(b, &qcp) < errorLimit); // error limit, careful
|
|
||||||
if (isOk || spanlength < 0.1) {
|
|
||||||
p->append(qcp);
|
p->append(qcp);
|
||||||
p->append(b.pt4());
|
p->append(b.pt4());
|
||||||
} else {
|
} else {
|
||||||
QBezier rhs = b;
|
QBezier rhs = b;
|
||||||
QBezier lhs;
|
QBezier lhs;
|
||||||
rhs.parameterSplitLeft(0.5, &lhs);
|
rhs.parameterSplitLeft(0.5, &lhs);
|
||||||
qt_addToQuadratics(lhs, p, spanlength / 2, errorLimit);
|
qt_addToQuadratics(lhs, p, maxSplits - 1, maxDiff);
|
||||||
qt_addToQuadratics(rhs, p, spanlength / 2, errorLimit);
|
qt_addToQuadratics(rhs, p, maxSplits - 1, maxDiff);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QPolygonF qt_toQuadratics(const QBezier &b, qreal errorLimit)
|
void qt_toQuadratics(const QBezier &b, QPolygonF *out, qreal errorLimit)
|
||||||
{
|
{
|
||||||
|
out->resize(0);
|
||||||
|
out->append(b.pt1());
|
||||||
|
|
||||||
|
{
|
||||||
|
// Shortcut if the cubic is really a quadratic
|
||||||
|
const qreal f = 3.0 / 2.0;
|
||||||
|
const QPointF c1 = b.pt1() + f * (b.pt2() - b.pt1());
|
||||||
|
const QPointF c2 = b.pt4() + f * (b.pt3() - b.pt4());
|
||||||
|
if (c1 == c2) {
|
||||||
|
out->append(c1);
|
||||||
|
out->append(b.pt4());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QPolygonF res;
|
|
||||||
res.reserve(16);
|
|
||||||
res.append(b.pt1());
|
|
||||||
const QRectF cpr = b.bounds();
|
const QRectF cpr = b.bounds();
|
||||||
qreal epsilon = QLineF(cpr.topLeft(), cpr.bottomRight()).length() * 0.5 * errorLimit;
|
const QPointF dim = cpr.bottomRight() - cpr.topLeft();
|
||||||
bool degenerate = false;
|
qreal maxDiff = QPointF::dotProduct(dim, dim) * errorLimit * errorLimit; // maxdistance^2
|
||||||
if (QLineF(b.pt2(), b.pt1()).length() < epsilon) {
|
|
||||||
res.append(b.pt3());
|
|
||||||
degenerate = true;
|
|
||||||
} else if (QLineF(b.pt4(), b.pt3()).length() < epsilon) {
|
|
||||||
res.append(b.pt2());
|
|
||||||
degenerate = true;
|
|
||||||
}
|
|
||||||
if (degenerate) {
|
|
||||||
res.append(b.pt4());
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
qreal infPoints[2];
|
qreal infPoints[2];
|
||||||
int numInfPoints = qt_getInflectionPoints(b, infPoints);
|
int numInfPoints = qt_getInflectionPoints(b, infPoints);
|
||||||
|
const int maxSubSplits = numInfPoints > 0 ? 2 : 3;
|
||||||
qreal t0 = 0;
|
qreal t0 = 0;
|
||||||
for (int i = 0; i < numInfPoints + 1; i++) { // #segments == #inflectionpoints + 1
|
// number of main segments == #inflectionpoints + 1
|
||||||
|
for (int i = 0; i < numInfPoints + 1; i++) {
|
||||||
qreal t1 = (i < numInfPoints) ? infPoints[i] : 1;
|
qreal t1 = (i < numInfPoints) ? infPoints[i] : 1;
|
||||||
QBezier segment = b.bezierOnInterval(t0, t1);
|
QBezier segment = b.bezierOnInterval(t0, t1);
|
||||||
qt_addToQuadratics(segment, &res, t1 - t0, errorLimit);
|
qt_addToQuadratics(segment, out, maxSubSplits, maxDiff);
|
||||||
t0 = t1;
|
t0 = t1;
|
||||||
}
|
}
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QT_END_NAMESPACE
|
QT_END_NAMESPACE
|
||||||
|
|
|
@ -14,7 +14,6 @@ qt_internal_add_manual_test(painterPathQuickShape
|
||||||
Qt::Quick
|
Qt::Quick
|
||||||
Qt::QuickPrivate
|
Qt::QuickPrivate
|
||||||
Qt::QuickShapesPrivate
|
Qt::QuickShapesPrivate
|
||||||
Qt::SvgPrivate
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -29,6 +28,7 @@ set(qml_resource_files
|
||||||
"SmallPolygon.qml"
|
"SmallPolygon.qml"
|
||||||
"Squircle.qml"
|
"Squircle.qml"
|
||||||
"ControlledShape.qml"
|
"ControlledShape.qml"
|
||||||
|
"Mussel.qml"
|
||||||
"Graziano.ttf"
|
"Graziano.ttf"
|
||||||
"CubicShape.qml"
|
"CubicShape.qml"
|
||||||
"hand-print.svg"
|
"hand-print.svg"
|
||||||
|
|
|
@ -9,10 +9,13 @@ import QtQuick.Dialogs
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
property real scale: +scaleSlider.value.toFixed(4)
|
property real scale: +scaleSlider.value.toFixed(4)
|
||||||
property color outlineColor: enableOutline.checked ? Qt.rgba(outlineColor.color.r, outlineColor.color.g, outlineColor.color.b, pathAlpha) : Qt.rgba(0,0,0,0)
|
property color backgroundColor: setBackground.checked ? Qt.rgba(bgColor.color.r, bgColor.color.g, bgColor.color.b, 1.0) : Qt.rgba(0,0,0,0)
|
||||||
|
|
||||||
|
property color outlineColor: enableOutline.checked ? Qt.rgba(outlineColor.color.r, outlineColor.color.g, outlineColor.color.b, outlineAlpha) : Qt.rgba(0,0,0,0)
|
||||||
property color fillColor: Qt.rgba(fillColor.color.r, fillColor.color.g, fillColor.color.b, pathAlpha)
|
property color fillColor: Qt.rgba(fillColor.color.r, fillColor.color.g, fillColor.color.b, pathAlpha)
|
||||||
property alias pathAlpha: alphaSlider.value
|
property alias pathAlpha: alphaSlider.value
|
||||||
property alias outlineWidth: outlineWidth.value
|
property alias outlineAlpha: outlineAlphaSlider.value
|
||||||
|
property real outlineWidth: cosmeticPen.checked ? outlineWidth.value / scale : outlineWidth.value ** 2
|
||||||
property alias outlineStyle: outlineStyle.currentValue
|
property alias outlineStyle: outlineStyle.currentValue
|
||||||
property alias capStyle: capStyle.currentValue
|
property alias capStyle: capStyle.currentValue
|
||||||
property alias joinStyle: joinStyle.currentValue
|
property alias joinStyle: joinStyle.currentValue
|
||||||
|
@ -27,6 +30,7 @@ Item {
|
||||||
property alias preferCurve: rendererLabel.preferCurve
|
property alias preferCurve: rendererLabel.preferCurve
|
||||||
|
|
||||||
property int subShape: pickSubShape.checked ? subShapeSelector.value : -1
|
property int subShape: pickSubShape.checked ? subShapeSelector.value : -1
|
||||||
|
property bool subShapeGreaterThan : pickSubShapeGreaterThan.checked
|
||||||
|
|
||||||
property real pathMargin: marginEdit.text
|
property real pathMargin: marginEdit.text
|
||||||
|
|
||||||
|
@ -100,10 +104,10 @@ Item {
|
||||||
text: "Alpha"
|
text: "Alpha"
|
||||||
color: "white"
|
color: "white"
|
||||||
}
|
}
|
||||||
CheckBox { id: pickSubShape }
|
CheckBox {
|
||||||
Label {
|
|
||||||
text: "Pick SVG sub-shape"
|
text: "Pick SVG sub-shape"
|
||||||
color: "white"
|
id: pickSubShape
|
||||||
|
palette.windowText: "white"
|
||||||
}
|
}
|
||||||
SpinBox {
|
SpinBox {
|
||||||
id: subShapeSelector
|
id: subShapeSelector
|
||||||
|
@ -112,6 +116,35 @@ Item {
|
||||||
to: 999
|
to: 999
|
||||||
editable: true
|
editable: true
|
||||||
}
|
}
|
||||||
|
CheckBox {
|
||||||
|
id: pickSubShapeGreaterThan
|
||||||
|
visible: pickSubShape.checked
|
||||||
|
text: "show greater than"
|
||||||
|
palette.windowText: "white"
|
||||||
|
}
|
||||||
|
CheckBox {
|
||||||
|
id: setBackground
|
||||||
|
text: "Solid background"
|
||||||
|
palette.windowText: "white"
|
||||||
|
}
|
||||||
|
RowLayout {
|
||||||
|
visible: setBackground.checked
|
||||||
|
Rectangle {
|
||||||
|
id: bgColor
|
||||||
|
property color selectedColor: "#a9a9a9"
|
||||||
|
color: selectedColor
|
||||||
|
border.color: "black"
|
||||||
|
border.width: 2
|
||||||
|
width: 21
|
||||||
|
height: 21
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: {
|
||||||
|
bgColorDialog.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
RowLayout {
|
RowLayout {
|
||||||
Label {
|
Label {
|
||||||
|
@ -256,7 +289,6 @@ Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
text: "Outline width"
|
text: "Outline width"
|
||||||
color: "white"
|
color: "white"
|
||||||
|
@ -265,13 +297,34 @@ Item {
|
||||||
id: outlineWidth
|
id: outlineWidth
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
from: 0.0
|
from: 0.0
|
||||||
to: 100.0
|
to: 10.0
|
||||||
value: 10.0
|
value: Math.sqrt(10)
|
||||||
|
}
|
||||||
|
CheckBox {
|
||||||
|
id: cosmeticPen
|
||||||
|
text: "Cosmetic pen"
|
||||||
|
palette.windowText: "white"
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
text: "Outline alpha"
|
||||||
|
color: "white"
|
||||||
|
}
|
||||||
|
Slider {
|
||||||
|
id: outlineAlphaSlider
|
||||||
|
Layout.fillWidth: true
|
||||||
|
from: 0.0
|
||||||
|
to: 1.0
|
||||||
|
value: 1.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
ColorDialog {
|
||||||
|
id: bgColorDialog
|
||||||
|
selectedColor: bgColor.selectedColor
|
||||||
|
onAccepted: bgColor.selectedColor = selectedColor
|
||||||
|
}
|
||||||
ColorDialog {
|
ColorDialog {
|
||||||
id: outlineColorDialog
|
id: outlineColorDialog
|
||||||
selectedColor: outlineColor.selectedColor
|
selectedColor: outlineColor.selectedColor
|
||||||
|
|
|
@ -19,16 +19,17 @@ Rectangle {
|
||||||
property point pt: Qt.point(cx, cy)
|
property point pt: Qt.point(cx, cy)
|
||||||
|
|
||||||
DragHandler {
|
DragHandler {
|
||||||
|
id: handler
|
||||||
xAxis.minimum: -controlPanel.pathMargin
|
xAxis.minimum: -controlPanel.pathMargin
|
||||||
yAxis.minimum: -controlPanel.pathMargin
|
yAxis.minimum: -controlPanel.pathMargin
|
||||||
}
|
xAxis.onActiveValueChanged: {
|
||||||
onXChanged: {
|
cx = (x + width/2) / controlPanel.scale
|
||||||
cx = (x + width/2) / controlPanel.scale
|
controlPanel.updatePath()
|
||||||
controlPanel.updatePath()
|
}
|
||||||
}
|
yAxis.onActiveValueChanged: {
|
||||||
onYChanged: {
|
cy = (y + height/2) / controlPanel.scale
|
||||||
cy = (y + height/2) / controlPanel.scale
|
controlPanel.updatePath()
|
||||||
controlPanel.updatePath()
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
|
@ -43,5 +44,4 @@ Rectangle {
|
||||||
y = cy * controlPanel.scale - height/2
|
y = cy * controlPanel.scale - height/2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ Item {
|
||||||
property alias strokeColor: shapePath.strokeColor
|
property alias strokeColor: shapePath.strokeColor
|
||||||
property alias strokeWidth: shapePath.strokeWidth
|
property alias strokeWidth: shapePath.strokeWidth
|
||||||
property alias fillRule: shapePath.fillRule
|
property alias fillRule: shapePath.fillRule
|
||||||
|
property alias shapeTransform: shape.transform
|
||||||
|
|
||||||
property alias startX: shapePath.startX
|
property alias startX: shapePath.startX
|
||||||
property alias startY: shapePath.startY
|
property alias startY: shapePath.startY
|
||||||
|
@ -58,48 +59,48 @@ Item {
|
||||||
|
|
||||||
property var gradients: [ null, linearGradient, radialGradient, conicalGradient ]
|
property var gradients: [ null, linearGradient, radialGradient, conicalGradient ]
|
||||||
|
|
||||||
Shape {
|
Item {
|
||||||
id: shape
|
|
||||||
x: 0
|
|
||||||
y: 0
|
|
||||||
preferredRendererType: controlPanel.preferCurve ? Shape.CurveRenderer : Shape.UnknownRenderer
|
|
||||||
onRendererTypeChanged: {
|
|
||||||
controlPanel.rendererName = rendererType == Shape.SoftwareRenderer ? "Software" :
|
|
||||||
rendererType == Shape.GeometryRenderer ? "Geometry" :
|
|
||||||
rendererType == Shape.CurveRenderer ? "Curve" : "Unknown";
|
|
||||||
}
|
|
||||||
|
|
||||||
transform: [
|
transform: [
|
||||||
Scale {
|
Scale {
|
||||||
xScale: controlPanel.scale
|
xScale: controlPanel.scale
|
||||||
yScale: controlPanel.scale
|
yScale: controlPanel.scale
|
||||||
origin.x: shape.implicitWidth / 2
|
origin.x: shape.implicitWidth / 2
|
||||||
origin.y: shape.implicitHeight / 2
|
origin.y: shape.implicitHeight / 2
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
Shape {
|
||||||
|
id: shape
|
||||||
|
x: 0
|
||||||
|
y: 0
|
||||||
|
preferredRendererType: controlPanel.preferCurve ? Shape.CurveRenderer : Shape.UnknownRenderer
|
||||||
|
onRendererTypeChanged: {
|
||||||
|
controlPanel.rendererName = rendererType == Shape.SoftwareRenderer ? "Software" :
|
||||||
|
rendererType == Shape.GeometryRenderer ? "Geometry" :
|
||||||
|
rendererType == Shape.CurveRenderer ? "Curve" : "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
ShapePath {
|
ShapePath {
|
||||||
id: shapePath
|
id: shapePath
|
||||||
fillRule: ShapePath.WindingFill
|
fillRule: ShapePath.WindingFill
|
||||||
fillGradient: gradients[controlPanel.gradientType]
|
fillGradient: gradients[controlPanel.gradientType]
|
||||||
strokeColor: controlPanel.outlineColor
|
strokeColor: controlPanel.outlineColor
|
||||||
fillColor: controlPanel.fillColor
|
fillColor: controlPanel.fillColor
|
||||||
strokeWidth: controlPanel.outlineWidth
|
strokeWidth: controlPanel.outlineWidth
|
||||||
strokeStyle: controlPanel.outlineStyle
|
strokeStyle: controlPanel.outlineStyle
|
||||||
joinStyle: controlPanel.joinStyle
|
joinStyle: controlPanel.joinStyle
|
||||||
capStyle: controlPanel.capStyle
|
capStyle: controlPanel.capStyle
|
||||||
}
|
}
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: topLevel.delegate
|
model: topLevel.delegate
|
||||||
onModelChanged: {
|
onModelChanged: {
|
||||||
shapePath.pathElements = []
|
shapePath.pathElements = []
|
||||||
for (var i = 0; i < model.length; ++i)
|
for (var i = 0; i < model.length; ++i)
|
||||||
shapePath.pathElements.push(model[i])
|
shapePath.pathElements.push(model[i])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: controlPanel
|
target: controlPanel
|
||||||
function onPathChanged() {
|
function onPathChanged() {
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
// Copyright (C) 2023 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Shapes
|
||||||
|
|
||||||
|
ControlledShape {
|
||||||
|
delegate: [
|
||||||
|
PathMove { x: p1.cx; y: p1.cy },
|
||||||
|
PathQuad { x: p2.cx; y: p2.cy; controlX: c1.cx; controlY: c1.cy },
|
||||||
|
PathQuad { x: p3.cx; y: p3.cy; controlX: c2.cx; controlY: c2.cy },
|
||||||
|
PathLine { x: p1.cx; y: p1.cy }
|
||||||
|
]
|
||||||
|
|
||||||
|
ControlPoint {
|
||||||
|
id: p1
|
||||||
|
cx: 200
|
||||||
|
cy: 200
|
||||||
|
}
|
||||||
|
ControlPoint {
|
||||||
|
id: c1
|
||||||
|
color: "blue"
|
||||||
|
cx: 600
|
||||||
|
cy: 0
|
||||||
|
}
|
||||||
|
ControlPoint {
|
||||||
|
id: p2
|
||||||
|
cx: 1000
|
||||||
|
cy: 200
|
||||||
|
}
|
||||||
|
ControlPoint {
|
||||||
|
id: c2
|
||||||
|
color: "blue"
|
||||||
|
cx: 1000
|
||||||
|
cy: 1000
|
||||||
|
}
|
||||||
|
ControlPoint {
|
||||||
|
id: p3
|
||||||
|
cx: 200
|
||||||
|
cy: 1000
|
||||||
|
}
|
||||||
|
}
|
|
@ -40,19 +40,24 @@ Item {
|
||||||
children = []
|
children = []
|
||||||
|
|
||||||
let first = true
|
let first = true
|
||||||
let pickOne = controlPanel.subShape
|
let pickShape = controlPanel.subShape
|
||||||
if (pickOne < 0)
|
let pickOne = pickShape >= 0 && !controlPanel.subShapeGreaterThan
|
||||||
console.debug("Creating " + pathLoader.paths.length + " SVG items")
|
let pickGreater = pickShape >= 0 && controlPanel.subShapeGreaterThan
|
||||||
|
if (pickOne)
|
||||||
|
console.log("Creating SVG item", pickShape, "out of", pathLoader.paths.length)
|
||||||
|
else if (pickGreater)
|
||||||
|
console.debug("Creating " + (pathLoader.paths.length - pickShape) + " SVG items")
|
||||||
else
|
else
|
||||||
console.log("Creating SVG item", pickOne, "out of", pathLoader.paths.length)
|
console.debug("Creating " + pathLoader.paths.length + " SVG items")
|
||||||
for (var i = 0; i < pathLoader.paths.length; ++i) {
|
for (var i = 0; i < pathLoader.paths.length; ++i) {
|
||||||
if (pickOne >= 0 && pickOne !== i)
|
if ((pickOne && pickShape !== i ) || (pickGreater && i < pickShape))
|
||||||
continue
|
continue
|
||||||
var s = pathLoader.paths[i]
|
var s = pathLoader.paths[i]
|
||||||
var fillColor = pathLoader.fillColors[i]
|
var fillColor = pathLoader.fillColors[i]
|
||||||
let strokeText = "";
|
let strokeText = "";
|
||||||
let strokeColor = pathLoader.strokeColors[i]
|
let strokeColor = pathLoader.strokeColors[i]
|
||||||
let strokeWidth = pathLoader.strokeWidths[i]
|
let strokeWidth = pathLoader.strokeWidths[i]
|
||||||
|
let transform = pathLoader.transforms[i]
|
||||||
if (strokeColor) {
|
if (strokeColor) {
|
||||||
if (!strokeWidth)
|
if (!strokeWidth)
|
||||||
strokeWidth = "1.0" // default value defined by SVG standard
|
strokeWidth = "1.0" // default value defined by SVG standard
|
||||||
|
@ -62,10 +67,11 @@ Item {
|
||||||
fillColor = "#00000000"
|
fillColor = "#00000000"
|
||||||
}
|
}
|
||||||
|
|
||||||
var obj = Qt.createQmlObject("import QtQuick\nimport QtQuick.Shapes\n ControlledShape { "
|
var obj = Qt.createQmlObject("import QtQuick\nimport QtQuick.Shapes\n ControlledShape { \n"
|
||||||
+ "fillColor: \"" + fillColor + "\";"
|
+ "fillColor: \"" + fillColor + "\";\n"
|
||||||
+ strokeText
|
+ "shapeTransform: Matrix4x4 { matrix: Qt.matrix4x4(" + transform + "); }\n"
|
||||||
+ "fillRule: ShapePath.WindingFill; delegate: [ PathSvg { path: \"" + s + "\"; } ] }",
|
+ strokeText + "\n"
|
||||||
|
+ "fillRule: ShapePath.WindingFill; delegate: [ PathSvg { path: \"" + s + "\"; } ] }\n",
|
||||||
topLevel, "SvgPathComponent_" + i)
|
topLevel, "SvgPathComponent_" + i)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,10 @@ Window {
|
||||||
text: "CubicShape"
|
text: "CubicShape"
|
||||||
source: "CubicShape.qml"
|
source: "CubicShape.qml"
|
||||||
}
|
}
|
||||||
|
ListElement {
|
||||||
|
text: "Mussel"
|
||||||
|
source: "Mussel.qml"
|
||||||
|
}
|
||||||
ListElement {
|
ListElement {
|
||||||
text: "Arc Direction"
|
text: "Arc Direction"
|
||||||
source: "arcDirection.qml"
|
source: "arcDirection.qml"
|
||||||
|
@ -138,6 +142,11 @@ Window {
|
||||||
source: "qrc:/background.png"
|
source: "qrc:/background.png"
|
||||||
smooth: true
|
smooth: true
|
||||||
}
|
}
|
||||||
|
Rectangle {
|
||||||
|
id: solidBackground
|
||||||
|
anchors.fill: flickable
|
||||||
|
color: controlPanel.backgroundColor
|
||||||
|
}
|
||||||
|
|
||||||
Flickable {
|
Flickable {
|
||||||
id: flickable
|
id: flickable
|
||||||
|
@ -152,10 +161,9 @@ Window {
|
||||||
WheelHandler {
|
WheelHandler {
|
||||||
onWheel: (event)=> {
|
onWheel: (event)=> {
|
||||||
let scale = controlPanel.scale
|
let scale = controlPanel.scale
|
||||||
let posX = event.x
|
// position in scaled path:
|
||||||
let posY = event.y
|
let posX = event.x - controlPanel.pathMargin
|
||||||
let xOff = posX - flickable.contentX
|
let posY = event.y - controlPanel.pathMargin
|
||||||
let yOff = posY - flickable.contentY
|
|
||||||
|
|
||||||
let pathX = posX / scale
|
let pathX = posX / scale
|
||||||
let pathY = posY / scale
|
let pathY = posY / scale
|
||||||
|
@ -166,8 +174,12 @@ Window {
|
||||||
scale = scale / 1.1
|
scale = scale / 1.1
|
||||||
controlPanel.setScale(scale)
|
controlPanel.setScale(scale)
|
||||||
|
|
||||||
flickable.contentX = pathX * controlPanel.scale - xOff
|
scale = controlPanel.scale
|
||||||
flickable.contentY = pathY * controlPanel.scale - yOff
|
let scaledPosX = pathX * scale
|
||||||
|
let scaledPosY = pathY * scale
|
||||||
|
|
||||||
|
flickable.contentX += scaledPosX - posX
|
||||||
|
flickable.contentY += scaledPosY - posY
|
||||||
flickable.returnToBounds()
|
flickable.returnToBounds()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,11 @@
|
||||||
|
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QPainterPath>
|
#include <QPainterPath>
|
||||||
#include <QtSvg/private/qsvgtinydocument_p.h>
|
#include <QXmlStreamReader>
|
||||||
#include <QtSvg/private/qsvggraphics_p.h>
|
#include <QXmlStreamAttributes>
|
||||||
|
#include <QStack>
|
||||||
|
#include <QLocale>
|
||||||
|
#include <QMatrix4x4>
|
||||||
|
|
||||||
SvgPathLoader::SvgPathLoader()
|
SvgPathLoader::SvgPathLoader()
|
||||||
{
|
{
|
||||||
|
@ -20,6 +23,7 @@ struct SvgState
|
||||||
QString fillColor = {};
|
QString fillColor = {};
|
||||||
QString strokeColor = {};
|
QString strokeColor = {};
|
||||||
QString strokeWidth = {};
|
QString strokeWidth = {};
|
||||||
|
QMatrix4x4 transform;
|
||||||
};
|
};
|
||||||
|
|
||||||
void SvgPathLoader::loadPaths()
|
void SvgPathLoader::loadPaths()
|
||||||
|
@ -28,6 +32,7 @@ void SvgPathLoader::loadPaths()
|
||||||
m_fillColors.clear();
|
m_fillColors.clear();
|
||||||
m_strokeColors.clear();
|
m_strokeColors.clear();
|
||||||
m_strokeWidths.clear();
|
m_strokeWidths.clear();
|
||||||
|
m_transforms.clear();
|
||||||
if (m_source.isEmpty())
|
if (m_source.isEmpty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -49,8 +54,50 @@ void SvgPathLoader::loadPaths()
|
||||||
QXmlStreamAttributes attrs = reader.attributes();
|
QXmlStreamAttributes attrs = reader.attributes();
|
||||||
if (reader.isStartElement())
|
if (reader.isStartElement())
|
||||||
states.push(currentState);
|
states.push(currentState);
|
||||||
|
|
||||||
|
if (attrs.hasAttribute(QStringLiteral("transform"))) {
|
||||||
|
QString t = attrs.value(QStringLiteral("transform")).toString();
|
||||||
|
const bool isTranslate = t.startsWith(QStringLiteral("translate"));
|
||||||
|
const bool isScale = t.startsWith(QStringLiteral("scale"));
|
||||||
|
const bool isMatrix = t.startsWith(QStringLiteral("matrix"));
|
||||||
|
if (isTranslate || isScale || isMatrix) {
|
||||||
|
int pStart = t.indexOf(QLatin1Char('('));
|
||||||
|
int pEnd = t.indexOf(QLatin1Char(')'));
|
||||||
|
if (pStart >= 0 && pEnd > pStart + 1) {
|
||||||
|
t = t.mid(pStart + 1, pEnd - pStart - 1);
|
||||||
|
QStringList coords = t.split(QLatin1Char(','));
|
||||||
|
if (isMatrix && coords.size() == 6) {
|
||||||
|
QMatrix3x3 m;
|
||||||
|
m(0, 0) = coords.at(0).toDouble();
|
||||||
|
m(1, 0) = coords.at(1).toDouble();
|
||||||
|
m(2, 0) = 0.0f;
|
||||||
|
|
||||||
|
m(0, 1) = coords.at(2).toDouble();
|
||||||
|
m(1, 1) = coords.at(3).toDouble();
|
||||||
|
m(2, 1) = 0.0f;
|
||||||
|
|
||||||
|
m(0, 2) = coords.at(4).toDouble();
|
||||||
|
m(1, 2) = coords.at(5).toDouble();
|
||||||
|
m(2, 2) = 1.0f;
|
||||||
|
|
||||||
|
currentState.transform *= QMatrix4x4(m);
|
||||||
|
} else if (coords.size() == 2) {
|
||||||
|
qreal c1 = coords.first().toDouble();
|
||||||
|
qreal c2 = coords.last().toDouble();
|
||||||
|
|
||||||
|
if (isTranslate)
|
||||||
|
currentState.transform.translate(c1, c2);
|
||||||
|
else if (isScale)
|
||||||
|
currentState.transform.scale(c1, c2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (attrs.hasAttribute(QStringLiteral("fill"))) {
|
if (attrs.hasAttribute(QStringLiteral("fill"))) {
|
||||||
currentState.fillColor = attrs.value(QStringLiteral("fill")).toString();
|
currentState.fillColor = attrs.value(QStringLiteral("fill")).toString();
|
||||||
|
if (!currentState.fillColor.startsWith("#"))
|
||||||
|
currentState.fillColor = "";
|
||||||
} else if (attrs.hasAttribute(QStringLiteral("style"))) {
|
} else if (attrs.hasAttribute(QStringLiteral("style"))) {
|
||||||
QString s = attrs.value(QStringLiteral("style")).toString();
|
QString s = attrs.value(QStringLiteral("style")).toString();
|
||||||
int idx = s.indexOf(QStringLiteral("fill:"));
|
int idx = s.indexOf(QStringLiteral("fill:"));
|
||||||
|
@ -73,6 +120,22 @@ void SvgPathLoader::loadPaths()
|
||||||
m_fillColors.append(currentState.fillColor);
|
m_fillColors.append(currentState.fillColor);
|
||||||
m_strokeColors.append(currentState.strokeColor);
|
m_strokeColors.append(currentState.strokeColor);
|
||||||
m_strokeWidths.append(currentState.strokeWidth);
|
m_strokeWidths.append(currentState.strokeWidth);
|
||||||
|
|
||||||
|
QString t;
|
||||||
|
for (int i = 0; i < 4; ++i) {
|
||||||
|
if (i > 0)
|
||||||
|
t += QLatin1Char(',');
|
||||||
|
QVector4D row = currentState.transform.row(i);
|
||||||
|
|
||||||
|
QLocale c(QLocale::C);
|
||||||
|
t += QStringLiteral("%1, %2, %3, %4")
|
||||||
|
.arg(c.toString(row.x()))
|
||||||
|
.arg(c.toString(row.y()))
|
||||||
|
.arg(c.toString(row.z()))
|
||||||
|
.arg(c.toString(row.w()));
|
||||||
|
}
|
||||||
|
|
||||||
|
m_transforms.append(t);
|
||||||
if (attrs.hasAttribute(QStringLiteral("d"))) {
|
if (attrs.hasAttribute(QStringLiteral("d"))) {
|
||||||
m_paths.append(attrs.value(QStringLiteral("d")).toString());
|
m_paths.append(attrs.value(QStringLiteral("d")).toString());
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ class SvgPathLoader : public QObject
|
||||||
Q_PROPERTY(QStringList fillColors READ fillColors NOTIFY pathsChanged)
|
Q_PROPERTY(QStringList fillColors READ fillColors NOTIFY pathsChanged)
|
||||||
Q_PROPERTY(QStringList strokeColors READ strokeColors NOTIFY pathsChanged)
|
Q_PROPERTY(QStringList strokeColors READ strokeColors NOTIFY pathsChanged)
|
||||||
Q_PROPERTY(QStringList strokeWidths READ strokeWidths NOTIFY pathsChanged)
|
Q_PROPERTY(QStringList strokeWidths READ strokeWidths NOTIFY pathsChanged)
|
||||||
|
Q_PROPERTY(QStringList transforms READ transforms NOTIFY pathsChanged)
|
||||||
public:
|
public:
|
||||||
SvgPathLoader();
|
SvgPathLoader();
|
||||||
|
|
||||||
|
@ -53,6 +54,11 @@ public:
|
||||||
return m_strokeWidths;
|
return m_strokeWidths;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QStringList transforms() const
|
||||||
|
{
|
||||||
|
return m_transforms;
|
||||||
|
}
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void loadPaths();
|
void loadPaths();
|
||||||
|
|
||||||
|
@ -66,6 +72,7 @@ private:
|
||||||
QStringList m_fillColors;
|
QStringList m_fillColors;
|
||||||
QStringList m_strokeColors;
|
QStringList m_strokeColors;
|
||||||
QStringList m_strokeWidths;
|
QStringList m_strokeWidths;
|
||||||
|
QStringList m_transforms;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // SVGPATHLOADER_H
|
#endif // SVGPATHLOADER_H
|
||||||
|
|
Loading…
Reference in New Issue