From 194bd8f01d8a0f2e8de33979a87cbd9dd8fc72f3 Mon Sep 17 00:00:00 2001 From: Lucas Niu Date: Sun, 14 Apr 2024 16:10:03 +0900 Subject: [PATCH] lottie: Support the Animation Segment(Marker) A single animation might have a desinated markers with naming: 0 ~ 0.5 (sector A), 0.5 ~ 1.0 (sector B). Selecting one of them using a marker name(sector A) and could play only that part with animation controllers. usage: - `animation->segment("sectionA") // Named segment(Marker)` - `auto cnt = animation->markerCnt()` - `auto name = animation->markers(index)` - `animation->segment(0, 0.5) // Segment` - `animation->segment(&begin, &end)` Co-authored-by: Jinny You --- inc/thorvg.h | 38 +++++++++++++++++ src/loaders/lottie/thorvg_lottie.h | 44 ++++++++++++++++++++ src/loaders/lottie/tvgLottieAnimation.cpp | 33 +++++++++++++++ src/loaders/lottie/tvgLottieLoader.cpp | 50 +++++++++++++++++++++-- src/loaders/lottie/tvgLottieLoader.h | 6 +++ src/loaders/lottie/tvgLottieModel.cpp | 4 ++ src/loaders/lottie/tvgLottieModel.h | 12 ++++++ src/loaders/lottie/tvgLottieParser.cpp | 24 +++++++++++ src/loaders/lottie/tvgLottieParser.h | 2 + src/renderer/tvgAnimation.cpp | 27 ++++++++++++ src/renderer/tvgFrameModule.h | 13 ++++++ 11 files changed, 249 insertions(+), 4 deletions(-) diff --git a/inc/thorvg.h b/inc/thorvg.h index ed0e13c3..f7f83091 100644 --- a/inc/thorvg.h +++ b/inc/thorvg.h @@ -1909,6 +1909,44 @@ public: */ float duration() const noexcept; + /** + * @brief Specifies the playback segment of the animation. + * + * The set segment is designated as the play area of the animation. + * This is useful for playing a specific segment within the entire animation. + * After setting, the number of animation frames and the playback time are calculated + * by mapping the playback segment as the entire range. + * + * @param[in] begin segment start. + * @param[in] end segment end. + * + * @retval Result::Success When succeed. + * @retval Result::InsufficientCondition In case the animation is not loaded. + * @retval Result::InvalidArguments When the given parameter is invalid. + * @retval Result::NonSupport When it's not animatable. + * + * @note Range from 0.0~1.0 + * @note If a marker has been specified, its range will be disregarded. + * @see LottieAnimation::segment(const char* marker) + * @note Experimental API + */ + Result segment(float begin, float end) noexcept; + + /** + * @brief Gets the current segment. + * + * @param[out] begin segment start. + * @param[out] end segment end. + * + * @retval Result::Success When succeed. + * @retval Result::InsufficientCondition In case the animation is not loaded. + * @retval Result::InvalidArguments When the given parameter is invalid. + * @retval Result::NonSupport When it's not animatable. + * + * @note Experimental API + */ + Result segment(float* begin, float* end = nullptr) noexcept; + /** * @brief Creates a new Animation object. * diff --git a/src/loaders/lottie/thorvg_lottie.h b/src/loaders/lottie/thorvg_lottie.h index 7f59dfb1..3af73e95 100644 --- a/src/loaders/lottie/thorvg_lottie.h +++ b/src/loaders/lottie/thorvg_lottie.h @@ -35,6 +35,50 @@ public: */ Result override(const char* slot) noexcept; + /** + * @brief Specifies a segment by marker. + * + * Markers are used to control animation playback by specifying start and end points, + * eliminating the need to know the exact frame numbers. + * Generally, markers are designated at the design level, + * meaning the callers must know the marker name in advance to use it. + * + * @param[in] marker The name of the segment marker. + * + * @retval Result::Success When successful. + * @retval Result::InsufficientCondition If the animation is not loaded. + * @retval Result::InvalidArguments When the given parameter is invalid. + * @retval Result::NonSupport When it's not animatable. + * + * @note If a @c marker is specified, the previously set segment will be disregarded. + * @note Set @c nullptr to reset the specified segment. + * @see Animation::segment(float begin, float end) + * @note Experimental API + */ + Result segment(const char* marker) noexcept; + + /** + * @brief Gets the marker count of the animation. + * + * @retval The count of the markers, zero if there is no marker. + * + * @see LottieAnimation::marker() + * @note Experimental API + */ + uint32_t markersCnt() noexcept; + + /** + * @brief Gets the marker name by a given index. + * + * @param[in] idx The index of the animation marker, starts from 0. + * + * @retval The name of marker when succeed, @c nullptr otherwise. + * + * @see LottieAnimation::markersCnt() + * @note Experimental API + */ + const char* marker(uint32_t idx) noexcept; + /** * @brief Creates a new LottieAnimation object. * diff --git a/src/loaders/lottie/tvgLottieAnimation.cpp b/src/loaders/lottie/tvgLottieAnimation.cpp index 63ce4cc2..636fc377 100644 --- a/src/loaders/lottie/tvgLottieAnimation.cpp +++ b/src/loaders/lottie/tvgLottieAnimation.cpp @@ -49,6 +49,39 @@ Result LottieAnimation::override(const char* slot) noexcept return Result::InvalidArguments; } +Result LottieAnimation::segment(const char* marker) noexcept +{ + auto loader = pImpl->picture->pImpl->loader; + if (!loader) return Result::InsufficientCondition; + if (!loader->animatable()) return Result::NonSupport; + + auto lottieLoader = static_cast(loader); + + if (!marker) { + lottieLoader->segment(0.0, 1.0); + return Result::Success; + } + + float begin, end; + if (!lottieLoader->getSegment(begin, end, marker)) { + return Result::InvalidArguments; + } + return static_cast(this)->segment(begin, end); +} + +uint32_t LottieAnimation::markersCnt() noexcept +{ + auto loader = pImpl->picture->pImpl->loader; + if (!loader || !loader->animatable()) return 0; + return static_cast(loader)->markerCount(); +} + +const char* LottieAnimation::marker(uint32_t idx) noexcept +{ + auto loader = pImpl->picture->pImpl->loader; + if (!loader || !loader->animatable()) return nullptr; + return static_cast(loader)->markers(idx); +} unique_ptr LottieAnimation::gen() noexcept { diff --git a/src/loaders/lottie/tvgLottieLoader.cpp b/src/loaders/lottie/tvgLottieLoader.cpp index 25e80e57..d2f3face 100644 --- a/src/loaders/lottie/tvgLottieLoader.cpp +++ b/src/loaders/lottie/tvgLottieLoader.cpp @@ -316,7 +316,7 @@ bool LottieLoader::frame(float no) no = roundf(no); no *= 0.001f; - this->frameNo = no; + this->frameNo = no + startFrame(); TaskScheduler::request(this); @@ -324,21 +324,32 @@ bool LottieLoader::frame(float no) } +float LottieLoader::startFrame() +{ + return frameCnt * segmentBegin; +} + + float LottieLoader::totalFrame() { - return frameCnt; + return (segmentEnd - segmentBegin) * frameCnt; } float LottieLoader::curFrame() { - return frameNo; + return frameNo - startFrame(); } float LottieLoader::duration() { - return frameDuration; + if (segmentBegin == 0.0f && segmentEnd == 1.0f) return frameDuration; + + if (!comp) done(); + + auto frameNo = frameCnt * (segmentEnd - segmentBegin); + return frameNo / comp->frameRate; } @@ -346,3 +357,34 @@ void LottieLoader::sync() { this->done(); } + + +uint32_t LottieLoader::markerCount() +{ + if (!comp) done(); + return comp->markers.count; +} + + +const char* LottieLoader::markers(uint32_t index) +{ + if (!comp) done(); + if (index < 0 || index >= markerCount()) return nullptr; + auto marker = comp->markers.begin() + index; + return (*marker)->name; +} + + +bool LottieLoader::getSegment(float& begin, float& end, const char* marker) +{ + if (!comp) done(); + + for (auto m = comp->markers.begin(); m < comp->markers.end(); ++m) { + if (!strcmp(marker, (*m)->name)) { + begin = (*m)->time / frameCnt; + end = ((*m)->time + (*m)->duration) / frameCnt; + return true; + } + } + return false; +} diff --git a/src/loaders/lottie/tvgLottieLoader.h b/src/loaders/lottie/tvgLottieLoader.h index 4d91b20c..a23344a5 100644 --- a/src/loaders/lottie/tvgLottieLoader.h +++ b/src/loaders/lottie/tvgLottieLoader.h @@ -63,9 +63,15 @@ public: float duration() override; void sync() override; + //Marker Supports + uint32_t markerCount(); + const char* markers(uint32_t index); + bool getSegment(float& beign, float& end, const char* marker); + private: bool header(); void clear(); + float startFrame(); void run(unsigned tid) override; }; diff --git a/src/loaders/lottie/tvgLottieModel.cpp b/src/loaders/lottie/tvgLottieModel.cpp index eb925a69..0f518785 100644 --- a/src/loaders/lottie/tvgLottieModel.cpp +++ b/src/loaders/lottie/tvgLottieModel.cpp @@ -255,4 +255,8 @@ LottieComposition::~LottieComposition() for (auto s = slots.begin(); s < slots.end(); ++s) { delete(*s); } + + for (auto m = markers.begin(); m < markers.end(); ++m) { + delete(*m); + } } diff --git a/src/loaders/lottie/tvgLottieModel.h b/src/loaders/lottie/tvgLottieModel.h index 9d657d56..79207947 100644 --- a/src/loaders/lottie/tvgLottieModel.h +++ b/src/loaders/lottie/tvgLottieModel.h @@ -175,6 +175,17 @@ struct LottieFont Origin origin = Embedded; }; +struct LottieMarker +{ + char* name = nullptr; + float time = 0.0f; + float duration = 0.0f; + + ~LottieMarker() + { + free(name); + } +}; struct LottieText : LottieObject { @@ -765,6 +776,7 @@ struct LottieComposition Array interpolators; Array fonts; Array slots; + Array markers; bool initiated = false; }; diff --git a/src/loaders/lottie/tvgLottieParser.cpp b/src/loaders/lottie/tvgLottieParser.cpp index 996fa919..9b1e579c 100644 --- a/src/loaders/lottie/tvgLottieParser.cpp +++ b/src/loaders/lottie/tvgLottieParser.cpp @@ -947,6 +947,29 @@ void LottieParser::parseAssets() } } +LottieMarker* LottieParser::parseMarker() +{ + enterObject(); + + auto marker = new LottieMarker; + + while (auto key = nextObjectKey()) { + if (!strcmp(key, "cm")) marker->name = getStringCopy(); + else if (!strcmp(key, "tm")) marker->time = getFloat(); + else if (!strcmp(key, "dr")) marker->duration = getFloat(); + else skip(key); + } + + return marker; +} + +void LottieParser::parseMarkers() +{ + enterArray(); + while (nextArrayValue()) { + comp->markers.push(parseMarker()); + } +} void LottieParser::parseChars(Array& glyphes) { @@ -1314,6 +1337,7 @@ bool LottieParser::parse() else if (!strcmp(key, "layers")) comp->root = parseLayers(); else if (!strcmp(key, "fonts")) parseFonts(); else if (!strcmp(key, "chars")) parseChars(glyphes); + else if (!strcmp(key, "markers")) parseMarkers(); else skip(key); } diff --git a/src/loaders/lottie/tvgLottieParser.h b/src/loaders/lottie/tvgLottieParser.h index 9c573666..326fe51a 100644 --- a/src/loaders/lottie/tvgLottieParser.h +++ b/src/loaders/lottie/tvgLottieParser.h @@ -92,6 +92,7 @@ private: LottieTrimpath* parseTrimpath(); LottieRepeater* parseRepeater(); LottieFont* parseFont(); + LottieMarker* parseMarker(); void parseObject(Array& parent); void parseShapes(Array& parent); @@ -104,6 +105,7 @@ private: void parseAssets(); void parseFonts(); void parseChars(Array& glyphes); + void parseMarkers(); void postProcess(Array& glyphes); //Current parsing context diff --git a/src/renderer/tvgAnimation.cpp b/src/renderer/tvgAnimation.cpp index 809c4e98..be6c2dc7 100644 --- a/src/renderer/tvgAnimation.cpp +++ b/src/renderer/tvgAnimation.cpp @@ -93,6 +93,33 @@ float Animation::duration() const noexcept } +Result Animation::segment(float begin, float end) noexcept +{ + if (begin < 0.0 || end > 1.0 || begin >= end) return Result::InvalidArguments; + + auto loader = pImpl->picture->pImpl->loader; + if (!loader) return Result::InsufficientCondition; + if (!loader->animatable()) return Result::NonSupport; + + static_cast(loader)->segment(begin, end); + + return Result::Success; +} + + +Result Animation::segment(float *begin, float *end) noexcept +{ + auto loader = pImpl->picture->pImpl->loader; + if (!loader) return Result::InsufficientCondition; + if (!loader->animatable()) return Result::NonSupport; + if (!begin && !end) return Result::InvalidArguments; + + static_cast(loader)->segment(begin, end); + + return Result::Success; +} + + unique_ptr Animation::gen() noexcept { return unique_ptr(new Animation); diff --git a/src/renderer/tvgFrameModule.h b/src/renderer/tvgFrameModule.h index df97ccb8..adca1ad9 100644 --- a/src/renderer/tvgFrameModule.h +++ b/src/renderer/tvgFrameModule.h @@ -31,6 +31,9 @@ namespace tvg class FrameModule: public ImageLoader { public: + float segmentBegin = 0.0f; + float segmentEnd = 1.0f; + FrameModule(FileType type) : ImageLoader(type) {} virtual ~FrameModule() {} @@ -39,6 +42,16 @@ public: virtual float curFrame() = 0; //return the current frame number virtual float duration() = 0; //return the animation duration in seconds + void segment(float* begin, float* end) { + if (begin) *begin = segmentBegin; + if (end) *end = segmentEnd; + } + + void segment(float begin, float end) { + segmentBegin = begin; + segmentEnd = end; + } + virtual bool animatable() override { return true; } };