diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..4483e7fe --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "src/lottie/JSON"] + path = src/lottie/JSON + url = https://github.com/X-Ryl669/JSON.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 38a98622..2cbd91e6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,6 +34,9 @@ option(LOTTIE_CACHE "Enable LOTTIE CACHE SUPPORT" ON) option(LOTTIE_TEST "Build LOTTIE AUTOTESTS" OFF) option(LOTTIE_CCACHE "Enable LOTTIE ccache SUPPORT" OFF) option(LOTTIE_ASAN "Compile with asan" OFF) +option(LOTTIE_JSON "Use readonly JSON parser" OFF) +option(LOTTIE_MEMSHRINK "Minimize memory but increase parsing time" OFF) +option(LOTTIE_EXAMPLE "Build examples" ON) set(LOTTIE_MODULE_PATH "${CMAKE_SHARED_LIBRARY_PREFIX}rlottie-image-loader${CMAKE_SHARED_LIBRARY_SUFFIX}" CACHE STRING "Absolute or relative path to dynamic loader plugin.") @@ -137,7 +140,10 @@ endif (NOT LIB_INSTALL_DIR) #declare source and include files add_subdirectory(inc) add_subdirectory(src) -add_subdirectory(example) + +if (LOTTIE_EXAMPLE) + add_subdirectory(example) +endif() if (LOTTIE_TEST) enable_testing() diff --git a/cmake/config.h.in b/cmake/config.h.in index ca42a9e1..5f3f1a9d 100644 --- a/cmake/config.h.in +++ b/cmake/config.h.in @@ -17,3 +17,15 @@ #ifdef LOTTIE_CACHE #define LOTTIE_CACHE_SUPPORT #endif + +#cmakedefine LOTTIE_JSON + +#ifdef LOTTIE_JSON +#define LOTTIE_JSON_SUPPORT +#endif + +#cmakedefine LOTTIE_MEMSHRINK + +#ifdef LOTTIE_MEMSHRINK +#define LOTTIE_MEMSHRINK_SUPPORT +#endif \ No newline at end of file diff --git a/example/lottieperf.cpp b/example/lottieperf.cpp index f1b6cb06..0c8250f3 100644 --- a/example/lottieperf.cpp +++ b/example/lottieperf.cpp @@ -75,6 +75,11 @@ class PerfTest { _resourceList = jsonFiles(std::string(DEMO_DIR)); } + explicit PerfTest(const std::string& filename): + _resourceCount(1), _iterations(1) + { + _resourceList.push_back(filename); + } void test(bool async) { setup(); @@ -125,7 +130,7 @@ class PerfTest static int help() { - std::cout<<"\nUsage : ./perf [--sync] [-c] [resource count] [-i] [iteration count] \n"; + std::cout<<"\nUsage : ./perf [--sync] [-c] [resource count] [-i] [iteration count] [-f] [filename] \n"; std::cout<<"\nExample : ./perf -c 50 -i 100 \n"; std::cout<<"\n\t runs perf test for 100 iterations. renders 50 resource per iteration\n\n"; return 0; @@ -137,6 +142,7 @@ main(int argc, char ** argv) size_t resourceCount = 250; size_t iterations = 500; auto index = 0; + std::string filename; while (index < argc) { const char* option = argv[index]; @@ -151,10 +157,14 @@ main(int argc, char ** argv) } else if (!strcmp(option,"-i")) { iterations = (index < argc) ? atoi(argv[index]) : iterations; index++; + } else if (!strcmp(option,"-f")) { + filename = (index < argc) ? argv[index] : ""; + index++; } } - PerfTest obj(resourceCount, iterations); + + PerfTest obj = filename.length() ? PerfTest(filename) : PerfTest(resourceCount, iterations); obj.test(async); return 0; } diff --git a/example/lottieview.h b/example/lottieview.h index ca8de7e1..4e48eebc 100644 --- a/example/lottieview.h +++ b/example/lottieview.h @@ -41,6 +41,7 @@ #include #include #include +#include class RenderStrategy { public: diff --git a/example/lottieviewtest.cpp b/example/lottieviewtest.cpp index 402fcd27..fc775c6c 100644 --- a/example/lottieviewtest.cpp +++ b/example/lottieviewtest.cpp @@ -53,6 +53,19 @@ class LottieViewTest ecore_animator_frametime_set(1.0f/120.0f); } + void show(const char * filepath) { + std::unique_ptr view(new LottieView(mApp->evas(), mStrategy)); + view->setFilePath(filepath); + view->setPos(3, 3); + int vw = mApp->width() - 6; + view->setSize(vw, vw); + view->show(); + view->play(); + view->loop(true); + mViews.push_back(std::move(view)); + } + + void show(int numberOfImage) { auto resource = EvasApp::jsonFiles(std::string(DEMO_DIR)); @@ -92,9 +105,10 @@ class LottieViewTest } static int help() { - printf("Usage ./lottieviewTest [-s] [strategy] [-t] [timeout] [-c] [count]\n"); + printf("Usage ./lottieviewTest [-s] [strategy] [-t] [timeout] [-c] [count] [-f] path\n"); printf("\n \t-t : timeout duration in seconds\n"); printf("\n \t-c : number of resource in the grid\n"); + printf("\n \t-f : File to play\n"); printf("\n \t-s : Rendering Strategy\n"); printf("\t\t 0 - Test Lottie SYNC Renderer with CPP API\n"); printf("\t\t 1 - Test Lottie ASYNC Renderer with CPP API\n"); @@ -134,6 +148,7 @@ main(int argc, char **argv) auto index = 0; double timeOut = 0; size_t itemCount = 250; + std::string filePath; while (index < argc) { const char* option = argv[index]; index++; @@ -148,6 +163,9 @@ main(int argc, char **argv) } else if (!strcmp(option,"-c")) { itemCount = (index < argc) ? atoi(argv[index]) : 10; index++; + } else if (!strcmp(option,"-f")) { + filePath = argv[index]; + index++; } } @@ -155,7 +173,8 @@ main(int argc, char **argv) app->setup(); LottieViewTest *view = new LottieViewTest(app, st, timeOut); - view->show(itemCount); + if (filePath.length()) view->show(filePath.c_str()); + else view->show(itemCount); app->addExitCb(onExitCb, view); diff --git a/inc/rlottie.h b/inc/rlottie.h index 67565691..6d795c94 100644 --- a/inc/rlottie.h +++ b/inc/rlottie.h @@ -24,8 +24,8 @@ #define _RLOTTIE_H_ #include -#include #include +#include "../src/vector/vvector.h" #if defined _WIN32 || defined __CYGWIN__ #ifdef RLOTTIE_BUILD @@ -142,7 +142,7 @@ class RLOTTIE_API Surface { * @brief Sets the Draw Area available on the Surface. * * Lottie will use the draw region size to generate frame image - * and will update only the draw rgion of the surface. + * and will update only the draw region of the surface. * * @param[in] x region area x position. * @param[in] y region area y position. @@ -255,7 +255,7 @@ class RLOTTIE_API Surface { }mDrawArea; }; -using MarkerList = std::vector>; +using MarkerList = VVector>; /** * @brief https://helpx.adobe.com/after-effects/using/layer-markers-composition-markers.html * Markers exported form AE are used to describe a segmnet of an animation {comment/tag , startFrame, endFrame} @@ -263,7 +263,7 @@ using MarkerList = std::vector>; * start frame and duration of that segment. */ -using LayerInfoList = std::vector>; +using LayerInfoList = VVector>; using ColorFilter = std::function; @@ -308,6 +308,21 @@ class RLOTTIE_API Animation { loadFromData(std::string jsonData, const std::string &key, const std::string &resourcePath="", bool cachePolicy=true); + /** + * @brief Constructs an animation object from JSON string read only data. + * + * @param[in] data The JSON string internal data (not modified) + * @param[in] len The pointed data array length in bytes + * @param[in] resourcePath the path will be used to search for external resource. + * + * @return Animation object that can render the contents of the + * Lottie resource represented by JSON string data. + * + * @internal + */ + static std::unique_ptr + loadFromROData(const char * data, const size_t len, const char * resourcePath); + /** * @brief Constructs an animation object from JSON string data and update. * the color properties using ColorFilter. @@ -421,6 +436,18 @@ class RLOTTIE_API Animation { */ void renderSync(size_t frameNo, Surface surface, bool keepAspectRatio=true); + /** + * @brief Renders the content to partial surface synchronously. + * for performance use the async rendering @see render + * + * @param[in] frameNo Content corresponds to the @p frameNo needs to be drawn + * @param[in] surface Surface in which content will be drawn + * + * @internal + */ + void renderPartialSync(size_t frameNo, Surface surface); + + /** * @brief Returns root layer of the composition updated with * content of the Lottie resource at frame number @p frameNo. diff --git a/inc/rlottie_capi.h b/inc/rlottie_capi.h index 80907ae6..00c60ac9 100644 --- a/inc/rlottie_capi.h +++ b/inc/rlottie_capi.h @@ -106,6 +106,21 @@ RLOTTIE_API Lottie_Animation *lottie_animation_from_file(const char *path); */ RLOTTIE_API Lottie_Animation *lottie_animation_from_data(const char *data, const char *key, const char *resource_path); +/** + * @brief Constructs an animation object from JSON string data. + * + * @param[in] data The JSON string data. + * @param[in] len The data length in bytes + * @param[in] resource_path the path that will be used to load external resource needed by the JSON data. + * + * @return Animation object that can build the contents of the + * Lottie resource represented by JSON string data. + * + * @ingroup Lottie_Animation + * @internal + */ +RLOTTIE_API Lottie_Animation *lottie_animation_from_rodata(const char *data, const size_t len, const char *resourcePath); + /** * @brief Free given Animation object resource. * @@ -231,6 +246,25 @@ RLOTTIE_API size_t lottie_animation_get_frame_at_pos(const Lottie_Animation *ani */ RLOTTIE_API void lottie_animation_render(Lottie_Animation *animation, size_t frame_num, uint32_t *buffer, size_t width, size_t height, size_t bytes_per_line); +/** + * @brief Request to render the content of the frame @p frame_num to buffer @p buffer. + * + * @param[in] animation Animation object. + * @param[in] frame_num the frame number needs to be rendered. + * @param[in] buffer surface buffer use for rendering. + * @param[in] width width of the surface + * @param[in] height height of the surface + * @param[in] top offset to render from + * @param[in] bottom offset to render from + * @param[in] bytes_per_line stride of the surface in bytes. + * + * + * @ingroup Lottie_Animation + * @internal + */ +RLOTTIE_API void lottie_animation_render_partial(Lottie_Animation *animation, size_t frame_num, uint32_t *buffer, size_t width, size_t height, size_t top, size_t bottom, size_t bytes_per_line); + + /** * @brief Request to render the content of the frame @p frame_num to buffer @p buffer asynchronously. * diff --git a/inc/rlottiecommon.h b/inc/rlottiecommon.h index 784fbe28..13525324 100644 --- a/inc/rlottiecommon.h +++ b/inc/rlottiecommon.h @@ -37,6 +37,24 @@ #endif #endif +#if defined LOTTIE_JSON_SUPPORT + #define RLOTTIE_FEATURE_RO_JSON 1 +#else + #define RLOTTIE_FEATURE_RO_JSON 0 +#endif + +#if defined LOTTIE_THREAD_SUPPORT + #define RLOTTIE_FEATURE_THREAD 1 +#else + #define RLOTTIE_FEATURE_THREAD 0 +#endif + +#if defined LOTTIE_NO_PARTIAL_RENDER + #define RLOTTIE_FEATURE_PARTIAL_RENDER 0 +#else + #define RLOTTIE_FEATURE_PARTIAL_RENDER 1 +#endif + /** * @defgroup Lottie_Animation Lottie_Animation diff --git a/meson.build b/meson.build index 50404c58..5f5daaad 100644 --- a/meson.build +++ b/meson.build @@ -54,6 +54,14 @@ if get_option('dumptree') == true config_h.set10('LOTTIE_DUMP_TREE_SUPPORT', true) endif +if get_option('json') == true + config_h.set10('LOTTIE_JSON_SUPPORT', true) +endif + +if get_option('memshrink') == true + config_h.set10('LOTTIE_MEMSHRINK_SUPPORT', true) +endif + configure_file( output: 'config.h', diff --git a/meson_options.txt b/meson_options.txt index 25d8ec38..d8e4cb12 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -42,5 +42,12 @@ option('cmake', value: false, description: 'Enable Generating CMake config files') +option('json', + type: 'boolean', + value: false, + description: 'Use readonly JSON parser instead of RapidJSON') - +option('memshrink', + type: 'boolean', + value: false, + description: 'Minimize memory usage but increase parsing time') diff --git a/src/binding/c/lottieanimation_capi.cpp b/src/binding/c/lottieanimation_capi.cpp index 53ca7bda..31d142f1 100644 --- a/src/binding/c/lottieanimation_capi.cpp +++ b/src/binding/c/lottieanimation_capi.cpp @@ -91,6 +91,17 @@ RLOTTIE_API Lottie_Animation_S *lottie_animation_from_data(const char *data, con } } +RLOTTIE_API Lottie_Animation_S *lottie_animation_from_rodata(const char *data, const size_t len, const char *resourcePath) +{ + if (auto animation = Animation::loadFromROData(data, len, resourcePath) ) { + Lottie_Animation_S *handle = new Lottie_Animation_S(); + handle->mAnimation = std::move(animation); + return handle; + } + return nullptr; +} + + RLOTTIE_API void lottie_animation_destroy(Lottie_Animation_S *animation) { if (animation) { @@ -168,6 +179,24 @@ lottie_animation_render(Lottie_Animation_S *animation, animation->mAnimation->renderSync(frame_number, surface); } +RLOTTIE_API void +lottie_animation_render_partial(Lottie_Animation_S *animation, + size_t frame_number, + uint32_t *buffer, + size_t width, + size_t height, + size_t top, + size_t bottom, + size_t bytes_per_line) +{ + if (!animation) return; + + rlottie::Surface surface(buffer, width, height, bytes_per_line); + surface.setDrawRegion(0, top, width, bottom - top); + animation->mAnimation->renderPartialSync(frame_number, surface); +} + + RLOTTIE_API void lottie_animation_render_async(Lottie_Animation_S *animation, size_t frame_number, diff --git a/src/lottie/CMakeLists.txt b/src/lottie/CMakeLists.txt index 09840b9b..3014efad 100644 --- a/src/lottie/CMakeLists.txt +++ b/src/lottie/CMakeLists.txt @@ -6,11 +6,23 @@ target_sources(rlottie "${CMAKE_CURRENT_LIST_DIR}/lottieloader.cpp" "${CMAKE_CURRENT_LIST_DIR}/lottiemodel.cpp" "${CMAKE_CURRENT_LIST_DIR}/lottieproxymodel.cpp" - "${CMAKE_CURRENT_LIST_DIR}/lottieparser.cpp" "${CMAKE_CURRENT_LIST_DIR}/lottieanimation.cpp" "${CMAKE_CURRENT_LIST_DIR}/lottiekeypath.cpp" ) +if (LOTTIE_JSON) +target_sources(rlottie + PRIVATE + "${CMAKE_CURRENT_LIST_DIR}/lottieroparser.cpp" + ) +else () +target_sources(rlottie + PRIVATE + "${CMAKE_CURRENT_LIST_DIR}/lottieparser.cpp" + ) +endif() + + target_include_directories(rlottie PRIVATE "${CMAKE_CURRENT_LIST_DIR}" diff --git a/src/lottie/JSON b/src/lottie/JSON new file mode 160000 index 00000000..d9b64a9f --- /dev/null +++ b/src/lottie/JSON @@ -0,0 +1 @@ +Subproject commit d9b64a9f6dda68f7da5e7fb8dedf173bdf0a8440 diff --git a/src/lottie/lottieanimation.cpp b/src/lottie/lottieanimation.cpp index afcc400b..2ec336d3 100644 --- a/src/lottie/lottieanimation.cpp +++ b/src/lottie/lottieanimation.cpp @@ -25,6 +25,7 @@ #include "rlottie.h" #include +#include "vvector.h" using namespace rlottie; using namespace rlottie::internal; @@ -49,13 +50,16 @@ class AnimationImpl { public: void init(std::shared_ptr composition); bool update(size_t frameNo, const VSize &size, bool keepAspectRatio); + bool updatePartial(size_t frameNo, const VSize &size, uint32_t offset); VSize size() const { return mModel->size(); } double duration() const { return mModel->duration(); } double frameRate() const { return mModel->frameRate(); } size_t totalFrame() const { return mModel->totalFrame(); } size_t frameAtPos(double pos) const { return mModel->frameAtPos(pos); } + bool checkRender(); Surface render(size_t frameNo, const Surface &surface, bool keepAspectRatio); + Surface renderPartial(size_t frameNo, const Surface &surface); std::future renderAsync(size_t frameNo, Surface &&surface, bool keepAspectRatio); const LOTLayerNode * renderTree(size_t frameNo, const VSize &size); @@ -105,16 +109,34 @@ bool AnimationImpl::update(size_t frameNo, const VSize &size, return mRenderer->update(int(frameNo), size, keepAspectRatio); } -Surface AnimationImpl::render(size_t frameNo, const Surface &surface, - bool keepAspectRatio) +bool AnimationImpl::updatePartial(size_t frameNo, const VSize &size, uint32_t offset) +{ + frameNo += mModel->startFrame(); + + if (frameNo > mModel->endFrame()) frameNo = mModel->endFrame(); + + if (frameNo < mModel->startFrame()) frameNo = mModel->startFrame(); + + return mRenderer->updatePartial(int(frameNo), size, offset); +} + + +bool AnimationImpl::checkRender() { bool renderInProgress = mRenderInProgress.load(); if (renderInProgress) { vCritical << "Already Rendering Scheduled for this Animation"; - return surface; + return false; } - mRenderInProgress.store(true); + return true; +} + +Surface AnimationImpl::render(size_t frameNo, const Surface &surface, + bool keepAspectRatio) +{ + if (!checkRender()) return surface; + update( frameNo, VSize(int(surface.drawRegionWidth()), int(surface.drawRegionHeight())), @@ -125,6 +147,21 @@ Surface AnimationImpl::render(size_t frameNo, const Surface &surface, return surface; } +Surface AnimationImpl::renderPartial(size_t frameNo, const Surface &surface) +{ + if (!checkRender()) return surface; + + updatePartial( + frameNo, + VSize(int(surface.drawRegionWidth()), int(surface.drawRegionHeight())), + uint32_t(surface.drawRegionPosY())); + mRenderer->renderPartial(surface); + mRenderInProgress.store(false); + + return surface; +} + + void AnimationImpl::init(std::shared_ptr composition) { mModel = composition.get(); @@ -149,10 +186,10 @@ void AnimationImpl::init(std::shared_ptr composition) * just waits for new task on its own queue. */ class RenderTaskScheduler { - const unsigned _count{std::thread::hardware_concurrency()}; - std::vector _threads; - std::vector> _q{_count}; - std::atomic _index{0}; + const unsigned _count{std::thread::hardware_concurrency()}; + VVector _threads; + VVector> _q{_count}; + std::atomic _index{0}; void run(unsigned i) { @@ -308,6 +345,23 @@ std::unique_ptr Animation::loadFromData(std::string jsonData, return nullptr; } +std::unique_ptr Animation::loadFromROData(const char * data, const size_t len, + const char * resourcePath) +{ + if (!data || !len) { + vWarning << "json data is empty"; + return nullptr; + } + + auto composition = model::loadFromROData(data, len, resourcePath); + if (composition) { + auto animation = std::unique_ptr(new Animation); + animation->d->init(std::move(composition)); + return animation; + } + return nullptr; +} + std::unique_ptr Animation::loadFromFile(const std::string &path, bool cachePolicy) { @@ -370,6 +424,11 @@ void Animation::renderSync(size_t frameNo, Surface surface, { d->render(frameNo, surface, keepAspectRatio); } +void Animation::renderPartialSync(size_t frameNo, Surface surface) +{ + d->renderPartial(frameNo, surface); +} + const LayerInfoList &Animation::layers() const { diff --git a/src/lottie/lottiefiltermodel.h b/src/lottie/lottiefiltermodel.h index 4f78c11a..a2ed8e97 100644 --- a/src/lottie/lottiefiltermodel.h +++ b/src/lottie/lottiefiltermodel.h @@ -307,7 +307,7 @@ class FilterData { return *result; } std::bitset<32> mBitset{0}; - std::vector mFilters; + VVector mFilters; }; template @@ -366,7 +366,7 @@ class Filter : public FilterBase { CapStyle capStyle() const { return this->model()->capStyle(); } JoinStyle joinStyle() const { return this->model()->joinStyle(); } bool hasDashInfo() const { return this->model()->hasDashInfo(); } - void getDashInfo(int frameNo, std::vector& result) const + void getDashInfo(int frameNo, VVector& result) const { return this->model()->getDashInfo(frameNo, result); } diff --git a/src/lottie/lottieitem.cpp b/src/lottie/lottieitem.cpp index cb1bc2cd..ba2849f1 100644 --- a/src/lottie/lottieitem.cpp +++ b/src/lottie/lottieitem.cpp @@ -147,6 +147,35 @@ bool renderer::Composition::update(int frameNo, const VSize &size, return true; } +bool renderer::Composition::updatePartial(int frameNo, const VSize &size, + unsigned int offset) +{ + // check if cached frame is same as requested frame. + if ((mViewSize.width() == size.width()) && (mCurFrameNo == frameNo) && + (mOffset == offset)) + return false; + + mViewSize = size; + mCurFrameNo = frameNo; + mKeepAspectRatio = false; + mOffset = offset; + + /* + * if viewbox dosen't scale exactly to the viewport + * we scale the viewbox keeping AspectRatioPreserved and then align the + * viewbox to the viewport using AlignCenter rule. + */ + VMatrix m; + VSize viewPort = mViewSize; + VSize viewBox = mModel->size(); + float sx = float(viewPort.width()) / viewBox.width(); + float ty = offset; + m.translate(0, -ty).scale(sx, sx); + + mRootLayer->update(frameNo, m, 1.0); + return true; +} + bool renderer::Composition::render(const rlottie::Surface &surface) { mSurface.reset(reinterpret_cast(surface.buffer()), @@ -170,6 +199,30 @@ bool renderer::Composition::render(const rlottie::Surface &surface) return true; } +bool renderer::Composition::renderPartial(const rlottie::Surface &surface) +{ + mSurface.reset(reinterpret_cast(surface.buffer()), + uint32_t(surface.width()), uint32_t(surface.drawRegionHeight()), + uint32_t(surface.bytesPerLine()), + VBitmap::Format::ARGB32_Premultiplied); + + /* schedule all preprocess task for this frame at once. + */ + VRect clip(0, 0, int(surface.drawRegionWidth()), + int(surface.drawRegionHeight())); + mRootLayer->preprocess(clip); + + VPainter painter(&mSurface); + // set sub surface area for drawing. + painter.setDrawRegion( + VRect(int(surface.drawRegionPosX()), 0, + int(surface.drawRegionWidth()), int(surface.drawRegionHeight()))); + mRootLayer->render(&painter, {}, {}, mSurfaceCache); + painter.end(); + return true; +} + + void renderer::Mask::update(int frameNo, const VMatrix &parentMatrix, float /*parentAlpha*/, const DirtyFlag &flag) { @@ -443,7 +496,7 @@ VMatrix renderer::Layer::matrix(int frameNo) const bool renderer::Layer::visible() const { return (frameNo() >= mLayerData->inFrame() && - frameNo() < mLayerData->outFrame()); + frameNo() <= mLayerData->outFrame()); } void renderer::Layer::preprocess(const VRect &clip) @@ -825,7 +878,7 @@ renderer::ShapeLayer::ShapeLayer(model::Layer *layerData, { mRoot->addChildren(layerData, allocator); - std::vector list; + VVector list; mRoot->processPaintItems(list); if (layerData->hasPathOperator()) { @@ -991,14 +1044,14 @@ void renderer::Group::applyTrim() } } -void renderer::Group::renderList(std::vector &list) +void renderer::Group::renderList(VVector &list) { for (const auto &content : mContents) { content->renderList(list); } } -void renderer::Group::processPaintItems(std::vector &list) +void renderer::Group::processPaintItems(VVector &list) { size_t curOpCount = list.size(); for (auto i = mContents.rbegin(); i != mContents.rend(); ++i) { @@ -1025,7 +1078,7 @@ void renderer::Group::processPaintItems(std::vector &list) } } -void renderer::Group::processTrimItems(std::vector &list) +void renderer::Group::processTrimItems(VVector &list) { size_t curOpCount = list.size(); for (auto i = mContents.rbegin(); i != mContents.rend(); ++i) { @@ -1207,7 +1260,7 @@ void renderer::Paint::updateRenderNode() } } -void renderer::Paint::renderList(std::vector &list) +void renderer::Paint::renderList(VVector &list) { if (mRenderNodeUpdate) { updateRenderNode(); @@ -1225,11 +1278,11 @@ void renderer::Paint::renderList(std::vector &list) if (mContentToRender) list.push_back(&mDrawable); } -void renderer::Paint::addPathItems(std::vector &list, +void renderer::Paint::addPathItems(VVector &list, size_t startOffset) { - std::copy(list.begin() + startOffset, list.end(), - back_inserter(mPathItems)); + mPathItems.reserve(mPathItems.size() + list.end() - list.begin() - startOffset); + std::copy(list.begin() + startOffset, list.end(), std::back_inserter(mPathItems)); } renderer::Fill::Fill(model::Fill *data) @@ -1281,7 +1334,7 @@ renderer::Stroke::Stroke(model::Stroke *data) } } -static vthread_local std::vector Dash_Vector; +static vthread_local VVector Dash_Vector; bool renderer::Stroke::updateContent(int frameNo, const VMatrix &matrix, float alpha) @@ -1426,11 +1479,11 @@ void renderer::Trim::update() } } -void renderer::Trim::addPathItems(std::vector &list, +void renderer::Trim::addPathItems(VVector &list, size_t startOffset) { - std::copy(list.begin() + startOffset, list.end(), - back_inserter(mPathItems)); + mPathItems.reserve(mPathItems.size() + list.end() - list.begin() - startOffset); + std::copy(list.begin() + startOffset, list.end(), std::back_inserter(mPathItems)); } renderer::Repeater::Repeater(model::Repeater *data, VArenaAlloc *allocator) @@ -1484,7 +1537,7 @@ void renderer::Repeater::update(int frameNo, const VMatrix &parentMatrix, } } -void renderer::Repeater::renderList(std::vector &list) +void renderer::Repeater::renderList(VVector &list) { if (mHidden) return; return renderer::Group::renderList(list); diff --git a/src/lottie/lottieitem.h b/src/lottie/lottieitem.h index 001ce667..032464ef 100644 --- a/src/lottie/lottieitem.h +++ b/src/lottie/lottieitem.h @@ -98,7 +98,7 @@ class SurfaceCache { { if (mCache.empty()) return {width, height, format}; - auto surface = mCache.back(); + VBitmap surface = mCache.back(); surface.reset(width, height, format); mCache.pop_back(); @@ -108,7 +108,7 @@ class SurfaceCache { void release_surface(VBitmap &surface) { mCache.push_back(surface); } private: - std::vector mCache; + VVector mCache; }; class Drawable final : public VDrawable { @@ -128,9 +128,9 @@ class Drawable final : public VDrawable { struct CApiData { CApiData(); LOTLayerNode mLayer; - std::vector mMasks; - std::vector mLayers; - std::vector mCNodeList; + VVector mMasks; + VVector mLayers; + VVector mCNodeList; }; class Clipper { @@ -179,7 +179,7 @@ class LayerMask { void preprocess(const VRect &clip); public: - std::vector mMasks; + VVector mMasks; VRle mRle; bool mStatic{true}; bool mDirty{true}; @@ -191,10 +191,12 @@ class Composition { public: explicit Composition(std::shared_ptr composition); bool update(int frameNo, const VSize &size, bool keepAspectRatio); + bool updatePartial(int frameNo, const VSize &size, unsigned int offset); VSize size() const { return mViewSize; } void buildRenderTree(); const LOTLayerNode *renderTree() const; bool render(const rlottie::Surface &surface); + bool renderPartial(const rlottie::Surface &surface); void setValue(const std::string &keypath, LOTVariant &value); private: @@ -207,6 +209,7 @@ class Composition { VArenaAlloc mAllocator{2048}; int mCurFrameNo; bool mKeepAspectRatio{true}; + unsigned int mOffset{0}; }; class Layer { @@ -235,9 +238,9 @@ class Layer { bool visible() const; virtual void buildLayerNode(); LOTLayerNode & clayer() { return mCApiData->mLayer; } - std::vector &clayers() { return mCApiData->mLayers; } - std::vector & cmasks() { return mCApiData->mMasks; } - std::vector & cnodes() { return mCApiData->mCNodeList; } + VVector &clayers() { return mCApiData->mLayers; } + VVector & cmasks() { return mCApiData->mMasks; } + VVector & cnodes() { return mCApiData->mCNodeList; } const char * name() const { return mLayerData->name(); } virtual bool resolveKeyPath(LOTKeyPath &keyPath, uint32_t depth, LOTVariant &value); @@ -290,7 +293,7 @@ class CompLayer final : public Layer { SurfaceCache &cache); private: - std::vector mLayers; + VVector mLayers; std::unique_ptr mClipper; }; @@ -323,7 +326,7 @@ class ShapeLayer final : public Layer { protected: void preprocessStage(const VRect &clip) final; void updateContent() final; - std::vector mDrawableList; + VVector mDrawableList; Group * mRoot{nullptr}; }; @@ -360,7 +363,7 @@ class Object { Object & operator=(Object &&) noexcept = delete; virtual void update(int frameNo, const VMatrix &parentMatrix, float parentAlpha, const DirtyFlag &flag) = 0; - virtual void renderList(std::vector &) {} + virtual void renderList(VVector &) {} virtual bool resolveKeyPath(LOTKeyPath &, uint32_t, LOTVariant &) { return false; @@ -377,9 +380,9 @@ class Group : public Object { void update(int frameNo, const VMatrix &parentMatrix, float parentAlpha, const DirtyFlag &flag) override; void applyTrim(); - void processTrimItems(std::vector &list); - void processPaintItems(std::vector &list); - void renderList(std::vector &list) override; + void processTrimItems(VVector &list); + void processPaintItems(VVector &list); + void renderList(VVector &list) override; Object::Type type() const final { return Object::Type::Group; } const VMatrix &matrix() const { return mMatrix; } const char * name() const @@ -391,7 +394,7 @@ class Group : public Object { LOTVariant &value) override; protected: - std::vector mContents; + VVector mContents; VMatrix mMatrix; private: @@ -503,10 +506,10 @@ class Polystar final : public Shape { class Paint : public Object { public: Paint(bool staticContent); - void addPathItems(std::vector &list, size_t startOffset); + void addPathItems(VVector &list, size_t startOffset); void update(int frameNo, const VMatrix &parentMatrix, float parentAlpha, const DirtyFlag &flag) override; - void renderList(std::vector &list) final; + void renderList(VVector &list) final; Object::Type type() const final { return Object::Type::Paint; } protected: @@ -517,7 +520,7 @@ class Paint : public Object { void updateRenderNode(); protected: - std::vector mPathItems; + VVector mPathItems; Drawable mDrawable; VPath mPath; DirtyFlag mFlag; @@ -583,7 +586,7 @@ class Trim final : public Object { const DirtyFlag &flag) final; Object::Type type() const final { return Object::Type::Trim; } void update(); - void addPathItems(std::vector &list, size_t startOffset); + void addPathItems(VVector &list, size_t startOffset); private: bool pathDirty() const @@ -598,7 +601,7 @@ class Trim final : public Object { model::Trim::Segment mSegment{}; }; Cache mCache; - std::vector mPathItems; + VVector mPathItems; model::Trim * mData{nullptr}; VPathMesure mPathMesure; bool mDirty{true}; @@ -609,7 +612,7 @@ class Repeater final : public Group { explicit Repeater(model::Repeater *data, VArenaAlloc *allocator); void update(int frameNo, const VMatrix &parentMatrix, float parentAlpha, const DirtyFlag &flag) final; - void renderList(std::vector &list) final; + void renderList(VVector &list) final; private: model::Repeater *mRepeaterData{nullptr}; diff --git a/src/lottie/lottieitem_capi.cpp b/src/lottie/lottieitem_capi.cpp index 31ca54a6..4049a642 100644 --- a/src/lottie/lottieitem_capi.cpp +++ b/src/lottie/lottieitem_capi.cpp @@ -237,8 +237,8 @@ void renderer::Drawable::sync() if (mFlag & DirtyState::Path) { applyDashOp(); - const std::vector &elm = mPath.elements(); - const std::vector & pts = mPath.points(); + const VVector &elm = mPath.elements(); + const VVector & pts = mPath.points(); const float *ptPtr = reinterpret_cast(pts.data()); const char * elmPtr = reinterpret_cast(elm.data()); mCNode->mPath.elmPtr = elmPtr; diff --git a/src/lottie/lottiekeypath.h b/src/lottie/lottiekeypath.h index 2c53287e..6b3fcd81 100644 --- a/src/lottie/lottiekeypath.h +++ b/src/lottie/lottiekeypath.h @@ -24,8 +24,8 @@ #define LOTTIEKEYPATH_H #include -#include #include "vglobal.h" +#include "vvector.h" class LOTKeyPath { public: @@ -47,7 +47,7 @@ class LOTKeyPath { size_t size() const { return mKeys.size() - 1; } private: - std::vector mKeys; + VVector mKeys; }; #endif // LOTTIEKEYPATH_H diff --git a/src/lottie/lottieloader.cpp b/src/lottie/lottieloader.cpp index 8a52949e..9410e450 100644 --- a/src/lottie/lottieloader.cpp +++ b/src/lottie/lottieloader.cpp @@ -128,18 +128,26 @@ std::shared_ptr model::loadFromFile(const std::string &path, vCritical << "failed to open file = " << path.c_str(); return {}; } else { - std::string content; + // Not using string here since it's over allocating up to 2x the input size + f.seekg(0, std::ios_base::end); + std::streampos sz = f.tellg(); + f.seekg(0, std::ios_base::beg); - std::getline(f, content, '\0'); - f.close(); + if (!sz) return {}; - if (content.empty()) return {}; + char * buf = new char[sz + std::streampos{1}]; + f.read(buf, sz); + f.close(); - auto obj = internal::model::parse(const_cast(content.c_str()), - dirname(path)); +#ifdef LOTTIE_JSON_SUPPORT + auto obj = internal::model::parse(buf, sz, dirname(path)); +#else + buf[sz] = 0; + auto obj = internal::model::parse(buf, dirname(path)); +#endif if (obj && cachePolicy) ModelCache::instance().add(path, obj); - + delete[] buf; return obj; } } @@ -153,17 +161,37 @@ std::shared_ptr model::loadFromData( if (obj) return obj; } - auto obj = internal::model::parse(const_cast(jsonData.c_str()), +#ifdef LOTTIE_JSON_SUPPORT + auto obj = internal::model::parse(jsonData.c_str(), jsonData.length(), std::move(resourcePath)); +#else + auto obj = internal::model::parse(const_cast(jsonData.c_str()), std::move(resourcePath)); +#endif if (obj && cachePolicy) ModelCache::instance().add(key, obj); return obj; } + + std::shared_ptr model::loadFromData( std::string jsonData, std::string resourcePath, model::ColorFilter filter) { - return internal::model::parse(const_cast(jsonData.c_str()), +#ifdef LOTTIE_JSON_SUPPORT + return internal::model::parse(jsonData.c_str(), jsonData.length(), std::move(resourcePath), std::move(filter)); +#else + return internal::model::parse(const_cast(jsonData.c_str()), + std::move(resourcePath), std::move(filter)); +#endif +} + +std::shared_ptr model::loadFromROData(const char * data, const size_t len, const char * resourcePath) +{ +#ifdef LOTTIE_JSON_SUPPORT + return internal::model::parse(data, len, resourcePath); +#else + return nullptr; +#endif } diff --git a/src/lottie/lottiemodel.cpp b/src/lottie/lottiemodel.cpp index 1bca99d2..e7a677e6 100644 --- a/src/lottie/lottiemodel.cpp +++ b/src/lottie/lottiemodel.cpp @@ -62,8 +62,7 @@ class LottieRepeaterProcesser { // object before the repeater ++i; // 2. move all the children till repater to the group - std::move(obj->mChildren.begin(), i.base(), - back_inserter(content->mChildren)); + std::move(obj->mChildren.begin(), i.base(), std::back_inserter(content->mChildren)); // 3. erase the objects from the original children list obj->mChildren.erase(obj->mChildren.begin(), i.base()); @@ -201,7 +200,7 @@ VMatrix model::Transform::Data::matrix(int frameNo, bool autoOrient) const return m; } -void model::Dash::getDashInfo(int frameNo, std::vector &result) const +void model::Dash::getDashInfo(int frameNo, VVector &result) const { result.clear(); @@ -348,11 +347,11 @@ void model::Asset::loadImagePath(std::string path) if (!path.empty()) mBitmap = VImageLoader::instance().load(path.c_str()); } -std::vector model::Composition::layerInfoList() const +VVector model::Composition::layerInfoList() const { if (!mRootLayer || mRootLayer->mChildren.empty()) return {}; - std::vector result; + VVector result; result.reserve(mRootLayer->mChildren.size()); diff --git a/src/lottie/lottiemodel.h b/src/lottie/lottiemodel.h index 3e043085..5aac8ade 100644 --- a/src/lottie/lottiemodel.h +++ b/src/lottie/lottiemodel.h @@ -29,7 +29,6 @@ #include #include #include -#include #include "varenaalloc.h" #include "vbezier.h" #include "vbrush.h" @@ -38,6 +37,7 @@ #include "vpath.h" #include "vpoint.h" #include "vrect.h" +#include "vvector.h" V_USE_NAMESPACE @@ -104,7 +104,7 @@ inline const Color operator*(float m, const Color &c) } struct PathData { - std::vector mPoints; + VVector mPoints; bool mClosed = false; /* "c" */ void reserve(size_t size) { mPoints.reserve(mPoints.size() + size); } static void lerp(const PathData &start, const PathData &end, float t, @@ -244,7 +244,7 @@ class KeyFrames { if (frames_.back().end_ <= frameNo) return frames_.back().value_.end_; for (const auto &keyFrame : frames_) { - if (frameNo >= keyFrame.start_ && frameNo < keyFrame.end_) + if (frameNo >= keyFrame.start_ && frameNo <= keyFrame.end_) return keyFrame.value(frameNo); } return {}; @@ -257,7 +257,7 @@ class KeyFrames { return 0; for (const auto &frame : frames_) { - if (frameNo >= frame.start_ && frameNo < frame.end_) + if (frameNo >= frame.start_ && frameNo <= frame.end_) return frame.angle(frameNo); } return 0; @@ -277,7 +277,7 @@ class KeyFrames { } public: - std::vector frames_; + VVector frames_; }; template @@ -346,7 +346,7 @@ class Property { return vec.back().value_.end_.toPath(path); for (const auto &keyFrame : vec) { - if (frameNo >= keyFrame.start_ && frameNo < keyFrame.end_) { + if (frameNo >= keyFrame.start_ && frameNo <= keyFrame.end_) { T::lerp(keyFrame.value_.start_, keyFrame.value_.end_, keyFrame.progress(frameNo), path); } @@ -400,7 +400,7 @@ class Property { class Path; struct PathData; struct Dash { - std::vector> mData; + VVector> mData; bool empty() const { return mData.empty(); } size_t size() const { return mData.size(); } bool isStatic() const @@ -409,7 +409,7 @@ struct Dash { if (!elm.isStatic()) return false; return true; } - void getDashInfo(int frameNo, std::vector &result) const; + void getDashInfo(int frameNo, VVector &result) const; }; class Mask { @@ -469,16 +469,21 @@ class Object { void setHidden(bool value) { mData._hidden = value; } void setType(Object::Type type) { mData._type = type; } Object::Type type() const { return mData._type; } - void setName(const char *name) + void setName(const char*name) { setName(name, strlen(name)); } + void setName(const char *name, const size_t len) { if (name) { - auto len = strlen(name); if (len < maxShortStringLength) { setShortString(true); - strncpy(mData._buffer, name, len + 1); + memcpy(mData._buffer, name, len); + mData._buffer[len] = 0; } else { setShortString(false); - mPtr = strdup(name); + mPtr = (char*)malloc(len+1); + if (mPtr) { + memcpy(mPtr, name, len); + mPtr[len] = 0; + } } } } @@ -511,7 +516,7 @@ struct Asset { Type mAssetType{Type::Precomp}; bool mStatic{true}; std::string mRefId; // ref id - std::vector mLayers; + VVector mLayers; // image asset data int mWidth{0}; int mHeight{0}; @@ -523,8 +528,8 @@ class Layer; class Composition : public Object { public: Composition() : Object(Object::Type::Composition) {} - std::vector layerInfoList() const; - const std::vector &markers() const { return mMarkers; } + VVector layerInfoList() const; + const VVector &markers() const { return mMarkers; } double duration() const { return frameDuration() / frameRate(); // in second @@ -539,8 +544,8 @@ class Composition : public Object { { return long(frameAtPos(timeInSec / duration())); } - size_t totalFrame() const { return mEndFrame - mStartFrame; } - long frameDuration() const { return mEndFrame - mStartFrame - 1; } + size_t totalFrame() const { return mEndFrame - mStartFrame + 1; } + long frameDuration() const { return mEndFrame - mStartFrame; } float frameRate() const { return mFrameRate; } size_t startFrame() const { return mStartFrame; } size_t endFrame() const { return mEndFrame; } @@ -567,7 +572,7 @@ class Composition : public Object { Layer * mRootLayer{nullptr}; std::unordered_map mAssets; - std::vector mMarkers; + VVector mMarkers; VArenaAlloc mArenaAlloc{2048}; Stats mStats; }; @@ -661,7 +666,7 @@ class Group : public Object { explicit Group(Object::Type type) : Object(type) {} public: - std::vector mChildren; + VVector mChildren; Transform * mTransform{nullptr}; }; @@ -711,7 +716,7 @@ class Layer : public Group { Property mTimeRemap; /* "tm" */ Composition * mCompRef{nullptr}; Asset * mAsset{nullptr}; - std::vector mMasks; + VVector mMasks; }; Layer::Extra *extra() @@ -782,7 +787,7 @@ class Stroke : public Object { JoinStyle joinStyle() const { return mJoinStyle; } float miterLimit() const { return mMiterLimit; } bool hasDashInfo() const { return !mDash.empty(); } - void getDashInfo(int frameNo, std::vector &result) const + void getDashInfo(int frameNo, VVector &result) const { return mDash.getDashInfo(frameNo, result); } @@ -810,7 +815,7 @@ class Gradient : public Object { const Gradient::Data &g); public: - std::vector mGradient; + VVector mGradient; }; explicit Gradient(Object::Type type) : Object(type) {} inline float opacity(int frameNo) const @@ -843,7 +848,7 @@ class GradientStroke : public Gradient { JoinStyle joinStyle() const { return mJoinStyle; } float miterLimit() const { return mMiterLimit; } bool hasDashInfo() const { return !mDash.empty(); } - void getDashInfo(int frameNo, std::vector &result) const + void getDashInfo(int frameNo, VVector &result) const { return mDash.getDashInfo(frameNo, result); } @@ -1137,8 +1142,16 @@ std::shared_ptr loadFromData(std::string jsonData, std::string resourcePath, ColorFilter filter); +std::shared_ptr loadFromROData(const char * data, const size_t len, + const char * resourcePath); + +#ifdef LOTTIE_JSON_SUPPORT +std::shared_ptr parse(const char *str, size_t len, std::string dir_path, + ColorFilter filter = {}); +#else std::shared_ptr parse(char *str, std::string dir_path, ColorFilter filter = {}); +#endif } // namespace model diff --git a/src/lottie/lottieparser.cpp b/src/lottie/lottieparser.cpp index 83be17e3..72fc301f 100644 --- a/src/lottie/lottieparser.cpp +++ b/src/lottie/lottieparser.cpp @@ -258,7 +258,7 @@ class LottieParserImpl : public LookaheadParserHandler { void getValue(int &ival); void getValue(model::PathData &shape); void getValue(model::Gradient::Data &gradient); - void getValue(std::vector &v); + void getValue(VVector &v); void getValue(model::Repeater::Transform &); template @@ -290,10 +290,10 @@ class LottieParserImpl : public LookaheadParserHandler { private: model::ColorFilter mColorFilter; struct { - std::vector mInPoint; /* "i" */ - std::vector mOutPoint; /* "o" */ - std::vector mVertices; /* "v" */ - std::vector mResult; + VVector mInPoint; /* "i" */ + VVector mOutPoint; /* "o" */ + VVector mVertices; /* "v" */ + VVector mResult; bool mClosed{false}; void convert() @@ -367,7 +367,7 @@ class LottieParserImpl : public LookaheadParserHandler { std::shared_ptr mComposition; model::Composition * compRef{nullptr}; model::Layer * curLayerRef{nullptr}; - std::vector mLayersToUpdate; + VVector mLayersToUpdate; std::string mDirPath; void SkipOut(int depth); }; @@ -1824,7 +1824,7 @@ model::GradientStroke *LottieParserImpl::parseGStrokeObject() return obj; } -void LottieParserImpl::getValue(std::vector &v) +void LottieParserImpl::getValue(VVector &v) { EnterArray(); while (NextArrayValue()) { diff --git a/src/lottie/lottieroparser.cpp b/src/lottie/lottieroparser.cpp new file mode 100644 index 00000000..5e77fc8d --- /dev/null +++ b/src/lottie/lottieroparser.cpp @@ -0,0 +1,2261 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +//#define DEBUG_PARSER + +// This parser implements JSON token-by-token parsing with an API that is +// more direct; we don't have to create handler object and +// callbacks. Instead, we retrieve values from the JSON stream by calling +// GetInt(), GetDouble(), GetString() and GetBool(), traverse into structures +// by calling EnterObject() and EnterArray(), and skip over unwanted data by +// calling SkipValue(). As we know the lottie file structure this way will be +// the efficient way of parsing the file. +// +// If you aren't sure of what's next in the JSON data, you can use PeekType() +// and PeekValue() to look ahead to the next object before reading it. +// +// If you call the wrong retrieval method--e.g. GetInt when the next JSON token +// is not an int, EnterObject or EnterArray when there isn't actually an object +// or array to read--the stream parsing will end immediately and no more data +// will be delivered. +// +// After calling EnterObject, you retrieve keys via NextObjectKey() and values +// via the normal getters. When NextObjectKey() returns null, you have exited +// the object, or you can call SkipObject() to skip to the end of the object +// immediately. If you fetch the entire object (i.e. NextObjectKey() returned +// null), you should not call SkipObject(). +// +// After calling EnterArray(), you must alternate between calling +// NextArrayValue() to see if the array has more data, and then retrieving +// values via the normal getters. You can call SkipArray() to skip to the end of +// the array immediately. If you fetch the entire array (i.e. NextArrayValue() +// returned null), you should not call SkipArray(). +// +// This parser uses in-situ strings, so the JSON buffer will be altered during +// the parse. + +#include +#include +#include + +#include "lottiemodel.h" +#include "JSON/JSON.hpp" +#include "JSON/ROString.hpp" + +typedef JSONT JSON; +using namespace rlottie::internal; + +/** JSON does not have a value type so make one for generic usage */ +struct Value { + ROString strValue; + JSON::Token token; + + Value() { } + + double GetDouble() { if (token.type == JSON::Token::Number) return (double)strValue; return 0; } + int GetInt() { if (token.type == JSON::Token::Number) return (int)strValue; return 0; } + ROString GetString() { if (token.type == JSON::Token::String) return strValue; return 0; } + bool GetBool() { return (token.type == JSON::Token::True); } + void GetNull() { return; } // WTF? + //TODO: Do everything else + + void set(const ROString & value, const JSON::Token & token) { strValue = value; this->token = token;} +}; + +struct LottieParserImpl { + enum ValueType + { + Undefined = 0, + Null = 1, + Bool = 2, + Number = 3, + String = 5, + Array = 6, + Object = 7, + }; + +public: + LottieParserImpl(const char *str, std::string dir_path, model::ColorFilter filter) + : mColorFilter(std::move(filter)), + mDirPath(std::move(dir_path)), + mLastSuper(JSON::InvalidPos), + mInput(str), + errorPos(JSON::InvalidPos) + { } + LottieParserImpl(const char *str, size_t len, std::string dir_path, model::ColorFilter filter) + : mColorFilter(std::move(filter)), + mDirPath(std::move(dir_path)), + mLastSuper(JSON::InvalidPos), + mInput(str, (int)len), + errorPos(JSON::InvalidPos) + { } + bool VerifyType(); + bool ParseNext(); + void Error() { errorPos = parser.pos; } + +public: + VArenaAlloc &allocator() { return compRef->mArenaAlloc; } + bool EnterObject(); + bool EnterArray(); + ROString NextObjectKey(); + bool NextArrayValue(); + int GetInt(); + double GetDouble(); + ROString GetString(); + std::string GetStringObject(); + bool GetBool(); + void GetNull(); + + void SkipObject(); + void SkipArray(); + void SkipValue(); + Value * PeekValue(); + ValueType PeekType() const; + bool IsValid() { return errorPos == JSON::InvalidPos; } + + void Skip(const ROString & key); + model::BlendMode getBlendMode(); + CapStyle getLineCap(); + JoinStyle getLineJoin(); + FillRule getFillRule(); + model::Trim::TrimType getTrimType(); + model::MatteType getMatteType(); + model::Layer::Type getLayerType(); + + std::shared_ptr composition() const + { + return mComposition; + } + void parseComposition(); + void parseMarkers(); + void parseMarker(); + void parseAssets(model::Composition *comp); + model::Asset * parseAsset(); + void parseLayers(model::Composition *comp); + model::Layer * parseLayer(); + void parseMaskProperty(model::Layer *layer); + void parseShapesAttr(model::Layer *layer); + void parseObject(model::Group *parent); + model::Mask * parseMaskObject(); + model::Object * parseObjectTypeAttr(); + model::Object * parseGroupObject(); + model::Rect * parseRectObject(); + model::RoundedCorner * parseRoundedCorner(); + void updateRoundedCorner(model::Group *parent, model::RoundedCorner *rc); + + model::Ellipse * parseEllipseObject(); + model::Path * parseShapeObject(); + model::Polystar *parsePolystarObject(); + + model::Transform * parseTransformObject(bool ddd = false); + model::Fill * parseFillObject(); + model::GradientFill * parseGFillObject(); + model::Stroke * parseStrokeObject(); + model::GradientStroke *parseGStrokeObject(); + model::Trim * parseTrimObject(); + model::Repeater * parseReapeaterObject(); + + void parseGradientProperty(model::Gradient *gradient, const ROString & key); + + VPointF parseInperpolatorPoint(); + + void getValue(VPointF &pt); + void getValue(float &fval); + void getValue(model::Color &color); + void getValue(int &ival); + void getValue(model::PathData &shape); + void getValue(model::Gradient::Data &gradient); + void getValue(VVector &v); + void getValue(model::Repeater::Transform &); + + template + bool parseKeyFrameValue(const ROString &, model::Value &) + { + return false; + } + + template + bool parseKeyFrameValue(const ROString & key, + model::Value &value); + template + void parseKeyFrame(model::KeyFrames &obj); + template + void parseProperty(model::Property &obj); + template + void parsePropertyHelper(model::Property &obj); + + void parseShapeProperty(model::Property &obj); + void parseDashProperty(model::Dash &dash); + + VInterpolator *interpolator(VPointF, VPointF, std::string); + + model::Color toColor(const ROString & str); + + void resolveLayerRefs(); + void parsePathInfo(); + +private: + model::ColorFilter mColorFilter; + struct { + VVector mInPoint; /* "i" */ + VVector mOutPoint; /* "o" */ + VVector mVertices; /* "v" */ + VVector mResult; + bool mClosed{false}; + + void convert() + { + // shape data could be empty. + if (mInPoint.empty() || mOutPoint.empty() || mVertices.empty()) { + mResult.clear(); + return; + } + + /* + * Convert the AE shape format to + * list of bazier curves + * The final structure will be Move +size*Cubic + Cubic (if the path + * is closed one) + */ + if (mInPoint.size() != mOutPoint.size() || + mInPoint.size() != mVertices.size()) { + mResult.clear(); + } else { + auto size = mVertices.size(); + mResult.push_back(mVertices[0]); + for (size_t i = 1; i < size; i++) { + mResult.push_back( + mVertices[i - 1] + + mOutPoint[i - 1]); // CP1 = start + outTangent + mResult.push_back(mVertices[i] + + mInPoint[i]); // CP2 = end + inTangent + mResult.push_back(mVertices[i]); // end point + } + + if (mClosed) { + mResult.push_back( + mVertices[size - 1] + + mOutPoint[size - 1]); // CP1 = start + outTangent + mResult.push_back(mVertices[0] + + mInPoint[0]); // CP2 = end + inTangent + mResult.push_back(mVertices[0]); // end point + } + } + } + void reset() + { + mInPoint.clear(); + mOutPoint.clear(); + mVertices.clear(); + mResult.clear(); + mClosed = false; + } + void updatePath(VPath &out) + { + if (mResult.empty()) return; + + auto size = mResult.size(); + auto points = mResult.data(); + /* reserve exact memory requirement at once + * ptSize = size + 1(size + close) + * elmSize = size/3 cubic + 1 move + 1 close + */ + out.reserve(size + 1, size / 3 + 2); + out.moveTo(points[0]); + for (size_t i = 1; i < size; i += 3) { + out.cubicTo(points[i], points[i + 1], points[i + 2]); + } + if (mClosed) out.close(); + } + } mPathInfo; + + ROString current() const { return mInput.midString(token.start, token.end - token.start); } + ValueType toType() const { + switch(token.type) { + case JSON::Token::Null : return Null; + case JSON::Token::True: case JSON::Token::False: return Bool; + case JSON::Token::String: return String; + case JSON::Token::Object: return Object; + case JSON::Token::Array: return Array; + case JSON::Token::Number: return Number; + case JSON::Token::Undefined: + default: return Undefined; + } + } + +protected: + std::unordered_map mInterpolatorCache; + std::shared_ptr mComposition; + model::Composition * compRef{nullptr}; + model::Layer * curLayerRef{nullptr}; + VVector mLayersToUpdate; + std::string mDirPath; + void SkipOut(int depth); + + JSON parser; + JSON::Token token; + int32_t mLastSuper; + std::stack mSuperLIFO; + ROString mInput; + int32_t errorPos; + Value v_; +}; + +bool LottieParserImpl::VerifyType() +{ + /* Verify the media type is lottie json. + Could add more strict check. */ + return ParseNext(); +} + +bool LottieParserImpl::ParseNext() +{ + // Are we done? + if (parser.state == JSON::Done) return false; + + JSON::IndexType res = parser.parseOne(mInput.getData(), mInput.getLength(), token, mLastSuper); + if (res < 0) { + Error(); + printf("parse error: %d/%u\n", res, parser.pos); + return false; + } + + if (res == JSON::SaveSuper) + mSuperLIFO.push(mLastSuper); + if (res == JSON::RestoreSuper) { + if (mSuperLIFO.size()) mSuperLIFO.pop(); + mLastSuper = mSuperLIFO.size() ? mSuperLIFO.top() : JSON::InvalidPos; + } + if (res == JSON::Finished) { + // We are done. + return false; + } + + // Then decide on what to do + /* + switch(token.parent) { + case JSON::EnteringObject: return EnterObject(); + case JSON::EnteringArray: return EnterArray(); + case JSON::HadKey: return NextObjectKey(); + case JSON::HadValue: + } + vCritical << "Lottie file parsing error"; + */ + return true; +} + +bool LottieParserImpl::EnterObject() +{ + ParseNext(); + return true; +} + +bool LottieParserImpl::EnterArray() +{ + ParseNext(); + return true; +} + +ROString LottieParserImpl::NextObjectKey() +{ + if (token.parent == JSON::HadKey) { + ROString key = current(); + ParseNext(); + return key; + } + + /* SPECIAL CASE + * The parser works with a predefined rule that it will be only + * while (NextObjectKey()) for each object but in case of our nested group + * object we can call multiple time NextObjectKey() while exiting the object + * so ignore those and don't put parser in the error state. + * */ + if (token.parent == JSON::LeavingArray || token.parent == JSON::EnteringObject) { + // #ifdef DEBUG_PARSER + // vDebug<<"Object: Exiting nested loop"; + // #endif + return ROString(); + } + + if (token.parent != JSON::LeavingObject) { + Error(); + return ROString(); + } + + ParseNext(); + return ROString(); +} + +bool LottieParserImpl::NextArrayValue() +{ + if (token.parent == JSON::LeavingArray) { + ParseNext(); + return false; + } + + /* SPECIAL CASE + * same as NextObjectKey() + */ + if (token.parent == JSON::LeavingObject) { + return false; + } + + if (token.parent == JSON::HadKey) { + Error(); + return false; + } + + return true; +} + +int LottieParserImpl::GetInt() +{ + if (token.parent != JSON::HadValue || token.type != JSON::Token::Number) { + Error(); + return 0; + } + + int result = (int)current(); + ParseNext(); + return result; +} + +double LottieParserImpl::GetDouble() +{ + if (token.parent != JSON::HadValue || token.type != JSON::Token::Number) { + Error(); + return 0.; + } + + double result = (double)current(); + ParseNext(); + return result; +} + +bool LottieParserImpl::GetBool() +{ + if (token.parent != JSON::HadValue || (token.type != JSON::Token::True && token.type != JSON::Token::False)) { + Error(); + return false; + } + + bool result = current() == "true"; + ParseNext(); + return result; +} + +void LottieParserImpl::GetNull() +{ + if (token.parent != JSON::HadValue || token.type != JSON::Token::Null) { + Error(); + return; + } + + ParseNext(); +} + +ROString LottieParserImpl::GetString() +{ + if (token.parent != JSON::HadValue || token.type != JSON::Token::String) { + Error(); + return ROString(); + } + + ROString result = current(); + ParseNext(); + return result; +} + +std::string LottieParserImpl::GetStringObject() +{ + ROString str = GetString(); + + if (str) { + return std::string(str.getData(), str.getLength()); + } + + return {}; +} + +void LottieParserImpl::SkipOut(int depth) +{ + do { + if (token.parent == JSON::EnteringArray || token.parent == JSON::EnteringObject) { + ++depth; + } else if (token.parent == JSON::LeavingArray || token.parent == JSON::LeavingObject) { + --depth; + } else if (errorPos != JSON::InvalidPos) { + return; + } + + ParseNext(); + } while (depth > 0); +} + +void LottieParserImpl::SkipValue() +{ + SkipOut(0); +} + +void LottieParserImpl::SkipArray() +{ + SkipOut(1); +} + +void LottieParserImpl::SkipObject() +{ + SkipOut(1); +} + +Value *LottieParserImpl::PeekValue() +{ + if (token.parent == JSON::HadValue) { + v_.set(current(), token); + return &v_; + } + + return nullptr; +} + +// returns a rapidjson::Type, or -1 for no value (at end of +// object/array) +LottieParserImpl::ValueType LottieParserImpl::PeekType() const +{ + return toType(); +} + +void LottieParserImpl::Skip(const ROString & /*key*/) +{ + if (PeekType() == Array) { + EnterArray(); + SkipArray(); + } else if (PeekType() == Object) { + EnterObject(); + SkipObject(); + } else { + SkipValue(); + } +} + +model::BlendMode LottieParserImpl::getBlendMode() +{ + auto mode = model::BlendMode::Normal; + + switch (GetInt()) { + case 1: + mode = model::BlendMode::Multiply; + break; + case 2: + mode = model::BlendMode::Screen; + break; + case 3: + mode = model::BlendMode::OverLay; + break; + default: + break; + } + return mode; +} + +void LottieParserImpl::resolveLayerRefs() +{ + for (const auto &layer : mLayersToUpdate) { + auto search = compRef->mAssets.find(layer->extra()->mPreCompRefId); + if (search != compRef->mAssets.end()) { + if (layer->mLayerType == model::Layer::Type::Image) { + layer->extra()->mAsset = search->second; + } else if (layer->mLayerType == model::Layer::Type::Precomp) { + layer->mChildren = search->second->mLayers; + layer->setStatic(layer->isStatic() && + search->second->isStatic()); + } + } + } +} + +void LottieParserImpl::parseComposition() +{ + EnterObject(); + std::shared_ptr sharedComposition = + std::make_shared(); + model::Composition *comp = sharedComposition.get(); + compRef = comp; + while (ROString key = NextObjectKey()) { + switch(key.hash()) + { + case "v"_hash: comp->mVersion = GetStringObject(); break; + case "w"_hash: comp->mSize.setWidth(GetInt()); break; + case "h"_hash: comp->mSize.setHeight(GetInt()); break; + case "ip"_hash: comp->mStartFrame = GetDouble(); break; + case "op"_hash: comp->mEndFrame = GetDouble(); break; + case "fr"_hash: comp->mFrameRate = GetDouble(); break; + case "assets"_hash: parseAssets(comp); break; + case "layers"_hash: parseLayers(comp); break; + case "markers"_hash: parseMarkers(); break; + default: +#ifdef DEBUG_PARSER + vWarning << "Composition Attribute Skipped : " << key; +#endif + Skip(key); + break; + } + } + + if (comp->mVersion.empty() || !comp->mRootLayer) { + // don't have a valid bodymovin header + return; + } + if (comp->mStartFrame > comp->mEndFrame) { + // reversed animation? missing data? + return; + } + if (!IsValid()) { + return; + } + + resolveLayerRefs(); + comp->setStatic(comp->mRootLayer->isStatic()); + comp->mRootLayer->mInFrame = comp->mStartFrame; + comp->mRootLayer->mOutFrame = comp->mEndFrame; + + mComposition = sharedComposition; +} + +void LottieParserImpl::parseMarker() +{ + EnterObject(); + std::string comment; + int timeframe{0}; + int duration{0}; + while (ROString key = NextObjectKey()) { + switch (key.hash()) { + case "cm"_hash: comment = GetStringObject(); break; + case "tm"_hash: timeframe = GetDouble(); break; + case "dr"_hash: duration = GetDouble(); break; + default: +#ifdef DEBUG_PARSER + vWarning << "Marker Attribute Skipped : " << key; +#endif + Skip(key); + break; + } + } + compRef->mMarkers.emplace_back(std::move(comment), timeframe, + timeframe + duration); +} + +void LottieParserImpl::parseMarkers() +{ + EnterArray(); + while (NextArrayValue()) { + parseMarker(); + } + // update the precomp layers with the actual layer object +} + +void LottieParserImpl::parseAssets(model::Composition *composition) +{ + EnterArray(); + while (NextArrayValue()) { + auto asset = parseAsset(); + composition->mAssets[asset->mRefId] = asset; + } + // update the precomp layers with the actual layer object +} + +static constexpr const unsigned char B64index[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}; + +std::string b64decode(const char *data, const size_t len) +{ + auto p = reinterpret_cast(data); + int pad = len > 0 && (len % 4 || p[len - 1] == '='); + const size_t L = ((len + 3) / 4 - pad) * 4; + std::string str(L / 4 * 3 + pad, '\0'); + + for (size_t i = 0, j = 0; i < L; i += 4) { + int n = B64index[p[i]] << 18 | B64index[p[i + 1]] << 12 | + B64index[p[i + 2]] << 6 | B64index[p[i + 3]]; + str[j++] = n >> 16; + str[j++] = n >> 8 & 0xFF; + str[j++] = n & 0xFF; + } + if (pad) { + int n = B64index[p[L]] << 18 | B64index[p[L + 1]] << 12; + str[str.size() - 1] = n >> 16; + + if (len > L + 2 && p[L + 2] != '=') { + n |= B64index[p[L + 2]] << 6; + str.push_back(n >> 8 & 0xFF); + } + } + return str; +} + +static std::string convertFromBase64(const std::string &str) +{ + // usual header look like "data:image/png;base64," + // so need to skip till ','. + size_t startIndex = str.find(",", 0); + startIndex += 1; // skip "," + size_t length = str.length() - startIndex; + + const char *b64Data = str.c_str() + startIndex; + + return b64decode(b64Data, length); +} + +/* + * std::to_string() function is missing in VS2017 + * so this is workaround for windows build + */ +#include +template +static std::string toString(const T &value) +{ + std::ostringstream os; + os << value; + return os.str(); +} + +/* + * https://github.com/airbnb/lottie-web/blob/master/docs/json/layers/shape.json + * + */ +model::Asset *LottieParserImpl::parseAsset() +{ + auto asset = allocator().make(); + std::string filename; + std::string relativePath; + bool embededResource = false; + EnterObject(); + while (ROString key = NextObjectKey()) { + switch(key.hash()) { + case "w"_hash: asset->mWidth = GetInt(); break; + case "h"_hash: asset->mHeight = GetInt(); break; + case "p"_hash: /* image name */ + asset->mAssetType = model::Asset::Type::Image; + filename = GetStringObject(); + break; + case "u"_hash: /* relative image path */ + relativePath = GetStringObject(); + break; + case "e"_hash: /* relative image path */ + embededResource = GetInt(); + break; + case "id"_hash: /* reference id*/ + if (PeekType() == String) + asset->mRefId = GetStringObject(); + else asset->mRefId = toString(GetInt()); + break; + case "layers"_hash: { + asset->mAssetType = model::Asset::Type::Precomp; + EnterArray(); + bool staticFlag = true; + while (NextArrayValue()) { + auto layer = parseLayer(); + if (layer) { + staticFlag = staticFlag && layer->isStatic(); + asset->mLayers.push_back(layer); + } + } + asset->setStatic(staticFlag); + break; + } + default: +#ifdef DEBUG_PARSER + vWarning << "Asset Attribute Skipped : " << key; +#endif + Skip(key); + break; + } + } + + if (asset->mAssetType == model::Asset::Type::Image) { + if (embededResource) { + // embeder resource should start with "data:" + if (filename.compare(0, 5, "data:") == 0) { + asset->loadImageData(convertFromBase64(filename)); + } + } else { + asset->loadImagePath(mDirPath + relativePath + filename); + } + } + + return asset; +} + +void LottieParserImpl::parseLayers(model::Composition *comp) +{ + comp->mRootLayer = allocator().make(); + comp->mRootLayer->mLayerType = model::Layer::Type::Precomp; + comp->mRootLayer->setName("__", 2); + bool staticFlag = true; + EnterArray(); + while (NextArrayValue()) { + auto layer = parseLayer(); + if (layer) { + staticFlag = staticFlag && layer->isStatic(); + comp->mRootLayer->mChildren.push_back(layer); + } + } + comp->mRootLayer->setStatic(staticFlag); +} + +model::Color LottieParserImpl::toColor(const ROString & str) +{ + if (!str) return {}; + + model::Color color; + size_t len = str.getLength(); + + // some resource has empty color string + // return a default color for those cases. + if (len != 7 || str[0] != '#') return color; + + char tmp[3] = {'\0', '\0', '\0'}; + tmp[0] = str[1]; + tmp[1] = str[2]; + color.r = std::strtol(tmp, nullptr, 16) / 255.0f; + + tmp[0] = str[3]; + tmp[1] = str[4]; + color.g = std::strtol(tmp, nullptr, 16) / 255.0f; + + tmp[0] = str[5]; + tmp[1] = str[6]; + color.b = std::strtol(tmp, nullptr, 16) / 255.0f; + + return color; +} + +model::MatteType LottieParserImpl::getMatteType() +{ + switch (GetInt()) { + case 1: + return model::MatteType::Alpha; + break; + case 2: + return model::MatteType::AlphaInv; + break; + case 3: + return model::MatteType::Luma; + break; + case 4: + return model::MatteType::LumaInv; + break; + default: + return model::MatteType::None; + break; + } +} + +model::Layer::Type LottieParserImpl::getLayerType() +{ + switch (GetInt()) { + case 0: + return model::Layer::Type::Precomp; + break; + case 1: + return model::Layer::Type::Solid; + break; + case 2: + return model::Layer::Type::Image; + break; + case 3: + return model::Layer::Type::Null; + break; + case 4: + return model::Layer::Type::Shape; + break; + case 5: + return model::Layer::Type::Text; + break; + default: + return model::Layer::Type::Null; + break; + } +} + +/* + * https://github.com/airbnb/lottie-web/blob/master/docs/json/layers/shape.json + * + */ +model::Layer *LottieParserImpl::parseLayer() +{ + model::Layer *layer = allocator().make(); + curLayerRef = layer; + bool ddd = true; + EnterObject(); + while (ROString key = NextObjectKey()) { + switch(key.hash()) { + case "ty"_hash: layer->mLayerType = getLayerType(); break; + /*Layer index in AE. Used for parenting and expressions.*/ + case "ind"_hash: layer->mId = GetInt(); break; + /*3d layer */ + case "ddd"_hash: ddd = GetInt(); break; + // "Layer Time Stretching" + case "sr"_hash: layer->mTimeStreatch = GetDouble(); break; + case "ip"_hash: layer->mInFrame = std::lround(GetDouble()); break; + case "op"_hash: layer->mOutFrame = std::lround(GetDouble()); break; + case "st"_hash: layer->mStartFrame = GetDouble(); break; + case "bm"_hash: layer->mBlendMode = getBlendMode(); break; + case "w"_hash: layer->mLayerSize.setWidth(GetInt()); break; + case "h"_hash: layer->mLayerSize.setHeight(GetInt()); break; + case "sw"_hash: layer->mLayerSize.setWidth(GetInt()); break; + case "sh"_hash: layer->mLayerSize.setHeight(GetInt()); break; + case "sc"_hash: layer->extra()->mSolidColor = toColor(GetString()); break; + case "tt"_hash: layer->mMatteType = getMatteType(); break; + case "ao"_hash: layer->mAutoOrient = GetInt(); break; + case "hd"_hash: layer->setHidden(GetBool()); break; + /*Layer Parent. Uses "ind" of parent.*/ + case "parent"_hash: layer->mParentId = GetInt(); break; + case "hasMask"_hash: layer->mHasMask = GetBool(); break; + // time remapping + case "tm"_hash: parseProperty(layer->extra()->mTimeRemap); break; + case "shapes"_hash: parseShapesAttr(layer); break; + case "masksProperties"_hash: parseMaskProperty(layer); break; + case "ks"_hash: { + EnterObject(); + layer->mTransform = parseTransformObject(ddd); + break; + } + case "nm"_hash: { + ROString name = GetString(); + layer->setName(name.getData(), name.getLength()); + break; + } + /*preComp Layer reference id*/ + case "refId"_hash: { + layer->extra()->mPreCompRefId = GetStringObject(); + layer->mHasGradient = true; + mLayersToUpdate.push_back(layer); + } break; + default: +#ifdef DEBUG_PARSER + vWarning << "Layer Attribute Skipped : " << key; +#endif + Skip(key); + } + } + + if (!layer->mTransform) { + // not a valid layer + return nullptr; + } + + // make sure layer data is not corrupted. + if (layer->hasParent() && (layer->id() == layer->parentId())) + return nullptr; + + if (layer->mExtra) layer->mExtra->mCompRef = compRef; + + if (layer->hidden()) { + // if layer is hidden, only data that is usefull is its + // transform matrix(when it is a parent of some other layer) + // so force it to be a Null Layer and release all resource. + layer->setStatic(layer->mTransform->isStatic()); + layer->mLayerType = model::Layer::Type::Null; + layer->mChildren = {}; + return layer; + } + + // update the static property of layer + bool staticFlag = true; + for (const auto &child : layer->mChildren) { + staticFlag &= child->isStatic(); + } + + if (layer->hasMask() && layer->mExtra) { + for (const auto &mask : layer->mExtra->mMasks) { + staticFlag &= mask->isStatic(); + } + } + + layer->setStatic(staticFlag && layer->mTransform->isStatic()); + + return layer; +} + +void LottieParserImpl::parseMaskProperty(model::Layer *layer) +{ + EnterArray(); + while (NextArrayValue()) { + layer->extra()->mMasks.push_back(parseMaskObject()); + } +} + +model::Mask *LottieParserImpl::parseMaskObject() +{ + auto obj = allocator().make(); + + EnterObject(); + while (ROString key = NextObjectKey()) { + switch(key.hash()) { + case "inv"_hash: obj->mInv = GetBool(); break; + case "mode"_hash: { + ROString str = GetString(); + if (!str) { + obj->mMode = model::Mask::Mode::None; + continue; + } + switch (str[0]) { + case 'n': + obj->mMode = model::Mask::Mode::None; + break; + case 'a': + obj->mMode = model::Mask::Mode::Add; + break; + case 's': + obj->mMode = model::Mask::Mode::Substarct; + break; + case 'i': + obj->mMode = model::Mask::Mode::Intersect; + break; + case 'f': + obj->mMode = model::Mask::Mode::Difference; + break; + default: + obj->mMode = model::Mask::Mode::None; + break; + } + break; + } + case "pt"_hash: parseShapeProperty(obj->mShape); break; + case "o"_hash: parseProperty(obj->mOpacity); break; + default: + Skip(key); + } + } + obj->mIsStatic = obj->mShape.isStatic() && obj->mOpacity.isStatic(); + return obj; +} + +void LottieParserImpl::parseShapesAttr(model::Layer *layer) +{ + EnterArray(); + while (NextArrayValue()) { + parseObject(layer); + } +} + +model::Object *LottieParserImpl::parseObjectTypeAttr() +{ + ROString type = GetString(); + if (!type) { + vWarning << "No object type specified"; + return nullptr; + } + switch(type.hash()) { + case "gr"_hash: return parseGroupObject(); + case "rc"_hash: return parseRectObject(); + case "rd"_hash: { + curLayerRef->mHasRoundedCorner = true; + return parseRoundedCorner(); + } + case "el"_hash: return parseEllipseObject(); + case "tr"_hash: return parseTransformObject(); + case "fl"_hash: return parseFillObject(); + case "st"_hash: return parseStrokeObject(); + case "gf"_hash: { + curLayerRef->mHasGradient = true; + return parseGFillObject(); + } + case "gs"_hash: { + curLayerRef->mHasGradient = true; + return parseGStrokeObject(); + } + case "sh"_hash: return parseShapeObject(); + case "sr"_hash: return parsePolystarObject(); + case "tm"_hash: { + curLayerRef->mHasPathOperator = true; + return parseTrimObject(); + } + case "rp"_hash: { + curLayerRef->mHasRepeater = true; + return parseReapeaterObject(); + } + case "mm"_hash: + vWarning << "Merge Path is not supported yet"; + return nullptr; + default: +#ifdef DEBUG_PARSER + vDebug << "The Object Type not yet handled = " << type; +#endif + return nullptr; + } +} + +void LottieParserImpl::parseObject(model::Group *parent) +{ + EnterObject(); + while (ROString key = NextObjectKey()) { + if (key == "ty") { + auto child = parseObjectTypeAttr(); + if (child && !child->hidden()) { + if (child->type() == model::Object::Type::RoundedCorner) { + updateRoundedCorner(parent, static_cast(child)); + } + parent->mChildren.push_back(child); + } + } else { + Skip(key); + } + } +} + +void LottieParserImpl::updateRoundedCorner(model::Group *group, model::RoundedCorner *rc) +{ + for(auto &e : group->mChildren) + { + if (e->type() == model::Object::Type::Rect) { + static_cast(e)->mRoundedCorner = rc; + if (!rc->isStatic()) { + e->setStatic(false); + group->setStatic(false); + //@TODO need to propagate. + } + } else if ( e->type() == model::Object::Type::Group) { + updateRoundedCorner(static_cast(e), rc); + } + } +} + +model::Object *LottieParserImpl::parseGroupObject() +{ + auto group = allocator().make(); + + while (ROString key = NextObjectKey()) { + switch(key.hash()) { + case "nm"_hash: { ROString name = GetString(); group->setName(name.getData(), name.getLength()); break; } + case "it"_hash: { + EnterArray(); + while (NextArrayValue()) { + parseObject(group); + } + if (!group->mChildren.empty() + && group->mChildren.back()->type() + == model::Object::Type::Transform) { + group->mTransform = + static_cast(group->mChildren.back()); + group->mChildren.pop_back(); + } + break; + } + default: + Skip(key); + } + } + bool staticFlag = true; + for (const auto &child : group->mChildren) { + staticFlag &= child->isStatic(); + } + + if (group->mTransform) { + group->setStatic(staticFlag && group->mTransform->isStatic()); + } + + return group; +} + +/* + * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/rect.json + */ +model::Rect *LottieParserImpl::parseRectObject() +{ + auto obj = allocator().make(); + + while (ROString key = NextObjectKey()) { + switch(key.hash()) { + case "nm"_hash: { ROString name = GetString(); obj->setName(name.getData(), name.getLength()); break; } + case "p"_hash: parseProperty(obj->mPos); break; + case "s"_hash: parseProperty(obj->mSize); break; + case "r"_hash: parseProperty(obj->mRound); break; + case "d"_hash: obj->mDirection = GetInt(); break; + case "hd"_hash: obj->setHidden(GetBool()); break; + default: Skip(key); + } + } + obj->setStatic(obj->mPos.isStatic() && obj->mSize.isStatic() && + obj->mRound.isStatic()); + return obj; +} + +/* + * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/rect.json + */ +model::RoundedCorner *LottieParserImpl::parseRoundedCorner() +{ + auto obj = allocator().make(); + + while (ROString key = NextObjectKey()) { + switch(key.hash()) { + case "nm"_hash: { ROString name = GetString(); obj->setName(name.getData(), name.getLength()); break; } + case "r"_hash: parseProperty(obj->mRadius); break; + case "hd"_hash: obj->setHidden(GetBool()); break; + default: + Skip(key); + } + } + obj->setStatic(obj->mRadius.isStatic()); + return obj; +} + +/* + * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/ellipse.json + */ +model::Ellipse *LottieParserImpl::parseEllipseObject() +{ + auto obj = allocator().make(); + + while (ROString key = NextObjectKey()) { + switch(key.hash()) { + case "nm"_hash: { ROString name = GetString(); obj->setName(name.getData(), name.getLength()); break; } + case "p"_hash: parseProperty(obj->mPos); break; + case "s"_hash: parseProperty(obj->mSize); break; + case "d"_hash: obj->mDirection = GetInt(); break; + case "hd"_hash: obj->setHidden(GetBool()); break; + default: + Skip(key); + } + } + obj->setStatic(obj->mPos.isStatic() && obj->mSize.isStatic()); + return obj; +} + +/* + * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/shape.json + */ +model::Path *LottieParserImpl::parseShapeObject() +{ + auto obj = allocator().make(); + + while (ROString key = NextObjectKey()) { + switch(key.hash()) { + case "nm"_hash: { ROString name = GetString(); obj->setName(name.getData(), name.getLength()); break; } + case "ks"_hash: parseShapeProperty(obj->mShape); break; + case "d"_hash: obj->mDirection = GetInt(); break; + case "hd"_hash: obj->setHidden(GetBool()); break; + default: +#ifdef DEBUG_PARSER + vDebug << "Shape property ignored :" << key; +#endif + Skip(key); + } + } + obj->setStatic(obj->mShape.isStatic()); + + return obj; +} + +/* + * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/star.json + */ +model::Polystar *LottieParserImpl::parsePolystarObject() +{ + auto obj = allocator().make(); + + while (ROString key = NextObjectKey()) { + switch(key.hash()) { + case "nm"_hash: { ROString name = GetString(); obj->setName(name.getData(), name.getLength()); break; } + case "p"_hash: parseProperty(obj->mPos); break; + case "pt"_hash: parseProperty(obj->mPointCount); break; + case "ir"_hash: parseProperty(obj->mInnerRadius); break; + case "is"_hash: parseProperty(obj->mInnerRoundness); break; + case "or"_hash: parseProperty(obj->mOuterRadius); break; + case "os"_hash: parseProperty(obj->mOuterRoundness); break; + case "r"_hash: parseProperty(obj->mRotation); break; + case "sy"_hash: { + int starType = GetInt(); + if (starType == 1) obj->mPolyType = model::Polystar::PolyType::Star; + if (starType == 2) + obj->mPolyType = model::Polystar::PolyType::Polygon; + break; + } + case "d"_hash: obj->mDirection = GetInt(); break; + case "hd"_hash: obj->setHidden(GetBool()); break; + default: +#ifdef DEBUG_PARSER + vDebug << "Polystar property ignored :" << key; +#endif + Skip(key); + } + } + obj->setStatic( + obj->mPos.isStatic() && obj->mPointCount.isStatic() && + obj->mInnerRadius.isStatic() && obj->mInnerRoundness.isStatic() && + obj->mOuterRadius.isStatic() && obj->mOuterRoundness.isStatic() && + obj->mRotation.isStatic()); + + return obj; +} + +model::Trim::TrimType LottieParserImpl::getTrimType() +{ + switch (GetInt()) { + case 1: + return model::Trim::TrimType::Simultaneously; + break; + case 2: + return model::Trim::TrimType::Individually; + break; + default: + Error(); + return model::Trim::TrimType::Simultaneously; + break; + } +} + +/* + * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/trim.json + */ +model::Trim *LottieParserImpl::parseTrimObject() +{ + auto obj = allocator().make(); + + while (ROString key = NextObjectKey()) { + switch(key.hash()) { + case "nm"_hash: { ROString name = GetString(); obj->setName(name.getData(), name.getLength()); break; } + case "s"_hash: parseProperty(obj->mStart); break; + case "e"_hash: parseProperty(obj->mEnd); break; + case "o"_hash: parseProperty(obj->mOffset); break; + case "m"_hash: obj->mTrimType = getTrimType(); break; + case "hd"_hash: obj->setHidden(GetBool()); break; + default: +#ifdef DEBUG_PARSER + vDebug << "Trim property ignored :" << key; +#endif + Skip(key); + } + } + obj->setStatic(obj->mStart.isStatic() && obj->mEnd.isStatic() && + obj->mOffset.isStatic()); + return obj; +} + +void LottieParserImpl::getValue(model::Repeater::Transform &obj) +{ + EnterObject(); + + while (ROString key = NextObjectKey()) { + switch(key.hash()) { + case "a"_hash: parseProperty(obj.mAnchor); break; + case "p"_hash: parseProperty(obj.mPosition); break; + case "r"_hash: parseProperty(obj.mRotation); break; + case "s"_hash: parseProperty(obj.mScale); break; + case "so"_hash: parseProperty(obj.mStartOpacity); break; + case "eo"_hash: parseProperty(obj.mEndOpacity); break; + default: + Skip(key); + } + } +} + +model::Repeater *LottieParserImpl::parseReapeaterObject() +{ + auto obj = allocator().make(); + + obj->setContent(allocator().make()); + + while (ROString key = NextObjectKey()) { + switch(key.hash()) { + case "nm"_hash: { ROString name = GetString(); obj->setName(name.getData(), name.getLength()); break; } + case "c"_hash: { + parseProperty(obj->mCopies); + float maxCopy = 0.0; + if (!obj->mCopies.isStatic()) { + for (auto &keyFrame : obj->mCopies.animation().frames_) { + if (maxCopy < keyFrame.value_.start_) + maxCopy = keyFrame.value_.start_; + if (maxCopy < keyFrame.value_.end_) + maxCopy = keyFrame.value_.end_; + } + } + else maxCopy = obj->mCopies.value(); + obj->mMaxCopies = maxCopy; + break; + } + case "o"_hash: parseProperty(obj->mOffset); break; + case "tr"_hash: getValue(obj->mTransform); break; + case "hd"_hash: obj->setHidden(GetBool()); break; + default: +#ifdef DEBUG_PARSER + vDebug << "Repeater property ignored :" << key; +#endif + Skip(key); + } + } + obj->setStatic(obj->mCopies.isStatic() && obj->mOffset.isStatic() && + obj->mTransform.isStatic()); + + return obj; +} + +/* + * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/transform.json + */ +model::Transform *LottieParserImpl::parseTransformObject(bool ddd) +{ + auto objT = allocator().make(); + + auto obj = allocator().make(); + if (ddd) { + obj->createExtraData(); + obj->mExtra->m3DData = true; + } + + while (ROString key = NextObjectKey()) { + switch(key.hash()) { + case "nm"_hash: { ROString name = GetString(); objT->setName(name.getData(), name.getLength()); break; } + case "a"_hash: parseProperty(obj->mAnchor); break; + case "r"_hash: parseProperty(obj->mRotation); break; + case "s"_hash: parseProperty(obj->mScale); break; + case "o"_hash: parseProperty(obj->mOpacity); break; + case "hd"_hash: objT->setHidden(GetBool()); break; + case "rx"_hash: + if (!obj->mExtra) return nullptr; + parseProperty(obj->mExtra->m3DRx); + break; + case "ry"_hash: + if (!obj->mExtra) return nullptr; + parseProperty(obj->mExtra->m3DRy); + break; + case "rz"_hash: + if (!obj->mExtra) return nullptr; + parseProperty(obj->mExtra->m3DRz); + break; + case "p"_hash: { + EnterObject(); + bool separate = false; + while (ROString k = NextObjectKey()) { + switch(k.hash()) { + case "k"_hash: parsePropertyHelper(obj->mPosition); break; + case "s"_hash: + obj->createExtraData(); + obj->mExtra->mSeparate = GetBool(); + separate = true; + break; + case "x"_hash: if (separate) parseProperty(obj->mExtra->mSeparateX); break; + case "y"_hash: if (separate) parseProperty(obj->mExtra->mSeparateY); break; + default: + Skip(key); + } + } + break; + } + default: + Skip(key); + } + } + bool isStatic = obj->mAnchor.isStatic() && obj->mPosition.isStatic() && + obj->mRotation.isStatic() && obj->mScale.isStatic() && + obj->mOpacity.isStatic(); + if (obj->mExtra) { + isStatic = isStatic && obj->mExtra->m3DRx.isStatic() && + obj->mExtra->m3DRy.isStatic() && + obj->mExtra->m3DRz.isStatic() && + obj->mExtra->mSeparateX.isStatic() && + obj->mExtra->mSeparateY.isStatic(); + } + + objT->set(obj, isStatic); + + return objT; +} + +/* + * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/fill.json + */ +model::Fill *LottieParserImpl::parseFillObject() +{ + auto obj = allocator().make(); + + while (ROString key = NextObjectKey()) { + switch(key.hash()) { + case "nm"_hash: { ROString name = GetString(); obj->setName(name.getData(), name.getLength()); break; } + case "c"_hash: parseProperty(obj->mColor); break; + case "o"_hash: parseProperty(obj->mOpacity); break; + case "fillEnabled"_hash: obj->mEnabled = GetBool(); break; + case "r"_hash: obj->mFillRule = getFillRule(); break; + case "hd"_hash: obj->setHidden(GetBool()); break; + default: +#ifdef DEBUG_PARSER + vWarning << "Fill property skipped = " << key; +#endif + Skip(key); + } + } + obj->setStatic(obj->mColor.isStatic() && obj->mOpacity.isStatic()); + + return obj; +} + +/* + * https://github.com/airbnb/lottie-web/blob/master/docs/json/helpers/lineCap.json + */ +CapStyle LottieParserImpl::getLineCap() +{ + switch (GetInt()) { + case 1: + return CapStyle::Flat; + break; + case 2: + return CapStyle::Round; + break; + default: + return CapStyle::Square; + break; + } +} + +FillRule LottieParserImpl::getFillRule() +{ + switch (GetInt()) { + case 1: + return FillRule::Winding; + break; + case 2: + return FillRule::EvenOdd; + break; + default: + return FillRule::Winding; + break; + } +} + +/* + * https://github.com/airbnb/lottie-web/blob/master/docs/json/helpers/lineJoin.json + */ +JoinStyle LottieParserImpl::getLineJoin() +{ + switch (GetInt()) { + case 1: + return JoinStyle::Miter; + break; + case 2: + return JoinStyle::Round; + break; + default: + return JoinStyle::Bevel; + break; + } +} + +/* + * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/stroke.json + */ +model::Stroke *LottieParserImpl::parseStrokeObject() +{ + auto obj = allocator().make(); + + while (ROString key = NextObjectKey()) { + switch(key.hash()) { + case "nm"_hash: { ROString name = GetString(); obj->setName(name.getData(), name.getLength()); break; } + case "c"_hash: parseProperty(obj->mColor); break; + case "o"_hash: parseProperty(obj->mOpacity); break; + case "w"_hash: parseProperty(obj->mWidth); break; + case "fillEnabled"_hash: obj->mEnabled = GetBool(); break; + case "lc"_hash: obj->mCapStyle = getLineCap(); break; + case "lj"_hash: obj->mJoinStyle = getLineJoin(); break; + case "ml"_hash: obj->mMiterLimit = GetDouble(); break; + case "d"_hash: parseDashProperty(obj->mDash); break; + case "hd"_hash: obj->setHidden(GetBool()); break; + default: +#ifdef DEBUG_PARSER + vWarning << "Stroke property skipped = " << key; +#endif + Skip(key); + } + } + obj->setStatic(obj->mColor.isStatic() && obj->mOpacity.isStatic() && + obj->mWidth.isStatic() && obj->mDash.isStatic()); + return obj; +} + +void LottieParserImpl::parseGradientProperty(model::Gradient *obj, + const ROString & key) +{ + switch(key.hash()) { + case "t"_hash: obj->mGradientType = GetInt(); break; + case "o"_hash: parseProperty(obj->mOpacity); break; + case "s"_hash: parseProperty(obj->mStartPoint); break; + case "e"_hash: parseProperty(obj->mEndPoint); break; + case "h"_hash: parseProperty(obj->mHighlightLength); break; + case "a"_hash: parseProperty(obj->mHighlightAngle); break; + case "hd"_hash: obj->setHidden(GetBool()); break; + case "g"_hash: { + EnterObject(); + while (ROString k = NextObjectKey()) { + if (k == "k") parseProperty(obj->mGradient); + else if (k == "p") obj->mColorPoints = GetInt(); + else + { + Skip(k); + } + } + break; + } + default: +#ifdef DEBUG_PARSER + vWarning << "Gradient property skipped = " << key; +#endif + Skip(key); + } + obj->setStatic( + obj->mOpacity.isStatic() && obj->mStartPoint.isStatic() && + obj->mEndPoint.isStatic() && obj->mHighlightAngle.isStatic() && + obj->mHighlightLength.isStatic() && obj->mGradient.isStatic()); +} + +/* + * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/gfill.json + */ +model::GradientFill *LottieParserImpl::parseGFillObject() +{ + auto obj = allocator().make(); + + while (ROString key = NextObjectKey()) { + switch(key.hash()) { + case "nm"_hash: { ROString name = GetString(); obj->setName(name.getData(), name.getLength()); break; } + case "r"_hash: obj->mFillRule = getFillRule(); break; + default: + parseGradientProperty(obj, key); + } + } + return obj; +} + +void LottieParserImpl::parseDashProperty(model::Dash &dash) +{ + EnterArray(); + while (NextArrayValue()) { + EnterObject(); + while (ROString key = NextObjectKey()) { + if (key == "v") { + dash.mData.emplace_back(); + parseProperty(dash.mData.back()); + } else { + Skip(key); + } + } + } +} + +/* + * https://github.com/airbnb/lottie-web/blob/master/docs/json/shapes/gstroke.json + */ +model::GradientStroke *LottieParserImpl::parseGStrokeObject() +{ + auto obj = allocator().make(); + + while (ROString key = NextObjectKey()) { + switch(key.hash()) { + case "nm"_hash: { ROString name = GetString(); obj->setName(name.getData(), name.getLength()); break; } + case "w"_hash: parseProperty(obj->mWidth); break; + case "lc"_hash: obj->mCapStyle = getLineCap(); break; + case "lj"_hash: obj->mJoinStyle = getLineJoin(); break; + case "ml"_hash: obj->mMiterLimit = GetDouble(); break; + case "d"_hash: parseDashProperty(obj->mDash); break; + default: + parseGradientProperty(obj, key); + } + } + + obj->setStatic(obj->isStatic() && obj->mWidth.isStatic() && + obj->mDash.isStatic()); + return obj; +} + +void LottieParserImpl::getValue(VVector &v) +{ + EnterArray(); + while (NextArrayValue()) { + EnterArray(); + VPointF pt; + getValue(pt); + v.push_back(pt); + } +} + +void LottieParserImpl::getValue(VPointF &pt) +{ + float val[4] = {0.f}; + int i = 0; + + if (PeekType() == Array) EnterArray(); + + while (NextArrayValue()) { + const auto value = GetDouble(); + if (i < 4) { + val[i++] = value; + } + } + pt.setX(val[0]); + pt.setY(val[1]); +} + +void LottieParserImpl::getValue(float &val) +{ + if (PeekType() == Array) { + EnterArray(); + if (NextArrayValue()) val = GetDouble(); + // discard rest + while (NextArrayValue()) { + GetDouble(); + } + } else if (PeekType() == Number) { + val = GetDouble(); + } else { + Error(); + } +} + +void LottieParserImpl::getValue(model::Color &color) +{ + float val[4] = {0.f}; + int i = 0; + if (PeekType() == Array) EnterArray(); + + while (NextArrayValue()) { + const auto value = GetDouble(); + if (i < 4) { + val[i++] = value; + } + } + + if (mColorFilter) mColorFilter(val[0], val[1], val[2]); + + color.r = val[0]; + color.g = val[1]; + color.b = val[2]; +} + +void LottieParserImpl::getValue(model::Gradient::Data &grad) +{ + if (PeekType() == Array) EnterArray(); + + while (NextArrayValue()) { + grad.mGradient.push_back(GetDouble()); + } +} + +void LottieParserImpl::getValue(int &val) +{ + if (PeekType() == Array) { + EnterArray(); + while (NextArrayValue()) { + val = GetInt(); + } + } else if (PeekType() == Number) { + val = GetInt(); + } else { + Error(); + } +} + +void LottieParserImpl::parsePathInfo() +{ + mPathInfo.reset(); + + /* + * The shape object could be wrapped by a array + * if its part of the keyframe object + */ + bool arrayWrapper = (PeekType() == Array); + if (arrayWrapper) EnterArray(); + + EnterObject(); + while (ROString key = NextObjectKey()) { + switch(key.hash()) { + case "i"_hash: getValue(mPathInfo.mInPoint); break; + case "o"_hash: getValue(mPathInfo.mOutPoint); break; + case "v"_hash: getValue(mPathInfo.mVertices); break; + case "c"_hash: mPathInfo.mClosed = GetBool(); break; + default: + Error(); + Skip(key); + } + } + // exit properly from the array + if (arrayWrapper) NextArrayValue(); + + mPathInfo.convert(); +} + +void LottieParserImpl::getValue(model::PathData &obj) +{ + parsePathInfo(); + obj.mPoints = mPathInfo.mResult; + obj.mClosed = mPathInfo.mClosed; +} + +VPointF LottieParserImpl::parseInperpolatorPoint() +{ + VPointF cp; + EnterObject(); + while (ROString key = NextObjectKey()) { + if (key == "x") { + getValue(cp.rx()); + } + else if (key == "y") { + getValue(cp.ry()); + } + } + return cp; +} + +template +bool LottieParserImpl::parseKeyFrameValue( + const ROString & key, model::Value &value) +{ + if (key == "ti") { + value.hasTangent_ = true; + getValue(value.inTangent_); + } else if (key == "to") { + value.hasTangent_ = true; + getValue(value.outTangent_); + } else { + return false; + } + return true; +} + +VInterpolator *LottieParserImpl::interpolator(VPointF inTangent, + VPointF outTangent, + std::string key) +{ + if (key.empty()) { + std::array temp; + snprintf(temp.data(), temp.size(), "%.2f_%.2f_%.2f_%.2f", inTangent.x(), + inTangent.y(), outTangent.x(), outTangent.y()); + key = temp.data(); + } + + auto search = mInterpolatorCache.find(key); + + if (search != mInterpolatorCache.end()) { + return search->second; + } + + auto obj = allocator().make(outTangent, inTangent); + mInterpolatorCache[std::move(key)] = obj; + return obj; +} + +/* + * https://github.com/airbnb/lottie-web/blob/master/docs/json/properties/multiDimensionalKeyframed.json + */ +template +void LottieParserImpl::parseKeyFrame(model::KeyFrames &obj) +{ + struct ParsedField { + std::string interpolatorKey; + bool interpolator{false}; + bool value{false}; + bool hold{false}; + bool noEndValue{true}; + }; + + EnterObject(); + ParsedField parsed; + typename model::KeyFrames::Frame keyframe; + VPointF inTangent; + VPointF outTangent; + + while (ROString key = NextObjectKey()) { + switch(key.hash()) { + case "i"_hash: { + parsed.interpolator = true; + inTangent = parseInperpolatorPoint(); + break; + } + case "o"_hash: outTangent = parseInperpolatorPoint(); break; + case "t"_hash: keyframe.start_ = GetDouble(); break; + case "s"_hash: { + parsed.value = true; + getValue(keyframe.value_.start_); + continue; + } + case "e"_hash: { + parsed.noEndValue = false; + getValue(keyframe.value_.end_); + continue; + } + case "n"_hash: { + if (PeekType() == String) { + parsed.interpolatorKey = GetStringObject(); + } else { + EnterArray(); + while (NextArrayValue()) { + if (parsed.interpolatorKey.empty()) { + parsed.interpolatorKey = GetStringObject(); + } else { + // skip rest of the string + Skip(key); + } + } + } + continue; + } + case "h"_hash: { + parsed.hold = GetInt(); + continue; + } + default: + if (parseKeyFrameValue(key, keyframe.value_)) continue; +#ifdef DEBUG_PARSER + vDebug << "key frame property skipped = " << key; +#endif + Skip(key); + } + } + + auto &list = obj.frames_; + if (!list.empty()) { + // update the endFrame value of current keyframe + list.back().end_ = keyframe.start_; + // if no end value provided, copy start value to previous frame + if (parsed.value && parsed.noEndValue) { + list.back().value_.end_ = keyframe.value_.start_; + } + } + + if (parsed.hold) { + keyframe.value_.end_ = keyframe.value_.start_; + keyframe.end_ = keyframe.start_; + list.push_back(std::move(keyframe)); + } else if (parsed.interpolator) { + keyframe.interpolator_ = interpolator( + inTangent, outTangent, std::move(parsed.interpolatorKey)); + list.push_back(std::move(keyframe)); + } else { + // its the last frame discard. + } +} + +/* + * https://github.com/airbnb/lottie-web/blob/master/docs/json/properties/shapeKeyframed.json + */ + +/* + * https://github.com/airbnb/lottie-web/blob/master/docs/json/properties/shape.json + */ +void LottieParserImpl::parseShapeProperty(model::Property &obj) +{ + EnterObject(); + while (ROString key = NextObjectKey()) { + if (key == "k") { + if (PeekType() == Array) { + EnterArray(); + while (NextArrayValue()) { + parseKeyFrame(obj.animation()); + } + } else { + if (!obj.isStatic()) { + Error(); + return; + } + getValue(obj.value()); + } + } else { +#ifdef DEBUG_PARSER + vDebug << "shape property ignored = " << key; +#endif + Skip(key); + } + } + obj.cache(); +} + +template +void LottieParserImpl::parsePropertyHelper(model::Property &obj) +{ + if (PeekType() == Number) { + if (!obj.isStatic()) { + Error(); + return; + } + /*single value property with no animation*/ + getValue(obj.value()); + } else { + EnterArray(); + while (NextArrayValue()) { + /* property with keyframe info*/ + if (PeekType() == Object) { + parseKeyFrame(obj.animation()); + } else { + /* Read before modifying. + * as there is no way of knowing if the + * value of the array is either array of numbers + * or array of object without entering the array + * thats why this hack is there + */ + if (!obj.isStatic()) { + Error(); + return; + } + /*multi value property with no animation*/ + getValue(obj.value()); + /*break here as we already reached end of array*/ + break; + } + } + obj.cache(); + } +} + +/* + * https://github.com/airbnb/lottie-web/tree/master/docs/json/properties + */ +template +void LottieParserImpl::parseProperty(model::Property &obj) +{ + EnterObject(); + while (ROString key = NextObjectKey()) { + if (key == "k") { + parsePropertyHelper(obj); + } else { + Skip(key); + } + } +} + +#ifdef LOTTIE_DUMP_TREE_SUPPORT + +class ObjectInspector { +public: + void visit(model::Composition *obj, std::string level) + { + vDebug << " { " << level << "Composition:: a: " << !obj->isStatic() + << ", v: " << obj->mVersion << ", stFm: " << obj->startFrame() + << ", endFm: " << obj->endFrame() + << ", W: " << obj->size().width() + << ", H: " << obj->size().height() << "\n"; + level.append("\t"); + visit(obj->mRootLayer, level); + level.erase(level.end() - 1, level.end()); + vDebug << " } " << level << "Composition End\n"; + } + void visit(model::Layer *obj, std::string level) + { + vDebug << level << "{ " << layerType(obj->mLayerType) + << ", name: " << obj->name() << ", id:" << obj->mId + << " Pid:" << obj->mParentId << ", a:" << !obj->isStatic() + << ", " << matteType(obj->mMatteType) + << ", mask:" << obj->hasMask() << ", inFm:" << obj->mInFrame + << ", outFm:" << obj->mOutFrame << ", stFm:" << obj->mStartFrame + << ", ts:" << obj->mTimeStreatch << ", ao:" << obj->autoOrient() + << ", W:" << obj->layerSize().width() + << ", H:" << obj->layerSize().height(); + + if (obj->mLayerType == model::Layer::Type::Image) + vDebug << level << "\t{ " + << "ImageInfo:" + << " W :" << obj->extra()->mAsset->mWidth + << ", H :" << obj->extra()->mAsset->mHeight << " }" + << "\n"; + else { + vDebug << level; + } + visitChildren(static_cast(obj), level); + vDebug << level << "} " << layerType(obj->mLayerType).c_str() + << ", id: " << obj->mId << "\n"; + } + void visitChildren(model::Group *obj, std::string level) + { + level.append("\t"); + for (const auto &child : obj->mChildren) visit(child, level); + if (obj->mTransform) visit(obj->mTransform, level); + } + + void visit(model::Object *obj, std::string level) + { + switch (obj->type()) { + case model::Object::Type::Repeater: { + auto r = static_cast(obj); + vDebug << level << "{ Repeater: name: " << obj->name() + << " , a:" << !obj->isStatic() + << ", copies:" << r->maxCopies() + << ", offset:" << r->offset(0); + visitChildren(r->mContent, level); + vDebug << level << "} Repeater"; + break; + } + case model::Object::Type::Group: { + vDebug << level << "{ Group: name: " << obj->name() + << " , a:" << !obj->isStatic(); + visitChildren(static_cast(obj), level); + vDebug << level << "} Group"; + break; + } + case model::Object::Type::Layer: { + visit(static_cast(obj), level); + break; + } + case model::Object::Type::Trim: { + vDebug << level << "{ Trim: name: " << obj->name() + << " , a:" << !obj->isStatic() << " }"; + break; + } + case model::Object::Type::Rect: { + vDebug << level << "{ Rect: name: " << obj->name() + << " , a:" << !obj->isStatic() << " }"; + break; + } + case model::Object::Type::RoundedCorner: { + vDebug << level << "{ RoundedCorner: name: " << obj->name() + << " , a:" << !obj->isStatic() << " }"; + break; + } + case model::Object::Type::Ellipse: { + vDebug << level << "{ Ellipse: name: " << obj->name() + << " , a:" << !obj->isStatic() << " }"; + break; + } + case model::Object::Type::Path: { + vDebug << level << "{ Shape: name: " << obj->name() + << " , a:" << !obj->isStatic() << " }"; + break; + } + case model::Object::Type::Polystar: { + vDebug << level << "{ Polystar: name: " << obj->name() + << " , a:" << !obj->isStatic() << " }"; + break; + } + case model::Object::Type::Transform: { + vDebug << level << "{ Transform: name: " << obj->name() + << " , a: " << !obj->isStatic() << " }"; + break; + } + case model::Object::Type::Stroke: { + vDebug << level << "{ Stroke: name: " << obj->name() + << " , a:" << !obj->isStatic() << " }"; + break; + } + case model::Object::Type::GStroke: { + vDebug << level << "{ GStroke: name: " << obj->name() + << " , a:" << !obj->isStatic() << " }"; + break; + } + case model::Object::Type::Fill: { + vDebug << level << "{ Fill: name: " << obj->name() + << " , a:" << !obj->isStatic() << " }"; + break; + } + case model::Object::Type::GFill: { + auto f = static_cast(obj); + vDebug << level << "{ GFill: name: " << obj->name() + << " , a:" << !f->isStatic() << ", ty:" << f->mGradientType + << ", s:" << f->mStartPoint.value(0) + << ", e:" << f->mEndPoint.value(0) << " }"; + break; + } + default: + break; + } + } + + std::string matteType(model::MatteType type) + { + switch (type) { + case model::MatteType::None: + return "Matte::None"; + break; + case model::MatteType::Alpha: + return "Matte::Alpha"; + break; + case model::MatteType::AlphaInv: + return "Matte::AlphaInv"; + break; + case model::MatteType::Luma: + return "Matte::Luma"; + break; + case model::MatteType::LumaInv: + return "Matte::LumaInv"; + break; + default: + return "Matte::Unknown"; + break; + } + } + std::string layerType(model::Layer::Type type) + { + switch (type) { + case model::Layer::Type::Precomp: + return "Layer::Precomp"; + break; + case model::Layer::Type::Null: + return "Layer::Null"; + break; + case model::Layer::Type::Shape: + return "Layer::Shape"; + break; + case model::Layer::Type::Solid: + return "Layer::Solid"; + break; + case model::Layer::Type::Image: + return "Layer::Image"; + break; + case model::Layer::Type::Text: + return "Layer::Text"; + break; + default: + return "Layer::Unknown"; + break; + } + } +}; + +#endif + +std::shared_ptr model::parse(const char * str, size_t len, + std::string dir_path, + model::ColorFilter filter) +{ + LottieParserImpl obj(str, len, std::move(dir_path), std::move(filter)); + + if (obj.VerifyType()) { + obj.parseComposition(); + auto composition = obj.composition(); + if (composition) { + composition->processRepeaterObjects(); + composition->updateStats(); + +#ifdef LOTTIE_DUMP_TREE_SUPPORT + ObjectInspector inspector; + inspector.visit(composition.get(), ""); +#endif + + return composition; + } + } + + vWarning << "Input data is not Lottie format!"; + return {}; +} + diff --git a/src/lottie/meson.build b/src/lottie/meson.build index fcf8239c..1df3b82e 100644 --- a/src/lottie/meson.build +++ b/src/lottie/meson.build @@ -1,6 +1,5 @@ source_file = [ - 'lottieparser.cpp', 'lottieloader.cpp', 'lottiemodel.cpp', 'lottieproxymodel.cpp', @@ -10,6 +9,13 @@ source_file = [ 'lottiekeypath.cpp' ] +if get_option('json') + source_file += ['lottieroparser.cpp'] +else + source_file += ['lottieparser.cpp'] +endif + + lottie_dep = declare_dependency( include_directories : include_directories('.'), sources : source_file diff --git a/src/vector/varenaalloc.h b/src/vector/varenaalloc.h index ed03b53f..90272074 100644 --- a/src/vector/varenaalloc.h +++ b/src/vector/varenaalloc.h @@ -16,7 +16,7 @@ #include #include #include -#include +#include "vvector.h" // SkArenaAlloc allocates object and destroys the allocated objects when destroyed. It's designed // to minimize the number of underlying block allocations. SkArenaAlloc allocates first out of an diff --git a/src/vector/vbrush.h b/src/vector/vbrush.h index a1abbd34..08ba9f52 100644 --- a/src/vector/vbrush.h +++ b/src/vector/vbrush.h @@ -23,7 +23,7 @@ #ifndef VBRUSH_H #define VBRUSH_H -#include +#include "vvector.h" #include "vglobal.h" #include "vmatrix.h" #include "vpoint.h" @@ -32,7 +32,7 @@ V_BEGIN_NAMESPACE using VGradientStop = std::pair; -using VGradientStops = std::vector; +using VGradientStops = VVector; class VGradient { public: enum class Mode { Absolute, Relative }; diff --git a/src/vector/vdasher.cpp b/src/vector/vdasher.cpp index 759e30ac..b5ccd73f 100644 --- a/src/vector/vdasher.cpp +++ b/src/vector/vdasher.cpp @@ -194,8 +194,8 @@ void VDasher::dashHelper(const VPath &path, VPath &result) mResult = &result; mResult->reserve(path.points().size(), path.elements().size()); mIndex = 0; - const std::vector &elms = path.elements(); - const std::vector & pts = path.points(); + const VVector &elms = path.elements(); + const VVector & pts = path.points(); const VPointF * ptPtr = pts.data(); for (auto &i : elms) { diff --git a/src/vector/vdrawable.cpp b/src/vector/vdrawable.cpp index 55b1bc84..2fdab6d2 100644 --- a/src/vector/vdrawable.cpp +++ b/src/vector/vdrawable.cpp @@ -97,7 +97,7 @@ void VDrawable::setStrokeInfo(CapStyle cap, JoinStyle join, float miterLimit, mFlag |= DirtyState::Path; } -void VDrawable::setDashInfo(std::vector &dashInfo) +void VDrawable::setDashInfo(VVector &dashInfo) { assert(mStrokeInfo); assert(mType == VDrawable::Type::StrokeWithDash); diff --git a/src/vector/vdrawable.h b/src/vector/vdrawable.h index 357a69fd..b9302483 100644 --- a/src/vector/vdrawable.h +++ b/src/vector/vdrawable.h @@ -28,6 +28,7 @@ #include "vpath.h" #include "vrle.h" #include "vraster.h" +#include "vvector.h" class VDrawable { public: @@ -55,7 +56,7 @@ class VDrawable { void setBrush(const VBrush &brush) { mBrush = brush; } void setStrokeInfo(CapStyle cap, JoinStyle join, float miterLimit, float strokeWidth); - void setDashInfo(std::vector &dashInfo); + void setDashInfo(VVector &dashInfo); void preprocess(const VRect &clip); void applyDashOp(); VRle rle(); @@ -74,7 +75,7 @@ class VDrawable { }; struct StrokeWithDashInfo : public StrokeInfo{ - std::vector mDash; + VVector mDash; }; public: diff --git a/src/vector/vimageloader.cpp b/src/vector/vimageloader.cpp index c2446be9..9ac1983e 100644 --- a/src/vector/vimageloader.cpp +++ b/src/vector/vimageloader.cpp @@ -5,7 +5,7 @@ #ifdef _WIN32 # include -#else +#elif !defined(ESP_PLATFORM) # include #endif // _WIN32 diff --git a/src/vector/vpath.cpp b/src/vector/vpath.cpp index d49cd009..7cf99202 100644 --- a/src/vector/vpath.cpp +++ b/src/vector/vpath.cpp @@ -22,7 +22,6 @@ #include "vpath.h" #include #include -#include #include "vbezier.h" #include "vdebug.h" #include "vline.h" @@ -695,12 +694,10 @@ void VPath::VPathData::addPath(const VPathData &path, const VMatrix *m) m_points.push_back(m->map(i)); } } else { - std::copy(path.m_points.begin(), path.m_points.end(), - back_inserter(m_points)); + std::copy(path.m_points.begin(), path.m_points.end(), std::back_inserter(m_points)); } - std::copy(path.m_elements.begin(), path.m_elements.end(), - back_inserter(m_elements)); + std::copy(path.m_elements.begin(), path.m_elements.end(), std::back_inserter(m_elements)); m_segments += segment; mLengthDirty = true; diff --git a/src/vector/vpath.h b/src/vector/vpath.h index 9273cb61..a427bd37 100644 --- a/src/vector/vpath.h +++ b/src/vector/vpath.h @@ -22,11 +22,11 @@ #ifndef VPATH_H #define VPATH_H -#include #include "vcowptr.h" #include "vmatrix.h" #include "vpoint.h" #include "vrect.h" +#include "vvector.h" V_BEGIN_NAMESPACE @@ -70,8 +70,8 @@ class VPath { void addPath(const VPath &path, const VMatrix &m); void transform(const VMatrix &m); float length() const; - const std::vector &elements() const; - const std::vector & points() const; + const VVector &elements() const; + const VVector & points() const; void clone(const VPath &srcPath); bool unique() const { return d.unique();} size_t refCount() const { return d.refCount();} @@ -105,13 +105,13 @@ class VPath { VPath::Direction dir = Direction::CW); void addPath(const VPathData &path, const VMatrix *m = nullptr); void clone(const VPath::VPathData &o) { *this = o;} - const std::vector &elements() const + const VVector &elements() const { return m_elements; } - const std::vector &points() const { return m_points; } - std::vector m_points; - std::vector m_elements; + const VVector &points() const { return m_points; } + VVector m_points; + VVector m_elements; size_t m_segments; VPointF mStartPoint; mutable float mLength{0}; @@ -265,12 +265,12 @@ inline void VPath::addPath(const VPath &path, const VMatrix &m) d.write().addPath(path.d.read(), &m); } -inline const std::vector &VPath::elements() const +inline const VVector &VPath::elements() const { return d->elements(); } -inline const std::vector &VPath::points() const +inline const VVector &VPath::points() const { return d->points(); } diff --git a/src/vector/vraster.cpp b/src/vector/vraster.cpp index fdf66fb0..ed0fdd80 100644 --- a/src/vector/vraster.cpp +++ b/src/vector/vraster.cpp @@ -104,8 +104,8 @@ void FTOutline::grow(size_t points, size_t segments) void FTOutline::convert(const VPath &path) { - const std::vector &elements = path.elements(); - const std::vector & points = path.points(); + const VVector &elements = path.elements(); + const VVector & points = path.points(); grow(points.size(), path.segments()); @@ -422,8 +422,8 @@ using VTask = std::shared_ptr; class RleTaskScheduler { const unsigned _count{std::thread::hardware_concurrency()}; - std::vector _threads; - std::vector> _q{_count}; + VVector _threads; + VVector> _q{_count}; std::atomic _index{0}; void run(unsigned i) diff --git a/src/vector/vrle.cpp b/src/vector/vrle.cpp index 753db1a1..641779c2 100644 --- a/src/vector/vrle.cpp +++ b/src/vector/vrle.cpp @@ -28,7 +28,7 @@ #include #include #include -#include +#include "vvector.h" #include "vdebug.h" #include "vglobal.h" @@ -47,11 +47,11 @@ static inline uint8_t divBy255(int x) } inline static void copy(const VRle::Span *span, size_t count, - std::vector &v) + VVector &v) { // make sure enough memory available if (v.capacity() < v.size() + count) v.reserve(v.size() + count); - std::copy(span, span + count, back_inserter(v)); + std::copy(span, span + count, std::back_inserter(v)); } void VRle::Data::addSpan(const VRle::Span *span, size_t count) diff --git a/src/vector/vrle.h b/src/vector/vrle.h index 1bbb8365..989a9ea7 100644 --- a/src/vector/vrle.h +++ b/src/vector/vrle.h @@ -23,7 +23,7 @@ #ifndef VRLE_H #define VRLE_H -#include +#include "vvector.h" #include "vcowptr.h" #include "vglobal.h" #include "vpoint.h" @@ -99,7 +99,7 @@ class VRle { void addRect(const VRect &rect); void clone(const VRle::Data &); - std::vector mSpans; + VVector mSpans; VPoint mOffset; mutable VRect mBbox; mutable bool mBboxDirty = true; diff --git a/src/vector/vvector.h b/src/vector/vvector.h new file mode 100644 index 00000000..79a7d84f --- /dev/null +++ b/src/vector/vvector.h @@ -0,0 +1,446 @@ +/* + * Copyright (c) 2022 Samsung Electronics Co., Ltd. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef VVECTOR_H +#define VVECTOR_H + +#include +#include +#include +#include "config.h" + +namespace lottie +{ + +#ifndef CUSTOM_LOTTIE_ALLOCATOR + struct LottieAllocator + { + static void * alloc(size_t n) { return ::malloc(n); } + static void free(void * p) { return ::free(p); } + static void * realloc(void * p , size_t n) { return ::realloc(p, n); } + }; + +#endif + /** An memory efficient vector class that implement std::vector interface. + Unlike std::vector, this implementation does not grow exponentially, so appending to + the vector is a O(N) operation + + @param T The type stored by the vector + @param LottieAllocator An allocator that must implement: + - static void * alloc(size_t) + - static void free(void *) + - static void * realloc(void *, size_t) + */ + template + class vector + { + typedef vector this_type; + + public: + typedef T value_type; + typedef T* pointer; + typedef const T* const_pointer; + typedef T& reference; + typedef const T& const_reference; + typedef T* iterator; + typedef const T* const_iterator; + typedef std::reverse_iterator reverse_iterator; + typedef std::reverse_iterator const_reverse_iterator; + typedef size_t size_type; + typedef ptrdiff_t difference_type; + + const static bool trivial = std::is_trivially_copyable::value; + + public: + vector() noexcept : first(nullptr), last(nullptr) {} + explicit vector(size_type n) { allocate(n); } + vector(size_type n, const value_type& value) { allocate(n, value); } + vector(const this_type& x) { allocate(x.size()); assign(x.first, x.last); } + + template + vector(InputIterator first, InputIterator last) { + allocate(last - first); assign(first, last); + } + + ~vector() { + deallocate(); + } + + this_type& operator=(const this_type& x) { + if (this != &x) { + deallocate(); + allocate(x.size()); + assign(x.first, x.last); + } + return *this; + } + + iterator begin() noexcept { return first; } + const_iterator begin() const noexcept { return first; } + const_iterator cbegin() const noexcept { return first; } + + iterator end() noexcept { return last; } + const_iterator end() const noexcept { return last; } + const_iterator cend() const noexcept { return last; } + + reverse_iterator rbegin() noexcept { return reverse_iterator(end()); } + const_reverse_iterator rbegin() const noexcept{ return const_reverse_iterator(end()); } + const_reverse_iterator crbegin() const noexcept{ return const_reverse_iterator(cend()); } + + reverse_iterator rend() noexcept { return reverse_iterator(begin()); } + const_reverse_iterator rend() const noexcept { return const_reverse_iterator(begin()); } + const_reverse_iterator crend() const noexcept { return const_reverse_iterator(crbegin()); } + + bool empty() const noexcept { return size() == 0; } + size_type size() const noexcept { return (size_t)(last - first); } + size_type capacity() const noexcept { return cap; } + + void resize(size_type n, const value_type& value) { resize_impl(n, value); } + void resize(size_type n) { resize_impl(n); } + void reserve(size_type n) { if (n > cap) reserve_impl(n); } + + pointer data() noexcept { return first; } + const_pointer data() const noexcept { return first; } + + reference operator[](size_type n) { return first[n]; } + const_reference operator[](size_type n) const { return first[n]; } + + reference front() { return *first; } + const_reference front() const { return *first; } + + reference back() { return *(last - 1); } + const_reference back() const { return *(last - 1); } + + void push_back(const value_type& value) { if (size() == cap) reserve(cap + 1); new(last++) value_type(value); } + reference push_back() { if (size() == cap) resize(cap + 1); return *(last - 1); } + void push_back(value_type&& value) { if (size() == cap) reserve(cap + 1); new(last++) value_type(std::move(value)); } + void pop_back() { free(last - 1, last != nullptr ? 1 : 0); } // Don't reduce the capacity here? + + template + reference emplace_back(Args&&... args) { + if (size() == cap) reserve(cap + 1); + new(last++) value_type(std::forward(args)...); + return back(); + } + + reference emplace_back() { + if (size() == cap) reserve(cap + 1); + new(last++) value_type(); + return back(); + } + + + // iterator erase(const_iterator position); + iterator erase(const_iterator begin, const_iterator end) { + if (begin == end) return first; + move(const_cast(end), const_cast(begin)); + free(begin, (end - begin)); + last -= (end - begin); + return first; + } + + // reverse_iterator erase(const_reverse_iterator position); + // reverse_iterator erase(const_reverse_iterator first, const_reverse_iterator last); + + void clear() noexcept { deallocate(); } + + private: + pointer first, last; + size_t cap {0}; + + void allocate(size_t nelem) { + first = (value_type*)LottieAllocator::alloc(nelem * sizeof(value_type)); + last = &first[nelem]; + set(first, nelem); + cap = nelem; + } + + void allocate(size_t nelem, const_reference value) { + first = (value_type*)LottieAllocator::alloc(nelem * sizeof(value_type)); + last = &first[nelem]; + set(first, nelem, value); + cap = nelem; + } + + void assign(const_iterator b, const_iterator e) { + if ((size_t)(e - b) > size()) + e = b + size(); + copy(first, b, e - b); + } + + void deallocate() { + free(first, last - first); + LottieAllocator::free(first); + first = last = nullptr; + cap = 0; + } + + // Optimization functions for trivial types + private: + template typename std::enable_if::value>::type copy(U* dest, const U* src, size_t size) + { + memcpy(dest, src, sizeof(U) * size); + } + + template typename std::enable_if::value>::type copy(U* dest, const U* src, size_t size) + { + std::copy_n(src, size, dest); + } + + void move(pointer start, pointer into, const_pointer end = 0) { + if (!end) end = last; + for (pointer f = start; f != end; ++f, ++into) { + new(into) value_type(std::move(*f)); + f->~T(); + } + } + + template typename std::enable_if::value>::type reserve_impl(size_t size) + { + // Try to realloc first + pointer p = (pointer)LottieAllocator::realloc(first, size * sizeof(value_type)); + if (p == nullptr) return; // Should throw here + // No memcpy required, it's done in realloc + cap = size; + last = p + (last - first); + first = p; + } + + template typename std::enable_if::value>::type reserve_impl(size_t size) + { + // Can't realloc here, need the new size and copy the objects here + pointer p = (pointer)LottieAllocator::alloc(size * sizeof(value_type)); + if (p == nullptr) return; // Should throw here + move(first, p); + cap = size; + last = p + (last - first); + LottieAllocator::free(first); + first = p; + } + + template typename std::enable_if::value>::type resize_impl(size_t size) + { + size_t cur_size = (last - first); + if (cur_size == size) return; + // Try to realloc first + pointer p = (pointer)LottieAllocator::realloc(first, size * sizeof(value_type)); + if (p == nullptr) return; // Should throw here + // No memcpy required, it's done in realloc + cap = size; + if (cur_size < size) { + set(p + cur_size, size - cur_size); + } + last = p + size; + first = p; + } + + template typename std::enable_if::value>::type resize_impl(size_t size) + { + size_t cur_size = (last - first); + if (cur_size == size) return; + // Try to realloc first + pointer p = (pointer)LottieAllocator::alloc(size * sizeof(value_type)); + if (p == nullptr) return; // Should throw here + std::copy_n(std::make_move_iterator(first), std::min(cur_size, size), p); + free(first, cur_size); + cap = size; + if (cur_size < size) { + set(p + cur_size, size - cur_size); + } + last = p + size; + LottieAllocator::free(first); + first = p; + } + + template typename std::enable_if::value>::type resize_impl(size_t size, const U & value) + { + size_t cur_size = (last - first); + if (cur_size == size) return; + // Try to realloc first + pointer p = (pointer)LottieAllocator::realloc(first, size * sizeof(value_type)); + if (p == nullptr) return; // Should throw here + // No memcpy required, it's done in realloc + cap = size; + if (cur_size < size) { + set(p + cur_size, size - cur_size, value); + } + last = p + size; + first = p; + } + + template typename std::enable_if::value>::type resize_impl(size_t size, const U & value) + { + size_t cur_size = (last - first); + if (cur_size == size) return; + // Try to realloc first + pointer p = (pointer)LottieAllocator::alloc(size * sizeof(value_type)); + if (p == nullptr) return; // Should throw here + free(first + size, cur_size > size ? cur_size - size : 0); + std::copy_n(std::make_move_iterator(first), std::min(cur_size, size), p); + cap = size; + if (cur_size < size) { + set(p + cur_size, size - cur_size, value); + } + last = p + size; + LottieAllocator::free(first); + first = p; + } + + template typename std::enable_if::value>::type set(U* dest, size_t size, const U & value) + { + while (size--) + memcpy(dest++, &value, sizeof(U)); + } + + template typename std::enable_if::value>::type set(U* dest, size_t size, const U & value) + { + while (size--) + new(dest++) U(value); + } + template typename std::enable_if::value>::type set(U* dest, size_t size) + { + U u{}; + while (size--) + memcpy(dest++, &u, sizeof(U)); + } + + template typename std::enable_if::value>::type set(U* dest, size_t size) + { + while (size--) + new(dest++) U(); + } + + template typename std::enable_if::value>::type free(const U* dest, size_t size) + { + } + template typename std::enable_if::value>::type free(const U* dest, size_t size) + { + while (size--) + (dest++)->~U(); + } + + + + private: + /* Below is the missing interface from a std::vector that's not used in lottie, hence, not implemented */ + + // void set_capacity(size_type n = 0); // Revises the capacity to the user-specified value. Resizes the container to match the capacity if the requested capacity n is less than the current size. If n == npos then the capacity is reallocated (if necessary) such that capacity == size. + // void shrink_to_fit(); // C++11 function which is the same as set_capacity(). + // explicit vector(const allocator_type& allocator) noexcept; + // vector(const this_type& x, const allocator_type& allocator); + // vector(this_type&& x) noexcept; + // vector(this_type&& x, const allocator_type& allocator); + // vector(std::initializer_list ilist); + // this_type& operator=(std::initializer_list ilist); + // this_type& operator=(this_type&& x); // TODO(c++17): noexcept(allocator_traits::propagate_on_container_move_assignment::value || allocator_traits::is_always_equal::value) + + // void swap(this_type& x); // TODO(c++17): noexcept(allocator_traits::propagate_on_container_move_assignment::value || allocator_traits::is_always_equal::value) + + // void assign(size_type n, const value_type& value); + + // template + // void assign(InputIterator first, InputIterator last); + // template + // iterator emplace(const_iterator position, Args&&... args); + + // void assign(std::initializer_list ilist); + // iterator insert(const_iterator position, const value_type& value); + // iterator insert(const_iterator position, size_type n, const value_type& value); + // iterator insert(const_iterator position, value_type&& value); + // iterator insert(const_iterator position, std::initializer_list ilist); + + // template + // iterator insert(const_iterator position, InputIterator first, InputIterator last); + + // iterator erase_first(const T& value); + // iterator erase_first_unsorted(const T& value); // Same as erase, except it doesn't preserve order, but is faster because it simply copies the last item in the vector over the erased position. + // reverse_iterator erase_last(const T& value); + // reverse_iterator erase_last_unsorted(const T& value); // Same as erase, except it doesn't preserve order, but is faster because it simply copies the last item in the vector over the erased position. + // iterator erase_unsorted(const_iterator position); // Same as erase, except it doesn't preserve order, but is faster because it simply copies the last item in the vector over the erased position. + // reverse_iterator erase_unsorted(const_reverse_iterator position); + // void reset_lose_memory() noexcept; // This is a unilateral reset to an initially empty state. No destructors are called, no deallocation occurs. + + // bool validate() const noexcept; + // int validate_iterator(const_iterator i) const noexcept; + // reference at(size_type n); + // const_reference at(size_type n) const; + // void* push_back_uninitialized(); + }; + + template + inline bool operator==(const vector& a, const vector& b) + { + return ((a.size() == b.size()) && std::equal(a.begin(), a.end(), b.begin())); + } + + + template + inline bool operator!=(const vector& a, const vector& b) + { + return ((a.size() != b.size()) || !std::equal(a.begin(), a.end(), b.begin())); + } + + + template + inline bool operator<(const vector& a, const vector& b) + { + return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end()); + } + + + template + inline bool operator>(const vector& a, const vector& b) + { + return b < a; + } + + + template + inline bool operator<=(const vector& a, const vector& b) + { + return !(b < a); + } + + + template + inline bool operator>=(const vector& a, const vector& b) + { + return !(a < b); + } + + + template + inline void swap(vector& a, vector& b) + { + a.swap(b); + } +} + +#ifdef LOTTIE_MEMSHRINK_SUPPORT + template + using VVector = lottie::vector; +#else + #include + template + using VVector = std::vector; +#endif + +#endif