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 <jinny@lottiefiles.com>
This commit is contained in:
Lucas Niu 2024-04-14 16:10:03 +09:00 committed by Hermet Park
parent 9904d7b468
commit 194bd8f01d
11 changed files with 249 additions and 4 deletions

View file

@ -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.
*

View file

@ -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.
*

View file

@ -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<LottieLoader*>(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<Animation*>(this)->segment(begin, end);
}
uint32_t LottieAnimation::markersCnt() noexcept
{
auto loader = pImpl->picture->pImpl->loader;
if (!loader || !loader->animatable()) return 0;
return static_cast<LottieLoader*>(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<LottieLoader*>(loader)->markers(idx);
}
unique_ptr<LottieAnimation> LottieAnimation::gen() noexcept
{

View file

@ -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;
}

View file

@ -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;
};

View file

@ -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);
}
}

View file

@ -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<LottieInterpolator*> interpolators;
Array<LottieFont*> fonts;
Array<LottieSlot*> slots;
Array<LottieMarker*> markers;
bool initiated = false;
};

View file

@ -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<LottieGlyph*>& 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);
}

View file

@ -92,6 +92,7 @@ private:
LottieTrimpath* parseTrimpath();
LottieRepeater* parseRepeater();
LottieFont* parseFont();
LottieMarker* parseMarker();
void parseObject(Array<LottieObject*>& parent);
void parseShapes(Array<LottieObject*>& parent);
@ -104,6 +105,7 @@ private:
void parseAssets();
void parseFonts();
void parseChars(Array<LottieGlyph*>& glyphes);
void parseMarkers();
void postProcess(Array<LottieGlyph*>& glyphes);
//Current parsing context

View file

@ -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<FrameModule*>(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<FrameModule*>(loader)->segment(begin, end);
return Result::Success;
}
unique_ptr<Animation> Animation::gen() noexcept
{
return unique_ptr<Animation>(new Animation);

View file

@ -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; }
};