diff --git a/src/loaders/lottie/tvgLottieBuilder.cpp b/src/loaders/lottie/tvgLottieBuilder.cpp index aad9f061..d0eecf62 100644 --- a/src/loaders/lottie/tvgLottieBuilder.cpp +++ b/src/loaders/lottie/tvgLottieBuilder.cpp @@ -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(*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(*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(*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(*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; diff --git a/src/loaders/lottie/tvgLottieLoader.cpp b/src/loaders/lottie/tvgLottieLoader.cpp index 262bb8de..a689f9fb 100644 --- a/src/loaders/lottie/tvgLottieLoader.cpp +++ b/src/loaders/lottie/tvgLottieLoader.cpp @@ -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; diff --git a/src/loaders/lottie/tvgLottieLoader.h b/src/loaders/lottie/tvgLottieLoader.h index f529bab4..87427082 100644 --- a/src/loaders/lottie/tvgLottieLoader.h +++ b/src/loaders/lottie/tvgLottieLoader.h @@ -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(); diff --git a/src/loaders/lottie/tvgLottieModel.h b/src/loaders/lottie/tvgLottieModel.h index c91b82e5..dff1e532 100644 --- a/src/loaders/lottie/tvgLottieModel.h +++ b/src/loaders/lottie/tvgLottieModel.h @@ -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() { diff --git a/src/loaders/lottie/tvgLottieParser.cpp b/src/loaders/lottie/tvgLottieParser.cpp index 70d68155..c1e6035f 100644 --- a/src/loaders/lottie/tvgLottieParser.cpp +++ b/src/loaders/lottie/tvgLottieParser.cpp @@ -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& 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 decoded; + _decodeB64(reinterpret_cast(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(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(*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(*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(*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; diff --git a/src/loaders/lottie/tvgLottieParser.h b/src/loaders/lottie/tvgLottieParser.h index 668c4e1d..6d3de409 100644 --- a/src/loaders/lottie/tvgLottieParser.h +++ b/src/loaders/lottie/tvgLottieParser.h @@ -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();