Lottie: Fixed handling of multiple strokes in one layer.

Revised the rendering logic of Lottie by creating a new rendering context
using a queue when multiple strokes are requested.

Issue: https://github.com/thorvg/thorvg/issues/1642
This commit is contained in:
Hermet Park 2023-10-05 16:49:31 +09:00 committed by Hermet Park
parent 32b825c6d7
commit 57038df21f

View file

@ -20,6 +20,8 @@
* SOFTWARE. * SOFTWARE.
*/ */
#include <queue>
#include "tvgCommon.h" #include "tvgCommon.h"
#include "tvgPaint.h" #include "tvgPaint.h"
#include "tvgShape.h" #include "tvgShape.h"
@ -36,6 +38,7 @@ struct RenderContext
{ {
Shape* propagator = nullptr; Shape* propagator = nullptr;
Shape* merging = nullptr; //merging shapes if possible (if shapes have same properties) Shape* merging = nullptr; //merging shapes if possible (if shapes have same properties)
LottieObject** begin = nullptr; //iteration entry point
float roundness = 0.0f; float roundness = 0.0f;
struct { struct {
@ -52,6 +55,8 @@ struct RenderContext
bool valid = false; bool valid = false;
} repeater; } repeater;
bool fragmented = false; //context has been separated. allow adding visual properties(fill/stroking) if it is false.
RenderContext() RenderContext()
{ {
propagator = Shape::gen().release(); propagator = Shape::gen().release();
@ -64,14 +69,15 @@ struct RenderContext
RenderContext(const RenderContext& rhs) RenderContext(const RenderContext& rhs)
{ {
propagator = rhs.propagator ? static_cast<Shape*>(rhs.propagator->duplicate()) : Shape::gen().release(); propagator = static_cast<Shape*>(rhs.propagator->duplicate());
repeater = rhs.repeater; repeater = rhs.repeater;
roundness = rhs.roundness; roundness = rhs.roundness;
fragmented = rhs.fragmented;
} }
}; };
static void _updateChildren(LottieGroup* parent, int32_t frameNo, RenderContext& ctx); static void _updateChildren(LottieGroup* parent, int32_t frameNo, queue<RenderContext>& contexts);
static void _updateLayer(LottieLayer* root, LottieLayer* layer, int32_t frameNo); static void _updateLayer(LottieLayer* root, LottieLayer* layer, int32_t frameNo);
static bool _buildPrecomp(LottieComposition* comp, LottieGroup* parent); static bool _buildPrecomp(LottieComposition* comp, LottieGroup* parent);
@ -180,23 +186,15 @@ static void _updateTransform(LottieTransform* transform, int32_t frameNo, Render
static void _updateGroup(LottieGroup* parent, LottieGroup* group, int32_t frameNo, RenderContext& ctx) static void _updateGroup(LottieGroup* parent, LottieGroup* group, int32_t frameNo, RenderContext& ctx)
{ {
if (group->children.empty()) return;
//Prepare render data //Prepare render data
group->scene = parent->scene; group->scene = parent->scene;
auto ctx2 = ctx;
_updateChildren(group, frameNo, ctx2);
}
queue<RenderContext> contexts;
contexts.push(ctx);
static void _updateFill(LottieSolidFill* fill, int32_t frameNo, RenderContext& ctx) _updateChildren(group, frameNo, contexts);
{
//TODO: Skip if property has not been modified actually.
ctx.merging = nullptr;
auto color = fill->color(frameNo);
ctx.propagator->fill(color.rgb[0], color.rgb[1], color.rgb[2], fill->opacity(frameNo));
ctx.propagator->fill(fill->rule);
if (ctx.propagator->strokeWidth() > 0) ctx.propagator->order(true);
} }
@ -218,25 +216,73 @@ static void _updateStroke(LottieStroke* stroke, int32_t frameNo, RenderContext&
} }
static void _updateStroke(LottieSolidStroke* stroke, int32_t frameNo, RenderContext& ctx) static void _updateSolidStroke(LottieObject** child, int32_t frameNo, queue<RenderContext>& 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;
auto stroke = static_cast<LottieSolidStroke*>(*child);
ctx.merging = nullptr; ctx.merging = nullptr;
auto color = stroke->color(frameNo); 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<LottieStroke*>(stroke), frameNo, ctx); _updateStroke(static_cast<LottieStroke*>(stroke), frameNo, ctx);
} }
static void _updateStroke(LottieGradientStroke* stroke, int32_t frameNo, RenderContext& ctx) static void _updateGradientStroke(LottieObject** child, int32_t frameNo, queue<RenderContext>& 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;
auto stroke = static_cast<LottieGradientStroke*>(*child);
ctx.merging = nullptr; ctx.merging = nullptr;
ctx.propagator->stroke(unique_ptr<Fill>(stroke->fill(frameNo))); ctx.propagator->stroke(unique_ptr<Fill>(stroke->fill(frameNo)));
_updateStroke(static_cast<LottieStroke*>(stroke), frameNo, ctx); _updateStroke(static_cast<LottieStroke*>(stroke), frameNo, ctx);
} }
static void _updateFill(LottieSolidFill* fill, int32_t frameNo, RenderContext& ctx)
{
if (ctx.fragmented) return;
ctx.merging = nullptr;
auto color = fill->color(frameNo);
ctx.propagator->fill(color.rgb[0], color.rgb[1], color.rgb[2], fill->opacity(frameNo));
ctx.propagator->fill(fill->rule);
if (ctx.propagator->strokeWidth() > 0) ctx.propagator->order(true);
}
static Shape* _updateFill(LottieGradientFill* fill, int32_t frameNo, RenderContext& ctx) static Shape* _updateFill(LottieGradientFill* fill, int32_t frameNo, RenderContext& ctx)
{ {
if (ctx.fragmented) return nullptr;
ctx.merging = nullptr; ctx.merging = nullptr;
//TODO: reuse the fill instance? //TODO: reuse the fill instance?
@ -559,7 +605,6 @@ static void _updatePolystar(LottieGroup* parent, LottiePolyStar* star, int32_t f
auto identity = mathIdentity((const Matrix*)&matrix); auto identity = mathIdentity((const Matrix*)&matrix);
if (ctx.repeater.valid) { if (ctx.repeater.valid) {
auto p = Shape::gen(); auto p = Shape::gen();
if (star->type == LottiePolyStar::Star) _updateStar(parent, star, identity ? nullptr : &matrix, frameNo, p.get()); if (star->type == LottiePolyStar::Star) _updateStar(parent, star, identity ? nullptr : &matrix, frameNo, p.get());
@ -659,12 +704,13 @@ static void _updateTrimpath(LottieTrimpath* trimpath, int32_t frameNo, RenderCon
} }
static void _updateChildren(LottieGroup* parent, int32_t frameNo, RenderContext& ctx) static void _updateChildren(LottieGroup* parent, int32_t frameNo, queue<RenderContext>& contexts)
{ {
if (parent->children.empty()) return; contexts.front().begin = parent->children.end() - 1;
//Draw the parent shapes first while (contexts.size() > 0) {
for (auto child = parent->children.end() - 1; child >= parent->children.data; --child) { auto& ctx = contexts.front();
for (auto child = ctx.begin; child >= parent->children.data; --child) {
//TODO: Polymorphsim? //TODO: Polymorphsim?
switch ((*child)->type) { switch ((*child)->type) {
case LottieObject::Group: { case LottieObject::Group: {
@ -680,7 +726,7 @@ static void _updateChildren(LottieGroup* parent, int32_t frameNo, RenderContext&
break; break;
} }
case LottieObject::SolidStroke: { case LottieObject::SolidStroke: {
_updateStroke(static_cast<LottieSolidStroke*>(*child), frameNo, ctx); _updateSolidStroke(child, frameNo, contexts, ctx);
break; break;
} }
case LottieObject::GradientFill: { case LottieObject::GradientFill: {
@ -688,7 +734,7 @@ static void _updateChildren(LottieGroup* parent, int32_t frameNo, RenderContext&
break; break;
} }
case LottieObject::GradientStroke: { case LottieObject::GradientStroke: {
_updateStroke(static_cast<LottieGradientStroke*>(*child), frameNo, ctx); _updateGradientStroke(child, frameNo, contexts, ctx);
break; break;
} }
case LottieObject::Rect: { case LottieObject::Rect: {
@ -726,6 +772,8 @@ static void _updateChildren(LottieGroup* parent, int32_t frameNo, RenderContext&
default: break; default: break;
} }
} }
contexts.pop();
}
} }
@ -839,7 +887,9 @@ static void _updateLayer(LottieLayer* root, LottieLayer* layer, int32_t frameNo)
switch (layer->type) { switch (layer->type) {
case LottieLayer::Precomp: { case LottieLayer::Precomp: {
if (!layer->children.empty()) {
_updatePrecomp(layer, frameNo); _updatePrecomp(layer, frameNo);
}
break; break;
} }
case LottieLayer::Solid: { case LottieLayer::Solid: {
@ -847,8 +897,11 @@ static void _updateLayer(LottieLayer* root, LottieLayer* layer, int32_t frameNo)
break; break;
} }
default: { default: {
RenderContext ctx; if (!layer->children.empty()) {
_updateChildren(layer, frameNo, ctx); queue<RenderContext> contexts;
contexts.emplace();
_updateChildren(layer, frameNo, contexts);
}
break; break;
} }
} }