diff --git a/src/loaders/lottie/tvgLottieBuilder.cpp b/src/loaders/lottie/tvgLottieBuilder.cpp index ab79c44d..02a45e7b 100644 --- a/src/loaders/lottie/tvgLottieBuilder.cpp +++ b/src/loaders/lottie/tvgLottieBuilder.cpp @@ -715,7 +715,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); @@ -725,7 +725,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); } @@ -1230,7 +1230,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); } pOpacity = opacity; diff --git a/src/loaders/lottie/tvgLottieBuilder.h b/src/loaders/lottie/tvgLottieBuilder.h index c600f072..519f7f5e 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) { @@ -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]; //alternating buffers used when chaining multiple modifiers; for a single modifier only buffer[0] is used LottieExpressions* exps; Tween tween; }; diff --git a/src/loaders/lottie/tvgLottieModifier.cpp b/src/loaders/lottie/tvgLottieModifier.cpp index de72fc98..1912cc5b 100644 --- a/src/loaders/lottie/tvgLottieModifier.cpp +++ b/src/loaders/lottie/tvgLottieModifier.cpp @@ -176,9 +176,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)); @@ -237,9 +236,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; @@ -316,10 +314,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; @@ -331,12 +330,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; } @@ -359,9 +358,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; } @@ -369,23 +368,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 (!tvg::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..6ca28b15 100644 --- a/src/loaders/lottie/tvgLottieModifier.h +++ b/src/loaders/lottie/tvgLottieModifier.h @@ -34,7 +34,11 @@ struct LottieModifier enum Type : uint8_t {Roundness = 0, Offset}; LottieModifier* next = nullptr; + RenderPath* buffer; Type type; + bool chained = false; + + LottieModifier(RenderPath* buffer) : buffer(buffer) {} virtual ~LottieModifier() {} @@ -43,18 +47,17 @@ struct LottieModifier LottieModifier* decorate(LottieModifier* next) { - /* TODO: build the decorative chaining here. - currently we only have roundness and offset. */ - - //roundness -> offset - if (next->type == Roundness) { - next->next = this; - return next; + //TODO: resolve the possible cyclic decoration by adding support for multiple modifiers of the same type in the RenderContext + //prevent cyclic decoration: 1) self-decoration: a->decorate(a); 2) mutual decoration: a->decorate(b); b->decorate(a); + if (next->chained || next == this) { + TVGERR("LOTTIE", "Decoration skipped to prevent cyclic chain with modifier: %p", next); + return this; } + this->chained = true; - //just in the order. - this->next = next; - return this; + //reversed order + next->next = this; + return next; } }; @@ -62,10 +65,9 @@ struct LottieRoundnessModifier : LottieModifier { static constexpr float ROUNDNESS_EPSILON = 1.0f; - RenderPath* buffer; float r; - LottieRoundnessModifier(RenderPath* buffer, float r) : buffer(buffer), r(r) + LottieRoundnessModifier(RenderPath* buffer, float r) : LottieModifier(buffer), r(r) { type = Roundness; } @@ -82,7 +84,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) + LottieOffsetModifier(RenderPath* buffer, float offset, float miter = 4.0f, StrokeJoin join = StrokeJoin::Round) : LottieModifier(buffer), offset(offset), miterLimit(miter), join(join) { type = Offset; }