lottie: suppport writable expressions

This function sets the value of a specified expression variable within a particular layer.
It is useful for dynamically changing the properties of a layer at runtime.

Experimental API
- Result LottieAnimation::assign(const char* layer, uint32_t ix, const char* variable, float value)
- Tvg_Result tvg_lottie_animation_assign(Tvg_Animation* animation, const char* layer, uint32_t ix, const char* var, float val)
This commit is contained in:
Hermet Park 2025-02-28 00:36:55 +09:00 committed by Hermet Park
parent 4c5ce9862e
commit 5de098f128
12 changed files with 148 additions and 1 deletions

View file

@ -2569,6 +2569,24 @@ TVG_API Tvg_Result tvg_lottie_animation_get_marker(Tvg_Animation* animation, uin
TVG_API Tvg_Result tvg_lottie_animation_tween(Tvg_Animation* animation, float from, float to, float progress); TVG_API Tvg_Result tvg_lottie_animation_tween(Tvg_Animation* animation, float from, float to, float progress);
/*!
* \brief Updates the value of an expression variable for a specific layer.
*
* \param[in] animation The Tvg_Animation pointer to the Lottie animation object.
* \param[in] layer The name of the layer containing the variable to be updated.
* \param[in] ix The property index of the variable within the layer.
* \param[in] var The name of the variable to be updated.
* \param[in] val The new value to assign to the variable.
*
* \return Tvg_Result enumeration.
* \retval TVG_RESULT_INSUFFICIENT_CONDITION If the animation is not loaded.
* \retval TVG_RESULT_INVALID_ARGUMENT When the given parameter is invalid.
* \retval TVG_RESULT_NOT_SUPPORTED When neither the layer nor the property is found in the current animation.
*
* \note Experimental API
*/
TVG_API Tvg_Result tvg_lottie_animation_assign(Tvg_Animation* animation, const char* layer, uint32_t ix, const char* var, float val);
/** \} */ // end addtogroup ThorVGCapi_LottieAnimation /** \} */ // end addtogroup ThorVGCapi_LottieAnimation

View file

@ -974,6 +974,16 @@ TVG_API Tvg_Result tvg_lottie_animation_tween(Tvg_Animation* animation, float fr
return TVG_RESULT_NOT_SUPPORTED; return TVG_RESULT_NOT_SUPPORTED;
} }
TVG_API Tvg_Result tvg_lottie_animation_assign(Tvg_Animation* animation, const char* layer, uint32_t ix, const char* var, float val)
{
#ifdef THORVG_LOTTIE_LOADER_SUPPORT
if (!animation) return TVG_RESULT_INVALID_ARGUMENT;
return (Tvg_Result) reinterpret_cast<LottieAnimation*>(animation)->assign(layer, ix, var, val);
#endif
return TVG_RESULT_NOT_SUPPORTED;
}
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View file

@ -89,6 +89,25 @@ public:
*/ */
const char* marker(uint32_t idx) noexcept; const char* marker(uint32_t idx) noexcept;
/**
* @brief Updates the value of an expression variable for a specific layer.
*
* This function sets the value of a specified expression variable within a particular layer.
* It is useful for dynamically changing the properties of a layer at runtime.
*
* @param[in] layer The name of the layer containing the variable to be updated.
* @param[in] ix The property index of the variable within the layer.
* @param[in] var The name of the variable to be updated.
* @param[in] val The new value to assign to the variable.
*
* @retval Result::InsufficientCondition If the animation is not loaded.
* @retval Result::InvalidArguments When the given parameter is invalid.
* @retval Result::NonSupport When neither the layer nor the property is found in the current animation.
*
* @note Experimental API
*/
Result assign(const char* layer, uint32_t ix, const char* var, float val);
/** /**
* @brief Creates a new LottieAnimation object. * @brief Creates a new LottieAnimation object.
* *

View file

@ -84,6 +84,18 @@ const char* LottieAnimation::marker(uint32_t idx) noexcept
} }
Result LottieAnimation::assign(const char* layer, uint32_t ix, const char* var, float val)
{
if (!layer || !var) return Result::InvalidArguments;
auto loader = PICTURE(pImpl->picture)->loader;
if (!loader) return Result::InsufficientCondition;
if (static_cast<LottieLoader*>(loader)->assign(layer, ix, var, val)) return Result::Success;
return Result::NonSupport;
}
LottieAnimation* LottieAnimation::gen() noexcept LottieAnimation* LottieAnimation::gen() noexcept
{ {
return new LottieAnimation; return new LottieAnimation;

View file

@ -1326,9 +1326,20 @@ jerry_value_t LottieExpressions::buildGlobal()
} }
void LottieExpressions::buildWritables(LottieExpression* exp)
{
if (exp->writables.empty()) return;
ARRAY_FOREACH(p, exp->writables) {
auto writable = jerry_number(p->val);
jerry_object_set_sz(global, p->var, writable);
jerry_value_free(writable);
}
}
jerry_value_t LottieExpressions::evaluate(float frameNo, LottieExpression* exp) jerry_value_t LottieExpressions::evaluate(float frameNo, LottieExpression* exp)
{ {
if (exp->disabled) return jerry_undefined(); if (exp->disabled && exp->writables.empty()) return jerry_undefined();
buildGlobal(exp); buildGlobal(exp);
@ -1352,6 +1363,9 @@ jerry_value_t LottieExpressions::evaluate(float frameNo, LottieExpression* exp)
//expansions per object type //expansions per object type
if (exp->object->type == LottieObject::Transform) _buildTransform(global, frameNo, static_cast<LottieTransform*>(exp->object)); if (exp->object->type == LottieObject::Transform) _buildTransform(global, frameNo, static_cast<LottieTransform*>(exp->object));
//update writable values
buildWritables(exp);
//evaluate the code //evaluate the code
auto eval = jerry_eval((jerry_char_t *) exp->code, strlen(exp->code), JERRY_PARSE_NO_OPTS); auto eval = jerry_eval((jerry_char_t *) exp->code, strlen(exp->code), JERRY_PARSE_NO_OPTS);

View file

@ -139,6 +139,7 @@ private:
void buildComp(LottieComposition* comp, float frameNo, LottieExpression* exp); void buildComp(LottieComposition* comp, float frameNo, LottieExpression* exp);
void buildComp(jerry_value_t context, float frameNo, LottieLayer* comp, LottieExpression* exp); void buildComp(jerry_value_t context, float frameNo, LottieLayer* comp, LottieExpression* exp);
void buildGlobal(LottieExpression* exp); void buildGlobal(LottieExpression* exp);
void buildWritables(LottieExpression* exp);
//global object, attributes, methods //global object, attributes, methods
jerry_value_t global; jerry_value_t global;

View file

@ -458,3 +458,12 @@ bool LottieLoader::tween(float from, float to, float progress)
return true; return true;
} }
bool LottieLoader::assign(const char* layer, uint32_t ix, const char* var, float val)
{
if (!ready() || !comp->expressions) return false;
comp->root->assign(layer, ix, var, val);
return true;
}

View file

@ -73,6 +73,7 @@ public:
float shorten(float frameNo); //Reduce the accuracy for performance float shorten(float frameNo); //Reduce the accuracy for performance
bool tween(float from, float to, float progress); bool tween(float from, float to, float progress);
bool assign(const char* layer, uint32_t ix, const char* var, float val);
private: private:
bool ready(); bool ready();

View file

@ -23,6 +23,7 @@
#include "tvgMath.h" #include "tvgMath.h"
#include "tvgTaskScheduler.h" #include "tvgTaskScheduler.h"
#include "tvgLottieModel.h" #include "tvgLottieModel.h"
#include "tvgCompressor.h"
/************************************************************************/ /************************************************************************/
@ -531,6 +532,17 @@ LottieGroup::LottieGroup()
} }
LottieProperty* LottieGroup::property(uint16_t ix)
{
ARRAY_FOREACH(p, children) {
auto child = static_cast<LottieObject*>(*p);
if (auto property = child->property(ix)) return property;
}
return nullptr;
}
void LottieGroup::prepare(LottieObject::Type type) void LottieGroup::prepare(LottieObject::Type type)
{ {
LottieObject::type = type; LottieObject::type = type;
@ -617,6 +629,16 @@ LottieLayer::~LottieLayer()
} }
LottieProperty* LottieLayer::property(uint16_t ix)
{
if (transform) {
if (auto property = transform->property(ix)) return property;
}
return LottieGroup::property(ix);
}
void LottieLayer::prepare(RGB24* color) void LottieLayer::prepare(RGB24* color)
{ {
/* if layer is hidden, only useful data is its transform matrix. /* if layer is hidden, only useful data is its transform matrix.
@ -658,6 +680,20 @@ float LottieLayer::remap(LottieComposition* comp, float frameNo, LottieExpressio
} }
bool LottieLayer::assign(const char* layer, uint32_t ix, const char* var, float val)
{
//find the target layer by name
auto target = layerById(djb2Encode(layer));
if (!target) return false;
//find the target property by ix
auto property = target->property(ix);
if (property && property->exp) return property->exp->assign(var, val);
return false;
}
LottieComposition::~LottieComposition() LottieComposition::~LottieComposition()
{ {
if (!initiated && root) delete(root->scene); if (!initiated && root) delete(root->scene);

View file

@ -845,6 +845,7 @@ struct LottieGroup : LottieObject, LottieRenderPooler<tvg::Shape>
void prepare(LottieObject::Type type = LottieObject::Group); void prepare(LottieObject::Type type = LottieObject::Group);
bool mergeable() override { return allowMerge; } bool mergeable() override { return allowMerge; }
LottieProperty* property(uint16_t ix);
LottieObject* content(unsigned long id) LottieObject* content(unsigned long id)
{ {
@ -880,6 +881,8 @@ struct LottieLayer : LottieGroup
bool mergeable() override { return false; } bool mergeable() override { return false; }
void prepare(RGB24* color = nullptr); void prepare(RGB24* color = nullptr);
float remap(LottieComposition* comp, float frameNo, LottieExpressions* exp); float remap(LottieComposition* comp, float frameNo, LottieExpressions* exp);
LottieProperty* property(uint16_t ix);
bool assign(const char* layer, uint32_t ix, const char* var, float val);
char* name = nullptr; char* name = nullptr;
LottieLayer* parent = nullptr; LottieLayer* parent = nullptr;

View file

@ -573,6 +573,7 @@ LottieTransform* LottieParser::parseTransform(bool ddd)
else if (transform->coords && KEY_AS("y")) parseProperty<LottieProperty::Type::Float>(transform->coords->y); else if (transform->coords && KEY_AS("y")) parseProperty<LottieProperty::Type::Float>(transform->coords->y);
else if (KEY_AS("x") && expressions) transform->position.exp = getExpression(getStringCopy(), comp, context.layer, context.parent, &transform->position); else if (KEY_AS("x") && expressions) transform->position.exp = getExpression(getStringCopy(), comp, context.layer, context.parent, &transform->position);
else if (KEY_AS("sid")) registerSlot<LottieProperty::Type::Position>(transform, getString()); else if (KEY_AS("sid")) registerSlot<LottieProperty::Type::Position>(transform, getString());
else if (KEY_AS("ix")) transform->position.ix = getInt();
else skip(); else skip();
} }
transform->position.type = LottieProperty::Type::Position; transform->position.type = LottieProperty::Type::Position;

View file

@ -128,11 +128,18 @@ struct LottieExpression
{ {
enum LoopMode : uint8_t { None = 0, InCycle = 1, InPingPong, InOffset, InContinue, OutCycle, OutPingPong, OutOffset, OutContinue }; enum LoopMode : uint8_t { None = 0, InCycle = 1, InPingPong, InOffset, InContinue, OutCycle, OutPingPong, OutOffset, OutContinue };
//writable expressions variable name and value.
struct Writable {
char* var;
float val;
};
char* code; char* code;
LottieComposition* comp; LottieComposition* comp;
LottieLayer* layer; LottieLayer* layer;
LottieObject* object; LottieObject* object;
LottieProperty* property; LottieProperty* property;
Array<Writable> writables;
bool disabled = false; bool disabled = false;
struct { struct {
@ -155,8 +162,24 @@ struct LottieExpression
~LottieExpression() ~LottieExpression()
{ {
ARRAY_FOREACH(p, writables) {
tvg::free(p->var);
}
tvg::free(code); tvg::free(code);
} }
bool assign(const char* var, float val)
{
//overwrite the existing value
ARRAY_FOREACH(p, writables) {
if (tvg::equal(var, p->var)) {
p->val = val;
return true;
}
}
writables.push({tvg::duplicate(var), val});
return true;
}
}; };