diff --git a/src/core/Animators/SmartPath/smartpathcollection.cpp b/src/core/Animators/SmartPath/smartpathcollection.cpp index dd20617ec..f726ae362 100644 --- a/src/core/Animators/SmartPath/smartpathcollection.cpp +++ b/src/core/Animators/SmartPath/smartpathcollection.cpp @@ -76,7 +76,7 @@ void SmartPathCollection::savePathsSVG(SvgExporter& exp, if(!forceDumbIncrement && ca_getNumberOfChildren() == 1) { const auto path0 = getChild(0); path0->graph_saveSVG(exp, parent, visRange, "d", - [path0, &applier](const int relFrame) { + [path0, &applier](const qreal relFrame) { auto path = path0->getPathAtRelFrame(relFrame); if(applier) applier(relFrame, path); SkString pathStr; @@ -86,7 +86,7 @@ void SmartPathCollection::savePathsSVG(SvgExporter& exp, }); } else { Animator::saveSVG(exp, parent, visRange, "d", - [this, &applier](const int relFrame) { + [this, &applier](const qreal relFrame) { auto path = getPathAtRelFrame(relFrame); if(applier) applier(relFrame, path); SkString pathStr; diff --git a/src/core/Animators/SmartPath/smartpathcollection.h b/src/core/Animators/SmartPath/smartpathcollection.h index 4afbf2aa2..7cdda0335 100644 --- a/src/core/Animators/SmartPath/smartpathcollection.h +++ b/src/core/Animators/SmartPath/smartpathcollection.h @@ -43,7 +43,7 @@ class CORE_EXPORT SmartPathCollection : public SmartPathCollectionBase { void prp_writeProperty_impl(eWriteStream& dst) const; void prp_readProperty_impl(eReadStream& src); - using EffectApplier = std::function; void savePathsSVG(SvgExporter& exp, QDomElement& parent, diff --git a/src/core/Animators/animator.cpp b/src/core/Animators/animator.cpp index 48f0300f2..11a8459a9 100644 --- a/src/core/Animators/animator.cpp +++ b/src/core/Animators/animator.cpp @@ -725,6 +725,51 @@ void Animator::saveSVG(SvgExporter& exp, const QList extInfl) const { Q_ASSERT(!transform || attrName == "transform"); + if(exp.hasFrameMapping()) { + const int span = exp.fAbsRange.span(); + if(span <= 1) { + auto value = valueGetter(exp.mapRelFrame(visRange.fMin)); + if(transform) { + value = parent.attribute(attrName) + " " + + type + "(" + value + ")"; + } + parent.setAttribute(attrName, value.trimmed()); + return; + } + + const auto tagName = transform ? "animateTransform" : "animate"; + auto anim = exp.createElement(tagName); + anim.setAttribute("calcMode", exp.forceDiscreteMapping() ? "discrete" : interpolation); + anim.setAttribute("attributeName", attrName); + if(!type.isEmpty()) anim.setAttribute("type", type); + const qreal div = span - 1; + const qreal dur = div/exp.fFps; + anim.setAttribute("dur", QString::number(dur) + 's'); + + QStringList values; + QStringList keyTimes; + for(int i = visRange.fMin; i <= visRange.fMax; i++) { + const qreal mapped = exp.mapRelFrame(i); + values << valueGetter(mapped); + const qreal t = (i - exp.fAbsRange.fMin)/div; + keyTimes << QString::number(t); + } + if(keyTimes.isEmpty()) return; + if(keyTimes.last() != "1") { + values << values.last(); + keyTimes << "1"; + } + if(keyTimes.first() != "0") { + values.prepend(values.first()); + keyTimes.prepend("0"); + } + anim.setAttribute("values", values.join(';')); + anim.setAttribute("keyTimes", keyTimes.join(';')); + SvgExportHelpers::assignLoop(anim, exp.fLoop); + parent.appendChild(anim); + return; + } + const auto thisIdRange = prp_getIdenticalRelRange(visRange.fMin); auto idRange = thisIdRange; for(const auto& infl : extInfl) { diff --git a/src/core/Animators/animator.h b/src/core/Animators/animator.h index a501d3c8d..cadc1a753 100644 --- a/src/core/Animators/animator.h +++ b/src/core/Animators/animator.h @@ -200,7 +200,7 @@ class CORE_EXPORT Animator : public Property { void anim_appendKeyAction(const stdsptr &newKey); void anim_removeKeyAction(const stdsptr newKey); - using ValueGetter = std::function; + using ValueGetter = std::function; void saveSVG(SvgExporter& exp, QDomElement& parent, const FrameRange& visRange, diff --git a/src/core/Animators/coloranimator.cpp b/src/core/Animators/coloranimator.cpp index 89d0c6203..dac13bb1f 100644 --- a/src/core/Animators/coloranimator.cpp +++ b/src/core/Animators/coloranimator.cpp @@ -274,7 +274,7 @@ void ColorAnimator::saveColorSVG(SvgExporter &exp, { qDebug() << "save color for SVG" << name; Animator::saveSVG(exp, parent, visRange, name, - [this, &rgba, &a, &exp, &name, &parent](const int relFrame) { + [this, &rgba, &a, &exp, &name, &parent](const qreal relFrame) { const auto color = getColor(relFrame); if (exp.fColors11) { if (color.alphaF() != 1. && (name == "fill" || name == "stroke" || name == "stop-color")) { diff --git a/src/core/Animators/graphanimator.cpp b/src/core/Animators/graphanimator.cpp index 1c3af842c..d44e7f00d 100644 --- a/src/core/Animators/graphanimator.cpp +++ b/src/core/Animators/graphanimator.cpp @@ -537,6 +537,70 @@ void GraphAnimator::graph_saveSVG(SvgExporter& exp, const QString &motionPath) const { Q_ASSERT(!transform || attrName == "transform"); + if(exp.hasFrameMapping()) { + const int span = exp.fAbsRange.span(); + if(span <= 1) { + if (motion) { return; } + auto value = valueGetter(exp.mapRelFrame(visRange.fMin)); + if (transform) { + value = parent.attribute(attrName) + " " + type + "(" + value + ")"; + } + parent.setAttribute(attrName, value.trimmed()); + return; + } + + 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); } + + 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'); + + QStringList values; + QStringList keyTimes; + for(int i = visRange.fMin; i <= visRange.fMax; i++) { + const qreal mapped = exp.mapRelFrame(i); + values << valueGetter(mapped); + const qreal t = (i - exp.fAbsRange.fMin)/div; + keyTimes << QString::number(t); + } + if(keyTimes.isEmpty()) return; + if(keyTimes.last() != "1") { + values << values.last(); + keyTimes << "1"; + } + if(keyTimes.first() != "0") { + values.prepend(values.first()); + keyTimes.prepend("0"); + } + + anim.setAttribute("calcMode", exp.forceDiscreteMapping() ? "discrete" : "linear"); + anim.setAttribute("values", values.join(';')); + if (motion) { + anim.setAttribute("keyPoints", values.join(';')); + } + anim.setAttribute("keyTimes", keyTimes.join(';')); + SvgExportHelpers::assignLoop(anim, exp.fLoop); + parent.appendChild(anim); + return; + } + const auto relRange = prp_absRangeToRelRange(exp.fAbsRange); const auto idRange = prp_getIdenticalRelRange(visRange.fMin); const int span = exp.fAbsRange.span(); diff --git a/src/core/Animators/qpointfanimator.cpp b/src/core/Animators/qpointfanimator.cpp index c006d3b79..830cc0e12 100644 --- a/src/core/Animators/qpointfanimator.cpp +++ b/src/core/Animators/qpointfanimator.cpp @@ -244,7 +244,7 @@ void QPointFAnimator::saveQPointFSVG(SvgExporter& exp, const bool transform, const QString& type) const { Animator::saveSVG(exp, parent, visRange, name, - [this](const int relFrame) { + [this](const qreal relFrame) { const auto value = getEffectiveValue(relFrame); return QString::number(value.x()) + " " + QString::number(value.y()); diff --git a/src/core/Animators/qrealanimator.cpp b/src/core/Animators/qrealanimator.cpp index e6380ba60..f73e6b423 100644 --- a/src/core/Animators/qrealanimator.cpp +++ b/src/core/Animators/qrealanimator.cpp @@ -852,7 +852,7 @@ void QrealAnimator::saveQrealSVG(SvgExporter& exp, setExpression(mExpression.sptr()); } else { graph_saveSVG(exp, parent, visRange, attrName, - [this, mangler, &templ](const int relFrame) { + [this, mangler, &templ](const qreal relFrame) { const qreal val = mangler(getEffectiveValue(relFrame)); return templ.arg(val); }, transform, type, beginEvent, endEvent, motion, motionRotate, motionPath); diff --git a/src/core/Animators/qstringanimator.cpp b/src/core/Animators/qstringanimator.cpp index 0d506eefa..6cc6f5a64 100644 --- a/src/core/Animators/qstringanimator.cpp +++ b/src/core/Animators/qstringanimator.cpp @@ -53,8 +53,40 @@ QDomElement createTextElement(SvgExporter& exp, const QString& text) { void QStringAnimator::saveSVG(SvgExporter& exp, QDomElement& parent, const PropSetter& propSetter) const { const auto relRange = prp_absRangeToRelRange(exp.fAbsRange); - const auto idRange = prp_getIdenticalRelRange(relRange.fMin); const int span = exp.fAbsRange.span(); + if(exp.hasFrameMapping()) { + QString currentValue; + FrameRange currentRange{FrameRange::EMIN, FrameRange::EMIN}; + for(int i = relRange.fMin; i <= relRange.fMax; i++) { + const qreal mapped = exp.mapRelFrame(i); + const auto value = getValueAtRelFrame(mapped); + if(i == relRange.fMin) { + currentValue = value; + currentRange = {i, i}; + } else if(value == currentValue) { + currentRange.fMax = i; + } else { + auto ele = createTextElement(exp, currentValue); + propSetter(ele); + const auto absRange = prp_relRangeToAbsRange(currentRange); + SvgExportHelpers::assignVisibility(exp, ele, exp.fAbsRange*absRange); + parent.appendChild(ele); + currentValue = value; + currentRange = {i, i}; + } + } + + if(currentRange.fMin != FrameRange::EMIN) { + auto ele = createTextElement(exp, currentValue); + propSetter(ele); + const auto absRange = prp_relRangeToAbsRange(currentRange); + SvgExportHelpers::assignVisibility(exp, ele, exp.fAbsRange*absRange); + parent.appendChild(ele); + } + return; + } + + const auto idRange = prp_getIdenticalRelRange(relRange.fMin); if(idRange.inRange(relRange) || span == 1) { auto ele = createTextElement(exp, getValueAtRelFrame(relRange.fMin)); propSetter(ele); diff --git a/src/core/Boxes/boundingbox.cpp b/src/core/Boxes/boundingbox.cpp index 4da8ea0fd..253880574 100644 --- a/src/core/Boxes/boundingbox.cpp +++ b/src/core/Boxes/boundingbox.cpp @@ -1609,6 +1609,8 @@ eTask* BoundingBox::saveSVGWithTransform(SvgExporter& exp, const auto expPtr = &exp; const auto parentPtr = &parent; taskPtr->addDependent({[ptr, taskPtr, expPtr, parentPtr, visRange, maskId]() { + const SvgExporter::FrameMappingScope mappingScope(*expPtr, + taskPtr->frameMapping()); auto& ele = taskPtr->element(); if (ptr) { ele.setAttribute("id", AppSupport::filterId(ptr->prp_getName())); diff --git a/src/core/Boxes/containerbox.cpp b/src/core/Boxes/containerbox.cpp index e5fbe400d..58a52608e 100644 --- a/src/core/Boxes/containerbox.cpp +++ b/src/core/Boxes/containerbox.cpp @@ -189,6 +189,7 @@ class GroupSaverSVG : public ComplexTask , mExp(exp) , mEle(ele) , mVisRange(visRange) + , mFrameMapping(exp.currentFrameMapping()) { // check for masks (DstIn or DstOut) if (mSrc->isLayer()) { @@ -206,6 +207,7 @@ class GroupSaverSVG : public ComplexTask } void nextStep() override { + const SvgExporter::FrameMappingScope mappingScope(mExp, mFrameMapping); if (!mSrc) { return cancel(); } if (setValue(mI)) { return; } if (done()) { return; } @@ -228,6 +230,7 @@ class GroupSaverSVG : public ComplexTask QDomElement& mEle; const FrameRange mVisRange; QString mItemMaskId; + const SvgExporter::FrameMapping mFrameMapping; int mI = 0; }; diff --git a/src/core/Boxes/internallinkcanvas.cpp b/src/core/Boxes/internallinkcanvas.cpp index 39997d461..f0d26ae67 100644 --- a/src/core/Boxes/internallinkcanvas.cpp +++ b/src/core/Boxes/internallinkcanvas.cpp @@ -27,6 +27,7 @@ #include "linkcanvasrenderdata.h" #include "Animators/transformanimator.h" #include "canvas.h" +#include "svgexporter.h" InternalLinkCanvas::InternalLinkCanvas(ContainerBox * const linkTarget, const bool innerLink) : @@ -90,6 +91,27 @@ void InternalLinkCanvas::setupRenderData(const qreal relFrame, } } +void InternalLinkCanvas::saveSVG(SvgExporter& exp, DomEleTask* const task) const { + const auto linkTarget = getLinkTarget(); + if(!linkTarget) return; + if(!mFrameRemapping->enabled()) { + linkTarget->saveSVG(exp, task); + return; + } + + SvgExporter::FrameMapping mapping; + const QPointer remap = mFrameRemapping.get(); + mapping.active = true; + mapping.discrete = true; + mapping.mapper = [remap](const qreal frame) -> qreal { + if(!remap) return frame; + return remap->frame(frame); + }; + + const SvgExporter::FrameMappingScope scope(exp, mapping); + linkTarget->saveSVG(exp, task); +} + bool InternalLinkCanvas::clipToCanvas() { return mClipToCanvas->getValue(); } @@ -97,6 +119,8 @@ bool InternalLinkCanvas::clipToCanvas() { qsptr InternalLinkCanvas::createLink(const bool inner) { auto linkBox = enve::make_shared(this, inner); copyTransformationTo(linkBox.get()); + sWriteReadMember(this, linkBox.get(), &InternalLinkCanvas::mClipToCanvas); + sWriteReadMember(this, linkBox.get(), &InternalLinkCanvas::mFrameRemapping); return std::move(linkBox); } diff --git a/src/core/Boxes/internallinkcanvas.h b/src/core/Boxes/internallinkcanvas.h index 530df0ea5..31fa1c1c4 100644 --- a/src/core/Boxes/internallinkcanvas.h +++ b/src/core/Boxes/internallinkcanvas.h @@ -39,6 +39,8 @@ class CORE_EXPORT InternalLinkCanvas : public InternalLinkGroupBox { BoxRenderData * const data, Canvas * const scene); + void saveSVG(SvgExporter& exp, DomEleTask* const task) const override; + qsptr createLink(const bool inner); stdsptr createRenderData(); diff --git a/src/core/Boxes/smartvectorpath.cpp b/src/core/Boxes/smartvectorpath.cpp index a82c2e133..b456b4450 100644 --- a/src/core/Boxes/smartvectorpath.cpp +++ b/src/core/Boxes/smartvectorpath.cpp @@ -68,7 +68,7 @@ void SmartVectorPath::saveSVG(SvgExporter& exp, DomEleTask* const task) const { auto fill = exp.createElement("path"); SmartPathCollection::EffectApplier fillApplier; if(baseEffects || fillEffects) { - fillApplier = [this](const int relFrame, SkPath& path) { + fillApplier = [this](const qreal relFrame, SkPath& path) { applyBasePathEffects(relFrame, path); applyFillEffects(relFrame, path); }; @@ -93,7 +93,7 @@ void SmartVectorPath::saveSVG(SvgExporter& exp, DomEleTask* const task) const { auto stroke = exp.createElement("path"); SmartPathCollection::EffectApplier strokeApplier; if(baseEffects || outlineBaseEffects || outlineEffects) { - strokeApplier = [this, outlineEffects](const int relFrame, SkPath& path) { + strokeApplier = [this, outlineEffects](const qreal relFrame, SkPath& path) { applyBasePathEffects(relFrame, path); applyOutlineBaseEffects(relFrame, path); if(!outlineEffects) return; @@ -121,7 +121,7 @@ void SmartVectorPath::saveSVG(SvgExporter& exp, DomEleTask* const task) const { auto& ele = task->initialize("path"); SmartPathCollection::EffectApplier applier; if(baseEffects) { - applier = [this](const int relFrame, SkPath& path) { + applier = [this](const qreal relFrame, SkPath& path) { applyBasePathEffects(relFrame, path); }; }; diff --git a/src/core/FileCacheHandlers/animationcachehandler.cpp b/src/core/FileCacheHandlers/animationcachehandler.cpp index 4468bd45b..3f0dc599c 100644 --- a/src/core/FileCacheHandlers/animationcachehandler.cpp +++ b/src/core/FileCacheHandlers/animationcachehandler.cpp @@ -44,6 +44,7 @@ class AnimationSaverSVG : public ComplexTask { ComplexTask(visRelRange.fMax, "SVG Paint Object"), mSrc(src), mExp(exp), mUse(use), mRelRange(relRange), mVisRange(visRelRange), + mFrameMapping(exp.currentFrameMapping()), mDiv(div), mRelFrame(visRelRange.fMin - 1) {} void nextStep() override { @@ -59,13 +60,13 @@ class AnimationSaverSVG : public ComplexTask { const int span = mExp.fAbsRange.span(); if(idRange.inRange(mVisRange) || span == 1) { - addSurface(0, mVisRange.fMin); + addSurface(mappedFrameForTime(0), mVisRange.fMin); mRelFrame = mVisRange.fMax; return nextStep(); } } - if(mRelFrame >= mSrc->getFrameCount()) { + if(!mFrameMapping.active && mRelFrame >= mSrc->getFrameCount()) { mRelFrame = mVisRange.fMax; return nextStep(); } @@ -73,10 +74,11 @@ class AnimationSaverSVG : public ComplexTask { if(mRelFrame >= mVisRange.fMax) return nextStep(); if(mRelFrame >= mVisRange.fMin) { bool wait; + const qreal mapped = mappedFrameForTime(mRelFrame); if(mRelFrame < 0) { - wait = addSurface(0, mRelFrame); + wait = addSurface(mappedFrameForTime(0), mRelFrame); } else { - wait = addSurface(mRelFrame, mRelFrame); + wait = addSurface(mapped, mRelFrame); } if(!wait) addEmptyTask(); } else nextStep(); @@ -84,26 +86,41 @@ class AnimationSaverSVG : public ComplexTask { private: //! @brief Returns true if there is a task, does have to wait. - bool addSurface(const int relFrame, const int timeFrame) { + bool addSurface(const qreal relFrame, const int timeFrame) { + const int srcFrame = clampFrame(qRound(relFrame)); const QString imageId = SvgExportHelpers::ptrToStr(mSrc) + - QString::number(relFrame); - const auto task = mSrc->scheduleFrameLoad(relFrame); + QString::number(srcFrame); + const auto task = mSrc->scheduleFrameLoad(srcFrame); if(task) { const QPointer ptr = this; - task->addDependent({[ptr, relFrame, timeFrame, imageId]() { + task->addDependent({[ptr, srcFrame, timeFrame, imageId]() { if(!ptr) return; - const auto cont = ptr->mSrc->getFrameAtOrBeforeFrame(relFrame); + const auto cont = ptr->mSrc->getFrameAtOrBeforeFrame(srcFrame); if(cont) ptr->saveSurfaceValues(timeFrame, cont->getImage(), imageId); }, nullptr}); addTask(task->ref()); return true; } else { - const auto cont = mSrc->getFrameAtOrBeforeFrame(relFrame); + const auto cont = mSrc->getFrameAtOrBeforeFrame(srcFrame); if(cont) saveSurfaceValues(timeFrame, cont->getImage(), imageId); return false; } } + qreal mappedFrameForTime(const int timeFrame) const { + if(!mFrameMapping.active || !mFrameMapping.mapper) return timeFrame; + return mFrameMapping.mapper(timeFrame); + } + + int clampFrame(const int frame) const { + if(!mSrc) return frame; + const int maxFrame = mSrc->getFrameCount() - 1; + if(frame < 0) return 0; + if(maxFrame < 0) return 0; + if(frame > maxFrame) return maxFrame; + return frame; + } + void saveSurfaceValues(const int timeFrame, const sk_sp& image, const QString& imageId) { const qreal t = (timeFrame - mRelRange.fMin)/mDiv; @@ -148,6 +165,7 @@ class AnimationSaverSVG : public ComplexTask { QDomElement& mUse; const FrameRange mRelRange; const FrameRange mVisRange; + const SvgExporter::FrameMapping mFrameMapping; const qreal mDiv; bool mFirst = true; diff --git a/src/core/Tasks/domeletask.cpp b/src/core/Tasks/domeletask.cpp index 93bb2d58b..6e0c30fa9 100644 --- a/src/core/Tasks/domeletask.cpp +++ b/src/core/Tasks/domeletask.cpp @@ -28,7 +28,9 @@ #include "svgexporter.h" DomEleTask::DomEleTask(SvgExporter& exp, const FrameRange& visRange) : - mExp(exp), mVisRange(visRange) {} + mExp(exp), + mVisRange(visRange), + mFrameMapping(exp.currentFrameMapping()) {} QDomElement& DomEleTask::initialize(const QString& tag) { mEle = mExp.createElement(tag); diff --git a/src/core/Tasks/domeletask.h b/src/core/Tasks/domeletask.h index bf9dbb2d7..89cdf30aa 100644 --- a/src/core/Tasks/domeletask.h +++ b/src/core/Tasks/domeletask.h @@ -29,11 +29,10 @@ #include "updatable.h" #include "framerange.h" +#include "svgexporter.h" #include -class SvgExporter; - class CORE_EXPORT DomEleTask : public eCpuTask { public: DomEleTask(SvgExporter& exp, const FrameRange& visRange); @@ -44,10 +43,12 @@ class CORE_EXPORT DomEleTask : public eCpuTask { QDomElement& element(); const FrameRange& visRange() const { return mVisRange; } + const SvgExporter::FrameMapping& frameMapping() const { return mFrameMapping; } private: SvgExporter& mExp; QDomElement mEle; const FrameRange mVisRange; + const SvgExporter::FrameMapping mFrameMapping; }; #endif // DOMELETASK_H diff --git a/src/core/svgexporter.cpp b/src/core/svgexporter.cpp index 3febcb308..a2f5d0b79 100644 --- a/src/core/svgexporter.cpp +++ b/src/core/svgexporter.cpp @@ -27,6 +27,15 @@ using namespace Friction; +namespace { +struct FrameMappingEntry { + const SvgExporter* exp; + SvgExporter::FrameMapping mapping; +}; + +static thread_local QList sFrameMappingStack; +} + SvgExporter::SvgExporter(const QString& path, Canvas* const scene, const FrameRange& frameRange, @@ -61,6 +70,55 @@ SvgExporter::SvgExporter(const QString& path, } +SvgExporter::FrameMappingScope::FrameMappingScope(SvgExporter& exp, + const FrameMapping& mapping) : + mExp(exp) { + if(mapping.active) { + mExp.pushFrameMapping(mapping); + mActive = true; + } +} + +SvgExporter::FrameMappingScope::~FrameMappingScope() { + if(mActive) mExp.popFrameMapping(); +} + +SvgExporter::FrameMapping SvgExporter::currentFrameMapping() const { + for(int i = sFrameMappingStack.size() - 1; i >= 0; --i) { + if(sFrameMappingStack.at(i).exp == this) { + return sFrameMappingStack.at(i).mapping; + } + } + return {}; +} + +qreal SvgExporter::mapRelFrame(const qreal frame) const { + const auto mapping = currentFrameMapping(); + if(!mapping.active || !mapping.mapper) return frame; + return mapping.mapper(frame); +} + +bool SvgExporter::hasFrameMapping() const { + return currentFrameMapping().active; +} + +bool SvgExporter::forceDiscreteMapping() const { + return currentFrameMapping().discrete; +} + +void SvgExporter::pushFrameMapping(const FrameMapping& mapping) { + sFrameMappingStack.append({this, mapping}); +} + +void SvgExporter::popFrameMapping() { + for(int i = sFrameMappingStack.size() - 1; i >= 0; --i) { + if(sFrameMappingStack.at(i).exp == this) { + sFrameMappingStack.removeAt(i); + return; + } + } +} + void SvgExporter::nextStep() { if (!mOpen) { diff --git a/src/core/svgexporter.h b/src/core/svgexporter.h index 19fc6e196..28c7a6e3e 100644 --- a/src/core/svgexporter.h +++ b/src/core/svgexporter.h @@ -26,12 +26,29 @@ #include "framerange.h" #include +#include class Canvas; class CORE_EXPORT SvgExporter : public ComplexTask { public: + using FrameMapper = std::function; + struct FrameMapping { + FrameMapper mapper; + bool active = false; + bool discrete = false; + }; + + class FrameMappingScope { + public: + FrameMappingScope(SvgExporter& exp, const FrameMapping& mapping); + ~FrameMappingScope(); + private: + SvgExporter& mExp; + bool mActive = false; + }; + SvgExporter(const QString& path, Canvas* const scene, const FrameRange& frameRange, @@ -50,6 +67,11 @@ class CORE_EXPORT SvgExporter : public ComplexTask void addNextTask(const stdsptr& task); + FrameMapping currentFrameMapping() const; + qreal mapRelFrame(const qreal frame) const; + bool hasFrameMapping() const; + bool forceDiscreteMapping() const; + Canvas* const fScene; const FrameRange fAbsRange; const qreal fFps; @@ -84,6 +106,8 @@ class CORE_EXPORT SvgExporter : public ComplexTask private: void finish(); + void pushFrameMapping(const FrameMapping& mapping); + void popFrameMapping(); bool mHtml; bool mOpen; QFile mFile;