animation/lottie: updated the frame count unit.

replace the frame count unit from the int32_t to float
since animations could smoothly interpolate key-frames.

This notificably improve the animation smoothness in Lottie

Beta API changes:
Result Animation::frame(uint32_t no) -> Result Animation::frame(float no)
uint32_t Animation::curFrame() const -> float Animation::curFrame() const
uint32_t Animation::totalFrame() const -> float Animation::totalFrame() const
This commit is contained in:
Hermet Park 2023-10-23 18:25:57 +09:00
parent e17cc45ba8
commit dc02e131e9
19 changed files with 137 additions and 146 deletions

View file

@ -1671,7 +1671,7 @@ public:
*
* @BETA_API
*/
Result frame(uint32_t no) noexcept;
Result frame(float no) noexcept;
/**
* @brief Retrieves a picture instance associated with this animation instance.
@ -1695,12 +1695,12 @@ public:
*
* @note If the Picture is not properly configured, this function will return 0.
*
* @see Animation::frame(uint32_t no)
* @see Animation::frame(float no)
* @see Animation::totalFrame()
*
* @BETA_API
*/
uint32_t curFrame() const noexcept;
float curFrame() const noexcept;
/**
* @brief Retrieves the total number of frames in the animation.
@ -1712,7 +1712,7 @@ public:
*
* @BETA_API
*/
uint32_t totalFrame() const noexcept;
float totalFrame() const noexcept;
/**
* @brief Retrieves the duration of the animation in seconds.

View file

@ -2173,7 +2173,7 @@ TVG_API Tvg_Animation* tvg_animation_new();
*
* \see tvg_animation_get_total_frame()
*/
TVG_API Tvg_Result tvg_animation_set_frame(Tvg_Animation* animation, uint32_t no);
TVG_API Tvg_Result tvg_animation_set_frame(Tvg_Animation* animation, float no);
/*!
@ -2205,7 +2205,7 @@ TVG_API Tvg_Paint* tvg_animation_get_picture(Tvg_Animation* animation);
* \see tvg_animation_get_total_frame()
* \see tvg_animation_set_frame()
*/
TVG_API Tvg_Result tvg_animation_get_frame(Tvg_Animation* animation, uint32_t* no);
TVG_API Tvg_Result tvg_animation_get_frame(Tvg_Animation* animation, float* no);
/*!
@ -2221,7 +2221,7 @@ TVG_API Tvg_Result tvg_animation_get_frame(Tvg_Animation* animation, uint32_t* n
* \note Frame numbering starts from 0.
* \note If the Picture is not properly configured, this function will return 0.
*/
TVG_API Tvg_Result tvg_animation_get_total_frame(Tvg_Animation* animation, uint32_t* cnt);
TVG_API Tvg_Result tvg_animation_get_total_frame(Tvg_Animation* animation, float* cnt);
/*!

View file

@ -713,14 +713,14 @@ TVG_API Tvg_Animation* tvg_animation_new()
}
TVG_API Tvg_Result tvg_animation_set_frame(Tvg_Animation* animation, uint32_t no)
TVG_API Tvg_Result tvg_animation_set_frame(Tvg_Animation* animation, float no)
{
if (!animation) return TVG_RESULT_INVALID_ARGUMENT;
return (Tvg_Result) reinterpret_cast<Animation*>(animation)->frame(no);
}
TVG_API Tvg_Result tvg_animation_get_frame(Tvg_Animation* animation, uint32_t* no)
TVG_API Tvg_Result tvg_animation_get_frame(Tvg_Animation* animation, float* no)
{
if (!animation || !no) return TVG_RESULT_INVALID_ARGUMENT;
*no = reinterpret_cast<Animation*>(animation)->curFrame();
@ -728,7 +728,7 @@ TVG_API Tvg_Result tvg_animation_get_frame(Tvg_Animation* animation, uint32_t* n
}
TVG_API Tvg_Result tvg_animation_get_total_frame(Tvg_Animation* animation, uint32_t* cnt)
TVG_API Tvg_Result tvg_animation_get_total_frame(Tvg_Animation* animation, float* cnt)
{
if (!animation || !cnt) return TVG_RESULT_INVALID_ARGUMENT;
*cnt = reinterpret_cast<Animation*>(animation)->totalFrame();

View file

@ -149,10 +149,9 @@ public:
return val(animation->totalFrame());
}
bool frame(uint32_t no)
bool frame(float no)
{
if (!canvas || !animation) return false;
if (animation->curFrame() == no) return true;
if (animation->frame(no) != Result::Success) {
errorMsg = "frame() fail";
return false;

View file

@ -33,10 +33,7 @@ void tvgUpdateCmds(tvg::Canvas* canvas, tvg::Animation* animation, float progres
if (!canvas) return;
//Update animation frame only when it's changed
auto frame = lroundf(animation->totalFrame() * progress);
if (frame != animation->curFrame()) {
animation->frame(frame);
if (animation->frame(animation->totalFrame() * progress) == tvg::Result::Success) {
canvas->update(animation->picture());
}
}

View file

@ -257,19 +257,18 @@ void transitCb(Elm_Transit_Effect *effect, Elm_Transit* transit, double progress
{
if (!canvas) return;
uint32_t total_frame = 0;
float total_frame = 0.0f;
tvg_animation_get_total_frame(animation, &total_frame);
uint32_t new_frame = lroundf(total_frame * progress);
float new_frame = total_frame * progress;
uint32_t cur_frame = 0;
float cur_frame = 0.0f;
tvg_animation_get_frame(animation, &cur_frame);
//Update animation frame only when it's changed
if (new_frame == cur_frame) return;
tvg_animation_set_frame(animation, new_frame);
tvg_canvas_update_paint(canvas, tvg_animation_get_picture(animation));
if (tvg_animation_set_frame(animation, new_frame) == TVG_RESULT_SUCCESS) {
tvg_canvas_update_paint(canvas, tvg_animation_get_picture(animation));
}
//Draw the canvas
tvg_canvas_draw(canvas);

View file

@ -95,14 +95,10 @@ void tvgUpdateCmds(Elm_Transit_Effect *effect, Elm_Transit* transit, double prog
auto animation = static_cast<tvg::Animation*>(effect);
//Update animation frame only when it's changed
auto frame = lroundf(animation->totalFrame() * progress);
if (frame != animation->curFrame()) {
auto before = ecore_time_get();
animation->frame(frame);
auto after = ecore_time_get();
updateTime += after - before;
}
auto before = ecore_time_get();
animation->frame(animation->totalFrame() * progress);
auto after = ecore_time_get();
updateTime += after - before;
}
void tvgSwTest(uint32_t* buffer)

View file

@ -83,8 +83,8 @@ struct RenderContext
};
static void _updateChildren(LottieGroup* parent, int32_t frameNo, queue<RenderContext>& contexts);
static void _updateLayer(LottieLayer* root, LottieLayer* layer, int32_t frameNo);
static void _updateChildren(LottieGroup* parent, float frameNo, queue<RenderContext>& contexts);
static void _updateLayer(LottieLayer* root, LottieLayer* layer, float frameNo);
static bool _buildPrecomp(LottieComposition* comp, LottieGroup* parent);
static void _rotateX(Matrix* m, float degree)
@ -114,7 +114,7 @@ static void _rotationZ(Matrix* m, float degree)
}
static bool _updateTransform(LottieTransform* transform, int32_t frameNo, bool autoOrient, Matrix& matrix, uint8_t& opacity)
static bool _updateTransform(LottieTransform* transform, float frameNo, bool autoOrient, Matrix& matrix, uint8_t& opacity)
{
mathIdentity(&matrix);
@ -154,9 +154,9 @@ static bool _updateTransform(LottieTransform* transform, int32_t frameNo, bool a
}
static void _updateTransform(LottieLayer* layer, int32_t frameNo)
static void _updateTransform(LottieLayer* layer, float frameNo)
{
if (!layer || layer->cache.frameNo == frameNo) return;
if (!layer || mathEqual(layer->cache.frameNo, frameNo)) return;
auto transform = layer->transform;
auto parent = layer->parent;
@ -177,7 +177,7 @@ static void _updateTransform(LottieLayer* layer, int32_t frameNo)
}
static void _updateTransform(LottieTransform* transform, int32_t frameNo, RenderContext& ctx)
static void _updateTransform(LottieTransform* transform, float frameNo, RenderContext& ctx)
{
if (!transform) return;
@ -198,7 +198,7 @@ static void _updateTransform(LottieTransform* transform, int32_t frameNo, Render
}
static void _updateGroup(LottieGroup* parent, LottieGroup* group, int32_t frameNo, RenderContext& ctx)
static void _updateGroup(LottieGroup* parent, LottieGroup* group, float frameNo, RenderContext& ctx)
{
if (group->children.empty()) return;
@ -212,7 +212,7 @@ static void _updateGroup(LottieGroup* parent, LottieGroup* group, int32_t frameN
}
static void _updateStroke(LottieStroke* stroke, int32_t frameNo, RenderContext& ctx)
static void _updateStroke(LottieStroke* stroke, float frameNo, RenderContext& ctx)
{
ctx.propagator->stroke(stroke->width(frameNo));
ctx.propagator->stroke(stroke->cap);
@ -245,7 +245,7 @@ static bool _fragmentedStroking(LottieObject** child, queue<RenderContext>& cont
}
static void _updateSolidStroke(LottieObject** child, int32_t frameNo, queue<RenderContext>& contexts, RenderContext& ctx)
static void _updateSolidStroke(LottieObject** child, float frameNo, queue<RenderContext>& contexts, RenderContext& ctx)
{
if (_fragmentedStroking(child, contexts, ctx)) return;
@ -259,7 +259,7 @@ static void _updateSolidStroke(LottieObject** child, int32_t frameNo, queue<Rend
}
static void _updateGradientStroke(LottieObject** child, int32_t frameNo, queue<RenderContext>& contexts, RenderContext& ctx)
static void _updateGradientStroke(LottieObject** child, float frameNo, queue<RenderContext>& contexts, RenderContext& ctx)
{
if (_fragmentedStroking(child, contexts, ctx)) return;
@ -272,7 +272,7 @@ static void _updateGradientStroke(LottieObject** child, int32_t frameNo, queue<R
}
static void _updateFill(LottieSolidFill* fill, int32_t frameNo, RenderContext& ctx)
static void _updateFill(LottieSolidFill* fill, float frameNo, RenderContext& ctx)
{
if (ctx.stroking) return;
@ -286,7 +286,7 @@ static void _updateFill(LottieSolidFill* fill, int32_t frameNo, RenderContext& c
}
static Shape* _updateFill(LottieGradientFill* fill, int32_t frameNo, RenderContext& ctx)
static Shape* _updateFill(LottieGradientFill* fill, float frameNo, RenderContext& ctx)
{
if (ctx.stroking) return nullptr;
@ -303,7 +303,7 @@ static Shape* _updateFill(LottieGradientFill* fill, int32_t frameNo, RenderConte
}
static Shape* _draw(LottieGroup* parent, int32_t frameNo, RenderContext& ctx)
static Shape* _draw(LottieGroup* parent, RenderContext& ctx)
{
if (ctx.allowMerging && ctx.merging) return ctx.merging;
@ -316,7 +316,7 @@ static Shape* _draw(LottieGroup* parent, int32_t frameNo, RenderContext& ctx)
//OPTIMIZE: path?
static void _repeat(LottieGroup* parent, int32_t frameNo, unique_ptr<Shape> path, RenderContext& ctx)
static void _repeat(LottieGroup* parent, unique_ptr<Shape> path, RenderContext& ctx)
{
auto repeater = ctx.repeater;
@ -363,7 +363,7 @@ static void _repeat(LottieGroup* parent, int32_t frameNo, unique_ptr<Shape> path
}
static void _updateRect(LottieGroup* parent, LottieRect* rect, int32_t frameNo, RenderContext& ctx)
static void _updateRect(LottieGroup* parent, LottieRect* rect, float frameNo, RenderContext& ctx)
{
auto position = rect->position(frameNo);
auto size = rect->size(frameNo);
@ -378,15 +378,15 @@ static void _updateRect(LottieGroup* parent, LottieRect* rect, int32_t frameNo,
if (ctx.repeater) {
auto path = Shape::gen();
path->appendRect(position.x - size.x * 0.5f, position.y - size.y * 0.5f, size.x, size.y, roundness, roundness);
_repeat(parent, frameNo, std::move(path), ctx);
_repeat(parent, std::move(path), ctx);
} else {
auto merging = _draw(parent, frameNo, ctx);
auto merging = _draw(parent, ctx);
merging->appendRect(position.x - size.x * 0.5f, position.y - size.y * 0.5f, size.x, size.y, roundness, roundness);
}
}
static void _updateEllipse(LottieGroup* parent, LottieEllipse* ellipse, int32_t frameNo, RenderContext& ctx)
static void _updateEllipse(LottieGroup* parent, LottieEllipse* ellipse, float frameNo, RenderContext& ctx)
{
auto position = ellipse->position(frameNo);
auto size = ellipse->size(frameNo);
@ -394,22 +394,22 @@ static void _updateEllipse(LottieGroup* parent, LottieEllipse* ellipse, int32_t
if (ctx.repeater) {
auto path = Shape::gen();
path->appendCircle(position.x, position.y, size.x * 0.5f, size.y * 0.5f);
_repeat(parent, frameNo, std::move(path), ctx);
_repeat(parent, std::move(path), ctx);
} else {
auto merging = _draw(parent, frameNo, ctx);
auto merging = _draw(parent, ctx);
merging->appendCircle(position.x, position.y, size.x * 0.5f, size.y * 0.5f);
}
}
static void _updatePath(LottieGroup* parent, LottiePath* path, int32_t frameNo, RenderContext& ctx)
static void _updatePath(LottieGroup* parent, LottiePath* path, float frameNo, RenderContext& ctx)
{
if (ctx.repeater) {
auto p = Shape::gen();
path->pathset(frameNo, P(p)->rs.path.cmds, P(p)->rs.path.pts);
_repeat(parent, frameNo, std::move(p), ctx);
_repeat(parent, std::move(p), ctx);
} else {
auto merging = _draw(parent, frameNo, ctx);
auto merging = _draw(parent, ctx);
if (path->pathset(frameNo, P(merging)->rs.path.cmds, P(merging)->rs.path.pts)) {
P(merging)->update(RenderUpdateFlag::Path);
@ -423,7 +423,7 @@ static void _updatePath(LottieGroup* parent, LottiePath* path, int32_t frameNo,
}
static void _updateStar(LottieGroup* parent, LottiePolyStar* star, Matrix* transform, int32_t frameNo, Shape* merging)
static void _updateStar(LottieGroup* parent, LottiePolyStar* star, Matrix* transform, float frameNo, Shape* merging)
{
static constexpr auto POLYSTAR_MAGIC_NUMBER = 0.47829f / 0.28f;
@ -532,7 +532,7 @@ static void _updateStar(LottieGroup* parent, LottiePolyStar* star, Matrix* trans
}
static void _updatePolygon(LottieGroup* parent, LottiePolyStar* star, Matrix* transform, int32_t frameNo, Shape* merging)
static void _updatePolygon(LottieGroup* parent, LottiePolyStar* star, Matrix* transform, float frameNo, Shape* merging)
{
static constexpr auto POLYGON_MAGIC_NUMBER = 0.25f;
@ -601,7 +601,7 @@ static void _updatePolygon(LottieGroup* parent, LottiePolyStar* star, Matrix* tr
}
static void _updatePolystar(LottieGroup* parent, LottiePolyStar* star, int32_t frameNo, RenderContext& ctx)
static void _updatePolystar(LottieGroup* parent, LottiePolyStar* star, float frameNo, RenderContext& ctx)
{
//Optimize: Can we skip the individual coords transform?
Matrix matrix;
@ -616,9 +616,9 @@ static void _updatePolystar(LottieGroup* parent, LottiePolyStar* star, int32_t f
auto p = Shape::gen();
if (star->type == LottiePolyStar::Star) _updateStar(parent, star, identity ? nullptr : &matrix, frameNo, p.get());
else _updatePolygon(parent, star, identity ? nullptr : &matrix, frameNo, p.get());
_repeat(parent, frameNo, std::move(p), ctx);
_repeat(parent, std::move(p), ctx);
} else {
auto merging = _draw(parent, frameNo, ctx);
auto merging = _draw(parent, ctx);
if (star->type == LottiePolyStar::Star) _updateStar(parent, star, identity ? nullptr : &matrix, frameNo, merging);
else _updatePolygon(parent, star, identity ? nullptr : &matrix, frameNo, merging);
P(merging)->update(RenderUpdateFlag::Path);
@ -626,7 +626,7 @@ static void _updatePolystar(LottieGroup* parent, LottiePolyStar* star, int32_t f
}
static void _updateImage(LottieGroup* parent, LottieImage* image, int32_t frameNo, RenderContext& ctx)
static void _updateImage(LottieGroup* parent, LottieImage* image, float frameNo, RenderContext& ctx)
{
auto picture = image->picture;
@ -665,14 +665,14 @@ static void _updateImage(LottieGroup* parent, LottieImage* image, int32_t frameN
}
static void _updateRoundedCorner(LottieRoundedCorner* roundedCorner, int32_t frameNo, RenderContext& ctx)
static void _updateRoundedCorner(LottieRoundedCorner* roundedCorner, float frameNo, RenderContext& ctx)
{
auto roundness = roundedCorner->radius(frameNo);
if (ctx.roundness < roundness) ctx.roundness = roundness;
}
static void _updateRepeater(LottieRepeater* repeater, int32_t frameNo, RenderContext& ctx)
static void _updateRepeater(LottieRepeater* repeater, float frameNo, RenderContext& ctx)
{
if (!ctx.repeater) ctx.repeater = new RenderRepeater();
ctx.repeater->cnt = static_cast<int>(repeater->copies(frameNo));
@ -690,7 +690,7 @@ static void _updateRepeater(LottieRepeater* repeater, int32_t frameNo, RenderCon
}
static void _updateTrimpath(LottieTrimpath* trimpath, int32_t frameNo, RenderContext& ctx)
static void _updateTrimpath(LottieTrimpath* trimpath, float frameNo, RenderContext& ctx)
{
float begin, end;
trimpath->segment(frameNo, begin, end);
@ -709,7 +709,7 @@ static void _updateTrimpath(LottieTrimpath* trimpath, int32_t frameNo, RenderCon
}
static void _updateChildren(LottieGroup* parent, int32_t frameNo, queue<RenderContext>& contexts)
static void _updateChildren(LottieGroup* parent, float frameNo, queue<RenderContext>& contexts)
{
contexts.front().begin = parent->children.end() - 1;
@ -783,7 +783,7 @@ static void _updateChildren(LottieGroup* parent, int32_t frameNo, queue<RenderCo
}
static void _updatePrecomp(LottieLayer* precomp, int32_t frameNo)
static void _updatePrecomp(LottieLayer* precomp, float frameNo)
{
if (precomp->children.count == 0) return;
@ -808,7 +808,7 @@ static void _updatePrecomp(LottieLayer* precomp, int32_t frameNo)
}
static void _updateSolid(LottieLayer* layer, int32_t frameNo)
static void _updateSolid(LottieLayer* layer, float frameNo)
{
auto shape = Shape::gen();
shape->appendRect(0, 0, static_cast<float>(layer->w), static_cast<float>(layer->h));
@ -817,7 +817,7 @@ static void _updateSolid(LottieLayer* layer, int32_t frameNo)
}
static void _updateMaskings(LottieLayer* layer, int32_t frameNo)
static void _updateMaskings(LottieLayer* layer, float frameNo)
{
if (layer->masks.count == 0) return;
@ -855,7 +855,7 @@ static void _updateMaskings(LottieLayer* layer, int32_t frameNo)
}
static void _updateLayer(LottieLayer* root, LottieLayer* layer, int32_t frameNo)
static void _updateLayer(LottieLayer* root, LottieLayer* layer, float frameNo)
{
layer->scene = nullptr;
@ -1021,7 +1021,7 @@ static bool _buildPrecomp(LottieComposition* comp, LottieGroup* parent)
/* External Class Implementation */
/************************************************************************/
bool LottieBuilder::update(LottieComposition* comp, int32_t frameNo)
bool LottieBuilder::update(LottieComposition* comp, float frameNo)
{
frameNo += comp->startFrame;
if (frameNo < comp->startFrame) frameNo = comp->startFrame;

View file

@ -29,7 +29,7 @@ struct LottieComposition;
struct LottieBuilder
{
bool update(LottieComposition* comp, int32_t frameNo);
bool update(LottieComposition* comp, float progress);
void build(LottieComposition* comp);
};

View file

@ -191,7 +191,8 @@ bool LottieLoader::header()
return false;
}
frameDuration = (endFrame - startFrame) / frameRate;
frameCnt = (endFrame - startFrame);
frameDuration = frameCnt / frameRate;
TVGLOG("LOTTIE", "info: frame rate = %f, duration = %f size = %d x %d", frameRate, frameDuration, (int)w, (int)h);
@ -286,6 +287,10 @@ bool LottieLoader::read()
TaskScheduler::request(this);
if (comp && TaskScheduler::threads() == 0) {
frameCnt = comp->frameCnt();
}
return true;
}
@ -309,15 +314,13 @@ unique_ptr<Paint> LottieLoader::paint()
}
bool LottieLoader::frame(uint32_t frameNo)
bool LottieLoader::frame(float no)
{
if (this->frameNo == frameNo) return true;
if (mathEqual(this->frameNo, no)) return false;
this->done();
if (!comp || frameNo >= comp->frameCnt()) return false;
this->frameNo = frameNo;
this->frameNo = no;
TaskScheduler::request(this);
@ -325,16 +328,13 @@ bool LottieLoader::frame(uint32_t frameNo)
}
uint32_t LottieLoader::totalFrame()
float LottieLoader::totalFrame()
{
this->done();
if (!comp) return 0;
return comp->frameCnt();
return frameCnt;
}
uint32_t LottieLoader::curFrame()
float LottieLoader::curFrame()
{
return frameNo;
}

View file

@ -35,8 +35,9 @@ class LottieLoader : public FrameModule, public Task
public:
const char* content = nullptr; //lottie file data
uint32_t size = 0; //lottie data size
uint32_t frameNo = 0; //current frame number
float frameDuration;
float frameNo = 0.0f; //current frame number
float frameCnt = 0.0f;
float frameDuration = 0.0f;
LottieBuilder* builder = nullptr;
LottieComposition* comp = nullptr;
@ -57,9 +58,9 @@ public:
unique_ptr<Paint> paint() override;
//Frame Controls
bool frame(uint32_t frameNo) override;
uint32_t totalFrame() override;
uint32_t curFrame() override;
bool frame(float no) override;
float totalFrame() override;
float curFrame() override;
float duration() override;
void sync() override;

View file

@ -34,7 +34,7 @@
/* External Class Implementation */
/************************************************************************/
void LottieTrimpath::segment(int32_t frameNo, float& start, float& end)
void LottieTrimpath::segment(float frameNo, float& start, float& end)
{
auto s = this->start(frameNo) * 0.01f;
auto e = this->end(frameNo) * 0.01f;
@ -77,7 +77,7 @@ void LottieTrimpath::segment(int32_t frameNo, float& start, float& end)
}
Fill* LottieGradient::fill(int32_t frameNo)
Fill* LottieGradient::fill(float frameNo)
{
Fill* fill = nullptr;
@ -145,14 +145,14 @@ void LottieLayer::prepare()
}
int32_t LottieLayer::remap(int32_t frameNo)
float LottieLayer::remap(float frameNo)
{
if (timeRemap.frames || timeRemap.value) {
frameNo = comp->frameAtTime(timeRemap(frameNo));
} else {
frameNo -= startFrame;
}
return (int32_t)(frameNo / timeStretch);
return (frameNo / timeStretch);
}

View file

@ -49,17 +49,17 @@ struct LottieStroke
return dashattr->value[no];
}
float dashOffset(int32_t frameNo)
float dashOffset(float frameNo)
{
return dash(0)(frameNo);
}
float dashGap(int32_t frameNo)
float dashGap(float frameNo)
{
return dash(2)(frameNo);
}
float dashSize(int32_t frameNo)
float dashSize(float frameNo)
{
auto d = dash(1)(frameNo);
if (d == 0.0f) return 0.1f;
@ -177,7 +177,7 @@ struct LottieGradient
return false;
}
Fill* fill(int32_t frameNo);
Fill* fill(float frameNo);
LottiePoint start = Point{0.0f, 0.0f};
LottiePoint end = Point{0.0f, 0.0f};
@ -248,7 +248,7 @@ struct LottieTrimpath : LottieObject
if (start.frames || end.frames || offset.frames) statical = false;
}
void segment(int32_t frameNo, float& start, float& end);
void segment(float frameNo, float& start, float& end);
LottieFloat start = 0.0f;
LottieFloat end = 0.0f;
@ -507,7 +507,7 @@ struct LottieLayer : LottieGroup
delete(transform);
}
uint8_t opacity(int32_t frameNo)
uint8_t opacity(float frameNo)
{
//return zero if the visibility is false.
if (type == Null) return 255;
@ -515,7 +515,7 @@ struct LottieLayer : LottieGroup
}
void prepare();
int32_t remap(int32_t frameNo);
float remap(float frameNo);
//Optimize: compact data??
RGB24 color;
@ -534,16 +534,16 @@ struct LottieLayer : LottieGroup
float timeStretch = 1.0f;
uint32_t w = 0, h = 0;
int32_t inFrame = 0;
int32_t outFrame = 0;
uint32_t startFrame = 0;
float inFrame = 0.0f;
float outFrame = 0.0f;
float startFrame = 0.0f;
char* refId = nullptr; //pre-composition reference.
int16_t pid = -1; //id of the parent layer.
int16_t id = -1; //id of the current layer.
//cached data
struct {
int32_t frameNo = -1;
float frameNo = -1.0f;
Matrix matrix;
uint8_t opacity;
} cache;
@ -583,7 +583,7 @@ struct LottieComposition
char* version = nullptr;
char* name = nullptr;
uint32_t w, h;
int32_t startFrame, endFrame;
float startFrame, endFrame;
float frameRate;
Array<LottieObject*> assets;
Array<LottieInterpolator*> interpolators;

View file

@ -382,7 +382,7 @@ void LottieParser::parseKeyFrame(T& prop)
}
}
} else if (!strcmp(key, "t")) {
frame.no = lroundf(getFloat());
frame.no = getFloat();
} else if (!strcmp(key, "s")) {
getValue(frame.value);
} else if (!strcmp(key, "e")) {
@ -1003,9 +1003,9 @@ LottieLayer* LottieParser::parseLayer()
}
else if (!strcmp(key, "ao")) layer->autoOrient = getInt();
else if (!strcmp(key, "shapes")) parseShapes(layer);
else if (!strcmp(key, "ip")) layer->inFrame = lroundf(getFloat());
else if (!strcmp(key, "op")) layer->outFrame = lroundf(getFloat());
else if (!strcmp(key, "st")) layer->startFrame = lroundf(getFloat());
else if (!strcmp(key, "ip")) layer->inFrame = getFloat();
else if (!strcmp(key, "op")) layer->outFrame = getFloat();
else if (!strcmp(key, "st")) layer->startFrame = getFloat();
else if (!strcmp(key, "bm")) layer->blendMethod = getBlendMethod();
else if (!strcmp(key, "parent")) layer->pid = getInt();
else if (!strcmp(key, "tm")) parseTimeRemap(layer);
@ -1091,8 +1091,8 @@ bool LottieParser::parse()
while (auto key = nextObjectKey()) {
if (!strcmp(key, "v")) comp->version = getStringCopy();
else if (!strcmp(key, "fr")) comp->frameRate = getFloat();
else if (!strcmp(key, "ip")) comp->startFrame = lroundf(getFloat());
else if (!strcmp(key, "op")) comp->endFrame = lroundf(getFloat());
else if (!strcmp(key, "ip")) comp->startFrame = getFloat();
else if (!strcmp(key, "op")) comp->endFrame = getFloat();
else if (!strcmp(key, "w")) comp->w = getInt();
else if (!strcmp(key, "h")) comp->h = getInt();
else if (!strcmp(key, "nm")) comp->name = getStringCopy();

View file

@ -93,13 +93,13 @@ template<typename T>
struct LottieScalarFrame
{
T value; //keyframe value
int32_t no; //frame number
float no; //frame number
LottieInterpolator* interpolator;
bool hold = false; //do not interpolate.
T interpolate(LottieScalarFrame<T>* next, int32_t frameNo)
T interpolate(LottieScalarFrame<T>* next, float frameNo)
{
auto t = float(frameNo - no) / float(next->no - no);
auto t = (frameNo - no) / (next->no - no);
if (interpolator) t = interpolator->progress(t);
if (hold) {
@ -115,16 +115,16 @@ template<typename T>
struct LottieVectorFrame
{
T value; //keyframe value
int32_t no; //frame number
float no; //frame number
LottieInterpolator* interpolator;
T outTangent, inTangent;
float length;
bool hasTangent = false;
bool hold = false;
T interpolate(LottieVectorFrame* next, int32_t frameNo)
T interpolate(LottieVectorFrame* next, float frameNo)
{
auto t = float(frameNo - no) / float(next->no - no);
auto t = (frameNo - no) / (next->no - no);
if (interpolator) t = interpolator->progress(t);
if (hold) {
@ -141,10 +141,10 @@ struct LottieVectorFrame
}
}
float angle(LottieVectorFrame* next, int32_t frameNo)
float angle(LottieVectorFrame* next, float frameNo)
{
if (!hasTangent) return 0;
auto t = float(frameNo - no) / float(next->no - no);
auto t = (frameNo - no) / (next->no - no);
if (interpolator) t = interpolator->progress(t);
Bezier bz = {value, value + outTangent, next->value + inTangent, next->value};
t = bezAt(bz, t * length, length);
@ -190,7 +190,7 @@ struct LottieProperty
return (*frames)[frames->count];
}
T operator()(int32_t frameNo)
T operator()(float frameNo)
{
if (!frames) return value;
if (frames->count == 1 || frameNo <= frames->first().no) return frames->first().value;
@ -202,7 +202,7 @@ struct LottieProperty
while (low <= high) {
auto mid = low + (high - low) / 2;
auto frame = frames->data + mid;
if (frameNo == frame->no) return frame->value;
if (mathEqual(frameNo, frame->no)) return frame->value;
else if (frameNo > frame->no) low = mid + 1;
else high = mid - 1;
}
@ -211,7 +211,7 @@ struct LottieProperty
return (frame - 1)->interpolate(frame, frameNo);
}
float angle(int32_t frameNo) { return 0; }
float angle(float frameNo) { return 0; }
void prepare() {}
};
@ -258,7 +258,7 @@ struct LottiePathSet
return (*frames)[frames->count];
}
bool operator()(int32_t frameNo, Array<PathCommand>& cmds, Array<Point>& pts)
bool operator()(float frameNo, Array<PathCommand>& cmds, Array<Point>& pts)
{
if (!frames) {
copy(value, cmds);
@ -284,7 +284,7 @@ struct LottiePathSet
while (low <= high) {
auto mid = low + (high - low) / 2;
auto frame = frames->data + mid;
if (frameNo == frame->no) {
if (mathEqual(frameNo, frame->no)) {
copy(frame->value, cmds);
copy(frame->value, pts);
return true;
@ -300,7 +300,7 @@ struct LottiePathSet
auto pframe = frame - 1;
copy(pframe->value, cmds);
auto t = float(frameNo - pframe->no) / float(frame->no - pframe->no);
auto t = (frameNo - pframe->no) / (frame->no - pframe->no);
if (pframe->interpolator) t = pframe->interpolator->progress(t);
if (pframe->hold) {
@ -358,7 +358,7 @@ struct LottieColorStop
return (*frames)[frames->count];
}
void operator()(int32_t frameNo, Fill* fill)
void operator()(float frameNo, Fill* fill)
{
if (!frames) {
fill->colorStops(value.data, count);
@ -381,7 +381,7 @@ struct LottieColorStop
while (low <= high) {
auto mid = low + (high - low) / 2;
auto frame = frames->data + mid;
if (frameNo == frame->no) {
if (mathEqual(frameNo, frame->no)) {
fill->colorStops(frame->value.data, count);
return;
} else if (frameNo > frame->no) {
@ -394,7 +394,7 @@ struct LottieColorStop
//interpolate
auto frame = frames->data + low;
auto pframe = frame - 1;
auto t = float(frameNo - pframe->no) / float(frame->no - pframe->no);
auto t = (frameNo - pframe->no) / (frame->no - pframe->no);
if (pframe->interpolator) t = pframe->interpolator->progress(t);
if (pframe->hold) {
@ -453,7 +453,7 @@ struct LottiePosition
return (*frames)[frames->count];
}
Point operator()(int32_t frameNo)
Point operator()(float frameNo)
{
if (!frames) return value;
if (frames->count == 1 || frameNo <= frames->first().no) return frames->first().value;
@ -465,7 +465,7 @@ struct LottiePosition
while (low <= high) {
auto mid = low + (high - low) / 2;
auto frame = frames->data + mid;
if (frameNo == frame->no) return frame->value;
if (mathEqual(frameNo, frame->no)) return frame->value;
else if (frameNo > frame->no) low = mid + 1;
else high = mid - 1;
}
@ -474,7 +474,7 @@ struct LottiePosition
return (frame - 1)->interpolate(frame, frameNo);
}
float angle(int32_t frameNo)
float angle(float frameNo)
{
if (!frames) return 0;
if (frames->count == 1 || frameNo <= frames->first().no) return 0;

View file

@ -62,7 +62,7 @@ Animation::Animation() : pImpl(new Impl)
}
Result Animation::frame(uint32_t no) noexcept
Result Animation::frame(float no) noexcept
{
auto loader = pImpl->picture->pImpl->loader.get();
@ -80,7 +80,7 @@ Picture* Animation::picture() const noexcept
}
uint32_t Animation::curFrame() const noexcept
float Animation::curFrame() const noexcept
{
auto loader = pImpl->picture->pImpl->loader.get();
@ -91,7 +91,7 @@ uint32_t Animation::curFrame() const noexcept
}
uint32_t Animation::totalFrame() const noexcept
float Animation::totalFrame() const noexcept
{
auto loader = pImpl->picture->pImpl->loader.get();

View file

@ -33,10 +33,9 @@ class FrameModule: public LoadModule
public:
virtual ~FrameModule() {}
virtual bool frame(uint32_t frameNo) = 0; //set the current frame number
virtual uint32_t totalFrame() = 0; //return the total frame count
virtual uint32_t curFrame() = 0; //return the current frame number
virtual bool frame(float no) = 0; //set the current frame number
virtual float totalFrame() = 0; //return the total frame count
virtual float curFrame() = 0; //return the current frame number
virtual float duration() = 0; //return the animation duration in seconds
virtual bool animatable() override { return true; }

View file

@ -40,16 +40,16 @@ TEST_CASE("Animation Basic", "[capiAnimation]")
//Negative cases
REQUIRE(tvg_animation_set_frame(animation, 0) == TVG_RESULT_INSUFFICIENT_CONDITION);
uint32_t frame = 0;
auto frame = 0.0f;
REQUIRE(tvg_animation_set_frame(animation, frame) == TVG_RESULT_INSUFFICIENT_CONDITION);
REQUIRE(tvg_animation_get_frame(animation, nullptr) == TVG_RESULT_INVALID_ARGUMENT);
REQUIRE(tvg_animation_get_frame(animation, &frame) == TVG_RESULT_SUCCESS);
REQUIRE(frame == 0);
REQUIRE(frame == 0.0f);
REQUIRE(tvg_animation_get_total_frame(animation, nullptr) == TVG_RESULT_INVALID_ARGUMENT);
REQUIRE(tvg_animation_get_total_frame(animation, &frame) == TVG_RESULT_SUCCESS);
REQUIRE(frame == 0);
REQUIRE(frame == 0.0f);
REQUIRE(tvg_animation_get_duration(animation, nullptr) == TVG_RESULT_INVALID_ARGUMENT);
float duration = 0.0f;
@ -79,13 +79,13 @@ TEST_CASE("Animation Lottie", "[capiAnimation]")
REQUIRE(tvg_picture_load(picture, TEST_DIR"/invalid.json") == TVG_RESULT_INVALID_ARGUMENT);
REQUIRE(tvg_picture_load(picture, TEST_DIR"/test.json") == TVG_RESULT_SUCCESS);
uint32_t frame;
float frame;
REQUIRE(tvg_animation_get_total_frame(animation, &frame) == TVG_RESULT_SUCCESS);
REQUIRE(frame == 120);
REQUIRE(frame == Approx(120).margin(004004));
REQUIRE(tvg_animation_set_frame(animation, frame - 1) == TVG_RESULT_SUCCESS);
REQUIRE(tvg_animation_get_frame(animation, &frame) == TVG_RESULT_SUCCESS);
REQUIRE(frame == 119);
REQUIRE(frame == Approx(119).margin(004004));
float duration;
REQUIRE(tvg_animation_get_duration(animation, &duration) == TVG_RESULT_SUCCESS);

View file

@ -39,9 +39,9 @@ TEST_CASE("Animation Basic", "[tvgAnimation]")
REQUIRE(picture->identifier == Picture::identifier);
//Negative cases
REQUIRE(animation->frame(0) == Result::InsufficientCondition);
REQUIRE(animation->curFrame() == 0);
REQUIRE(animation->totalFrame() == 0);
REQUIRE(animation->frame(0.0f) == Result::InsufficientCondition);
REQUIRE(animation->curFrame() == 0.0f);
REQUIRE(animation->totalFrame() == 0.0f);
REQUIRE(animation->duration() == 0.0f);
}
@ -60,7 +60,7 @@ TEST_CASE("Animation Lottie", "[tvgAnimation]")
REQUIRE(picture->load(TEST_DIR"/invalid.json") == Result::InvalidArguments);
REQUIRE(picture->load(TEST_DIR"/test.json") == Result::Success);
REQUIRE(animation->totalFrame() == 120);
REQUIRE(animation->totalFrame() == Approx(120).margin(004004));
REQUIRE(animation->curFrame() == 0);
REQUIRE(animation->duration() == Approx(4).margin(004004));
REQUIRE(animation->frame(20) == Result::Success);