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)
{
//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 read() override;
Paint* paint() override;
bool override(const char* slot);
//Frame Controls
bool frame(float no) override;

View file

@ -228,4 +228,9 @@ LottieComposition::~LottieComposition()
for (auto f = fonts.begin(); f < fonts.end(); ++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);
}
virtual void override(LottieObject* prop)
{
TVGERR("LOTTIE", "Unsupported slot type");
}
char* name = nullptr;
Type type;
bool statical = true; //no keyframes
@ -183,6 +188,12 @@ struct LottieText : LottieObject
LottieObject::type = LottieObject::Text;
}
void override(LottieObject* prop) override
{
this->doc = static_cast<LottieText*>(prop)->doc;
this->prepare();
}
LottieTextDoc doc;
LottieFont* font;
LottieFloat spacing = 0.0f; //letter spacing
@ -339,6 +350,12 @@ struct LottieSolidStroke : LottieSolid, LottieStroke
LottieObject::type = LottieObject::SolidStroke;
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;
}
void override(LottieObject* prop) override
{
this->color = static_cast<LottieSolid*>(prop)->color;
this->prepare();
}
FillRule rule = FillRule::Winding;
};
@ -468,6 +491,12 @@ struct LottieGradientFill : LottieGradient
if (LottieGradient::prepare()) statical = false;
}
void override(LottieObject* prop) override
{
this->colorStops = static_cast<LottieGradient*>(prop)->colorStops;
this->prepare();
}
FillRule rule = FillRule::Winding;
};
@ -479,6 +508,12 @@ struct LottieGradientStroke : LottieGradient, LottieStroke
LottieObject::type = LottieObject::GradientStroke;
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
{
~LottieComposition();
@ -622,6 +675,7 @@ struct LottieComposition
Array<LottieObject*> assets;
Array<LottieInterpolator*> interpolators;
Array<LottieFont*> fonts;
Array<LottieSlot*> slots;
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>
bool LottieParser::parseTangent(const char *key, LottieVectorFrame<T>& value)
{
@ -456,13 +467,22 @@ void LottieParser::parsePropertyInternal(T& prop)
}
template<typename T>
void LottieParser::parseProperty(T& prop)
template<LottieProperty::Type type, typename T>
void LottieParser::parseProperty(T& prop, LottieObject* obj)
{
enterObject();
while (auto key = nextObjectKey()) {
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()) {
if (!strcmp(key, "nm")) fill->name = getStringCopy();
else if (!strcmp(key, "c")) parseProperty(fill->color);
else if (!strcmp(key, "o")) parseProperty(fill->opacity);
else if (!strcmp(key, "c")) parseProperty<LottieProperty::Type::Color>(fill->color, fill);
else if (!strcmp(key, "o")) parseProperty<LottieProperty::Type::Opacity>(fill->opacity, fill);
else if (!strcmp(key, "fillEnabled")) fill->hidden |= !getBool();
else if (!strcmp(key, "r")) fill->rule = getFillRule();
else if (!strcmp(key, "hd")) fill->hidden = getBool();
@ -590,9 +610,9 @@ LottieSolidStroke* LottieParser::parseSolidStroke()
if (!stroke) return nullptr;
while (auto key = nextObjectKey()) {
if (!strcmp(key, "c")) parseProperty(stroke->color);
else if (!strcmp(key, "o")) parseProperty(stroke->opacity);
else if (!strcmp(key, "w")) parseProperty(stroke->width);
if (!strcmp(key, "c")) parseProperty<LottieProperty::Type::Color>(stroke->color, stroke);
else if (!strcmp(key, "o")) parseProperty<LottieProperty::Type::Opacity>(stroke->opacity, stroke);
else if (!strcmp(key, "w")) parseProperty<LottieProperty::Type::Float>(stroke->width, stroke);
else if (!strcmp(key, "lc")) stroke->cap = getStrokeCap();
else if (!strcmp(key, "lj")) stroke->join = getStrokeJoin();
else if (!strcmp(key, "ml")) stroke->miterLimit = getFloat();
@ -680,21 +700,23 @@ LottieRoundedCorner* LottieParser::parseRoundedCorner()
void LottieParser::parseGradient(LottieGradient* gradient, const char* key)
{
context->gradient = gradient;
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"))
{
enterObject();
while (auto key = nextObjectKey()) {
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 if (!strcmp(key, "s")) parseProperty(gradient->start);
else if (!strcmp(key, "e")) parseProperty(gradient->end);
else if (!strcmp(key, "h")) parseProperty(gradient->height);
else if (!strcmp(key, "a")) parseProperty(gradient->angle);
else if (!strcmp(key, "s")) parseProperty<LottieProperty::Type::Point>(gradient->start, gradient);
else if (!strcmp(key, "e")) parseProperty<LottieProperty::Type::Point>(gradient->end, gradient);
else if (!strcmp(key, "h")) parseProperty<LottieProperty::Type::Float>(gradient->height, gradient);
else if (!strcmp(key, "a")) parseProperty<LottieProperty::Type::Float>(gradient->angle, gradient);
else skip(key);
}
@ -704,8 +726,6 @@ LottieGradientFill* LottieParser::parseGradientFill()
auto fill = new LottieGradientFill;
if (!fill) return nullptr;
context->gradient = fill;
while (auto key = nextObjectKey()) {
if (!strcmp(key, "nm")) fill->name = getStringCopy();
else if (!strcmp(key, "r")) fill->rule = getFillRule();
@ -724,8 +744,6 @@ LottieGradientStroke* LottieParser::parseGradientStroke()
auto stroke = new LottieGradientStroke;
if (!stroke) return nullptr;
context->gradient = stroke;
while (auto key = nextObjectKey()) {
if (!strcmp(key, "nm")) stroke->name = getStringCopy();
else if (!strcmp(key, "lc")) stroke->cap = getStrokeCap();
@ -1055,7 +1073,7 @@ void LottieParser::parseText(Array<LottieObject*>& parent)
auto text = new LottieText;
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, "p")) TVGLOG("LOTTIE", "Text Follow Path (p) 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 */
/************************************************************************/
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()
{
//verify json.

View file

@ -25,6 +25,7 @@
#include "tvgCommon.h"
#include "tvgLottieParserHandler.h"
#include "tvgLottieProperty.h"
struct LottieParser : LookaheadParserHandler
{
@ -35,6 +36,8 @@ public:
}
bool parse();
bool parse(LottieSlot* slot);
const char* sid();
LottieComposition* comp = nullptr;
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> void parseKeyFrame(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* parseAsset();

View file

@ -249,6 +249,15 @@ struct LottieGenericProperty : LottieProperty
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; }
void prepare() {}
};
@ -433,6 +442,15 @@ struct LottieColorStop : LottieProperty
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() {}
};
@ -544,6 +562,15 @@ struct LottieTextDoc : LottieProperty
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() {}
};