mirror of
https://github.com/thorvg/thorvg.git
synced 2025-06-09 14:13:43 +00:00
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:
parent
f31076a67e
commit
f757eb6a71
6 changed files with 159 additions and 50 deletions
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Add table
Reference in a new issue