aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/quickvectorimage/generator/qquickitemgenerator.cpp170
-rw-r--r--src/quickvectorimage/generator/qquickitemgenerator_p.h5
-rw-r--r--src/quickvectorimage/generator/qquicknodeinfo_p.h16
-rw-r--r--src/quickvectorimage/generator/qquickqmlgenerator.cpp217
-rw-r--r--src/quickvectorimage/generator/qquickqmlgenerator_p.h1
-rw-r--r--src/quickvectorimage/generator/qsvgvisitorimpl.cpp337
-rw-r--r--src/quickvectorimage/generator/qsvgvisitorimpl_p.h3
-rw-r--r--tests/baseline/scenegraph/data/shared/svg/animateRotateTransform.svg13
-rw-r--r--tests/baseline/scenegraph/data/shared/svg/animateRotateTransformRemove.svg13
-rw-r--r--tests/baseline/scenegraph/data/shared/svg/animateScaleTransform.svg13
-rw-r--r--tests/baseline/scenegraph/data/shared/svg/animateScaleTransform2.svg14
-rw-r--r--tests/baseline/scenegraph/data/shared/svg/animateSkewTransformX.svg12
-rw-r--r--tests/baseline/scenegraph/data/shared/svg/animateSkewTransformY.svg12
-rw-r--r--tests/baseline/scenegraph/data/shared/svg/animateTransformAll.svg14
-rw-r--r--tests/baseline/scenegraph/data/shared/svg/animateTransformFreezeSum.svg13
-rw-r--r--tests/baseline/scenegraph/data/shared/svg/animateTransformRemove.svg13
-rw-r--r--tests/baseline/scenegraph/data/shared/svg/animateTransformRemoveSum.svg13
-rw-r--r--tests/baseline/scenegraph/data/shared/svg/animateTransformReplace.svg5
-rw-r--r--tests/baseline/scenegraph/data/shared/svg/animateTransformSum.svg5
-rw-r--r--tests/baseline/scenegraph/data/shared/svg/animateTransformSum2.svg13
-rw-r--r--tests/baseline/scenegraph/data/vectorimages/rotateTransformAnimation.qml29
-rw-r--r--tests/baseline/scenegraph/data/vectorimages/scaleTransformAnimation.qml29
-rw-r--r--tests/baseline/scenegraph/data/vectorimages/skewTransformAnimation.qml29
-rw-r--r--tests/baseline/scenegraph/data/vectorimages/transformAnimation.qml35
-rw-r--r--tests/manual/svg/CMakeLists.txt2
-rw-r--r--tools/svgtoqml/CMakeLists.txt1
26 files changed, 997 insertions, 30 deletions
diff --git a/src/quickvectorimage/generator/qquickitemgenerator.cpp b/src/quickvectorimage/generator/qquickitemgenerator.cpp
index ad5216aba0..797c9b0a30 100644
--- a/src/quickvectorimage/generator/qquickitemgenerator.cpp
+++ b/src/quickvectorimage/generator/qquickitemgenerator.cpp
@@ -33,13 +33,54 @@ QQuickItemGenerator::~QQuickItemGenerator()
void QQuickItemGenerator::generateNodeBase(const NodeInfo &info)
{
- if (!info.isDefaultTransform) {
+ auto xformProp = currentItem()->transform();
+
+ if (!info.transformAnimation.animationTypes.isEmpty()) {
+ QList<QQuickTransform *> transforms;
+
+ for (int i = info.transformAnimation.animationTypes.size() - 1; i >= 0; --i) {
+ QQuickTransform *transform = nullptr;
+ switch (info.transformAnimation.animationTypes.at(i)) {
+ case QTransform::TxTranslate:
+ transform = new QQuickTranslate;
+ break;
+ case QTransform::TxScale:
+ transform = new QQuickScale;
+ break;
+ case QTransform::TxRotate:
+ transform = new QQuickRotation;
+ static_cast<QQuickRotation *>(transform)->setOrigin(QVector3D(currentItem()->width() / 2.0f,
+ currentItem()->height() / 2.0f,
+ 0.0f));
+ break;
+ case QTransform::TxShear:
+ transform = new QQuickShear;
+ break;
+ default:
+ Q_UNREACHABLE();
+ }
+
+ xformProp.append(&xformProp, transform);
+ transforms.prepend(transform);
+ }
+
+ QQuickMatrix4x4 *mainTransform = nullptr;
+ if (!info.isDefaultTransform) {
+ const QMatrix4x4 m(info.transform);
+ mainTransform = new QQuickMatrix4x4;
+ mainTransform->setMatrix(m);
+ xformProp.append(&xformProp, mainTransform);
+ }
+
+ generateAnimateTransform(transforms,
+ mainTransform,
+ info);
+ } else if (!info.isDefaultTransform) {
auto sx = info.transform.m11();
auto sy = info.transform.m22();
auto x = info.transform.m31();
auto y = info.transform.m32();
- auto xformProp = currentItem()->transform();
if (info.transform.type() == QTransform::TxTranslate) {
auto *translate = new QQuickTranslate;
translate->setX(x);
@@ -58,6 +99,7 @@ void QQuickItemGenerator::generateNodeBase(const NodeInfo &info)
xformProp.append(&xformProp, xform);
}
}
+
if (!info.isDefaultOpacity) {
currentItem()->setOpacity(info.opacity);
}
@@ -347,6 +389,130 @@ void QQuickItemGenerator::generatePathContainer(const StructureNodeInfo &info)
addCurrentItem(shapeItem, info);
}
+void QQuickItemGenerator::generateAnimateTransform(const QList<QQuickTransform *> &transforms,
+ QQuickMatrix4x4 *mainTransform,
+ const NodeInfo &info)
+{
+ // Main animation which contains one animation for the finite part and optionally
+ // one animation for the infinite part
+ auto *mainAnimation = new QQuickSequentialAnimation(currentItem());
+
+ QQmlListProperty<QQuickAbstractAnimation> mainAnims = mainAnimation->animations();
+
+ auto *sequentialAnimation = new QQuickSequentialAnimation(mainAnimation);
+ sequentialAnimation->setLoops(1);
+ mainAnims.append(&mainAnims, sequentialAnimation);
+
+ const auto &keyFrames = info.transformAnimation.keyFrames;
+ qreal previousTimeCode = 0.0;
+ for (auto it = keyFrames.constBegin(); it != keyFrames.constEnd(); ++it) {
+ const auto &keyFrame = it.value();
+ const qreal timeCode = it.key().toReal();
+ const qreal frameTime = timeCode - previousTimeCode;
+ previousTimeCode = timeCode;
+
+ if (keyFrame.indefiniteAnimation && sequentialAnimation->loops() == 1) {
+ sequentialAnimation = new QQuickSequentialAnimation(mainAnimation);
+ sequentialAnimation->setLoops(-1);
+ mainAnims.append(&mainAnims, sequentialAnimation);
+ }
+
+ QQmlListProperty<QQuickAbstractAnimation> anims = sequentialAnimation->animations();
+
+ QQuickParallelAnimation *keyFrameAnimation = new QQuickParallelAnimation(sequentialAnimation);
+ anims.append(&anims, keyFrameAnimation);
+
+ QQmlListProperty<QQuickAbstractAnimation> propertyAnims = keyFrameAnimation->animations();
+
+ for (int i = 0; i < info.transformAnimation.animationTypes.size(); ++i) {
+ switch (info.transformAnimation.animationTypes.at(i)) {
+ case QTransform::TxTranslate:
+ {
+ auto *transXAnimation = new QQuickPropertyAnimation(keyFrameAnimation);
+ transXAnimation->setDuration(frameTime);
+ transXAnimation->setTargetObject(transforms.at(i));
+ transXAnimation->setProperty(QStringLiteral("x"));
+ transXAnimation->setTo(keyFrame.values.at(i * 3));
+ propertyAnims.append(&propertyAnims, transXAnimation);
+
+ auto *transYAnimation = new QQuickPropertyAnimation(keyFrameAnimation);
+ transYAnimation->setDuration(frameTime);
+ transYAnimation->setTargetObject(transforms.at(i));
+ transYAnimation->setProperty(QStringLiteral("y"));
+ transYAnimation->setTo(keyFrame.values.at(i * 3 + 1));
+ propertyAnims.append(&propertyAnims, transYAnimation);
+ }
+ break;
+ case QTransform::TxScale:
+ {
+ auto *scaleXAnimation = new QQuickPropertyAnimation(keyFrameAnimation);
+ scaleXAnimation->setDuration(frameTime);
+ scaleXAnimation->setTargetObject(transforms.at(i));
+ scaleXAnimation->setProperty(QStringLiteral("xScale"));
+ scaleXAnimation->setTo(keyFrame.values.at(i * 3));
+ propertyAnims.append(&propertyAnims, scaleXAnimation);
+
+ auto *scaleYAnimation = new QQuickPropertyAnimation(keyFrameAnimation);
+ scaleYAnimation->setDuration(frameTime);
+ scaleYAnimation->setTargetObject(transforms.at(i));
+ scaleYAnimation->setProperty(QStringLiteral("yScale"));
+ scaleYAnimation->setTo(keyFrame.values.at(i * 3 + 1));
+ propertyAnims.append(&propertyAnims, scaleYAnimation);
+ }
+ break;
+ case QTransform::TxRotate:
+ {
+ auto *rotationOriginAnimation = new QQuickPropertyAnimation(keyFrameAnimation);
+ rotationOriginAnimation->setDuration(frameTime);
+ rotationOriginAnimation->setTargetObject(transforms.at(i));
+ rotationOriginAnimation->setProperty(QStringLiteral("origin"));
+ rotationOriginAnimation->setTo(QVector3D(keyFrame.values.at(i * 3),
+ keyFrame.values.at(i * 3 + 1),
+ 0.0));
+ propertyAnims.append(&propertyAnims, rotationOriginAnimation);
+
+ auto *rotationAngleAnimation = new QQuickPropertyAnimation(keyFrameAnimation);
+ rotationAngleAnimation->setDuration(frameTime);
+ rotationAngleAnimation->setTargetObject(transforms.at(i));
+ rotationAngleAnimation->setProperty(QStringLiteral("angle"));
+ rotationAngleAnimation->setTo(keyFrame.values.at(i * 3 + 2));
+ propertyAnims.append(&propertyAnims, rotationAngleAnimation);
+ }
+ break;
+ case QTransform::TxShear:
+ {
+ auto *xSkewAnimation = new QQuickPropertyAnimation(keyFrameAnimation);
+ xSkewAnimation->setDuration(frameTime);
+ xSkewAnimation->setTargetObject(transforms.at(i));
+ xSkewAnimation->setProperty(QStringLiteral("xAngle"));
+ xSkewAnimation->setTo(keyFrame.values.at(i * 3));
+ propertyAnims.append(&propertyAnims, xSkewAnimation);
+
+ auto *ySkewAnimation = new QQuickPropertyAnimation(keyFrameAnimation);
+ ySkewAnimation->setDuration(frameTime);
+ ySkewAnimation->setTargetObject(transforms.at(i));
+ ySkewAnimation->setProperty(QStringLiteral("yAngle"));
+ ySkewAnimation->setTo(keyFrame.values.at(i * 3 + 1));
+ propertyAnims.append(&propertyAnims, ySkewAnimation);
+ }
+ break;
+ default:
+ Q_UNREACHABLE();
+ }
+ }
+
+ if (mainTransform != nullptr) {
+ auto *mainTransformUpdate = new QQuickPropertyAction(keyFrameAnimation);
+ mainTransformUpdate->setTargetObject(mainTransform);
+ mainTransformUpdate->setProperty(QStringLiteral("matrix"));
+ mainTransformUpdate->setValue(QMatrix4x4(keyFrame.baseMatrix));
+ propertyAnims.append(&propertyAnims, mainTransformUpdate);
+ }
+ }
+
+ mainAnimation->setRunning(true);
+}
+
void QQuickItemGenerator::generateAnimateColor(QObject *target,
const QString &propertyName,
const NodeInfo::AnimateColor &animateColor,
diff --git a/src/quickvectorimage/generator/qquickitemgenerator_p.h b/src/quickvectorimage/generator/qquickitemgenerator_p.h
index 53c1452ba4..34ba0f69d5 100644
--- a/src/quickvectorimage/generator/qquickitemgenerator_p.h
+++ b/src/quickvectorimage/generator/qquickitemgenerator_p.h
@@ -22,6 +22,8 @@
QT_BEGIN_NAMESPACE
+class QQuickMatrix4x4;
+
class Q_QUICKVECTORIMAGEGENERATOR_EXPORT QQuickItemGenerator : public QQuickGenerator
{
public:
@@ -44,6 +46,9 @@ private:
void generateGradient(const QGradient *grad, QQuickShapePath *shapePath);
void generatePathContainer(const StructureNodeInfo &info);
void generateAnimateColor(QObject *target, const QString &propertyName, const NodeInfo::AnimateColor &animateColor, const QColor &resetColor);
+ void generateAnimateTransform(const QList<QQuickTransform *> &transforms,
+ QQuickMatrix4x4 *mainTransform,
+ const NodeInfo &info);
QQuickItem *currentItem();
void addCurrentItem(QQuickItem *item, const NodeInfo &info);
diff --git a/src/quickvectorimage/generator/qquicknodeinfo_p.h b/src/quickvectorimage/generator/qquicknodeinfo_p.h
index 7ccdfd5599..d2808dac2b 100644
--- a/src/quickvectorimage/generator/qquicknodeinfo_p.h
+++ b/src/quickvectorimage/generator/qquicknodeinfo_p.h
@@ -20,6 +20,7 @@
#include <QPainterPath>
#include <QMatrix4x4>
#include <QQuickItem>
+#include <QtGui/private/qfixed_p.h>
QT_BEGIN_NAMESPACE
@@ -42,6 +43,21 @@ struct NodeInfo
QList<QPair<qreal, QColor> > keyFrames;
};
QList<AnimateColor> animateColors;
+
+ struct TransformAnimation {
+ struct TransformKeyFrame {
+ TransformKeyFrame() = default;
+
+ QTransform baseMatrix;
+ QList<qreal> values; // animationTypes.size() * 3, content depends on each type
+ bool indefiniteAnimation = false;
+ };
+
+ QList<QTransform::TransformationType> animationTypes;
+ QMap<QFixed, TransformKeyFrame> keyFrames;
+ };
+
+ TransformAnimation transformAnimation;
};
struct ImageNodeInfo : NodeInfo
diff --git a/src/quickvectorimage/generator/qquickqmlgenerator.cpp b/src/quickvectorimage/generator/qquickqmlgenerator.cpp
index f0f7051fe3..72b2bd6d41 100644
--- a/src/quickvectorimage/generator/qquickqmlgenerator.cpp
+++ b/src/quickvectorimage/generator/qquickqmlgenerator.cpp
@@ -80,28 +80,53 @@ QString QQuickQmlGenerator::commentString() const
void QQuickQmlGenerator::generateNodeBase(const NodeInfo &info)
{
- m_indentLevel++;
if (!info.nodeId.isEmpty())
stream() << "objectName: \"" << info.nodeId << "\"";
- if (!info.isDefaultTransform) {
- auto sx = info.transform.m11();
- auto sy = info.transform.m22();
- auto x = info.transform.m31();
- auto y = info.transform.m32();
- if (info.transform.type() == QTransform::TxTranslate) {
- stream() << "transform: Translate { " << "x: " << x << "; y: " << y << " }";
- } else if (info.transform.type() == QTransform::TxScale && !x && !y) {
- stream() << "transform: Scale { xScale: " << sx << "; yScale: " << sy << " }";
- } else {
- stream() << "transform: Matrix4x4 { matrix: ";
- generateTransform(info.transform);
- stream(SameLine) << " }";
- }
- }
- if (!info.isDefaultOpacity) {
+
+ static int counter = 0;
+ const QString idString = QStringLiteral("_qt_node%1").arg(counter++);
+ stream() << "id: " << idString;
+
+ if (!info.isDefaultOpacity)
stream() << "opacity: " << info.opacity;
+
+ if (!info.transformAnimation.animationTypes.isEmpty()) {
+ stream() << "transform: [";
+ m_indentLevel++;
+ for (int i = info.transformAnimation.animationTypes.size() - 1; i >= 0; --i) {
+ switch (info.transformAnimation.animationTypes.at(i)) {
+ case QTransform::TxTranslate:
+ stream() << "Translate { id: " << idString << "_transform_" << i << " }";
+ break;
+ case QTransform::TxScale:
+ stream() << "Scale { id: " << idString << "_transform_" << i << "}";
+ break;
+ case QTransform::TxRotate:
+ stream() << "Rotation { id: " << idString << "_transform_" << i << "; origin.x: " << idString << ".width / 2.0; origin.y: " << idString << ".height / 2.0 }";
+ break;
+ case QTransform::TxShear:
+ stream() << "Shear { id: " << idString << "_transform_" << i << " }";
+ break;
+ default:
+ Q_UNREACHABLE();
+ }
+
+ if (i > 0)
+ stream(SameLine) << ",";
+ }
+
+ if (!info.isDefaultTransform)
+ stream() << ", Matrix4x4 { id: " << idString << "_transform_base }";
+
+ m_indentLevel--;
+ stream() << "]";
+
+ generateAnimateTransform(idString, info);
+ } else if (!info.isDefaultTransform) {
+ stream() << "transform: Matrix4x4 { matrix: ";
+ generateTransform(info.transform);
+ stream(SameLine) << "}";
}
- m_indentLevel--;
}
bool QQuickQmlGenerator::generateDefsNode(const NodeInfo &info)
@@ -151,8 +176,8 @@ void QQuickQmlGenerator::generateImageNode(const ImageNodeInfo &info)
stream() << "Image {";
- generateNodeBase(info);
m_indentLevel++;
+ generateNodeBase(info);
stream() << "x: " << info.rect.x();
stream() << "y: " << info.rect.y();
stream() << "width: " << info.rect.width();
@@ -177,9 +202,9 @@ void QQuickQmlGenerator::generatePath(const PathNodeInfo &info, const QRectF &ov
m_inShapeItem = true;
stream() << shapeName() << " {";
+ m_indentLevel++;
generateNodeBase(info);
- m_indentLevel++;
if (m_flags.testFlag(QQuickVectorImageGenerator::GeneratorFlag::CurveRenderer))
stream() << "preferredRendererType: Shape.CurveRenderer";
optimizePaths(info, overrideBoundingRect);
@@ -364,7 +389,9 @@ void QQuickQmlGenerator::generateNode(const NodeInfo &info)
stream() << "// Missing Implementation for SVG Node: " << info.typeName;
stream() << "// Adding an empty Item and skipping";
stream() << "Item {";
+ m_indentLevel++;
generateNodeBase(info);
+ m_indentLevel--;
stream() << "}";
}
@@ -375,8 +402,8 @@ void QQuickQmlGenerator::generateTextNode(const TextNodeInfo &info)
static int counter = 0;
stream() << "Item {";
- generateNodeBase(info);
m_indentLevel++;
+ generateNodeBase(info);
if (!info.isTextArea)
stream() << "Item { id: textAlignItem_" << counter << "; x: " << info.position.x() << "; y: " << info.position.y() << "}";
@@ -462,8 +489,8 @@ void QQuickQmlGenerator::generateUseNode(const UseNodeInfo &info)
if (info.stage == StructureNodeStage::Start) {
stream() << "Item {";
- generateNodeBase(info);
m_indentLevel++;
+ generateNodeBase(info);
stream() << "x: " << info.startPos.x();
stream() << "y: " << info.startPos.y();
} else {
@@ -484,6 +511,146 @@ void QQuickQmlGenerator::generatePathContainer(const StructureNodeInfo &info)
m_inShapeItem = true;
}
+void QQuickQmlGenerator::generateAnimateTransform(const QString &targetName, const NodeInfo &info)
+{
+ // Main animation which contains one animation for the finite part and optionally
+ // one animation for the infinite part
+ stream() << "SequentialAnimation {";
+ m_indentLevel++;
+ stream() << "running: true";
+
+ stream() << "SequentialAnimation {";
+ m_indentLevel++;
+
+ const auto &keyFrames = info.transformAnimation.keyFrames;
+ qreal previousTimeCode = 0.0;
+
+ bool inFinitePart = true;
+ for (auto it = keyFrames.constBegin(); it != keyFrames.constEnd(); ++it) {
+ const auto &keyFrame = it.value();
+ const qreal timeCode = it.key().toReal();
+ const qreal frameTime = timeCode - previousTimeCode;
+ previousTimeCode = timeCode;
+
+ if (keyFrame.indefiniteAnimation && inFinitePart) {
+ m_indentLevel--;
+ stream() << "}";
+ stream() << "SequentialAnimation {";
+ m_indentLevel++;
+ stream() << "loops: Animation.Infinite";
+
+ inFinitePart = false;
+ }
+
+ stream() << "ParallelAnimation {";
+ m_indentLevel++;
+
+ for (int i = 0; i < info.transformAnimation.animationTypes.size(); ++i) {
+ switch (info.transformAnimation.animationTypes.at(i)) {
+ case QTransform::TxTranslate:
+ stream() << "PropertyAnimation {";
+ m_indentLevel++;
+ stream() << "duration: " << frameTime;
+ stream() << "target: " << targetName << "_transform_" << i;
+ stream() << "property: \"x\"";
+ stream() << "to: " << keyFrame.values.at(i * 3);
+ m_indentLevel--;
+ stream() << "}";
+
+ stream() << "PropertyAnimation {";
+ m_indentLevel++;
+ stream() << "duration: " << frameTime;
+ stream() << "target: " << targetName << "_transform_" << i;
+ stream() << "property: \"y\"";
+ stream() << "to: " << keyFrame.values.at(i * 3 + 1);
+ m_indentLevel--;
+ stream() << "}";
+ break;
+ case QTransform::TxScale:
+ stream() << "PropertyAnimation {";
+ m_indentLevel++;
+ stream() << "duration: " << frameTime;
+ stream() << "target: " << targetName << "_transform_" << i;
+ stream() << "property: \"xScale\"";
+ stream() << "to: " << keyFrame.values.at(i * 3);
+ m_indentLevel--;
+ stream() << "}";
+
+ stream() << "PropertyAnimation {";
+ m_indentLevel++;
+ stream() << "duration: " << frameTime;
+ stream() << "target: " << targetName << "_transform_" << i;
+ stream() << "property: \"yScale\"";
+ stream() << "to: " << keyFrame.values.at(i * 3 + 1);
+ m_indentLevel--;
+ stream() << "}";
+ break;
+ case QTransform::TxRotate:
+ stream() << "PropertyAnimation {";
+ m_indentLevel++;
+ stream() << "duration: " << frameTime;
+ stream() << "target: " << targetName << "_transform_" << i;
+ stream() << "property: \"origin\"";
+ stream() << "to: Qt.vector3d(" << keyFrame.values.at(i * 3) << ", " << keyFrame.values.at(i * 3 + 1) << ", 0.0)";
+ m_indentLevel--;
+ stream() << "}";
+
+ stream() << "PropertyAnimation {";
+ m_indentLevel++;
+ stream() << "duration: " << frameTime;
+ stream() << "target: " << targetName << "_transform_" << i;
+ stream() << "property: \"angle\"";
+ stream() << "to: " << keyFrame.values.at(i * 3 + 2);
+ m_indentLevel--;
+ stream() << "}";
+ break;
+ case QTransform::TxShear:
+ stream() << "PropertyAnimation {";
+ m_indentLevel++;
+ stream() << "duration: " << frameTime;
+ stream() << "target: " << targetName << "_transform_" << i;
+ stream() << "property: \"xAngle\"";
+ stream() << "to: " << keyFrame.values.at(i * 3);
+ m_indentLevel--;
+ stream() << "}";
+
+ stream() << "PropertyAnimation {";
+ m_indentLevel++;
+ stream() << "duration: " << frameTime;
+ stream() << "target: " << targetName << "_transform_" << i;
+ stream() << "property: \"yAngle\"";
+ stream() << "to: " << keyFrame.values.at(i * 3 + 1);
+ m_indentLevel--;
+ stream() << "}";
+ break;
+ default:
+ Q_UNREACHABLE();
+ }
+ }
+
+ if (!info.isDefaultTransform) {
+ stream() << "PropertyAction {";
+ m_indentLevel++;
+ stream() << "target: " << targetName << "_transform_base";
+ stream() << "property: \"matrix\"";
+ stream() << "value: ";
+ generateTransform(keyFrame.baseMatrix);
+
+ m_indentLevel--;
+ stream() << "}";
+ }
+
+ m_indentLevel--;
+ stream() << "}";
+ }
+
+ m_indentLevel--;
+ stream() << "}";
+
+ m_indentLevel--;
+ stream() << "}";
+}
+
void QQuickQmlGenerator::generateAnimateColor(const QString &targetName,
const QString &propertyName,
const NodeInfo::AnimateColor &animateColor,
@@ -565,8 +732,8 @@ bool QQuickQmlGenerator::generateStructureNode(const StructureNodeInfo &info)
m_indentLevel--;
}
- generateNodeBase(info);
m_indentLevel++;
+ generateNodeBase(info);
} else {
m_indentLevel--;
stream() << "}";
@@ -640,12 +807,12 @@ bool QQuickQmlGenerator::generateRootNode(const StructureNodeInfo &info)
stream() << "]";
}
- generateNodeBase(info);
-
if (!info.forceSeparatePaths && info.isPathContainer) {
generatePathContainer(info);
m_indentLevel++;
}
+
+ generateNodeBase(info);
} else {
if (m_inShapeItem) {
m_inShapeItem = false;
diff --git a/src/quickvectorimage/generator/qquickqmlgenerator_p.h b/src/quickvectorimage/generator/qquickqmlgenerator_p.h
index 4739fd3a88..0e9f44f8a7 100644
--- a/src/quickvectorimage/generator/qquickqmlgenerator_p.h
+++ b/src/quickvectorimage/generator/qquickqmlgenerator_p.h
@@ -84,6 +84,7 @@ private:
void generateTransform(const QTransform &xf);
void generatePathContainer(const StructureNodeInfo &info);
void generateAnimateColor(const QString &targetName, const QString &propertyName, const NodeInfo::AnimateColor &animateColor, const QColor &resetColor);
+ void generateAnimateTransform(const QString &targetName, const NodeInfo &info);
QStringView indent();
enum StreamFlags { NoFlags = 0x0, SameLine = 0x1 };
diff --git a/src/quickvectorimage/generator/qsvgvisitorimpl.cpp b/src/quickvectorimage/generator/qsvgvisitorimpl.cpp
index 1684c67e77..fb494f53d8 100644
--- a/src/quickvectorimage/generator/qsvgvisitorimpl.cpp
+++ b/src/quickvectorimage/generator/qsvgvisitorimpl.cpp
@@ -34,6 +34,8 @@
QT_BEGIN_NAMESPACE
+Q_STATIC_LOGGING_CATEGORY(lcVectorImageAnimations, "qt.quick.vectorimage.animations")
+
using namespace Qt::StringLiterals;
class QSvgStyleResolver
@@ -179,12 +181,34 @@ inline bool isPathContainer(const QSvgStructureNode *node)
case QSvgNode::Path:
case QSvgNode::Polygon:
case QSvgNode::Polyline:
+ {
if (!child->style().transform.isDefault()) {
//qCDebug(lcQuickVectorGraphics) << "NOT path container because local transform";
return false;
}
+ const QList<QSvgAbstractAnimation *> animations = child->document()->animator()->animationsForNode(child);
+
+ bool hasTransformAnimation = false;
+ for (const QSvgAbstractAnimation *animation : animations) {
+ const QList<QSvgAbstractAnimatedProperty *> properties = animation->properties();
+ for (const QSvgAbstractAnimatedProperty *property : properties) {
+ if (property->type() == QSvgAbstractAnimatedProperty::Transform) {
+ hasTransformAnimation = true;
+ break;
+ }
+ }
+
+ if (hasTransformAnimation)
+ break;
+ }
+
+ if (hasTransformAnimation) {
+ //qCDebug(lcQuickVectorGraphics) << "NOT path container because local transform animation";
+ return false;
+ }
foundPath = true;
break;
+ }
default:
qCDebug(lcQuickVectorImage) << "Unhandled type in switch" << child->type();
break;
@@ -287,6 +311,7 @@ void QSvgVisitorImpl::visitNode(const QSvgNode *node)
NodeInfo info;
fillCommonNodeInfo(node, info);
+ fillAnimationInfo(node, info);
m_generator->generateNode(info);
@@ -300,6 +325,7 @@ void QSvgVisitorImpl::visitImageNode(const QSvgImage *node)
ImageNodeInfo info;
fillCommonNodeInfo(node, info);
+ fillAnimationInfo(node, info);
info.image = node->image();
info.rect = node->rect();
info.externalFileReference = node->filename();
@@ -808,6 +834,7 @@ void QSvgVisitorImpl::visitTextNode(const QSvgText *node)
auto addPathForFormat = [&](QPainterPath p, QTextCharFormat fmt) {
PathNodeInfo info;
fillCommonNodeInfo(node, info);
+ fillAnimationInfo(node, info);
auto fillStyle = node->style().fill;
if (fillStyle)
info.fillRule = fillStyle->fillRule();
@@ -848,6 +875,7 @@ void QSvgVisitorImpl::visitTextNode(const QSvgText *node)
if (strokeGradient != nullptr) {
PathNodeInfo strokeInfo;
fillCommonNodeInfo(node, strokeInfo);
+ fillAnimationInfo(node, strokeInfo);
strokeInfo.grad = *strokeGradient;
@@ -904,6 +932,7 @@ void QSvgVisitorImpl::visitTextNode(const QSvgText *node)
{
TextNodeInfo info;
fillCommonNodeInfo(node, info);
+ fillAnimationInfo(node, info);
info.position = node->position();
info.size = node->size();
@@ -936,6 +965,7 @@ void QSvgVisitorImpl::visitUseNode(const QSvgUse *node)
handleBaseNodeSetup(node);
UseNodeInfo info;
fillCommonNodeInfo(node, info);
+ fillAnimationInfo(node, info);
info.stage = StructureNodeStage::Start;
info.startPos = node->start();
@@ -980,6 +1010,7 @@ bool QSvgVisitorImpl::visitStructureNodeStart(const QSvgStructureNode *node)
StructureNodeInfo info;
fillCommonNodeInfo(node, info);
+ fillAnimationInfo(node, info);
info.forceSeparatePaths = forceSeparatePaths;
info.isPathContainer = isPathContainer(node);
info.stage = StructureNodeStage::Start;
@@ -1006,6 +1037,7 @@ bool QSvgVisitorImpl::visitDocumentNodeStart(const QSvgTinyDocument *node)
StructureNodeInfo info;
fillCommonNodeInfo(node, info);
+ fillAnimationInfo(node, info);
const QSvgTinyDocument *doc = static_cast<const QSvgTinyDocument *>(node);
info.size = doc->size();
@@ -1041,7 +1073,10 @@ void QSvgVisitorImpl::fillCommonNodeInfo(const QSvgNode *node, NodeInfo &info)
info.opacity = !info.isDefaultOpacity ? node->style().opacity->opacity() : 1.0;
info.isVisible = node->isVisible();
info.isDisplayed = node->displayMode() != QSvgNode::DisplayMode::NoneMode;
+}
+void QSvgVisitorImpl::fillColorAnimationInfo(const QSvgNode *node, NodeInfo &info)
+{
const QList<QSvgAbstractAnimation *> animations = node->document()->animator()->animationsForNode(node);
for (const QSvgAbstractAnimation *animation : animations) {
const QList<QSvgAbstractAnimatedProperty *> properties = animation->properties();
@@ -1055,8 +1090,8 @@ void QSvgVisitorImpl::fillCommonNodeInfo(const QSvgNode *node, NodeInfo &info)
animateColor.fill = colorProperty->propertyName() == QStringLiteral("fill");
animateColor.repeatCount = animation->iterationCount();
animateColor.freeze = animation->animationType() == QSvgAbstractAnimation::SMIL
- ? static_cast<const QSvgAnimateNode *>(animation)->fill() == QSvgAnimateNode::Freeze
- : true;
+ ? static_cast<const QSvgAnimateNode *>(animation)->fill() == QSvgAnimateNode::Freeze
+ : true;
const QList<QColor> colors = colorProperty->colors();
Q_ASSERT(colors.size() == keyFrames.size());
@@ -1074,6 +1109,303 @@ void QSvgVisitorImpl::fillCommonNodeInfo(const QSvgNode *node, NodeInfo &info)
}
}
+void QSvgVisitorImpl::fillTransformAnimationInfo(const QSvgNode *node, NodeInfo &info)
+{
+ // We convert transform animations into key frames ahead of time, resolving things like
+ // freeze, repeat, replace etc. to avoid having to do this in the generators.
+ // One complexity here is if some animations repeat indefinitely and others do not.
+ // For these, we need to first have the finite animation and then have this be replaced by
+ // an infinite animation afterwards.
+
+ // First, we collect all animated properties. We assume that each QSvgAbstractAnimatedProperty
+ // only modifies a single property each in the following code.
+ QList<QPair<const QSvgAbstractAnimation *, const QSvgAnimatedPropertyTransform *> > animateTransforms;
+ const QList<QSvgAbstractAnimation *> animations = node->document()->animator()->animationsForNode(node);
+ for (const QSvgAbstractAnimation *animation : animations) {
+ const QList<QSvgAbstractAnimatedProperty *> properties = animation->properties();
+ for (const QSvgAbstractAnimatedProperty *property : properties) {
+ if (property->type() == QSvgAbstractAnimatedProperty::Transform) {
+ auto v = qMakePair(animation, static_cast<const QSvgAnimatedPropertyTransform *>(property));
+ animateTransforms.append(v);
+ }
+ }
+ }
+
+ if (!animateTransforms.isEmpty()) {
+ // If the animation has some animations with a finite repeat count and some that loop
+ // infinitely, we split the duration into two: First one part with the duration of the
+ // longest finite animation. Then we add an infinitely looping tail at the end.
+ // We record the longest finite animation as maxRunningTime and the looping tail duration as
+ // infiniteAnimationTail
+ int maxRunningTime = 0;
+ int infiniteAnimationTail = 0;
+
+ auto &keyFrames = info.transformAnimation.keyFrames;
+ for (int i = 0; i < animateTransforms.size(); ++i) {
+ const QSvgAbstractAnimation *animation = animateTransforms.at(i).first;
+ const QSvgAnimatedPropertyTransform *property = animateTransforms.at(i).second;
+
+ const int start = animation->start();
+ const int duration = animation->duration();
+ const int iterationCount = animation->iterationCount();
+ const int repeatCount = qMax(iterationCount, 1);
+ const int runningTime = start + duration * repeatCount;
+
+ const qsizetype translationCount = property->translations().size();
+ const qsizetype scaleCount = property->scales().size();
+ const qsizetype rotationCount = property->rotations().size();
+ const qsizetype skewCount = property->skews().size();
+
+ if (translationCount > 0)
+ info.transformAnimation.animationTypes.append(QTransform::TxTranslate);
+ else if (scaleCount > 0)
+ info.transformAnimation.animationTypes.append(QTransform::TxScale);
+ else if (rotationCount > 0)
+ info.transformAnimation.animationTypes.append(QTransform::TxRotate);
+ else if (skewCount > 0)
+ info.transformAnimation.animationTypes.append(QTransform::TxShear);
+
+ maxRunningTime = qMax(maxRunningTime, runningTime);
+
+ // If this animation is looping infinitely, we need to make sure the duration of
+ // the infinitely looping tail animation is divisible by its duration, so that it
+ // will be able to finish a whole number of repeats before looping. We do this
+ // by multiplying the current tail by the duration.
+ // (So if there is an infinitely looping animation of 2s and another of 3s then we
+ // make the looping part 6s, so that the first loops 3 times and the second 2 times
+ // during the length of the animation.)
+ if (iterationCount < 0) {
+ if (infiniteAnimationTail == 0)
+ infiniteAnimationTail = duration;
+ else if (duration == 0 || (infiniteAnimationTail % duration) != 0) {
+ if (duration <= 0 || infiniteAnimationTail >= INT_MAX / duration) {
+ qCWarning(lcVectorImageAnimations)
+ << "Error adding indefinite animation of duration"
+ << duration
+ << "to tail of length"
+ << infiniteAnimationTail;
+ } else {
+ infiniteAnimationTail *= duration;
+ }
+ }
+ }
+ }
+
+ qCDebug(lcVectorImageAnimations) << "Finite running time" << maxRunningTime << "infinite tail" << infiniteAnimationTail;
+
+ // Then we record the key frames. We determine specific positions in the animations where we
+ // need to know the state and record all the time codes for these up-front.
+ for (int i = 0; i < animateTransforms.size(); ++i) {
+ const QSvgAbstractAnimation *animation = animateTransforms.at(i).first;
+ const QSvgAnimatedPropertyTransform *property = animateTransforms.at(i).second;
+
+ const int repeatCount = animation->iterationCount();
+ const int start = animation->start();
+ const int duration = animation->duration();
+ const int runningTime = repeatCount > 0 ? start + duration * repeatCount : maxRunningTime;
+ const qreal frameLength = qreal(duration) / property->keyFrames().size();
+
+ if (repeatCount > 0) {
+ // For animations with a finite number of loops, we record the state right before the
+ // animation, at all key frames of the animation for each loop, right before the
+ // end of the loop, and at the end of the whole thing
+ qreal currentFrameTime = start;
+ if (currentFrameTime > 0)
+ keyFrames[QFixed::fromReal(currentFrameTime) - 1] = NodeInfo::TransformAnimation::TransformKeyFrame{};
+ for (int j = 0; j < repeatCount; ++j) {
+ for (int k = 0; k < property->keyFrames().size(); ++k) {
+ auto keyFrame = NodeInfo::TransformAnimation::TransformKeyFrame{};
+ keyFrames[QFixed::fromReal(currentFrameTime)] = keyFrame;
+ currentFrameTime += frameLength;
+ }
+
+ keyFrames[QFixed::fromReal(currentFrameTime) - 1] = NodeInfo::TransformAnimation::TransformKeyFrame{};
+ }
+
+ keyFrames[QFixed::fromReal(currentFrameTime)] = NodeInfo::TransformAnimation::TransformKeyFrame{};
+ } else {
+ // For animations with infinite repeats, we first do the same as for finite
+ // animations during the finite part, and then we add key frames for the infinite
+ // tail
+ qreal currentFrameTime = start;
+ while (currentFrameTime < runningTime) {
+ for (int k = 0; k < property->keyFrames().size(); ++k) {
+ auto keyFrame = NodeInfo::TransformAnimation::TransformKeyFrame{};
+ keyFrames[QFixed::fromReal(currentFrameTime)] = keyFrame;
+ currentFrameTime += frameLength;
+ }
+ }
+
+ keyFrames[QFixed::fromReal(currentFrameTime) - 1] = NodeInfo::TransformAnimation::TransformKeyFrame{};
+
+ // Start infinite portion at 1ms after finite part to make sure we
+ // reset the animation to the correct position
+ while (currentFrameTime <= runningTime + infiniteAnimationTail) {
+ for (int k = 0; k < property->keyFrames().size(); ++k) {
+ auto keyFrame = NodeInfo::TransformAnimation::TransformKeyFrame{};
+ keyFrames[QFixed::fromReal(currentFrameTime)] = keyFrame;
+ currentFrameTime += frameLength;
+ }
+
+ keyFrames[QFixed::fromReal(currentFrameTime) - 1] = NodeInfo::TransformAnimation::TransformKeyFrame{};
+ }
+ }
+ }
+
+ // For each keyframe, we iterate over all animations to see if they affect the frame.
+ // We record whether a finite animation touches the frame or not. If no finite animation
+ // touches the frame, it means we are in the "tail" period after all finite animations
+ // have finished and which should be looped indefinitely.
+ QTransform baseTransform = info.transform;
+ for (auto it = keyFrames.begin(); it != keyFrames.end(); ++it) {
+ QFixed timecode = it.key();
+ qCDebug(lcVectorImageAnimations) << "Frame at" << timecode;
+
+ if (timecode >= maxRunningTime && infiniteAnimationTail > 0) {
+ qCDebug(lcVectorImageAnimations) << " -> Infinite repeats";
+ it.value().indefiniteAnimation = true;
+ }
+
+ // The base matrix is the matrix set on the item ahead of time. This will be
+ // kept unless a replace animation is active.
+ it.value().baseMatrix = baseTransform;
+
+ // Initialize values to default all animations to inactive
+ Q_ASSERT(animateTransforms.size() == info.transformAnimation.animationTypes.size());
+ for (int i = 0; i < info.transformAnimation.animationTypes.size(); ++i) {
+ if (info.transformAnimation.animationTypes.at(i) == QTransform::TxScale)
+ it.value().values.append({ 1.0, 1.0, 0.0 });
+ else
+ it.value().values.append({ 0.0, 0.0, 0.0 });
+ }
+
+ // For debugging purposes
+ QPointF accumulatedScale = QPointF(1.0, 1.0);
+ QPointF accumulatedTranslation;
+ QPointF accumulatedSkew;
+ qreal accumulatedRotation = 0.0;
+
+ // We count backwards so that we only evaluate up until the last active animation
+ // that is set to additive==replace
+ for (int i = animateTransforms.size() - 1; i >= 0; --i) {
+ const QSvgAbstractAnimation *animation = animateTransforms.at(i).first;
+ const QSvgAnimatedPropertyTransform *property = animateTransforms.at(i).second;
+ const int start = animation->start();
+ const int repeatCount = animation->iterationCount();
+ const int duration = animation->duration();
+ const int end = start + duration * qMax(1, repeatCount);
+ QTransform::TransformationType type = info.transformAnimation.animationTypes.at(i);
+
+ // Does this animation replace all other animations, then we need to clear
+ // the base transform
+ bool replacesOtherTransforms = true;
+ bool freeze = false;
+ if (animation->animationType() == QSvgAbstractAnimation::SMIL) {
+ const QSvgAnimateNode *animateNode = static_cast<const QSvgAnimateNode *>(animation);
+ replacesOtherTransforms = animateNode->additiveType() == QSvgAnimateNode::Replace;
+ freeze = animateNode->fill() == QSvgAnimateNode::Freeze;
+ }
+
+ // Does it apply to this time code? If not, we skip this animation
+ if (QFixed(start) > timecode
+ || (repeatCount > 0 && QFixed(end) < timecode && !freeze)) {
+ continue;
+ }
+
+ QFixed relativeTimeCode = timecode - QFixed(start);
+ while (it.value().indefiniteAnimation && relativeTimeCode > duration) {
+ relativeTimeCode -= duration;
+ }
+
+ qreal fractionOfTotalTime = relativeTimeCode.toReal() / duration;
+ qreal fractionOfCurrentIterationTime = fractionOfTotalTime - std::trunc(fractionOfTotalTime);
+ if (timecode >= end && !it.value().indefiniteAnimation)
+ fractionOfCurrentIterationTime = 1.0;
+
+ qCDebug(lcVectorImageAnimations) << " -> Checking frame at"
+ << relativeTimeCode
+ << "(fraction of total:"
+ << fractionOfTotalTime
+ << ", of current iteration:"
+ << fractionOfCurrentIterationTime << ")"
+ << "animation index:" << i;
+
+ const QList<qreal> propertyKeyFrames = property->keyFrames();
+
+ if (replacesOtherTransforms) {
+ baseTransform = QTransform{};
+ it.value().baseMatrix = QTransform{};
+ }
+
+ for (int j = 1; j < propertyKeyFrames.size(); ++j) {
+ qreal from = propertyKeyFrames.at(j - 1);
+ qreal to = propertyKeyFrames.at(j);
+
+ if (fractionOfCurrentIterationTime >= from && (fractionOfCurrentIterationTime < to || freeze)) {
+ qreal currFraction = (fractionOfCurrentIterationTime - from) / (to - from);
+
+ if (type == QTransform::TxTranslate) {
+ const QPointF trans = property->interpolatedTranslation(j, currFraction);
+ it.value().values[i * 3] = trans.x();
+ it.value().values[i * 3 + 1] = trans.y();
+
+ accumulatedTranslation += trans;
+
+ qCDebug(lcVectorImageAnimations) << " -> Adding translation of" << trans;
+ } else if (type == QTransform::TxScale) {
+ const QPointF scale = property->interpolatedScale(j, currFraction);
+
+ it.value().values[i * 3] = scale.x();
+ it.value().values[i * 3 + 1] = scale.y();
+
+ accumulatedScale.rx() *= scale.x();
+ accumulatedScale.ry() *= scale.y();
+
+ qCDebug(lcVectorImageAnimations) << " -> Adding scale of" << scale;
+ } else if (type == QTransform::TxRotate) {
+ const QPointF origin = property->interpolatedCenterOfRotation(j, currFraction);
+ const qreal rotation = property->interpolatedRotation(j, currFraction);
+
+ it.value().values[i * 3] = origin.x();
+ it.value().values[i * 3 + 1] = origin.y();
+ it.value().values[i * 3 + 2] = rotation;
+
+ accumulatedRotation += rotation;
+
+ qCDebug(lcVectorImageAnimations) << " -> Adding rotation of" << rotation << "around" << origin;
+ } else if (type == QTransform::TxShear) {
+ const QPointF skew = property->interpolatedSkew(j, currFraction);
+
+ it.value().values[i * 3] = skew.x();
+ it.value().values[i * 3 + 1] = skew.y();
+ accumulatedSkew += skew;
+
+ qCDebug(lcVectorImageAnimations) << " -> Adding skew of" << skew;
+ }
+ }
+ }
+
+ // This animation replaces all animations further down the stack, so we just
+ // escape here
+ if (replacesOtherTransforms)
+ break;
+ }
+
+ qCDebug(lcVectorImageAnimations) << " -> Transform: "
+ << "translation == " << accumulatedTranslation
+ << "| scales == " << accumulatedScale
+ << "| rotation == " << accumulatedRotation
+ << "| skew == " << accumulatedSkew;
+ }
+ }
+}
+
+void QSvgVisitorImpl::fillAnimationInfo(const QSvgNode *node, NodeInfo &info)
+{
+ fillColorAnimationInfo(node, info);
+ fillTransformAnimationInfo(node, info);
+}
+
void QSvgVisitorImpl::handleBaseNodeSetup(const QSvgNode *node)
{
qCDebug(lcQuickVectorImage) << "Before SETUP" << node << "fill" << styleResolver->currentFillColor()
@@ -1110,6 +1442,7 @@ void QSvgVisitorImpl::handlePathNode(const QSvgNode *node, const QPainterPath &p
PathNodeInfo info;
fillCommonNodeInfo(node, info);
+ fillAnimationInfo(node, info);
auto fillStyle = node->style().fill;
if (fillStyle)
info.fillRule = fillStyle->fillRule();
diff --git a/src/quickvectorimage/generator/qsvgvisitorimpl_p.h b/src/quickvectorimage/generator/qsvgvisitorimpl_p.h
index 29d4c28345..d3f06a6c99 100644
--- a/src/quickvectorimage/generator/qsvgvisitorimpl_p.h
+++ b/src/quickvectorimage/generator/qsvgvisitorimpl_p.h
@@ -54,6 +54,9 @@ protected:
private:
void fillCommonNodeInfo(const QSvgNode *node, NodeInfo &info);
+ void fillAnimationInfo(const QSvgNode *node, NodeInfo &info);
+ void fillColorAnimationInfo(const QSvgNode *node, NodeInfo &info);
+ void fillTransformAnimationInfo(const QSvgNode *node, NodeInfo &info);
void handleBaseNodeSetup(const QSvgNode *node);
void handleBaseNode(const QSvgNode *node);
void handleBaseNodeEnd(const QSvgNode *node);
diff --git a/tests/baseline/scenegraph/data/shared/svg/animateRotateTransform.svg b/tests/baseline/scenegraph/data/shared/svg/animateRotateTransform.svg
new file mode 100644
index 0000000000..029db9f187
--- /dev/null
+++ b/tests/baseline/scenegraph/data/shared/svg/animateRotateTransform.svg
@@ -0,0 +1,13 @@
+<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
+ <rect x="10" y="10" width="180" height="180" fill="green" />
+ <rect
+ x="10"
+ y="10"
+ width="180"
+ height="180"
+ fill="red"
+ >
+ <animateTransform attributeName="transform" type="rotate" additive="sum" values="0; 15" dur="200ms" />
+ <animateTransform attributeName="transform" type="rotate" from="15" to="45" begin="200ms" dur="200ms" additive="sum" fill="freeze" />
+ </rect>
+</svg> \ No newline at end of file
diff --git a/tests/baseline/scenegraph/data/shared/svg/animateRotateTransformRemove.svg b/tests/baseline/scenegraph/data/shared/svg/animateRotateTransformRemove.svg
new file mode 100644
index 0000000000..a55d1fec7c
--- /dev/null
+++ b/tests/baseline/scenegraph/data/shared/svg/animateRotateTransformRemove.svg
@@ -0,0 +1,13 @@
+<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
+ <rect x="10" y="10" width="180" height="180" fill="green" />
+ <rect
+ x="10"
+ y="10"
+ width="180"
+ height="180"
+ fill="red"
+ >
+ <animateTransform attributeName="transform" type="rotate" additive="sum" values="0; 15" dur="200ms" />
+ <animateTransform attributeName="transform" type="rotate" from="15" to="45" begin="200ms" dur="200ms" additive="sum" fill="remove" />
+ </rect>
+</svg> \ No newline at end of file
diff --git a/tests/baseline/scenegraph/data/shared/svg/animateScaleTransform.svg b/tests/baseline/scenegraph/data/shared/svg/animateScaleTransform.svg
new file mode 100644
index 0000000000..146e9899f8
--- /dev/null
+++ b/tests/baseline/scenegraph/data/shared/svg/animateScaleTransform.svg
@@ -0,0 +1,13 @@
+<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
+ <rect x="10" y="10" width="180" height="180" fill="green" />
+ <rect
+ x="10"
+ y="10"
+ width="180"
+ height="180"
+ fill="red"
+ >
+ <animateTransform attributeName="transform" type="scale" from="0 0" to="2 2" dur="200ms" additive="sum" fill="freeze" />
+ <animateTransform attributeName="transform" type="scale" from="1 1" to="0.5 0.5" begin="100ms" dur="100ms" additive="sum" fill="freeze" />
+ </rect>
+</svg> \ No newline at end of file
diff --git a/tests/baseline/scenegraph/data/shared/svg/animateScaleTransform2.svg b/tests/baseline/scenegraph/data/shared/svg/animateScaleTransform2.svg
new file mode 100644
index 0000000000..087921ccf1
--- /dev/null
+++ b/tests/baseline/scenegraph/data/shared/svg/animateScaleTransform2.svg
@@ -0,0 +1,14 @@
+<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
+ <rect x="10" y="10" width="180" height="180" fill="green" />
+ <rect
+ x="10"
+ y="10"
+ width="180"
+ height="180"
+ fill="red"
+ >
+ <animateTransform attributeName="transform" type="scale" from="0 0" to="1 1" dur="100ms" additive="sum" fill="freeze" />
+ <animateTransform attributeName="transform" type="scale" from="1 1" to="2 2" begin="100ms" dur="100ms" additive="sum" fill="freeze" />
+ <animateTransform attributeName="transform" type="scale" from="0 0" to="0.5 0.5" dur="100ms" additive="sum" fill="freeze" />
+ </rect>
+</svg> \ No newline at end of file
diff --git a/tests/baseline/scenegraph/data/shared/svg/animateSkewTransformX.svg b/tests/baseline/scenegraph/data/shared/svg/animateSkewTransformX.svg
new file mode 100644
index 0000000000..66ece0f2c9
--- /dev/null
+++ b/tests/baseline/scenegraph/data/shared/svg/animateSkewTransformX.svg
@@ -0,0 +1,12 @@
+<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
+ <rect x="0" y="0" width="200" height="200" fill="green" />
+ <rect
+ x="0"
+ y="0"
+ width="100"
+ height="100"
+ fill="red"
+ >
+ <animateTransform attributeName="transform" type="skewX" from="0" to="45" dur="200ms" additive="sum" fill="freeze" />
+ </rect>
+</svg>
diff --git a/tests/baseline/scenegraph/data/shared/svg/animateSkewTransformY.svg b/tests/baseline/scenegraph/data/shared/svg/animateSkewTransformY.svg
new file mode 100644
index 0000000000..902070750b
--- /dev/null
+++ b/tests/baseline/scenegraph/data/shared/svg/animateSkewTransformY.svg
@@ -0,0 +1,12 @@
+<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
+ <rect x="0" y="0" width="200" height="200" fill="green" />
+ <rect
+ x="0"
+ y="0"
+ width="100"
+ height="100"
+ fill="red"
+ >
+ <animateTransform attributeName="transform" type="skewY" from="0" to="45" dur="200ms" additive="sum" fill="freeze" />
+ </rect>
+</svg>
diff --git a/tests/baseline/scenegraph/data/shared/svg/animateTransformAll.svg b/tests/baseline/scenegraph/data/shared/svg/animateTransformAll.svg
new file mode 100644
index 0000000000..67bdb262f5
--- /dev/null
+++ b/tests/baseline/scenegraph/data/shared/svg/animateTransformAll.svg
@@ -0,0 +1,14 @@
+<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
+ <rect x="10" y="10" width="180" height="180" fill="green" />
+ <rect
+ x="10"
+ y="10"
+ width="150"
+ height="150"
+ fill="red"
+ >
+ <animateTransform attributeName="transform" type="rotate" additive="sum" values="0; 15" dur="200ms" fill="freeze" />
+ <animateTransform attributeName="transform" type="scale" additive="sum" from="1 1" to="0.5 0.5" dur="300ms" fill="freeze" />
+ <animateTransform attributeName="transform" type="translate" additive="sum" from="0 0" to="50 0" dur="400ms" fill="freeze" />
+ </rect>
+</svg>
diff --git a/tests/baseline/scenegraph/data/shared/svg/animateTransformFreezeSum.svg b/tests/baseline/scenegraph/data/shared/svg/animateTransformFreezeSum.svg
new file mode 100644
index 0000000000..80cfcafd68
--- /dev/null
+++ b/tests/baseline/scenegraph/data/shared/svg/animateTransformFreezeSum.svg
@@ -0,0 +1,13 @@
+<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
+ <rect x="10" y="10" width="180" height="180" fill="green" />
+ <rect
+ x="10"
+ y="10"
+ width="180"
+ height="180"
+ fill="red"
+ >
+ <animateTransform attributeName="transform" type="scale" from="1 1" to="2 2" dur="200ms" additive="sum" fill="freeze" />
+ <animateTransform attributeName="transform" type="scale" from="1 1" to="0.5 0.5" dur="200ms" additive="sum" fill="freeze" />
+ </rect>
+</svg> \ No newline at end of file
diff --git a/tests/baseline/scenegraph/data/shared/svg/animateTransformRemove.svg b/tests/baseline/scenegraph/data/shared/svg/animateTransformRemove.svg
new file mode 100644
index 0000000000..84e3d7abf4
--- /dev/null
+++ b/tests/baseline/scenegraph/data/shared/svg/animateTransformRemove.svg
@@ -0,0 +1,13 @@
+<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
+ <rect x="10" y="10" width="180" height="180" fill="green" />
+ <rect
+ x="10"
+ y="10"
+ width="90"
+ height="90"
+ fill="red"
+ >
+ <animateTransform attributeName="transform" type="translate" from="0 0" to="90 0" dur="200ms" additive="sum" fill="freeze" />
+ <animateTransform attributeName="transform" type="scale" from="1 1" to="0.5 0.5" dur="200ms" additive="sum" fill="remove" />
+ </rect>
+</svg> \ No newline at end of file
diff --git a/tests/baseline/scenegraph/data/shared/svg/animateTransformRemoveSum.svg b/tests/baseline/scenegraph/data/shared/svg/animateTransformRemoveSum.svg
new file mode 100644
index 0000000000..e02bcae9fa
--- /dev/null
+++ b/tests/baseline/scenegraph/data/shared/svg/animateTransformRemoveSum.svg
@@ -0,0 +1,13 @@
+<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
+ <rect x="10" y="10" width="180" height="180" fill="green" />
+ <rect
+ x="10"
+ y="10"
+ width="180"
+ height="180"
+ fill="red"
+ >
+ <animateTransform attributeName="transform" type="scale" from="1 1" to="2 2" dur="200ms" additive="sum" fill="remove" />
+ <animateTransform attributeName="transform" type="scale" from="1 1" to="0.5 0.5" dur="200ms" additive="sum" fill="freeze" />
+ </rect>
+</svg> \ No newline at end of file
diff --git a/tests/baseline/scenegraph/data/shared/svg/animateTransformReplace.svg b/tests/baseline/scenegraph/data/shared/svg/animateTransformReplace.svg
new file mode 100644
index 0000000000..077216df39
--- /dev/null
+++ b/tests/baseline/scenegraph/data/shared/svg/animateTransformReplace.svg
@@ -0,0 +1,5 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="200" height="100">
+ <rect width="100" height="100" fill="blue" transform="scale(0.5,0.5)">
+ <animateTransform attributeName="transform" type="translate" from="0 0" to="100 0" dur="200ms" repeatCount="1" additive="replace" fill="freeze" />
+ </rect>
+</svg>
diff --git a/tests/baseline/scenegraph/data/shared/svg/animateTransformSum.svg b/tests/baseline/scenegraph/data/shared/svg/animateTransformSum.svg
new file mode 100644
index 0000000000..46aba0aa4c
--- /dev/null
+++ b/tests/baseline/scenegraph/data/shared/svg/animateTransformSum.svg
@@ -0,0 +1,5 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="200" height="100">
+ <rect width="100" height="100" fill="blue" transform="scale(0.5,0.5)">
+ <animateTransform attributeName="transform" type="translate" from="0 0" to="200 0" dur="200ms" repeatCount="1" additive="sum" fill="freeze" />
+ </rect>
+</svg>
diff --git a/tests/baseline/scenegraph/data/shared/svg/animateTransformSum2.svg b/tests/baseline/scenegraph/data/shared/svg/animateTransformSum2.svg
new file mode 100644
index 0000000000..392a75ac3b
--- /dev/null
+++ b/tests/baseline/scenegraph/data/shared/svg/animateTransformSum2.svg
@@ -0,0 +1,13 @@
+<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
+ <rect x="0" y="0" width="200" height="200" fill="green" />
+ <rect
+ x="0"
+ y="0"
+ width="100"
+ height="100"
+ fill="red"
+ >
+ <animateTransform attributeName="transform" type="translate" from="0 0" to="50 0" dur="200ms" additive="sum" fill="freeze" />
+ <animateTransform attributeName="transform" type="translate" from="0 0" to="50 0" dur="300ms" additive="sum" fill="freeze" />
+ </rect>
+</svg> \ No newline at end of file
diff --git a/tests/baseline/scenegraph/data/vectorimages/rotateTransformAnimation.qml b/tests/baseline/scenegraph/data/vectorimages/rotateTransformAnimation.qml
new file mode 100644
index 0000000000..5ae49c9f12
--- /dev/null
+++ b/tests/baseline/scenegraph/data/vectorimages/rotateTransformAnimation.qml
@@ -0,0 +1,29 @@
+import QtQuick
+import QtQuick.VectorImage
+
+Rectangle {
+ id: topLevelItem
+ width: 800
+ height: 200
+
+ ListModel {
+ id: renderers
+ ListElement { renderer: VectorImage.GeometryRenderer; src: "../shared/svg/animateRotateTransform.svg" }
+ ListElement { renderer: VectorImage.CurveRenderer; src: "../shared/svg/animateRotateTransform.svg" }
+ ListElement { renderer: VectorImage.GeometryRenderer; src: "../shared/svg/animateRotateTransformRemove.svg" }
+ ListElement { renderer: VectorImage.CurveRenderer; src: "../shared/svg/animateRotateTransformRemove.svg" }
+ }
+
+ Row {
+ Repeater {
+ model: renderers
+
+ VectorImage {
+ layer.enabled: true
+ layer.samples: 4
+ source: src
+ preferredRendererType: renderer
+ }
+ }
+ }
+}
diff --git a/tests/baseline/scenegraph/data/vectorimages/scaleTransformAnimation.qml b/tests/baseline/scenegraph/data/vectorimages/scaleTransformAnimation.qml
new file mode 100644
index 0000000000..c52d16d14e
--- /dev/null
+++ b/tests/baseline/scenegraph/data/vectorimages/scaleTransformAnimation.qml
@@ -0,0 +1,29 @@
+import QtQuick
+import QtQuick.VectorImage
+
+Rectangle {
+ id: topLevelItem
+ width: 800
+ height: 200
+
+ ListModel {
+ id: renderers
+ ListElement { renderer: VectorImage.GeometryRenderer; src: "../shared/svg/animateScaleTransform.svg" }
+ ListElement { renderer: VectorImage.GeometryRenderer; src: "../shared/svg/animateScaleTransform2.svg" }
+ ListElement { renderer: VectorImage.CurveRenderer; src: "../shared/svg/animateScaleTransform.svg" }
+ ListElement { renderer: VectorImage.CurveRenderer; src: "../shared/svg/animateScaleTransform2.svg" }
+ }
+
+ Row {
+ Repeater {
+ model: renderers
+
+ VectorImage {
+ layer.enabled: true
+ layer.samples: 4
+ source: src
+ preferredRendererType: renderer
+ }
+ }
+ }
+}
diff --git a/tests/baseline/scenegraph/data/vectorimages/skewTransformAnimation.qml b/tests/baseline/scenegraph/data/vectorimages/skewTransformAnimation.qml
new file mode 100644
index 0000000000..260d9130d1
--- /dev/null
+++ b/tests/baseline/scenegraph/data/vectorimages/skewTransformAnimation.qml
@@ -0,0 +1,29 @@
+import QtQuick
+import QtQuick.VectorImage
+
+Rectangle {
+ id: topLevelItem
+ width: 800
+ height: 200
+
+ ListModel {
+ id: renderers
+ ListElement { renderer: VectorImage.GeometryRenderer; src: "../shared/svg/animateSkewTransformX.svg" }
+ ListElement { renderer: VectorImage.CurveRenderer; src: "../shared/svg/animateSkewTransformY.svg" }
+ ListElement { renderer: VectorImage.GeometryRenderer; src: "../shared/svg/animateSkewTransformX.svg" }
+ ListElement { renderer: VectorImage.CurveRenderer; src: "../shared/svg/animateSkewTransformY.svg" }
+ }
+
+ Row {
+ Repeater {
+ model: renderers
+
+ VectorImage {
+ layer.enabled: true
+ layer.samples: 4
+ source: src
+ preferredRendererType: renderer
+ }
+ }
+ }
+}
diff --git a/tests/baseline/scenegraph/data/vectorimages/transformAnimation.qml b/tests/baseline/scenegraph/data/vectorimages/transformAnimation.qml
new file mode 100644
index 0000000000..3201d2b729
--- /dev/null
+++ b/tests/baseline/scenegraph/data/vectorimages/transformAnimation.qml
@@ -0,0 +1,35 @@
+import QtQuick
+import QtQuick.VectorImage
+
+Rectangle {
+ id: topLevelItem
+ width: 800
+ height: 600
+
+ ListModel {
+ id: renderers
+ ListElement { src: "../shared/svg/animateTransformReplace.svg" }
+ ListElement { src: "../shared/svg/animateTransformSum.svg" }
+ ListElement { src: "../shared/svg/animateTransformSum2.svg" }
+ ListElement { src: "../shared/svg/animateTransformRemove.svg" }
+ ListElement { src: "../shared/svg/animateTransformRemoveSum.svg" }
+ ListElement { src: "../shared/svg/animateTransformFreezeSum.svg" }
+ ListElement { src: "../shared/svg/animateTransformAll.svg" }
+ }
+
+ Grid {
+ columns: 4
+ spacing: 10
+ anchors.fill: parent
+ Repeater {
+ model: renderers
+
+ VectorImage {
+ layer.enabled: true
+ layer.samples: 4
+ source: src
+ preferredRendererType: VectorImage.CurveRenderer
+ }
+ }
+ }
+}
diff --git a/tests/manual/svg/CMakeLists.txt b/tests/manual/svg/CMakeLists.txt
index 1576c0d741..8aca3f469e 100644
--- a/tests/manual/svg/CMakeLists.txt
+++ b/tests/manual/svg/CMakeLists.txt
@@ -46,7 +46,7 @@ else()
endif()
endif()
-target_link_libraries(svg PRIVATE Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Quick Qt${QT_VERSION_MAJOR}::Qml Qt${QT_VERSION_MAJOR}::Svg Qt${QT_VERSION_MAJOR}::QuickWidgets Qt${QT_VERSION_MAJOR}::QuickVectorImageGeneratorPrivate Qt${QT_VERSION_MAJOR}::SvgWidgets)
+target_link_libraries(svg PRIVATE Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Quick Qt${QT_VERSION_MAJOR}::Qml Qt${QT_VERSION_MAJOR}::Svg Qt${QT_VERSION_MAJOR}::QuickWidgets Qt${QT_VERSION_MAJOR}::QuickVectorImageGeneratorPrivate Qt${QT_VERSION_MAJOR}::SvgWidgets Qt${QT_VERSION_MAJOR}::GuiPrivate)
set_target_properties(svg PROPERTIES
MACOSX_BUNDLE_GUI_IDENTIFIER my.example.com
diff --git a/tools/svgtoqml/CMakeLists.txt b/tools/svgtoqml/CMakeLists.txt
index fd6c91e7ea..277a0a587d 100644
--- a/tools/svgtoqml/CMakeLists.txt
+++ b/tools/svgtoqml/CMakeLists.txt
@@ -14,6 +14,7 @@ qt_internal_add_tool(${target_name}
LIBRARIES
Qt::Core
Qt::Gui
+ Qt::GuiPrivate
Qt::Qml
Qt::Quick
Qt::QuickVectorImageGeneratorPrivate