lottie/slot: Introduce reusable Slot API

Introduce Slot APIs for efficient slot data reuse

New APIs:
- uint32_t LottieAnimation::gen(const char* slots)
- Result LottieAnimation::apply(uint32_t id)
- Result LottieAnimation::del(uint32_t id)

Removed API:
- Result override(const char* slot)
This commit is contained in:
Jinny You 2025-04-17 16:59:27 +09:00
parent 16604a873a
commit 549cb4033c
8 changed files with 247 additions and 62 deletions

View file

@ -22,17 +22,6 @@ class TVG_API LottieAnimation final : public Animation
public: public:
~LottieAnimation() override; ~LottieAnimation() override;
/**
* @brief Override Lottie properties using slot data.
*
* @param[in] slot The Lottie slot data in JSON format to override, or @c nullptr to reset.
*
* @retval Result::InsufficientCondition In case the animation is not loaded.
*
* @since 1.0
*/
Result override(const char* slot) noexcept;
/** /**
* @brief Specifies a segment by marker. * @brief Specifies a segment by marker.
* *
@ -110,6 +99,56 @@ public:
*/ */
Result assign(const char* layer, uint32_t ix, const char* var, float val); Result assign(const char* layer, uint32_t ix, const char* var, float val);
/**
* @brief Creates a new slot based on the given Lottie slot data.
*
* This function parses the provided JSON-formatted slot data and generates
* a new slot for animation control. The returned slot ID can be used to apply
* or delete the slot later.
*
* @param[in] slot A JSON string representing the Lottie slot data.
*
* @return A unique, non-zero slot ID on success. Returns @c 0 if the slot generation fails.
*
* @see apply(uint32_t id)
* @see del(uint32_t id)
*
* @since 1.0
*/
uint32_t gen(const char* slot) noexcept;
/**
* @brief Applies a previously generated slot to the animation.
*
* This function applies the animation parameters defined by a slot.
* If the provided slot ID is 0, all previously applied slots will be reset.
*
* @param[in] id The ID of the slot to apply. Use 0 to reset all slots.
*
* @retval Result::InvalidArguments If the animation is not loaded or the slot ID is invalid.
*
* @see gen(const char* slot)
*
* @since 1.0
*/
Result apply(uint32_t id) noexcept;
/**
* @brief Deletes a previously generated slot.
*
* This function removes a slot by its ID.
*
* @param[in] id The ID of the slot to delete. Retrieve the ID from gen().
*
* @retval Result::InvalidArguments If the animation is not loaded or the slot ID is invalid.
*
* @note This function should be paired with gen.
* @see gen(const char* slot)
*
* @since 1.0
*/
Result del(uint32_t id) noexcept;
/** /**
* @brief Creates a new LottieAnimation object. * @brief Creates a new LottieAnimation object.
* *

View file

@ -30,15 +30,39 @@ LottieAnimation::LottieAnimation() = default;
LottieAnimation::~LottieAnimation() = default; LottieAnimation::~LottieAnimation() = default;
Result LottieAnimation::override(const char* slot) noexcept uint32_t LottieAnimation::gen(const char* slot) noexcept
{
auto loader = PICTURE(pImpl->picture)->loader;
if (!loader) return 0;
return static_cast<LottieLoader*>(loader)->gen(slot);
}
Result LottieAnimation::apply(uint32_t id) noexcept
{ {
auto loader = PICTURE(pImpl->picture)->loader; auto loader = PICTURE(pImpl->picture)->loader;
if (!loader) return Result::InsufficientCondition; if (!loader) return Result::InsufficientCondition;
if (static_cast<LottieLoader*>(loader)->override(slot)) { if (static_cast<LottieLoader*>(loader)->apply(id)) {
PAINT(pImpl->picture)->mark(RenderUpdateFlag::All); PAINT(pImpl->picture)->mark(RenderUpdateFlag::All);
return Result::Success; return Result::Success;
} }
return Result::InvalidArguments;
}
Result LottieAnimation::del(uint32_t id) noexcept
{
auto loader = PICTURE(pImpl->picture)->loader;
if (!loader) return Result::InsufficientCondition;
if (static_cast<LottieLoader*>(loader)->del(id)) {
PAINT(pImpl->picture)->mark(RenderUpdateFlag::All);
return Result::Success;
}
return Result::InvalidArguments; return Result::InvalidArguments;
} }

View file

@ -25,6 +25,7 @@
#include "tvgLottieModel.h" #include "tvgLottieModel.h"
#include "tvgLottieParser.h" #include "tvgLottieParser.h"
#include "tvgLottieBuilder.h" #include "tvgLottieBuilder.h"
#include "tvgCompressor.h"
/************************************************************************/ /************************************************************************/
/* Internal Class Implementation */ /* Internal Class Implementation */
@ -44,7 +45,9 @@ void LottieLoader::run(unsigned tid)
comp = parser.comp; comp = parser.comp;
} }
if (parser.slots) { if (parser.slots) {
override(parser.slots, true); auto slotcode = gen(parser.slots, true);
apply(slotcode, true);
del(slotcode, true);
parser.slots = nullptr; parser.slots = nullptr;
} }
builder->build(comp); builder->build(comp);
@ -288,45 +291,98 @@ Paint* LottieLoader::paint()
} }
bool LottieLoader::override(const char* slots, bool byDefault) bool LottieLoader::apply(uint32_t slotcode, bool byDefault)
{ {
if (!ready() || comp->slots.count == 0) return false; if (!ready() || comp->slots.count == 0) return false;
//override slots bool applied = false;
if (slots) { ARRAY_FOREACH(p, comp->slots) {
//Copy the input data because the JSON parser will encode the data immediately. // Reset all slots if slotcode is 0
auto temp = byDefault ? slots : duplicate(slots); if (slotcode == 0) {
(*p)->reset();
//parsing slot json applied = true;
LottieParser parser(temp, dirName, builder->expressions()); continue;
parser.comp = comp; }
auto idx = 0; INLIST_FOREACH((*p)->values, q) {
auto succeed = false; if (q->slotcode != slotcode) continue;
while (auto sid = parser.sid(idx == 0)) { (*p)->apply(q->prop, byDefault);
auto applied = false; applied = true;
ARRAY_FOREACH(p, comp->slots) { break;
if (strcmp((*p)->sid, sid)) continue;
if (parser.apply(*p, byDefault)) succeed = applied = true;
break;
}
if (!applied) parser.skip();
++idx;
} }
tvg::free((char*)temp);
rebuild = succeed;
overridden |= succeed;
return rebuild;
//reset slots
} else if (overridden) {
ARRAY_FOREACH(p, comp->slots) (*p)->reset();
overridden = false;
rebuild = true;
} }
if (!applied) return false;
overridden = slotcode != 0;
rebuild = true;
return true; return true;
} }
bool LottieLoader::del(uint32_t slotcode, bool byDefault)
{
if (!ready() || comp->slots.count == 0 || slotcode == 0) return false;
// Search matching value and remove
ARRAY_FOREACH(p, comp->slots) {
INLIST_FOREACH((*p)->values, q) {
if (q->slotcode != slotcode) continue;
if (!byDefault && (*p)->overridden) {
(*p)->reset();
rebuild = true;
}
(*p)->values.remove(q);
delete(q);
break;
}
if ((*p)->values.empty()) (*p)->overridden = false;
}
return true;
}
uint32_t LottieLoader::gen(const char* slots, bool byDefault)
{
if (!slots || !ready() || comp->slots.count == 0) return 0;
auto slotcode = djb2Encode(slots);
ARRAY_FOREACH(p, comp->slots) {
INLIST_FOREACH((*p)->values, q) {
if (q->slotcode == slotcode) return slotcode;
}
}
auto temp = byDefault ? slots : duplicate(slots);
//parsing slot json
LottieParser parser(temp, dirName, builder->expressions());
parser.comp = comp;
auto idx = 0;
bool generated = false;
while (auto sid = parser.sid(idx == 0)) {
ARRAY_FOREACH(p, comp->slots) {
if (strcmp((*p)->sid, sid)) continue;
auto prop = parser.parseSlot(*p);
if (prop) {
(*p)->add(slotcode, prop);
generated = true;
}
break;
}
++idx;
}
tvg::free((char*)temp);
if (!generated) return 0;
return slotcode;
}
float LottieLoader::shorten(float frameNo) float LottieLoader::shorten(float frameNo)
{ {
//This ensures that the target frame number is reached. //This ensures that the target frame number is reached.

View file

@ -56,7 +56,11 @@ public:
bool resize(Paint* paint, float w, float h) override; bool resize(Paint* paint, float w, float h) override;
bool read() override; bool read() override;
Paint* paint() override; Paint* paint() override;
bool override(const char* slot, bool byDefault = false);
//Slot APIs
uint32_t gen(const char* slot, bool byDefault = false);
bool apply(uint32_t slotcode, bool byDefault = false);
bool del(uint32_t slotcode, bool byDefault = false);
//Frame Controls //Frame Controls
bool frame(float no) override; bool frame(float no) override;

View file

@ -203,7 +203,52 @@ void LottieSlot::reset()
} }
void LottieSlot::assign(LottieObject* target, bool byDefault) LottieProperty* LottieSlot::property(LottieObject* target)
{
LottieProperty* prop = nullptr;
//apply slot object to all targets
ARRAY_FOREACH(pair, pairs) {
//backup the original properties before overwriting
switch (type) {
case LottieProperty::Type::Vector: {
return new LottieVector(static_cast<LottieTransform*>(target)->position);
}
case LottieProperty::Type::Scalar: {
return new LottieScalar(static_cast<LottieTransform*>(target)->scale);
}
case LottieProperty::Type::Float: {
return new LottieFloat(static_cast<LottieTransform*>(target)->rotation);
}
case LottieProperty::Type::Opacity: {
return new LottieOpacity(static_cast<LottieSolid*>(target)->opacity);
}
case LottieProperty::Type::Color: {
return new LottieColor(static_cast<LottieSolid*>(target)->color);
}
case LottieProperty::Type::ColorStop: {
return new LottieColorStop(static_cast<LottieGradient*>(target)->colorStops);
}
case LottieProperty::Type::TextDoc: {
return new LottieTextDoc(static_cast<LottieText*>(target)->doc);
}
case LottieProperty::Type::Image: {
return new LottieBitmap(static_cast<LottieImage*>(target)->data);
}
default: break;
}
}
return prop;
}
void LottieSlot::add(uint32_t slotcode, LottieProperty* prop)
{
values.back(new Value{nullptr, nullptr, slotcode, prop});
}
void LottieSlot::apply(LottieProperty* prop, bool byDefault)
{ {
auto copy = !overridden && !byDefault; auto copy = !overridden && !byDefault;
auto shallow = pairs.count == 1 ? true : false; auto shallow = pairs.count == 1 ? true : false;
@ -214,22 +259,22 @@ void LottieSlot::assign(LottieObject* target, bool byDefault)
switch (type) { switch (type) {
case LottieProperty::Type::Float: { case LottieProperty::Type::Float: {
if (copy) pair->prop = new LottieFloat(static_cast<LottieTransform*>(pair->obj)->rotation); if (copy) pair->prop = new LottieFloat(static_cast<LottieTransform*>(pair->obj)->rotation);
pair->obj->override(&static_cast<LottieTransform*>(target)->rotation, shallow, !copy); pair->obj->override(prop, shallow, !copy);
break; break;
} }
case LottieProperty::Type::Scalar: { case LottieProperty::Type::Scalar: {
if (copy) pair->prop = new LottieScalar(static_cast<LottieTransform*>(pair->obj)->scale); if (copy) pair->prop = new LottieScalar(static_cast<LottieTransform*>(pair->obj)->scale);
pair->obj->override(&static_cast<LottieTransform*>(target)->scale, shallow, !copy); pair->obj->override(prop, shallow, !copy);
break; break;
} }
case LottieProperty::Type::Vector: { case LottieProperty::Type::Vector: {
if (copy) pair->prop = new LottieVector(static_cast<LottieTransform*>(pair->obj)->position); if (copy) pair->prop = new LottieVector(static_cast<LottieTransform*>(pair->obj)->position);
pair->obj->override(&static_cast<LottieTransform*>(target)->position, shallow, !copy); pair->obj->override(prop, shallow, !copy);
break; break;
} }
case LottieProperty::Type::Color: { case LottieProperty::Type::Color: {
if (copy) pair->prop = new LottieColor(static_cast<LottieSolid*>(pair->obj)->color); if (copy) pair->prop = new LottieColor(static_cast<LottieSolid*>(pair->obj)->color);
pair->obj->override(&static_cast<LottieSolid*>(target)->color, shallow, !copy); pair->obj->override(prop, shallow, !copy);
break; break;
} }
case LottieProperty::Type::Opacity: { case LottieProperty::Type::Opacity: {
@ -237,27 +282,28 @@ void LottieSlot::assign(LottieObject* target, bool byDefault)
if (pair->obj->type == LottieObject::Type::Transform) pair->prop = new LottieOpacity(static_cast<LottieTransform*>(pair->obj)->opacity); if (pair->obj->type == LottieObject::Type::Transform) pair->prop = new LottieOpacity(static_cast<LottieTransform*>(pair->obj)->opacity);
else pair->prop = new LottieOpacity(static_cast<LottieSolid*>(pair->obj)->opacity); else pair->prop = new LottieOpacity(static_cast<LottieSolid*>(pair->obj)->opacity);
} }
pair->obj->override(&static_cast<LottieSolid*>(target)->opacity, shallow, !copy); pair->obj->override(prop, shallow, !copy);
break; break;
} }
case LottieProperty::Type::ColorStop: { case LottieProperty::Type::ColorStop: {
if (copy) pair->prop = new LottieColorStop(static_cast<LottieGradient*>(pair->obj)->colorStops); if (copy) pair->prop = new LottieColorStop(static_cast<LottieGradient*>(pair->obj)->colorStops);
pair->obj->override(&static_cast<LottieGradient*>(target)->colorStops, shallow, !copy); pair->obj->override(prop, shallow, !copy);
break; break;
} }
case LottieProperty::Type::TextDoc: { case LottieProperty::Type::TextDoc: {
if (copy) pair->prop = new LottieTextDoc(static_cast<LottieText*>(pair->obj)->doc); if (copy) pair->prop = new LottieTextDoc(static_cast<LottieText*>(pair->obj)->doc);
pair->obj->override(&static_cast<LottieText*>(target)->doc, shallow, !copy); pair->obj->override(prop, shallow, !copy);
break; break;
} }
case LottieProperty::Type::Image: { case LottieProperty::Type::Image: {
if (copy) pair->prop = new LottieBitmap(static_cast<LottieImage*>(pair->obj)->data); if (copy) pair->prop = new LottieBitmap(static_cast<LottieImage*>(pair->obj)->data);
pair->obj->override(&static_cast<LottieImage*>(target)->data, shallow, !copy); pair->obj->override(prop, shallow, !copy);
break; break;
} }
default: break; default: break;
} }
} }
if (!byDefault) overridden = true; if (!byDefault) overridden = true;
} }

View file

@ -26,6 +26,7 @@
#include "tvgCommon.h" #include "tvgCommon.h"
#include "tvgStr.h" #include "tvgStr.h"
#include "tvgCompressor.h" #include "tvgCompressor.h"
#include "tvgInlist.h"
#include "tvgRender.h" #include "tvgRender.h"
#include "tvgLottieProperty.h" #include "tvgLottieProperty.h"
#include "tvgLottieRenderPooler.h" #include "tvgLottieRenderPooler.h"
@ -1058,7 +1059,21 @@ struct LottieSlot
LottieProperty* prop; LottieProperty* prop;
}; };
void assign(LottieObject* target, bool byDefault); struct Value {
INLIST_ITEM(Value);
uint32_t slotcode;
LottieProperty* prop;
~Value()
{
delete(prop);
}
};
LottieProperty* property(LottieObject* target);
void add(uint32_t slotcode, LottieProperty* prop);
void apply(LottieProperty* prop, bool byDefault = false);
void reset(); void reset();
LottieSlot(LottieLayer* layer, LottieObject* parent, char* sid, LottieObject* obj, LottieProperty::Type type) : context{layer, parent}, sid(sid), type(type) LottieSlot(LottieLayer* layer, LottieObject* parent, char* sid, LottieObject* obj, LottieProperty::Type type) : context{layer, parent}, sid(sid), type(type)
@ -1069,6 +1084,7 @@ struct LottieSlot
~LottieSlot() ~LottieSlot()
{ {
tvg::free(sid); tvg::free(sid);
values.free();
if (!overridden) return; if (!overridden) return;
ARRAY_FOREACH(pair, pairs) delete(pair->prop); ARRAY_FOREACH(pair, pairs) delete(pair->prop);
} }
@ -1079,7 +1095,8 @@ struct LottieSlot
} context; } context;
char* sid; char* sid;
Array<Pair> pairs; Array<Pair> pairs; // Object-property pairs that can be overridden by this slot
Inlist<Value> values; // Available slot values for property overrides
LottieProperty::Type type; LottieProperty::Type type;
bool overridden = false; bool overridden = false;

View file

@ -1539,7 +1539,7 @@ const char* LottieParser::sid(bool first)
} }
bool LottieParser::apply(LottieSlot* slot, bool byDefault) LottieProperty* LottieParser::parseSlot(LottieSlot* slot)
{ {
enterObject(); enterObject();
@ -1598,14 +1598,13 @@ bool LottieParser::apply(LottieSlot* slot, bool byDefault)
if (!obj || Invalid()) { if (!obj || Invalid()) {
delete(obj); delete(obj);
return false; return nullptr;
} }
slot->assign(obj, byDefault); auto prop = slot->property(obj);
delete(obj); delete(obj);
return true; return prop;
} }

View file

@ -37,8 +37,8 @@ public:
} }
bool parse(); bool parse();
bool apply(LottieSlot* slot, bool byDefault);
const char* sid(bool first = false); const char* sid(bool first = false);
LottieProperty* parseSlot(LottieSlot* slot);
void captureSlots(const char* key); void captureSlots(const char* key);
void registerSlot(LottieObject* obj, const char* sid, LottieProperty::Type type); void registerSlot(LottieObject* obj, const char* sid, LottieProperty::Type type);