api: revised the animation segment specification

Changed the unit of the segment from a normalized value to frame numbers,
ensuring alignment with other frame control interfaces.

Note that This change may break backward compatibility.

issue: https://github.com/thorvg/thorvg/issues/3116
This commit is contained in:
Hermet Park 2025-01-22 07:28:44 +09:00 committed by Hermet Park
parent 53487421b5
commit fdd760a34f
9 changed files with 69 additions and 60 deletions

View file

@ -48,32 +48,32 @@ struct UserExample : tvgexam::Example
//pad1 touch? //pad1 touch?
if (auto paint = picture->paint(tvg::Accessor::id("pad1"))) { if (auto paint = picture->paint(tvg::Accessor::id("pad1"))) {
if (hitting(paint, x, y, 0.2222f, 0.3333f)) return true; if (hitting(paint, x, y, 20.0f, 30.0f)) return true;
} }
//pad3 touch? //pad3 touch?
if (auto paint = picture->paint(tvg::Accessor::id("pad3"))) { if (auto paint = picture->paint(tvg::Accessor::id("pad3"))) {
if (hitting(paint, x, y, 0.4444f, 0.5555f)) return true; if (hitting(paint, x, y, 40.0f, 50.0f)) return true;
} }
//pad5 touch? //pad5 touch?
if (auto paint = picture->paint(tvg::Accessor::id("pad5"))) { if (auto paint = picture->paint(tvg::Accessor::id("pad5"))) {
if (hitting(paint, x, y, 0.1111f, 0.2222f)) return true; if (hitting(paint, x, y, 10.0f, 20.0f)) return true;
} }
//pad7 touch? //pad7 touch?
if (auto paint = picture->paint(tvg::Accessor::id("pad7"))) { if (auto paint = picture->paint(tvg::Accessor::id("pad7"))) {
if (hitting(paint, x, y, 0.0000f, 0.1111f)) return true; if (hitting(paint, x, y, 0.0f, 10.0f)) return true;
} }
//pad9 touch? //pad9 touch?
if (auto paint = picture->paint(tvg::Accessor::id("pad9"))) { if (auto paint = picture->paint(tvg::Accessor::id("pad9"))) {
if (hitting(paint, x, y, 0.3333f, 0.4444f)) return true; if (hitting(paint, x, y, 30.0f, 40.0f)) return true;
} }
//bar touch? //bar touch?
if (auto paint = picture->paint(tvg::Accessor::id("bar"))) { if (auto paint = picture->paint(tvg::Accessor::id("bar"))) {
if (hitting(paint, x, y, 0.6666f, 1.0f)) return true; if (hitting(paint, x, y, 60.0f, 90.0f)) return true;
} }
return false; return false;

View file

@ -1891,7 +1891,6 @@ public:
* *
* @since 0.13 * @since 0.13
*/ */
class TVG_API Animation class TVG_API Animation
{ {
public: public:
@ -1910,7 +1909,6 @@ public:
* Values less than 0.001 may be disregarded and may not be accurately retained by the Animation. * Values less than 0.001 may be disregarded and may not be accurately retained by the Animation.
* *
* @see totalFrame() * @see totalFrame()
*
*/ */
Result frame(float no) noexcept; Result frame(float no) noexcept;
@ -1924,7 +1922,6 @@ public:
* @return A picture instance that is tied to this animation. * @return A picture instance that is tied to this animation.
* *
* @warning The picture instance is owned by Animation. It should not be deleted manually. * @warning The picture instance is owned by Animation. It should not be deleted manually.
*
*/ */
Picture* picture() const noexcept; Picture* picture() const noexcept;
@ -1935,9 +1932,8 @@ public:
* *
* @note If the Picture is not properly configured, this function will return 0. * @note If the Picture is not properly configured, this function will return 0.
* *
* @see Animation::frame(float no) * @see Animation::frame()
* @see Animation::totalFrame() * @see Animation::totalFrame()
*
*/ */
float curFrame() const noexcept; float curFrame() const noexcept;
@ -1948,7 +1944,6 @@ public:
* *
* @note Frame numbering starts from 0. * @note Frame numbering starts from 0.
* @note If the Picture is not properly configured, this function will return 0. * @note If the Picture is not properly configured, this function will return 0.
*
*/ */
float totalFrame() const noexcept; float totalFrame() const noexcept;
@ -1958,7 +1953,6 @@ public:
* @return The duration of the animation in seconds. * @return The duration of the animation in seconds.
* *
* @note If the Picture is not properly configured, this function will return 0. * @note If the Picture is not properly configured, this function will return 0.
*
*/ */
float duration() const noexcept; float duration() const noexcept;
@ -1970,30 +1964,33 @@ public:
* After setting, the number of animation frames and the playback time are calculated * After setting, the number of animation frames and the playback time are calculated
* by mapping the playback segment as the entire range. * by mapping the playback segment as the entire range.
* *
* @param[in] begin segment start. * @param[in] begin segment begin frame.
* @param[in] end segment end. * @param[in] end segment end frame.
* *
* @retval Result::InsufficientCondition In case the animation is not loaded. * @retval Result::InsufficientCondition In case the animation is not loaded.
* @retval Result::InvalidArguments If the @p begin is higher than @p end.
* @retval Result::NonSupport When it's not animatable. * @retval Result::NonSupport When it's not animatable.
* *
* @note Animation allows a range from 0.0 to 1.0. @p end should not be higher than @p begin. * @note Animation allows a range from 0.0 to the total frame. @p end should not be higher than @p begin.
* @note If a marker has been specified, its range will be disregarded. * @note If a marker has been specified, its range will be disregarded.
* @see LottieAnimation::segment(const char* marker)
* *
* @note Experimental API * @see LottieAnimation::segment(const char* marker)
* @see Animation::totalFrame()
*
* @since 1.0
*/ */
Result segment(float begin, float end) noexcept; Result segment(float begin, float end) noexcept;
/** /**
* @brief Gets the current segment. * @brief Gets the current segment range information.
* *
* @param[out] begin segment start. * @param[out] begin segment begin frame.
* @param[out] end segment end. * @param[out] end segment end frame.
* *
* @retval Result::InsufficientCondition In case the animation is not loaded. * @retval Result::InsufficientCondition In case the animation is not loaded.
* @retval Result::NonSupport When it's not animatable. * @retval Result::NonSupport When it's not animatable.
* *
* @note Experimental API * @since 1.0
*/ */
Result segment(float* begin, float* end = nullptr) noexcept; Result segment(float* begin, float* end = nullptr) noexcept;
@ -2001,7 +1998,6 @@ public:
* @brief Creates a new Animation object. * @brief Creates a new Animation object.
* *
* @return A new Animation object. * @return A new Animation object.
*
*/ */
static Animation* gen() noexcept; static Animation* gen() noexcept;

View file

@ -2373,31 +2373,42 @@ TVG_API Tvg_Result tvg_animation_get_duration(Tvg_Animation* animation, float* d
/*! /*!
* @brief Specifies the playback segment of the animation. * @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] animation The Tvg_Animation pointer to the animation object. * @param[in] animation The Tvg_Animation pointer to the animation object.
* @param[in] begin segment begin. * @param[in] begin segment begin frame.
* @param[in] end segment end. * @param[in] end segment end frame.
* *
* @return Tvg_Result enumeration. * @return Tvg_Result enumeration.
* @retval TVG_RESULT_INSUFFICIENT_CONDITION In case the animation is not loaded. * @retval TVG_RESULT_INSUFFICIENT_CONDITION In case the animation is not loaded.
* @retval TVG_RESULT_INVALID_ARGUMENT When the given parameters are out of range. * @retval TVG_RESULT_INVALID_ARGUMENT If the @p begin is higher than @p end.
* *
* @note Experimental API * @note Animation allows a range from 0.0 to the total frame. @p end should not be higher than @p begin.
* @note If a marker has been specified, its range will be disregarded.
*
* @see tvg_lottie_animation_set_marker()
* @see tvg_animation_get_total_frame()
* @since 1.0
*/ */
TVG_API Tvg_Result tvg_animation_set_segment(Tvg_Animation* animation, float begin, float end); TVG_API Tvg_Result tvg_animation_set_segment(Tvg_Animation* animation, float begin, float end);
/*! /*!
* @brief Gets the current segment. * @brief Gets the current segment range information.
* *
* @param[in] animation The Tvg_Animation pointer to the animation object. * @param[in] animation The Tvg_Animation pointer to the animation object.
* @param[out] begin segment begin. * @param[out] begin segment begin frame.
* @param[out] end segment end. * @param[out] end segment end frame.
* *
* @return Tvg_Result enumeration. * @return Tvg_Result enumeration.
* @retval TVG_RESULT_INSUFFICIENT_CONDITION In case the animation is not loaded. * @retval TVG_RESULT_INSUFFICIENT_CONDITION In case the animation is not loaded.
* @retval TVG_RESULT_INVALID_ARGUMENT When the given parameters are @c nullptr. * @retval TVG_RESULT_INVALID_ARGUMENT An invalid Tvg_Animation pointer.
* *
* @note Experimental API * @since 1.0
*/ */
TVG_API Tvg_Result tvg_animation_get_segment(Tvg_Animation* animation, float* begin, float* end); TVG_API Tvg_Result tvg_animation_get_segment(Tvg_Animation* animation, float* begin, float* end);

View file

@ -47,14 +47,14 @@ Result LottieAnimation::segment(const char* marker) noexcept
if (!loader) return Result::InsufficientCondition; if (!loader) return Result::InsufficientCondition;
if (!marker) { if (!marker) {
static_cast<FrameModule*>(loader)->segment(0.0f, 1.0f); static_cast<LottieLoader*>(loader)->segment(0.0f, FLT_MAX);
return Result::Success; return Result::Success;
} }
float begin, end; float begin, end;
if (!static_cast<LottieLoader*>(loader)->segment(marker, begin, end)) return Result::InvalidArguments; if (!static_cast<LottieLoader*>(loader)->segment(marker, begin, end)) return Result::InvalidArguments;
return static_cast<Animation*>(this)->segment(begin, end); return static_cast<LottieLoader*>(loader)->segment(begin, end);
} }

View file

@ -97,8 +97,7 @@ bool LottieLoader::header()
if (comp) { if (comp) {
w = static_cast<float>(comp->w); w = static_cast<float>(comp->w);
h = static_cast<float>(comp->h); h = static_cast<float>(comp->h);
frameDuration = comp->duration(); segmentEnd = frameCnt = comp->frameCnt();
frameCnt = comp->frameCnt();
frameRate = comp->frameRate; frameRate = comp->frameRate;
return true; return true;
} else { } else {
@ -190,10 +189,9 @@ bool LottieLoader::header()
return false; return false;
} }
frameCnt = (endFrame - startFrame); segmentEnd = frameCnt = (endFrame - startFrame);
frameDuration = frameCnt / frameRate;
TVGLOG("LOTTIE", "info: frame rate = %f, duration = %f size = %f x %f", frameRate, frameDuration, w, h); TVGLOG("LOTTIE", "info: frame rate = %f, duration = %f size = %f x %f", frameRate, frameCnt / frameRate, w, h);
return true; return true;
} }
@ -355,13 +353,13 @@ bool LottieLoader::frame(float no)
float LottieLoader::startFrame() float LottieLoader::startFrame()
{ {
return frameCnt * segmentBegin; return segmentBegin;
} }
float LottieLoader::totalFrame() float LottieLoader::totalFrame()
{ {
return (segmentEnd - segmentBegin) * frameCnt; return segmentEnd - segmentBegin;
} }
@ -373,8 +371,7 @@ float LottieLoader::curFrame()
float LottieLoader::duration() float LottieLoader::duration()
{ {
if (segmentBegin == 0.0f && segmentEnd == 1.0f) return frameDuration; return (segmentEnd - segmentBegin) / frameRate;
return frameCnt * (segmentEnd - segmentBegin) / frameRate;
} }
@ -400,14 +397,28 @@ const char* LottieLoader::markers(uint32_t index)
} }
Result LottieLoader::segment(float begin, float end)
{
if (begin < 0.0f) begin = 0.0f;
if (end > frameCnt) end = frameCnt;
if (begin > end) return Result::InvalidArguments;
segmentBegin = begin;
segmentEnd = end;
return Result::Success;
}
bool LottieLoader::segment(const char* marker, float& begin, float& end) bool LottieLoader::segment(const char* marker, float& begin, float& end)
{ {
if (!ready() || comp->markers.count == 0) return false; if (!ready() || comp->markers.count == 0) return false;
ARRAY_FOREACH(p, comp->markers) { ARRAY_FOREACH(p, comp->markers) {
if (!strcmp(marker, (*p)->name)) { if (!strcmp(marker, (*p)->name)) {
begin = (*p)->time / frameCnt; begin = (*p)->time;
end = ((*p)->time + (*p)->duration) / frameCnt; end = (*p)->time + (*p)->duration;
return true; return true;
} }
} }

View file

@ -37,7 +37,6 @@ public:
uint32_t size = 0; //lottie data size uint32_t size = 0; //lottie data size
float frameNo = 0.0f; //current frame number float frameNo = 0.0f; //current frame number
float frameCnt = 0.0f; float frameCnt = 0.0f;
float frameDuration = 0.0f;
float frameRate = 0.0f; float frameRate = 0.0f;
LottieBuilder* builder; LottieBuilder* builder;
@ -70,6 +69,7 @@ public:
uint32_t markersCnt(); uint32_t markersCnt();
const char* markers(uint32_t index); const char* markers(uint32_t index);
bool segment(const char* marker, float& begin, float& end); bool segment(const char* marker, float& begin, float& end);
Result segment(float begin, float end) override;
private: private:
bool ready(); bool ready();

View file

@ -88,15 +88,11 @@ float Animation::duration() const noexcept
Result Animation::segment(float begin, float end) noexcept Result Animation::segment(float begin, float end) noexcept
{ {
if (begin < 0.0f || end > 1.0f || begin > end) return Result::InvalidArguments;
auto loader = PICTURE(pImpl->picture)->loader; auto loader = PICTURE(pImpl->picture)->loader;
if (!loader) return Result::InsufficientCondition; if (!loader) return Result::InsufficientCondition;
if (!loader->animatable()) return Result::NonSupport; if (!loader->animatable()) return Result::NonSupport;
static_cast<FrameModule*>(loader)->segment(begin, end); return static_cast<FrameModule*>(loader)->segment(begin, end);
return Result::Success;
} }

View file

@ -32,7 +32,7 @@ class FrameModule: public ImageLoader
{ {
public: public:
float segmentBegin = 0.0f; float segmentBegin = 0.0f;
float segmentEnd = 1.0f; float segmentEnd; //Initialize the value with the total frame number
FrameModule(FileType type) : ImageLoader(type) {} FrameModule(FileType type) : ImageLoader(type) {}
virtual ~FrameModule() {} virtual ~FrameModule() {}
@ -41,6 +41,7 @@ public:
virtual float totalFrame() = 0; //return the total frame count virtual float totalFrame() = 0; //return the total frame count
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
virtual Result segment(float begin, float end) = 0;
void segment(float* begin, float* end) void segment(float* begin, float* end)
{ {
@ -48,12 +49,6 @@ public:
if (end) *end = segmentEnd; 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; }
}; };

View file

@ -301,7 +301,7 @@ TEST_CASE("Animation Segment", "[tvgAnimation]")
//Get current segment before segment //Get current segment before segment
REQUIRE(animation->segment(&begin, &end) == Result::Success); REQUIRE(animation->segment(&begin, &end) == Result::Success);
REQUIRE(begin == 0.0f); REQUIRE(begin == 0.0f);
REQUIRE(end == 1.0f); REQUIRE(end == animation->totalFrame());
//Segment by range //Segment by range
REQUIRE(animation->segment(0.25, 0.5) == Result::Success); REQUIRE(animation->segment(0.25, 0.5) == Result::Success);
@ -320,7 +320,7 @@ TEST_CASE("Animation Segment", "[tvgAnimation]")
REQUIRE(end == 0.5f); REQUIRE(end == 0.5f);
//Segment by invalid range //Segment by invalid range
REQUIRE(animation->segment(-0.5, 1.5) == Result::InvalidArguments); REQUIRE(animation->segment(1.5, -0.5) == Result::InvalidArguments);
REQUIRE(Initializer::term() == Result::Success); REQUIRE(Initializer::term() == Result::Success);
} }