mirror of
https://github.com/thorvg/thorvg.git
synced 2025-06-08 13:43:43 +00:00
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:
parent
b4a0933a10
commit
3af9faf116
11 changed files with 249 additions and 4 deletions
38
inc/thorvg.h
38
inc/thorvg.h
|
@ -1858,6 +1858,44 @@ public:
|
||||||
*/
|
*/
|
||||||
float duration() const noexcept;
|
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.
|
* @brief Creates a new Animation object.
|
||||||
*
|
*
|
||||||
|
|
|
@ -35,6 +35,50 @@ public:
|
||||||
*/
|
*/
|
||||||
Result override(const char* slot) noexcept;
|
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.
|
* @brief Creates a new LottieAnimation object.
|
||||||
*
|
*
|
||||||
|
|
|
@ -49,6 +49,39 @@ Result LottieAnimation::override(const char* slot) noexcept
|
||||||
return Result::InvalidArguments;
|
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
|
unique_ptr<LottieAnimation> LottieAnimation::gen() noexcept
|
||||||
{
|
{
|
||||||
|
|
|
@ -320,7 +320,7 @@ bool LottieLoader::frame(float no)
|
||||||
no = roundf(no);
|
no = roundf(no);
|
||||||
no *= 0.001f;
|
no *= 0.001f;
|
||||||
|
|
||||||
this->frameNo = no;
|
this->frameNo = no + startFrame();
|
||||||
|
|
||||||
TaskScheduler::request(this);
|
TaskScheduler::request(this);
|
||||||
|
|
||||||
|
@ -328,21 +328,32 @@ bool LottieLoader::frame(float no)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
float LottieLoader::startFrame()
|
||||||
|
{
|
||||||
|
return frameCnt * segmentBegin;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
float LottieLoader::totalFrame()
|
float LottieLoader::totalFrame()
|
||||||
{
|
{
|
||||||
return frameCnt;
|
return (segmentEnd - segmentBegin) * frameCnt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
float LottieLoader::curFrame()
|
float LottieLoader::curFrame()
|
||||||
{
|
{
|
||||||
return frameNo;
|
return frameNo - startFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
float LottieLoader::duration()
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -350,3 +361,34 @@ void LottieLoader::sync()
|
||||||
{
|
{
|
||||||
this->done();
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -63,9 +63,15 @@ public:
|
||||||
float duration() override;
|
float duration() override;
|
||||||
void sync() 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:
|
private:
|
||||||
bool header();
|
bool header();
|
||||||
void clear();
|
void clear();
|
||||||
|
float startFrame();
|
||||||
void run(unsigned tid) override;
|
void run(unsigned tid) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -255,4 +255,8 @@ LottieComposition::~LottieComposition()
|
||||||
for (auto s = slots.begin(); s < slots.end(); ++s) {
|
for (auto s = slots.begin(); s < slots.end(); ++s) {
|
||||||
delete(*s);
|
delete(*s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (auto m = markers.begin(); m < markers.end(); ++m) {
|
||||||
|
delete(*m);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -175,6 +175,17 @@ struct LottieFont
|
||||||
Origin origin = Embedded;
|
Origin origin = Embedded;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct LottieMarker
|
||||||
|
{
|
||||||
|
char* name = nullptr;
|
||||||
|
float time = 0.0f;
|
||||||
|
float duration = 0.0f;
|
||||||
|
|
||||||
|
~LottieMarker()
|
||||||
|
{
|
||||||
|
free(name);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
struct LottieText : LottieObject
|
struct LottieText : LottieObject
|
||||||
{
|
{
|
||||||
|
@ -765,6 +776,7 @@ struct LottieComposition
|
||||||
Array<LottieInterpolator*> interpolators;
|
Array<LottieInterpolator*> interpolators;
|
||||||
Array<LottieFont*> fonts;
|
Array<LottieFont*> fonts;
|
||||||
Array<LottieSlot*> slots;
|
Array<LottieSlot*> slots;
|
||||||
|
Array<LottieMarker*> markers;
|
||||||
bool initiated = false;
|
bool initiated = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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)
|
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, "layers")) comp->root = parseLayers();
|
||||||
else if (!strcmp(key, "fonts")) parseFonts();
|
else if (!strcmp(key, "fonts")) parseFonts();
|
||||||
else if (!strcmp(key, "chars")) parseChars(glyphes);
|
else if (!strcmp(key, "chars")) parseChars(glyphes);
|
||||||
|
else if (!strcmp(key, "markers")) parseMarkers();
|
||||||
else skip(key);
|
else skip(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -92,6 +92,7 @@ private:
|
||||||
LottieTrimpath* parseTrimpath();
|
LottieTrimpath* parseTrimpath();
|
||||||
LottieRepeater* parseRepeater();
|
LottieRepeater* parseRepeater();
|
||||||
LottieFont* parseFont();
|
LottieFont* parseFont();
|
||||||
|
LottieMarker* parseMarker();
|
||||||
|
|
||||||
void parseObject(Array<LottieObject*>& parent);
|
void parseObject(Array<LottieObject*>& parent);
|
||||||
void parseShapes(Array<LottieObject*>& parent);
|
void parseShapes(Array<LottieObject*>& parent);
|
||||||
|
@ -104,6 +105,7 @@ private:
|
||||||
void parseAssets();
|
void parseAssets();
|
||||||
void parseFonts();
|
void parseFonts();
|
||||||
void parseChars(Array<LottieGlyph*>& glyphes);
|
void parseChars(Array<LottieGlyph*>& glyphes);
|
||||||
|
void parseMarkers();
|
||||||
void postProcess(Array<LottieGlyph*>& glyphes);
|
void postProcess(Array<LottieGlyph*>& glyphes);
|
||||||
|
|
||||||
//Current parsing context
|
//Current parsing context
|
||||||
|
|
|
@ -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
|
unique_ptr<Animation> Animation::gen() noexcept
|
||||||
{
|
{
|
||||||
return unique_ptr<Animation>(new Animation);
|
return unique_ptr<Animation>(new Animation);
|
||||||
|
|
|
@ -31,6 +31,9 @@ namespace tvg
|
||||||
class FrameModule: public ImageLoader
|
class FrameModule: public ImageLoader
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
float segmentBegin = 0.0f;
|
||||||
|
float segmentEnd = 1.0f;
|
||||||
|
|
||||||
FrameModule(FileType type) : ImageLoader(type) {}
|
FrameModule(FileType type) : ImageLoader(type) {}
|
||||||
virtual ~FrameModule() {}
|
virtual ~FrameModule() {}
|
||||||
|
|
||||||
|
@ -39,6 +42,16 @@ public:
|
||||||
virtual float curFrame() = 0; //return the current frame number
|
virtual float curFrame() = 0; //return the current frame number
|
||||||
virtual float duration() = 0; //return the animation duration in seconds
|
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; }
|
virtual bool animatable() override { return true; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue