loader/lottie: Support image property

This change makes it possible to use Lottie animations
that contain images from embedded/external resources.
This commit is contained in:
Hermet Park 2023-08-07 15:31:54 +09:00 committed by Hermet Park
parent f31076a67e
commit f757eb6a71
6 changed files with 159 additions and 50 deletions

View file

@ -32,6 +32,7 @@
static void _updateChildren(LottieGroup* parent, int32_t frameNo, Shape* baseShape, bool reset);
static void _updateLayer(LottieLayer* root, LottieLayer* layer, int32_t frameNo, bool reset);
static bool _buildPrecomp(LottieComposition* comp, LottieGroup* parent);
static bool _invisible(LottieGroup* group, int32_t frameNo)
{
@ -228,6 +229,20 @@ static Shape* _updatePath(LottieGroup* parent, LottiePath* path, int32_t frameNo
}
static void _updateImage(LottieGroup* parent, LottieImage* image, int32_t frameNo, Shape* baseShape)
{
auto picture = Picture::gen();
if (image->size > 0) picture->load((const char*)image->b64Data, image->size, image->mimeType, false);
else picture->load(image->path);
if (baseShape) {
picture->transform(baseShape->transform());
picture->opacity(baseShape->opacity());
}
parent->scene->push(std::move(picture));
}
static void _updateChildren(LottieGroup* parent, int32_t frameNo, Shape* baseShape, bool reset)
{
if (parent->children.empty()) return;
@ -285,6 +300,10 @@ static void _updateChildren(LottieGroup* parent, int32_t frameNo, Shape* baseSha
TVGERR("LOTTIE", "TODO: update Round Corner");
break;
}
case LottieObject::Image: {
_updateImage(parent, static_cast<LottieImage*>(*child), frameNo, baseShape);
break;
}
default: {
TVGERR("LOTTIE", "TODO: Missing type??");
break;
@ -339,10 +358,6 @@ static void _updateLayer(LottieLayer* root, LottieLayer* layer, int32_t frameNo,
TVGERR("LOTTIE", "TODO: update Solid Layer");
break;
}
case LottieLayer::Image: {
TVGERR("LOTTIE", "TODO: update Image Layer");
break;
}
default: {
_updateChildren(layer, frameNo, nullptr, reset);
break;
@ -351,6 +366,50 @@ static void _updateLayer(LottieLayer* root, LottieLayer* layer, int32_t frameNo,
}
static void _buildReference(LottieComposition* comp, LottieLayer* layer)
{
for (auto asset = comp->assets.data; asset < comp->assets.end(); ++asset) {
if (strcmp(layer->refId, (*asset)->name)) continue;
auto assetLayer = static_cast<LottieLayer*>(*asset);
if (layer->type == LottieLayer::Precomp) {
if (_buildPrecomp(comp, assetLayer)) {
layer->children = assetLayer->children;
}
} else if (layer->type == LottieLayer::Image) {
layer->children.push(*asset);
}
layer->statical &= assetLayer->statical;
break;
}
}
static bool _buildPrecomp(LottieComposition* comp, LottieGroup* parent)
{
if (parent->children.count == 0) return false;
for (auto c = parent->children.data; c < parent->children.end(); ++c) {
auto child = static_cast<LottieLayer*>(*c);
//attach the referencing layer.
if (child->refId) _buildReference(comp, child);
if (child->pid == -1) continue;
//parenting
for (auto p = parent->children.data; p < parent->children.end(); ++p) {
if (c == p) continue;
auto parent = static_cast<LottieLayer*>(*p);
if (child->pid == parent->id) {
child->parent = parent;
parent->statical &= child->statical;
break;
}
}
}
return true;
}
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
@ -388,6 +447,8 @@ void LottieBuilder::build(LottieComposition* comp)
comp->scene = Scene::gen().release();
if (!comp->scene) return;
_buildPrecomp(comp, comp->root);
//TODO: Process repeater objects?
if (!update(comp, 0)) return;

View file

@ -51,7 +51,8 @@ static int _str2float(const char* str, int len)
void LottieLoader::clear()
{
if (copy) free((char*)content);
free(dirName);
dirName = nullptr;
size = 0;
content = nullptr;
copy = false;
@ -65,7 +66,7 @@ void LottieLoader::run(unsigned tid)
builder->update(comp, frameNo);
//initial loading
} else {
LottieParser parser(content);
LottieParser parser(content, dirName);
parser.parse();
comp = parser.comp;
builder->build(comp);
@ -217,6 +218,7 @@ bool LottieLoader::open(const string& path)
return false;
}
this->dirName = strDirname(path.c_str());
this->content = content;
this->copy = true;

View file

@ -41,6 +41,7 @@ public:
LottieBuilder* builder = nullptr;
LottieComposition* comp = nullptr;
char* dirName = nullptr; //base resource directory
bool copy = false; //"content" is owned by this loader
LottieLoader();

View file

@ -294,7 +294,18 @@ struct LottieGradientStroke : LottieObject, LottieStroke, LottieGradient
struct LottieImage : LottieObject
{
Surface surface;
union {
char* b64Data = nullptr;
char* path;
};
char* mimeType = nullptr;
uint32_t size = 0;
~LottieImage()
{
free(b64Data);
free(mimeType);
}
void prepare()
{

View file

@ -20,6 +20,7 @@
* SOFTWARE.
*/
#include "tvgStr.h"
#include "tvgLottieModel.h"
#include "tvgLottieParser.h"
@ -28,6 +29,18 @@
/* Internal Class Implementation */
/************************************************************************/
static constexpr const char B64_INDEX[256] =
{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 62, 63, 62, 62, 63, 52, 53, 54, 55, 56, 57,
58, 59, 60, 61, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6,
7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
25, 0, 0, 0, 0, 63, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36,
37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
};
static char* _int2str(int num)
{
char str[20];
@ -36,6 +49,31 @@ static char* _int2str(int num)
}
static void _decodeB64(const uint8_t* input, const size_t len, Array<char>& output)
{
int pad = len > 0 && (len % 4 || input[len - 1] == '=');
const size_t L = ((len + 3) / 4 - pad) * 4;
output.reserve(L / 4 * 3 + pad);
output.data[output.reserved - 1] = '\0';
for (size_t i = 0; i < L; i += 4) {
int n = B64_INDEX[input[i]] << 18 | B64_INDEX[input[i + 1]] << 12 | B64_INDEX[input[i + 2]] << 6 | B64_INDEX[input[i + 3]];
output.push(n >> 16);
output.push(n >> 8 & 0xFF);
output.push(n & 0xFF);
}
if (pad) {
if (pad > 1 ) TVGERR("LOTTIE", "b64 pad size = %d", pad);
int n = B64_INDEX[input[L]] << 18 | B64_INDEX[input[L + 1]] << 12;
output.last() = n >> 16;
if (len > L + 2 && input[L + 2] != '=') {
n |= B64_INDEX[input[L + 2]] << 6;
output.push(n >> 8 & 0xFF);
}
}
}
BlendMethod LottieParser::getBlendMethod()
{
switch (getInt()) {
@ -772,34 +810,52 @@ LottieImage* LottieParser::parseImage(const char* key)
if (!image) return nullptr;
//Used for Image Asset
const char* fileName = nullptr;
const char* relativePath = nullptr;
const char* data = nullptr;
const char* subPath = nullptr;
auto embedded = false;
do {
if (!strcmp(key, "w")) {
image->surface.w = getInt();
} else if (!strcmp(key, "h")) {
image->surface.h = getInt();
} else if (!strcmp(key, "u")) {
relativePath = getString();
if (!strcmp(key, "u")) {
subPath = getString();
} else if (!strcmp(key, "p")) {
data = getString();
} else if (!strcmp(key, "e")) {
embedded = getInt();
} else if (!strcmp(key, "p")) {
fileName = getString();
#if 0
} else if (!strcmp(key, "w")) {
auto w = getInt();
} else if (!strcmp(key, "h")) {
auto h = getInt();
#endif
} else skip(key);
} while ((key = nextObjectKey()));
image->prepare();
//embeded image resource. should start with "data:"
//header look like "data:image/png;base64," so need to skip till ','.
if (embedded && !strncmp(data, "data:", 5)) {
//figure out the mimetype
auto mimeType = data + 11;
auto needle = strstr(mimeType, ";");
image->mimeType = strDuplicate(mimeType, needle - mimeType);
// embeded resource should start with "data:"
if (embedded && !strncmp(fileName, "data:", 5)) {
//TODO:
//b64 data
auto b64Data = strstr(data, ",") + 1;
size_t length = strlen(data) - (b64Data - data);
Array<char> decoded;
_decodeB64(reinterpret_cast<const uint8_t*>(b64Data), length, decoded);
image->b64Data = decoded.data;
image->size = decoded.count;
decoded.data = nullptr;
//external image resource
} else {
//TODO:
auto len = strlen(dirName) + strlen(subPath) + strlen(data) + 1;
image->path = static_cast<char*>(malloc(len));
snprintf(image->path, len, "%s%s%s", dirName, subPath, data);
}
TVGLOG("LOTTIE", "Image is not supported: (dirPath + %s + %s)", relativePath, fileName);
image->prepare();
return image;
}
@ -989,32 +1045,6 @@ bool LottieParser::parse()
if (Invalid() || !comp->root) return false;
for (auto c = comp->root->children.data; c < comp->root->children.end(); ++c) {
auto child = static_cast<LottieLayer*>(*c);
//Organize the parent-chlid layers.
if (child->pid != -1) {
for (auto p = comp->root->children.data; p < comp->root->children.end(); ++p) {
if (c == p) continue;
auto parent = static_cast<LottieLayer*>(*p);
if (child->pid == parent->id) {
child->parent = parent;
break;
}
}
}
//Resolve Assets
if (child->refId) {
for (auto asset = comp->assets.data; asset < comp->assets.end(); ++asset) {
if (strcmp(child->refId, (*asset)->name)) continue;
if (child->type == LottieLayer::Precomp) {
child->children = static_cast<LottieLayer*>(*asset)->children;
child->statical &= (*asset)->statical;
} else if (child->type == LottieLayer::Image) {
//TODO:
}
}
}
}
comp->root->inFrame = comp->startFrame;
comp->root->outFrame = comp->endFrame;
return true;

View file

@ -29,11 +29,15 @@
struct LottieParser : LookaheadParserHandler
{
public:
LottieParser(const char *str) : LookaheadParserHandler(str) {}
LottieParser(const char *str, const char* dirName) : LookaheadParserHandler(str)
{
this->dirName = dirName;
}
bool parse();
LottieComposition* comp = nullptr;
const char* dirName = nullptr; //base resource directory
private:
BlendMethod getBlendMethod();