lottie: Support the slot overriding feature

Internal model and parser modifications have been made
to parse "sid" and retrieve their data into the LottieComposition.
This will enable dynamic changes to the following Lottie objects:

The slot feature will encompass these properties:

- LottieSolidStroke
- LottieSolidFill
- LottieGradientStroke
- LottieGradientFill
- LottieTextDoc"

Issue: https://github.com/thorvg/thorvg/issues/1808

Co-authored-by: Hermet Park <hermet@lottiefiles.com>
This commit is contained in:
Jinny You 2024-02-22 20:38:42 +09:00 committed by Hermet Park
parent a361924887
commit 362e6faacb
7 changed files with 200 additions and 20 deletions

View file

@ -300,6 +300,27 @@ Paint* LottieLoader::paint()
} }
bool LottieLoader::override(const char* slot)
{
if (!slot) return false;
//parsing slot json
LottieParser parser(slot, dirName);
auto sid = parser.sid();
if (!sid) return false;
bool ret = false;
for (auto s = comp->slots.begin(); s < comp->slots.end(); ++s) {
if (!strcmp((*s)->sid, sid)) continue;
ret = parser.parse(*s);
break;
}
return ret;
}
bool LottieLoader::frame(float no) bool LottieLoader::frame(float no)
{ {
//no meaing to update if frame diff is less then 1ms //no meaing to update if frame diff is less then 1ms

View file

@ -53,6 +53,7 @@ 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);
//Frame Controls //Frame Controls
bool frame(float no) override; bool frame(float no) override;

View file

@ -228,4 +228,9 @@ LottieComposition::~LottieComposition()
for (auto f = fonts.begin(); f < fonts.end(); ++f) { for (auto f = fonts.begin(); f < fonts.end(); ++f) {
delete(*f); delete(*f);
} }
//delete slots
for (auto s = slots.begin(); s < slots.end(); ++s) {
delete(*s);
}
} }

View file

@ -125,6 +125,11 @@ struct LottieObject
free(name); free(name);
} }
virtual void override(LottieObject* prop)
{
TVGERR("LOTTIE", "Unsupported slot type");
}
char* name = nullptr; char* name = nullptr;
Type type; Type type;
bool statical = true; //no keyframes bool statical = true; //no keyframes
@ -183,6 +188,12 @@ struct LottieText : LottieObject
LottieObject::type = LottieObject::Text; LottieObject::type = LottieObject::Text;
} }
void override(LottieObject* prop) override
{
this->doc = static_cast<LottieText*>(prop)->doc;
this->prepare();
}
LottieTextDoc doc; LottieTextDoc doc;
LottieFont* font; LottieFont* font;
LottieFloat spacing = 0.0f; //letter spacing LottieFloat spacing = 0.0f; //letter spacing
@ -339,6 +350,12 @@ struct LottieSolidStroke : LottieSolid, LottieStroke
LottieObject::type = LottieObject::SolidStroke; LottieObject::type = LottieObject::SolidStroke;
if (color.frames || opacity.frames || LottieStroke::dynamic()) statical = false; if (color.frames || opacity.frames || LottieStroke::dynamic()) statical = false;
} }
void override(LottieObject* prop) override
{
this->color = static_cast<LottieSolid*>(prop)->color;
this->prepare();
}
}; };
@ -350,6 +367,12 @@ struct LottieSolidFill : LottieSolid
if (color.frames || opacity.frames) statical = false; if (color.frames || opacity.frames) statical = false;
} }
void override(LottieObject* prop) override
{
this->color = static_cast<LottieSolid*>(prop)->color;
this->prepare();
}
FillRule rule = FillRule::Winding; FillRule rule = FillRule::Winding;
}; };
@ -468,6 +491,12 @@ struct LottieGradientFill : LottieGradient
if (LottieGradient::prepare()) statical = false; if (LottieGradient::prepare()) statical = false;
} }
void override(LottieObject* prop) override
{
this->colorStops = static_cast<LottieGradient*>(prop)->colorStops;
this->prepare();
}
FillRule rule = FillRule::Winding; FillRule rule = FillRule::Winding;
}; };
@ -479,6 +508,12 @@ struct LottieGradientStroke : LottieGradient, LottieStroke
LottieObject::type = LottieObject::GradientStroke; LottieObject::type = LottieObject::GradientStroke;
if (LottieGradient::prepare() || LottieStroke::dynamic()) statical = false; if (LottieGradient::prepare() || LottieStroke::dynamic()) statical = false;
} }
void override(LottieObject* prop) override
{
this->colorStops = static_cast<LottieGradient*>(prop)->colorStops;
this->prepare();
}
}; };
@ -592,6 +627,24 @@ struct LottieLayer : LottieGroup
}; };
struct LottieSlot
{
char* sid;
Array<LottieObject*> objs;
LottieProperty::Type type;
LottieSlot(char* sid, LottieObject* obj, LottieProperty::Type type) : sid(sid), type(type)
{
objs.push(obj);
}
~LottieSlot()
{
free(sid);
}
};
struct LottieComposition struct LottieComposition
{ {
~LottieComposition(); ~LottieComposition();
@ -622,6 +675,7 @@ struct LottieComposition
Array<LottieObject*> assets; Array<LottieObject*> assets;
Array<LottieInterpolator*> interpolators; Array<LottieInterpolator*> interpolators;
Array<LottieFont*> fonts; Array<LottieFont*> fonts;
Array<LottieSlot*> slots;
bool initiated = false; bool initiated = false;
}; };

View file

@ -334,6 +334,17 @@ void LottieParser::getInperpolatorPoint(Point& pt)
} }
} }
template<typename T>
void LottieParser::parseSlotProperty(T& prop)
{
while (auto key = nextObjectKey()) {
if (!strcmp(key, "p")) parseProperty(prop);
else skip(key);
}
}
template<typename T> template<typename T>
bool LottieParser::parseTangent(const char *key, LottieVectorFrame<T>& value) bool LottieParser::parseTangent(const char *key, LottieVectorFrame<T>& value)
{ {
@ -456,13 +467,22 @@ void LottieParser::parsePropertyInternal(T& prop)
} }
template<typename T> template<LottieProperty::Type type, typename T>
void LottieParser::parseProperty(T& prop) void LottieParser::parseProperty(T& prop, LottieObject* obj)
{ {
enterObject(); enterObject();
while (auto key = nextObjectKey()) { while (auto key = nextObjectKey()) {
if (!strcmp(key, "k")) parsePropertyInternal(prop); if (!strcmp(key, "k")) parsePropertyInternal(prop);
else skip(key); else if (obj && !strcmp(key, "sid")) {
auto sid = getStringCopy();
//append object if the slot already exists.
for (auto slot = comp->slots.begin(); slot < comp->slots.end(); ++slot) {
if (strcmp((*slot)->sid, sid)) continue;
(*slot)->objs.push(obj);
return;
}
comp->slots.push(new LottieSlot(sid, obj, type));
} else skip(key);
} }
} }
@ -552,8 +572,8 @@ LottieSolidFill* LottieParser::parseSolidFill()
while (auto key = nextObjectKey()) { while (auto key = nextObjectKey()) {
if (!strcmp(key, "nm")) fill->name = getStringCopy(); if (!strcmp(key, "nm")) fill->name = getStringCopy();
else if (!strcmp(key, "c")) parseProperty(fill->color); else if (!strcmp(key, "c")) parseProperty<LottieProperty::Type::Color>(fill->color, fill);
else if (!strcmp(key, "o")) parseProperty(fill->opacity); else if (!strcmp(key, "o")) parseProperty<LottieProperty::Type::Opacity>(fill->opacity, fill);
else if (!strcmp(key, "fillEnabled")) fill->hidden |= !getBool(); else if (!strcmp(key, "fillEnabled")) fill->hidden |= !getBool();
else if (!strcmp(key, "r")) fill->rule = getFillRule(); else if (!strcmp(key, "r")) fill->rule = getFillRule();
else if (!strcmp(key, "hd")) fill->hidden = getBool(); else if (!strcmp(key, "hd")) fill->hidden = getBool();
@ -590,9 +610,9 @@ LottieSolidStroke* LottieParser::parseSolidStroke()
if (!stroke) return nullptr; if (!stroke) return nullptr;
while (auto key = nextObjectKey()) { while (auto key = nextObjectKey()) {
if (!strcmp(key, "c")) parseProperty(stroke->color); if (!strcmp(key, "c")) parseProperty<LottieProperty::Type::Color>(stroke->color, stroke);
else if (!strcmp(key, "o")) parseProperty(stroke->opacity); else if (!strcmp(key, "o")) parseProperty<LottieProperty::Type::Opacity>(stroke->opacity, stroke);
else if (!strcmp(key, "w")) parseProperty(stroke->width); else if (!strcmp(key, "w")) parseProperty<LottieProperty::Type::Float>(stroke->width, stroke);
else if (!strcmp(key, "lc")) stroke->cap = getStrokeCap(); else if (!strcmp(key, "lc")) stroke->cap = getStrokeCap();
else if (!strcmp(key, "lj")) stroke->join = getStrokeJoin(); else if (!strcmp(key, "lj")) stroke->join = getStrokeJoin();
else if (!strcmp(key, "ml")) stroke->miterLimit = getFloat(); else if (!strcmp(key, "ml")) stroke->miterLimit = getFloat();
@ -680,21 +700,23 @@ LottieRoundedCorner* LottieParser::parseRoundedCorner()
void LottieParser::parseGradient(LottieGradient* gradient, const char* key) void LottieParser::parseGradient(LottieGradient* gradient, const char* key)
{ {
context->gradient = gradient;
if (!strcmp(key, "t")) gradient->id = getInt(); if (!strcmp(key, "t")) gradient->id = getInt();
else if (!strcmp(key, "o")) parseProperty(gradient->opacity); else if (!strcmp(key, "o")) parseProperty<LottieProperty::Type::Opacity>(gradient->opacity, gradient);
else if (!strcmp(key, "g")) else if (!strcmp(key, "g"))
{ {
enterObject(); enterObject();
while (auto key = nextObjectKey()) { while (auto key = nextObjectKey()) {
if (!strcmp(key, "p")) gradient->colorStops.count = getInt(); if (!strcmp(key, "p")) gradient->colorStops.count = getInt();
else if (!strcmp(key, "k")) parseProperty(gradient->colorStops); else if (!strcmp(key, "k")) parseProperty<LottieProperty::Type::ColorStop>(gradient->colorStops, gradient);
else skip(key); else skip(key);
} }
} }
else if (!strcmp(key, "s")) parseProperty(gradient->start); else if (!strcmp(key, "s")) parseProperty<LottieProperty::Type::Point>(gradient->start, gradient);
else if (!strcmp(key, "e")) parseProperty(gradient->end); else if (!strcmp(key, "e")) parseProperty<LottieProperty::Type::Point>(gradient->end, gradient);
else if (!strcmp(key, "h")) parseProperty(gradient->height); else if (!strcmp(key, "h")) parseProperty<LottieProperty::Type::Float>(gradient->height, gradient);
else if (!strcmp(key, "a")) parseProperty(gradient->angle); else if (!strcmp(key, "a")) parseProperty<LottieProperty::Type::Float>(gradient->angle, gradient);
else skip(key); else skip(key);
} }
@ -704,8 +726,6 @@ LottieGradientFill* LottieParser::parseGradientFill()
auto fill = new LottieGradientFill; auto fill = new LottieGradientFill;
if (!fill) return nullptr; if (!fill) return nullptr;
context->gradient = fill;
while (auto key = nextObjectKey()) { while (auto key = nextObjectKey()) {
if (!strcmp(key, "nm")) fill->name = getStringCopy(); if (!strcmp(key, "nm")) fill->name = getStringCopy();
else if (!strcmp(key, "r")) fill->rule = getFillRule(); else if (!strcmp(key, "r")) fill->rule = getFillRule();
@ -724,8 +744,6 @@ LottieGradientStroke* LottieParser::parseGradientStroke()
auto stroke = new LottieGradientStroke; auto stroke = new LottieGradientStroke;
if (!stroke) return nullptr; if (!stroke) return nullptr;
context->gradient = stroke;
while (auto key = nextObjectKey()) { while (auto key = nextObjectKey()) {
if (!strcmp(key, "nm")) stroke->name = getStringCopy(); if (!strcmp(key, "nm")) stroke->name = getStringCopy();
else if (!strcmp(key, "lc")) stroke->cap = getStrokeCap(); else if (!strcmp(key, "lc")) stroke->cap = getStrokeCap();
@ -1055,7 +1073,7 @@ void LottieParser::parseText(Array<LottieObject*>& parent)
auto text = new LottieText; auto text = new LottieText;
while (auto key = nextObjectKey()) { while (auto key = nextObjectKey()) {
if (!strcmp(key, "d")) parseProperty(text->doc); if (!strcmp(key, "d")) parseProperty<LottieProperty::Type::TextDoc>(text->doc, text);
else if (!strcmp(key, "a")) parseTextRange(text); else if (!strcmp(key, "a")) parseTextRange(text);
//else if (!strcmp(key, "p")) TVGLOG("LOTTIE", "Text Follow Path (p) is not supported"); //else if (!strcmp(key, "p")) TVGLOG("LOTTIE", "Text Follow Path (p) is not supported");
//else if (!strcmp(key, "m")) TVGLOG("LOTTIE", "Text Alignment Option (m) is not supported"); //else if (!strcmp(key, "m")) TVGLOG("LOTTIE", "Text Alignment Option (m) is not supported");
@ -1220,6 +1238,56 @@ void LottieParser::postProcess(Array<LottieGlyph*>& glyphes)
/* External Class Implementation */ /* External Class Implementation */
/************************************************************************/ /************************************************************************/
const char* LottieParser::sid()
{
//verify json
if (!parseNext()) return nullptr;
enterObject();
return nextObjectKey();
}
bool LottieParser::parse(LottieSlot* slot)
{
enterObject();
LottieParser::Context context;
this->context = &context;
LottieObject* obj = nullptr; //slot object
switch (slot->type) {
case LottieProperty::Type::ColorStop: {
obj = new LottieGradient;
context.gradient = static_cast<LottieGradient*>(obj);
parseSlotProperty(static_cast<LottieGradient*>(obj)->colorStops);
break;
}
case LottieProperty::Type::Color: {
obj = new LottieSolid;
parseSlotProperty(static_cast<LottieSolid*>(obj)->color);
break;
}
case LottieProperty::Type::TextDoc: {
obj = new LottieText;
parseSlotProperty(static_cast<LottieText*>(obj)->doc);
break;
}
default: break;
}
if (!obj || Invalid()) return false;
//apply slot object to all targets
for (auto target = slot->objs.begin(); target < slot->objs.end(); ++target) {
(*target)->override(obj);
}
delete(obj);
return true;
}
bool LottieParser::parse() bool LottieParser::parse()
{ {
//verify json. //verify json.

View file

@ -25,6 +25,7 @@
#include "tvgCommon.h" #include "tvgCommon.h"
#include "tvgLottieParserHandler.h" #include "tvgLottieParserHandler.h"
#include "tvgLottieProperty.h"
struct LottieParser : LookaheadParserHandler struct LottieParser : LookaheadParserHandler
{ {
@ -35,6 +36,8 @@ public:
} }
bool parse(); bool parse();
bool parse(LottieSlot* slot);
const char* sid();
LottieComposition* comp = nullptr; LottieComposition* comp = nullptr;
const char* dirName = nullptr; //base resource directory const char* dirName = nullptr; //base resource directory
@ -66,7 +69,8 @@ private:
template<typename T> bool parseTangent(const char *key, LottieScalarFrame<T>& value); template<typename T> bool parseTangent(const char *key, LottieScalarFrame<T>& value);
template<typename T> void parseKeyFrame(T& prop); template<typename T> void parseKeyFrame(T& prop);
template<typename T> void parsePropertyInternal(T& prop); template<typename T> void parsePropertyInternal(T& prop);
template<typename T> void parseProperty(T& prop); template<LottieProperty::Type type = LottieProperty::Type::Invalid, typename T> void parseProperty(T& prop, LottieObject* obj = nullptr);
template<typename T> void parseSlotProperty(T& prop);
LottieObject* parseObject(); LottieObject* parseObject();
LottieObject* parseAsset(); LottieObject* parseAsset();

View file

@ -249,6 +249,15 @@ struct LottieGenericProperty : LottieProperty
return frame->interpolate(frame + 1, frameNo); return frame->interpolate(frame + 1, frameNo);
} }
T& operator=(const T& other)
{
//shallow copy, used for slot overriding
delete(frames);
*this = other;
const_cast<T&>(other).frames = nullptr;
return *this;
}
float angle(float frameNo) { return 0; } float angle(float frameNo) { return 0; }
void prepare() {} void prepare() {}
}; };
@ -433,6 +442,15 @@ struct LottieColorStop : LottieProperty
fill->colorStops(result.data, count); fill->colorStops(result.data, count);
} }
LottieColorStop& operator=(const LottieColorStop& other)
{
//shallow copy, used for slot overriding
delete(frames);
*this = other;
const_cast<LottieColorStop&>(other).frames = nullptr;
return *this;
}
void prepare() {} void prepare() {}
}; };
@ -544,6 +562,15 @@ struct LottieTextDoc : LottieProperty
return frame->value; return frame->value;
} }
LottieTextDoc& operator=(const LottieTextDoc& other)
{
//shallow copy, used for slot overriding
delete(frames);
*this = other;
const_cast<LottieTextDoc&>(other).frames = nullptr;
return *this;
}
void prepare() {} void prepare() {}
}; };