diff --git a/src/bindings/capi/thorvg_capi.h b/src/bindings/capi/thorvg_capi.h index c4dd9c6a..fc7dbac3 100644 --- a/src/bindings/capi/thorvg_capi.h +++ b/src/bindings/capi/thorvg_capi.h @@ -2542,6 +2542,25 @@ TVG_API Tvg_Result tvg_lottie_animation_get_markers_cnt(Tvg_Animation* animation TVG_API Tvg_Result tvg_lottie_animation_get_marker(Tvg_Animation* animation, uint32_t idx, const char** name); +/** + * @brief Interpolates between two frames over a specified duration. + * + * This method performs tweening, a process of generating intermediate frame + * between @p from and @p to based on the given @p progress. + * + * @param[in] animation The Tvg_Animation pointer to the Lottie animation object. + * @param[in] from The start frame number of the interpolation. + * @param[in] to The end frame number of the interpolation. + * @param[in] progress The current progress of the interpolation (range: 0.0 to 1.0). + * + * @return Tvg_Result enumeration. + * @retval TVG_RESULT_INSUFFICIENT_CONDITION In case the animation is not loaded. + * + * @note Experimental API + */ +TVG_API Tvg_Result tvg_lottie_animation_tween(Tvg_Animation* animation, float from, float to, float progress); + + /** \} */ // end addtogroup ThorVGCapi_LottieAnimation diff --git a/src/bindings/capi/tvgCapi.cpp b/src/bindings/capi/tvgCapi.cpp index d205bd48..8d6af594 100644 --- a/src/bindings/capi/tvgCapi.cpp +++ b/src/bindings/capi/tvgCapi.cpp @@ -960,6 +960,16 @@ TVG_API Tvg_Result tvg_lottie_animation_get_marker(Tvg_Animation* animation, uin return TVG_RESULT_NOT_SUPPORTED; } + +TVG_API Tvg_Result tvg_lottie_animation_tween(Tvg_Animation* animation, float from, float to, float progress) +{ +#ifdef THORVG_LOTTIE_LOADER_SUPPORT + if (!animation) return TVG_RESULT_INVALID_ARGUMENT; + return (Tvg_Result) reinterpret_cast(animation)->tween(from, to, progress); +#endif + return TVG_RESULT_NOT_SUPPORTED; +} + #ifdef __cplusplus } #endif diff --git a/src/loaders/lottie/thorvg_lottie.h b/src/loaders/lottie/thorvg_lottie.h index f2714a19..501aac2d 100644 --- a/src/loaders/lottie/thorvg_lottie.h +++ b/src/loaders/lottie/thorvg_lottie.h @@ -51,6 +51,22 @@ public: */ Result segment(const char* marker) noexcept; + /** + * @brief Interpolates between two frames over a specified duration. + * + * This method performs tweening, a process of generating intermediate frame + * between @p from and @p to based on the given @p progress. + * + * @param[in] from The start frame number of the interpolation. + * @param[in] to The end frame number of the interpolation. + * @param[in] progress The current progress of the interpolation (range: 0.0 to 1.0). + * + * @retval Result::InsufficientCondition In case the animation is not loaded. + * + * @note Experimental API + */ + Result tween(float from, float to, float progress) noexcept; + /** * @brief Gets the marker count of the animation. * diff --git a/src/loaders/lottie/tvgLottieAnimation.cpp b/src/loaders/lottie/tvgLottieAnimation.cpp index 7b5f8332..b2b77f80 100644 --- a/src/loaders/lottie/tvgLottieAnimation.cpp +++ b/src/loaders/lottie/tvgLottieAnimation.cpp @@ -33,9 +33,10 @@ LottieAnimation::LottieAnimation() Result LottieAnimation::override(const char* slot) noexcept { - if (!PICTURE(pImpl->picture)->loader) return Result::InsufficientCondition; + auto loader = PICTURE(pImpl->picture)->loader; + if (!loader) return Result::InsufficientCondition; - if (static_cast(PICTURE(pImpl->picture)->loader)->override(slot)) return Result::Success; + if (static_cast(loader)->override(slot)) return Result::Success; return Result::InvalidArguments; } @@ -58,6 +59,15 @@ Result LottieAnimation::segment(const char* marker) noexcept } +Result LottieAnimation::tween(float from, float to, float progress) noexcept +{ + auto loader = PICTURE(pImpl->picture)->loader; + if (!loader) return Result::InsufficientCondition; + if (!static_cast(loader)->tween(from, to, progress)) return Result::InsufficientCondition; + return Result::Success; +} + + uint32_t LottieAnimation::markersCnt() noexcept { auto loader = PICTURE(pImpl->picture)->loader; diff --git a/src/loaders/lottie/tvgLottieBuilder.cpp b/src/loaders/lottie/tvgLottieBuilder.cpp index 3173420c..da3e23a1 100644 --- a/src/loaders/lottie/tvgLottieBuilder.cpp +++ b/src/loaders/lottie/tvgLottieBuilder.cpp @@ -38,13 +38,13 @@ static bool _buildComposition(LottieComposition* comp, LottieLayer* parent); static bool _draw(LottieGroup* parent, LottieShape* shape, RenderContext* ctx); -static void _rotate(LottieTransform* transform, float frameNo, Matrix& m, float angle, LottieExpressions* exps) +static void _rotate(LottieTransform* transform, float frameNo, Matrix& m, float angle, Tween& tween, LottieExpressions* exps) { //rotation xyz if (transform->rotationEx) { - auto radianX = deg2rad(transform->rotationEx->x(frameNo, exps)); - auto radianY = deg2rad(transform->rotationEx->y(frameNo, exps)); - auto radianZ = deg2rad(transform->rotation(frameNo, exps)) + angle; + auto radianX = deg2rad(transform->rotationEx->x(frameNo, tween, exps)); + auto radianY = deg2rad(transform->rotationEx->y(frameNo, tween, exps)); + auto radianZ = deg2rad(transform->rotation(frameNo, tween, exps)) + angle; auto cx = cosf(radianX), sx = sinf(radianX); auto cy = cosf(radianY), sy = sinf(radianY);; auto cz = cosf(radianZ), sz = sinf(radianZ);; @@ -54,7 +54,7 @@ static void _rotate(LottieTransform* transform, float frameNo, Matrix& m, float m.e22 = -sx * sy * sz + cx * cz; //rotation z } else { - auto degree = transform->rotation(frameNo, exps) + angle; + auto degree = transform->rotation(frameNo, tween, exps) + angle; if (degree == 0.0f) return; auto radian = deg2rad(degree); m.e11 = cosf(radian); @@ -68,7 +68,7 @@ static void _rotate(LottieTransform* transform, float frameNo, Matrix& m, float static void _skew(Matrix* m, float angleDeg, float axisDeg) { auto angle = -deg2rad(angleDeg); - float tanVal = tanf(angle); + auto tanVal = tanf(angle); axisDeg = fmod(axisDeg, 180.0f); if (fabsf(axisDeg) < 0.01f || fabsf(axisDeg - 180.0f) < 0.01f || fabsf(axisDeg + 180.0f) < 0.01f) { @@ -86,8 +86,8 @@ static void _skew(Matrix* m, float angleDeg, float axisDeg) } auto axis = -deg2rad(axisDeg); - float cosVal = cosf(axis); - float sinVal = sinf(axis); + auto cosVal = cosf(axis); + auto sinVal = sinf(axis); auto A = sinVal * cosVal * tanVal; auto B = cosVal * cosVal * tanVal; auto C = sinVal * sinVal * tanVal; @@ -101,7 +101,7 @@ static void _skew(Matrix* m, float angleDeg, float axisDeg) } -static bool _updateTransform(LottieTransform* transform, float frameNo, bool autoOrient, Matrix& matrix, uint8_t& opacity, LottieExpressions* exps) +static bool _updateTransform(LottieTransform* transform, float frameNo, Matrix& matrix, uint8_t& opacity, bool autoOrient, Tween& tween, LottieExpressions* exps) { identity(&matrix); @@ -110,14 +110,13 @@ static bool _updateTransform(LottieTransform* transform, float frameNo, bool aut return false; } - if (transform->coords) translate(&matrix, {transform->coords->x(frameNo, exps), transform->coords->y(frameNo, exps)}); - else translate(&matrix, transform->position(frameNo, exps)); + if (transform->coords) translate(&matrix, {transform->coords->x(frameNo, tween, exps), transform->coords->y(frameNo, tween, exps)}); + else translate(&matrix, transform->position(frameNo, tween, exps)); - auto angle = 0.0f; - if (autoOrient) angle = transform->position.angle(frameNo); - _rotate(transform, frameNo, matrix, angle, exps); + auto angle = (autoOrient) ? transform->position.angle(frameNo, tween) : 0.0f; + _rotate(transform, frameNo, matrix, angle, tween, exps); - auto skewAngle = transform->skewAngle(frameNo, exps); + auto skewAngle = transform->skewAngle(frameNo, tween, exps); if (skewAngle != 0.0f) { // For angles where tangent explodes, the shape degenerates into an infinitely thin line. // This is handled by zeroing out the matrix due to finite numerical precision. @@ -126,15 +125,15 @@ static bool _updateTransform(LottieTransform* transform, float frameNo, bool aut _skew(&matrix, skewAngle, transform->skewAxis(frameNo, exps)); } - auto scale = transform->scale(frameNo, exps); + auto scale = transform->scale(frameNo, tween, exps); scaleR(&matrix, scale * 0.01f); //Lottie specific anchor transform. - translateR(&matrix, -transform->anchor(frameNo, exps)); + translateR(&matrix, -transform->anchor(frameNo, tween, exps)); //invisible just in case. - if (scale.x == 0.0f || scale.y == 0.0f) opacity = 0; - else opacity = transform->opacity(frameNo, exps); + if (tvg::zero(scale)) opacity = 0; + else opacity = transform->opacity(frameNo, tween, exps); return true; } @@ -142,7 +141,7 @@ static bool _updateTransform(LottieTransform* transform, float frameNo, bool aut void LottieBuilder::updateTransform(LottieLayer* layer, float frameNo) { - if (!layer || tvg::equal(layer->cache.frameNo, frameNo)) return; + if (!layer || (!tweening() && tvg::equal(layer->cache.frameNo, frameNo))) return; auto transform = layer->transform; auto parent = layer->parent; @@ -151,7 +150,7 @@ void LottieBuilder::updateTransform(LottieLayer* layer, float frameNo) auto& matrix = layer->cache.matrix; - _updateTransform(transform, frameNo, layer->autoOrient, matrix, layer->cache.opacity, exps); + _updateTransform(transform, frameNo, matrix, layer->cache.opacity, layer->autoOrient, tween, exps); if (parent) { if (!identity((const Matrix*) &parent->cache.matrix)) { @@ -172,14 +171,14 @@ void LottieBuilder::updateTransform(LottieGroup* parent, LottieObject** child, f if (parent->mergeable()) { if (!ctx->transform) ctx->transform = (Matrix*)malloc(sizeof(Matrix)); - _updateTransform(transform, frameNo, false, *ctx->transform, opacity, exps); + _updateTransform(transform, frameNo, *ctx->transform, opacity, false, tween, exps); return; } ctx->merging = nullptr; Matrix matrix; - if (!_updateTransform(transform, frameNo, false, matrix, opacity, exps)) return; + if (!_updateTransform(transform, frameNo, matrix, opacity, false, tween, exps)) return; ctx->propagator->transform(ctx->propagator->transform() * matrix); ctx->propagator->opacity(MULTIPLY(opacity, PAINT(ctx->propagator)->opacity)); @@ -213,18 +212,18 @@ void LottieBuilder::updateGroup(LottieGroup* parent, LottieObject** child, float } -static void _updateStroke(LottieStroke* stroke, float frameNo, RenderContext* ctx, LottieExpressions* exps) +static void _updateStroke(LottieStroke* stroke, float frameNo, RenderContext* ctx, Tween& tween, LottieExpressions* exps) { - ctx->propagator->strokeWidth(stroke->width(frameNo, exps)); + ctx->propagator->strokeWidth(stroke->width(frameNo, tween, exps)); ctx->propagator->strokeCap(stroke->cap); ctx->propagator->strokeJoin(stroke->join); ctx->propagator->strokeMiterlimit(stroke->miterLimit); if (stroke->dashattr) { float dashes[2]; - dashes[0] = stroke->dashSize(frameNo, exps); - dashes[1] = dashes[0] + stroke->dashGap(frameNo, exps); - ctx->propagator->strokeDash(dashes, 2, stroke->dashOffset(frameNo, exps)); + dashes[0] = stroke->dashSize(frameNo, tween, exps); + dashes[1] = dashes[0] + stroke->dashGap(frameNo, tween, exps); + ctx->propagator->strokeDash(dashes, 2, stroke->dashOffset(frameNo, tween, exps)); } else { ctx->propagator->strokeDash(nullptr, 0); } @@ -252,9 +251,9 @@ void LottieBuilder::updateSolidStroke(LottieGroup* parent, LottieObject** child, auto stroke = static_cast(*child); ctx->merging = nullptr; - auto color = stroke->color(frameNo, exps); - ctx->propagator->strokeFill(color.rgb[0], color.rgb[1], color.rgb[2], stroke->opacity(frameNo, exps)); - _updateStroke(static_cast(stroke), frameNo, ctx, exps); + auto color = stroke->color(frameNo, tween, exps); + ctx->propagator->strokeFill(color.rgb[0], color.rgb[1], color.rgb[2], stroke->opacity(frameNo, tween, exps)); + _updateStroke(static_cast(stroke), frameNo, ctx, tween, exps); } @@ -265,8 +264,8 @@ void LottieBuilder::updateGradientStroke(LottieGroup* parent, LottieObject** chi auto stroke = static_cast(*child); ctx->merging = nullptr; - ctx->propagator->strokeFill(stroke->fill(frameNo, exps)); - _updateStroke(static_cast(stroke), frameNo, ctx, exps); + ctx->propagator->strokeFill(stroke->fill(frameNo, tween, exps)); + _updateStroke(static_cast(stroke), frameNo, ctx, tween, exps); } @@ -277,8 +276,9 @@ void LottieBuilder::updateSolidFill(LottieGroup* parent, LottieObject** child, f auto fill = static_cast(*child); ctx->merging = nullptr; - auto color = fill->color(frameNo, exps); - ctx->propagator->fill(color.rgb[0], color.rgb[1], color.rgb[2], fill->opacity(frameNo, exps)); + + auto color = fill->color(frameNo, tween, exps); + ctx->propagator->fill(color.rgb[0], color.rgb[1], color.rgb[2], fill->opacity(frameNo, tween, exps)); ctx->propagator->fill(fill->rule); if (ctx->propagator->strokeWidth() > 0) ctx->propagator->order(true); @@ -293,7 +293,7 @@ void LottieBuilder::updateGradientFill(LottieGroup* parent, LottieObject** child ctx->merging = nullptr; //TODO: reuse the fill instance? - ctx->propagator->fill(fill->fill(frameNo, exps)); + ctx->propagator->fill(fill->fill(frameNo, tween, exps)); ctx->propagator->fill(fill->rule); if (ctx->propagator->strokeWidth() > 0) ctx->propagator->order(true); @@ -464,9 +464,9 @@ void LottieBuilder::appendRect(Shape* shape, Point& pos, Point& size, float r, b void LottieBuilder::updateRect(LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist& contexts, RenderContext* ctx) { auto rect = static_cast(*child); - auto size = rect->size(frameNo, exps); - auto pos = rect->position(frameNo, exps) - size * 0.5f; - auto r = rect->radius(frameNo, exps); + auto size = rect->size(frameNo, tween, exps); + auto pos = rect->position(frameNo, tween, exps) - size * 0.5f; + auto r = rect->radius(frameNo, tween, exps); if (r == 0.0f) { if (ctx->roundness) ctx->roundness->modifyRect(size, r); @@ -523,8 +523,8 @@ void LottieBuilder::updateEllipse(LottieGroup* parent, LottieObject** child, flo { auto ellipse = static_cast(*child); - auto pos = ellipse->position(frameNo, exps); - auto size = ellipse->size(frameNo, exps) * 0.5f; + auto pos = ellipse->position(frameNo, tween, exps); + auto size = ellipse->size(frameNo, tween, exps) * 0.5f; if (!ctx->repeaters.empty()) { auto shape = ellipse->pooling(); @@ -542,29 +542,29 @@ void LottieBuilder::updatePath(LottieGroup* parent, LottieObject** child, float { auto path = static_cast(*child); - if (!ctx->repeaters.empty()) { - auto shape = path->pooling(); - shape->reset(); - path->pathset(frameNo, SHAPE(shape)->rs.path, ctx->transform, exps, ctx->modifier); - _repeat(parent, shape, ctx); - } else { + if (ctx->repeaters.empty()) { _draw(parent, path, ctx); - if (path->pathset(frameNo, SHAPE(ctx->merging)->rs.path, ctx->transform, exps, ctx->modifier)) { + if (path->pathset(frameNo, SHAPE(ctx->merging)->rs.path, ctx->transform, tween, exps, ctx->modifier)) { PAINT(ctx->merging)->update(RenderUpdateFlag::Path); } + } else { + auto shape = path->pooling(); + shape->reset(); + path->pathset(frameNo, SHAPE(shape)->rs.path, ctx->transform, tween, exps, ctx->modifier); + _repeat(parent, shape, ctx); } } -void LottieBuilder::updateStar(LottiePolyStar* star, float frameNo, Matrix* transform, Shape* merging, RenderContext* ctx) +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; - auto ptsCnt = star->ptsCnt(frameNo, exps); - auto innerRadius = star->innerRadius(frameNo, exps); - auto outerRadius = star->outerRadius(frameNo, exps); - auto innerRoundness = star->innerRoundness(frameNo, exps) * 0.01f; - auto outerRoundness = star->outerRoundness(frameNo, exps) * 0.01f; + auto ptsCnt = star->ptsCnt(frameNo, tween, exps); + auto innerRadius = star->innerRadius(frameNo, tween, exps); + auto outerRadius = star->outerRadius(frameNo, tween, exps); + auto innerRoundness = star->innerRoundness(frameNo, tween, exps) * 0.01f; + auto outerRoundness = star->outerRoundness(frameNo, tween, exps) * 0.01f; auto angle = deg2rad(-90.0f); auto partialPointRadius = 0.0f; @@ -676,13 +676,13 @@ void LottieBuilder::updateStar(LottiePolyStar* star, float frameNo, Matrix* tran } -void LottieBuilder::updatePolygon(LottieGroup* parent, LottiePolyStar* star, float frameNo, Matrix* transform, Shape* merging, RenderContext* ctx) +void LottieBuilder::updatePolygon(LottieGroup* parent, LottiePolyStar* star, float frameNo, Matrix* transform, Shape* merging, RenderContext* ctx, Tween& tween, LottieExpressions* exps) { static constexpr auto POLYGON_MAGIC_NUMBER = 0.25f; - auto ptsCnt = size_t(floor(star->ptsCnt(frameNo, exps))); - auto radius = star->outerRadius(frameNo, exps); - auto outerRoundness = star->outerRoundness(frameNo, exps) * 0.01f; + auto ptsCnt = size_t(floor(star->ptsCnt(frameNo, tween, exps))); + auto radius = star->outerRadius(frameNo, tween, exps); + auto outerRoundness = star->outerRoundness(frameNo, tween, exps) * 0.01f; auto angle = deg2rad(-90.0f); auto anglePerPoint = 2.0f * MATH_PI / float(ptsCnt); @@ -761,8 +761,8 @@ void LottieBuilder::updatePolystar(LottieGroup* parent, LottieObject** child, fl //Optimize: Can we skip the individual coords transform? Matrix matrix; identity(&matrix); - translate(&matrix, star->position(frameNo, exps)); - rotate(&matrix, star->rotation(frameNo, exps)); + translate(&matrix, star->position(frameNo, tween, exps)); + rotate(&matrix, star->rotation(frameNo, tween, exps)); if (ctx->transform) matrix = *ctx->transform * matrix; @@ -771,13 +771,13 @@ void LottieBuilder::updatePolystar(LottieGroup* parent, LottieObject** child, fl if (!ctx->repeaters.empty()) { auto shape = star->pooling(); shape->reset(); - if (star->type == LottiePolyStar::Star) updateStar(star, frameNo, (identity ? nullptr : &matrix), shape, ctx); - else updatePolygon(parent, star, frameNo, (identity ? nullptr : &matrix), shape, ctx); + if (star->type == LottiePolyStar::Star) updateStar(star, frameNo, (identity ? nullptr : &matrix), shape, ctx, tween, exps); + else updatePolygon(parent, star, frameNo, (identity ? nullptr : &matrix), shape, ctx, tween, exps); _repeat(parent, shape, ctx); } else { _draw(parent, star, ctx); - if (star->type == LottiePolyStar::Star) updateStar(star, frameNo, (identity ? nullptr : &matrix), ctx->merging, ctx); - else updatePolygon(parent, star, frameNo, (identity ? nullptr : &matrix), ctx->merging, ctx); + if (star->type == LottiePolyStar::Star) updateStar(star, frameNo, (identity ? nullptr : &matrix), ctx->merging, ctx, tween, exps); + else updatePolygon(parent, star, frameNo, (identity ? nullptr : &matrix), ctx->merging, ctx, tween, exps); PAINT(ctx->merging)->update(RenderUpdateFlag::Path); } } @@ -786,7 +786,7 @@ void LottieBuilder::updatePolystar(LottieGroup* parent, LottieObject** child, fl void LottieBuilder::updateRoundedCorner(TVG_UNUSED LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist& contexts, RenderContext* ctx) { auto roundedCorner = static_cast(*child); - auto r = roundedCorner->radius(frameNo, exps); + auto r = roundedCorner->radius(frameNo, tween, exps); if (r < LottieRoundnessModifier::ROUNDNESS_EPSILON) return; if (!ctx->roundness) ctx->roundness = new LottieRoundnessModifier(&buffer, r); @@ -799,7 +799,7 @@ void LottieBuilder::updateRoundedCorner(TVG_UNUSED LottieGroup* parent, LottieOb void LottieBuilder::updateOffsetPath(TVG_UNUSED LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist& contexts, RenderContext* ctx) { auto offset = static_cast(*child); - if (!ctx->offset) ctx->offset = new LottieOffsetModifier(offset->offset(frameNo, exps), offset->miterLimit(frameNo, exps), offset->join); + if (!ctx->offset) ctx->offset = new LottieOffsetModifier(offset->offset(frameNo, tween, exps), offset->miterLimit(frameNo, tween, exps), offset->join); ctx->update(ctx->offset); } @@ -810,15 +810,15 @@ void LottieBuilder::updateRepeater(TVG_UNUSED LottieGroup* parent, LottieObject* auto repeater = static_cast(*child); RenderRepeater r; - r.cnt = static_cast(repeater->copies(frameNo, exps)); + r.cnt = static_cast(repeater->copies(frameNo, tween, exps)); r.transform = ctx->propagator->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.offset = repeater->offset(frameNo, tween, exps); + r.position = repeater->position(frameNo, tween, exps); + r.anchor = repeater->anchor(frameNo, tween, exps); + r.scale = repeater->scale(frameNo, tween, exps); + r.rotation = repeater->rotation(frameNo, tween, exps); + r.startOpacity = repeater->startOpacity(frameNo, tween, exps); + r.endOpacity = repeater->endOpacity(frameNo, tween, exps); r.inorder = repeater->inorder; r.interpOpacity = (r.startOpacity == r.endOpacity) ? false : true; ctx->repeaters.push(r); @@ -832,7 +832,7 @@ void LottieBuilder::updateTrimpath(TVG_UNUSED LottieGroup* parent, LottieObject* auto trimpath = static_cast(*child); float begin, end; - trimpath->segment(frameNo, begin, end, exps); + trimpath->segment(frameNo, begin, end, tween, exps); if (SHAPE(ctx->propagator)->rs.stroke) { auto pbegin = SHAPE(ctx->propagator)->rs.stroke->trim.begin; @@ -940,6 +940,18 @@ void LottieBuilder::updatePrecomp(LottieComposition* comp, LottieLayer* precomp, } +void LottieBuilder::updatePrecomp(LottieComposition* comp, LottieLayer* precomp, float frameNo, Tween& tween) +{ + //record & recover the tweening frame number before remapping + auto record = tween.frameNo; + tween.frameNo = precomp->remap(comp, record, exps); + + updatePrecomp(comp, precomp, frameNo); + + tween.frameNo = record; +} + + void LottieBuilder::updateSolid(LottieLayer* layer) { auto solidFill = layer->statical.pooling(true); @@ -1056,7 +1068,7 @@ void LottieBuilder::updateText(LottieLayer* layer, float frameNo) ARRAY_FOREACH(p, glyph->children) { auto group = static_cast(*p); ARRAY_FOREACH(p, group->children) { - if (static_cast(*p)->pathset(frameNo, SHAPE(shape)->rs.path, nullptr, exps)) { + if (static_cast(*p)->pathset(frameNo, SHAPE(shape)->rs.path, nullptr, tween, exps)) { PAINT(shape)->update(RenderUpdateFlag::Path); } } @@ -1095,40 +1107,39 @@ void LottieBuilder::updateText(LottieLayer* layer, float frameNo) if (tvg::zero(f)) continue; needGroup = true; - translation = translation + f * range->style.position(frameNo, exps); - scaling = scaling * (f * (range->style.scale(frameNo, exps) * 0.01f - Point{1.0f, 1.0f}) + Point{1.0f, 1.0f}); - rotation += f * range->style.rotation(frameNo, exps); + translation = translation + f * range->style.position(frameNo, tween, exps); + scaling = scaling * (f * (range->style.scale(frameNo, tween, exps) * 0.01f - Point{1.0f, 1.0f}) + Point{1.0f, 1.0f}); + rotation += f * range->style.rotation(frameNo, tween, exps); - opacity = (uint8_t)(opacity - f * (opacity - range->style.opacity(frameNo, exps))); + opacity = (uint8_t)(opacity - f * (opacity - range->style.opacity(frameNo, tween, exps))); shape->opacity(opacity); - auto rangeColor = range->style.fillColor(frameNo, exps); //TODO: use flag to check whether it was really set + auto rangeColor = range->style.fillColor(frameNo, tween, exps); //TODO: use flag to check whether it was really set if (tvg::equal(f, 1.0f)) color = rangeColor; else { color.rgb[0] = lerp(color.rgb[0], rangeColor.rgb[0], f); color.rgb[1] = lerp(color.rgb[1], rangeColor.rgb[1], f); color.rgb[2] = lerp(color.rgb[2], rangeColor.rgb[2], f); } - fillOpacity = (uint8_t)(fillOpacity - f * (fillOpacity - range->style.fillOpacity(frameNo, exps))); + fillOpacity = (uint8_t)(fillOpacity - f * (fillOpacity - range->style.fillOpacity(frameNo, tween, exps))); shape->fill(color.rgb[0], color.rgb[1], color.rgb[2], fillOpacity); - shape->strokeWidth(f * range->style.strokeWidth(frameNo, exps) / scale); + shape->strokeWidth(f * range->style.strokeWidth(frameNo, tween, exps) / scale); if (shape->strokeWidth() > 0.0f) { - auto rangeColor = range->style.strokeColor(frameNo, exps); //TODO: use flag to check whether it was really set + auto rangeColor = range->style.strokeColor(frameNo, tween, exps); //TODO: use flag to check whether it was really set if (tvg::equal(f, 1.0f)) strokeColor = rangeColor; else { strokeColor.rgb[0] = lerp(strokeColor.rgb[0], rangeColor.rgb[0], f); strokeColor.rgb[1] = lerp(strokeColor.rgb[1], rangeColor.rgb[1], f); strokeColor.rgb[2] = lerp(strokeColor.rgb[2], rangeColor.rgb[2], f); } - strokeOpacity = (uint8_t)(strokeOpacity - f * (strokeOpacity - range->style.strokeOpacity(frameNo, exps))); + strokeOpacity = (uint8_t)(strokeOpacity - f * (strokeOpacity - range->style.strokeOpacity(frameNo, tween, exps))); shape->strokeFill(strokeColor.rgb[0], strokeColor.rgb[1], strokeColor.rgb[2], strokeOpacity); shape->order(doc.stroke.below); } + cursor.x += f * range->style.letterSpacing(frameNo, tween, exps); - cursor.x += f * range->style.letterSpacing(frameNo, exps); - - auto spacing = f * range->style.lineSpacing(frameNo, exps); + auto spacing = f * range->style.lineSpacing(frameNo, tween, exps); if (spacing > lineSpacing) lineSpacing = spacing; } @@ -1137,7 +1148,7 @@ void LottieBuilder::updateText(LottieLayer* layer, float frameNo) identity(&textGroupMatrix); translate(&textGroupMatrix, cursor); - auto alignment = text->alignOption.anchor(frameNo, exps); + auto alignment = text->alignOption.anchor(frameNo, tween, exps); // center pivoting textGroupMatrix.e13 += alignment.x; @@ -1246,12 +1257,12 @@ void LottieBuilder::updateMasks(LottieLayer* layer, float frameNo) //Default Masking if (expand == 0.0f) { - mask->pathset(frameNo, SHAPE(pShape)->rs.path, nullptr, exps); + mask->pathset(frameNo, SHAPE(pShape)->rs.path, nullptr, tween, exps); //Masking with Expansion (Offset) } else { //TODO: Once path direction support is implemented, ensure that the direction is ignored here auto offset = LottieOffsetModifier(expand); - mask->pathset(frameNo, SHAPE(pShape)->rs.path, nullptr, exps, &offset); + mask->pathset(frameNo, SHAPE(pShape)->rs.path, nullptr, tween, exps, &offset); } if (fastTrack) return; @@ -1291,15 +1302,13 @@ void LottieBuilder::updateStrokeEffect(LottieLayer* layer, LottieFxStroke* effec //FIXME: all mask if (effect->allMask(frameNo)) { ARRAY_FOREACH(p, layer->masks) { - auto mask = *p; - mask->pathset(frameNo, SHAPE(shape)->rs.path, nullptr, exps); + (*p)->pathset(frameNo, SHAPE(shape)->rs.path, nullptr, tween, exps); } //A specific mask } else { auto idx = static_cast(effect->mask(frameNo) - 1); if (idx < 0 || idx >= layer->masks.count) return; - auto mask = layer->masks[idx]; - mask->pathset(frameNo, SHAPE(shape)->rs.path, nullptr, exps); + layer->masks[idx]->pathset(frameNo, SHAPE(shape)->rs.path, nullptr, tween, exps); } shape->transform(layer->cache.matrix); @@ -1414,7 +1423,8 @@ void LottieBuilder::updateLayer(LottieComposition* comp, Scene* scene, LottieLay switch (layer->type) { case LottieLayer::Precomp: { - updatePrecomp(comp, layer, frameNo); + if (!tweening()) updatePrecomp(comp, layer, frameNo); + else updatePrecomp(comp, layer, frameNo, tween); break; } case LottieLayer::Solid: { @@ -1558,9 +1568,13 @@ bool LottieBuilder::update(LottieComposition* comp, float frameNo) { if (comp->root->children.empty()) return false; - frameNo += comp->root->inFrame; - if (frameNo root->inFrame) frameNo = comp->root->inFrame; - if (frameNo >= comp->root->outFrame) frameNo = (comp->root->outFrame - 1); + comp->clamp(frameNo); + + if (tweening()) { + comp->clamp(tween.frameNo); + //tweening is not necessary. + if (equal(frameNo, tween.frameNo)) offTween(); + } //update children layers auto root = comp->root; diff --git a/src/loaders/lottie/tvgLottieBuilder.h b/src/loaders/lottie/tvgLottieBuilder.h index b44bcaee..27c3911f 100644 --- a/src/loaders/lottie/tvgLottieBuilder.h +++ b/src/loaders/lottie/tvgLottieBuilder.h @@ -116,6 +116,23 @@ struct LottieBuilder return exps ? true : false; } + void offTween() + { + if (tween.active) tween.active = false; + } + + void onTween(float to, float progress) + { + tween.frameNo = to; + tween.progress = progress; + tween.active = true; + } + + bool tweening() + { + return tween.active; + } + bool update(LottieComposition* comp, float progress); void build(LottieComposition* comp); @@ -128,6 +145,7 @@ private: void updateLayer(LottieComposition* comp, Scene* scene, LottieLayer* layer, float frameNo); bool updateMatte(LottieComposition* comp, float frameNo, Scene* scene, LottieLayer* layer); void updatePrecomp(LottieComposition* comp, LottieLayer* precomp, float frameNo); + void updatePrecomp(LottieComposition* comp, LottieLayer* precomp, float frameNo, Tween& tween); void updateSolid(LottieLayer* layer); void updateImage(LottieGroup* layer); void updateText(LottieLayer* layer, float frameNo); @@ -144,8 +162,8 @@ private: void updateEllipse(LottieGroup* parent, LottieObject** child, float frameNo, Inlist& contexts, RenderContext* ctx); void updatePath(LottieGroup* parent, LottieObject** child, float frameNo, Inlist& contexts, RenderContext* ctx); void updatePolystar(LottieGroup* parent, LottieObject** child, float frameNo, Inlist& contexts, RenderContext* ctx); - void updateStar(LottiePolyStar* star, float frameNo, Matrix* transform, Shape* merging, RenderContext* ctx); - void updatePolygon(LottieGroup* parent, LottiePolyStar* star, float frameNo, Matrix* transform, Shape* merging, RenderContext* ctx); + void updateStar(LottiePolyStar* star, float frameNo, Matrix* transform, Shape* merging, RenderContext* ctx, Tween& tween, LottieExpressions* exps); + void updatePolygon(LottieGroup* parent, LottiePolyStar* star, float frameNo, Matrix* transform, Shape* merging, RenderContext* ctx, Tween& tween, LottieExpressions* exps); void updateTrimpath(LottieGroup* parent, LottieObject** child, float frameNo, Inlist& contexts, RenderContext* ctx); void updateRepeater(LottieGroup* parent, LottieObject** child, float frameNo, Inlist& contexts, RenderContext* ctx); void updateRoundedCorner(LottieGroup* parent, LottieObject** child, float frameNo, Inlist& contexts, RenderContext* ctx); @@ -153,6 +171,7 @@ private: RenderPath buffer; //resusable path LottieExpressions* exps; + Tween tween; }; #endif //_TVG_LOTTIE_BUILDER_H \ No newline at end of file diff --git a/src/loaders/lottie/tvgLottieData.h b/src/loaders/lottie/tvgLottieData.h index d1c0f28c..1419f2ab 100644 --- a/src/loaders/lottie/tvgLottieData.h +++ b/src/loaders/lottie/tvgLottieData.h @@ -72,6 +72,14 @@ struct TextDocument }; +struct Tween +{ + float frameNo = 0.0f; + float progress = 0.0f; //greater than 0 and smaller than 1 + bool active = false; +}; + + static inline int32_t REMAP255(float val) { return (int32_t)nearbyintf(val * 255.0f); diff --git a/src/loaders/lottie/tvgLottieLoader.cpp b/src/loaders/lottie/tvgLottieLoader.cpp index 66364ba8..56f8bf96 100644 --- a/src/loaders/lottie/tvgLottieLoader.cpp +++ b/src/loaders/lottie/tvgLottieLoader.cpp @@ -127,7 +127,7 @@ bool LottieLoader::header() ++p; continue; } - //version. + //version if (!strncmp(p, "\"v\":", 4)) { p += 4; continue; @@ -329,21 +329,25 @@ bool LottieLoader::override(const char* slots, bool byDefault) } +float LottieLoader::shorten(float frameNo) +{ + //This ensures that the target frame number is reached. + return nearbyintf((frameNo + startFrame()) * 10000.0f) * 0.0001f; +} + + bool LottieLoader::frame(float no) { - auto frameNo = no + startFrame(); - - //This ensures that the target frame number is reached. - frameNo *= 10000.0f; - frameNo = nearbyintf(frameNo); - frameNo *= 0.0001f; + no = shorten(no); //Skip update if frame diff is too small. - if (fabsf(this->frameNo - frameNo) <= 0.0009f) return false; + if (!builder->tweening() && fabsf(this->frameNo - no) <= 0.0009f) return false; this->done(); - this->frameNo = frameNo; + this->frameNo = no; + + builder->offTween(); TaskScheduler::request(this); @@ -435,4 +439,22 @@ bool LottieLoader::ready() done(); if (comp) return true; return false; +} + + +bool LottieLoader::tween(float from, float to, float progress) +{ + //tweening is not necessary + if (tvg::zero(progress)) return frame(from); + else if (tvg::equal(progress, 1.0f)) return frame(to); + + done(); + + frameNo = shorten(from); + + builder->onTween(shorten(to), progress); + + TaskScheduler::request(this); + + return true; } \ No newline at end of file diff --git a/src/loaders/lottie/tvgLottieLoader.h b/src/loaders/lottie/tvgLottieLoader.h index 3b86f0e2..9758e6ba 100644 --- a/src/loaders/lottie/tvgLottieLoader.h +++ b/src/loaders/lottie/tvgLottieLoader.h @@ -45,7 +45,7 @@ public: Key key; char* dirName = nullptr; //base resource directory bool copy = false; //"content" is owned by this loader - bool overridden = false; //overridden properties with slots + bool overridden = false; //overridden properties with slots bool rebuild = false; //require building the lottie scene LottieLoader(); @@ -71,6 +71,9 @@ public: bool segment(const char* marker, float& begin, float& end); Result segment(float begin, float end) override; + float shorten(float frameNo); //Reduce the accuracy for performance + bool tween(float from, float to, float progress); + private: bool ready(); bool header(); diff --git a/src/loaders/lottie/tvgLottieModel.cpp b/src/loaders/lottie/tvgLottieModel.cpp index 8645da5a..68fa43df 100644 --- a/src/loaders/lottie/tvgLottieModel.cpp +++ b/src/loaders/lottie/tvgLottieModel.cpp @@ -225,12 +225,10 @@ void LottieImage::update() } -void LottieTrimpath::segment(float frameNo, float& start, float& end, LottieExpressions* exps) +void LottieTrimpath::segment(float frameNo, float& start, float& end, Tween& tween, LottieExpressions* exps) { - start = tvg::clamp(this->start(frameNo, exps) * 0.01f, 0.0f, 1.0f); - end = tvg::clamp(this->end(frameNo, exps) * 0.01f, 0.0f, 1.0f); - - auto o = fmodf(this->offset(frameNo, exps), 360.0f) / 360.0f; //0 ~ 1 + start = tvg::clamp(this->start(frameNo, tween, exps) * 0.01f, 0.0f, 1.0f); + end = tvg::clamp(this->end(frameNo, tween, exps) * 0.01f, 0.0f, 1.0f); auto diff = fabs(start - end); if (tvg::zero(diff)) { @@ -245,6 +243,8 @@ void LottieTrimpath::segment(float frameNo, float& start, float& end, LottieExpr } if (start > end) std::swap(start, end); + + auto o = fmodf(this->offset(frameNo, tween, exps), 360.0f) / 360.0f; //0 ~ 1 start += o; end += o; } @@ -337,14 +337,14 @@ uint32_t LottieGradient::populate(ColorStop& color, size_t count) } -Fill* LottieGradient::fill(float frameNo, LottieExpressions* exps) +Fill* LottieGradient::fill(float frameNo, Tween& tween, LottieExpressions* exps) { - auto opacity = this->opacity(frameNo); + auto opacity = this->opacity(frameNo, tween, exps); if (opacity == 0) return nullptr; Fill* fill = nullptr; - auto s = start(frameNo, exps); - auto e = end(frameNo, exps); + auto s = start(frameNo, tween, exps); + auto e = end(frameNo, tween, exps); //Linear Graident if (id == 1) { @@ -358,14 +358,14 @@ Fill* LottieGradient::fill(float frameNo, LottieExpressions* exps) auto w = fabsf(e.x - s.x); auto h = fabsf(e.y - s.y); auto r = (w > h) ? (w + 0.375f * h) : (h + 0.375f * w); - auto progress = this->height(frameNo, exps) * 0.01f; + auto progress = this->height(frameNo, tween, exps) * 0.01f; if (tvg::zero(progress)) { static_cast(fill)->radial(s.x, s.y, r, s.x, s.y, 0.0f); } else { if (tvg::equal(progress, 1.0f)) progress = 0.99f; auto startAngle = rad2deg(tvg::atan2(e.y - s.y, e.x - s.x)); - auto angle = deg2rad((startAngle + this->angle(frameNo, exps))); + auto angle = deg2rad((startAngle + this->angle(frameNo, tween, exps))); auto fx = s.x + cos(angle) * progress * r; auto fy = s.y + sin(angle) * progress * r; // Lottie doesn't have any focal radius concept @@ -375,7 +375,7 @@ Fill* LottieGradient::fill(float frameNo, LottieExpressions* exps) if (!fill) return nullptr; - colorStops(frameNo, fill, exps); + colorStops(frameNo, fill, tween, exps); //multiply the current opacity with the fill if (opacity < 255) { diff --git a/src/loaders/lottie/tvgLottieModel.h b/src/loaders/lottie/tvgLottieModel.h index e0bf2250..4299cda7 100644 --- a/src/loaders/lottie/tvgLottieModel.h +++ b/src/loaders/lottie/tvgLottieModel.h @@ -52,21 +52,20 @@ struct LottieStroke return dashattr->value[no]; } - float dashOffset(float frameNo, LottieExpressions* exps) + float dashOffset(float frameNo, Tween& tween, LottieExpressions* exps) { - return dash(0)(frameNo, exps); + return dash(0)(frameNo, tween, exps); } - float dashGap(float frameNo, LottieExpressions* exps) + float dashSize(float frameNo, Tween& tween, LottieExpressions* exps) { - return dash(2)(frameNo, exps); + auto d = dash(1)(frameNo, tween, exps); + return (d > 0.0f) ? d : 0.0f; } - float dashSize(float frameNo, LottieExpressions* exps) + float dashGap(float frameNo, Tween& tween, LottieExpressions* exps) { - auto d = dash(1)(frameNo, exps); - if (d == 0.0f) return 0.1f; - else return d; + return dash(2)(frameNo, tween, exps); } LottieFloat width = 0.0f; @@ -382,7 +381,7 @@ struct LottieTrimpath : LottieObject return nullptr; } - void segment(float frameNo, float& start, float& end, LottieExpressions* exps); + void segment(float frameNo, float& start, float& end, Tween& tween, LottieExpressions* exps); LottieFloat start = 0.0f; LottieFloat end = 100.0f; @@ -690,7 +689,7 @@ struct LottieGradient : LottieObject } uint32_t populate(ColorStop& color, size_t count); - Fill* fill(float frameNo, LottieExpressions* exps); + Fill* fill(float frameNo, Tween& tween, LottieExpressions* exps); LottieScalar start = Point{0.0f, 0.0f}; LottieScalar end = Point{0.0f, 0.0f}; @@ -962,6 +961,13 @@ struct LottieComposition return nullptr; } + void clamp(float& frameNo) + { + frameNo += root->inFrame; + if (frameNo < root->inFrame) frameNo = root->inFrame; + if (frameNo >= root->outFrame) frameNo = root->outFrame - 1; + } + LottieLayer* root = nullptr; char* version = nullptr; char* name = nullptr; diff --git a/src/loaders/lottie/tvgLottieProperty.h b/src/loaders/lottie/tvgLottieProperty.h index 70f6f583..08401f7f 100644 --- a/src/loaders/lottie/tvgLottieProperty.h +++ b/src/loaders/lottie/tvgLottieProperty.h @@ -36,6 +36,10 @@ struct LottieLayer; struct LottieObject; +//default keyframe updates condition (no tweening) +#define DEFAULT_COND (!tween.active || !frames || (frames->count == 1)) + + template struct LottieScalarFrame { @@ -159,32 +163,32 @@ struct LottieExpression }; -static void _copy(PathSet* pathset, Array& outPts, Matrix* transform) +static void _copy(PathSet* pathset, Array& out, Matrix* transform) { - Array inPts; + Array in; if (transform) { for (int i = 0; i < pathset->ptsCnt; ++i) { Point pt = pathset->pts[i]; pt *= *transform; - outPts.push(pt); + out.push(pt); } } else { - inPts.data = pathset->pts; - inPts.count = pathset->ptsCnt; - outPts.push(inPts); - inPts.data = nullptr; + in.data = pathset->pts; + in.count = pathset->ptsCnt; + out.push(in); + in.data = nullptr; } } -static void _copy(PathSet* pathset, Array& outCmds) +static void _copy(PathSet* pathset, Array& out) { - Array inCmds; - inCmds.data = pathset->cmds; - inCmds.count = pathset->cmdsCnt; - outCmds.push(inCmds); - inCmds.data = nullptr; + Array in; + in.data = pathset->cmds; + in.count = pathset->cmdsCnt; + out.push(in); + in.data = nullptr; } @@ -343,6 +347,12 @@ struct LottieGenericProperty : LottieProperty return frame->interpolate(frame + 1, frameNo); } + Value operator()(float frameNo, Tween& tween, LottieExpressions* exps) + { + if (DEFAULT_COND) return operator()(frameNo, exps); + return lerp(operator()(frameNo, exps), operator()(tween.frameNo, exps), tween.progress); + } + void copy(const LottieGenericProperty& rhs, bool shallow = true) { if (rhs.frames) { @@ -370,6 +380,12 @@ struct LottieGenericProperty : LottieProperty return frame->angle(frame + 1, frameNo); } + float angle(float frameNo, Tween& tween) + { + if (DEFAULT_COND) return angle(frameNo); + return lerp(angle(frameNo), angle(tween.frameNo), tween.progress); + } + void prepare() { if (Scalar) return; @@ -456,7 +472,6 @@ struct LottiePathSet : LottieProperty if (tvg::equal(frame->no, frameNo)) path = &frame->value; else if (frame->value.ptsCnt != (frame + 1)->value.ptsCnt) { path = &frame->value; - TVGLOG("LOTTIE", "Different numbers of points in consecutive frames - interpolation omitted."); } else { t = (frameNo - frame->no) / ((frame + 1)->no - frame->no); if (frame->interpolator) t = frame->interpolator->progress(t); @@ -524,6 +539,27 @@ struct LottiePathSet : LottieProperty return true; } + bool tweening(float frameNo, RenderPath& out, Matrix* transform, LottieModifier* modifier, Tween& tween, LottieExpressions* exps) + { + RenderPath to; //used as temp as well. + auto pivot = out.pts.count; + if (!operator()(frameNo, out, transform, exps)) return false; + if (!operator()(tween.frameNo, to, transform, exps)) return false; + + auto from = out.pts.data + pivot; + if (to.pts.count != out.pts.count - pivot) TVGLOG("LOTTIE", "Tweening has different numbers of points in consecutive frames."); + + for (uint32_t i = 0; i < std::min(to.pts.count, (out.pts.count - pivot)); ++i) { + from[i] = lerp(from[i], to.pts[i], tween.progress); + } + + if (!modifier) return true; + + //Apply modifiers + to.clear(); + return modifier->modifyPath(to.cmds.data, to.cmds.count, to.pts.data, to.pts.count, transform, out); + } + bool operator()(float frameNo, RenderPath& out, Matrix* transform, LottieExpressions* exps, LottieModifier* modifier = nullptr) { //overriding with expressions @@ -536,7 +572,11 @@ struct LottiePathSet : LottieProperty else return defaultPath(frameNo, out, transform); } - void prepare() {} + bool operator()(float frameNo, RenderPath& out, Matrix* transform, Tween& tween, LottieExpressions* exps, LottieModifier* modifier = nullptr) + { + if (DEFAULT_COND) return operator()(frameNo, out, transform, exps, modifier); + return tweening(frameNo, out, transform, modifier, tween, exps); + } }; @@ -616,6 +656,38 @@ struct LottieColorStop : LottieProperty return (*frames)[frames->count]; } + Result tweening(float frameNo, Fill* fill, Tween& tween, LottieExpressions* exps) + { + auto frame = frames->data + _bsearch(frames, frameNo); + if (tvg::equal(frame->no, frameNo)) return fill->colorStops(frame->value.data, count); + + //from + operator()(frameNo, fill, exps); + + //to + auto dup = fill->duplicate(); + operator()(tween.frameNo, dup, exps); + + //interpolate + const Fill::ColorStop* from; + auto fromCnt = fill->colorStops(&from); + + const Fill::ColorStop* to; + auto toCnt = fill->colorStops(&to); + + if (fromCnt != toCnt) TVGLOG("LOTTIE", "Tweening has different numbers of color data in consecutive frames."); + + for (uint32_t i = 0; i < std::min(fromCnt, toCnt); ++i) { + const_cast(from)->offset = lerp(from->offset, to->offset, tween.progress); + const_cast(from)->r = lerp(from->r, to->r, tween.progress); + const_cast(from)->g = lerp(from->g, to->g, tween.progress); + const_cast(from)->b = lerp(from->b, to->b, tween.progress); + const_cast(from)->a = lerp(from->a, to->a, tween.progress); + } + + return Result::Success; + } + Result operator()(float frameNo, Fill* fill, LottieExpressions* exps = nullptr) { //overriding with expressions @@ -660,6 +732,12 @@ struct LottieColorStop : LottieProperty return fill->colorStops(result.data, count); } + Result operator()(float frameNo, Fill* fill, Tween& tween, LottieExpressions* exps) + { + if (DEFAULT_COND) return operator()(frameNo, fill, exps); + return tweening(frameNo, fill, tween, exps); + } + void copy(const LottieColorStop& rhs, bool shallow = true) { if (rhs.frames) {