diff options
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 |
