mirror of
https://github.com/thorvg/thorvg.git
synced 2025-06-14 12:04:29 +00:00
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:
parent
0dd0a3b45c
commit
78cfc33f4c
12 changed files with 355 additions and 150 deletions
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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<LottieAnimation*>(animation)->tween(from, to, progress);
|
||||
#endif
|
||||
return TVG_RESULT_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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<LottieLoader*>(PICTURE(pImpl->picture)->loader)->override(slot)) return Result::Success;
|
||||
if (static_cast<LottieLoader*>(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<LottieLoader*>(loader)->tween(from, to, progress)) return Result::InsufficientCondition;
|
||||
return Result::Success;
|
||||
}
|
||||
|
||||
|
||||
uint32_t LottieAnimation::markersCnt() noexcept
|
||||
{
|
||||
auto loader = PICTURE(pImpl->picture)->loader;
|
||||
|
|
|
@ -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<LottieSolidStroke*>(*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<LottieStroke*>(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<LottieStroke*>(stroke), frameNo, ctx, tween, exps);
|
||||
}
|
||||
|
||||
|
||||
|
@ -265,8 +264,8 @@ void LottieBuilder::updateGradientStroke(LottieGroup* parent, LottieObject** chi
|
|||
auto stroke = static_cast<LottieGradientStroke*>(*child);
|
||||
|
||||
ctx->merging = nullptr;
|
||||
ctx->propagator->strokeFill(stroke->fill(frameNo, exps));
|
||||
_updateStroke(static_cast<LottieStroke*>(stroke), frameNo, ctx, exps);
|
||||
ctx->propagator->strokeFill(stroke->fill(frameNo, tween, 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);
|
||||
|
||||
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<RenderContext>& contexts, RenderContext* ctx)
|
||||
{
|
||||
auto rect = static_cast<LottieRect*>(*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<LottieEllipse*>(*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<LottiePath*>(*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<RenderContext>& contexts, RenderContext* ctx)
|
||||
{
|
||||
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 (!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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
@ -810,15 +810,15 @@ void LottieBuilder::updateRepeater(TVG_UNUSED LottieGroup* parent, LottieObject*
|
|||
auto repeater = static_cast<LottieRepeater*>(*child);
|
||||
|
||||
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.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<LottieTrimpath*>(*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<LottieGroup*>(*p);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -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<uint8_t>(color.rgb[0], rangeColor.rgb[0], 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);
|
||||
}
|
||||
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<uint8_t>(strokeColor.rgb[0], rangeColor.rgb[0], 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);
|
||||
}
|
||||
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<uint32_t>(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 <comp->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;
|
||||
|
|
|
@ -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<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 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<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);
|
||||
|
@ -153,6 +171,7 @@ private:
|
|||
|
||||
RenderPath buffer; //resusable path
|
||||
LottieExpressions* exps;
|
||||
Tween tween;
|
||||
};
|
||||
|
||||
#endif //_TVG_LOTTIE_BUILDER_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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
@ -436,3 +440,21 @@ bool LottieLoader::ready()
|
|||
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;
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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<RadialGradient*>(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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -36,6 +36,10 @@ struct LottieLayer;
|
|||
struct LottieObject;
|
||||
|
||||
|
||||
//default keyframe updates condition (no tweening)
|
||||
#define DEFAULT_COND (!tween.active || !frames || (frames->count == 1))
|
||||
|
||||
|
||||
template<typename T>
|
||||
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) {
|
||||
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<PathCommand>& outCmds)
|
||||
static void _copy(PathSet* pathset, Array<PathCommand>& out)
|
||||
{
|
||||
Array<PathCommand> inCmds;
|
||||
inCmds.data = pathset->cmds;
|
||||
inCmds.count = pathset->cmdsCnt;
|
||||
outCmds.push(inCmds);
|
||||
inCmds.data = nullptr;
|
||||
Array<PathCommand> 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<Frame, Value, Scalar>& 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<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)
|
||||
{
|
||||
//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) {
|
||||
|
|
Loading…
Add table
Reference in a new issue