From 1833bbfd12d3d8a31a331ece5787c8b76d50108d Mon Sep 17 00:00:00 2001 From: Mira Grudzinska Date: Thu, 12 Jun 2025 00:51:45 +0200 Subject: [PATCH] lottie: allow modifiers in any order Previously, the rounding modifier was always applied first, which led to incorrect results when a different modifier order was expected. This update removes that limitation and enables modifiers to be applied in any order. Additionally, the code now prevents an infinite loop that occurred when the rounded corners modifier was applied multiple times (a cycle where next was set to this). --- src/loaders/lottie/tvgLottieBuilder.cpp | 6 ++-- src/loaders/lottie/tvgLottieBuilder.h | 10 +++--- src/loaders/lottie/tvgLottieModifier.cpp | 44 +++++++++++++----------- src/loaders/lottie/tvgLottieModifier.h | 36 ++++++------------- 4 files changed, 42 insertions(+), 54 deletions(-) diff --git a/src/loaders/lottie/tvgLottieBuilder.cpp b/src/loaders/lottie/tvgLottieBuilder.cpp index 194d4cff..c1ead920 100644 --- a/src/loaders/lottie/tvgLottieBuilder.cpp +++ b/src/loaders/lottie/tvgLottieBuilder.cpp @@ -705,7 +705,7 @@ void LottieBuilder::updateRoundedCorner(TVG_UNUSED LottieGroup* parent, LottieOb auto r = roundedCorner->radius(frameNo, tween, exps); if (r < LottieRoundnessModifier::ROUNDNESS_EPSILON) return; - if (!ctx->roundness) ctx->roundness = new LottieRoundnessModifier(&buffer, r); + if (!ctx->roundness) ctx->roundness = new LottieRoundnessModifier(buffer, r); else if (ctx->roundness->r < r) ctx->roundness->r = r; ctx->update(ctx->roundness); @@ -715,7 +715,7 @@ void LottieBuilder::updateRoundedCorner(TVG_UNUSED LottieGroup* parent, LottieOb void LottieBuilder::updateOffsetPath(TVG_UNUSED LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist& contexts, RenderContext* ctx) { auto offset = static_cast(*child); - if (!ctx->offset) ctx->offset = new LottieOffsetModifier(offset->offset(frameNo, tween, exps), offset->miterLimit(frameNo, tween, exps), offset->join); + if (!ctx->offset) ctx->offset = new LottieOffsetModifier(buffer, offset->offset(frameNo, tween, exps), offset->miterLimit(frameNo, tween, exps), offset->join); ctx->update(ctx->offset); } @@ -1222,7 +1222,7 @@ void LottieBuilder::updateMasks(LottieLayer* layer, float frameNo) //Masking with Expansion (Offset) } else { //TODO: Once path direction support is implemented, ensure that the direction is ignored here - auto offset = LottieOffsetModifier(expand); + auto offset = LottieOffsetModifier(buffer, expand); mask->pathset(frameNo, SHAPE(pShape)->rs.path, nullptr, tween, exps, &offset); } diff --git a/src/loaders/lottie/tvgLottieBuilder.h b/src/loaders/lottie/tvgLottieBuilder.h index c600f072..dfbe5233 100644 --- a/src/loaders/lottie/tvgLottieBuilder.h +++ b/src/loaders/lottie/tvgLottieBuilder.h @@ -88,7 +88,7 @@ struct RenderContext update(roundness); } if (rhs.offset) { - offset = new LottieOffsetModifier(rhs.offset->offset, rhs.offset->miterLimit, rhs.offset->join); + offset = new LottieOffsetModifier(rhs.offset->buffer, rhs.offset->offset, rhs.offset->miterLimit, rhs.offset->join); update(offset); } if (rhs.transform) { @@ -97,10 +97,10 @@ struct RenderContext } } - void update(LottieModifier* next) + void update(LottieModifier* prev) { - if (modifier) modifier = modifier->decorate(next); - else modifier = next; + if (modifier) modifier = modifier->decorate(prev); + else modifier = prev; } }; @@ -174,7 +174,7 @@ private: void updateRoundedCorner(LottieGroup* parent, LottieObject** child, float frameNo, Inlist& contexts, RenderContext* ctx); void updateOffsetPath(LottieGroup* parent, LottieObject** child, float frameNo, Inlist& contexts, RenderContext* ctx); - RenderPath buffer; //resusable path + RenderPath buffer[2]; //resusable path LottieExpressions* exps; Tween tween; }; diff --git a/src/loaders/lottie/tvgLottieModifier.cpp b/src/loaders/lottie/tvgLottieModifier.cpp index e9a54682..f0a8096f 100644 --- a/src/loaders/lottie/tvgLottieModifier.cpp +++ b/src/loaders/lottie/tvgLottieModifier.cpp @@ -180,9 +180,8 @@ void LottieOffsetModifier::line(RenderPath& out, PathCommand* inCmds, uint32_t i bool LottieRoundnessModifier::modifyPath(PathCommand* inCmds, uint32_t inCmdsCnt, Point* inPts, uint32_t inPtsCnt, Matrix* transform, RenderPath& out) { - buffer->clear(); - - auto& path = (next) ? *buffer : out; + auto& path = next ? (inCmds == buffer[0].cmds.data ? buffer[1] : buffer[0]) : out; + if (next) path.clear(); path.cmds.reserve(inCmdsCnt * 2); path.pts.reserve((uint32_t)(inPtsCnt * 1.5)); @@ -272,9 +271,8 @@ bool LottieRoundnessModifier::modifyPolystar(RenderPath& in, RenderPath& out, fl { constexpr auto ROUNDED_POLYSTAR_MAGIC_NUMBER = 0.47829f; - buffer->clear(); - - auto& path = (next) ? *buffer : out; + auto& path = next ? (&in == &buffer[0] ? buffer[1] : buffer[0]) : out; + if (next) path.clear(); auto len = length(in.pts[1] - in.pts[2]); auto r = len > 0.0f ? ROUNDED_POLYSTAR_MAGIC_NUMBER * std::min(len * 0.5f, this->r) / len : 0.0f; @@ -351,10 +349,11 @@ bool LottieRoundnessModifier::modifyRect(Point& size, float& r) bool LottieOffsetModifier::modifyPath(PathCommand* inCmds, uint32_t inCmdsCnt, Point* inPts, uint32_t inPtsCnt, TVG_UNUSED Matrix* transform, RenderPath& out) { - if (next) TVGERR("LOTTIE", "Offset has a next modifier?"); + auto& path = next ? (inCmds == buffer[0].cmds.data ? buffer[1] : buffer[0]) : out; + if (next) path.clear(); - out.cmds.reserve(inCmdsCnt * 2); - out.pts.reserve(inPtsCnt * (join == StrokeJoin::Round ? 4 : 2)); + path.cmds.reserve(inCmdsCnt * 2); + path.pts.reserve(inPtsCnt * (join == StrokeJoin::Round ? 4 : 2)); Array stack{5}; State state; @@ -366,12 +365,12 @@ bool LottieOffsetModifier::modifyPath(PathCommand* inCmds, uint32_t inCmdsCnt, P state.moveto = true; state.movetoInIndex = iPt++; } else if (inCmds[iCmd] == PathCommand::LineTo) { - line(out, inCmds, inCmdsCnt, inPts, iPt, iCmd, state, offset, false); + line(path, inCmds, inCmdsCnt, inPts, iPt, iCmd, state, offset, false); } else if (inCmds[iCmd] == PathCommand::CubicTo) { //cubic degenerated to a line if (tvg::zero(inPts[iPt - 1] - inPts[iPt]) || tvg::zero(inPts[iPt + 1] - inPts[iPt + 2])) { ++iPt; - line(out, inCmds, inCmdsCnt, inPts, iPt, iCmd, state, offset, true); + line(path, inCmds, inCmdsCnt, inPts, iPt, iCmd, state, offset, true); ++iPt; continue; } @@ -394,9 +393,9 @@ bool LottieOffsetModifier::modifyPath(PathCommand* inCmds, uint32_t inCmdsCnt, P auto line3 = _offset(bezier.ctrl2, bezier.end, offset); if (state.moveto) { - out.cmds.push(PathCommand::MoveTo); - state.movetoOutIndex = out.pts.count; - out.pts.push(line1.pt1); + path.cmds.push(PathCommand::MoveTo); + state.movetoOutIndex = path.pts.count; + path.pts.push(line1.pt1); state.firstLine = line1; state.moveto = false; } @@ -404,23 +403,26 @@ bool LottieOffsetModifier::modifyPath(PathCommand* inCmds, uint32_t inCmdsCnt, P bool inside{}; Point intersect{}; _intersect(line1, line2, intersect, inside); - out.pts.push(intersect); + path.pts.push(intersect); _intersect(line2, line3, intersect, inside); - out.pts.push(intersect); - out.pts.push(line3.pt2); - out.cmds.push(PathCommand::CubicTo); + path.pts.push(intersect); + path.pts.push(line3.pt2); + path.cmds.push(PathCommand::CubicTo); } iPt += 3; } else { if (!_zero(inPts[iPt - 1], inPts[state.movetoInIndex])) { - out.cmds.push(PathCommand::LineTo); - corner(out, state.line, state.firstLine, state.movetoOutIndex, true); + path.cmds.push(PathCommand::LineTo); + corner(path, state.line, state.firstLine, state.movetoOutIndex, true); } - out.cmds.push(PathCommand::Close); + path.cmds.push(PathCommand::Close); } } + + if (next) return next->modifyPath(path.cmds.data, path.cmds.count, path.pts.data, path.pts.count, transform, out); + return true; } diff --git a/src/loaders/lottie/tvgLottieModifier.h b/src/loaders/lottie/tvgLottieModifier.h index b9367431..7ab1b120 100644 --- a/src/loaders/lottie/tvgLottieModifier.h +++ b/src/loaders/lottie/tvgLottieModifier.h @@ -31,44 +31,33 @@ struct LottieModifier { - enum Type : uint8_t {Roundness = 0, Offset}; - LottieModifier* next = nullptr; - Type type; + RenderPath* buffer; + bool chained = false; + LottieModifier(RenderPath* buffer) : buffer(buffer) {} virtual ~LottieModifier() {} virtual bool modifyPath(PathCommand* inCmds, uint32_t inCmdsCnt, Point* inPts, uint32_t inPtsCnt, Matrix* transform, RenderPath& out) = 0; virtual bool modifyPolystar(RenderPath& in, RenderPath& out, float outerRoundness, bool hasRoundness) = 0; - LottieModifier* decorate(LottieModifier* next) + LottieModifier* decorate(LottieModifier* prev) { - /* TODO: build the decorative chaining here. - currently we only have roundness and offset. */ + //prevent cyclic decoration: 1) self-decoration: a->decorate(a); 2) mutual decoration: a->decorate(b); b->decorate(a); + if (prev->chained || prev == this) return this; + this->chained = true; - //roundness -> offset - if (next->type == Roundness) { - next->next = this; - return next; - } - - //just in the order. - this->next = next; - return this; + prev->next = this; + return prev; } }; struct LottieRoundnessModifier : LottieModifier { static constexpr float ROUNDNESS_EPSILON = 1.0f; - - RenderPath* buffer; float r; - LottieRoundnessModifier(RenderPath* buffer, float r) : buffer(buffer), r(r) - { - type = Roundness; - } + LottieRoundnessModifier(RenderPath* buffer, float r) : LottieModifier(buffer), r(r) {} bool modifyPath(PathCommand* inCmds, uint32_t inCmdsCnt, Point* inPts, uint32_t inPtsCnt, Matrix* transform, RenderPath& out) override; bool modifyPolystar(RenderPath& in, RenderPath& out, float outerRoundness, bool hasRoundness) override; @@ -82,10 +71,7 @@ struct LottieOffsetModifier : LottieModifier float miterLimit; StrokeJoin join; - LottieOffsetModifier(float offset, float miter = 4.0f, StrokeJoin join = StrokeJoin::Round) : offset(offset), miterLimit(miter), join(join) - { - type = Offset; - } + LottieOffsetModifier(RenderPath* buffer, float offset, float miter = 4.0f, StrokeJoin join = StrokeJoin::Round) : LottieModifier(buffer), offset(offset), miterLimit(miter), join(join) {} bool modifyPath(PathCommand* inCmds, uint32_t inCmdsCnt, Point* inPts, uint32_t inPtsCnt, Matrix* transform, RenderPath& out) override; bool modifyPolystar(RenderPath& in, RenderPath& out, float outerRoundness, bool hasRoundness) override;