lottie/slot: revise gradient fill support

Revise gradient fill data parsing by adding support
for the color stop count (p) parameter in slot data.

issue: https://github.com/thorvg/thorvg/issues/2795
This commit is contained in:
Hermet Park 2024-11-02 00:17:52 +09:00 committed by Hermet Park
parent c75ec0f333
commit 87761dc7c7
7 changed files with 43 additions and 29 deletions

View file

@ -103,7 +103,7 @@ struct UserExample : tvgexam::Example
auto picture = slot1->picture(); auto picture = slot1->picture();
if (!tvgexam::verify(picture->load(EXAMPLE_DIR"/lottie/extensions/slotsample.json"))) return false; if (!tvgexam::verify(picture->load(EXAMPLE_DIR"/lottie/extensions/slotsample.json"))) return false;
const char* slotJson = R"({"gradient_fill":{"p":{"a":0,"k":[0,0.1,0.1,0.2,1,1,0.1,0.2,0.1,1]}}})"; const char* slotJson = R"({"gradient_fill":{"p":{"p":2,"k":{"k":[0,0.1,0.1,0.2,1,1,0.1,0.2,0,0,1,1]}}}})";
if (!tvgexam::verify(slot1->override(slotJson))) return false; if (!tvgexam::verify(slot1->override(slotJson))) return false;
sizing(picture, 0); sizing(picture, 0);

View file

@ -1 +1 @@
{"v":"4.8.0","meta":{"g":"LottieFiles AE 3.0.2","a":"","k":"","d":"","tc":""},"fr":60,"ip":0,"op":121,"w":550,"h":550,"nm":"C","assets":[],"layers":[{"ind":1,"ty":4,"nm":"S","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":120,"s":[360]}]},"p":{"a":0,"k":[275,275,0]},"a":{"a":0,"k":[-7.886,88.719,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[282.019,134.888]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"nm":"R"},{"ty":"st","c":{"a":0,"k":[0.991,0,1,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":3},"lc":1,"lj":1,"ml":4,"nm":"S"},{"ty":"gf","o":{"a":0,"k":100},"r":1,"g":{"p":9,"k":{"a":0,"k":[0,0.514,0.373,0.984,0.141,0.478,0.412,0.984,0.283,0.443,0.451,0.984,0.379,0.408,0.49,0.984,0.475,0.373,0.529,0.984,0.606,0.278,0.647,0.925,0.737,0.184,0.765,0.867,0.868,0.092,0.882,0.808,1,0,1,0.749],"sid":"gradient_fill"}},"s":{"a":0,"k":[-159.51,23.531]},"e":{"a":0,"k":[183.084,8.059]},"t":1,"nm":"G"},{"ty":"tr","p":{"a":0,"k":[-7.886,88.719]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"R"}],"ip":0,"op":300,"st":0}],"slots":{"gradient_fill":{"p":{"a":0,"k":[0,0.1,0.1,0.2,1,1,0.1,0.2,0.1,1]}}},"markers":[]} {"v":"4.8.0","meta":{"g":"LottieFiles AE 3.0.2","a":"","k":"","d":"","tc":""},"fr":60,"ip":0,"op":121,"w":550,"h":550,"nm":"C","assets":[],"layers":[{"ind":1,"ty":4,"nm":"S","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":120,"s":[360]}]},"p":{"a":0,"k":[275,275,0]},"a":{"a":0,"k":[-7.886,88.719,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[282.019,134.888]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"nm":"R"},{"ty":"st","c":{"a":0,"k":[0.991,0,1,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":3},"lc":1,"lj":1,"ml":4,"nm":"S"},{"ty":"gf","o":{"a":0,"k":100},"r":1,"g":{"p":9,"k":{"a":0,"k":[0,0.514,0.373,0.984,0.141,0.478,0.412,0.984,0.283,0.443,0.451,0.984,0.379,0.408,0.49,0.984,0.475,0.373,0.529,0.984,0.606,0.278,0.647,0.925,0.737,0.184,0.765,0.867,0.868,0.092,0.882,0.808,1,0,1,0.749]},"sid":"gradient_fill"},"s":{"a":0,"k":[-159.51,23.531]},"e":{"a":0,"k":[183.084,8.059]},"t":1,"nm":"G"},{"ty":"tr","p":{"a":0,"k":[-7.886,88.719]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"R"}],"ip":0,"op":300,"st":0}],"slots":{"gradient_fill":{"p":{"a":0,"k":[0,0.1,0.1,0.2,1,1,0.1,0.2,0.1,1]}}},"markers":[]}

View file

@ -485,26 +485,31 @@ void LottieParser::parsePropertyInternal(T& prop)
} }
template<LottieProperty::Type type>
void LottieParser::registerSlot(LottieObject* obj)
{
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)->pairs.push({obj});
return;
}
comp->slots.push(new LottieSlot(sid, obj, type));
}
template<LottieProperty::Type type, typename T> template<LottieProperty::Type type, typename T>
void LottieParser::parseProperty(T& prop, LottieObject* obj) void LottieParser::parseProperty(T& prop, LottieObject* obj)
{ {
enterObject(); enterObject();
while (auto key = nextObjectKey()) { while (auto key = nextObjectKey()) {
if (KEY_AS("k")) parsePropertyInternal(prop); if (KEY_AS("k")) parsePropertyInternal(prop);
else if (obj && KEY_AS("sid")) { else if (obj && KEY_AS("sid")) registerSlot<type>(obj);
auto sid = getStringCopy(); else if (KEY_AS("x")) prop.exp = _expression(getStringCopy(), comp, context.layer, context.parent, &prop);
//append object if the slot already exists. else if (KEY_AS("ix")) prop.ix = getInt();
for (auto slot = comp->slots.begin(); slot < comp->slots.end(); ++slot) { else skip(key);
if (strcmp((*slot)->sid, sid)) continue;
(*slot)->pairs.push({obj});
break;
}
comp->slots.push(new LottieSlot(sid, obj, type));
} else if (KEY_AS("x")) {
prop.exp = _expression(getStringCopy(), comp, context.layer, context.parent, &prop);
} else if (KEY_AS("ix")) {
prop.ix = getInt();
} else skip(key);
} }
prop.type = type; prop.type = type;
} }
@ -750,19 +755,23 @@ LottieRoundedCorner* LottieParser::parseRoundedCorner()
} }
void LottieParser::parseColorStop(LottieGradient* gradient)
{
enterObject();
while (auto key = nextObjectKey()) {
if (KEY_AS("p")) gradient->colorStops.count = getInt();
else if (KEY_AS("k")) parseProperty<LottieProperty::Type::ColorStop>(gradient->colorStops, gradient);
else if (KEY_AS("sid")) registerSlot<LottieProperty::Type::ColorStop>(gradient);
else skip(key);
}
}
void LottieParser::parseGradient(LottieGradient* gradient, const char* key) void LottieParser::parseGradient(LottieGradient* gradient, const char* key)
{ {
if (KEY_AS("t")) gradient->id = getInt(); if (KEY_AS("t")) gradient->id = getInt();
else if (KEY_AS("o")) parseProperty<LottieProperty::Type::Opacity>(gradient->opacity, gradient); else if (KEY_AS("o")) parseProperty<LottieProperty::Type::Opacity>(gradient->opacity, gradient);
else if (KEY_AS("g")) else if (KEY_AS("g")) parseColorStop(gradient);
{
enterObject();
while (auto key = nextObjectKey()) {
if (KEY_AS("p")) gradient->colorStops.count = getInt();
else if (KEY_AS("k")) parseProperty<LottieProperty::Type::ColorStop>(gradient->colorStops, gradient);
else skip(key);
}
}
else if (KEY_AS("s")) parseProperty<LottieProperty::Type::Point>(gradient->start, gradient); else if (KEY_AS("s")) parseProperty<LottieProperty::Type::Point>(gradient->start, gradient);
else if (KEY_AS("e")) parseProperty<LottieProperty::Type::Point>(gradient->end, gradient); else if (KEY_AS("e")) parseProperty<LottieProperty::Type::Point>(gradient->end, gradient);
else if (KEY_AS("h")) parseProperty<LottieProperty::Type::Float>(gradient->height, gradient); else if (KEY_AS("h")) parseProperty<LottieProperty::Type::Float>(gradient->height, gradient);
@ -1445,7 +1454,10 @@ bool LottieParser::apply(LottieSlot* slot)
case LottieProperty::Type::ColorStop: { case LottieProperty::Type::ColorStop: {
obj = new LottieGradient; obj = new LottieGradient;
context.parent = obj; context.parent = obj;
parseSlotProperty<LottieProperty::Type::ColorStop>(static_cast<LottieGradient*>(obj)->colorStops); while (auto key = nextObjectKey()) {
if (KEY_AS("p")) parseColorStop(static_cast<LottieGradient*>(obj));
else skip(key);
}
break; break;
} }
case LottieProperty::Type::Color: { case LottieProperty::Type::Color: {

View file

@ -38,6 +38,7 @@ public:
bool parse(); bool parse();
bool apply(LottieSlot* slot); bool apply(LottieSlot* slot);
const char* sid(bool first = false); const char* sid(bool first = false);
template<LottieProperty::Type type = LottieProperty::Type::Invalid> void registerSlot(LottieObject* obj);
LottieComposition* comp = nullptr; LottieComposition* comp = nullptr;
const char* dirName = nullptr; //base resource directory const char* dirName = nullptr; //base resource directory
@ -107,6 +108,7 @@ private:
void parseTimeRemap(LottieLayer* layer); void parseTimeRemap(LottieLayer* layer);
void parseStrokeDash(LottieStroke* stroke); void parseStrokeDash(LottieStroke* stroke);
void parseGradient(LottieGradient* gradient, const char* key); void parseGradient(LottieGradient* gradient, const char* key);
void parseColorStop(LottieGradient* gradient);
void parseTextRange(LottieText* text); void parseTextRange(LottieText* text);
void parseTextAlignmentOption(LottieText* text); void parseTextAlignmentOption(LottieText* text);
void parseAssets(); void parseAssets();

View file

@ -41,7 +41,7 @@ TEST_CASE("Lottie Slot", "[capiLottie]")
REQUIRE(tvg_paint_get_type(picture, &type) == TVG_RESULT_SUCCESS); REQUIRE(tvg_paint_get_type(picture, &type) == TVG_RESULT_SUCCESS);
REQUIRE(type == TVG_TYPE_PICTURE); REQUIRE(type == TVG_TYPE_PICTURE);
const char* slotJson = R"({"gradient_fill":{"p":{"a":0,"k":[0,0.1,0.1,0.2,1,1,0.1,0.2,0.1,1]}}})"; const char* slotJson = R"({"gradient_fill":{"p":{"p":2,"k":{"a":0,"k":[0,0.1,0.1,0.2,1,1,0.1,0.2,0.1,1]}}}})";
//Slot override before loaded //Slot override before loaded
REQUIRE(tvg_lottie_animation_override(animation, slotJson) == TVG_RESULT_INSUFFICIENT_CONDITION); REQUIRE(tvg_lottie_animation_override(animation, slotJson) == TVG_RESULT_INSUFFICIENT_CONDITION);

View file

@ -1 +1 @@
{"v":"4.8.0","meta":{"g":"LottieFiles AE 3.0.2","a":"","k":"","d":"","tc":""},"fr":60,"ip":0,"op":121,"w":550,"h":550,"nm":"C","assets":[],"layers":[{"ind":1,"ty":4,"nm":"S","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":120,"s":[360]}]},"p":{"a":0,"k":[275,275,0]},"a":{"a":0,"k":[-7.886,88.719,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[282.019,134.888]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"nm":"R"},{"ty":"st","c":{"a":0,"k":[0.991,0,1,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":3},"lc":1,"lj":1,"ml":4,"nm":"S"},{"ty":"gf","o":{"a":0,"k":100},"r":1,"g":{"p":9,"k":{"a":0,"k":[0,0.514,0.373,0.984,0.141,0.478,0.412,0.984,0.283,0.443,0.451,0.984,0.379,0.408,0.49,0.984,0.475,0.373,0.529,0.984,0.606,0.278,0.647,0.925,0.737,0.184,0.765,0.867,0.868,0.092,0.882,0.808,1,0,1,0.749],"sid":"gradient_fill"}},"s":{"a":0,"k":[-159.51,23.531]},"e":{"a":0,"k":[183.084,8.059]},"t":1,"nm":"G"},{"ty":"tr","p":{"a":0,"k":[-7.886,88.719]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"R"}],"ip":0,"op":300,"st":0}],"slots":{"gradient_fill":{"p":{"a":0,"k":[0,0.1,0.1,0.2,1,1,0.1,0.2,0.1,1]}}},"markers":[]} {"v":"4.8.0","meta":{"g":"LottieFiles AE 3.0.2","a":"","k":"","d":"","tc":""},"fr":60,"ip":0,"op":121,"w":550,"h":550,"nm":"C","assets":[],"layers":[{"ind":1,"ty":4,"nm":"S","sr":1,"ks":{"o":{"a":0,"k":100},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":120,"s":[360]}]},"p":{"a":0,"k":[275,275,0]},"a":{"a":0,"k":[-7.886,88.719,0]},"s":{"a":0,"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[282.019,134.888]},"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"nm":"R"},{"ty":"st","c":{"a":0,"k":[0.991,0,1,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":3},"lc":1,"lj":1,"ml":4,"nm":"S"},{"ty":"gf","o":{"a":0,"k":100},"r":1,"g":{"p":9,"k":{"a":0,"k":[0,0.514,0.373,0.984,0.141,0.478,0.412,0.984,0.283,0.443,0.451,0.984,0.379,0.408,0.49,0.984,0.475,0.373,0.529,0.984,0.606,0.278,0.647,0.925,0.737,0.184,0.765,0.867,0.868,0.092,0.882,0.808,1,0,1,0.749]},"sid":"gradient_fill"},"s":{"a":0,"k":[-159.51,23.531]},"e":{"a":0,"k":[183.084,8.059]},"t":1,"nm":"G"},{"ty":"tr","p":{"a":0,"k":[-7.886,88.719]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"T"}],"nm":"R"}],"ip":0,"op":300,"st":0}],"slots":{"gradient_fill":{"p":{"a":0,"k":[0,0.1,0.1,0.2,1,1,0.1,0.2,0.1,1]}}},"markers":[]}

View file

@ -44,7 +44,7 @@ TEST_CASE("Lottie Slot", "[tvgLottie]")
auto picture = animation->picture(); auto picture = animation->picture();
REQUIRE(picture->type() == Type::Picture); REQUIRE(picture->type() == Type::Picture);
const char* slotJson = R"({"gradient_fill":{"p":{"a":0,"k":[0,0.1,0.1,0.2,1,1,0.1,0.2,0.1,1]}}})"; const char* slotJson = R"({"gradient_fill":{"p":{"p":2,"k":{"a":0,"k":[0,0.1,0.1,0.2,1,1,0.1,0.2,0.1,1]}}}})";
//Slot override before loaded //Slot override before loaded
REQUIRE(animation->override(slotJson) == Result::InsufficientCondition); REQUIRE(animation->override(slotJson) == Result::InsufficientCondition);