From 709ef9158f0d5656bcb9071d3b8b5dac4631a60b Mon Sep 17 00:00:00 2001 From: Hermet Park Date: Thu, 18 Apr 2024 23:02:18 +0900 Subject: [PATCH] lottie: support the expression feature The current development of the expression engine is experimental. It does not support multi-threading. Therefore, when tvg::Initializer::init() is configured with more than one thread, expressions will be automatically disabled. issue: https://github.com/thorvg/thorvg/issues/1640 --- src/loaders/lottie/meson.build | 6 +- src/loaders/lottie/tvgLottieBuilder.cpp | 122 +- src/loaders/lottie/tvgLottieBuilder.h | 13 + src/loaders/lottie/tvgLottieExpressions.cpp | 1261 +++++++++++++++++++ src/loaders/lottie/tvgLottieExpressions.h | 170 +++ src/loaders/lottie/tvgLottieModel.cpp | 16 +- src/loaders/lottie/tvgLottieModel.h | 25 +- src/loaders/lottie/tvgLottieParser.cpp | 27 +- src/loaders/lottie/tvgLottieProperty.h | 340 ++++- 9 files changed, 1828 insertions(+), 152 deletions(-) create mode 100644 src/loaders/lottie/tvgLottieExpressions.cpp create mode 100644 src/loaders/lottie/tvgLottieExpressions.h diff --git a/src/loaders/lottie/meson.build b/src/loaders/lottie/meson.build index 0042db5f..3af9e254 100644 --- a/src/loaders/lottie/meson.build +++ b/src/loaders/lottie/meson.build @@ -3,15 +3,17 @@ if lottie_expressions endif source_file = [ - 'tvgLottieAnimation.cpp', 'tvgLottieBuilder.h', + 'tvgLottieExpressions.h', 'tvgLottieInterpolator.h', 'tvgLottieLoader.h', 'tvgLottieModel.h', 'tvgLottieParser.h', 'tvgLottieParserHandler.h', 'tvgLottieProperty.h', + 'tvgLottieAnimation.cpp', 'tvgLottieBuilder.cpp', + 'tvgLottieExpressions.cpp', 'tvgLottieInterpolator.cpp', 'tvgLottieLoader.cpp', 'tvgLottieModel.cpp', @@ -25,4 +27,4 @@ subloader_dep += [declare_dependency( )] install_headers('thorvg_lottie.h') -headers += include_directories('.') \ No newline at end of file +headers += include_directories('.') diff --git a/src/loaders/lottie/tvgLottieBuilder.cpp b/src/loaders/lottie/tvgLottieBuilder.cpp index b943d853..10b0ade2 100644 --- a/src/loaders/lottie/tvgLottieBuilder.cpp +++ b/src/loaders/lottie/tvgLottieBuilder.cpp @@ -27,9 +27,10 @@ #include "tvgPaint.h" #include "tvgShape.h" #include "tvgInlist.h" +#include "tvgTaskScheduler.h" #include "tvgLottieModel.h" #include "tvgLottieBuilder.h" -#include "tvgTaskScheduler.h" +#include "tvgLottieExpressions.h" /************************************************************************/ @@ -96,8 +97,8 @@ struct RenderContext }; -static void _updateChildren(LottieGroup* parent, float frameNo, Inlist& contexts); -static void _updateLayer(LottieLayer* root, LottieLayer* layer, float frameNo); +static void _updateChildren(LottieGroup* parent, float frameNo, Inlist& contexts, LottieExpressions* exps); +static void _updateLayer(LottieLayer* root, LottieLayer* layer, float frameNo, LottieExpressions* exps); static bool _buildComposition(LottieComposition* comp, LottieGroup* parent); static Shape* _draw(LottieGroup* parent, RenderContext* ctx); @@ -128,7 +129,7 @@ static void _rotationZ(Matrix* m, float degree) } -static bool _updateTransform(LottieTransform* transform, float frameNo, bool autoOrient, Matrix& matrix, uint8_t& opacity) +static bool _updateTransform(LottieTransform* transform, float frameNo, bool autoOrient, Matrix& matrix, uint8_t& opacity, LottieExpressions* exps) { mathIdentity(&matrix); @@ -140,46 +141,46 @@ static bool _updateTransform(LottieTransform* transform, float frameNo, bool aut if (transform->coords) { mathTranslate(&matrix, transform->coords->x(frameNo), transform->coords->y(frameNo)); } else { - auto position = transform->position(frameNo); + auto position = transform->position(frameNo, exps); mathTranslate(&matrix, position.x, position.y); } auto angle = 0.0f; if (autoOrient) angle = transform->position.angle(frameNo); - _rotationZ(&matrix, transform->rotation(frameNo) + angle); + _rotationZ(&matrix, transform->rotation(frameNo, exps) + angle); if (transform->rotationEx) { _rotateY(&matrix, transform->rotationEx->y(frameNo)); _rotateX(&matrix, transform->rotationEx->x(frameNo)); } - auto scale = transform->scale(frameNo); + auto scale = transform->scale(frameNo, exps); mathScaleR(&matrix, scale.x * 0.01f, scale.y * 0.01f); //Lottie specific anchor transform. - auto anchor = transform->anchor(frameNo); + auto anchor = transform->anchor(frameNo, exps); mathTranslateR(&matrix, -anchor.x, -anchor.y); //invisible just in case. if (scale.x == 0.0f || scale.y == 0.0f) opacity = 0; - else opacity = transform->opacity(frameNo); + else opacity = transform->opacity(frameNo, exps); return true; } -static void _updateTransform(LottieLayer* layer, float frameNo) +static void _updateTransform(LottieLayer* layer, float frameNo, LottieExpressions* exps) { if (!layer || mathEqual(layer->cache.frameNo, frameNo)) return; auto transform = layer->transform; auto parent = layer->parent; - if (parent) _updateTransform(parent, frameNo); + if (parent) _updateTransform(parent, frameNo, exps); auto& matrix = layer->cache.matrix; - _updateTransform(transform, frameNo, layer->autoOrient, matrix, layer->cache.opacity); + _updateTransform(transform, frameNo, layer->autoOrient, matrix, layer->cache.opacity, exps); if (parent) { if (!mathIdentity((const Matrix*) &parent->cache.matrix)) { @@ -191,7 +192,7 @@ static void _updateTransform(LottieLayer* layer, float frameNo) } -static void _updateTransform(LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist& contexts, RenderContext* ctx) +static void _updateTransform(LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist& contexts, RenderContext* ctx, LottieExpressions* exps) { auto transform = static_cast(*child); if (!transform) return; @@ -200,14 +201,14 @@ static void _updateTransform(LottieGroup* parent, LottieObject** child, float fr if (parent->mergeable()) { if (!ctx->transform) ctx->transform = (Matrix*)malloc(sizeof(Matrix)); - _updateTransform(transform, frameNo, false, *ctx->transform, opacity); + _updateTransform(transform, frameNo, false, *ctx->transform, opacity, exps); return; } ctx->merging = nullptr; Matrix matrix; - if (!_updateTransform(transform, frameNo, false, matrix, opacity)) return; + if (!_updateTransform(transform, frameNo, false, matrix, opacity, exps)) return; auto pmatrix = PP(ctx->propagator)->transform(); ctx->propagator->transform(pmatrix ? mathMultiply(pmatrix, &matrix) : matrix); @@ -221,7 +222,7 @@ static void _updateTransform(LottieGroup* parent, LottieObject** child, float fr } -static void _updateGroup(LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist& pcontexts, RenderContext* ctx) +static void _updateGroup(LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist& pcontexts, RenderContext* ctx, LottieExpressions* exps) { auto group = static_cast(*child); @@ -237,13 +238,13 @@ static void _updateGroup(LottieGroup* parent, LottieObject** child, float frameN Inlist contexts; contexts.back(new RenderContext(*ctx, group->mergeable())); - _updateChildren(group, frameNo, contexts); + _updateChildren(group, frameNo, contexts, exps); contexts.free(); } -static void _updateStroke(LottieStroke* stroke, float frameNo, RenderContext* ctx) +static void _updateStroke(LottieStroke* stroke, float frameNo, RenderContext* ctx, LottieExpressions* exps) { ctx->propagator->stroke(stroke->width(frameNo)); ctx->propagator->stroke(stroke->cap); @@ -252,9 +253,9 @@ static void _updateStroke(LottieStroke* stroke, float frameNo, RenderContext* ct if (stroke->dashattr) { float dashes[2]; - dashes[0] = stroke->dashSize(frameNo); - dashes[1] = dashes[0] + stroke->dashGap(frameNo); - P(ctx->propagator)->strokeDash(dashes, 2, stroke->dashOffset(frameNo)); + dashes[0] = stroke->dashSize(frameNo, exps); + dashes[1] = dashes[0] + stroke->dashGap(frameNo, exps); + P(ctx->propagator)->strokeDash(dashes, 2, stroke->dashOffset(frameNo, exps)); } else { ctx->propagator->stroke(nullptr, 0); } @@ -275,7 +276,7 @@ static bool _fragmented(LottieObject** child, Inlist& contexts, R } -static void _updateSolidStroke(TVG_UNUSED LottieGroup* parent, LottieObject** child, float frameNo, Inlist& contexts, RenderContext* ctx) +static void _updateSolidStroke(TVG_UNUSED LottieGroup* parent, LottieObject** child, float frameNo, Inlist& contexts, RenderContext* ctx, LottieExpressions* exps) { if (_fragmented(child, contexts, ctx)) return; @@ -284,19 +285,19 @@ static void _updateSolidStroke(TVG_UNUSED LottieGroup* parent, LottieObject** ch ctx->merging = nullptr; auto color = stroke->color(frameNo); ctx->propagator->stroke(color.rgb[0], color.rgb[1], color.rgb[2], stroke->opacity(frameNo)); - _updateStroke(static_cast(stroke), frameNo, ctx); + _updateStroke(static_cast(stroke), frameNo, ctx, exps); } -static void _updateGradientStroke(TVG_UNUSED LottieGroup* parent, LottieObject** child, float frameNo, Inlist& contexts, RenderContext* ctx) +static void _updateGradientStroke(TVG_UNUSED LottieGroup* parent, LottieObject** child, float frameNo, Inlist& contexts, RenderContext* ctx, LottieExpressions* exps) { if (_fragmented(child, contexts, ctx)) return; auto stroke = static_cast(*child); ctx->merging = nullptr; - ctx->propagator->stroke(unique_ptr(stroke->fill(frameNo))); - _updateStroke(static_cast(stroke), frameNo, ctx); + ctx->propagator->stroke(unique_ptr(stroke->fill(frameNo, exps))); + _updateStroke(static_cast(stroke), frameNo, ctx, exps); } @@ -315,7 +316,7 @@ static void _updateSolidFill(TVG_UNUSED LottieGroup* parent, LottieObject** chil } -static Shape* _updateGradientFill(TVG_UNUSED LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist& contexts, RenderContext* ctx) +static Shape* _updateGradientFill(TVG_UNUSED LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist& contexts, RenderContext* ctx, LottieExpressions* exps) { if (_fragmented(child, contexts, ctx)) return nullptr; @@ -323,7 +324,7 @@ static Shape* _updateGradientFill(TVG_UNUSED LottieGroup* parent, LottieObject** ctx->merging = nullptr; //TODO: reuse the fill instance? - ctx->propagator->fill(unique_ptr(fill->fill(frameNo))); + ctx->propagator->fill(unique_ptr(fill->fill(frameNo, exps))); ctx->propagator->fill(fill->rule); ctx->propagator->opacity(MULTIPLY(fill->opacity(frameNo), PP(ctx->propagator)->opacity)); @@ -513,17 +514,17 @@ static void _updateEllipse(LottieGroup* parent, LottieObject** child, float fram } -static void _updatePath(LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist& contexts, RenderContext* ctx) +static void _updatePath(LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist& contexts, RenderContext* ctx, LottieExpressions* exps) { auto path = static_cast(*child); if (ctx->repeater) { auto p = Shape::gen(); - path->pathset(frameNo, P(p)->rs.path.cmds, P(p)->rs.path.pts, ctx->transform); + path->pathset(frameNo, P(p)->rs.path.cmds, P(p)->rs.path.pts, ctx->transform, exps); _repeat(parent, std::move(p), ctx); } else { auto merging = _draw(parent, ctx); - if (path->pathset(frameNo, P(merging)->rs.path.cmds, P(merging)->rs.path.pts, ctx->transform)) { + if (path->pathset(frameNo, P(merging)->rs.path.cmds, P(merging)->rs.path.pts, ctx->transform, exps)) { P(merging)->update(RenderUpdateFlag::Path); } if (ctx->roundness > 1.0f && P(merging)->rs.stroke) { @@ -534,7 +535,7 @@ static void _updatePath(LottieGroup* parent, LottieObject** child, float frameNo } -static void _updateText(LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist& contexts, TVG_UNUSED RenderContext* ctx) +static void _updateText(LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist& contexts, TVG_UNUSED RenderContext* ctx, LottieExpressions* exps) { auto text = static_cast(*child); auto& doc = text->doc(frameNo); @@ -586,7 +587,7 @@ static void _updateText(LottieGroup* parent, LottieObject** child, float frameNo for (auto g = glyph->children.begin(); g < glyph->children.end(); ++g) { auto group = static_cast(*g); for (auto p = group->children.begin(); p < group->children.end(); ++p) { - if (static_cast(*p)->pathset(frameNo, P(shape)->rs.path.cmds, P(shape)->rs.path.pts)) { + if (static_cast(*p)->pathset(frameNo, P(shape)->rs.path.cmds, P(shape)->rs.path.pts, nullptr, exps)) { P(shape)->update(RenderUpdateFlag::Path); } } @@ -888,12 +889,12 @@ static void _updateRepeater(TVG_UNUSED LottieGroup* parent, LottieObject** child } -static void _updateTrimpath(TVG_UNUSED LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist& contexts, RenderContext* ctx) +static void _updateTrimpath(TVG_UNUSED LottieGroup* parent, LottieObject** child, float frameNo, TVG_UNUSED Inlist& contexts, RenderContext* ctx, LottieExpressions* exps) { auto trimpath= static_cast(*child); float begin, end; - trimpath->segment(frameNo, begin, end); + trimpath->segment(frameNo, begin, end, exps); if (P(ctx->propagator)->rs.stroke) { auto pbegin = P(ctx->propagator)->rs.stroke->trim.begin; @@ -907,7 +908,7 @@ static void _updateTrimpath(TVG_UNUSED LottieGroup* parent, LottieObject** child } -static void _updateChildren(LottieGroup* parent, float frameNo, Inlist& contexts) +static void _updateChildren(LottieGroup* parent, float frameNo, Inlist& contexts, LottieExpressions* exps) { contexts.head->begin = parent->children.end() - 1; @@ -917,11 +918,11 @@ static void _updateChildren(LottieGroup* parent, float frameNo, Inlistbegin; child >= parent->children.data; --child) { switch ((*child)->type) { case LottieObject::Group: { - _updateGroup(parent, child, frameNo, contexts, ctx); + _updateGroup(parent, child, frameNo, contexts, ctx, exps); break; } case LottieObject::Transform: { - _updateTransform(parent, child, frameNo, contexts, ctx); + _updateTransform(parent, child, frameNo, contexts, ctx, exps); break; } case LottieObject::SolidFill: { @@ -929,15 +930,15 @@ static void _updateChildren(LottieGroup* parent, float frameNo, Inlistchildren.empty()) return; - frameNo = precomp->remap(frameNo); + frameNo = precomp->remap(frameNo, exps); for (auto child = precomp->children.end() - 1; child >= precomp->children.begin(); --child) { - _updateLayer(precomp, static_cast(*child), frameNo); + _updateLayer(precomp, static_cast(*child), frameNo, exps); } //clip the layer viewport @@ -1018,7 +1019,7 @@ static void _updateSolid(LottieLayer* layer) } -static void _updateMaskings(LottieLayer* layer, float frameNo) +static void _updateMaskings(LottieLayer* layer, float frameNo, LottieExpressions* exps) { if (layer->masks.count == 0) return; @@ -1031,7 +1032,7 @@ static void _updateMaskings(LottieLayer* layer, float frameNo) auto shape = Shape::gen().release(); shape->fill(255, 255, 255, mask->opacity(frameNo)); shape->transform(layer->cache.matrix); - if (mask->pathset(frameNo, P(shape)->rs.path.cmds, P(shape)->rs.path.pts)) { + if (mask->pathset(frameNo, P(shape)->rs.path.cmds, P(shape)->rs.path.pts, nullptr, exps)) { P(shape)->update(RenderUpdateFlag::Path); } auto method = mask->method; @@ -1056,12 +1057,12 @@ static void _updateMaskings(LottieLayer* layer, float frameNo) } -static bool _updateMatte(LottieLayer* root, LottieLayer* layer, float frameNo) +static bool _updateMatte(LottieLayer* root, LottieLayer* layer, float frameNo, LottieExpressions* exps) { auto target = layer->matte.target; if (!target) return true; - _updateLayer(root, target, frameNo); + _updateLayer(root, target, frameNo, exps); if (target->scene) { layer->scene->composite(cast(target->scene), layer->matte.type); @@ -1075,14 +1076,14 @@ static bool _updateMatte(LottieLayer* root, LottieLayer* layer, float frameNo) } -static void _updateLayer(LottieLayer* root, LottieLayer* layer, float frameNo) +static void _updateLayer(LottieLayer* root, LottieLayer* layer, float frameNo, LottieExpressions* exps) { layer->scene = nullptr; //visibility if (frameNo < layer->inFrame || frameNo >= layer->outFrame) return; - _updateTransform(layer, frameNo); + _updateTransform(layer, frameNo, exps); //full transparent scene. no need to perform if (layer->type != LottieLayer::Null && layer->cache.opacity == 0) return; @@ -1097,13 +1098,13 @@ static void _updateLayer(LottieLayer* root, LottieLayer* layer, float frameNo) if (layer->matte.target && layer->masks.count > 0) TVGERR("LOTTIE", "FIXME: Matte + Masking??"); - if (!_updateMatte(root, layer, frameNo)) return; + if (!_updateMatte(root, layer, frameNo, exps)) return; - _updateMaskings(layer, frameNo); + _updateMaskings(layer, frameNo, exps); switch (layer->type) { case LottieLayer::Precomp: { - _updatePrecomp(layer, frameNo); + _updatePrecomp(layer, frameNo, exps); break; } case LottieLayer::Solid: { @@ -1114,7 +1115,7 @@ static void _updateLayer(LottieLayer* root, LottieLayer* layer, float frameNo) if (!layer->children.empty()) { Inlist contexts; contexts.back(new RenderContext); - _updateChildren(layer, frameNo, contexts); + _updateChildren(layer, frameNo, contexts, exps); contexts.free(); } break; @@ -1231,9 +1232,12 @@ bool LottieBuilder::update(LottieComposition* comp, float frameNo) auto root = comp->root; root->scene->clear(); + if (exps && comp->expressions) exps->update(frameNo, comp->timeAtFrame(frameNo)); + for (auto child = root->children.end() - 1; child >= root->children.begin(); --child) { - _updateLayer(root, static_cast(*child), frameNo); + _updateLayer(root, static_cast(*child), frameNo, exps); } + return true; } @@ -1253,4 +1257,4 @@ void LottieBuilder::build(LottieComposition* comp) auto clip = Shape::gen(); clip->appendRect(0, 0, static_cast(comp->w), static_cast(comp->h)); comp->root->scene->composite(std::move(clip), CompositeMethod::ClipPath); -} +} \ No newline at end of file diff --git a/src/loaders/lottie/tvgLottieBuilder.h b/src/loaders/lottie/tvgLottieBuilder.h index 52f8fc45..3e7d1bec 100644 --- a/src/loaders/lottie/tvgLottieBuilder.h +++ b/src/loaders/lottie/tvgLottieBuilder.h @@ -24,11 +24,24 @@ #define _TVG_LOTTIE_BUILDER_H_ #include "tvgCommon.h" +#include "tvgLottieExpressions.h" struct LottieComposition; struct LottieBuilder { + LottieExpressions* exps = nullptr; + + LottieBuilder() + { + exps = LottieExpressions::instance(); + } + + ~LottieBuilder() + { + LottieExpressions::retrieve(exps); + } + bool update(LottieComposition* comp, float progress); void build(LottieComposition* comp); }; diff --git a/src/loaders/lottie/tvgLottieExpressions.cpp b/src/loaders/lottie/tvgLottieExpressions.cpp new file mode 100644 index 00000000..72f5a386 --- /dev/null +++ b/src/loaders/lottie/tvgLottieExpressions.cpp @@ -0,0 +1,1261 @@ +/* + * Copyright (c) 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + + +#include "tvgMath.h" +#include "tvgLottieModel.h" +#include "tvgLottieExpressions.h" + +#ifdef THORVG_LOTTIE_EXPRESSIONS_SUPPORT + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +static uint32_t engineRefCnt = 0; //Expressions Engine reference count + +//reserved expressions speicifiers +static const char* EXP_NAME = "name"; +static const char* EXP_CONTENT = "content"; +static const char* EXP_WIDTH = "width"; +static const char* EXP_HEIGHT = "height"; +static const char* EXP_CYCLE = "cycle"; +static const char* EXP_PINGPONG = "pingpong"; +static const char* EXP_OFFSET = "offset"; +static const char* EXP_CONTINUE = "continue"; +static const char* EXP_TIME = "time"; +static const char* EXP_VALUE = "value"; +static const char* EXP_INDEX = "index"; +static const char* EXP_EFFECT= "effect"; + +static void _buildLayer(jerry_value_t context, LottieLayer* layer, LottieComposition* comp); + + +static char* _name(jerry_value_t args) +{ + auto arg0 = jerry_value_to_string(args); + auto len = jerry_string_length(arg0); + auto name = (jerry_char_t*)malloc(len * sizeof(jerry_char_t) + 1); + jerry_string_to_buffer(arg0, JERRY_ENCODING_UTF8, name, len); + name[len] = '\0'; + jerry_value_free(arg0); + return (char*) name; +} + + +static void _buildTransform(jerry_value_t context, LottieTransform* transform) +{ + if (!transform) return; + + auto obj = jerry_object(); + jerry_object_set_sz(context, "transform", obj); + + auto anchorPoint = jerry_object(); + jerry_object_set_native_ptr(anchorPoint, nullptr, &transform->anchor); + jerry_object_set_sz(obj, "anchorPoint", anchorPoint); + jerry_value_free(anchorPoint); + + auto position = jerry_object(); + jerry_object_set_native_ptr(position, nullptr, &transform->position); + jerry_object_set_sz(obj, "position", position); + jerry_value_free(position); + + auto scale = jerry_object(); + jerry_object_set_native_ptr(scale, nullptr, &transform->scale); + jerry_object_set_sz(obj, "scale", scale); + jerry_value_free(scale); + + auto rotation = jerry_object(); + jerry_object_set_native_ptr(rotation, nullptr, &transform->rotation); + jerry_object_set_sz(obj, "rotation", rotation); + jerry_value_free(rotation); + + auto opacity = jerry_object(); + jerry_object_set_native_ptr(opacity, nullptr, &transform->opacity); + jerry_object_set_sz(obj, "opacity", opacity); + jerry_value_free(opacity); + + jerry_value_free(obj); +} + + +static jerry_value_t _value(float frameNo, LottieExpression* exp) +{ + switch (exp->type) { + case LottieProperty::Type::Point: { + auto value = jerry_object(); + auto pos = (*static_cast(exp->property))(frameNo); + auto val1 = jerry_number(pos.x); + auto val2 = jerry_number(pos.y); + jerry_object_set_index(value, 0, val1); + jerry_object_set_index(value, 1, val2); + jerry_value_free(val1); + jerry_value_free(val2); + return value; + } + case LottieProperty::Type::Float: { + return jerry_number((*static_cast(exp->property))(frameNo)); + } + case LottieProperty::Type::Opacity: { + return jerry_number((*static_cast(exp->property))(frameNo)); + } + case LottieProperty::Type::PathSet: { + auto value = jerry_object(); + jerry_object_set_native_ptr(value, nullptr, exp->property); + return value; + } + case LottieProperty::Type::Position: { + auto value = jerry_object(); + auto pos = (*static_cast(exp->property))(frameNo); + auto val1 = jerry_number(pos.x); + auto val2 = jerry_number(pos.y); + jerry_object_set_index(value, 0, val1); + jerry_object_set_index(value, 1, val2); + jerry_value_free(val1); + jerry_value_free(val2); + return value; + } + default: { + TVGERR("LOTTIE", "Non supported type for value? = %d", (int) exp->type); + } + } + return jerry_undefined(); +} + + +static jerry_value_t _addsub(const jerry_value_t args[], float addsub) +{ + //1d + if (jerry_value_is_number(args[0])) return jerry_number(jerry_value_as_number(args[0]) + addsub * jerry_value_as_number(args[1])); + + //2d + auto val1 = jerry_object_get_index(args[0], 0); + auto val2 = jerry_object_get_index(args[0], 1); + auto val3 = jerry_object_get_index(args[1], 0); + auto val4 = jerry_object_get_index(args[1], 1); + auto x = jerry_value_as_number(val1) + addsub * jerry_value_as_number(val3); + auto y = jerry_value_as_number(val2) + addsub * jerry_value_as_number(val4); + + jerry_value_free(val1); + jerry_value_free(val2); + jerry_value_free(val3); + jerry_value_free(val4); + + auto obj = jerry_object(); + val1 = jerry_number(x); + val2 = jerry_number(y); + jerry_object_set_index(obj, 0, val1); + jerry_object_set_index(obj, 1, val2); + jerry_value_free(val1); + jerry_value_free(val2); + + return obj; +} + + +static jerry_value_t _muldiv(const jerry_value_t arg1, float arg2) +{ + //1d + if (jerry_value_is_number(arg1)) return jerry_number(jerry_value_as_number(arg1) * arg2); + + //2d + auto val1 = jerry_object_get_index(arg1, 0); + auto val2 = jerry_object_get_index(arg1, 1); + auto x = jerry_value_as_number(val1) * arg2; + auto y = jerry_value_as_number(val2) * arg2; + + jerry_value_free(val1); + jerry_value_free(val2); + + auto obj = jerry_object(); + val1 = jerry_number(x); + val2 = jerry_number(y); + jerry_object_set_index(obj, 0, val1); + jerry_object_set_index(obj, 1, val2); + jerry_value_free(val1); + jerry_value_free(val2); + + return obj; +} + + +static jerry_value_t _add(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + return _addsub(args, 1.0f); +} + + +static jerry_value_t _sub(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + return _addsub(args, -1.0f); +} + + +static jerry_value_t _mul(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + return _muldiv(args[0], jerry_value_as_number(args[1])); +} + + +static jerry_value_t _div(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + return _muldiv(args[0], 1.0f / jerry_value_as_number(args[1])); +} + + +static jerry_value_t _interp(float t, const jerry_value_t args[], int argsCnt) +{ + auto tMin = 0.0f; + auto tMax = 1.0f; + int idx = 0; + + if (argsCnt > 3) { + tMin = jerry_value_as_number(args[1]); + tMax = jerry_value_as_number(args[2]); + idx += 2; + } + + //2d + if (jerry_value_is_object(args[idx + 1]) && jerry_value_is_object(args[idx + 2])) { + auto val1 = jerry_object_get_index(args[0], 0); + auto val2 = jerry_object_get_index(args[0], 1); + auto val3 = jerry_object_get_index(args[1], 0); + auto val4 = jerry_object_get_index(args[1], 1); + + Point pt1 = {(float)jerry_value_as_number(val1), (float)jerry_value_as_number(val2)}; + Point pt2 = {(float)jerry_value_as_number(val3), (float)jerry_value_as_number(val4)}; + Point ret; + if (t <= tMin) ret = pt1; + else if (t >= tMax) ret = pt2; + else ret = mathLerp(pt1, pt2, t); + + jerry_value_free(val1); + jerry_value_free(val2); + jerry_value_free(val3); + jerry_value_free(val4); + + auto obj = jerry_object(); + val1 = jerry_number(ret.x); + val2 = jerry_number(ret.y); + jerry_object_set_index(obj, 0, val1); + jerry_object_set_index(obj, 1, val2); + jerry_value_free(val1); + jerry_value_free(val2); + + return obj; + } + + //1d + auto val1 = (float) jerry_value_as_number(args[idx + 1]); + if (t <= tMin) jerry_number(val1); + auto val2 = (float) jerry_value_as_number(args[idx + 2]); + if (t >= tMax) jerry_number(val2); + return jerry_number(mathLerp(val1, val2, t)); +} + + +static jerry_value_t _linear(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + auto t = (float) jerry_value_as_number(args[0]); + return _interp(t, args, jerry_value_as_uint32(argsCnt)); +} + + +static jerry_value_t _ease(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + auto t = (float) jerry_value_as_number(args[0]); + t = (t < 0.5) ? (4 * t * t * t) : (1.0f - pow(-2.0f * t + 2.0f, 3) * 0.5f); + return _interp(t, args, jerry_value_as_uint32(argsCnt)); +} + + + +static jerry_value_t _easeIn(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + auto t = (float) jerry_value_as_number(args[0]); + t = t * t * t; + return _interp(t, args, jerry_value_as_uint32(argsCnt)); +} + + +static jerry_value_t _easeOut(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + auto t = (float) jerry_value_as_number(args[0]); + t = 1.0f - pow(1.0f - t, 3); + return _interp(t, args, jerry_value_as_uint32(argsCnt)); +} + + +static jerry_value_t _clamp(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + auto num = jerry_value_as_number(args[0]); + auto limit1 = jerry_value_as_number(args[1]); + auto limit2 = jerry_value_as_number(args[2]); + + //clamping + if (num < limit1) num = limit1; + if (num > limit2) num = limit2; + + return jerry_number(num); +} + + +static jerry_value_t _dot(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + auto val1 = jerry_object_get_index(args[0], 0); + auto val2 = jerry_object_get_index(args[0], 1); + auto val3 = jerry_object_get_index(args[1], 0); + auto val4 = jerry_object_get_index(args[1], 1); + + auto x = jerry_value_as_number(val1) * jerry_value_as_number(val3); + auto y = jerry_value_as_number(val2) * jerry_value_as_number(val4); + + jerry_value_free(val1); + jerry_value_free(val2); + jerry_value_free(val3); + jerry_value_free(val4); + + return jerry_number(x + y); +} + + +static jerry_value_t _cross(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + auto val1 = jerry_object_get_index(args[0], 0); + auto val2 = jerry_object_get_index(args[0], 1); + auto val3 = jerry_object_get_index(args[1], 0); + auto val4 = jerry_object_get_index(args[1], 1); + + auto x = jerry_value_as_number(val1) * jerry_value_as_number(val4); + auto y = jerry_value_as_number(val2) * jerry_value_as_number(val3); + + jerry_value_free(val1); + jerry_value_free(val2); + jerry_value_free(val3); + jerry_value_free(val4); + + return jerry_number(x - y); +} + + +static jerry_value_t _normalize(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + auto val1 = jerry_object_get_index(args[0], 0); + auto val2 = jerry_object_get_index(args[0], 1); + auto x = jerry_value_as_number(val1); + auto y = jerry_value_as_number(val2); + + jerry_value_free(val1); + jerry_value_free(val2); + + auto length = sqrtf(x * x + y * y); + + x /= length; + y /= length; + + auto obj = jerry_object(); + val1 = jerry_number(x); + val2 = jerry_number(y); + jerry_object_set_index(obj, 0, val1); + jerry_object_set_index(obj, 0, val2); + jerry_value_free(val1); + jerry_value_free(val2); + + return obj; +} + + +static jerry_value_t _length(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + auto val1 = jerry_object_get_index(args[0], 0); + auto val2 = jerry_object_get_index(args[0], 1); + auto x = jerry_value_as_number(val1); + auto y = jerry_value_as_number(val2); + + jerry_value_free(val1); + jerry_value_free(val2); + + return jerry_number(sqrtf(x * x + y * y)); +} + + +static jerry_value_t _random(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + auto val = (float)(rand() % 10000001); + return jerry_number(val * 0.0000001f); +} + + +static jerry_value_t _deg2rad(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + return jerry_number(mathDeg2Rad((float)jerry_value_as_number(args[0]))); +} + + +static jerry_value_t _rad2deg(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + return jerry_number(mathRad2Deg((float)jerry_value_as_number(args[0]))); +} + + +static jerry_value_t _effect(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + TVGERR("LOTTIE", "effect is not supported in expressions!"); + + return jerry_undefined(); +} + + +static jerry_value_t _fromCompToSurface(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + TVGERR("LOTTIE", "fromCompToSurface is not supported in expressions!"); + + return jerry_undefined(); +} + + +static jerry_value_t _path(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + auto name = _name(args[0]); + + //find the a path property(sh) in the shape layer + auto group = static_cast(jerry_object_get_native_ptr(info->function, nullptr)); + auto path = group->content((char*)name); + free(name); + + if (!path) return jerry_undefined(); + + jerry_value_t pathset = jerry_object(); + jerry_object_set_native_ptr(pathset, nullptr, &static_cast(path)->pathset); + jerry_object_set_sz(pathset, "path", pathset); + + return pathset; +} + + +static jerry_value_t _shape(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + auto name = _name(args[0]); + + //find a shape layer(group) from the root + auto layer = static_cast(jerry_object_get_native_ptr(info->function, nullptr)); + auto group = static_cast(layer->content((char*)name)); + free(name); + + if (!group) return jerry_undefined(); + + auto property = jerry_function_external(_path); + + //attach a transform + for (auto c = group->children.begin(); c < group->children.end(); ++c) { + if ((*c)->type == LottieObject::Type::Transform) { + _buildTransform(property, static_cast(*c)); + break; + } + } + + jerry_object_set_native_ptr(property, nullptr, group); + jerry_object_set_sz(property, EXP_CONTENT, property); + + return property; +} + + +static jerry_value_t _layer(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + auto comp = static_cast(jerry_object_get_native_ptr(info->function, nullptr)); + LottieLayer* layer; + + //layer index + if (jerry_value_is_number(args[0])) { + auto idx = (uint16_t)jerry_value_as_int32(args[0]); + layer = comp->layer(idx); + jerry_value_free(idx); + //layer name + } else { + auto name = _name(args[0]); + layer = comp->layer((char*)name); + free(name); + } + + if (!layer) return jerry_undefined(); + + auto obj = jerry_object(); + jerry_object_set_native_ptr(obj, nullptr, layer); + _buildLayer(obj, layer, comp); + + return obj; +} + + +static jerry_value_t _nearestKey(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + auto exp = static_cast(jerry_object_get_native_ptr(info->function, nullptr)); + auto time = jerry_value_as_number(args[0]); + auto frameNo = exp->comp->frameAtTime(time); + auto index = jerry_number(exp->property->nearest(frameNo)); + + auto obj = jerry_object(); + jerry_object_set_sz(obj, EXP_INDEX, index); + jerry_value_free(index); + + return obj; +} + + +static jerry_value_t _valueAtTime(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + auto exp = static_cast(jerry_object_get_native_ptr(info->function, nullptr)); + auto time = jerry_value_as_number(args[0]); + auto frameNo = exp->comp->frameAtTime(time); + return _value(frameNo, exp); +} + + +static jerry_value_t _velocityAtTime(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + auto exp = static_cast(jerry_object_get_native_ptr(info->function, nullptr)); + auto time = jerry_value_as_number(args[0]); + auto frameNo = exp->comp->frameAtTime(time); + auto key = exp->property->nearest(frameNo); + auto pframe = exp->property->frameNo(key - 1); + auto cframe = exp->property->frameNo(key); + auto elapsed = (cframe - pframe) / (exp->comp->frameRate); + + Point cur, prv; + + //compute the velocity + switch (exp->type) { + case LottieProperty::Type::Point: { + prv = (*static_cast(exp->property))(pframe); + cur = (*static_cast(exp->property))(cframe); + break; + } + case LottieProperty::Type::Position: { + prv = (*static_cast(exp->property))(pframe); + cur = (*static_cast(exp->property))(cframe); + break; + } + default: { + TVGERR("LOTTIE", "Non supported type for velocityAtTime?"); + return jerry_undefined(); + } + } + + float velocity[] = {(cur.x - prv.x) / elapsed, (cur.y - prv.y) / elapsed}; + + auto obj = jerry_object(); + auto val1 = jerry_number(velocity[0]); + auto val2 = jerry_number(velocity[1]); + jerry_object_set_index(obj, 0, val1); + jerry_object_set_index(obj, 1, val2); + jerry_value_free(val1); + jerry_value_free(val2); + + return obj; +} + + +static jerry_value_t _speedAtTime(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + auto exp = static_cast(jerry_object_get_native_ptr(info->function, nullptr)); + auto time = jerry_value_as_number(args[0]); + auto frameNo = exp->comp->frameAtTime(time); + auto key = exp->property->nearest(frameNo); + auto pframe = exp->property->frameNo(key - 1); + auto cframe = exp->property->frameNo(key); + auto elapsed = (cframe - pframe) / (exp->comp->frameRate); + + Point cur, prv; + + //compute the velocity + switch (exp->type) { + case LottieProperty::Type::Point: { + prv = (*static_cast(exp->property))(pframe); + cur = (*static_cast(exp->property))(cframe); + break; + } + case LottieProperty::Type::Position: { + prv = (*static_cast(exp->property))(pframe); + cur = (*static_cast(exp->property))(cframe); + break; + } + default: { + TVGERR("LOTTIE", "Non supported type for speedAtTime?"); + return jerry_undefined(); + } + } + + auto speed = sqrtf(pow(cur.x - prv.x, 2) + pow(cur.y - prv.y, 2)) / elapsed; + auto obj = jerry_number(speed); + return obj; +} + + +static bool _loopOutCommon(LottieExpression* exp, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + exp->loop.mode = LottieExpression::LoopMode::OutCycle; + + if (argsCnt > 0) { + auto name = _name(args[0]); + if (!strcmp(name, EXP_CYCLE)) exp->loop.mode = LottieExpression::LoopMode::OutCycle; + else if (!strcmp(name, EXP_PINGPONG)) exp->loop.mode = LottieExpression::LoopMode::OutPingPong; + else if (!strcmp(name, EXP_OFFSET)) exp->loop.mode = LottieExpression::LoopMode::OutOffset; + else if (!strcmp(name, EXP_CONTINUE)) exp->loop.mode = LottieExpression::LoopMode::OutContinue; + free(name); + } + + if (exp->loop.mode != LottieExpression::LoopMode::OutCycle) { + TVGERR("hermet", "Not supported loopOut type = %d", exp->loop.mode); + return false; + } + + return true; +} + + +static jerry_value_t _loopOut(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + auto exp = static_cast(jerry_object_get_native_ptr(info->function, nullptr)); + + if (!_loopOutCommon(exp, args, argsCnt)) return jerry_undefined(); + + if (argsCnt > 1) exp->loop.key = jerry_value_as_int32(args[1]); + + auto obj = jerry_object(); + jerry_object_set_native_ptr(obj, nullptr, exp->property); + return obj; +} + + +static jerry_value_t _loopOutDuration(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + auto exp = static_cast(jerry_object_get_native_ptr(info->function, nullptr)); + + if (!_loopOutCommon(exp, args, argsCnt)) return jerry_undefined(); + + if (argsCnt > 1) { + exp->loop.in = exp->comp->frameAtTime((float)jerry_value_as_int32(args[1])); + } + + auto obj = jerry_object(); + jerry_object_set_native_ptr(obj, nullptr, exp->property); + return obj; +} + + +static bool _loopInCommon(LottieExpression* exp, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + exp->loop.mode = LottieExpression::LoopMode::InCycle; + + if (argsCnt > 0) { + auto name = _name(args[0]); + if (!strcmp(name, EXP_CYCLE)) exp->loop.mode = LottieExpression::LoopMode::InCycle; + else if (!strcmp(name, EXP_PINGPONG)) exp->loop.mode = LottieExpression::LoopMode::InPingPong; + else if (!strcmp(name, EXP_OFFSET)) exp->loop.mode = LottieExpression::LoopMode::InOffset; + else if (!strcmp(name, EXP_CONTINUE)) exp->loop.mode = LottieExpression::LoopMode::InContinue; + free(name); + } + + if (exp->loop.mode != LottieExpression::LoopMode::InCycle) { + TVGERR("hermet", "Not supported loopOut type = %d", exp->loop.mode); + return false; + } + + return true; +} + +static jerry_value_t _loopIn(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + auto exp = static_cast(jerry_object_get_native_ptr(info->function, nullptr)); + + if (!_loopInCommon(exp, args, argsCnt)) return jerry_undefined(); + + if (argsCnt > 1) { + exp->loop.in = exp->comp->frameAtTime((float)jerry_value_as_int32(args[1])); + } + + auto obj = jerry_object(); + jerry_object_set_native_ptr(obj, nullptr, exp->property); + return obj; +} + + +static jerry_value_t _loopInDuration(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + auto exp = static_cast(jerry_object_get_native_ptr(info->function, nullptr)); + + if (argsCnt > 1) { + exp->loop.in = exp->comp->frameAtTime((float)jerry_value_as_int32(args[1])); + } + + if (!_loopInCommon(exp, args, argsCnt)) return jerry_undefined(); + + auto obj = jerry_object(); + jerry_object_set_native_ptr(obj, nullptr, exp->property); + return obj; +} + + +static jerry_value_t _key(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + auto exp = static_cast(jerry_object_get_native_ptr(info->function, nullptr)); + auto key = jerry_value_as_int32(args[0]); + auto frameNo = exp->property->frameNo(key); + auto time = jerry_number(exp->comp->timeAtFrame(frameNo)); + auto value = _value(frameNo, exp); + + auto obj = jerry_object(); + jerry_object_set_sz(obj, EXP_TIME, time); + jerry_object_set_sz(obj, EXP_INDEX, args[0]); + jerry_object_set_sz(obj, EXP_VALUE, value); + + jerry_value_free(time); + jerry_value_free(value); + + return obj; +} + + +static jerry_value_t _toComp(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + TVGERR("LOTTIE", "toComp is not supported in expressions!"); + + return jerry_undefined(); +} + + +static jerry_value_t _createPath(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + //TODO: arg1: points, arg2: inTagents, arg3: outTangents, arg4: isClosed + auto arg1 = jerry_value_to_object(args[0]); + auto pathset = jerry_object_get_native_ptr(arg1, nullptr); + if (!pathset) { + TVGERR("LOTTIE", "failed createPath()"); + return jerry_undefined(); + } + + jerry_value_free(arg1); + + auto obj = jerry_object(); + jerry_object_set_native_ptr(obj, nullptr, pathset); + return obj; +} + + +static jerry_value_t _uniformPath(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + auto pathset = static_cast(jerry_object_get_native_ptr(info->function, nullptr)); + + /* TODO: ThorVG prebuilds the path data for performance. + It acutally need to constructs the Array for points, inTangents, outTangents and then return here... */ + auto obj = jerry_object(); + jerry_object_set_native_ptr(obj, nullptr, pathset); + return obj; +} + + +static jerry_value_t _isClosed(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + //TODO: Not used + return jerry_boolean(true); +} + + +static void _buildPath(jerry_value_t context, LottieExpression* exp) +{ + //Trick for fast buliding path. + auto points = jerry_function_external(_uniformPath); + jerry_object_set_native_ptr(points, nullptr, exp->property); + jerry_object_set_sz(context, "points", points); + jerry_value_free(points); + + auto inTangents = jerry_function_external(_uniformPath); + jerry_object_set_native_ptr(inTangents, nullptr, exp->property); + jerry_object_set_sz(context, "inTangents", inTangents); + jerry_value_free(inTangents); + + auto outTangents = jerry_function_external(_uniformPath); + jerry_object_set_native_ptr(outTangents, nullptr, exp->property); + jerry_object_set_sz(context, "outTangents", outTangents); + jerry_value_free(outTangents); + + auto isClosed = jerry_function_external(_isClosed); + jerry_object_set_native_ptr(isClosed, nullptr, exp->property); + jerry_object_set_sz(context, "isClosed", isClosed); + jerry_value_free(isClosed); + +} + + +static void _buildLayer(jerry_value_t context, LottieLayer* layer, LottieComposition* comp) +{ + auto width = jerry_number(layer->w); + jerry_object_set_sz(context, EXP_WIDTH, width); + jerry_value_free(width); + + auto height = jerry_number(layer->h); + jerry_object_set_sz(context, EXP_HEIGHT, height); + jerry_value_free(height); + + auto index = jerry_number(layer->id); + jerry_object_set_sz(context, EXP_INDEX, index); + jerry_value_free(index); + + auto parent = jerry_object(); + jerry_object_set_native_ptr(parent, nullptr, layer->parent); + jerry_object_set_sz(context, "parent", parent); + jerry_value_free(parent); + + auto hasParent = jerry_boolean(layer->parent ? true : false); + jerry_object_set_sz(context, "hasParent", hasParent); + jerry_value_free(hasParent); + + auto inPoint = jerry_number(layer->inFrame); + jerry_object_set_sz(context, "inPoint", inPoint); + jerry_value_free(inPoint); + + auto outPoint = jerry_number(layer->outFrame); + jerry_object_set_sz(context, "outPoint", outPoint); + jerry_value_free(outPoint); + + auto startTime = jerry_number(comp->timeAtFrame(layer->startFrame)); + jerry_object_set_sz(context, "startTime", startTime); + jerry_value_free(startTime); + + auto hasVideo = jerry_boolean(false); + jerry_object_set_sz(context, "hasVideo", hasVideo); + jerry_value_free(hasVideo); + + auto hasAudio = jerry_boolean(false); + jerry_object_set_sz(context, "hasAudio", hasAudio); + jerry_value_free(hasAudio); + + //active, #current in the animation range? + + auto enabled = jerry_boolean(!layer->hidden); + jerry_object_set_sz(context, "enabled", enabled); + jerry_value_free(enabled); + + auto audioActive = jerry_boolean(false); + jerry_object_set_sz(context, "audioActive", audioActive); + jerry_value_free(audioActive); + + //sampleImage(point, radius = [.5, .5], postEffect=true, t=time) + + _buildTransform(context, layer->transform); + + //audioLevels, #the value of the Audio Levels property of the layer in decibels + + auto timeRemap = jerry_object(); + jerry_object_set_native_ptr(timeRemap, nullptr, &layer->timeRemap); + jerry_object_set_sz(context, "timeRemap", timeRemap); + jerry_value_free(timeRemap); + + //marker.key(index) + //marker.key(name) + //marker.nearestKey(t) + //marker.numKeys + + auto name = jerry_string_sz(layer->name); + jerry_object_set_sz(context, EXP_NAME, name); + jerry_value_free(name); + + auto toComp = jerry_function_external(_toComp); + jerry_object_set_sz(context, "toComp", toComp); + jerry_object_set_native_ptr(toComp, nullptr, comp); + jerry_value_free(toComp); +} + + +static void _buildProperty(float frameNo, jerry_value_t context, LottieExpression* exp) +{ + auto value = _value(frameNo, exp); + jerry_object_set_sz(context, EXP_VALUE, value); + jerry_value_free(value); + + auto valueAtTime = jerry_function_external(_valueAtTime); + jerry_object_set_sz(context, "valueAtTime", valueAtTime); + jerry_object_set_native_ptr(valueAtTime, nullptr, exp); + jerry_value_free(valueAtTime); + + auto velocity = jerry_number(0.0f); + jerry_object_set_sz(context, "velocity", velocity); + jerry_value_free(velocity); + + auto velocityAtTime = jerry_function_external(_velocityAtTime); + jerry_object_set_sz(context, "velocityAtTime", velocityAtTime); + jerry_object_set_native_ptr(velocityAtTime, nullptr, exp); + jerry_value_free(velocityAtTime); + + auto speed = jerry_number(0.0f); + jerry_object_set_sz(context, "speed", speed); + jerry_value_free(speed); + + auto speedAtTime = jerry_function_external(_speedAtTime); + jerry_object_set_sz(context, "speedAtTime", speedAtTime); + jerry_object_set_native_ptr(speedAtTime, nullptr, exp); + jerry_value_free(speedAtTime); + + //wiggle(freq, amp, octaves=1, amp_mult=.5, t=time) + //temporalWiggle(freq, amp, octaves=1, amp_mult=.5, t=time) + //smooth(width=.2, samples=5, t=time) + + auto loopIn = jerry_function_external(_loopIn); + jerry_object_set_sz(context, "loopIn", loopIn); + jerry_object_set_native_ptr(loopIn, nullptr, exp); + jerry_value_free(loopIn); + + auto loopOut = jerry_function_external(_loopOut); + jerry_object_set_sz(context, "loopOut", loopOut); + jerry_object_set_native_ptr(loopOut, nullptr, exp); + jerry_value_free(loopOut); + + auto loopInDuration = jerry_function_external(_loopInDuration); + jerry_object_set_sz(context, "loopInDuration", loopInDuration); + jerry_object_set_native_ptr(loopInDuration, nullptr, exp); + jerry_value_free(loopInDuration); + + auto loopOutDuration = jerry_function_external(_loopOutDuration); + jerry_object_set_sz(context, "loopOutDuration", loopOutDuration); + jerry_object_set_native_ptr(loopOutDuration, nullptr, exp); + jerry_value_free(loopOutDuration); + + auto key = jerry_function_external(_key); + jerry_object_set_sz(context, "key", key); + jerry_object_set_native_ptr(key, nullptr, exp); + jerry_value_free(key); + + //key(markerName) + + auto nearestKey = jerry_function_external(_nearestKey); + jerry_object_set_native_ptr(nearestKey, nullptr, exp); + jerry_object_set_sz(context, "nearestKey", nearestKey); + jerry_value_free(nearestKey); + + auto numKeys = jerry_number(exp->property->frameCnt()); + jerry_object_set_sz(context, "numKeys", numKeys); + jerry_value_free(numKeys); + + //propertyGroup(countUp = 1) + //propertyIndex + //name + + //content("name"), #look for the named shape object from a layer + auto content = jerry_function_external(_shape); + jerry_object_set_sz(context, EXP_CONTENT, content); + jerry_object_set_native_ptr(content, nullptr, exp->layer); + jerry_value_free(content); +} + + +static jerry_value_t _comp(const jerry_call_info_t* info, const jerry_value_t args[], const jerry_length_t argsCnt) +{ + auto comp = static_cast(jerry_object_get_native_ptr(info->function, nullptr)); + LottieLayer* layer; + + auto arg0 = jerry_value_to_string(args[0]); + auto len = jerry_string_length(arg0); + auto name = (jerry_char_t*)alloca(len * sizeof(jerry_char_t) + 1); + jerry_string_to_buffer(arg0, JERRY_ENCODING_UTF8, name, len); + name[len] = '\0'; + + jerry_value_free(arg0); + + layer = comp->asset((char*)name); + + if (!layer) return jerry_undefined(); + + auto obj = jerry_object(); + jerry_object_set_native_ptr(obj, nullptr, layer); + _buildLayer(obj, layer, comp); + + return obj; +} + + +static void _buildMath(jerry_value_t context) +{ + auto bm_mul = jerry_function_external(_mul); + jerry_object_set_sz(context, "$bm_mul", bm_mul); + jerry_value_free(bm_mul); + + auto bm_sum = jerry_function_external(_add); + jerry_object_set_sz(context, "$bm_sum", bm_sum); + jerry_value_free(bm_sum); + + auto bm_add = jerry_function_external(_add); + jerry_object_set_sz(context, "$bm_add", bm_add); + jerry_value_free(bm_add); + + auto bm_sub = jerry_function_external(_sub); + jerry_object_set_sz(context, "$bm_sub", bm_sub); + jerry_value_free(bm_sub); + + auto bm_div = jerry_function_external(_div); + jerry_object_set_sz(context, "$bm_div", bm_div); + jerry_value_free(bm_div); + + auto mul = jerry_function_external(_mul); + jerry_object_set_sz(context, "mul", mul); + jerry_value_free(mul); + + auto sum = jerry_function_external(_add); + jerry_object_set_sz(context, "sum", sum); + jerry_value_free(sum); + + auto add = jerry_function_external(_add); + jerry_object_set_sz(context, "add", add); + jerry_value_free(add); + + auto sub = jerry_function_external(_sub); + jerry_object_set_sz(context, "sub", sub); + jerry_value_free(sub); + + auto div = jerry_function_external(_div); + jerry_object_set_sz(context, "div", div); + jerry_value_free(div); + + auto clamp = jerry_function_external(_clamp); + jerry_object_set_sz(context, "clamp", clamp); + jerry_value_free(clamp); + + auto dot = jerry_function_external(_dot); + jerry_object_set_sz(context, "dot", dot); + jerry_value_free(dot); + + auto cross = jerry_function_external(_cross); + jerry_object_set_sz(context, "cross", cross); + jerry_value_free(cross); + + auto normalize = jerry_function_external(_normalize); + jerry_object_set_sz(context, "normalize", normalize); + jerry_value_free(normalize); + + auto length = jerry_function_external(_length); + jerry_object_set_sz(context, "length", length); + jerry_value_free(length); + + auto random = jerry_function_external(_random); + jerry_object_set_sz(context, "random", random); + jerry_value_free(random); + + auto deg2rad = jerry_function_external(_deg2rad); + jerry_object_set_sz(context, "degreesToRadians", deg2rad); + jerry_value_free(deg2rad); + + auto rad2deg = jerry_function_external(_rad2deg); + jerry_object_set_sz(context, "radiansToDegrees", rad2deg); + jerry_value_free(rad2deg); + + auto linear = jerry_function_external(_linear); + jerry_object_set_sz(context, "linear", linear); + jerry_value_free(linear); + + auto ease = jerry_function_external(_ease); + jerry_object_set_sz(context, "ease", ease); + jerry_value_free(ease); + + auto easeIn = jerry_function_external(_easeIn); + jerry_object_set_sz(context, "easeIn", easeIn); + jerry_value_free(easeIn); + + auto easeOut = jerry_function_external(_easeOut); + jerry_object_set_sz(context, "easeOut", easeOut); + jerry_value_free(easeOut); + + //lookAt +} + + +void LottieExpressions::buildComp(LottieComposition* comp) +{ + jerry_object_set_native_ptr(this->comp, nullptr, comp); + jerry_object_set_native_ptr(thisComp, nullptr, comp); + jerry_object_set_native_ptr(layer, nullptr, comp); + + //marker + //marker.key(index) + //marker.key(name) + //marker.nearestKey(t) + //marker.numKeys + + auto numLayers = jerry_number(comp->root->children.count); + jerry_object_set_sz(thisComp, "numLayers", numLayers); + jerry_value_free(numLayers); + + //activeCamera + + auto width = jerry_number(comp->w); + jerry_object_set_sz(thisComp, EXP_WIDTH, width); + jerry_value_free(width); + + auto height = jerry_number(comp->h); + jerry_object_set_sz(thisComp, EXP_HEIGHT, height); + jerry_value_free(height); + + auto duration = jerry_number(comp->duration()); + jerry_object_set_sz(thisComp, "duration", duration); + jerry_value_free(duration); + + //ntscDropFrame + //displayStartTime + + auto frameDuration = jerry_number(1.0f / comp->frameRate); + jerry_object_set_sz(thisComp, "frameDuration", frameDuration); + jerry_value_free(frameDuration); + + //shutterAngle + //shutterPhase + //bgColor + //pixelAspect + + auto name = jerry_string((jerry_char_t*)comp->name, strlen(comp->name), JERRY_ENCODING_UTF8); + jerry_object_set_sz(thisComp, EXP_NAME, name); + jerry_value_free(name); +} + + +jerry_value_t LottieExpressions::buildGlobal() +{ + global = jerry_current_realm(); + + //comp(name) + comp = jerry_function_external(_comp); + jerry_object_set_sz(global, "comp", comp); + + //footage(name) + + thisComp = jerry_object(); + jerry_object_set_sz(global, "thisComp", thisComp); + + //layer(index) / layer(name) / layer(otherLayer, reIndex) + layer = jerry_function_external(_layer); + jerry_object_set_sz(thisComp, "layer", layer); + + thisLayer = jerry_object(); + jerry_object_set_sz(global, "thisLayer", thisLayer); + + thisProperty = jerry_object(); + jerry_object_set_sz(global, "thisProperty", thisProperty); + + auto effect = jerry_function_external(_effect); + jerry_object_set_sz(global, EXP_EFFECT, effect); + jerry_value_free(effect); + + auto fromCompToSurface = jerry_function_external(_fromCompToSurface); + jerry_object_set_sz(global, "fromCompToSurface", fromCompToSurface); + jerry_value_free(fromCompToSurface); + + auto createPath = jerry_function_external(_createPath); + jerry_object_set_sz(global, "createPath", createPath); + jerry_value_free(createPath); + + //posterizeTime(framesPerSecond) + //value + + return global; +} + + +jerry_value_t LottieExpressions::evaluate(float frameNo, LottieExpression* exp) +{ + buildComp(exp->comp); + + //update global context values + jerry_object_set_native_ptr(thisLayer, nullptr, exp->layer); + _buildLayer(thisLayer, exp->layer, exp->comp); + + jerry_object_set_native_ptr(thisProperty, nullptr, exp->property); + _buildProperty(frameNo, global, exp); + + if (exp->type == LottieProperty::Type::PathSet) _buildPath(thisProperty, exp); + if (exp->object->type == LottieObject::Transform) _buildTransform(global, static_cast(exp->object)); + + //evaluate the code + auto eval = jerry_eval((jerry_char_t *) exp->code, strlen(exp->code), JERRY_PARSE_NO_OPTS); + + if (jerry_value_is_undefined(eval)) TVGERR("LOTTIE", "Expression error"); + else jerry_value_free(eval); + + return jerry_object_get_sz(global, "$bm_rt"); +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +LottieExpressions::~LottieExpressions() +{ + jerry_value_free(thisProperty); + jerry_value_free(thisLayer); + jerry_value_free(layer); + jerry_value_free(thisComp); + jerry_value_free(comp); + jerry_value_free(global); + jerry_cleanup(); +} + + +LottieExpressions::LottieExpressions() +{ + jerry_init(JERRY_INIT_EMPTY); + _buildMath(buildGlobal()); +} + + +void LottieExpressions::update(float frameNo, float curTime) +{ + //time, #current time in seconds + auto time = jerry_number(curTime); + jerry_object_set_sz(global, EXP_TIME, time); + jerry_value_free(time); +} + + +//FIXME: Threads support +#include "tvgTaskScheduler.h" + +LottieExpressions* LottieExpressions::instance() +{ + static LottieExpressions* exps = nullptr; + + //FIXME: Threads support + if (TaskScheduler::threads() > 1) { + TVGLOG("LOTTIE", "Lottie Expressions are not supported with tvg threads"); + return nullptr; + } + + if (!exps) exps = new LottieExpressions; + ++engineRefCnt; + return exps; +} + + +void LottieExpressions::retrieve(LottieExpressions* instance) +{ + if (--engineRefCnt == 0) delete(instance); +} + + +#endif //THORVG_LOTTIE_EXPRESSIONS_SUPPORT \ No newline at end of file diff --git a/src/loaders/lottie/tvgLottieExpressions.h b/src/loaders/lottie/tvgLottieExpressions.h new file mode 100644 index 00000000..2fd0af8f --- /dev/null +++ b/src/loaders/lottie/tvgLottieExpressions.h @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2024 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _TVG_LOTTIE_EXPRESSIONS_H_ +#define _TVG_LOTTIE_EXPRESSIONS_H_ + +#include "tvgCommon.h" + +struct LottieExpression; +struct LottieComposition; +struct RGB24; + +#ifdef THORVG_LOTTIE_EXPRESSIONS_SUPPORT + +#include "jerryscript.h" + + +struct LottieExpressions +{ +public: + template + bool result(float frameNo, NumType& out, LottieExpression* exp) + { + auto success = true; + auto bm_rt = evaluate(frameNo, exp); + + if (auto prop = static_cast(jerry_object_get_native_ptr(bm_rt, nullptr))) { + out = (*prop)(frameNo); + } else if (jerry_value_is_number(bm_rt)) { + out = (NumType) jerry_value_as_number(bm_rt); + } else { + TVGERR("LOTTIE", "Failed dispatching a Value!"); + success = false; + } + jerry_value_free(bm_rt); + return success; + } + + template + bool result(float frameNo, Point& out, LottieExpression* exp) + { + auto success = true; + auto bm_rt = evaluate(frameNo, exp); + + if (jerry_value_is_object(bm_rt)) { + if (auto prop = static_cast(jerry_object_get_native_ptr(bm_rt, nullptr))) { + out = (*prop)(frameNo); + } else { + auto x = jerry_object_get_index(bm_rt, 0); + auto y = jerry_object_get_index(bm_rt, 1); + out.x = jerry_value_as_number(x); + out.y = jerry_value_as_number(y); + jerry_value_free(x); + jerry_value_free(y); + } + } else { + TVGERR("LOTTIE", "Failed dispatching Point!"); + success = false; + } + jerry_value_free(bm_rt); + return success; + } + + template + bool result(float frameNo, RGB24& out, LottieExpression* exp) + { + auto success = true; + auto bm_rt = evaluate(frameNo, exp); + + if (auto color = static_cast(jerry_object_get_native_ptr(bm_rt, nullptr))) { + out = (*color)(frameNo); + } else { + TVGERR("LOTTIE", "Failed dispatching Color!"); + success = false; + } + jerry_value_free(bm_rt); + return success; + } + + template + bool result(float frameNo, Fill* fill, LottieExpression* exp) + { + auto success = true; + auto bm_rt = evaluate(frameNo, exp); + + if (auto colorStop = static_cast(jerry_object_get_native_ptr(bm_rt, nullptr))) { + (*colorStop)(frameNo, fill, this); + } else { + TVGERR("LOTTIE", "Failed dispatching ColorStop!"); + success = false; + } + jerry_value_free(bm_rt); + return success; + } + + template + bool result(float frameNo, Array& cmds, Array& pts, Matrix* transform, LottieExpression* exp) + { + auto success = true; + auto bm_rt = evaluate(frameNo, exp); + + if (auto pathset = static_cast(jerry_object_get_native_ptr(bm_rt, nullptr))) { + (*pathset)(frameNo, cmds, pts, transform); + } else { + TVGERR("LOTTIE", "Failed dispatching PathSet!"); + success = false; + } + jerry_value_free(bm_rt); + return success; + } + + void update(float frameNo, float curTime); + + //singleton (no thread safety) + static LottieExpressions* instance(); + static void retrieve(LottieExpressions* instance); + +private: + LottieExpressions(); + ~LottieExpressions(); + + jerry_value_t evaluate(float frameNo, LottieExpression* exp); + jerry_value_t buildGlobal(); + void buildComp(LottieComposition* comp); + + //global object, attributes, methods + jerry_value_t global; + jerry_value_t comp; + jerry_value_t layer; + jerry_value_t thisComp; + jerry_value_t thisLayer; + jerry_value_t thisProperty; +}; + +#else + +struct LottieExpressions +{ + template bool result(TVG_UNUSED float, TVG_UNUSED NumType&, TVG_UNUSED LottieExpression*) { return false; } + template bool result(TVG_UNUSED float, TVG_UNUSED Point&, LottieExpression*) { return false; } + template bool result(TVG_UNUSED float, TVG_UNUSED RGB24&, TVG_UNUSED LottieExpression*) { return false; } + template bool result(TVG_UNUSED float, TVG_UNUSED Fill*, TVG_UNUSED LottieExpression*) { return false; } + template bool result(TVG_UNUSED float, TVG_UNUSED Array&, TVG_UNUSED Array&, TVG_UNUSED Matrix* transform, TVG_UNUSED LottieExpression*) { return false; } + void update(TVG_UNUSED float, TVG_UNUSED float) {} + static LottieExpressions* instance() { return nullptr; } + static void retrieve(TVG_UNUSED LottieExpressions* instance) {} +}; + +#endif //THORVG_LOTTIE_EXPRESSIONS_SUPPORT + +#endif //_TVG_LOTTIE_EXPRESSIONS_H_ \ No newline at end of file diff --git a/src/loaders/lottie/tvgLottieModel.cpp b/src/loaders/lottie/tvgLottieModel.cpp index 85352c02..20221a13 100644 --- a/src/loaders/lottie/tvgLottieModel.cpp +++ b/src/loaders/lottie/tvgLottieModel.cpp @@ -46,11 +46,11 @@ LottieImage::~LottieImage() } -void LottieTrimpath::segment(float frameNo, float& start, float& end) +void LottieTrimpath::segment(float frameNo, float& start, float& end, LottieExpressions* exps) { - auto s = this->start(frameNo) * 0.01f; - auto e = this->end(frameNo) * 0.01f; - auto o = fmodf(this->offset(frameNo), 360.0f) / 360.0f; //0 ~ 1 + auto s = this->start(frameNo, exps) * 0.01f; + auto e = this->end(frameNo, exps) * 0.01f; + auto o = fmodf(this->offset(frameNo, exps), 360.0f) / 360.0f; //0 ~ 1 auto diff = fabs(s - e); if (mathZero(diff)) { @@ -89,7 +89,7 @@ void LottieTrimpath::segment(float frameNo, float& start, float& end) } -Fill* LottieGradient::fill(float frameNo) +Fill* LottieGradient::fill(float frameNo, LottieExpressions* exps) { Fill* fill = nullptr; @@ -126,7 +126,7 @@ Fill* LottieGradient::fill(float frameNo) if (!fill) return nullptr; - colorStops(frameNo, fill); + colorStops(frameNo, fill, exps); return fill; } @@ -216,10 +216,10 @@ void LottieLayer::prepare() } -float LottieLayer::remap(float frameNo) +float LottieLayer::remap(float frameNo, LottieExpressions* exp) { if (timeRemap.frames || timeRemap.value) { - frameNo = comp->frameAtTime(timeRemap(frameNo)); + frameNo = comp->frameAtTime(timeRemap(frameNo, exp)); } else { frameNo -= startFrame; } diff --git a/src/loaders/lottie/tvgLottieModel.h b/src/loaders/lottie/tvgLottieModel.h index b6542fa3..7400a4a1 100644 --- a/src/loaders/lottie/tvgLottieModel.h +++ b/src/loaders/lottie/tvgLottieModel.h @@ -51,29 +51,23 @@ struct LottieStroke return dashattr->value[no]; } - float dashOffset(float frameNo) + float dashOffset(float frameNo, LottieExpressions* exps) { - return dash(0)(frameNo); + return dash(0)(frameNo, exps); } - float dashGap(float frameNo) + float dashGap(float frameNo, LottieExpressions* exps) { - return dash(2)(frameNo); + return dash(2)(frameNo, exps); } - float dashSize(float frameNo) + float dashSize(float frameNo, LottieExpressions* exps) { - auto d = dash(1)(frameNo); + auto d = dash(1)(frameNo, exps); if (d == 0.0f) return 0.1f; else return d; } - bool dynamic() - { - if (width.frames || dashattr) return true; - return false; - } - LottieFloat width = 0.0f; DashAttr* dashattr = nullptr; float miterLimit = 0; @@ -221,7 +215,7 @@ struct LottieTrimpath : LottieObject return false; } - void segment(float frameNo, float& start, float& end); + void segment(float frameNo, float& start, float& end, LottieExpressions* exps); LottieFloat start = 0.0f; LottieFloat end = 100.0f; @@ -489,7 +483,7 @@ struct LottieGradient : LottieObject return false; } - Fill* fill(float frameNo); + Fill* fill(float frameNo, LottieExpressions* exps); LottiePoint start = Point{0.0f, 0.0f}; LottiePoint end = Point{0.0f, 0.0f}; @@ -626,7 +620,7 @@ struct LottieLayer : LottieGroup bool mergeable() override { return false; } void prepare(); - float remap(float frameNo); + float remap(float frameNo, LottieExpressions* exp); struct { CompositeMethod type = CompositeMethod::None; @@ -823,6 +817,7 @@ struct LottieComposition Array fonts; Array slots; Array markers; + bool expressions = false; bool initiated = false; }; diff --git a/src/loaders/lottie/tvgLottieParser.cpp b/src/loaders/lottie/tvgLottieParser.cpp index d65ce4e9..ca1175ce 100644 --- a/src/loaders/lottie/tvgLottieParser.cpp +++ b/src/loaders/lottie/tvgLottieParser.cpp @@ -24,6 +24,7 @@ #include "tvgCompressor.h" #include "tvgLottieModel.h" #include "tvgLottieParser.h" +#include "tvgLottieExpressions.h" /************************************************************************/ @@ -33,6 +34,22 @@ #define KEY_AS(name) !strcmp(key, name) +static LottieExpression* _expression(char* code, LottieComposition* comp, LottieLayer* layer, LottieObject* object, LottieProperty* property, LottieProperty::Type type) +{ + if (!comp->expressions) comp->expressions = true; + + auto inst = new LottieExpression; + inst->code = code; + inst->comp = comp; + inst->layer = layer; + inst->object = object; + inst->property = property; + inst->type = type; + + return inst; +} + + static char* _int2str(int num) { char str[20]; @@ -486,7 +503,10 @@ void LottieParser::parseProperty(T& prop, LottieObject* obj) return; } comp->slots.push(new LottieSlot(sid, obj, type)); - } else skip(key); + } else if (!strcmp(key, "x")) { + prop.exp = _expression(getStringCopy(), comp, context.layer, context.parent, &prop, type); + } + else skip(key); } } @@ -552,6 +572,7 @@ LottieTransform* LottieParser::parseTransform(bool ddd) //check separateCoord to figure out whether "x(expression)" / "x(coord)" else if (transform->coords && KEY_AS("x")) parseProperty(transform->coords->x); else if (transform->coords && KEY_AS("y")) parseProperty(transform->coords->y); + else if (KEY_AS("x")) transform->position.exp = _expression(getStringCopy(), comp, context.layer, context.parent, &transform->position, LottieProperty::Type::Position); else skip(key); } } @@ -638,7 +659,7 @@ LottieSolidStroke* LottieParser::parseSolidStroke() } - void LottieParser::getPathSet(LottiePathSet& path) +void LottieParser::getPathSet(LottiePathSet& path) { enterObject(); while (auto key = nextObjectKey()) { @@ -649,6 +670,8 @@ LottieSolidStroke* LottieParser::parseSolidStroke() } else { getValue(path.value); } + } else if (!strcmp(key, "x")) { + path.exp = _expression(getStringCopy(), comp, context.layer, context.parent, &path, LottieProperty::Type::PathSet); } else skip(key); } } diff --git a/src/loaders/lottie/tvgLottieProperty.h b/src/loaders/lottie/tvgLottieProperty.h index 055c374d..f7aea077 100644 --- a/src/loaders/lottie/tvgLottieProperty.h +++ b/src/loaders/lottie/tvgLottieProperty.h @@ -28,6 +28,13 @@ #include "tvgMath.h" #include "tvgLines.h" #include "tvgLottieInterpolator.h" +#include "tvgLottieExpressions.h" + + +struct LottieFont; +struct LottieLayer; +struct LottieObject; + struct PathSet { @@ -51,8 +58,6 @@ struct ColorStop }; -struct LottieFont; - struct TextDocument { char* text = nullptr; @@ -93,35 +98,6 @@ static inline RGB24 operator*(const RGB24& lhs, float rhs) } -static void copy(PathSet& pathset, Array& outPts, Matrix* transform) -{ - Array inPts; - - if (transform) { - for (int i = 0; i < pathset.ptsCnt; ++i) { - Point pt = pathset.pts[i]; - mathMultiply(&pt, transform); - outPts.push(pt); - } - } else { - inPts.data = pathset.pts; - inPts.count = pathset.ptsCnt; - outPts.push(inPts); - inPts.data = nullptr; - } -} - - -static void copy(PathSet& pathset, Array& outCmds) -{ - Array inCmds; - inCmds.data = pathset.cmds; - inCmds.count = pathset.cmdsCnt; - outCmds.push(inCmds); - inCmds.data = nullptr; -} - - template struct LottieScalarFrame { @@ -192,8 +168,76 @@ struct LottieVectorFrame }; +//Property would have an either keyframes or single value. +struct LottieProperty +{ + enum class Type : uint8_t { Point = 0, Float, Opacity, Color, PathSet, ColorStop, Position, TextDoc, Invalid }; + virtual ~LottieProperty() {} + + LottieExpression* exp = nullptr; + + //TODO: Apply common bodies? + virtual uint32_t frameCnt() = 0; + virtual uint32_t nearest(float time) = 0; + virtual float frameNo(int32_t key) = 0; +}; + + +struct LottieExpression +{ + enum LoopMode : uint8_t { None = 0, InCycle = 1, InPingPong, InOffset, InContinue, OutCycle, OutPingPong, OutOffset, OutContinue }; + + char* code; + LottieComposition* comp; + LottieLayer* layer; + LottieObject* object; + LottieProperty* property; + LottieProperty::Type type; + + struct { + uint32_t key = 0; //the keyframe number repeating to + float in = FLT_MAX; //looping duration in frame number + LoopMode mode = None; + } loop; +; + ~LottieExpression() + { + free(code); + } +}; + + +static void _copy(PathSet& pathset, Array& outPts, Matrix* transform) +{ + Array inPts; + + if (transform) { + for (int i = 0; i < pathset.ptsCnt; ++i) { + Point pt = pathset.pts[i]; + mathMultiply(&pt, transform); + outPts.push(pt); + } + } else { + inPts.data = pathset.pts; + inPts.count = pathset.ptsCnt; + outPts.push(inPts); + inPts.data = nullptr; + } +} + + +static void _copy(PathSet& pathset, Array& outCmds) +{ + Array inCmds; + inCmds.data = pathset.cmds; + inCmds.count = pathset.cmdsCnt; + outCmds.push(inCmds); + inCmds.data = nullptr; +} + + template -uint32_t bsearch(T* frames, float frameNo) +uint32_t _bsearch(T* frames, float frameNo) { int32_t low = 0; int32_t high = int32_t(frames->count) - 1; @@ -206,16 +250,50 @@ uint32_t bsearch(T* frames, float frameNo) } if (high < low) low = high; if (low < 0) low = 0; - return low; } -struct LottieProperty +template +uint32_t _nearest(T* frames, float frameNo) { - enum class Type : uint8_t { Point = 0, Float, Opacity, Color, PathSet, ColorStop, Position, TextDoc, Invalid }; - virtual ~LottieProperty() {} -}; + if (frames) { + auto key = _bsearch(frames, frameNo); + if (key == frames->count - 1) return key; + return (fabsf(frames->data[key].no - frameNo) < fabsf(frames->data[key + 1].no - frameNo)) ? key : (key + 1); + } + return 0; +} + + +template +float _frameNo(T* frames, int32_t key) +{ + if (!frames) return 0.0f; + if (key < 0) key = 0; + if (key >= (int32_t) frames->count) key = (int32_t)(frames->count - 1); + return (*frames)[key].no; +} + + +template +float _loop(T* frames, float frameNo, LottieExpression* exp) +{ + if (frameNo >= exp->loop.in || frameNo < frames->first().no ||frameNo < frames->last().no) return frameNo; + + switch (exp->loop.mode) { + case LottieExpression::LoopMode::InCycle: { + frameNo -= frames->first().no; + return fmodf(frameNo, frames->last().no - frames->first().no) + (*frames)[exp->loop.key].no; + } + case LottieExpression::LoopMode::OutCycle: { + frameNo -= frames->first().no; + return fmodf(frameNo, (*frames)[frames->count - 1 - exp->loop.key].no - frames->first().no) + frames->first().no; + } + default: break; + } + return frameNo; +} template @@ -236,6 +314,26 @@ struct LottieGenericProperty : LottieProperty void release() { delete(frames); + frames = nullptr; + if (exp) { + delete(exp); + exp = nullptr; + } + } + + uint32_t nearest(float frameNo) override + { + return _nearest(frames, frameNo); + } + + uint32_t frameCnt() override + { + return frames ? frames->count : 1; + } + + float frameNo(int32_t key) override + { + return _frameNo(frames, key); } LottieScalarFrame& newFrame() @@ -261,11 +359,21 @@ struct LottieGenericProperty : LottieProperty if (frames->count == 1 || frameNo <= frames->first().no) return frames->first().value; if (frameNo >= frames->last().no) return frames->last().value; - auto frame = frames->data + bsearch(frames, frameNo); + auto frame = frames->data + _bsearch(frames, frameNo); if (frame->no == frameNo) return frame->value; return frame->interpolate(frame + 1, frameNo); } + T operator()(float frameNo, LottieExpressions* exps) + { + T out{}; + if (exps && exp) { + if (exp->loop.mode != LottieExpression::LoopMode::None) frameNo = _loop(frames, frameNo, exp); + if (exps->result>(frameNo, out, exp)) return out; + } + return operator()(frameNo); + } + T& operator=(const T& other) { //shallow copy, used for slot overriding @@ -293,6 +401,11 @@ struct LottiePathSet : LottieProperty void release() { + if (exp) { + delete(exp); + exp = nullptr; + } + free(value.cmds); free(value.pts); @@ -306,6 +419,21 @@ struct LottiePathSet : LottieProperty free(frames); } + uint32_t nearest(float frameNo) override + { + return _nearest(frames, frameNo); + } + + uint32_t frameCnt() override + { + return frames ? frames->count : 1; + } + + float frameNo(int32_t key) override + { + return _frameNo(frames, key); + } + LottieScalarFrame& newFrame() { if (!frames) { @@ -325,43 +453,43 @@ struct LottiePathSet : LottieProperty return (*frames)[frames->count]; } - bool operator()(float frameNo, Array& cmds, Array& pts, Matrix* transform = nullptr) + bool operator()(float frameNo, Array& cmds, Array& pts, Matrix* transform) { if (!frames) { - copy(value, cmds); - copy(value, pts, transform); + _copy(value, cmds); + _copy(value, pts, transform); return true; } if (frames->count == 1 || frameNo <= frames->first().no) { - copy(frames->first().value, cmds); - copy(frames->first().value, pts, transform); + _copy(frames->first().value, cmds); + _copy(frames->first().value, pts, transform); return true; } if (frameNo >= frames->last().no) { - copy(frames->last().value, cmds); - copy(frames->last().value, pts, transform); + _copy(frames->last().value, cmds); + _copy(frames->last().value, pts, transform); return true; } - auto frame = frames->data + bsearch(frames, frameNo); + auto frame = frames->data + _bsearch(frames, frameNo); if (frame->no == frameNo) { - copy(frame->value, cmds); - copy(frame->value, pts, transform); + _copy(frame->value, cmds); + _copy(frame->value, pts, transform); return true; } //interpolate - copy(frame->value, cmds); + _copy(frame->value, cmds); auto t = (frameNo - frame->no) / ((frame + 1)->no - frame->no); if (frame->interpolator) t = frame->interpolator->progress(t); if (frame->hold) { - if (t < 1.0f) copy(frame->value, pts, transform); - else copy((frame + 1)->value, pts, transform); + if (t < 1.0f) _copy(frame->value, pts, transform); + else _copy((frame + 1)->value, pts, transform); return true; } @@ -376,6 +504,16 @@ struct LottiePathSet : LottieProperty return true; } + + bool operator()(float frameNo, Array& cmds, Array& pts, Matrix* transform, LottieExpressions* exps) + { + if (exps && exp) { + if (exp->loop.mode != LottieExpression::LoopMode::None) frameNo = _loop(frames, frameNo, exp); + if (exps->result(frameNo, cmds, pts, transform, exp)) return true; + } + return operator()(frameNo, cmds, pts, transform); + } + void prepare() {} }; @@ -394,6 +532,11 @@ struct LottieColorStop : LottieProperty void release() { + if (exp) { + delete(exp); + exp = nullptr; + } + if (value.data) { free(value.data); value.data = nullptr; @@ -409,6 +552,21 @@ struct LottieColorStop : LottieProperty frames = nullptr; } + uint32_t nearest(float frameNo) override + { + return _nearest(frames, frameNo); + } + + uint32_t frameCnt() override + { + return frames ? frames->count : 1; + } + + float frameNo(int32_t key) override + { + return _frameNo(frames, key); + } + LottieScalarFrame& newFrame() { if (!frames) { @@ -428,27 +586,26 @@ struct LottieColorStop : LottieProperty return (*frames)[frames->count]; } - void operator()(float frameNo, Fill* fill) + Result operator()(float frameNo, Fill* fill, LottieExpressions* exps) { - if (!frames) { - fill->colorStops(value.data, count); - return; + if (exps && exp) { + if (exp->loop.mode != LottieExpression::LoopMode::None) frameNo = _loop(frames, frameNo, exp); + if (exps->result(frameNo, fill, exp)) return Result::Success; } + if (!frames) return fill->colorStops(value.data, count); + if (frames->count == 1 || frameNo <= frames->first().no) { - fill->colorStops(frames->first().value.data, count); - return; + return fill->colorStops(frames->first().value.data, count); } if (frameNo >= frames->last().no) { - fill->colorStops(frames->last().value.data, count); - return; + return fill->colorStops(frames->last().value.data, count); } - auto frame = frames->data + bsearch(frames, frameNo); + auto frame = frames->data + _bsearch(frames, frameNo); if (frame->no == frameNo) { - fill->colorStops(frame->value.data, count); - return; + return fill->colorStops(frame->value.data, count); } //interpolate @@ -473,7 +630,7 @@ struct LottieColorStop : LottieProperty auto a = mathLerp(s->a, e->a, t); result.push({offset, r, g, b, a}); } - fill->colorStops(result.data, count); + return fill->colorStops(result.data, count); } LottieColorStop& operator=(const LottieColorStop& other) @@ -513,6 +670,27 @@ struct LottiePosition : LottieProperty void release() { delete(frames); + frames = nullptr; + + if (exp) { + delete(exp); + exp = nullptr; + } + } + + uint32_t nearest(float frameNo) override + { + return _nearest(frames, frameNo); + } + + uint32_t frameCnt() override + { + return frames ? frames->count : 1; + } + + float frameNo(int32_t key) override + { + return _frameNo(frames, key); } LottieVectorFrame& newFrame() @@ -538,18 +716,28 @@ struct LottiePosition : LottieProperty if (frames->count == 1 || frameNo <= frames->first().no) return frames->first().value; if (frameNo >= frames->last().no) return frames->last().value; - auto frame = frames->data + bsearch(frames, frameNo); + auto frame = frames->data + _bsearch(frames, frameNo); if (frame->no == frameNo) return frame->value; return frame->interpolate(frame + 1, frameNo); } + Point operator()(float frameNo, LottieExpressions* exps) + { + Point out{}; + if (exps && exp) { + if (exp->loop.mode != LottieExpression::LoopMode::None) frameNo = _loop(frames, frameNo, exp); + if (exps->result(frameNo, out, exp)) return out; + } + return operator()(frameNo); + } + float angle(float frameNo) { if (!frames) return 0; if (frames->count == 1 || frameNo <= frames->first().no) return 0; if (frameNo >= frames->last().no) return 0; - auto frame = frames->data + bsearch(frames, frameNo); + auto frame = frames->data + _bsearch(frames, frameNo); return frame->angle(frame + 1, frameNo); } @@ -575,6 +763,11 @@ struct LottieTextDoc : LottieProperty void release() { + if (exp) { + delete(exp); + exp = nullptr; + } + if (value.text) { free(value.text); value.text = nullptr; @@ -594,6 +787,21 @@ struct LottieTextDoc : LottieProperty frames = nullptr; } + uint32_t nearest(float frameNo) override + { + return _nearest(frames, frameNo); + } + + uint32_t frameCnt() override + { + return frames ? frames->count : 1; + } + + float frameNo(int32_t key) override + { + return _frameNo(frames, key); + } + LottieScalarFrame& newFrame() { if (!frames) frames = new Array>; @@ -617,7 +825,7 @@ struct LottieTextDoc : LottieProperty if (frames->count == 1 || frameNo <= frames->first().no) return frames->first().value; if (frameNo >= frames->last().no) return frames->last().value; - auto frame = frames->data + bsearch(frames, frameNo); + auto frame = frames->data + _bsearch(frames, frameNo); return frame->value; }