diff --git a/src/core/Animators/graphanimator.cpp b/src/core/Animators/graphanimator.cpp index d2ab8e755..1c3af842c 100644 --- a/src/core/Animators/graphanimator.cpp +++ b/src/core/Animators/graphanimator.cpp @@ -27,6 +27,7 @@ #include "graphkey.h" #include "qrealpoint.h" #include "svgexporthelpers.h" +#include "appsupport.h" GraphAnimator::GraphAnimator(const QString& name) : Animator(name) { connect(this, &Animator::anim_addedKey, [this](Key * key) { @@ -530,28 +531,41 @@ void GraphAnimator::graph_saveSVG(SvgExporter& exp, const bool transform, const QString& type, const QString &beginEvent, - const QString &endEvent) const + const QString &endEvent, + const bool motion, + const bool motionRotate, + const QString &motionPath) const { Q_ASSERT(!transform || attrName == "transform"); const auto relRange = prp_absRangeToRelRange(exp.fAbsRange); const auto idRange = prp_getIdenticalRelRange(visRange.fMin); const int span = exp.fAbsRange.span(); - if(idRange.inRange(visRange) || span == 1) { + if (idRange.inRange(visRange) || span == 1) { + if (motion) { return; } auto value = valueGetter(visRange.fMin); - if(transform) { - value = parent.attribute(attrName) + " " + - type + "(" + value + ")"; + if (transform) { + value = parent.attribute(attrName) + " " + type + "(" + value + ")"; } parent.setAttribute(attrName, value.trimmed()); } else { - const auto tagName = transform ? "animateTransform" : "animate"; + const auto tagName = motion ? "animateMotion" : transform ? "animateTransform" : "animate"; auto anim = exp.createElement(tagName); if (!beginEvent.isEmpty()) { anim.setAttribute("begin", beginEvent); } if (!endEvent.isEmpty()) { anim.setAttribute("end", endEvent); } - anim.setAttribute("attributeName", attrName); - if(!type.isEmpty()) anim.setAttribute("type", type); + if (!motion) { + anim.setAttribute("attributeName", attrName); + if (!type.isEmpty()) { anim.setAttribute("type", type); } + } else { + if (motionRotate) { anim.setAttribute("rotate", "auto"); } + if (!motionPath.isEmpty()) { + auto mpath = exp.createElement("mpath"); + mpath.setAttribute("href", QString("#%1").arg(AppSupport::filterId(motionPath))); + anim.appendChild(mpath); + } + } + const qreal div = span - 1; const qreal dur = div/exp.fFps; anim.setAttribute("dur", QString::number(dur) + 's'); @@ -622,6 +636,9 @@ void GraphAnimator::graph_saveSVG(SvgExporter& exp, anim.setAttribute("calcMode", "spline"); anim.setAttribute("values", values.join(';')); + if (motion) { + anim.setAttribute("keyPoints", values.join(';')); + } anim.setAttribute("keyTimes", keyTimes.join(';')); anim.setAttribute("keySplines", keySplines.join(';')); diff --git a/src/core/Animators/graphanimator.h b/src/core/Animators/graphanimator.h index 73c44661a..6a179e776 100644 --- a/src/core/Animators/graphanimator.h +++ b/src/core/Animators/graphanimator.h @@ -94,7 +94,10 @@ class CORE_EXPORT GraphAnimator : public Animator { const bool transform = false, const QString& type = "", const QString& beginEvent = "", - const QString& endEvent = "") const; + const QString& endEvent = "", + const bool motion = false, + const bool motionRotate = false, + const QString & motionPath = QString()) const; protected: qreal graph_prevKeyWeight(const GraphKey * const prevKey, const GraphKey * const nextKey, diff --git a/src/core/Animators/qpointfanimator.cpp b/src/core/Animators/qpointfanimator.cpp index 7da84483d..c006d3b79 100644 --- a/src/core/Animators/qpointfanimator.cpp +++ b/src/core/Animators/qpointfanimator.cpp @@ -260,11 +260,25 @@ void QPointFAnimator::saveQPointFSVGX(SvgExporter& exp, const bool transform, const QString& type, const QString &beginEvent, - const QString &endEvent) const + const QString &endEvent, + const bool motion, + const bool motionRotate, + const QString &motionPath) const { const QString templ = "%1 " + QString::number(y); - mXAnimator->saveQrealSVG(exp, parent, visRange, name, multiplier, - transform, type, templ, beginEvent, endEvent); + mXAnimator->saveQrealSVG(exp, + parent, + visRange, + name, + multiplier, + transform, + type, + templ, + beginEvent, + endEvent, + motion, + motionRotate, + motionPath); } void QPointFAnimator::saveQPointFSVGY(SvgExporter& exp, @@ -276,12 +290,25 @@ void QPointFAnimator::saveQPointFSVGY(SvgExporter& exp, const bool transform, const QString& type, const QString &beginEvent, - const QString &endEvent) const + const QString &endEvent, + const bool motion, + const bool motionRotate, + const QString &motionPath) const { const QString templ = QString::number(x) + " %1"; - mYAnimator->saveQrealSVG(exp, parent, visRange, name, multiplier, - transform, type, templ, - beginEvent, endEvent); + mYAnimator->saveQrealSVG(exp, + parent, + visRange, + name, + multiplier, + transform, + type, + templ, + beginEvent, + endEvent, + motion, + motionRotate, + motionPath); } QDomElement QPointFAnimator::prp_writePropertyXEV_impl(const XevExporter& exp) const { diff --git a/src/core/Animators/qpointfanimator.h b/src/core/Animators/qpointfanimator.h index 1b8a126bd..e9eb50eb5 100644 --- a/src/core/Animators/qpointfanimator.h +++ b/src/core/Animators/qpointfanimator.h @@ -111,7 +111,10 @@ class CORE_EXPORT QPointFAnimator : public StaticComplexAnimator { const bool transform = false, const QString& type = "", const QString& beginEvent = "", - const QString& endEvent = "") const; + const QString& endEvent = "", + const bool motion = false, + const bool motionRotate = false, + const QString & motionPath = QString()) const; void saveQPointFSVGY(SvgExporter& exp, QDomElement& parent, const FrameRange& visRange, @@ -121,7 +124,10 @@ class CORE_EXPORT QPointFAnimator : public StaticComplexAnimator { const bool transform = false, const QString& type = "", const QString& beginEvent = "", - const QString& endEvent = "") const; + const QString& endEvent = "", + const bool motion = false, + const bool motionRotate = false, + const QString & motionPath = QString()) const; QDomElement prp_writePropertyXEV_impl(const XevExporter& exp) const; protected: qsptr mXAnimator; diff --git a/src/core/Animators/qrealanimator.cpp b/src/core/Animators/qrealanimator.cpp index c5c57c8ef..c261c6cad 100644 --- a/src/core/Animators/qrealanimator.cpp +++ b/src/core/Animators/qrealanimator.cpp @@ -796,37 +796,69 @@ void QrealAnimator::saveQrealSVG(SvgExporter& exp, const QString& type, const QString& templ, const QString &beginEvent, - const QString &endEvent) + const QString &endEvent, + const bool motion, + const bool motionRotate, + const QString &motionPath) { const auto mangler = [multiplier](const qreal value) { return value*multiplier; }; - saveQrealSVG(exp, parent, visRange, attrName, - mangler, transform, type, templ, beginEvent, endEvent); + saveQrealSVG(exp, + parent, + visRange, + attrName, + mangler, + transform, + type, + templ, + beginEvent, + endEvent, + motion, + motionRotate, + motionPath); } -void QrealAnimator::saveQrealSVG(SvgExporter& exp, QDomElement& parent, - const FrameRange& visRange, const QString& attrName, - const Mangler& mangler, const bool transform, - const QString& type, const QString& templ, - const QString &beginEvent, const QString &endEvent) +void QrealAnimator::saveQrealSVG(SvgExporter& exp, + QDomElement& parent, + const FrameRange& visRange, + const QString& attrName, + const Mangler& mangler, + const bool transform, + const QString& type, + const QString& templ, + const QString &beginEvent, + const QString &endEvent, + const bool motion, + const bool motionRotate, + const QString &motionPath) { - if(hasValidExpression()) { + if (hasValidExpression()) { const auto copy = enve::make_shared(""); const auto relRange = prp_absRangeToRelRange(exp.fAbsRange); copy->prp_setInheritedFrameShift(prp_getTotalFrameShift(), nullptr); copy->setExpression(mExpression.sptr()); copy->applyExpression(relRange, 10, false); - copy->saveQrealSVG(exp, parent, visRange, attrName, - mangler, transform, type, templ, - beginEvent, endEvent); + copy->saveQrealSVG(exp, + parent, + visRange, + attrName, + mangler, + transform, + type, + templ, + beginEvent, + endEvent, + motion, + motionRotate, + motionPath); setExpression(mExpression.sptr()); } else { graph_saveSVG(exp, parent, visRange, attrName, [this, mangler, &templ](const int relFrame) { const qreal val = mangler(getEffectiveValue(relFrame)); return templ.arg(val); - }, transform, type, beginEvent, endEvent); + }, transform, type, beginEvent, endEvent, motion, motionRotate, motionPath); } } diff --git a/src/core/Animators/qrealanimator.h b/src/core/Animators/qrealanimator.h index 07e2e8a3b..7e3e97043 100644 --- a/src/core/Animators/qrealanimator.h +++ b/src/core/Animators/qrealanimator.h @@ -183,7 +183,10 @@ class CORE_EXPORT QrealAnimator : public GraphAnimator { const QString& type = "", const QString& templ = "%1", const QString& beginEvent = "", - const QString& endEvent = ""); + const QString& endEvent = "", + const bool motion = false, + const bool motionRotate = false, + const QString & motionPath = QString()); using Mangler = std::function; void saveQrealSVG(SvgExporter& exp, QDomElement& parent, @@ -194,7 +197,10 @@ class CORE_EXPORT QrealAnimator : public GraphAnimator { const QString& type = "", const QString& templ = "%1", const QString& beginEvent = "", - const QString& endEvent = ""); + const QString& endEvent = "", + const bool motion = false, + const bool motionRotate = false, + const QString & motionPath = QString()); private: qreal calculateBaseValueAtRelFrame(const qreal frame) const; void startBaseValueTransform(); diff --git a/src/core/Boxes/boundingbox.cpp b/src/core/Boxes/boundingbox.cpp index 13e6bc65e..010c1fed0 100644 --- a/src/core/Boxes/boundingbox.cpp +++ b/src/core/Boxes/boundingbox.cpp @@ -25,6 +25,7 @@ #include "Boxes/boundingbox.h" #include "Boxes/containerbox.h" +#include "TransformEffects/followpatheffect.h" #include "canvas.h" #include "swt_abstraction.h" #include "Timeline/durationrectangle.h" @@ -358,6 +359,23 @@ bool BoundingBox::hasTransformEffects() const { return mTransformEffectCollection->ca_hasChildren(); } +const QStringList BoundingBox::checkTransformEffectsForSVGSupport() +{ + QStringList result; + const int totalEffects = mTransformEffectCollection->ca_getNumberOfChildren(); + for (int i = 0; i < totalEffects; ++i) { + const auto effect = enve_cast(mTransformEffectCollection->getChild(i)); + if (!effect) { continue; } + if (!effect->isVisible()) { continue; } + bool isSafeForSVG = false; + if (const auto followPath = enve_cast(effect)) { + isSafeForSVG = true; + } + if (!isSafeForSVG) { result.append(effect->prp_getName()); } + } + return result; +} + ContainerBox *BoundingBox::getFirstParentLayer() const { const auto parent = getParentGroup(); if(!parent) return nullptr; @@ -1433,15 +1451,29 @@ eTask* BoundingBox::saveSVGWithTransform(SvgExporter& exp, taskPtr->addDependent({[ptr, taskPtr, expPtr, parentPtr, visRange, maskId]() { auto& ele = taskPtr->element(); if (ptr) { + ele.setAttribute("id", AppSupport::filterId(ptr->prp_getName())); SvgExportHelpers::assignVisibility(*expPtr, ele, visRange); + + const auto transformEffects = ptr->mTransformEffectCollection.get(); + const bool hasTransformEffects = transformEffects->hasEffectsSVG(); + const auto transform = ptr->mTransformAnimator.get(); const auto transformed = transform->saveSVG(*expPtr, visRange, ele); const auto effects = ptr->mRasterEffectsAnimators.get(); - const auto withEffects = effects->saveEffectsSVG(*expPtr, visRange, transformed); + const auto withEffects = hasTransformEffects ? + transformEffects->saveEffectsSVG(*expPtr, + visRange, + ele, + effects->saveEffectsSVG(*expPtr, + visRange, + transformed)) : + effects->saveEffectsSVG(*expPtr, + visRange, + transformed); if (maskId == ptr->prp_getName()) { // move mask to defs auto& eleMask = taskPtr->initialize("mask"); - eleMask.setAttribute("id", QString(ptr->prp_getName()).simplified().replace(" ", "")); + eleMask.setAttribute("id", QString("%1Mask").arg(AppSupport::filterId(ptr->prp_getName()))); eleMask.appendChild(withEffects); expPtr->addToDefs(eleMask); } else { diff --git a/src/core/Boxes/boundingbox.h b/src/core/Boxes/boundingbox.h index 1a6d3fb3d..ced978d1f 100644 --- a/src/core/Boxes/boundingbox.h +++ b/src/core/Boxes/boundingbox.h @@ -435,6 +435,7 @@ class CORE_EXPORT BoundingBox : public eBoxOrSound { QMatrix& postTransform); bool hasTransformEffects() const; + const QStringList checkTransformEffectsForSVGSupport(); ContainerBox* getFirstParentLayer() const; diff --git a/src/core/Boxes/containerbox.cpp b/src/core/Boxes/containerbox.cpp index 73ee877d1..5f7033a4d 100644 --- a/src/core/Boxes/containerbox.cpp +++ b/src/core/Boxes/containerbox.cpp @@ -200,7 +200,7 @@ class GroupSaverSVG : public ComplexTask } } if (!mItemMaskId.isEmpty()) { - mEle.setAttribute("mask", QString("url(#%1)").arg(QString(mItemMaskId).simplified().replace(" ", ""))); + mEle.setAttribute("mask", QString("url(#%1Mask)").arg(AppSupport::filterId(mItemMaskId))); } } } diff --git a/src/core/TransformEffects/followpatheffect.cpp b/src/core/TransformEffects/followpatheffect.cpp index c980cf1b8..7b629f396 100644 --- a/src/core/TransformEffects/followpatheffect.cpp +++ b/src/core/TransformEffects/followpatheffect.cpp @@ -28,6 +28,7 @@ #include "Boxes/pathbox.h" #include "Animators/qrealanimator.h" #include "Animators/transformanimator.h" +#include "svgexporter.h" FollowPathEffect::FollowPathEffect() : TargetTransformEffect("follow path", TransformEffectType::followPath) { @@ -181,3 +182,31 @@ void FollowPathEffect::applyEffect(const qreal relFrame, posX += posXChange; //p1.x()*infl; posY += posYChange; //p1.y()*infl; } + +QDomElement FollowPathEffect::saveFollowPathSVG(SvgExporter &exp, + const FrameRange &visRange, + QDomElement &childElement, + QDomElement &parentElement) const +{ + Q_UNUSED(childElement) + const auto target = targetProperty()->getTarget(); + if (!target) { return parentElement; } + mComplete->saveQrealSVG(exp, + parentElement, + visRange, + "transform", + 1., + false, + "", + "%1", + "", + "", + true, + mRotate->getValue(), + target->prp_getName()); + const auto transform = target->getBoxTransformAnimator(); + const auto transformed = transform->saveSVG(exp, + visRange, + parentElement); + return transformed; +} diff --git a/src/core/TransformEffects/followpatheffect.h b/src/core/TransformEffects/followpatheffect.h index 00353aad4..0a87927c9 100644 --- a/src/core/TransformEffects/followpatheffect.h +++ b/src/core/TransformEffects/followpatheffect.h @@ -42,6 +42,12 @@ class FollowPathEffect : public TargetTransformEffect { qreal &shearX, qreal &shearY, QMatrix& postTransform, BoundingBox* const parent) override; + + QDomElement saveFollowPathSVG(SvgExporter& exp, + const FrameRange& visRange, + QDomElement &childElement, + QDomElement &parentElement) const; + private: void setRotScaleAfterTargetChange( BoundingBox* const oldTarget, diff --git a/src/core/TransformEffects/transformeffectcollection.cpp b/src/core/TransformEffects/transformeffectcollection.cpp index 59561c9e6..07f794c08 100644 --- a/src/core/TransformEffects/transformeffectcollection.cpp +++ b/src/core/TransformEffects/transformeffectcollection.cpp @@ -91,6 +91,35 @@ void TransformEffectCollection::prp_readProperty_impl(eReadStream &src) { } } +bool TransformEffectCollection::hasEffectsSVG() +{ + const auto& children = ca_getChildren(); + for (const auto& effect : children) { + if (const auto followPath = enve_cast(effect.get())) { + return true; + } + } + return false; +} + +QDomElement TransformEffectCollection::saveEffectsSVG(SvgExporter &exp, + const FrameRange &visRange, + QDomElement &childElement, + const QDomElement &parentElement) const +{ + QDomElement result = parentElement; + const auto& children = ca_getChildren(); + for (const auto& effect : children) { + if (const auto path = enve_cast(effect.get())) { + result = path->saveFollowPathSVG(exp, + visRange, + childElement, + result); + } + } + return result; +} + void TransformEffectCollection::applyEffects( const qreal relFrame, qreal& pivotX, qreal& pivotY, diff --git a/src/core/TransformEffects/transformeffectcollection.h b/src/core/TransformEffects/transformeffectcollection.h index 233d44281..864b02d9a 100644 --- a/src/core/TransformEffects/transformeffectcollection.h +++ b/src/core/TransformEffects/transformeffectcollection.h @@ -59,8 +59,15 @@ class CORE_EXPORT TransformEffectCollection : void prp_readProperty_impl(eReadStream &src); bool hasEffects(); + bool hasEffectsSVG(); + void readTransformEffect(eReadStream &target); + QDomElement saveEffectsSVG(SvgExporter& exp, + const FrameRange& visRange, + QDomElement &childElement, + const QDomElement &parentElement) const; + void applyEffects(const qreal relFrame, qreal& pivotX, qreal& pivotY, qreal& posX, qreal& posY, diff --git a/src/core/appsupport.cpp b/src/core/appsupport.cpp index 924ac5e2e..04cfbaaec 100644 --- a/src/core/appsupport.cpp +++ b/src/core/appsupport.cpp @@ -1135,3 +1135,8 @@ void AppSupport::handlePortableFirstRun() "

%1").arg(getAppConfigPath())); setSettings("portable", "PortableFirstRun", false); } + +const QString AppSupport::filterId(const QString &input) +{ + return QString(input).simplified().replace(" ", ""); +} diff --git a/src/core/appsupport.h b/src/core/appsupport.h index e2bb9af5a..45e7c3f5c 100644 --- a/src/core/appsupport.h +++ b/src/core/appsupport.h @@ -137,6 +137,7 @@ class CORE_EXPORT AppSupport : public QObject static const ExpressionPreset readEasingPreset(const QString &filename); static const QList> getEasingPresets(); static void handlePortableFirstRun(); + static const QString filterId(const QString &input); }; #endif // APPSUPPORT_H diff --git a/src/core/canvasselectedboxesactions.cpp b/src/core/canvasselectedboxesactions.cpp index 7a09904d6..efa3099a8 100644 --- a/src/core/canvasselectedboxesactions.cpp +++ b/src/core/canvasselectedboxesactions.cpp @@ -522,10 +522,13 @@ const QString Canvas::checkForUnsupportedBoxSVG(BoundingBox * const box) QString result; if (!box) { return result; } qDebug() << "check" << box->prp_getName() << "for SVG support"; - if (box->hasTransformEffects()) { - result.append(QString("- %1 => %2 : %3\n").arg(prp_getName(), - box->prp_getName(), - tr("Transform effects are unsupported"))); + + const auto transformEffects = box->checkTransformEffectsForSVGSupport(); + if (transformEffects.size() > 0) { + result.append(QString("- %1 => %2 : %3 %4\n").arg(prp_getName(), + box->prp_getName(), + transformEffects.join(", "), + tr("is unsupported"))); } if (box->hasEnabledBlendEffects()) { result.append(QString("- %1 => %2 : %3\n").arg(prp_getName(),