From 362e6faacbf603d5eb01bcf457a9377e4bda89dd Mon Sep 17 00:00:00 2001 From: Jinny You Date: Thu, 22 Feb 2024 20:38:42 +0900 Subject: [PATCH] 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 --- src/loaders/lottie/tvgLottieLoader.cpp | 21 +++++ src/loaders/lottie/tvgLottieLoader.h | 1 + src/loaders/lottie/tvgLottieModel.cpp | 5 ++ src/loaders/lottie/tvgLottieModel.h | 54 +++++++++++++ src/loaders/lottie/tvgLottieParser.cpp | 106 ++++++++++++++++++++----- src/loaders/lottie/tvgLottieParser.h | 6 +- src/loaders/lottie/tvgLottieProperty.h | 27 +++++++ 7 files changed, 200 insertions(+), 20 deletions(-) diff --git a/src/loaders/lottie/tvgLottieLoader.cpp b/src/loaders/lottie/tvgLottieLoader.cpp index d435e406..da5ba729 100644 --- a/src/loaders/lottie/tvgLottieLoader.cpp +++ b/src/loaders/lottie/tvgLottieLoader.cpp @@ -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 diff --git a/src/loaders/lottie/tvgLottieLoader.h b/src/loaders/lottie/tvgLottieLoader.h index 3c848e4c..94385a61 100644 --- a/src/loaders/lottie/tvgLottieLoader.h +++ b/src/loaders/lottie/tvgLottieLoader.h @@ -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; diff --git a/src/loaders/lottie/tvgLottieModel.cpp b/src/loaders/lottie/tvgLottieModel.cpp index 42d1ece2..2ba1499d 100644 --- a/src/loaders/lottie/tvgLottieModel.cpp +++ b/src/loaders/lottie/tvgLottieModel.cpp @@ -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); + } } \ No newline at end of file diff --git a/src/loaders/lottie/tvgLottieModel.h b/src/loaders/lottie/tvgLottieModel.h index 1e6e44fe..4ed89220 100644 --- a/src/loaders/lottie/tvgLottieModel.h +++ b/src/loaders/lottie/tvgLottieModel.h @@ -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(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(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(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(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(prop)->colorStops; + this->prepare(); + } }; @@ -592,6 +627,24 @@ struct LottieLayer : LottieGroup }; +struct LottieSlot +{ + char* sid; + Array 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 assets; Array interpolators; Array fonts; + Array slots; bool initiated = false; }; diff --git a/src/loaders/lottie/tvgLottieParser.cpp b/src/loaders/lottie/tvgLottieParser.cpp index 6a564f8f..66ec23db 100644 --- a/src/loaders/lottie/tvgLottieParser.cpp +++ b/src/loaders/lottie/tvgLottieParser.cpp @@ -334,6 +334,17 @@ void LottieParser::getInperpolatorPoint(Point& pt) } } + +template +void LottieParser::parseSlotProperty(T& prop) +{ + while (auto key = nextObjectKey()) { + if (!strcmp(key, "p")) parseProperty(prop); + else skip(key); + } +} + + template bool LottieParser::parseTangent(const char *key, LottieVectorFrame& value) { @@ -456,13 +467,22 @@ void LottieParser::parsePropertyInternal(T& prop) } -template -void LottieParser::parseProperty(T& prop) +template +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(fill->color, fill); + else if (!strcmp(key, "o")) parseProperty(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(stroke->color, stroke); + else if (!strcmp(key, "o")) parseProperty(stroke->opacity, stroke); + else if (!strcmp(key, "w")) parseProperty(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(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(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(gradient->start, gradient); + else if (!strcmp(key, "e")) parseProperty(gradient->end, gradient); + else if (!strcmp(key, "h")) parseProperty(gradient->height, gradient); + else if (!strcmp(key, "a")) parseProperty(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& parent) auto text = new LottieText; while (auto key = nextObjectKey()) { - if (!strcmp(key, "d")) parseProperty(text->doc); + if (!strcmp(key, "d")) parseProperty(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& 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(obj); + parseSlotProperty(static_cast(obj)->colorStops); + break; + } + case LottieProperty::Type::Color: { + obj = new LottieSolid; + parseSlotProperty(static_cast(obj)->color); + break; + } + case LottieProperty::Type::TextDoc: { + obj = new LottieText; + parseSlotProperty(static_cast(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. diff --git a/src/loaders/lottie/tvgLottieParser.h b/src/loaders/lottie/tvgLottieParser.h index ae8f6c5c..f1a892c3 100644 --- a/src/loaders/lottie/tvgLottieParser.h +++ b/src/loaders/lottie/tvgLottieParser.h @@ -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 bool parseTangent(const char *key, LottieScalarFrame& value); template void parseKeyFrame(T& prop); template void parsePropertyInternal(T& prop); - template void parseProperty(T& prop); + template void parseProperty(T& prop, LottieObject* obj = nullptr); + template void parseSlotProperty(T& prop); LottieObject* parseObject(); LottieObject* parseAsset(); diff --git a/src/loaders/lottie/tvgLottieProperty.h b/src/loaders/lottie/tvgLottieProperty.h index 7ee0bad8..0945c90e 100644 --- a/src/loaders/lottie/tvgLottieProperty.h +++ b/src/loaders/lottie/tvgLottieProperty.h @@ -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(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(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(other).frames = nullptr; + return *this; + } + void prepare() {} };