From bf29289b3cd7010467974761c4853bb5eaa726a9 Mon Sep 17 00:00:00 2001 From: Mira Grudzinska Date: Tue, 28 May 2024 13:13:36 +0200 Subject: [PATCH] lottie: repeater transformation fix In the case of a transformation (denoted as T1) appearing after the repeater, but at the same level of nesting, the objects to which the repeater is applied should not consider T1. However, the transformation of the repeater itself should take T1 into account. Also, it is possible to have several consecutive repeaters, and all of them should be taken into account. Until now, the last one was overwriting the previous one. --- src/loaders/lottie/tvgLottieBuilder.cpp | 115 +++++++++++++----------- 1 file changed, 65 insertions(+), 50 deletions(-) diff --git a/src/loaders/lottie/tvgLottieBuilder.cpp b/src/loaders/lottie/tvgLottieBuilder.cpp index fe774ba9..687a77e0 100644 --- a/src/loaders/lottie/tvgLottieBuilder.cpp +++ b/src/loaders/lottie/tvgLottieBuilder.cpp @@ -40,6 +40,7 @@ struct RenderRepeater { int cnt; + Matrix transform; float offset; Point position; Point anchor; @@ -59,7 +60,7 @@ struct RenderContext Shape* propagator = nullptr; Shape* merging = nullptr; //merging shapes if possible (if shapes have same properties) LottieObject** begin = nullptr; //iteration entry point - RenderRepeater* repeater = nullptr; + Array repeaters; Matrix* transform = nullptr; float roundness = 0.0f; bool fragmenting = false; //render context has been fragmented by filling @@ -74,7 +75,6 @@ struct RenderContext ~RenderContext() { if (ownPropagator) delete(propagator); - delete(repeater); free(transform); } @@ -88,9 +88,8 @@ struct RenderContext propagator = static_cast(rhs.propagator->duplicate()); } - if (rhs.repeater) { - repeater = new RenderRepeater(); - *repeater = *rhs.repeater; + for (auto repeater = rhs.repeaters.begin(); repeater < rhs.repeaters.end(); ++repeater) { + repeaters.push(*repeater); } roundness = rhs.roundness; } @@ -392,46 +391,59 @@ static Shape* _draw(LottieGroup* parent, RenderContext* ctx) //OPTIMIZE: path? static void _repeat(LottieGroup* parent, unique_ptr path, RenderContext* ctx) { - auto repeater = ctx->repeater; + Array propagators; + propagators.push(ctx->propagator); + Array shapes; - Array shapes(repeater->cnt); + for (auto repeater = ctx->repeaters.end() - 1; repeater >= ctx->repeaters.begin(); --repeater) { + shapes.reserve(repeater->cnt); - for (int i = 0; i < repeater->cnt; ++i) { - auto multiplier = repeater->offset + static_cast(i); + for (int i = 0; i < repeater->cnt; ++i) { + auto multiplier = repeater->offset + static_cast(i); - auto shape = static_cast(ctx->propagator->duplicate()); - P(shape)->rs.path = P(path.get())->rs.path; + for (auto propagator = propagators.begin(); propagator < propagators.end(); ++propagator) { + auto shape = static_cast((*propagator)->duplicate()); + P(shape)->rs.path = P(path.get())->rs.path; - auto opacity = repeater->interpOpacity ? mathLerp(repeater->startOpacity, repeater->endOpacity, static_cast(i + 1) / repeater->cnt) : repeater->startOpacity; - shape->opacity(opacity); + auto opacity = repeater->interpOpacity ? mathLerp(repeater->startOpacity, repeater->endOpacity, static_cast(i + 1) / repeater->cnt) : repeater->startOpacity; + shape->opacity(opacity); - Matrix m; - mathIdentity(&m); - mathTranslate(&m, repeater->position.x * multiplier + repeater->anchor.x, repeater->position.y * multiplier + repeater->anchor.y); - mathScale(&m, powf(repeater->scale.x * 0.01f, multiplier), powf(repeater->scale.y * 0.01f, multiplier)); - mathRotate(&m, repeater->rotation * multiplier); - mathTranslateR(&m, -repeater->anchor.x, -repeater->anchor.y); + Matrix m; + mathIdentity(&m); + mathTranslate(&m, repeater->position.x * multiplier + repeater->anchor.x, repeater->position.y * multiplier + repeater->anchor.y); + mathScale(&m, powf(repeater->scale.x * 0.01f, multiplier), powf(repeater->scale.y * 0.01f, multiplier)); + mathRotate(&m, repeater->rotation * multiplier); + mathTranslateR(&m, -repeater->anchor.x, -repeater->anchor.y); + m = repeater->transform * m; - auto pm = PP(shape)->transform(); - shape->transform(pm ? (m * *pm) : m); + auto pm = PP(shape)->transform(); + if (pm) { + Matrix inverse; + mathInverse(&repeater->transform, &inverse); + *pm = inverse * *pm; + } - if (ctx->roundness > 1.0f && P(shape)->rs.stroke) { - TVGERR("LOTTIE", "FIXME: Path roundesss should be applied properly!"); - P(shape)->rs.stroke->join = StrokeJoin::Round; + shape->transform(pm ? m * *pm : m); + shapes.push(shape); + } } - shapes.push(shape); - } + propagators.clear(); + propagators.reserve(shapes.count); - //push repeat shapes in order. - if (repeater->inorder) { - for (auto shape = shapes.begin(); shape < shapes.end(); ++shape) { - parent->scene->push(cast(*shape)); - } - } else { - for (auto shape = shapes.end() - 1; shape >= shapes.begin(); --shape) { - parent->scene->push(cast(*shape)); + //push repeat shapes in order. + if (repeater->inorder) { + for (auto shape = shapes.begin(); shape < shapes.end(); ++shape) { + parent->scene->push(cast(*shape)); + propagators.push(*shape); + } + } else { + for (auto shape = shapes.end() - 1; shape >= shapes.begin(); --shape) { + parent->scene->push(cast(*shape)); + propagators.push(*shape); + } } + shapes.clear(); } } @@ -504,7 +516,7 @@ static void _updateRect(LottieGroup* parent, LottieObject** child, float frameNo if (roundness > size.y * 0.5f) roundness = size.y * 0.5f; } - if (ctx->repeater) { + if (!ctx->repeaters.empty()) { auto path = Shape::gen(); _appendRect(path.get(), position.x - size.x * 0.5f, position.y - size.y * 0.5f, size.x, size.y, roundness, ctx->transform); _repeat(parent, std::move(path), ctx); @@ -552,7 +564,7 @@ static void _updateEllipse(LottieGroup* parent, LottieObject** child, float fram auto position = ellipse->position(frameNo, exps); auto size = ellipse->size(frameNo, exps); - if (ctx->repeater) { + if (!ctx->repeaters.empty()) { auto path = Shape::gen(); _appendCircle(path.get(), position.x, position.y, size.x * 0.5f, size.y * 0.5f, ctx->transform); _repeat(parent, std::move(path), ctx); @@ -567,7 +579,7 @@ static void _updatePath(LottieGroup* parent, LottieObject** child, float frameNo { auto path = static_cast(*child); - if (ctx->repeater) { + if (!ctx->repeaters.empty()) { auto p = Shape::gen(); path->pathset(frameNo, P(p)->rs.path.cmds, P(p)->rs.path.pts, ctx->transform, ctx->roundness, exps); _repeat(parent, std::move(p), ctx); @@ -923,7 +935,7 @@ static void _updatePolystar(LottieGroup* parent, LottieObject** child, float fra auto identity = mathIdentity((const Matrix*)&matrix); - if (ctx->repeater) { + if (!ctx->repeaters.empty()) { auto p = Shape::gen(); if (star->type == LottiePolyStar::Star) _updateStar(parent, star, identity ? nullptr : &matrix, ctx->roundness, frameNo, p.get(), exps); else _updatePolygon(parent, star, identity ? nullptr : &matrix, frameNo, p.get(), exps); @@ -988,17 +1000,20 @@ static void _updateRepeater(TVG_UNUSED LottieGroup* parent, LottieObject** child { auto repeater= static_cast(*child); - if (!ctx->repeater) ctx->repeater = new RenderRepeater(); - ctx->repeater->cnt = static_cast(repeater->copies(frameNo, exps)); - ctx->repeater->offset = repeater->offset(frameNo, exps); - ctx->repeater->position = repeater->position(frameNo, exps); - ctx->repeater->anchor = repeater->anchor(frameNo, exps); - ctx->repeater->scale = repeater->scale(frameNo, exps); - ctx->repeater->rotation = repeater->rotation(frameNo, exps); - ctx->repeater->startOpacity = repeater->startOpacity(frameNo, exps); - ctx->repeater->endOpacity = repeater->endOpacity(frameNo, exps); - ctx->repeater->inorder = repeater->inorder; - ctx->repeater->interpOpacity = (ctx->repeater->startOpacity == ctx->repeater->endOpacity) ? false : true; + RenderRepeater r; + r.cnt = static_cast(repeater->copies(frameNo, exps)); + if (auto tr = PP(ctx->propagator)->transform()) r.transform = *tr; + else mathIdentity(&r.transform); + r.offset = repeater->offset(frameNo, exps); + r.position = repeater->position(frameNo, exps); + r.anchor = repeater->anchor(frameNo, exps); + r.scale = repeater->scale(frameNo, exps); + r.rotation = repeater->rotation(frameNo, exps); + r.startOpacity = repeater->startOpacity(frameNo, exps); + r.endOpacity = repeater->endOpacity(frameNo, exps); + r.inorder = repeater->inorder; + r.interpOpacity = (r.startOpacity == r.endOpacity) ? false : true; + ctx->repeaters.push(r); ctx->merging = nullptr; } @@ -1006,7 +1021,7 @@ static void _updateRepeater(TVG_UNUSED LottieGroup* parent, LottieObject** child static void _updateTrimpath(TVG_UNUSED LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist& contexts, RenderContext* ctx, LottieExpressions* exps) { - auto trimpath= static_cast(*child); + auto trimpath = static_cast(*child); float begin, end; trimpath->segment(frameNo, begin, end, exps);