From 2679880bc3c967d438da1f2bea62ee117ed77c58 Mon Sep 17 00:00:00 2001 From: Mira Grudzinska Date: Wed, 18 Jun 2025 23:55:28 +0200 Subject: [PATCH] lottie: ensure proper shape closure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Ensured proper closure of star and polygon shapes. The start and end points now match - in cases with degenerate bezier curves, the second-to-last point is also aligned. Proper shape closure is necessary for modifiers like offset (future pucker bloat). If the start and end points aren’t equal (within the comparison function’s precision), the shape will be closed with a straight line - and during offsetting, that line will become visible, even though it’s not intended. - Use of the internal _zero() function for point equality check in modifiers algs led to incorrect results when p2.x or p2.y was zero (division by zero). The intent was to treat nearly identical points as equal, but this approach was flawed - at the modifier stage, it’s no longer possible to tell if small gaps are intentional or just due to limited numerical precision (as seen for example in the difference between the start and end points of star/polygon shapes). --- src/loaders/lottie/tvgLottieBuilder.cpp | 11 +++++++++++ src/loaders/lottie/tvgLottieModifier.cpp | 13 +++---------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/loaders/lottie/tvgLottieBuilder.cpp b/src/loaders/lottie/tvgLottieBuilder.cpp index fd0db10a..ca707e9f 100644 --- a/src/loaders/lottie/tvgLottieBuilder.cpp +++ b/src/loaders/lottie/tvgLottieBuilder.cpp @@ -488,6 +488,13 @@ 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; @@ -595,6 +602,8 @@ void LottieBuilder::updateStar(LottiePolyStar* star, float frameNo, Matrix* tran angle += dTheta * direction; 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); shape->close(); if (ctx->modifier) ctx->modifier->modifyPolystar(SHAPE(shape)->rs.path, SHAPE(merging)->rs.path, outerRoundness, hasRoundness); @@ -663,6 +672,8 @@ 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); 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 ed14d062..de72fc98 100644 --- a/src/loaders/lottie/tvgLottieModifier.cpp +++ b/src/loaders/lottie/tvgLottieModifier.cpp @@ -52,16 +52,9 @@ static void _roundCorner(Array& cmds, Array& pts, Point& pre } -static bool _zero(Point& p1, Point& p2) -{ - constexpr float epsilon = 1e-3f; - return fabsf(p1.x / p2.x - 1.0f) < epsilon && fabsf(p1.y / p2.y - 1.0f) < epsilon; -} - - static bool _intersect(Line& line1, Line& line2, Point& intersection, bool& inside) { - if (_zero(line1.pt2, line2.pt1)) { + if (tvg::zero(line1.pt2 - line2.pt1)) { intersection = line1.pt2; inside = true; return true; @@ -168,7 +161,7 @@ void LottieOffsetModifier::line(RenderPath& out, PathCommand* inCmds, uint32_t i Line nextLine = state.firstLine; if (inCmds[curCmd + 1] == PathCommand::LineTo) nextLine = _offset(inPts[curPt + degenerated], inPts[curPt + 1 + degenerated], offset); else if (inCmds[curCmd + 1] == PathCommand::CubicTo) nextLine = _offset(inPts[curPt + 1 + degenerated], inPts[curPt + 2 + degenerated], offset); - else if (inCmds[curCmd + 1] == PathCommand::Close && !_zero(inPts[curPt + degenerated], inPts[state.movetoInIndex + degenerated])) + else if (inCmds[curCmd + 1] == PathCommand::Close && !tvg::zero(inPts[curPt + degenerated] - inPts[state.movetoInIndex + degenerated])) nextLine = _offset(inPts[curPt + degenerated], inPts[state.movetoInIndex + degenerated], offset); corner(out, state.line, nextLine, state.movetoOutIndex, inCmds[curCmd + 1] == PathCommand::Close); @@ -386,7 +379,7 @@ bool LottieOffsetModifier::modifyPath(PathCommand* inCmds, uint32_t inCmdsCnt, P iPt += 3; } else { - if (!_zero(inPts[iPt - 1], inPts[state.movetoInIndex])) { + if (!tvg::zero(inPts[iPt - 1] - inPts[state.movetoInIndex])) { out.cmds.push(PathCommand::LineTo); corner(out, state.line, state.firstLine, state.movetoOutIndex, true); }