lottie: support frame tweening feature

frame tweening allows user to interpolate two frames
over a speicified duration. This Tweening functionality
can be particularly powerful when combined with state-based
(a.k.a. Marker) animation playback in Lottie.

For example, apps support state machine based animations,
where the transition sequences between states are not linear
and can occur in highly irregular directions.

Experimental APIs:
- Result LottieAnimation::tween(float from, float to, float progress)
- Tvg_Result tvg_lottie_animation_tween(Tvg_Animation* animation, float from, float to, float progress)
This commit is contained in:
Hermet Park 2025-02-06 12:34:12 +09:00 committed by Hermet Park
parent 0dd0a3b45c
commit 78cfc33f4c
12 changed files with 355 additions and 150 deletions

View file

@ -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); 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 /** \} */ // end addtogroup ThorVGCapi_LottieAnimation

View file

@ -960,6 +960,16 @@ TVG_API Tvg_Result tvg_lottie_animation_get_marker(Tvg_Animation* animation, uin
return TVG_RESULT_NOT_SUPPORTED; 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<LottieAnimation*>(animation)->tween(from, to, progress);
#endif
return TVG_RESULT_NOT_SUPPORTED;
}
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View file

@ -51,6 +51,22 @@ public:
*/ */
Result segment(const char* marker) noexcept; 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. * @brief Gets the marker count of the animation.
* *

View file

@ -33,9 +33,10 @@ LottieAnimation::LottieAnimation()
Result LottieAnimation::override(const char* slot) noexcept 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<LottieLoader*>(PICTURE(pImpl->picture)->loader)->override(slot)) return Result::Success; if (static_cast<LottieLoader*>(loader)->override(slot)) return Result::Success;
return Result::InvalidArguments; 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<LottieLoader*>(loader)->tween(from, to, progress)) return Result::InsufficientCondition;
return Result::Success;
}
uint32_t LottieAnimation::markersCnt() noexcept uint32_t LottieAnimation::markersCnt() noexcept
{ {
auto loader = PICTURE(pImpl->picture)->loader; auto loader = PICTURE(pImpl->picture)->loader;

View file

@ -38,13 +38,13 @@ static bool _buildComposition(LottieComposition* comp, LottieLayer* parent);
static bool _draw(LottieGroup* parent, LottieShape* shape, RenderContext* ctx); 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 //rotation xyz
if (transform->rotationEx) { if (transform->rotationEx) {
auto radianX = deg2rad(transform->rotationEx->x(frameNo, exps)); auto radianX = deg2rad(transform->rotationEx->x(frameNo, tween, exps));
auto radianY = deg2rad(transform->rotationEx->y(frameNo, exps)); auto radianY = deg2rad(transform->rotationEx->y(frameNo, tween, exps));
auto radianZ = deg2rad(transform->rotation(frameNo, exps)) + angle; auto radianZ = deg2rad(transform->rotation(frameNo, tween, exps)) + angle;
auto cx = cosf(radianX), sx = sinf(radianX); auto cx = cosf(radianX), sx = sinf(radianX);
auto cy = cosf(radianY), sy = sinf(radianY);; auto cy = cosf(radianY), sy = sinf(radianY);;
auto cz = cosf(radianZ), sz = sinf(radianZ);; 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; m.e22 = -sx * sy * sz + cx * cz;
//rotation z //rotation z
} else { } else {
auto degree = transform->rotation(frameNo, exps) + angle; auto degree = transform->rotation(frameNo, tween, exps) + angle;
if (degree == 0.0f) return; if (degree == 0.0f) return;
auto radian = deg2rad(degree); auto radian = deg2rad(degree);
m.e11 = cosf(radian); 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) static void _skew(Matrix* m, float angleDeg, float axisDeg)
{ {
auto angle = -deg2rad(angleDeg); auto angle = -deg2rad(angleDeg);
float tanVal = tanf(angle); auto tanVal = tanf(angle);
axisDeg = fmod(axisDeg, 180.0f); axisDeg = fmod(axisDeg, 180.0f);
if (fabsf(axisDeg) < 0.01f || fabsf(axisDeg - 180.0f) < 0.01f || fabsf(axisDeg + 180.0f) < 0.01f) { 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); auto axis = -deg2rad(axisDeg);
float cosVal = cosf(axis); auto cosVal = cosf(axis);
float sinVal = sinf(axis); auto sinVal = sinf(axis);
auto A = sinVal * cosVal * tanVal; auto A = sinVal * cosVal * tanVal;
auto B = cosVal * cosVal * tanVal; auto B = cosVal * cosVal * tanVal;
auto C = sinVal * sinVal * 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); identity(&matrix);
@ -110,14 +110,13 @@ static bool _updateTransform(LottieTransform* transform, float frameNo, bool aut
return false; return false;
} }
if (transform->coords) translate(&matrix, {transform->coords->x(frameNo, exps), transform->coords->y(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, exps)); else translate(&matrix, transform->position(frameNo, tween, exps));
auto angle = 0.0f; auto angle = (autoOrient) ? transform->position.angle(frameNo, tween) : 0.0f;
if (autoOrient) angle = transform->position.angle(frameNo); _rotate(transform, frameNo, matrix, angle, tween, exps);
_rotate(transform, frameNo, matrix, angle, exps);
auto skewAngle = transform->skewAngle(frameNo, exps); auto skewAngle = transform->skewAngle(frameNo, tween, exps);
if (skewAngle != 0.0f) { if (skewAngle != 0.0f) {
// For angles where tangent explodes, the shape degenerates into an infinitely thin line. // 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. // 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)); _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); scaleR(&matrix, scale * 0.01f);
//Lottie specific anchor transform. //Lottie specific anchor transform.
translateR(&matrix, -transform->anchor(frameNo, exps)); translateR(&matrix, -transform->anchor(frameNo, tween, exps));
//invisible just in case. //invisible just in case.
if (scale.x == 0.0f || scale.y == 0.0f) opacity = 0; if (tvg::zero(scale)) opacity = 0;
else opacity = transform->opacity(frameNo, exps); else opacity = transform->opacity(frameNo, tween, exps);
return true; return true;
} }
@ -142,7 +141,7 @@ static bool _updateTransform(LottieTransform* transform, float frameNo, bool aut
void LottieBuilder::updateTransform(LottieLayer* layer, float frameNo) 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 transform = layer->transform;
auto parent = layer->parent; auto parent = layer->parent;
@ -151,7 +150,7 @@ void LottieBuilder::updateTransform(LottieLayer* layer, float frameNo)
auto& matrix = layer->cache.matrix; 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 (parent) {
if (!identity((const Matrix*) &parent->cache.matrix)) { if (!identity((const Matrix*) &parent->cache.matrix)) {
@ -172,14 +171,14 @@ void LottieBuilder::updateTransform(LottieGroup* parent, LottieObject** child, f
if (parent->mergeable()) { if (parent->mergeable()) {
if (!ctx->transform) ctx->transform = (Matrix*)malloc(sizeof(Matrix)); 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; return;
} }
ctx->merging = nullptr; ctx->merging = nullptr;
Matrix matrix; 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->transform(ctx->propagator->transform() * matrix);
ctx->propagator->opacity(MULTIPLY(opacity, PAINT(ctx->propagator)->opacity)); 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->strokeCap(stroke->cap);
ctx->propagator->strokeJoin(stroke->join); ctx->propagator->strokeJoin(stroke->join);
ctx->propagator->strokeMiterlimit(stroke->miterLimit); ctx->propagator->strokeMiterlimit(stroke->miterLimit);
if (stroke->dashattr) { if (stroke->dashattr) {
float dashes[2]; float dashes[2];
dashes[0] = stroke->dashSize(frameNo, exps); dashes[0] = stroke->dashSize(frameNo, tween, exps);
dashes[1] = dashes[0] + stroke->dashGap(frameNo, exps); dashes[1] = dashes[0] + stroke->dashGap(frameNo, tween, exps);
ctx->propagator->strokeDash(dashes, 2, stroke->dashOffset(frameNo, exps)); ctx->propagator->strokeDash(dashes, 2, stroke->dashOffset(frameNo, tween, exps));
} else { } else {
ctx->propagator->strokeDash(nullptr, 0); ctx->propagator->strokeDash(nullptr, 0);
} }
@ -252,9 +251,9 @@ void LottieBuilder::updateSolidStroke(LottieGroup* parent, LottieObject** child,
auto stroke = static_cast<LottieSolidStroke*>(*child); auto stroke = static_cast<LottieSolidStroke*>(*child);
ctx->merging = nullptr; ctx->merging = nullptr;
auto color = stroke->color(frameNo, exps); auto color = stroke->color(frameNo, tween, exps);
ctx->propagator->strokeFill(color.rgb[0], color.rgb[1], color.rgb[2], stroke->opacity(frameNo, exps)); ctx->propagator->strokeFill(color.rgb[0], color.rgb[1], color.rgb[2], stroke->opacity(frameNo, tween, exps));
_updateStroke(static_cast<LottieStroke*>(stroke), frameNo, ctx, exps); _updateStroke(static_cast<LottieStroke*>(stroke), frameNo, ctx, tween, exps);
} }
@ -265,8 +264,8 @@ void LottieBuilder::updateGradientStroke(LottieGroup* parent, LottieObject** chi
auto stroke = static_cast<LottieGradientStroke*>(*child); auto stroke = static_cast<LottieGradientStroke*>(*child);
ctx->merging = nullptr; ctx->merging = nullptr;
ctx->propagator->strokeFill(stroke->fill(frameNo, exps)); ctx->propagator->strokeFill(stroke->fill(frameNo, tween, exps));
_updateStroke(static_cast<LottieStroke*>(stroke), frameNo, ctx, exps); _updateStroke(static_cast<LottieStroke*>(stroke), frameNo, ctx, tween, exps);
} }
@ -277,8 +276,9 @@ void LottieBuilder::updateSolidFill(LottieGroup* parent, LottieObject** child, f
auto fill = static_cast<LottieSolidFill*>(*child); auto fill = static_cast<LottieSolidFill*>(*child);
ctx->merging = nullptr; 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); ctx->propagator->fill(fill->rule);
if (ctx->propagator->strokeWidth() > 0) ctx->propagator->order(true); if (ctx->propagator->strokeWidth() > 0) ctx->propagator->order(true);
@ -293,7 +293,7 @@ void LottieBuilder::updateGradientFill(LottieGroup* parent, LottieObject** child
ctx->merging = nullptr; ctx->merging = nullptr;
//TODO: reuse the fill instance? //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); ctx->propagator->fill(fill->rule);
if (ctx->propagator->strokeWidth() > 0) ctx->propagator->order(true); 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<RenderContext>& contexts, RenderContext* ctx) void LottieBuilder::updateRect(LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist<RenderContext>& contexts, RenderContext* ctx)
{ {
auto rect = static_cast<LottieRect*>(*child); auto rect = static_cast<LottieRect*>(*child);
auto size = rect->size(frameNo, exps); auto size = rect->size(frameNo, tween, exps);
auto pos = rect->position(frameNo, exps) - size * 0.5f; auto pos = rect->position(frameNo, tween, exps) - size * 0.5f;
auto r = rect->radius(frameNo, exps); auto r = rect->radius(frameNo, tween, exps);
if (r == 0.0f) { if (r == 0.0f) {
if (ctx->roundness) ctx->roundness->modifyRect(size, r); if (ctx->roundness) ctx->roundness->modifyRect(size, r);
@ -523,8 +523,8 @@ void LottieBuilder::updateEllipse(LottieGroup* parent, LottieObject** child, flo
{ {
auto ellipse = static_cast<LottieEllipse*>(*child); auto ellipse = static_cast<LottieEllipse*>(*child);
auto pos = ellipse->position(frameNo, exps); auto pos = ellipse->position(frameNo, tween, exps);
auto size = ellipse->size(frameNo, exps) * 0.5f; auto size = ellipse->size(frameNo, tween, exps) * 0.5f;
if (!ctx->repeaters.empty()) { if (!ctx->repeaters.empty()) {
auto shape = ellipse->pooling(); auto shape = ellipse->pooling();
@ -542,29 +542,29 @@ void LottieBuilder::updatePath(LottieGroup* parent, LottieObject** child, float
{ {
auto path = static_cast<LottiePath*>(*child); auto path = static_cast<LottiePath*>(*child);
if (!ctx->repeaters.empty()) { 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 {
_draw(parent, path, ctx); _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); 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; static constexpr auto POLYSTAR_MAGIC_NUMBER = 0.47829f / 0.28f;
auto ptsCnt = star->ptsCnt(frameNo, exps); auto ptsCnt = star->ptsCnt(frameNo, tween, exps);
auto innerRadius = star->innerRadius(frameNo, exps); auto innerRadius = star->innerRadius(frameNo, tween, exps);
auto outerRadius = star->outerRadius(frameNo, exps); auto outerRadius = star->outerRadius(frameNo, tween, exps);
auto innerRoundness = star->innerRoundness(frameNo, exps) * 0.01f; auto innerRoundness = star->innerRoundness(frameNo, tween, exps) * 0.01f;
auto outerRoundness = star->outerRoundness(frameNo, exps) * 0.01f; auto outerRoundness = star->outerRoundness(frameNo, tween, exps) * 0.01f;
auto angle = deg2rad(-90.0f); auto angle = deg2rad(-90.0f);
auto partialPointRadius = 0.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; static constexpr auto POLYGON_MAGIC_NUMBER = 0.25f;
auto ptsCnt = size_t(floor(star->ptsCnt(frameNo, exps))); auto ptsCnt = size_t(floor(star->ptsCnt(frameNo, tween, exps)));
auto radius = star->outerRadius(frameNo, exps); auto radius = star->outerRadius(frameNo, tween, exps);
auto outerRoundness = star->outerRoundness(frameNo, exps) * 0.01f; auto outerRoundness = star->outerRoundness(frameNo, tween, exps) * 0.01f;
auto angle = deg2rad(-90.0f); auto angle = deg2rad(-90.0f);
auto anglePerPoint = 2.0f * MATH_PI / float(ptsCnt); 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? //Optimize: Can we skip the individual coords transform?
Matrix matrix; Matrix matrix;
identity(&matrix); identity(&matrix);
translate(&matrix, star->position(frameNo, exps)); translate(&matrix, star->position(frameNo, tween, exps));
rotate(&matrix, star->rotation(frameNo, exps)); rotate(&matrix, star->rotation(frameNo, tween, exps));
if (ctx->transform) matrix = *ctx->transform * matrix; if (ctx->transform) matrix = *ctx->transform * matrix;
@ -771,13 +771,13 @@ void LottieBuilder::updatePolystar(LottieGroup* parent, LottieObject** child, fl
if (!ctx->repeaters.empty()) { if (!ctx->repeaters.empty()) {
auto shape = star->pooling(); auto shape = star->pooling();
shape->reset(); shape->reset();
if (star->type == LottiePolyStar::Star) updateStar(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); else updatePolygon(parent, star, frameNo, (identity ? nullptr : &matrix), shape, ctx, tween, exps);
_repeat(parent, shape, ctx); _repeat(parent, shape, ctx);
} else { } else {
_draw(parent, star, ctx); _draw(parent, star, ctx);
if (star->type == LottiePolyStar::Star) updateStar(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); else updatePolygon(parent, star, frameNo, (identity ? nullptr : &matrix), ctx->merging, ctx, tween, exps);
PAINT(ctx->merging)->update(RenderUpdateFlag::Path); 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<RenderContext>& contexts, RenderContext* ctx) void LottieBuilder::updateRoundedCorner(TVG_UNUSED LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist<RenderContext>& contexts, RenderContext* ctx)
{ {
auto roundedCorner = static_cast<LottieRoundedCorner*>(*child); auto roundedCorner = static_cast<LottieRoundedCorner*>(*child);
auto r = roundedCorner->radius(frameNo, 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);
@ -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<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, 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); ctx->update(ctx->offset);
} }
@ -810,15 +810,15 @@ void LottieBuilder::updateRepeater(TVG_UNUSED LottieGroup* parent, LottieObject*
auto repeater = static_cast<LottieRepeater*>(*child); auto repeater = static_cast<LottieRepeater*>(*child);
RenderRepeater r; RenderRepeater r;
r.cnt = static_cast<int>(repeater->copies(frameNo, exps)); r.cnt = static_cast<int>(repeater->copies(frameNo, tween, exps));
r.transform = ctx->propagator->transform(); r.transform = ctx->propagator->transform();
r.offset = repeater->offset(frameNo, exps); r.offset = repeater->offset(frameNo, tween, exps);
r.position = repeater->position(frameNo, exps); r.position = repeater->position(frameNo, tween, exps);
r.anchor = repeater->anchor(frameNo, exps); r.anchor = repeater->anchor(frameNo, tween, exps);
r.scale = repeater->scale(frameNo, exps); r.scale = repeater->scale(frameNo, tween, exps);
r.rotation = repeater->rotation(frameNo, exps); r.rotation = repeater->rotation(frameNo, tween, exps);
r.startOpacity = repeater->startOpacity(frameNo, exps); r.startOpacity = repeater->startOpacity(frameNo, tween, exps);
r.endOpacity = repeater->endOpacity(frameNo, exps); r.endOpacity = repeater->endOpacity(frameNo, tween, exps);
r.inorder = repeater->inorder; r.inorder = repeater->inorder;
r.interpOpacity = (r.startOpacity == r.endOpacity) ? false : true; r.interpOpacity = (r.startOpacity == r.endOpacity) ? false : true;
ctx->repeaters.push(r); ctx->repeaters.push(r);
@ -832,7 +832,7 @@ void LottieBuilder::updateTrimpath(TVG_UNUSED LottieGroup* parent, LottieObject*
auto trimpath = static_cast<LottieTrimpath*>(*child); auto trimpath = static_cast<LottieTrimpath*>(*child);
float begin, end; float begin, end;
trimpath->segment(frameNo, begin, end, exps); trimpath->segment(frameNo, begin, end, tween, exps);
if (SHAPE(ctx->propagator)->rs.stroke) { if (SHAPE(ctx->propagator)->rs.stroke) {
auto pbegin = SHAPE(ctx->propagator)->rs.stroke->trim.begin; 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) void LottieBuilder::updateSolid(LottieLayer* layer)
{ {
auto solidFill = layer->statical.pooling(true); auto solidFill = layer->statical.pooling(true);
@ -1056,7 +1068,7 @@ void LottieBuilder::updateText(LottieLayer* layer, float frameNo)
ARRAY_FOREACH(p, glyph->children) { ARRAY_FOREACH(p, glyph->children) {
auto group = static_cast<LottieGroup*>(*p); auto group = static_cast<LottieGroup*>(*p);
ARRAY_FOREACH(p, group->children) { ARRAY_FOREACH(p, group->children) {
if (static_cast<LottiePath*>(*p)->pathset(frameNo, SHAPE(shape)->rs.path, nullptr, exps)) { if (static_cast<LottiePath*>(*p)->pathset(frameNo, SHAPE(shape)->rs.path, nullptr, tween, exps)) {
PAINT(shape)->update(RenderUpdateFlag::Path); PAINT(shape)->update(RenderUpdateFlag::Path);
} }
} }
@ -1095,40 +1107,39 @@ void LottieBuilder::updateText(LottieLayer* layer, float frameNo)
if (tvg::zero(f)) continue; if (tvg::zero(f)) continue;
needGroup = true; needGroup = true;
translation = translation + f * range->style.position(frameNo, exps); translation = translation + f * range->style.position(frameNo, tween, exps);
scaling = scaling * (f * (range->style.scale(frameNo, exps) * 0.01f - Point{1.0f, 1.0f}) + Point{1.0f, 1.0f}); 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, exps); 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); 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; if (tvg::equal(f, 1.0f)) color = rangeColor;
else { else {
color.rgb[0] = lerp<uint8_t>(color.rgb[0], rangeColor.rgb[0], f); color.rgb[0] = lerp<uint8_t>(color.rgb[0], rangeColor.rgb[0], f);
color.rgb[1] = lerp<uint8_t>(color.rgb[1], rangeColor.rgb[1], f); color.rgb[1] = lerp<uint8_t>(color.rgb[1], rangeColor.rgb[1], f);
color.rgb[2] = lerp<uint8_t>(color.rgb[2], rangeColor.rgb[2], f); color.rgb[2] = lerp<uint8_t>(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->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) { 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; if (tvg::equal(f, 1.0f)) strokeColor = rangeColor;
else { else {
strokeColor.rgb[0] = lerp<uint8_t>(strokeColor.rgb[0], rangeColor.rgb[0], f); strokeColor.rgb[0] = lerp<uint8_t>(strokeColor.rgb[0], rangeColor.rgb[0], f);
strokeColor.rgb[1] = lerp<uint8_t>(strokeColor.rgb[1], rangeColor.rgb[1], f); strokeColor.rgb[1] = lerp<uint8_t>(strokeColor.rgb[1], rangeColor.rgb[1], f);
strokeColor.rgb[2] = lerp<uint8_t>(strokeColor.rgb[2], rangeColor.rgb[2], f); strokeColor.rgb[2] = lerp<uint8_t>(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->strokeFill(strokeColor.rgb[0], strokeColor.rgb[1], strokeColor.rgb[2], strokeOpacity);
shape->order(doc.stroke.below); 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, tween, exps);
auto spacing = f * range->style.lineSpacing(frameNo, exps);
if (spacing > lineSpacing) lineSpacing = spacing; if (spacing > lineSpacing) lineSpacing = spacing;
} }
@ -1137,7 +1148,7 @@ void LottieBuilder::updateText(LottieLayer* layer, float frameNo)
identity(&textGroupMatrix); identity(&textGroupMatrix);
translate(&textGroupMatrix, cursor); translate(&textGroupMatrix, cursor);
auto alignment = text->alignOption.anchor(frameNo, exps); auto alignment = text->alignOption.anchor(frameNo, tween, exps);
// center pivoting // center pivoting
textGroupMatrix.e13 += alignment.x; textGroupMatrix.e13 += alignment.x;
@ -1246,12 +1257,12 @@ void LottieBuilder::updateMasks(LottieLayer* layer, float frameNo)
//Default Masking //Default Masking
if (expand == 0.0f) { 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) //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(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; if (fastTrack) return;
@ -1291,15 +1302,13 @@ void LottieBuilder::updateStrokeEffect(LottieLayer* layer, LottieFxStroke* effec
//FIXME: all mask //FIXME: all mask
if (effect->allMask(frameNo)) { if (effect->allMask(frameNo)) {
ARRAY_FOREACH(p, layer->masks) { ARRAY_FOREACH(p, layer->masks) {
auto mask = *p; (*p)->pathset(frameNo, SHAPE(shape)->rs.path, nullptr, tween, exps);
mask->pathset(frameNo, SHAPE(shape)->rs.path, nullptr, exps);
} }
//A specific mask //A specific mask
} else { } else {
auto idx = static_cast<uint32_t>(effect->mask(frameNo) - 1); auto idx = static_cast<uint32_t>(effect->mask(frameNo) - 1);
if (idx < 0 || idx >= layer->masks.count) return; if (idx < 0 || idx >= layer->masks.count) return;
auto mask = layer->masks[idx]; layer->masks[idx]->pathset(frameNo, SHAPE(shape)->rs.path, nullptr, tween, exps);
mask->pathset(frameNo, SHAPE(shape)->rs.path, nullptr, exps);
} }
shape->transform(layer->cache.matrix); shape->transform(layer->cache.matrix);
@ -1414,7 +1423,8 @@ void LottieBuilder::updateLayer(LottieComposition* comp, Scene* scene, LottieLay
switch (layer->type) { switch (layer->type) {
case LottieLayer::Precomp: { case LottieLayer::Precomp: {
updatePrecomp(comp, layer, frameNo); if (!tweening()) updatePrecomp(comp, layer, frameNo);
else updatePrecomp(comp, layer, frameNo, tween);
break; break;
} }
case LottieLayer::Solid: { case LottieLayer::Solid: {
@ -1558,9 +1568,13 @@ bool LottieBuilder::update(LottieComposition* comp, float frameNo)
{ {
if (comp->root->children.empty()) return false; if (comp->root->children.empty()) return false;
frameNo += comp->root->inFrame; comp->clamp(frameNo);
if (frameNo <comp->root->inFrame) frameNo = comp->root->inFrame;
if (frameNo >= comp->root->outFrame) frameNo = (comp->root->outFrame - 1); if (tweening()) {
comp->clamp(tween.frameNo);
//tweening is not necessary.
if (equal(frameNo, tween.frameNo)) offTween();
}
//update children layers //update children layers
auto root = comp->root; auto root = comp->root;

View file

@ -116,6 +116,23 @@ struct LottieBuilder
return exps ? true : false; 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); bool update(LottieComposition* comp, float progress);
void build(LottieComposition* comp); void build(LottieComposition* comp);
@ -128,6 +145,7 @@ private:
void updateLayer(LottieComposition* comp, Scene* scene, LottieLayer* layer, float frameNo); void updateLayer(LottieComposition* comp, Scene* scene, LottieLayer* layer, float frameNo);
bool updateMatte(LottieComposition* comp, float frameNo, Scene* scene, LottieLayer* layer); 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);
void updatePrecomp(LottieComposition* comp, LottieLayer* precomp, float frameNo, Tween& tween);
void updateSolid(LottieLayer* layer); void updateSolid(LottieLayer* layer);
void updateImage(LottieGroup* layer); void updateImage(LottieGroup* layer);
void updateText(LottieLayer* layer, float frameNo); void updateText(LottieLayer* layer, float frameNo);
@ -144,8 +162,8 @@ private:
void updateEllipse(LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx); void updateEllipse(LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx);
void updatePath(LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx); void updatePath(LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx);
void updatePolystar(LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx); void updatePolystar(LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx);
void updateStar(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); 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<RenderContext>& contexts, RenderContext* ctx); void updateTrimpath(LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx);
void updateRepeater(LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx); void updateRepeater(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 updateRoundedCorner(LottieGroup* parent, LottieObject** child, float frameNo, Inlist<RenderContext>& contexts, RenderContext* ctx);
@ -153,6 +171,7 @@ private:
RenderPath buffer; //resusable path RenderPath buffer; //resusable path
LottieExpressions* exps; LottieExpressions* exps;
Tween tween;
}; };
#endif //_TVG_LOTTIE_BUILDER_H #endif //_TVG_LOTTIE_BUILDER_H

View file

@ -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) static inline int32_t REMAP255(float val)
{ {
return (int32_t)nearbyintf(val * 255.0f); return (int32_t)nearbyintf(val * 255.0f);

View file

@ -127,7 +127,7 @@ bool LottieLoader::header()
++p; ++p;
continue; continue;
} }
//version. //version
if (!strncmp(p, "\"v\":", 4)) { if (!strncmp(p, "\"v\":", 4)) {
p += 4; p += 4;
continue; 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) bool LottieLoader::frame(float no)
{ {
auto frameNo = no + startFrame(); no = shorten(no);
//This ensures that the target frame number is reached.
frameNo *= 10000.0f;
frameNo = nearbyintf(frameNo);
frameNo *= 0.0001f;
//Skip update if frame diff is too small. //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->done();
this->frameNo = frameNo; this->frameNo = no;
builder->offTween();
TaskScheduler::request(this); TaskScheduler::request(this);
@ -435,4 +439,22 @@ bool LottieLoader::ready()
done(); done();
if (comp) return true; if (comp) return true;
return false; 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;
} }

View file

@ -45,7 +45,7 @@ public:
Key key; Key key;
char* dirName = nullptr; //base resource directory char* dirName = nullptr; //base resource directory
bool copy = false; //"content" is owned by this loader 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 bool rebuild = false; //require building the lottie scene
LottieLoader(); LottieLoader();
@ -71,6 +71,9 @@ public:
bool segment(const char* marker, float& begin, float& end); bool segment(const char* marker, float& begin, float& end);
Result segment(float begin, float end) override; 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: private:
bool ready(); bool ready();
bool header(); bool header();

View file

@ -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); start = tvg::clamp(this->start(frameNo, tween, exps) * 0.01f, 0.0f, 1.0f);
end = tvg::clamp(this->end(frameNo, exps) * 0.01f, 0.0f, 1.0f); end = tvg::clamp(this->end(frameNo, tween, exps) * 0.01f, 0.0f, 1.0f);
auto o = fmodf(this->offset(frameNo, exps), 360.0f) / 360.0f; //0 ~ 1
auto diff = fabs(start - end); auto diff = fabs(start - end);
if (tvg::zero(diff)) { 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); if (start > end) std::swap(start, end);
auto o = fmodf(this->offset(frameNo, tween, exps), 360.0f) / 360.0f; //0 ~ 1
start += o; start += o;
end += 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; if (opacity == 0) return nullptr;
Fill* fill = nullptr; Fill* fill = nullptr;
auto s = start(frameNo, exps); auto s = start(frameNo, tween, exps);
auto e = end(frameNo, exps); auto e = end(frameNo, tween, exps);
//Linear Graident //Linear Graident
if (id == 1) { if (id == 1) {
@ -358,14 +358,14 @@ Fill* LottieGradient::fill(float frameNo, LottieExpressions* exps)
auto w = fabsf(e.x - s.x); auto w = fabsf(e.x - s.x);
auto h = fabsf(e.y - s.y); auto h = fabsf(e.y - s.y);
auto r = (w > h) ? (w + 0.375f * h) : (h + 0.375f * w); 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)) { if (tvg::zero(progress)) {
static_cast<RadialGradient*>(fill)->radial(s.x, s.y, r, s.x, s.y, 0.0f); static_cast<RadialGradient*>(fill)->radial(s.x, s.y, r, s.x, s.y, 0.0f);
} else { } else {
if (tvg::equal(progress, 1.0f)) progress = 0.99f; if (tvg::equal(progress, 1.0f)) progress = 0.99f;
auto startAngle = rad2deg(tvg::atan2(e.y - s.y, e.x - s.x)); 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 fx = s.x + cos(angle) * progress * r;
auto fy = s.y + sin(angle) * progress * r; auto fy = s.y + sin(angle) * progress * r;
// Lottie doesn't have any focal radius concept // Lottie doesn't have any focal radius concept
@ -375,7 +375,7 @@ Fill* LottieGradient::fill(float frameNo, LottieExpressions* exps)
if (!fill) return nullptr; if (!fill) return nullptr;
colorStops(frameNo, fill, exps); colorStops(frameNo, fill, tween, exps);
//multiply the current opacity with the fill //multiply the current opacity with the fill
if (opacity < 255) { if (opacity < 255) {

View file

@ -52,21 +52,20 @@ struct LottieStroke
return dashattr->value[no]; 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); return dash(2)(frameNo, tween, exps);
if (d == 0.0f) return 0.1f;
else return d;
} }
LottieFloat width = 0.0f; LottieFloat width = 0.0f;
@ -382,7 +381,7 @@ struct LottieTrimpath : LottieObject
return nullptr; 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 start = 0.0f;
LottieFloat end = 100.0f; LottieFloat end = 100.0f;
@ -690,7 +689,7 @@ struct LottieGradient : LottieObject
} }
uint32_t populate(ColorStop& color, size_t count); 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 start = Point{0.0f, 0.0f};
LottieScalar end = Point{0.0f, 0.0f}; LottieScalar end = Point{0.0f, 0.0f};
@ -962,6 +961,13 @@ struct LottieComposition
return nullptr; 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; LottieLayer* root = nullptr;
char* version = nullptr; char* version = nullptr;
char* name = nullptr; char* name = nullptr;

View file

@ -36,6 +36,10 @@ struct LottieLayer;
struct LottieObject; struct LottieObject;
//default keyframe updates condition (no tweening)
#define DEFAULT_COND (!tween.active || !frames || (frames->count == 1))
template<typename T> template<typename T>
struct LottieScalarFrame struct LottieScalarFrame
{ {
@ -159,32 +163,32 @@ struct LottieExpression
}; };
static void _copy(PathSet* pathset, Array<Point>& outPts, Matrix* transform) static void _copy(PathSet* pathset, Array<Point>& out, Matrix* transform)
{ {
Array<Point> inPts; Array<Point> in;
if (transform) { if (transform) {
for (int i = 0; i < pathset->ptsCnt; ++i) { for (int i = 0; i < pathset->ptsCnt; ++i) {
Point pt = pathset->pts[i]; Point pt = pathset->pts[i];
pt *= *transform; pt *= *transform;
outPts.push(pt); out.push(pt);
} }
} else { } else {
inPts.data = pathset->pts; in.data = pathset->pts;
inPts.count = pathset->ptsCnt; in.count = pathset->ptsCnt;
outPts.push(inPts); out.push(in);
inPts.data = nullptr; in.data = nullptr;
} }
} }
static void _copy(PathSet* pathset, Array<PathCommand>& outCmds) static void _copy(PathSet* pathset, Array<PathCommand>& out)
{ {
Array<PathCommand> inCmds; Array<PathCommand> in;
inCmds.data = pathset->cmds; in.data = pathset->cmds;
inCmds.count = pathset->cmdsCnt; in.count = pathset->cmdsCnt;
outCmds.push(inCmds); out.push(in);
inCmds.data = nullptr; in.data = nullptr;
} }
@ -343,6 +347,12 @@ struct LottieGenericProperty : LottieProperty
return frame->interpolate(frame + 1, frameNo); 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<Frame, Value, Scalar>& rhs, bool shallow = true) void copy(const LottieGenericProperty<Frame, Value, Scalar>& rhs, bool shallow = true)
{ {
if (rhs.frames) { if (rhs.frames) {
@ -370,6 +380,12 @@ struct LottieGenericProperty : LottieProperty
return frame->angle(frame + 1, frameNo); 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() void prepare()
{ {
if (Scalar) return; if (Scalar) return;
@ -456,7 +472,6 @@ struct LottiePathSet : LottieProperty
if (tvg::equal(frame->no, frameNo)) path = &frame->value; if (tvg::equal(frame->no, frameNo)) path = &frame->value;
else if (frame->value.ptsCnt != (frame + 1)->value.ptsCnt) { else if (frame->value.ptsCnt != (frame + 1)->value.ptsCnt) {
path = &frame->value; path = &frame->value;
TVGLOG("LOTTIE", "Different numbers of points in consecutive frames - interpolation omitted.");
} else { } else {
t = (frameNo - frame->no) / ((frame + 1)->no - frame->no); t = (frameNo - frame->no) / ((frame + 1)->no - frame->no);
if (frame->interpolator) t = frame->interpolator->progress(t); if (frame->interpolator) t = frame->interpolator->progress(t);
@ -524,6 +539,27 @@ struct LottiePathSet : LottieProperty
return true; 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) bool operator()(float frameNo, RenderPath& out, Matrix* transform, LottieExpressions* exps, LottieModifier* modifier = nullptr)
{ {
//overriding with expressions //overriding with expressions
@ -536,7 +572,11 @@ struct LottiePathSet : LottieProperty
else return defaultPath(frameNo, out, transform); 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]; 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<Fill::ColorStop*>(from)->offset = lerp(from->offset, to->offset, tween.progress);
const_cast<Fill::ColorStop*>(from)->r = lerp(from->r, to->r, tween.progress);
const_cast<Fill::ColorStop*>(from)->g = lerp(from->g, to->g, tween.progress);
const_cast<Fill::ColorStop*>(from)->b = lerp(from->b, to->b, tween.progress);
const_cast<Fill::ColorStop*>(from)->a = lerp(from->a, to->a, tween.progress);
}
return Result::Success;
}
Result operator()(float frameNo, Fill* fill, LottieExpressions* exps = nullptr) Result operator()(float frameNo, Fill* fill, LottieExpressions* exps = nullptr)
{ {
//overriding with expressions //overriding with expressions
@ -660,6 +732,12 @@ struct LottieColorStop : LottieProperty
return fill->colorStops(result.data, count); 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) void copy(const LottieColorStop& rhs, bool shallow = true)
{ {
if (rhs.frames) { if (rhs.frames) {