mirror of
https://github.com/thorvg/thorvg.git
synced 2025-07-26 16:16:46 +00:00
lottie: chaining modifiers - path
Previously, the rounding modifier was always applied first, which led to incorrect results when a different modifier order was expected. This update removes that limitation and enables modifiers to be applied in any order. Additionally, the code now prevents an infinite loop that occurred when the rounded corners modifier was applied multiple times (a cycle where next was set to this).
This commit is contained in:
parent
1ac6f3d09f
commit
87af337497
4 changed files with 43 additions and 39 deletions
|
@ -715,7 +715,7 @@ void LottieBuilder::updateRoundedCorner(TVG_UNUSED LottieGroup* parent, LottieOb
|
||||||
auto r = roundedCorner->radius(frameNo, tween, exps);
|
auto r = roundedCorner->radius(frameNo, tween, exps);
|
||||||
if (r < LottieRoundnessModifier::ROUNDNESS_EPSILON) return;
|
if (r < LottieRoundnessModifier::ROUNDNESS_EPSILON) return;
|
||||||
|
|
||||||
if (!ctx->roundness) ctx->roundness = new LottieRoundnessModifier(&buffer, r);
|
if (!ctx->roundness) ctx->roundness = new LottieRoundnessModifier(buffer, r);
|
||||||
else if (ctx->roundness->r < r) ctx->roundness->r = r;
|
else if (ctx->roundness->r < r) ctx->roundness->r = r;
|
||||||
|
|
||||||
ctx->update(ctx->roundness);
|
ctx->update(ctx->roundness);
|
||||||
|
@ -725,7 +725,7 @@ void LottieBuilder::updateRoundedCorner(TVG_UNUSED LottieGroup* parent, LottieOb
|
||||||
void LottieBuilder::updateOffsetPath(TVG_UNUSED LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist<RenderContext>& contexts, RenderContext* ctx)
|
void LottieBuilder::updateOffsetPath(TVG_UNUSED LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist<RenderContext>& contexts, RenderContext* ctx)
|
||||||
{
|
{
|
||||||
auto offset = static_cast<LottieOffsetPath*>(*child);
|
auto offset = static_cast<LottieOffsetPath*>(*child);
|
||||||
if (!ctx->offset) ctx->offset = new LottieOffsetModifier(offset->offset(frameNo, tween, exps), offset->miterLimit(frameNo, tween, exps), offset->join);
|
if (!ctx->offset) ctx->offset = new LottieOffsetModifier(buffer, offset->offset(frameNo, tween, exps), offset->miterLimit(frameNo, tween, exps), offset->join);
|
||||||
|
|
||||||
ctx->update(ctx->offset);
|
ctx->update(ctx->offset);
|
||||||
}
|
}
|
||||||
|
@ -1230,7 +1230,7 @@ void LottieBuilder::updateMasks(LottieLayer* layer, float frameNo)
|
||||||
//Masking with Expansion (Offset)
|
//Masking with Expansion (Offset)
|
||||||
} else {
|
} else {
|
||||||
//TODO: Once path direction support is implemented, ensure that the direction is ignored here
|
//TODO: Once path direction support is implemented, ensure that the direction is ignored here
|
||||||
auto offset = LottieOffsetModifier(expand);
|
auto offset = LottieOffsetModifier(buffer, expand);
|
||||||
mask->pathset(frameNo, SHAPE(pShape)->rs.path, nullptr, tween, exps, &offset);
|
mask->pathset(frameNo, SHAPE(pShape)->rs.path, nullptr, tween, exps, &offset);
|
||||||
}
|
}
|
||||||
pOpacity = opacity;
|
pOpacity = opacity;
|
||||||
|
|
|
@ -88,7 +88,7 @@ struct RenderContext
|
||||||
update(roundness);
|
update(roundness);
|
||||||
}
|
}
|
||||||
if (rhs.offset) {
|
if (rhs.offset) {
|
||||||
offset = new LottieOffsetModifier(rhs.offset->offset, rhs.offset->miterLimit, rhs.offset->join);
|
offset = new LottieOffsetModifier(rhs.offset->buffer, rhs.offset->offset, rhs.offset->miterLimit, rhs.offset->join);
|
||||||
update(offset);
|
update(offset);
|
||||||
}
|
}
|
||||||
if (rhs.transform) {
|
if (rhs.transform) {
|
||||||
|
@ -174,7 +174,7 @@ private:
|
||||||
void updateRoundedCorner(LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx);
|
void updateRoundedCorner(LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx);
|
||||||
void updateOffsetPath(LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx);
|
void updateOffsetPath(LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx);
|
||||||
|
|
||||||
RenderPath buffer; //resusable path
|
RenderPath buffer[2]; //alternating buffers used when chaining multiple modifiers; for a single modifier only buffer[0] is used
|
||||||
LottieExpressions* exps;
|
LottieExpressions* exps;
|
||||||
Tween tween;
|
Tween tween;
|
||||||
};
|
};
|
||||||
|
|
|
@ -176,9 +176,8 @@ void LottieOffsetModifier::line(RenderPath& out, PathCommand* inCmds, uint32_t i
|
||||||
|
|
||||||
bool LottieRoundnessModifier::modifyPath(PathCommand* inCmds, uint32_t inCmdsCnt, Point* inPts, uint32_t inPtsCnt, Matrix* transform, RenderPath& out)
|
bool LottieRoundnessModifier::modifyPath(PathCommand* inCmds, uint32_t inCmdsCnt, Point* inPts, uint32_t inPtsCnt, Matrix* transform, RenderPath& out)
|
||||||
{
|
{
|
||||||
buffer->clear();
|
auto& path = next ? (inCmds == buffer[0].cmds.data ? buffer[1] : buffer[0]) : out;
|
||||||
|
if (next) path.clear();
|
||||||
auto& path = (next) ? *buffer : out;
|
|
||||||
|
|
||||||
path.cmds.reserve(inCmdsCnt * 2);
|
path.cmds.reserve(inCmdsCnt * 2);
|
||||||
path.pts.reserve((uint32_t)(inPtsCnt * 1.5));
|
path.pts.reserve((uint32_t)(inPtsCnt * 1.5));
|
||||||
|
@ -237,9 +236,8 @@ bool LottieRoundnessModifier::modifyPolystar(RenderPath& in, RenderPath& out, fl
|
||||||
{
|
{
|
||||||
constexpr auto ROUNDED_POLYSTAR_MAGIC_NUMBER = 0.47829f;
|
constexpr auto ROUNDED_POLYSTAR_MAGIC_NUMBER = 0.47829f;
|
||||||
|
|
||||||
buffer->clear();
|
auto& path = next ? (&in == &buffer[0] ? buffer[1] : buffer[0]) : out;
|
||||||
|
if (next) path.clear();
|
||||||
auto& path = (next) ? *buffer : out;
|
|
||||||
|
|
||||||
auto len = length(in.pts[1] - in.pts[2]);
|
auto len = length(in.pts[1] - in.pts[2]);
|
||||||
auto r = len > 0.0f ? ROUNDED_POLYSTAR_MAGIC_NUMBER * std::min(len * 0.5f, this->r) / len : 0.0f;
|
auto r = len > 0.0f ? ROUNDED_POLYSTAR_MAGIC_NUMBER * std::min(len * 0.5f, this->r) / len : 0.0f;
|
||||||
|
@ -316,10 +314,11 @@ bool LottieRoundnessModifier::modifyRect(Point& size, float& r)
|
||||||
|
|
||||||
bool LottieOffsetModifier::modifyPath(PathCommand* inCmds, uint32_t inCmdsCnt, Point* inPts, uint32_t inPtsCnt, TVG_UNUSED Matrix* transform, RenderPath& out)
|
bool LottieOffsetModifier::modifyPath(PathCommand* inCmds, uint32_t inCmdsCnt, Point* inPts, uint32_t inPtsCnt, TVG_UNUSED Matrix* transform, RenderPath& out)
|
||||||
{
|
{
|
||||||
if (next) TVGERR("LOTTIE", "Offset has a next modifier?");
|
auto& path = next ? (inCmds == buffer[0].cmds.data ? buffer[1] : buffer[0]) : out;
|
||||||
|
if (next) path.clear();
|
||||||
|
|
||||||
out.cmds.reserve(inCmdsCnt * 2);
|
path.cmds.reserve(inCmdsCnt * 2);
|
||||||
out.pts.reserve(inPtsCnt * (join == StrokeJoin::Round ? 4 : 2));
|
path.pts.reserve(inPtsCnt * (join == StrokeJoin::Round ? 4 : 2));
|
||||||
|
|
||||||
Array<Bezier> stack{5};
|
Array<Bezier> stack{5};
|
||||||
State state;
|
State state;
|
||||||
|
@ -331,12 +330,12 @@ bool LottieOffsetModifier::modifyPath(PathCommand* inCmds, uint32_t inCmdsCnt, P
|
||||||
state.moveto = true;
|
state.moveto = true;
|
||||||
state.movetoInIndex = iPt++;
|
state.movetoInIndex = iPt++;
|
||||||
} else if (inCmds[iCmd] == PathCommand::LineTo) {
|
} else if (inCmds[iCmd] == PathCommand::LineTo) {
|
||||||
line(out, inCmds, inCmdsCnt, inPts, iPt, iCmd, state, offset, false);
|
line(path, inCmds, inCmdsCnt, inPts, iPt, iCmd, state, offset, false);
|
||||||
} else if (inCmds[iCmd] == PathCommand::CubicTo) {
|
} else if (inCmds[iCmd] == PathCommand::CubicTo) {
|
||||||
//cubic degenerated to a line
|
//cubic degenerated to a line
|
||||||
if (tvg::zero(inPts[iPt - 1] - inPts[iPt]) || tvg::zero(inPts[iPt + 1] - inPts[iPt + 2])) {
|
if (tvg::zero(inPts[iPt - 1] - inPts[iPt]) || tvg::zero(inPts[iPt + 1] - inPts[iPt + 2])) {
|
||||||
++iPt;
|
++iPt;
|
||||||
line(out, inCmds, inCmdsCnt, inPts, iPt, iCmd, state, offset, true);
|
line(path, inCmds, inCmdsCnt, inPts, iPt, iCmd, state, offset, true);
|
||||||
++iPt;
|
++iPt;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -359,9 +358,9 @@ bool LottieOffsetModifier::modifyPath(PathCommand* inCmds, uint32_t inCmdsCnt, P
|
||||||
auto line3 = _offset(bezier.ctrl2, bezier.end, offset);
|
auto line3 = _offset(bezier.ctrl2, bezier.end, offset);
|
||||||
|
|
||||||
if (state.moveto) {
|
if (state.moveto) {
|
||||||
out.cmds.push(PathCommand::MoveTo);
|
path.cmds.push(PathCommand::MoveTo);
|
||||||
state.movetoOutIndex = out.pts.count;
|
state.movetoOutIndex = path.pts.count;
|
||||||
out.pts.push(line1.pt1);
|
path.pts.push(line1.pt1);
|
||||||
state.firstLine = line1;
|
state.firstLine = line1;
|
||||||
state.moveto = false;
|
state.moveto = false;
|
||||||
}
|
}
|
||||||
|
@ -369,23 +368,26 @@ bool LottieOffsetModifier::modifyPath(PathCommand* inCmds, uint32_t inCmdsCnt, P
|
||||||
bool inside{};
|
bool inside{};
|
||||||
Point intersect{};
|
Point intersect{};
|
||||||
_intersect(line1, line2, intersect, inside);
|
_intersect(line1, line2, intersect, inside);
|
||||||
out.pts.push(intersect);
|
path.pts.push(intersect);
|
||||||
_intersect(line2, line3, intersect, inside);
|
_intersect(line2, line3, intersect, inside);
|
||||||
out.pts.push(intersect);
|
path.pts.push(intersect);
|
||||||
out.pts.push(line3.pt2);
|
path.pts.push(line3.pt2);
|
||||||
out.cmds.push(PathCommand::CubicTo);
|
path.cmds.push(PathCommand::CubicTo);
|
||||||
}
|
}
|
||||||
|
|
||||||
iPt += 3;
|
iPt += 3;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (!tvg::zero(inPts[iPt - 1] - inPts[state.movetoInIndex])) {
|
if (!tvg::zero(inPts[iPt - 1] - inPts[state.movetoInIndex])) {
|
||||||
out.cmds.push(PathCommand::LineTo);
|
path.cmds.push(PathCommand::LineTo);
|
||||||
corner(out, state.line, state.firstLine, state.movetoOutIndex, true);
|
corner(path, state.line, state.firstLine, state.movetoOutIndex, true);
|
||||||
}
|
}
|
||||||
out.cmds.push(PathCommand::Close);
|
path.cmds.push(PathCommand::Close);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (next) return next->modifyPath(path.cmds.data, path.cmds.count, path.pts.data, path.pts.count, transform, out);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,11 @@ struct LottieModifier
|
||||||
enum Type : uint8_t {Roundness = 0, Offset};
|
enum Type : uint8_t {Roundness = 0, Offset};
|
||||||
|
|
||||||
LottieModifier* next = nullptr;
|
LottieModifier* next = nullptr;
|
||||||
|
RenderPath* buffer;
|
||||||
Type type;
|
Type type;
|
||||||
|
bool chained = false;
|
||||||
|
|
||||||
|
LottieModifier(RenderPath* buffer) : buffer(buffer) {}
|
||||||
|
|
||||||
virtual ~LottieModifier() {}
|
virtual ~LottieModifier() {}
|
||||||
|
|
||||||
|
@ -43,29 +47,27 @@ struct LottieModifier
|
||||||
|
|
||||||
LottieModifier* decorate(LottieModifier* next)
|
LottieModifier* decorate(LottieModifier* next)
|
||||||
{
|
{
|
||||||
/* TODO: build the decorative chaining here.
|
//TODO: resolve the possible cyclic decoration by adding support for multiple modifiers of the same type in the RenderContext
|
||||||
currently we only have roundness and offset. */
|
//prevent cyclic decoration: 1) self-decoration: a->decorate(a); 2) mutual decoration: a->decorate(b); b->decorate(a);
|
||||||
|
if (next->chained || next == this) {
|
||||||
|
TVGERR("LOTTIE", "Decoration skipped to prevent cyclic chain with modifier: %p", next);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
this->chained = true;
|
||||||
|
|
||||||
//roundness -> offset
|
//reversed order
|
||||||
if (next->type == Roundness) {
|
|
||||||
next->next = this;
|
next->next = this;
|
||||||
return next;
|
return next;
|
||||||
}
|
}
|
||||||
|
|
||||||
//just in the order.
|
|
||||||
this->next = next;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct LottieRoundnessModifier : LottieModifier
|
struct LottieRoundnessModifier : LottieModifier
|
||||||
{
|
{
|
||||||
static constexpr float ROUNDNESS_EPSILON = 1.0f;
|
static constexpr float ROUNDNESS_EPSILON = 1.0f;
|
||||||
|
|
||||||
RenderPath* buffer;
|
|
||||||
float r;
|
float r;
|
||||||
|
|
||||||
LottieRoundnessModifier(RenderPath* buffer, float r) : buffer(buffer), r(r)
|
LottieRoundnessModifier(RenderPath* buffer, float r) : LottieModifier(buffer), r(r)
|
||||||
{
|
{
|
||||||
type = Roundness;
|
type = Roundness;
|
||||||
}
|
}
|
||||||
|
@ -82,7 +84,7 @@ struct LottieOffsetModifier : LottieModifier
|
||||||
float miterLimit;
|
float miterLimit;
|
||||||
StrokeJoin join;
|
StrokeJoin join;
|
||||||
|
|
||||||
LottieOffsetModifier(float offset, float miter = 4.0f, StrokeJoin join = StrokeJoin::Round) : offset(offset), miterLimit(miter), join(join)
|
LottieOffsetModifier(RenderPath* buffer, float offset, float miter = 4.0f, StrokeJoin join = StrokeJoin::Round) : LottieModifier(buffer), offset(offset), miterLimit(miter), join(join)
|
||||||
{
|
{
|
||||||
type = Offset;
|
type = Offset;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue