From f8064f3b31f1e845fd0a06a0dfab2fc12c24cfe7 Mon Sep 17 00:00:00 2001 From: Mira Grudzinska Date: Mon, 23 Jun 2025 23:27:27 +0200 Subject: [PATCH] lottie: ensuring rectangle closure A rectangle with roundness = 0 is the only shape where one of its sides is created using a close command. This exceptional case requires additional handling of the close command for every modifier. By adding an extra lineTo command to the rectangle path, its becomes consistent with other shapes, eliminating the special case inside modifiers algorithms. --- src/loaders/lottie/tvgLottieBuilder.cpp | 28 ++++++++++++++++-------- src/loaders/lottie/tvgLottieModifier.cpp | 7 +----- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/loaders/lottie/tvgLottieBuilder.cpp b/src/loaders/lottie/tvgLottieBuilder.cpp index ab79c44d..41018f19 100644 --- a/src/loaders/lottie/tvgLottieBuilder.cpp +++ b/src/loaders/lottie/tvgLottieBuilder.cpp @@ -392,12 +392,29 @@ static void _repeat(LottieGroup* parent, Shape* path, RenderContext* ctx) } +static void _close(RenderPath& path, const Point& p, bool round, bool rect = false) +{ + //the only case where the shape was truly closed via a 'close' command - change important for modifiers to ensure consistency with other cases + if (rect) { + if (!round) { + path.pts.push(p); + path.cmds.last() = PathCommand::LineTo; + path.cmds.push(PathCommand::Close); + } + return; + } + if (round && tvg::zero(path.pts.last() - path.pts[path.pts.count - 2])) path.pts[path.pts.count - 2] = p; + path.pts.last() = p; +} + + void LottieBuilder::appendRect(Shape* shape, Point& pos, Point& size, float r, bool clockwise, RenderContext* ctx) { auto temp = (ctx->offset) ? Shape::gen() : shape; auto cnt = SHAPE(temp)->rs.path.pts.count; temp->appendRect(pos.x, pos.y, size.x, size.y, r, r, clockwise); + _close(SHAPE(temp)->rs.path, SHAPE(temp)->rs.path.pts[cnt], r > 0.0f, true); if (ctx->transform) { for (auto i = cnt; i < SHAPE(temp)->rs.path.pts.count; ++i) { @@ -489,13 +506,6 @@ void LottieBuilder::updatePath(LottieGroup* parent, LottieObject** child, float } -static void _close(Array& pts, const Point& p, bool round) -{ - if (round && tvg::zero(pts.last() - pts[pts.count - 2])) pts[pts.count - 2] = p; - pts.last() = p; -} - - void LottieBuilder::updateStar(LottiePolyStar* star, float frameNo, Matrix* transform, Shape* merging, RenderContext* ctx, Tween& tween, LottieExpressions* exps) { static constexpr auto POLYSTAR_MAGIC_NUMBER = 0.47829f / 0.28f; @@ -604,7 +614,7 @@ void LottieBuilder::updateStar(LottiePolyStar* star, float frameNo, Matrix* tran longSegment = !longSegment; } //ensure proper shape closure - important for modifiers that behave differently for degenerate (linear) vs curved cubics - _close(SHAPE(shape)->rs.path.pts, in, hasRoundness); + _close(SHAPE(shape)->rs.path, in, hasRoundness); shape->close(); if (ctx->modifier) ctx->modifier->modifyPolystar(SHAPE(shape)->rs.path, SHAPE(merging)->rs.path, outerRoundness, hasRoundness); @@ -674,7 +684,7 @@ void LottieBuilder::updatePolygon(LottieGroup* parent, LottiePolyStar* star, flo angle += anglePerPoint * direction; } //ensure proper shape closure - important for modifiers that behave differently for degenerate (linear) vs curved cubics - _close(SHAPE(shape)->rs.path.pts, in, hasRoundness); + _close(SHAPE(shape)->rs.path, in, hasRoundness); shape->close(); if (ctx->modifier) ctx->modifier->modifyPolystar(SHAPE(shape)->rs.path, SHAPE(merging)->rs.path, 0.0f, false); diff --git a/src/loaders/lottie/tvgLottieModifier.cpp b/src/loaders/lottie/tvgLottieModifier.cpp index de72fc98..536c7842 100644 --- a/src/loaders/lottie/tvgLottieModifier.cpp +++ b/src/loaders/lottie/tvgLottieModifier.cpp @@ -377,12 +377,7 @@ bool LottieOffsetModifier::modifyPath(PathCommand* inCmds, uint32_t inCmdsCnt, P } 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); - } + } else { out.cmds.push(PathCommand::Close); } }