Skip to content

Commit

Permalink
Merge pull request #349 from friction2d/svg-follow-path
Browse files Browse the repository at this point in the history
SVG Export: support follow path (effect)
  • Loading branch information
rodlie authored Nov 29, 2024
2 parents bcd9a4f + 619c0b6 commit 4c3cfdc
Show file tree
Hide file tree
Showing 16 changed files with 244 additions and 40 deletions.
33 changes: 25 additions & 8 deletions src/core/Animators/graphanimator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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');
Expand Down Expand Up @@ -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(';'));

Expand Down
5 changes: 4 additions & 1 deletion src/core/Animators/graphanimator.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
41 changes: 34 additions & 7 deletions src/core/Animators/qpointfanimator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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 {
Expand Down
10 changes: 8 additions & 2 deletions src/core/Animators/qpointfanimator.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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<QrealAnimator> mXAnimator;
Expand Down
58 changes: 45 additions & 13 deletions src/core/Animators/qrealanimator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<QrealAnimator>("");
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);
}
}

Expand Down
10 changes: 8 additions & 2 deletions src/core/Animators/qrealanimator.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<qreal(qreal)>;
void saveQrealSVG(SvgExporter& exp,
QDomElement& parent,
Expand All @@ -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();
Expand Down
36 changes: 34 additions & 2 deletions src/core/Boxes/boundingbox.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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<TransformEffect*>(mTransformEffectCollection->getChild(i));
if (!effect) { continue; }
if (!effect->isVisible()) { continue; }
bool isSafeForSVG = false;
if (const auto followPath = enve_cast<FollowPathEffect*>(effect)) {
isSafeForSVG = true;
}
if (!isSafeForSVG) { result.append(effect->prp_getName()); }
}
return result;
}

ContainerBox *BoundingBox::getFirstParentLayer() const {
const auto parent = getParentGroup();
if(!parent) return nullptr;
Expand Down Expand Up @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions src/core/Boxes/boundingbox.h
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,7 @@ class CORE_EXPORT BoundingBox : public eBoxOrSound {
QMatrix& postTransform);

bool hasTransformEffects() const;
const QStringList checkTransformEffectsForSVGSupport();

ContainerBox* getFirstParentLayer() const;

Expand Down
2 changes: 1 addition & 1 deletion src/core/Boxes/containerbox.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)));
}
}
}
Expand Down
29 changes: 29 additions & 0 deletions src/core/TransformEffects/followpatheffect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
}
Loading

0 comments on commit 4c3cfdc

Please sign in to comment.