From 0f334b48bb78c5d391cb9df9022a1657bec1dc4f Mon Sep 17 00:00:00 2001 From: Hermet Park Date: Fri, 13 Oct 2023 22:33:58 +0900 Subject: [PATCH] lottie/builder: Fix overlapped stroking outlines. Previously, the builder accumulated the outlines and fills in one paint to reduce the rendering context. However, this approach won't work for Lottie if the resource contains multiple strokes with branched groups. We should apply the optimization only when the specified condition is satisfied. --- src/loaders/lottie/tvgLottieBuilder.cpp | 88 +++++++++++++++++-------- src/loaders/lottie/tvgLottieModel.h | 2 + 2 files changed, 61 insertions(+), 29 deletions(-) diff --git a/src/loaders/lottie/tvgLottieBuilder.cpp b/src/loaders/lottie/tvgLottieBuilder.cpp index 530cf384..8aa218f6 100644 --- a/src/loaders/lottie/tvgLottieBuilder.cpp +++ b/src/loaders/lottie/tvgLottieBuilder.cpp @@ -56,7 +56,8 @@ struct RenderContext LottieObject** begin = nullptr; //iteration entry point RenderRepeater* repeater = nullptr; float roundness = 0.0f; - bool fragmented = false; //context has been separated. allow adding visual properties(fill/stroking) if it is false. + bool stroking = false; //context has been separated by the stroking + bool reqFragment = false; //requirment to fragment the render context RenderContext() { @@ -77,7 +78,6 @@ struct RenderContext *repeater = *rhs.repeater; } roundness = rhs.roundness; - fragmented = rhs.fragmented; } }; @@ -229,26 +229,30 @@ static void _updateStroke(LottieStroke* stroke, int32_t frameNo, RenderContext& } +static bool _fragmentedStroking(LottieObject** child, queue& contexts, RenderContext& ctx) +{ + if (!ctx.reqFragment) return false; + if (ctx.stroking) return true; + + contexts.push(ctx); + auto& fragment = contexts.back(); + fragment.propagator->stroke(0.0f); + fragment.begin = child - 1; + ctx.stroking = true; + + return false; +} + + static void _updateSolidStroke(LottieObject** child, int32_t frameNo, queue& contexts, RenderContext& ctx) { - //the rendering context is fragmented - if (ctx.propagator->strokeWidth() > 0.0f) { - contexts.push(ctx); - auto& fragment = contexts.back(); - fragment.propagator->stroke(0.0f); - fragment.begin = child - 1; - _updateSolidStroke(child, frameNo, contexts, fragment); - ctx.fragmented = true; - return; - } - - if (ctx.fragmented) return; + if (_fragmentedStroking(child, contexts, ctx)) return; auto stroke = static_cast(*child); ctx.merging = nullptr; auto color = stroke->color(frameNo); - ctx.propagator->stroke(color.rgb[0], color.rgb[1], color.rgb[2], stroke->opacity(frameNo)); + ctx.propagator->stroke(color.rgb[0], color.rgb[1], color.rgb[2], stroke->opacity(frameNo)); _updateStroke(static_cast(stroke), frameNo, ctx); } @@ -256,18 +260,7 @@ static void _updateSolidStroke(LottieObject** child, int32_t frameNo, queue& contexts, RenderContext& ctx) { - //the rendering context is fragmented - if (ctx.propagator->strokeWidth() > 0.0f) { - contexts.push(ctx); - auto& fragment = contexts.back(); - fragment.propagator->stroke(0.0f); - fragment.begin = child - 1; - _updateGradientStroke(child, frameNo, contexts, fragment); - ctx.fragmented = true; - return; - } - - if (ctx.fragmented) return; + if (_fragmentedStroking(child, contexts, ctx)) return; auto stroke = static_cast(*child); @@ -280,7 +273,7 @@ static void _updateGradientStroke(LottieObject** child, int32_t frameNo, queue 0) { auto& ctx = contexts.front(); + ctx.reqFragment = parent->reqFragment; for (auto child = ctx.begin; child >= parent->children.data; --child) { //TODO: Polymorphsim? switch ((*child)->type) { @@ -966,6 +960,40 @@ static void _bulidHierarchy(LottieGroup* parent, LottieLayer* child) } +//TODO: Optimize this. Can we preprocess in the parsing stage? +static void _checkFragment(LottieGroup* parent) +{ + if (parent->children.count == 0) return; + + int strokeCnt = 0; + + /* Figure out if the rendering context should be fragmented. + Multiple stroking or grouping with a stroking would occur this. + This fragment resolves the overlapped stroke outlines. */ + for (auto c = parent->children.end() - 1; c >= parent->children.data; --c) { + switch ((*c)->type) { + case LottieObject::Group: { + if (strokeCnt > 0) { + parent->reqFragment = true; + return; + } + break; + } + case LottieObject::SolidStroke: + case LottieObject::GradientStroke: { + if (strokeCnt > 0) { + parent->reqFragment = true; + return; + } + ++strokeCnt; + break; + } + default: break; + } + } +} + + static bool _buildPrecomp(LottieComposition* comp, LottieGroup* parent) { if (parent->children.count == 0) return false; @@ -983,6 +1011,8 @@ static bool _buildPrecomp(LottieComposition* comp, LottieGroup* parent) if (child->matte.target->refId) _buildReference(comp, child->matte.target); } _bulidHierarchy(parent, child); + + _checkFragment(static_cast(*c)); } return true; } diff --git a/src/loaders/lottie/tvgLottieModel.h b/src/loaders/lottie/tvgLottieModel.h index da1de994..e9ca26e6 100644 --- a/src/loaders/lottie/tvgLottieModel.h +++ b/src/loaders/lottie/tvgLottieModel.h @@ -482,6 +482,8 @@ struct LottieGroup : LottieObject Scene* scene = nullptr; //tvg render data Array children; + + bool reqFragment = false; //requirment to fragment the render context };