feat(thorvg): update thorvg version to 0.13.5 (#6274)
Signed-off-by: lhdjply <lhdjply@126.com>
@ -5,12 +5,11 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#define THORVG_CAPI_BINDING_SUPPORT 1
|
||||
|
||||
#define THORVG_SW_RASTER_SUPPORT 1
|
||||
|
||||
#define THORVG_SVG_LOADER_SUPPORT LV_USE_LOTTIE
|
||||
|
||||
#define THORVG_LOTTIE_LOADER_SUPPORT LV_USE_LOTTIE
|
||||
|
||||
#define THORVG_VERSION_STRING "0.11.99"
|
||||
#define THORVG_VERSION_STRING "0.13.5"
|
||||
|
||||
|
@ -1,16 +1,3 @@
|
||||
/*!
|
||||
* @file thorvg.h
|
||||
*
|
||||
* The main APIs enabling the TVG initialization, preparation of the canvas and provisioning of its content:
|
||||
* - drawing shapes: line, arc, curve, path, polygon...
|
||||
* - drawing pictures: tvg, svg, png, jpg, bitmap...
|
||||
* - drawing fillings: solid, linear and radial gradient...
|
||||
* - drawing stroking: continuous stroking with arbitrary width, join, cap, dash styles.
|
||||
* - drawing composition: blending, masking, path clipping...
|
||||
* - drawing scene graph & affine transformation (translation, rotation, scale, ...)
|
||||
* and finally drawing the canvas and TVG termination.
|
||||
*/
|
||||
|
||||
#ifndef _THORVG_H_
|
||||
#define _THORVG_H_
|
||||
|
||||
@ -673,6 +660,30 @@ public:
|
||||
*/
|
||||
virtual Result draw() noexcept;
|
||||
|
||||
/**
|
||||
* @brief Sets the drawing region in the canvas.
|
||||
*
|
||||
* This function defines the rectangular area of the canvas that will be used for drawing operations.
|
||||
* The specified viewport is used to clip the rendering output to the boundaries of the rectangle.
|
||||
*
|
||||
* @param[in] x The x-coordinate of the upper-left corner of the rectangle.
|
||||
* @param[in] y The y-coordinate of the upper-left corner of the rectangle.
|
||||
* @param[in] w The width of the rectangle.
|
||||
* @param[in] h The height of the rectangle.
|
||||
*
|
||||
* @retval Result::Success when succeed, Result::InsufficientCondition otherwise.
|
||||
*
|
||||
* @see SwCanvas::target()
|
||||
* @see GlCanvas::target()
|
||||
* @see WgCanvas::target()
|
||||
*
|
||||
* @warning It's not allowed to change the viewport during Canvas::push() - Canvas::sync() or Canvas::update() - Canvas::sync().
|
||||
*
|
||||
* @note When resetting the target, the viewport will also be reset to the target size.
|
||||
* @note Experimental API
|
||||
*/
|
||||
virtual Result viewport(int32_t x, int32_t y, int32_t w, int32_t h) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Guarantees that drawing task is finished.
|
||||
*
|
||||
@ -1051,7 +1062,7 @@ public:
|
||||
* @param[in] miterlimit The miterlimit imposes a limit on the extent of the stroke join, when the @c StrokeJoin::Miter join style is set. The default value is 4.
|
||||
*
|
||||
* @retval Result::Success when succeed, Result::NonSupport unsupported value, Result::FailedAllocation otherwise.
|
||||
*
|
||||
*
|
||||
* @since 0.11
|
||||
*/
|
||||
Result strokeMiterlimit(float miterlimit) noexcept;
|
||||
@ -1669,7 +1680,7 @@ public:
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Sets the target buffer for the rasterization.
|
||||
* @brief Sets the drawing target for the rasterization.
|
||||
*
|
||||
* The buffer of a desirable size should be allocated and owned by the caller.
|
||||
*
|
||||
@ -1684,7 +1695,9 @@ public:
|
||||
* @retval Result::InvalidArguments In case no valid pointer is provided or the width, or the height or the stride is zero.
|
||||
* @retval Result::NonSupport In case the software engine is not supported.
|
||||
*
|
||||
* @warning Do not access @p buffer during Canvas::draw() - Canvas::sync(). It should not be accessed while TVG is writing on it.
|
||||
* @warning Do not access @p buffer during Canvas::push() - Canvas::sync(). It should not be accessed while the engine is writing on it.
|
||||
*
|
||||
* @see Canvas::viewport()
|
||||
*/
|
||||
Result target(uint32_t* buffer, uint32_t stride, uint32_t w, uint32_t h, Colorspace cs) noexcept;
|
||||
|
||||
@ -1738,13 +1751,24 @@ public:
|
||||
~GlCanvas();
|
||||
|
||||
/**
|
||||
* @brief Sets the target buffer for the rasterization.
|
||||
* @brief Sets the drawing target for rasterization.
|
||||
*
|
||||
* @warning Please do not use it, this API is not official one. It could be modified in the next version.
|
||||
* This function specifies the drawing target where the rasterization will occur. It can target
|
||||
* a specific framebuffer object (FBO) or the main surface.
|
||||
*
|
||||
* @param[in] id The GL target ID, usually indicating the FBO ID. A value of @c 0 specifies the main surface.
|
||||
* @param[in] w The width (in pixels) of the raster image.
|
||||
* @param[in] h The height (in pixels) of the raster image.
|
||||
*
|
||||
* @warning This API is experimental and not officially supported. It may be modified or removed in future versions.
|
||||
* @warning Drawing on the main surface is currently not permitted. If the identifier (@p id) is set to @c 0, the operation will be aborted.
|
||||
*
|
||||
* @see Canvas::viewport()
|
||||
*
|
||||
* @note Currently, this only allows the GL_RGBA8 color space format.
|
||||
* @note Experimental API
|
||||
*/
|
||||
Result target(uint32_t* buffer, uint32_t stride, uint32_t w, uint32_t h) noexcept;
|
||||
*/
|
||||
Result target(int32_t id, uint32_t w, uint32_t h) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Creates a new GlCanvas object.
|
||||
@ -1779,6 +1803,7 @@ public:
|
||||
* @warning Please do not use it, this API is not official one. It could be modified in the next version.
|
||||
*
|
||||
* @note Experimental API
|
||||
* @see Canvas::viewport()
|
||||
*/
|
||||
Result target(void* window, uint32_t w, uint32_t h) noexcept;
|
||||
|
||||
@ -1852,7 +1877,7 @@ public:
|
||||
*
|
||||
* This class supports the display and control of animation frames.
|
||||
*
|
||||
* @note Experimental API
|
||||
* @since 0.13
|
||||
*/
|
||||
|
||||
class TVG_API Animation
|
||||
@ -1869,9 +1894,12 @@ public:
|
||||
* @retval Result::InsufficientCondition if the given @p no is the same as the current frame value.
|
||||
* @retval Result::NonSupport The current Picture data does not support animations.
|
||||
*
|
||||
* @note For efficiency, ThorVG ignores updates to the new frame value if the difference from the current frame value
|
||||
* is less than 0.001. In such cases, it returns @c Result::InsufficientCondition.
|
||||
* Values less than 0.001 may be disregarded and may not be accurately retained by the Animation.
|
||||
*
|
||||
* @see totalFrame()
|
||||
*
|
||||
* @note Experimental API
|
||||
*/
|
||||
Result frame(float no) noexcept;
|
||||
|
||||
@ -1886,7 +1914,6 @@ public:
|
||||
*
|
||||
* @warning The picture instance is owned by Animation. It should not be deleted manually.
|
||||
*
|
||||
* @note Experimental API
|
||||
*/
|
||||
Picture* picture() const noexcept;
|
||||
|
||||
@ -1900,7 +1927,6 @@ public:
|
||||
* @see Animation::frame(float no)
|
||||
* @see Animation::totalFrame()
|
||||
*
|
||||
* @note Experimental API
|
||||
*/
|
||||
float curFrame() const noexcept;
|
||||
|
||||
@ -1912,7 +1938,6 @@ public:
|
||||
* @note Frame numbering starts from 0.
|
||||
* @note If the Picture is not properly configured, this function will return 0.
|
||||
*
|
||||
* @note Experimental API
|
||||
*/
|
||||
float totalFrame() const noexcept;
|
||||
|
||||
@ -1923,16 +1948,52 @@ public:
|
||||
*
|
||||
* @note If the Picture is not properly configured, this function will return 0.
|
||||
*
|
||||
* @% Experimental API
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @return A new Animation object.
|
||||
*
|
||||
* @note Experimental API
|
||||
*/
|
||||
static std::unique_ptr<Animation> gen() noexcept;
|
||||
|
||||
|
@ -103,7 +103,7 @@ typedef struct _Tvg_Gradient Tvg_Gradient;
|
||||
typedef struct _Tvg_Saver Tvg_Saver;
|
||||
|
||||
/**
|
||||
* \brief A structure representing an animation controller object. (Experimental API)
|
||||
* \brief A structure representing an animation controller object.
|
||||
*/
|
||||
typedef struct _Tvg_Animation Tvg_Animation;
|
||||
|
||||
@ -404,8 +404,10 @@ typedef enum {
|
||||
* \brief Enumeration specifying the methods of combining the 8-bit color channels into 32-bit color.
|
||||
*/
|
||||
typedef enum {
|
||||
TVG_COLORSPACE_ABGR8888 = 0, ///< The 8-bit color channels are combined into 32-bit color in the order: alpha, blue, green, red.
|
||||
TVG_COLORSPACE_ARGB8888 ///< The 8-bit color channels are combined into 32-bit color in the order: alpha, red, green, blue.
|
||||
TVG_COLORSPACE_ABGR8888 = 0, ///< The channels are joined in the order: alpha, blue, green, red. Colors are alpha-premultiplied. (a << 24 | b << 16 | g << 8 | r)
|
||||
TVG_COLORSPACE_ARGB8888, ///< The channels are joined in the order: alpha, red, green, blue. Colors are alpha-premultiplied. (a << 24 | r << 16 | g << 8 | b)
|
||||
TVG_COLORSPACE_ABGR8888S, ///< The channels are joined in the order: alpha, blue, green, red. Colors are un-alpha-premultiplied. @since 0.13
|
||||
TVG_COLORSPACE_ARGB8888S ///< The channels are joined in the order: alpha, red, green, blue. Colors are un-alpha-premultiplied. @since 0.13
|
||||
} Tvg_Colorspace;
|
||||
|
||||
|
||||
@ -457,7 +459,7 @@ TVG_API Tvg_Canvas* tvg_swcanvas_create(void);
|
||||
* \retval TVG_RESULT_INVALID_ARGUMENTS An invalid canvas or buffer pointer passed or one of the @p stride, @p w or @p h being zero.
|
||||
* \retval TVG_RESULT_NOT_SUPPORTED The software engine is not supported.
|
||||
*
|
||||
* \warning Do not access @p buffer during tvg_canvas_draw() - tvg_canvas_sync(). It should not be accessed while TVG is writing on it.
|
||||
* \warning Do not access @p buffer during tvg_canvas_draw() - tvg_canvas_sync(). It should not be accessed while the engine is writing on it.
|
||||
*
|
||||
* \see Tvg_Colorspace
|
||||
*/
|
||||
@ -747,6 +749,30 @@ TVG_API Tvg_Result tvg_canvas_draw(Tvg_Canvas* canvas);
|
||||
TVG_API Tvg_Result tvg_canvas_sync(Tvg_Canvas* canvas);
|
||||
|
||||
|
||||
/*!
|
||||
* \brief Sets the drawing region in the canvas.
|
||||
*
|
||||
* This function defines the rectangular area of the canvas that will be used for drawing operations.
|
||||
* The specified viewport is used to clip the rendering output to the boundaries of the rectangle.
|
||||
*
|
||||
* \param[in] canvas The Tvg_Canvas object containing elements which were drawn.
|
||||
* \param[in] x The x-coordinate of the upper-left corner of the rectangle.
|
||||
* \param[in] y The y-coordinate of the upper-left corner of the rectangle.
|
||||
* \param[in] w The width of the rectangle.
|
||||
* \param[in] h The height of the rectangle.
|
||||
*
|
||||
* \return Tvg_Result enumeration.
|
||||
* \retval TVG_RESULT_SUCCESS Succeed.
|
||||
* \retval TVG_RESULT_INSUFFICIENT_CONDITION An internal error.
|
||||
*
|
||||
* \warning It's not allowed to change the viewport during tvg_canvas_update() - tvg_canvas_sync() or tvg_canvas_push() - tvg_canvas_sync().
|
||||
*
|
||||
* \note When resetting the target, the viewport will also be reset to the target size.
|
||||
* \note Experimental API
|
||||
* \see tvg_swcanvas_set_target()
|
||||
*/
|
||||
TVG_API Tvg_Result tvg_canvas_set_viewport(Tvg_Canvas* canvas, int32_t x, int32_t y, int32_t w, int32_t h);
|
||||
|
||||
/** \} */ // end defgroup ThorVGCapi_Canvas
|
||||
|
||||
|
||||
@ -2238,15 +2264,17 @@ TVG_API Tvg_Result tvg_saver_del(Tvg_Saver* saver);
|
||||
/************************************************************************/
|
||||
|
||||
/*!
|
||||
* \brief Creates a new Animation object. (Experimental API)
|
||||
* \brief Creates a new Animation object.
|
||||
*
|
||||
* \return Tvg_Animation A new Tvg_Animation object.
|
||||
*
|
||||
* \since 0.13
|
||||
*/
|
||||
TVG_API Tvg_Animation* tvg_animation_new(void);
|
||||
|
||||
|
||||
/*!
|
||||
* \brief Specifies the current frame in the animation. (Experimental API)
|
||||
* \brief Specifies the current frame in the animation.
|
||||
*
|
||||
* \param[in] animation A Tvg_Animation pointer to the animation object.
|
||||
* \param[in] no The index of the animation frame to be displayed. The index should be less than the tvg_animatio_total_frame().
|
||||
@ -2257,13 +2285,18 @@ TVG_API Tvg_Animation* tvg_animation_new(void);
|
||||
* \retval TVG_RESULT_INSUFFICIENT_CONDITION No animatable data loaded from the Picture.
|
||||
* \retval TVG_RESULT_NOT_SUPPORTED The picture data does not support animations.
|
||||
*
|
||||
* \note For efficiency, ThorVG ignores updates to the new frame value if the difference from the current frame value
|
||||
* is less than 0.001. In such cases, it returns @c Result::InsufficientCondition.
|
||||
* Values less than 0.001 may be disregarded and may not be accurately retained by the Animation.
|
||||
* \see tvg_animation_get_total_frame()
|
||||
*
|
||||
* \since 0.13
|
||||
*/
|
||||
TVG_API Tvg_Result tvg_animation_set_frame(Tvg_Animation* animation, float no);
|
||||
|
||||
|
||||
/*!
|
||||
* \brief Retrieves a picture instance associated with this animation instance. (Experimental API)
|
||||
* \brief Retrieves a picture instance associated with this animation instance.
|
||||
*
|
||||
* This function provides access to the picture instance that can be used to load animation formats, such as Lottie(json).
|
||||
* After setting up the picture, it can be pushed to the designated canvas, enabling control over animation frames
|
||||
@ -2274,12 +2307,14 @@ TVG_API Tvg_Result tvg_animation_set_frame(Tvg_Animation* animation, float no);
|
||||
* \return A picture instance that is tied to this animation.
|
||||
*
|
||||
* \warning The picture instance is owned by Animation. It should not be deleted manually.
|
||||
*
|
||||
* \since 0.13
|
||||
*/
|
||||
TVG_API Tvg_Paint* tvg_animation_get_picture(Tvg_Animation* animation);
|
||||
|
||||
|
||||
/*!
|
||||
* \brief Retrieves the current frame number of the animation. (Experimental API)
|
||||
* \brief Retrieves the current frame number of the animation.
|
||||
*
|
||||
* \param[in] animation A Tvg_Animation pointer to the animation object.
|
||||
* \param[in] no The current frame number of the animation, between 0 and totalFrame() - 1.
|
||||
@ -2290,12 +2325,14 @@ TVG_API Tvg_Paint* tvg_animation_get_picture(Tvg_Animation* animation);
|
||||
*
|
||||
* \see tvg_animation_get_total_frame()
|
||||
* \see tvg_animation_set_frame()
|
||||
*
|
||||
* \since 0.13
|
||||
*/
|
||||
TVG_API Tvg_Result tvg_animation_get_frame(Tvg_Animation* animation, float* no);
|
||||
|
||||
|
||||
/*!
|
||||
* \brief Retrieves the total number of frames in the animation. (Experimental API)
|
||||
* \brief Retrieves the total number of frames in the animation.
|
||||
*
|
||||
* \param[in] animation A Tvg_Animation pointer to the animation object.
|
||||
* \param[in] cnt The total number of frames in the animation.
|
||||
@ -2306,12 +2343,14 @@ TVG_API Tvg_Result tvg_animation_get_frame(Tvg_Animation* animation, float* no);
|
||||
*
|
||||
* \note Frame numbering starts from 0.
|
||||
* \note If the Picture is not properly configured, this function will return 0.
|
||||
*
|
||||
* \since 0.13
|
||||
*/
|
||||
TVG_API Tvg_Result tvg_animation_get_total_frame(Tvg_Animation* animation, float* cnt);
|
||||
|
||||
|
||||
/*!
|
||||
* \brief Retrieves the duration of the animation in seconds. (Experimental API)
|
||||
* \brief Retrieves the duration of the animation in seconds.
|
||||
*
|
||||
* \param[in] animation A Tvg_Animation pointer to the animation object.
|
||||
* \param[in] duration The duration of the animation in seconds.
|
||||
@ -2321,10 +2360,44 @@ TVG_API Tvg_Result tvg_animation_get_total_frame(Tvg_Animation* animation, float
|
||||
* \retval TVG_RESULT_INVALID_ARGUMENT An invalid Tvg_Animation pointer or @p duration.
|
||||
*
|
||||
* \note If the Picture is not properly configured, this function will return 0.
|
||||
*
|
||||
* \since 0.13
|
||||
*/
|
||||
TVG_API Tvg_Result tvg_animation_get_duration(Tvg_Animation* animation, float* duration);
|
||||
|
||||
|
||||
/*!
|
||||
* \brief Specifies the playback segment of the animation. (Experimental API)
|
||||
*
|
||||
* \param[in] animation The Tvg_Animation pointer to the animation object.
|
||||
* \param[in] begin segment begin.
|
||||
* \param[in] end segment end.
|
||||
*
|
||||
* \return Tvg_Result enumeration.
|
||||
* \retval TVG_RESULT_SUCCESS Succeed.
|
||||
* \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.
|
||||
*
|
||||
* \since 0.13
|
||||
*/
|
||||
TVG_API Tvg_Result tvg_animation_set_segment(Tvg_Animation* animation, float begin, float end);
|
||||
|
||||
|
||||
/*!
|
||||
* \brief Gets the current segment. (Experimental API)
|
||||
*
|
||||
* \param[in] animation The Tvg_Animation pointer to the animation object.
|
||||
* \param[out] begin segment begin.
|
||||
* \param[out] end segment end.
|
||||
*
|
||||
* \return Tvg_Result enumeration.
|
||||
* \retval TVG_RESULT_SUCCESS Succeed.
|
||||
* \retval TVG_RESULT_INSUFFICIENT_CONDITION In case the animation is not loaded.
|
||||
* \retval TVG_RESULT_INVALID_ARGUMENT When the given parameters are @c nullptr.
|
||||
*/
|
||||
TVG_API Tvg_Result tvg_animation_get_segment(Tvg_Animation* animation, float* begin, float* end);
|
||||
|
||||
|
||||
/*!
|
||||
* \brief Deletes the given Tvg_Animation object.
|
||||
*
|
||||
@ -2333,6 +2406,8 @@ TVG_API Tvg_Result tvg_animation_get_duration(Tvg_Animation* animation, float* d
|
||||
* \return Tvg_Result enumeration.
|
||||
* \retval TVG_RESULT_SUCCESS Succeed.
|
||||
* \retval TVG_RESULT_INVALID_ARGUMENT An invalid Tvg_Animation pointer.
|
||||
*
|
||||
* \since 0.13
|
||||
*/
|
||||
TVG_API Tvg_Result tvg_animation_del(Tvg_Animation* animation);
|
||||
|
||||
@ -2364,9 +2439,9 @@ TVG_API Tvg_Animation* tvg_lottie_animation_new(void);
|
||||
* \brief Override the lottie properties through the slot data. (Experimental API)
|
||||
*
|
||||
* \param[in] animation The Tvg_Animation object to override the property with the slot.
|
||||
* \param[in] slot The lottie slot data in json.
|
||||
* \param[in] slot The Lottie slot data in json, or @c nullptr to reset.
|
||||
*
|
||||
* \return Tvg_Animation A new Tvg_LottieAnimation object.
|
||||
* \return Tvg_Result enumeration.
|
||||
* \retval TVG_RESULT_SUCCESS Succeed.
|
||||
* \retval TVG_RESULT_INSUFFICIENT_CONDITION In case the animation is not loaded.
|
||||
* \retval TVG_RESULT_INVALID_ARGUMENT When the given @p slot is invalid
|
||||
@ -2375,6 +2450,48 @@ TVG_API Tvg_Animation* tvg_lottie_animation_new(void);
|
||||
TVG_API Tvg_Result tvg_lottie_animation_override(Tvg_Animation* animation, const char* slot);
|
||||
|
||||
|
||||
/*!
|
||||
* \brief Specifies a segment by marker. (Experimental API)
|
||||
*
|
||||
* \param[in] animation The Tvg_Animation pointer to the Lottie animation object.
|
||||
* \param[in] marker The name of the segment marker.
|
||||
*
|
||||
* \return Tvg_Result enumeration.
|
||||
* \retval TVG_RESULT_SUCCESS Succeed.
|
||||
* \retval TVG_RESULT_INSUFFICIENT_CONDITION In case the animation is not loaded.
|
||||
* \retval TVG_RESULT_INVALID_ARGUMENT When the given @p marker is invalid.
|
||||
* \retval TVG_RESULT_NOT_SUPPORTED The Lottie Animation is not supported.
|
||||
*/
|
||||
TVG_API Tvg_Result tvg_lottie_animation_set_marker(Tvg_Animation* animation, const char* marker);
|
||||
|
||||
|
||||
/*!
|
||||
* \brief Gets the marker count of the animation. (Experimental API)
|
||||
*
|
||||
* \param[in] animation The Tvg_Animation pointer to the Lottie animation object.
|
||||
* \param[out] cnt The count value of the merkers.
|
||||
*
|
||||
* \return Tvg_Result enumeration.
|
||||
* \retval TVG_RESULT_SUCCESS Succeed.
|
||||
* \retval TVG_RESULT_INVALID_ARGUMENT In case a @c nullptr is passed as the argument.
|
||||
*/
|
||||
TVG_API Tvg_Result tvg_lottie_animation_get_markers_cnt(Tvg_Animation* animation, uint32_t* cnt);
|
||||
|
||||
|
||||
/*!
|
||||
* \brief Gets the marker name by a given index. (Experimental API)
|
||||
*
|
||||
* \param[in] animation The Tvg_Animation pointer to the Lottie animation object.
|
||||
* \param[in] idx The index of the animation marker, starts from 0.
|
||||
* \param[out] name The name of marker when succeed.
|
||||
*
|
||||
* \return Tvg_Result enumeration.
|
||||
* \retval TVG_RESULT_SUCCESS Succeed.
|
||||
* \retval TVG_RESULT_INVALID_ARGUMENT In case @c nullptr is passed as the argument or @c idx is out of range.
|
||||
*/
|
||||
TVG_API Tvg_Result tvg_lottie_animation_get_marker(Tvg_Animation* animation, uint32_t idx, const char** name);
|
||||
|
||||
|
||||
/** \} */ // end addtogroup ThorVGCapi_LottieAnimation
|
||||
|
||||
|
||||
|
@ -14,7 +14,7 @@ namespace tvg
|
||||
* This class extends the Animation and has additional interfaces.
|
||||
*
|
||||
* @see Animation
|
||||
*
|
||||
*
|
||||
* @note Experimental API
|
||||
*/
|
||||
|
||||
@ -28,7 +28,7 @@ public:
|
||||
/**
|
||||
* @brief Override Lottie properties using slot data.
|
||||
*
|
||||
* @param[in] slot The Lottie slot data in JSON format.
|
||||
* @param[in] slot The Lottie slot data in JSON format to override, or @c nullptr to reset.
|
||||
*
|
||||
* @retval Result::Success When succeed.
|
||||
* @retval Result::InsufficientCondition In case the animation is not loaded.
|
||||
@ -38,6 +38,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.
|
||||
*
|
||||
|
@ -96,6 +96,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);
|
||||
|
@ -64,6 +64,7 @@ struct Array
|
||||
|
||||
void push(Array<T>& rhs)
|
||||
{
|
||||
if (rhs.count == 0) return;
|
||||
grow(rhs.count);
|
||||
memcpy(data + count, rhs.data, rhs.count * sizeof(T));
|
||||
count += rhs.count;
|
||||
|
@ -84,6 +84,12 @@ Result Canvas::update(Paint* paint) noexcept
|
||||
}
|
||||
|
||||
|
||||
Result Canvas::viewport(int32_t x, int32_t y, int32_t w, int32_t h) noexcept
|
||||
{
|
||||
return pImpl->viewport(x, y, w, h);
|
||||
}
|
||||
|
||||
|
||||
Result Canvas::sync() noexcept
|
||||
{
|
||||
return pImpl->sync();
|
||||
|
@ -31,10 +31,14 @@
|
||||
|
||||
struct Canvas::Impl
|
||||
{
|
||||
enum Status : uint8_t {Synced = 0, Updating, Drawing};
|
||||
|
||||
list<Paint*> paints;
|
||||
RenderMethod* renderer;
|
||||
RenderRegion vport = {0, 0, INT32_MAX, INT32_MAX};
|
||||
Status status = Status::Synced;
|
||||
|
||||
bool refresh = false; //if all paints should be updated by force.
|
||||
bool drawing = false; //on drawing condition?
|
||||
|
||||
Impl(RenderMethod* pRenderer) : renderer(pRenderer)
|
||||
{
|
||||
@ -44,14 +48,12 @@ struct Canvas::Impl
|
||||
~Impl()
|
||||
{
|
||||
//make it sure any deffered jobs
|
||||
if (renderer) {
|
||||
renderer->sync();
|
||||
renderer->clear();
|
||||
}
|
||||
renderer->sync();
|
||||
renderer->clear();
|
||||
|
||||
clearPaints();
|
||||
|
||||
if (renderer && (renderer->unref() == 0)) delete(renderer);
|
||||
if (renderer->unref() == 0) delete(renderer);
|
||||
}
|
||||
|
||||
void clearPaints()
|
||||
@ -65,7 +67,7 @@ struct Canvas::Impl
|
||||
Result push(unique_ptr<Paint> paint)
|
||||
{
|
||||
//You can not push paints during rendering.
|
||||
if (drawing) return Result::InsufficientCondition;
|
||||
if (status == Status::Drawing) return Result::InsufficientCondition;
|
||||
|
||||
auto p = paint.release();
|
||||
if (!p) return Result::MemoryCorruption;
|
||||
@ -78,12 +80,12 @@ struct Canvas::Impl
|
||||
Result clear(bool free)
|
||||
{
|
||||
//Clear render target before drawing
|
||||
if (!renderer || !renderer->clear()) return Result::InsufficientCondition;
|
||||
if (!renderer->clear()) return Result::InsufficientCondition;
|
||||
|
||||
//Free paints
|
||||
if (free) clearPaints();
|
||||
|
||||
drawing = false;
|
||||
status = Status::Synced;
|
||||
|
||||
return Result::Success;
|
||||
}
|
||||
@ -95,37 +97,27 @@ struct Canvas::Impl
|
||||
|
||||
Result update(Paint* paint, bool force)
|
||||
{
|
||||
if (paints.empty() || drawing || !renderer) return Result::InsufficientCondition;
|
||||
if (paints.empty() || status == Status::Drawing) return Result::InsufficientCondition;
|
||||
|
||||
Array<RenderData> clips;
|
||||
auto flag = RenderUpdateFlag::None;
|
||||
if (refresh || force) flag = RenderUpdateFlag::All;
|
||||
|
||||
//Update single paint node
|
||||
if (paint) {
|
||||
//Optimize Me: Can we skip the searching?
|
||||
for (auto paint2 : paints) {
|
||||
if (paint2 == paint) {
|
||||
paint->pImpl->update(renderer, nullptr, clips, 255, flag);
|
||||
return Result::Success;
|
||||
}
|
||||
}
|
||||
return Result::InvalidArguments;
|
||||
//Update all retained paint nodes
|
||||
paint->pImpl->update(renderer, nullptr, clips, 255, flag);
|
||||
} else {
|
||||
for (auto paint : paints) {
|
||||
paint->pImpl->update(renderer, nullptr, clips, 255, flag);
|
||||
}
|
||||
refresh = false;
|
||||
}
|
||||
|
||||
refresh = false;
|
||||
|
||||
status = Status::Updating;
|
||||
return Result::Success;
|
||||
}
|
||||
|
||||
Result draw()
|
||||
{
|
||||
if (drawing || paints.empty() || !renderer || !renderer->preRender()) return Result::InsufficientCondition;
|
||||
if (status == Status::Drawing || paints.empty() || !renderer->preRender()) return Result::InsufficientCondition;
|
||||
|
||||
bool rendered = false;
|
||||
for (auto paint : paints) {
|
||||
@ -134,22 +126,37 @@ struct Canvas::Impl
|
||||
|
||||
if (!rendered || !renderer->postRender()) return Result::InsufficientCondition;
|
||||
|
||||
drawing = true;
|
||||
|
||||
status = Status::Drawing;
|
||||
return Result::Success;
|
||||
}
|
||||
|
||||
Result sync()
|
||||
{
|
||||
if (!drawing) return Result::InsufficientCondition;
|
||||
if (status == Status::Synced) return Result::InsufficientCondition;
|
||||
|
||||
if (renderer->sync()) {
|
||||
drawing = false;
|
||||
status = Status::Synced;
|
||||
return Result::Success;
|
||||
}
|
||||
|
||||
return Result::InsufficientCondition;
|
||||
}
|
||||
|
||||
Result viewport(int32_t x, int32_t y, int32_t w, int32_t h)
|
||||
{
|
||||
if (status != Status::Synced) return Result::InsufficientCondition;
|
||||
RenderRegion val = {x, y, w, h};
|
||||
//intersect if the target buffer is already set.
|
||||
auto surface = renderer->mainSurface();
|
||||
if (surface && surface->w > 0 && surface->h > 0) {
|
||||
val.intersect({0, 0, (int32_t)surface->w, (int32_t)surface->h});
|
||||
}
|
||||
if (vport == val) return Result::Success;
|
||||
renderer->viewport(val);
|
||||
vport = val;
|
||||
needRefresh();
|
||||
return Result::Success;
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* _TVG_CANVAS_H_ */
|
||||
|
@ -26,8 +26,10 @@
|
||||
#include "config.h"
|
||||
#include <string>
|
||||
#include "thorvg.h"
|
||||
#include "thorvg_lottie.h"
|
||||
#include "thorvg_capi.h"
|
||||
#ifdef THORVG_LOTTIE_LOADER_SUPPORT
|
||||
#include "thorvg_lottie.h"
|
||||
#endif
|
||||
|
||||
using namespace std;
|
||||
using namespace tvg;
|
||||
@ -112,6 +114,13 @@ TVG_API Tvg_Result tvg_canvas_update(Tvg_Canvas* canvas)
|
||||
}
|
||||
|
||||
|
||||
TVG_API Tvg_Result tvg_canvas_est_viewport(Tvg_Canvas* canvas, int32_t x, int32_t y, int32_t w, int32_t h)
|
||||
{
|
||||
if (!canvas) return TVG_RESULT_INVALID_ARGUMENT;
|
||||
return (Tvg_Result) reinterpret_cast<Canvas*>(canvas)->viewport(x, y, w, h);
|
||||
}
|
||||
|
||||
|
||||
TVG_API Tvg_Result tvg_canvas_update_paint(Tvg_Canvas* canvas, Tvg_Paint* paint)
|
||||
{
|
||||
if (!canvas || !paint) return TVG_RESULT_INVALID_ARGUMENT;
|
||||
@ -771,6 +780,20 @@ TVG_API Tvg_Result tvg_animation_get_duration(Tvg_Animation* animation, float* d
|
||||
}
|
||||
|
||||
|
||||
TVG_API Tvg_Result tvg_animation_set_segment(Tvg_Animation* animation, float start, float end)
|
||||
{
|
||||
if (!animation) return TVG_RESULT_INVALID_ARGUMENT;
|
||||
return (Tvg_Result) reinterpret_cast<Animation*>(animation)->segment(start, end);
|
||||
}
|
||||
|
||||
|
||||
TVG_API Tvg_Result tvg_animation_get_segment(Tvg_Animation* animation, float* start, float* end)
|
||||
{
|
||||
if (!animation) return TVG_RESULT_INVALID_ARGUMENT;
|
||||
return (Tvg_Result) reinterpret_cast<Animation*>(animation)->segment(start, end);
|
||||
}
|
||||
|
||||
|
||||
TVG_API Tvg_Result tvg_animation_del(Tvg_Animation* animation)
|
||||
{
|
||||
if (!animation) return TVG_RESULT_INVALID_ARGUMENT;
|
||||
@ -801,6 +824,39 @@ TVG_API Tvg_Result tvg_lottie_animation_override(Tvg_Animation* animation, const
|
||||
return TVG_RESULT_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
|
||||
TVG_API Tvg_Result tvg_lottie_animation_set_marker(Tvg_Animation* animation, const char* marker)
|
||||
{
|
||||
#ifdef THORVG_LOTTIE_LOADER_SUPPORT
|
||||
if (!animation) return TVG_RESULT_INVALID_ARGUMENT;
|
||||
return (Tvg_Result) reinterpret_cast<LottieAnimation*>(animation)->segment(marker);
|
||||
#endif
|
||||
return TVG_RESULT_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
|
||||
TVG_API Tvg_Result tvg_lottie_animation_get_markers_cnt(Tvg_Animation* animation, uint32_t* cnt)
|
||||
{
|
||||
#ifdef THORVG_LOTTIE_LOADER_SUPPORT
|
||||
if (!animation || !cnt) return TVG_RESULT_INVALID_ARGUMENT;
|
||||
*cnt = reinterpret_cast<LottieAnimation*>(animation)->markersCnt();
|
||||
return TVG_RESULT_SUCCESS;
|
||||
#endif
|
||||
return TVG_RESULT_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
|
||||
TVG_API Tvg_Result tvg_lottie_animation_get_marker(Tvg_Animation* animation, uint32_t idx, const char** name)
|
||||
{
|
||||
#ifdef THORVG_LOTTIE_LOADER_SUPPORT
|
||||
if (!animation || !name) return TVG_RESULT_INVALID_ARGUMENT;
|
||||
*name = reinterpret_cast<LottieAnimation*>(animation)->marker(idx);
|
||||
if (!(*name)) return TVG_RESULT_INVALID_ARGUMENT;
|
||||
return TVG_RESULT_SUCCESS;
|
||||
#endif
|
||||
return TVG_RESULT_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
@ -461,11 +461,11 @@ size_t b64Decode(const char* encoded, const size_t len, char** decoded)
|
||||
auto value2 = B64_INDEX[(size_t)encoded[1]];
|
||||
output[idx++] = (value1 << 2) + ((value2 & 0x30) >> 4);
|
||||
|
||||
if (!encoded[2] || encoded[2] == '=' || encoded[2] == '.') break;
|
||||
if (!encoded[2] || encoded[3] < 0 || encoded[2] == '=' || encoded[2] == '.') break;
|
||||
auto value3 = B64_INDEX[(size_t)encoded[2]];
|
||||
output[idx++] = ((value2 & 0x0f) << 4) + ((value3 & 0x3c) >> 2);
|
||||
|
||||
if (!encoded[3] || encoded[3] == '=' || encoded[3] == '.') break;
|
||||
if (!encoded[3] || encoded[3] < 0 || encoded[3] == '=' || encoded[3] == '.') break;
|
||||
auto value4 = B64_INDEX[(size_t)encoded[3]];
|
||||
output[idx++] = ((value3 & 0x03) << 6) + value4;
|
||||
encoded += 4;
|
||||
|
@ -34,6 +34,9 @@ namespace tvg
|
||||
class FrameModule: public ImageLoader
|
||||
{
|
||||
public:
|
||||
float segmentBegin = 0.0f;
|
||||
float segmentEnd = 1.0f;
|
||||
|
||||
FrameModule(FileType type) : ImageLoader(type) {}
|
||||
virtual ~FrameModule() {}
|
||||
|
||||
@ -42,6 +45,18 @@ 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; }
|
||||
};
|
||||
|
||||
|
@ -63,14 +63,16 @@ GlCanvas::~GlCanvas()
|
||||
}
|
||||
|
||||
|
||||
Result GlCanvas::target(uint32_t* buffer, uint32_t stride, uint32_t w, uint32_t h) noexcept
|
||||
Result GlCanvas::target(int32_t id, uint32_t w, uint32_t h) noexcept
|
||||
{
|
||||
#ifdef THORVG_GL_RASTER_SUPPORT
|
||||
//We know renderer type, avoid dynamic_cast for performance.
|
||||
auto renderer = static_cast<GlRenderer*>(Canvas::pImpl->renderer);
|
||||
if (!renderer) return Result::MemoryCorruption;
|
||||
|
||||
if (!renderer->target(buffer, stride, w, h)) return Result::Unknown;
|
||||
if (!renderer->target(id, w, h)) return Result::Unknown;
|
||||
Canvas::pImpl->vport = {0, 0, (int32_t)w, (int32_t)h};
|
||||
renderer->viewport(Canvas::pImpl->vport);
|
||||
|
||||
//Paints must be updated again with this new target.
|
||||
Canvas::pImpl->needRefresh();
|
||||
|
@ -24,9 +24,9 @@
|
||||
#if LV_USE_THORVG_INTERNAL
|
||||
|
||||
#include "tvgMath.h"
|
||||
#include "tvgBezier.h"
|
||||
#include "tvgLines.h"
|
||||
|
||||
#define BEZIER_EPSILON 1e-4f
|
||||
#define BEZIER_EPSILON 1e-2f
|
||||
|
||||
/************************************************************************/
|
||||
/* Internal Class Implementation */
|
||||
@ -104,6 +104,25 @@ float _bezAt(const Bezier& bz, float at, float length, LengthFunc lineLengthFunc
|
||||
namespace tvg
|
||||
{
|
||||
|
||||
float lineLength(const Point& pt1, const Point& pt2)
|
||||
{
|
||||
return _lineLength(pt1, pt2);
|
||||
}
|
||||
|
||||
|
||||
void lineSplitAt(const Line& cur, float at, Line& left, Line& right)
|
||||
{
|
||||
auto len = lineLength(cur.pt1, cur.pt2);
|
||||
auto dx = ((cur.pt2.x - cur.pt1.x) / len) * at;
|
||||
auto dy = ((cur.pt2.y - cur.pt1.y) / len) * at;
|
||||
left.pt1 = cur.pt1;
|
||||
left.pt2.x = left.pt1.x + dx;
|
||||
left.pt2.y = left.pt1.y + dy;
|
||||
right.pt1 = left.pt2;
|
||||
right.pt2 = cur.pt2;
|
||||
}
|
||||
|
||||
|
||||
void bezSplit(const Bezier& cur, Bezier& left, Bezier& right)
|
||||
{
|
||||
auto c = (cur.ctrl1.x + cur.ctrl2.x) * 0.5f;
|
||||
@ -222,11 +241,9 @@ float bezAngleAt(const Bezier& bz, float t)
|
||||
pt.x *= 3;
|
||||
pt.y *= 3;
|
||||
|
||||
return atan2(pt.x, pt.y) * 180.0f / 3.141592f;
|
||||
return mathRad2Deg(atan2(pt.x, pt.y));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif /* LV_USE_THORVG_INTERNAL */
|
||||
|
@ -23,14 +23,24 @@
|
||||
#include "../../lv_conf_internal.h"
|
||||
#if LV_USE_THORVG_INTERNAL
|
||||
|
||||
#ifndef _TVG_BEZIER_H_
|
||||
#define _TVG_BEZIER_H_
|
||||
#ifndef _TVG_LINES_H_
|
||||
#define _TVG_LINES_H_
|
||||
|
||||
#include "tvgCommon.h"
|
||||
|
||||
namespace tvg
|
||||
{
|
||||
|
||||
struct Line
|
||||
{
|
||||
Point pt1;
|
||||
Point pt2;
|
||||
};
|
||||
|
||||
float lineLength(const Point& pt1, const Point& pt2);
|
||||
void lineSplitAt(const Line& cur, float at, Line& left, Line& right);
|
||||
|
||||
|
||||
struct Bezier
|
||||
{
|
||||
Point start;
|
||||
@ -51,7 +61,6 @@ float bezLengthApprox(const Bezier& cur);
|
||||
float bezAtApprox(const Bezier& bz, float at, float length);
|
||||
}
|
||||
|
||||
#endif //_TVG_BEZIER_H_
|
||||
#endif //_TVG_LINES_H_
|
||||
|
||||
#endif /* LV_USE_THORVG_INTERNAL */
|
||||
|
@ -181,7 +181,6 @@ static LoadModule* _findByPath(const string& path)
|
||||
if (!ext.compare("tvg")) return _find(FileType::Tvg);
|
||||
if (!ext.compare("svg")) return _find(FileType::Svg);
|
||||
if (!ext.compare("json")) return _find(FileType::Lottie);
|
||||
if (!ext.compare("lottie")) return _find(FileType::Lottie);
|
||||
if (!ext.compare("png")) return _find(FileType::Png);
|
||||
if (!ext.compare("jpg")) return _find(FileType::Jpg);
|
||||
if (!ext.compare("webp")) return _find(FileType::Webp);
|
||||
@ -274,7 +273,7 @@ bool LoaderMgr::term()
|
||||
auto tmp = loader;
|
||||
loader = loader->next;
|
||||
_activeLoaders.remove(tmp);
|
||||
if (ret) delete(loader);
|
||||
if (ret) delete(tmp);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -298,15 +297,24 @@ LoadModule* LoaderMgr::loader(const string& path, bool* invalid)
|
||||
{
|
||||
*invalid = false;
|
||||
|
||||
if (auto loader = _findFromCache(path)) return loader;
|
||||
//TODO: lottie is not sharable.
|
||||
auto allowCache = true;
|
||||
auto ext = path.substr(path.find_last_of(".") + 1);
|
||||
if (!ext.compare("json")) allowCache = false;
|
||||
|
||||
if (allowCache) {
|
||||
if (auto loader = _findFromCache(path)) return loader;
|
||||
}
|
||||
|
||||
if (auto loader = _findByPath(path)) {
|
||||
if (loader->open(path)) {
|
||||
loader->hashpath = strdup(path.c_str());
|
||||
loader->pathcache = true;
|
||||
{
|
||||
ScopedLock lock(key);
|
||||
_activeLoaders.back(loader);
|
||||
if (allowCache) {
|
||||
loader->hashpath = strdup(path.c_str());
|
||||
loader->pathcache = true;
|
||||
{
|
||||
ScopedLock lock(key);
|
||||
_activeLoaders.back(loader);
|
||||
}
|
||||
}
|
||||
return loader;
|
||||
}
|
||||
@ -316,11 +324,13 @@ LoadModule* LoaderMgr::loader(const string& path, bool* invalid)
|
||||
for (int i = 0; i < static_cast<int>(FileType::Raw); i++) {
|
||||
if (auto loader = _find(static_cast<FileType>(i))) {
|
||||
if (loader->open(path)) {
|
||||
loader->hashpath = strdup(path.c_str());
|
||||
loader->pathcache = true;
|
||||
{
|
||||
ScopedLock lock(key);
|
||||
_activeLoaders.back(loader);
|
||||
if (allowCache) {
|
||||
loader->hashpath = strdup(path.c_str());
|
||||
loader->pathcache = true;
|
||||
{
|
||||
ScopedLock lock(key);
|
||||
_activeLoaders.back(loader);
|
||||
}
|
||||
}
|
||||
return loader;
|
||||
}
|
||||
@ -357,7 +367,15 @@ LoadModule* LoaderMgr::loader(const char* data, uint32_t size, const string& mim
|
||||
{
|
||||
//Note that users could use the same data pointer with the different content.
|
||||
//Thus caching is only valid for shareable.
|
||||
if (!copy) {
|
||||
auto allowCache = !copy;
|
||||
|
||||
//TODO: lottie is not sharable.
|
||||
if (allowCache) {
|
||||
auto type = _convert(mimeType);
|
||||
if (type == FileType::Lottie) allowCache = false;
|
||||
}
|
||||
|
||||
if (allowCache) {
|
||||
if (auto loader = _findFromCache(data, size, mimeType)) return loader;
|
||||
}
|
||||
|
||||
@ -365,7 +383,7 @@ LoadModule* LoaderMgr::loader(const char* data, uint32_t size, const string& mim
|
||||
if (!mimeType.empty()) {
|
||||
if (auto loader = _findByType(mimeType)) {
|
||||
if (loader->open(data, size, copy)) {
|
||||
if (!copy) {
|
||||
if (allowCache) {
|
||||
loader->hashkey = HASH_KEY(data);
|
||||
ScopedLock lock(key);
|
||||
_activeLoaders.back(loader);
|
||||
@ -382,7 +400,7 @@ LoadModule* LoaderMgr::loader(const char* data, uint32_t size, const string& mim
|
||||
auto loader = _find(static_cast<FileType>(i));
|
||||
if (loader) {
|
||||
if (loader->open(data, size, copy)) {
|
||||
if (!copy) {
|
||||
if (allowCache) {
|
||||
loader->hashkey = HASH_KEY(data);
|
||||
ScopedLock lock(key);
|
||||
_activeLoaders.back(loader);
|
||||
|
@ -41,6 +41,7 @@ LottieAnimation::~LottieAnimation()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
Result LottieAnimation::override(const char* slot) noexcept
|
||||
{
|
||||
if (!pImpl->picture->pImpl->loader) return Result::InsufficientCondition;
|
||||
@ -53,6 +54,41 @@ Result LottieAnimation::override(const char* slot) noexcept
|
||||
}
|
||||
|
||||
|
||||
Result LottieAnimation::segment(const char* marker) noexcept
|
||||
{
|
||||
auto loader = pImpl->picture->pImpl->loader;
|
||||
if (!loader) return Result::InsufficientCondition;
|
||||
if (!loader->animatable()) return Result::NonSupport;
|
||||
|
||||
if (!marker) {
|
||||
static_cast<FrameModule*>(loader)->segment(0.0f, 1.0f);
|
||||
return Result::Success;
|
||||
}
|
||||
|
||||
float begin, end;
|
||||
if (!static_cast<LottieLoader*>(loader)->segment(marker, begin, end)) {
|
||||
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)->markersCnt();
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
{
|
||||
return unique_ptr<LottieAnimation>(new LottieAnimation);
|
||||
|
@ -22,16 +22,28 @@
|
||||
|
||||
#include "../../lv_conf_internal.h"
|
||||
#if LV_USE_THORVG_INTERNAL
|
||||
|
||||
#ifndef _TVG_LOTTIE_BUILDER_H_
|
||||
#define _TVG_LOTTIE_BUILDER_H_
|
||||
|
||||
#include "tvgCommon.h"
|
||||
#include "tvgLottieExpressions.h"
|
||||
|
||||
struct LottieComposition;
|
||||
|
||||
struct LottieBuilder
|
||||
{
|
||||
LottieExpressions* exps = nullptr;
|
||||
|
||||
LottieBuilder()
|
||||
{
|
||||
exps = LottieExpressions::instance();
|
||||
}
|
||||
|
||||
~LottieBuilder()
|
||||
{
|
||||
LottieExpressions::retrieve(exps);
|
||||
}
|
||||
|
||||
bool update(LottieComposition* comp, float progress);
|
||||
void build(LottieComposition* comp);
|
||||
};
|
||||
|
1298
src/libs/thorvg/tvgLottieExpressions.cpp
Normal file
171
src/libs/thorvg/tvgLottieExpressions.h
Normal file
@ -0,0 +1,171 @@
|
||||
/*
|
||||
* Copyright (c) 2024 the ThorVG project. 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.
|
||||
*/
|
||||
|
||||
#include "../../lv_conf_internal.h"
|
||||
#if LV_USE_THORVG_INTERNAL
|
||||
|
||||
#ifndef _TVG_LOTTIE_EXPRESSIONS_H_
|
||||
#define _TVG_LOTTIE_EXPRESSIONS_H_
|
||||
|
||||
#include "tvgCommon.h"
|
||||
|
||||
struct LottieExpression;
|
||||
struct LottieComposition;
|
||||
struct RGB24;
|
||||
|
||||
#ifdef THORVG_LOTTIE_EXPRESSIONS_SUPPORT
|
||||
|
||||
#include "jerryscript.h"
|
||||
|
||||
|
||||
struct LottieExpressions
|
||||
{
|
||||
public:
|
||||
template<typename Property, typename NumType>
|
||||
bool result(float frameNo, NumType& out, LottieExpression* exp)
|
||||
{
|
||||
auto bm_rt = evaluate(frameNo, exp);
|
||||
|
||||
if (auto prop = static_cast<Property*>(jerry_object_get_native_ptr(bm_rt, nullptr))) {
|
||||
out = (*prop)(frameNo);
|
||||
} else if (jerry_value_is_number(bm_rt)) {
|
||||
out = (NumType) jerry_value_as_number(bm_rt);
|
||||
} else {
|
||||
TVGERR("LOTTIE", "Failed dispatching a Value!");
|
||||
return false;
|
||||
}
|
||||
jerry_value_free(bm_rt);
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename Property>
|
||||
bool result(float frameNo, Point& out, LottieExpression* exp)
|
||||
{
|
||||
auto bm_rt = evaluate(frameNo, exp);
|
||||
|
||||
if (jerry_value_is_object(bm_rt)) {
|
||||
if (auto prop = static_cast<Property*>(jerry_object_get_native_ptr(bm_rt, nullptr))) {
|
||||
out = (*prop)(frameNo);
|
||||
} else {
|
||||
auto x = jerry_object_get_index(bm_rt, 0);
|
||||
auto y = jerry_object_get_index(bm_rt, 1);
|
||||
out.x = jerry_value_as_number(x);
|
||||
out.y = jerry_value_as_number(y);
|
||||
jerry_value_free(x);
|
||||
jerry_value_free(y);
|
||||
}
|
||||
} else {
|
||||
TVGERR("LOTTIE", "Failed dispatching Point!");
|
||||
return false;
|
||||
}
|
||||
jerry_value_free(bm_rt);
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename Property>
|
||||
bool result(float frameNo, RGB24& out, LottieExpression* exp)
|
||||
{
|
||||
auto bm_rt = evaluate(frameNo, exp);
|
||||
|
||||
if (auto color = static_cast<Property*>(jerry_object_get_native_ptr(bm_rt, nullptr))) {
|
||||
out = (*color)(frameNo);
|
||||
} else {
|
||||
TVGERR("LOTTIE", "Failed dispatching Color!");
|
||||
return false;
|
||||
}
|
||||
jerry_value_free(bm_rt);
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename Property>
|
||||
bool result(float frameNo, Fill* fill, LottieExpression* exp)
|
||||
{
|
||||
auto bm_rt = evaluate(frameNo, exp);
|
||||
|
||||
if (auto colorStop = static_cast<Property*>(jerry_object_get_native_ptr(bm_rt, nullptr))) {
|
||||
(*colorStop)(frameNo, fill, this);
|
||||
} else {
|
||||
TVGERR("LOTTIE", "Failed dispatching ColorStop!");
|
||||
return false;
|
||||
}
|
||||
jerry_value_free(bm_rt);
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename Property>
|
||||
bool result(float frameNo, Array<PathCommand>& cmds, Array<Point>& pts, Matrix* transform, float roundness, LottieExpression* exp)
|
||||
{
|
||||
auto bm_rt = evaluate(frameNo, exp);
|
||||
|
||||
if (auto pathset = static_cast<Property*>(jerry_object_get_native_ptr(bm_rt, nullptr))) {
|
||||
(*pathset)(frameNo, cmds, pts, transform, roundness);
|
||||
} else {
|
||||
TVGERR("LOTTIE", "Failed dispatching PathSet!");
|
||||
return false;
|
||||
}
|
||||
jerry_value_free(bm_rt);
|
||||
return true;
|
||||
}
|
||||
|
||||
void update(float curTime);
|
||||
|
||||
//singleton (no thread safety)
|
||||
static LottieExpressions* instance();
|
||||
static void retrieve(LottieExpressions* instance);
|
||||
|
||||
private:
|
||||
LottieExpressions();
|
||||
~LottieExpressions();
|
||||
|
||||
jerry_value_t evaluate(float frameNo, LottieExpression* exp);
|
||||
jerry_value_t buildGlobal();
|
||||
void buildComp(LottieComposition* comp);
|
||||
|
||||
//global object, attributes, methods
|
||||
jerry_value_t global;
|
||||
jerry_value_t comp;
|
||||
jerry_value_t layer;
|
||||
jerry_value_t thisComp;
|
||||
jerry_value_t thisLayer;
|
||||
jerry_value_t thisProperty;
|
||||
};
|
||||
|
||||
#else
|
||||
|
||||
struct LottieExpressions
|
||||
{
|
||||
template<typename Property, typename NumType> bool result(TVG_UNUSED float, TVG_UNUSED NumType&, TVG_UNUSED LottieExpression*) { return false; }
|
||||
template<typename Property> bool result(TVG_UNUSED float, TVG_UNUSED Point&, LottieExpression*) { return false; }
|
||||
template<typename Property> bool result(TVG_UNUSED float, TVG_UNUSED RGB24&, TVG_UNUSED LottieExpression*) { return false; }
|
||||
template<typename Property> bool result(TVG_UNUSED float, TVG_UNUSED Fill*, TVG_UNUSED LottieExpression*) { return false; }
|
||||
template<typename Property> bool result(TVG_UNUSED float, TVG_UNUSED Array<PathCommand>&, TVG_UNUSED Array<Point>&, TVG_UNUSED Matrix* transform, TVG_UNUSED float, TVG_UNUSED LottieExpression*) { return false; }
|
||||
void update(TVG_UNUSED float) {}
|
||||
static LottieExpressions* instance() { return nullptr; }
|
||||
static void retrieve(TVG_UNUSED LottieExpressions* instance) {}
|
||||
};
|
||||
|
||||
#endif //THORVG_LOTTIE_EXPRESSIONS_SUPPORT
|
||||
|
||||
#endif //_TVG_LOTTIE_EXPRESSIONS_H_
|
||||
|
||||
#endif /* LV_USE_THORVG_INTERNAL */
|
||||
|
@ -43,20 +43,20 @@
|
||||
#define SUBDIVISION_MAX_ITERATIONS 10
|
||||
|
||||
|
||||
static inline float tvg_A(float aA1, float aA2) { return 1.0f - 3.0f * aA2 + 3.0f * aA1; }
|
||||
static inline float tvg_B(float aA1, float aA2) { return 3.0f * aA2 - 6.0f * aA1; }
|
||||
static inline float tvg_C(float aA1) { return 3.0f * aA1; }
|
||||
static inline float _constA(float aA1, float aA2) { return 1.0f - 3.0f * aA2 + 3.0f * aA1; }
|
||||
static inline float _constB(float aA1, float aA2) { return 3.0f * aA2 - 6.0f * aA1; }
|
||||
static inline float _constC(float aA1) { return 3.0f * aA1; }
|
||||
|
||||
|
||||
static inline float _getSlope(float t, float aA1, float aA2)
|
||||
{
|
||||
return 3.0f * tvg_A(aA1, aA2) * t * t + 2.0f * tvg_B(aA1, aA2) * t + tvg_C(aA1);
|
||||
return 3.0f * _constA(aA1, aA2) * t * t + 2.0f * _constB(aA1, aA2) * t + _constC(aA1);
|
||||
}
|
||||
|
||||
|
||||
static inline float _calcBezier(float t, float aA1, float aA2)
|
||||
{
|
||||
return ((tvg_A(aA1, aA2) * t + tvg_B(aA1, aA2)) * t + tvg_C(aA1)) * t;
|
||||
return ((_constA(aA1, aA2) * t + _constB(aA1, aA2)) * t + _constC(aA1)) * t;
|
||||
}
|
||||
|
||||
|
||||
|
@ -33,23 +33,6 @@
|
||||
/* Internal Class Implementation */
|
||||
/************************************************************************/
|
||||
|
||||
static bool _checkDotLottie(const char *str)
|
||||
{
|
||||
//check the .Lottie signature.
|
||||
if (str[0] == 0x50 && str[1] == 0x4B && str[2] == 0x03 && str[3] == 0x04) return true;
|
||||
else return false;
|
||||
}
|
||||
|
||||
|
||||
static float _str2float(const char* str, int len)
|
||||
{
|
||||
auto tmp = strDuplicate(str, len);
|
||||
auto ret = strToFloat(tmp, nullptr);
|
||||
free(tmp);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
void LottieLoader::run(unsigned tid)
|
||||
{
|
||||
//update frame
|
||||
@ -62,6 +45,7 @@ void LottieLoader::run(unsigned tid)
|
||||
comp = parser.comp;
|
||||
builder->build(comp);
|
||||
}
|
||||
rebuild = false;
|
||||
}
|
||||
|
||||
|
||||
@ -138,7 +122,7 @@ bool LottieLoader::header()
|
||||
p += 5;
|
||||
auto e = strstr(p, ",");
|
||||
if (!e) e = strstr(p, "}");
|
||||
frameRate = _str2float(p, e - p);
|
||||
frameRate = strToFloat(p, nullptr);
|
||||
p = e;
|
||||
continue;
|
||||
}
|
||||
@ -148,7 +132,7 @@ bool LottieLoader::header()
|
||||
p += 5;
|
||||
auto e = strstr(p, ",");
|
||||
if (!e) e = strstr(p, "}");
|
||||
startFrame = _str2float(p, e - p);
|
||||
startFrame = strToFloat(p, nullptr);
|
||||
p = e;
|
||||
continue;
|
||||
}
|
||||
@ -158,7 +142,7 @@ bool LottieLoader::header()
|
||||
p += 5;
|
||||
auto e = strstr(p, ",");
|
||||
if (!e) e = strstr(p, "}");
|
||||
endFrame = _str2float(p, e - p);
|
||||
endFrame = strToFloat(p, nullptr);
|
||||
p = e;
|
||||
continue;
|
||||
}
|
||||
@ -168,7 +152,7 @@ bool LottieLoader::header()
|
||||
p += 4;
|
||||
auto e = strstr(p, ",");
|
||||
if (!e) e = strstr(p, "}");
|
||||
w = static_cast<float>(str2int(p, e - p));
|
||||
w = strToFloat(p, nullptr);
|
||||
p = e;
|
||||
continue;
|
||||
}
|
||||
@ -177,14 +161,14 @@ bool LottieLoader::header()
|
||||
p += 4;
|
||||
auto e = strstr(p, ",");
|
||||
if (!e) e = strstr(p, "}");
|
||||
h = static_cast<float>(str2int(p, e - p));
|
||||
h = strToFloat(p, nullptr);
|
||||
p = e;
|
||||
continue;
|
||||
}
|
||||
++p;
|
||||
}
|
||||
|
||||
if (frameRate < FLT_EPSILON) {
|
||||
if (frameRate < FLOAT_EPSILON) {
|
||||
TVGLOG("LOTTIE", "Not a Lottie file? Frame rate is 0!");
|
||||
return false;
|
||||
}
|
||||
@ -192,7 +176,7 @@ bool LottieLoader::header()
|
||||
frameCnt = (endFrame - startFrame);
|
||||
frameDuration = frameCnt / frameRate;
|
||||
|
||||
TVGLOG("LOTTIE", "info: frame rate = %f, duration = %f size = %d x %d", frameRate, frameDuration, (int)w, (int)h);
|
||||
TVGLOG("LOTTIE", "info: frame rate = %f, duration = %f size = %f x %f", frameRate, frameDuration, w, h);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -200,13 +184,6 @@ bool LottieLoader::header()
|
||||
|
||||
bool LottieLoader::open(const char* data, uint32_t size, bool copy)
|
||||
{
|
||||
//If the format is dotLottie
|
||||
auto dotLottie = _checkDotLottie(data);
|
||||
if (dotLottie) {
|
||||
TVGLOG("LOTTIE", "Requested .Lottie Format, Not Supported yet.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (copy) {
|
||||
content = (char*)malloc(size);
|
||||
if (!content) return false;
|
||||
@ -244,13 +221,6 @@ bool LottieLoader::open(const string& path)
|
||||
|
||||
fclose(f);
|
||||
|
||||
//If the format is dotLottie
|
||||
auto dotLottie = _checkDotLottie(content);
|
||||
if (dotLottie) {
|
||||
TVGLOG("LOTTIE", "Requested .Lottie Format, Not Supported yet.");
|
||||
return false;
|
||||
}
|
||||
|
||||
this->dirName = strDirname(path.c_str());
|
||||
this->content = content;
|
||||
this->copy = true;
|
||||
@ -292,7 +262,8 @@ bool LottieLoader::read()
|
||||
|
||||
Paint* LottieLoader::paint()
|
||||
{
|
||||
this->done();
|
||||
done();
|
||||
|
||||
if (!comp) return nullptr;
|
||||
comp->initiated = true;
|
||||
return comp->root->scene;
|
||||
@ -301,39 +272,60 @@ Paint* LottieLoader::paint()
|
||||
|
||||
bool LottieLoader::override(const char* slot)
|
||||
{
|
||||
if (!slot || !comp || comp->slots.count == 0) return false;
|
||||
if (!comp) done();
|
||||
|
||||
//TODO: Crashed, does this necessary?
|
||||
auto temp = strdup(slot);
|
||||
if (!comp || comp->slots.count == 0) return false;
|
||||
|
||||
//parsing slot json
|
||||
LottieParser parser(temp, dirName);
|
||||
auto sid = parser.sid();
|
||||
if (!sid) {
|
||||
auto success = true;
|
||||
|
||||
//override slots
|
||||
if (slot) {
|
||||
//Copy the input data because the JSON parser will encode the data immediately.
|
||||
auto temp = strdup(slot);
|
||||
|
||||
//parsing slot json
|
||||
LottieParser parser(temp, dirName);
|
||||
|
||||
auto idx = 0;
|
||||
while (auto sid = parser.sid(idx == 0)) {
|
||||
for (auto s = comp->slots.begin(); s < comp->slots.end(); ++s) {
|
||||
if (strcmp((*s)->sid, sid)) continue;
|
||||
if (!parser.apply(*s)) success = false;
|
||||
break;
|
||||
}
|
||||
++idx;
|
||||
}
|
||||
|
||||
if (idx < 1) success = false;
|
||||
free(temp);
|
||||
return false;
|
||||
rebuild = overriden = success;
|
||||
//reset slots
|
||||
} else if (overriden) {
|
||||
for (auto s = comp->slots.begin(); s < comp->slots.end(); ++s) {
|
||||
(*s)->reset();
|
||||
}
|
||||
overriden = false;
|
||||
rebuild = true;
|
||||
}
|
||||
|
||||
bool ret = false;
|
||||
for (auto s = comp->slots.begin(); s < comp->slots.end(); ++s) {
|
||||
if (strcmp((*s)->sid, sid)) continue;
|
||||
ret = parser.parse(*s);
|
||||
break;
|
||||
}
|
||||
|
||||
free(temp);
|
||||
return ret;
|
||||
return success;
|
||||
}
|
||||
|
||||
|
||||
bool LottieLoader::frame(float no)
|
||||
{
|
||||
//no meaing to update if frame diff is less then 1ms
|
||||
if (fabsf(this->frameNo - no) < 0.001f) return false;
|
||||
auto frameNo = no + startFrame();
|
||||
|
||||
//This ensures that the target frame number is reached.
|
||||
frameNo *= 10000.0f;
|
||||
frameNo = roundf(frameNo);
|
||||
frameNo *= 0.0001f;
|
||||
|
||||
//Skip update if frame diff is too small.
|
||||
if (fabsf(this->frameNo - frameNo) <= 0.0009f) return false;
|
||||
|
||||
this->done();
|
||||
|
||||
this->frameNo = no;
|
||||
this->frameNo = frameNo;
|
||||
|
||||
TaskScheduler::request(this);
|
||||
|
||||
@ -341,28 +333,75 @@ 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();
|
||||
if (!comp) return 0.0f;
|
||||
|
||||
return frameCnt * (segmentEnd - segmentBegin) / comp->frameRate;
|
||||
}
|
||||
|
||||
|
||||
void LottieLoader::sync()
|
||||
{
|
||||
this->done();
|
||||
|
||||
if (rebuild) run(0);
|
||||
}
|
||||
|
||||
|
||||
uint32_t LottieLoader::markersCnt()
|
||||
{
|
||||
if (!comp) done();
|
||||
if (!comp) return 0;
|
||||
return comp->markers.count;
|
||||
}
|
||||
|
||||
|
||||
const char* LottieLoader::markers(uint32_t index)
|
||||
{
|
||||
if (!comp) done();
|
||||
if (!comp || index >= comp->markers.count) return nullptr;
|
||||
auto marker = comp->markers.begin() + index;
|
||||
return (*marker)->name;
|
||||
}
|
||||
|
||||
|
||||
bool LottieLoader::segment(const char* marker, float& begin, float& end)
|
||||
{
|
||||
if (!comp) done();
|
||||
if (!comp) return false;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
#endif /* LV_USE_THORVG_INTERNAL */
|
||||
|
||||
|
@ -47,6 +47,8 @@ public:
|
||||
|
||||
char* dirName = nullptr; //base resource directory
|
||||
bool copy = false; //"content" is owned by this loader
|
||||
bool overriden = false; //overridden properties with slots
|
||||
bool rebuild = false; //require building the lottie scene
|
||||
|
||||
LottieLoader();
|
||||
~LottieLoader();
|
||||
@ -65,9 +67,15 @@ public:
|
||||
float duration() override;
|
||||
void sync() override;
|
||||
|
||||
//Marker Supports
|
||||
uint32_t markersCnt();
|
||||
const char* markers(uint32_t index);
|
||||
bool segment(const char* marker, float& beign, float& end);
|
||||
|
||||
private:
|
||||
bool header();
|
||||
void clear();
|
||||
float startFrame();
|
||||
void run(unsigned tid) override;
|
||||
};
|
||||
|
||||
|
@ -38,6 +38,79 @@
|
||||
/* External Class Implementation */
|
||||
/************************************************************************/
|
||||
|
||||
void LottieSlot::reset()
|
||||
{
|
||||
if (!overriden) return;
|
||||
|
||||
for (auto pair = pairs.begin(); pair < pairs.end(); ++pair) {
|
||||
switch (type) {
|
||||
case LottieProperty::Type::ColorStop: {
|
||||
static_cast<LottieGradient*>(pair->obj)->colorStops.release();
|
||||
static_cast<LottieGradient*>(pair->obj)->colorStops = *static_cast<LottieColorStop*>(pair->prop);
|
||||
static_cast<LottieColorStop*>(pair->prop)->frames = nullptr;
|
||||
break;
|
||||
}
|
||||
case LottieProperty::Type::Color: {
|
||||
static_cast<LottieSolid*>(pair->obj)->color.release();
|
||||
static_cast<LottieSolid*>(pair->obj)->color = *static_cast<LottieColor*>(pair->prop);
|
||||
static_cast<LottieColor*>(pair->prop)->frames = nullptr;
|
||||
break;
|
||||
}
|
||||
case LottieProperty::Type::TextDoc: {
|
||||
static_cast<LottieText*>(pair->obj)->doc.release();
|
||||
static_cast<LottieText*>(pair->obj)->doc = *static_cast<LottieTextDoc*>(pair->prop);
|
||||
static_cast<LottieTextDoc*>(pair->prop)->frames = nullptr;
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
delete(pair->prop);
|
||||
pair->prop = nullptr;
|
||||
}
|
||||
overriden = false;
|
||||
}
|
||||
|
||||
|
||||
void LottieSlot::assign(LottieObject* target)
|
||||
{
|
||||
//apply slot object to all targets
|
||||
for (auto pair = pairs.begin(); pair < pairs.end(); ++pair) {
|
||||
//backup the original properties before overwriting
|
||||
switch (type) {
|
||||
case LottieProperty::Type::ColorStop: {
|
||||
if (!overriden) {
|
||||
pair->prop = new LottieColorStop;
|
||||
*static_cast<LottieColorStop*>(pair->prop) = static_cast<LottieGradient*>(pair->obj)->colorStops;
|
||||
}
|
||||
|
||||
pair->obj->override(&static_cast<LottieGradient*>(target)->colorStops);
|
||||
break;
|
||||
}
|
||||
case LottieProperty::Type::Color: {
|
||||
if (!overriden) {
|
||||
pair->prop = new LottieColor;
|
||||
*static_cast<LottieColor*>(pair->prop) = static_cast<LottieSolid*>(pair->obj)->color;
|
||||
}
|
||||
|
||||
pair->obj->override(&static_cast<LottieSolid*>(target)->color);
|
||||
break;
|
||||
}
|
||||
case LottieProperty::Type::TextDoc: {
|
||||
if (!overriden) {
|
||||
pair->prop = new LottieTextDoc;
|
||||
*static_cast<LottieTextDoc*>(pair->prop) = static_cast<LottieText*>(pair->obj)->doc;
|
||||
}
|
||||
|
||||
pair->obj->override(&static_cast<LottieText*>(target)->doc);
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
overriden = true;
|
||||
}
|
||||
|
||||
|
||||
LottieImage::~LottieImage()
|
||||
{
|
||||
free(b64Data);
|
||||
@ -49,11 +122,11 @@ LottieImage::~LottieImage()
|
||||
}
|
||||
|
||||
|
||||
void LottieTrimpath::segment(float frameNo, float& start, float& end)
|
||||
void LottieTrimpath::segment(float frameNo, float& start, float& end, LottieExpressions* exps)
|
||||
{
|
||||
auto s = this->start(frameNo) * 0.01f;
|
||||
auto e = this->end(frameNo) * 0.01f;
|
||||
auto o = fmod(this->offset(frameNo), 360.0f) / 360.0f; //0 ~ 1
|
||||
auto s = this->start(frameNo, exps) * 0.01f;
|
||||
auto e = this->end(frameNo, exps) * 0.01f;
|
||||
auto o = fmodf(this->offset(frameNo, exps), 360.0f) / 360.0f; //0 ~ 1
|
||||
|
||||
auto diff = fabs(s - e);
|
||||
if (mathZero(diff)) {
|
||||
@ -92,44 +165,126 @@ void LottieTrimpath::segment(float frameNo, float& start, float& end)
|
||||
}
|
||||
|
||||
|
||||
Fill* LottieGradient::fill(float frameNo)
|
||||
uint32_t LottieGradient::populate(ColorStop& color)
|
||||
{
|
||||
colorStops.populated = true;
|
||||
if (!color.input) return 0;
|
||||
|
||||
uint32_t alphaCnt = (color.input->count - (colorStops.count * 4)) / 2;
|
||||
Array<Fill::ColorStop> output(colorStops.count + alphaCnt);
|
||||
uint32_t cidx = 0; //color count
|
||||
uint32_t clast = colorStops.count * 4;
|
||||
if (clast > color.input->count) clast = color.input->count;
|
||||
uint32_t aidx = clast; //alpha count
|
||||
Fill::ColorStop cs;
|
||||
|
||||
//merge color stops.
|
||||
for (uint32_t i = 0; i < color.input->count; ++i) {
|
||||
if (cidx == clast || aidx == color.input->count) break;
|
||||
if ((*color.input)[cidx] == (*color.input)[aidx]) {
|
||||
cs.offset = (*color.input)[cidx];
|
||||
cs.r = lroundf((*color.input)[cidx + 1] * 255.0f);
|
||||
cs.g = lroundf((*color.input)[cidx + 2] * 255.0f);
|
||||
cs.b = lroundf((*color.input)[cidx + 3] * 255.0f);
|
||||
cs.a = lroundf((*color.input)[aidx + 1] * 255.0f);
|
||||
cidx += 4;
|
||||
aidx += 2;
|
||||
} else if ((*color.input)[cidx] < (*color.input)[aidx]) {
|
||||
cs.offset = (*color.input)[cidx];
|
||||
cs.r = lroundf((*color.input)[cidx + 1] * 255.0f);
|
||||
cs.g = lroundf((*color.input)[cidx + 2] * 255.0f);
|
||||
cs.b = lroundf((*color.input)[cidx + 3] * 255.0f);
|
||||
//generate alpha value
|
||||
if (output.count > 0) {
|
||||
auto p = ((*color.input)[cidx] - output.last().offset) / ((*color.input)[aidx] - output.last().offset);
|
||||
cs.a = mathLerp<uint8_t>(output.last().a, lroundf((*color.input)[aidx + 1] * 255.0f), p);
|
||||
} else cs.a = 255;
|
||||
cidx += 4;
|
||||
} else {
|
||||
cs.offset = (*color.input)[aidx];
|
||||
cs.a = lroundf((*color.input)[aidx + 1] * 255.0f);
|
||||
//generate color value
|
||||
if (output.count > 0) {
|
||||
auto p = ((*color.input)[aidx] - output.last().offset) / ((*color.input)[cidx] - output.last().offset);
|
||||
cs.r = mathLerp<uint8_t>(output.last().r, lroundf((*color.input)[cidx + 1] * 255.0f), p);
|
||||
cs.g = mathLerp<uint8_t>(output.last().g, lroundf((*color.input)[cidx + 2] * 255.0f), p);
|
||||
cs.b = mathLerp<uint8_t>(output.last().b, lroundf((*color.input)[cidx + 3] * 255.0f), p);
|
||||
} else cs.r = cs.g = cs.b = 255;
|
||||
aidx += 2;
|
||||
}
|
||||
output.push(cs);
|
||||
}
|
||||
|
||||
//color remains
|
||||
while (cidx + 3 < clast) {
|
||||
cs.offset = (*color.input)[cidx];
|
||||
cs.r = lroundf((*color.input)[cidx + 1] * 255.0f);
|
||||
cs.g = lroundf((*color.input)[cidx + 2] * 255.0f);
|
||||
cs.b = lroundf((*color.input)[cidx + 3] * 255.0f);
|
||||
cs.a = (output.count > 0) ? output.last().a : 255;
|
||||
output.push(cs);
|
||||
cidx += 4;
|
||||
}
|
||||
|
||||
//alpha remains
|
||||
while (aidx < color.input->count) {
|
||||
cs.offset = (*color.input)[aidx];
|
||||
cs.a = lroundf((*color.input)[aidx + 1] * 255.0f);
|
||||
if (output.count > 0) {
|
||||
cs.r = output.last().r;
|
||||
cs.g = output.last().g;
|
||||
cs.b = output.last().b;
|
||||
} else cs.r = cs.g = cs.b = 255;
|
||||
output.push(cs);
|
||||
aidx += 2;
|
||||
}
|
||||
|
||||
color.data = output.data;
|
||||
output.data = nullptr;
|
||||
|
||||
color.input->reset();
|
||||
delete(color.input);
|
||||
|
||||
return output.count;
|
||||
}
|
||||
|
||||
|
||||
Fill* LottieGradient::fill(float frameNo, LottieExpressions* exps)
|
||||
{
|
||||
Fill* fill = nullptr;
|
||||
auto s = start(frameNo, exps);
|
||||
auto e = end(frameNo, exps);
|
||||
|
||||
//Linear Graident
|
||||
if (id == 1) {
|
||||
fill = LinearGradient::gen().release();
|
||||
static_cast<LinearGradient*>(fill)->linear(start(frameNo).x, start(frameNo).y, end(frameNo).x, end(frameNo).y);
|
||||
static_cast<LinearGradient*>(fill)->linear(s.x, s.y, e.x, e.y);
|
||||
}
|
||||
//Radial Gradient
|
||||
if (id == 2) {
|
||||
fill = RadialGradient::gen().release();
|
||||
|
||||
auto sx = start(frameNo).x;
|
||||
auto sy = start(frameNo).y;
|
||||
auto ex = end(frameNo).x;
|
||||
auto ey = end(frameNo).y;
|
||||
auto w = fabsf(ex - sx);
|
||||
auto h = fabsf(ey - sy);
|
||||
auto w = fabsf(e.x - s.x);
|
||||
auto h = fabsf(e.y - s.y);
|
||||
auto r = (w > h) ? (w + 0.375f * h) : (h + 0.375f * w);
|
||||
auto progress = this->height(frameNo) * 0.01f;
|
||||
auto progress = this->height(frameNo, exps) * 0.01f;
|
||||
|
||||
if (mathZero(progress)) {
|
||||
P(static_cast<RadialGradient*>(fill))->radial(sx, sy, r, sx, sy, 0.0f);
|
||||
P(static_cast<RadialGradient*>(fill))->radial(s.x, s.y, r, s.x, s.y, 0.0f);
|
||||
} else {
|
||||
if (mathEqual(progress, 1.0f)) progress = 0.99f;
|
||||
auto startAngle = atan2(ey - sy, ex - sx) * 180.0f / MATH_PI;
|
||||
auto angle = (startAngle + this->angle(frameNo)) * (MATH_PI / 180.0f);
|
||||
auto fx = sx + cos(angle) * progress * r;
|
||||
auto fy = sy + sin(angle) * progress * r;
|
||||
auto startAngle = mathRad2Deg(atan2(e.y - s.y, e.x - s.x));
|
||||
auto angle = mathDeg2Rad((startAngle + this->angle(frameNo, exps)));
|
||||
auto fx = s.x + cos(angle) * progress * r;
|
||||
auto fy = s.y + sin(angle) * progress * r;
|
||||
// Lottie dosen't have any focal radius concept
|
||||
P(static_cast<RadialGradient*>(fill))->radial(sx, sy, r, fx, fy, 0.0f);
|
||||
P(static_cast<RadialGradient*>(fill))->radial(s.x, s.y, r, fx, fy, 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
if (!fill) return nullptr;
|
||||
|
||||
colorStops(frameNo, fill);
|
||||
colorStops(frameNo, fill, exps);
|
||||
|
||||
return fill;
|
||||
}
|
||||
@ -145,14 +300,20 @@ void LottieGroup::prepare(LottieObject::Type type)
|
||||
size_t fillCnt = 0;
|
||||
|
||||
for (auto c = children.end() - 1; c >= children.begin(); --c) {
|
||||
if (reqFragment && !statical) break;
|
||||
auto child = static_cast<LottieObject*>(*c);
|
||||
if (statical) statical &= child->statical;
|
||||
|
||||
if (child->type == LottieObject::Type::Trimpath) trimpath = true;
|
||||
|
||||
/* Figure out if this group is a simple path drawing.
|
||||
In that case, the rendering context can be sharable with the parent's. */
|
||||
if (allowMerge && (child->type == LottieObject::Group || !child->mergeable())) allowMerge = false;
|
||||
|
||||
if (reqFragment) continue;
|
||||
|
||||
/* Figure out if the rendering context should be fragmented.
|
||||
Multiple stroking or grouping with a stroking would occur this.
|
||||
This fragment resolves the overlapped stroke outlines. */
|
||||
if (reqFragment) continue;
|
||||
if (child->type == LottieObject::Group) {
|
||||
if (child->type == LottieObject::Group && !child->mergeable()) {
|
||||
if (strokeCnt > 0 || fillCnt > 0) reqFragment = true;
|
||||
} else if (child->type == LottieObject::SolidStroke || child->type == LottieObject::GradientStroke) {
|
||||
if (strokeCnt > 0) reqFragment = true;
|
||||
@ -162,6 +323,25 @@ void LottieGroup::prepare(LottieObject::Type type)
|
||||
else ++fillCnt;
|
||||
}
|
||||
}
|
||||
|
||||
//Reverse the drawing order if this group has a trimpath.
|
||||
if (!trimpath) return;
|
||||
|
||||
for (uint32_t i = 0; i < children.count - 1; ) {
|
||||
auto child2 = children[i + 1];
|
||||
if (!child2->mergeable() || child2->type == LottieObject::Transform) {
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
auto child = children[i];
|
||||
if (!child->mergeable() || child->type == LottieObject::Transform) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
children[i] = child2;
|
||||
children[i + 1] = child;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -183,13 +363,11 @@ LottieLayer::~LottieLayer()
|
||||
|
||||
void LottieLayer::prepare()
|
||||
{
|
||||
if (transform) statical &= transform->statical;
|
||||
if (timeRemap.frames) statical = false;
|
||||
|
||||
/* if layer is hidden, only useful data is its transform matrix.
|
||||
so force it to be a Null Layer and release all resource. */
|
||||
so force it to be a Null Layer and release all resource. */
|
||||
if (hidden) {
|
||||
type = LottieLayer::Null;
|
||||
for (auto p = children.begin(); p < children.end(); ++p) delete(*p);
|
||||
children.reset();
|
||||
return;
|
||||
}
|
||||
@ -197,10 +375,10 @@ void LottieLayer::prepare()
|
||||
}
|
||||
|
||||
|
||||
float LottieLayer::remap(float frameNo)
|
||||
float LottieLayer::remap(float frameNo, LottieExpressions* exp)
|
||||
{
|
||||
if (timeRemap.frames || timeRemap.value) {
|
||||
frameNo = comp->frameAtTime(timeRemap(frameNo));
|
||||
frameNo = comp->frameAtTime(timeRemap(frameNo, exp));
|
||||
} else {
|
||||
frameNo -= startFrame;
|
||||
}
|
||||
@ -210,7 +388,7 @@ float LottieLayer::remap(float frameNo)
|
||||
|
||||
LottieComposition::~LottieComposition()
|
||||
{
|
||||
if (!initiated) delete(root->scene);
|
||||
if (!initiated && root) delete(root->scene);
|
||||
|
||||
delete(root);
|
||||
free(version);
|
||||
@ -236,6 +414,10 @@ LottieComposition::~LottieComposition()
|
||||
for (auto s = slots.begin(); s < slots.end(); ++s) {
|
||||
delete(*s);
|
||||
}
|
||||
|
||||
for (auto m = markers.begin(); m < markers.end(); ++m) {
|
||||
delete(*m);
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* LV_USE_THORVG_INTERNAL */
|
||||
|
@ -54,29 +54,23 @@ struct LottieStroke
|
||||
return dashattr->value[no];
|
||||
}
|
||||
|
||||
float dashOffset(float frameNo)
|
||||
float dashOffset(float frameNo, LottieExpressions* exps)
|
||||
{
|
||||
return dash(0)(frameNo);
|
||||
return dash(0)(frameNo, exps);
|
||||
}
|
||||
|
||||
float dashGap(float frameNo)
|
||||
float dashGap(float frameNo, LottieExpressions* exps)
|
||||
{
|
||||
return dash(2)(frameNo);
|
||||
return dash(2)(frameNo, exps);
|
||||
}
|
||||
|
||||
float dashSize(float frameNo)
|
||||
float dashSize(float frameNo, LottieExpressions* exps)
|
||||
{
|
||||
auto d = dash(1)(frameNo);
|
||||
auto d = dash(1)(frameNo, exps);
|
||||
if (d == 0.0f) return 0.1f;
|
||||
else return d;
|
||||
}
|
||||
|
||||
bool dynamic()
|
||||
{
|
||||
if (width.frames || dashattr) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
LottieFloat width = 0.0f;
|
||||
DashAttr* dashattr = nullptr;
|
||||
float miterLimit = 0;
|
||||
@ -91,12 +85,6 @@ struct LottieMask
|
||||
LottieOpacity opacity = 255;
|
||||
CompositeMethod method;
|
||||
bool inverse = false;
|
||||
|
||||
bool dynamic()
|
||||
{
|
||||
if (opacity.frames || pathset.frames) return true;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -128,14 +116,15 @@ struct LottieObject
|
||||
free(name);
|
||||
}
|
||||
|
||||
virtual void override(LottieObject* prop)
|
||||
virtual void override(LottieProperty* prop)
|
||||
{
|
||||
TVGERR("LOTTIE", "Unsupported slot type");
|
||||
}
|
||||
|
||||
virtual bool mergeable() { return false; }
|
||||
|
||||
char* name = nullptr;
|
||||
Type type;
|
||||
bool statical = true; //no keyframes
|
||||
bool hidden = false; //remove?
|
||||
};
|
||||
|
||||
@ -183,6 +172,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
|
||||
{
|
||||
@ -191,9 +191,9 @@ struct LottieText : LottieObject
|
||||
LottieObject::type = LottieObject::Text;
|
||||
}
|
||||
|
||||
void override(LottieObject* prop) override
|
||||
void override(LottieProperty* prop) override
|
||||
{
|
||||
this->doc = static_cast<LottieText*>(prop)->doc;
|
||||
this->doc = *static_cast<LottieTextDoc*>(prop);
|
||||
this->prepare();
|
||||
}
|
||||
|
||||
@ -205,18 +205,23 @@ struct LottieText : LottieObject
|
||||
|
||||
struct LottieTrimpath : LottieObject
|
||||
{
|
||||
enum Type : uint8_t { Individual = 1, Simultaneous = 2 };
|
||||
enum Type : uint8_t { Simultaneous = 1, Individual = 2 };
|
||||
|
||||
void prepare()
|
||||
{
|
||||
LottieObject::type = LottieObject::Trimpath;
|
||||
if (start.frames || end.frames || offset.frames) statical = false;
|
||||
}
|
||||
|
||||
void segment(float frameNo, float& start, float& end);
|
||||
bool mergeable() override
|
||||
{
|
||||
if (!start.frames && start.value == 0.0f && !end.frames && end.value == 100.0f && !offset.frames && offset.value == 0.0f) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void segment(float frameNo, float& start, float& end, LottieExpressions* exps);
|
||||
|
||||
LottieFloat start = 0.0f;
|
||||
LottieFloat end = 0.0f;
|
||||
LottieFloat end = 100.0f;
|
||||
LottieFloat offset = 0.0f;
|
||||
Type type = Simultaneous;
|
||||
};
|
||||
@ -226,6 +231,11 @@ struct LottieShape : LottieObject
|
||||
{
|
||||
virtual ~LottieShape() {}
|
||||
uint8_t direction = 0; //0: clockwise, 2: counter-clockwise, 3: xor(?)
|
||||
|
||||
bool mergeable() override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -234,7 +244,6 @@ struct LottieRoundedCorner : LottieObject
|
||||
void prepare()
|
||||
{
|
||||
LottieObject::type = LottieObject::RoundedCorner;
|
||||
if (radius.frames) statical = false;
|
||||
}
|
||||
LottieFloat radius = 0.0f;
|
||||
};
|
||||
@ -245,7 +254,6 @@ struct LottiePath : LottieShape
|
||||
void prepare()
|
||||
{
|
||||
LottieObject::type = LottieObject::Path;
|
||||
if (pathset.frames) statical = false;
|
||||
}
|
||||
|
||||
LottiePathSet pathset;
|
||||
@ -257,7 +265,6 @@ struct LottieRect : LottieShape
|
||||
void prepare()
|
||||
{
|
||||
LottieObject::type = LottieObject::Rect;
|
||||
if (position.frames || size.frames || radius.frames) statical = false;
|
||||
}
|
||||
|
||||
LottiePosition position = Point{0.0f, 0.0f};
|
||||
@ -273,7 +280,6 @@ struct LottiePolyStar : LottieShape
|
||||
void prepare()
|
||||
{
|
||||
LottieObject::type = LottieObject::Polystar;
|
||||
if (position.frames || innerRadius.frames || outerRadius.frames || innerRoundness.frames || outerRoundness.frames || rotation.frames || ptsCnt.frames) statical = false;
|
||||
}
|
||||
|
||||
LottiePosition position = Point{0.0f, 0.0f};
|
||||
@ -292,7 +298,6 @@ struct LottieEllipse : LottieShape
|
||||
void prepare()
|
||||
{
|
||||
LottieObject::type = LottieObject::Ellipse;
|
||||
if (position.frames || size.frames) statical = false;
|
||||
}
|
||||
|
||||
LottiePosition position = Point{0.0f, 0.0f};
|
||||
@ -323,9 +328,12 @@ struct LottieTransform : LottieObject
|
||||
void prepare()
|
||||
{
|
||||
LottieObject::type = LottieObject::Transform;
|
||||
if (position.frames || rotation.frames || scale.frames || anchor.frames || opacity.frames || (coords && (coords->x.frames || coords->y.frames)) || (rotationEx && (rotationEx->x.frames || rotationEx->y.frames))) {
|
||||
statical = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool mergeable() override
|
||||
{
|
||||
if (!opacity.frames && opacity.value == 255) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
LottiePosition position = Point{0.0f, 0.0f};
|
||||
@ -333,13 +341,15 @@ struct LottieTransform : LottieObject
|
||||
LottiePoint scale = Point{100.0f, 100.0f};
|
||||
LottiePoint anchor = Point{0.0f, 0.0f};
|
||||
LottieOpacity opacity = 255;
|
||||
LottieFloat skewAngle = 0.0f;
|
||||
LottieFloat skewAxis = 0.0f;
|
||||
|
||||
SeparateCoord* coords = nullptr; //either a position or separate coordinates
|
||||
RotationEx* rotationEx = nullptr; //extension for 3d rotation
|
||||
};
|
||||
|
||||
|
||||
struct LottieSolid : LottieObject
|
||||
struct LottieSolid : LottieObject
|
||||
{
|
||||
LottieColor color = RGB24{255, 255, 255};
|
||||
LottieOpacity opacity = 255;
|
||||
@ -351,12 +361,11 @@ struct LottieSolidStroke : LottieSolid, LottieStroke
|
||||
void prepare()
|
||||
{
|
||||
LottieObject::type = LottieObject::SolidStroke;
|
||||
if (color.frames || opacity.frames || LottieStroke::dynamic()) statical = false;
|
||||
}
|
||||
|
||||
void override(LottieObject* prop) override
|
||||
void override(LottieProperty* prop) override
|
||||
{
|
||||
this->color = static_cast<LottieSolid*>(prop)->color;
|
||||
this->color = *static_cast<LottieColor*>(prop);
|
||||
this->prepare();
|
||||
}
|
||||
};
|
||||
@ -367,12 +376,11 @@ struct LottieSolidFill : LottieSolid
|
||||
void prepare()
|
||||
{
|
||||
LottieObject::type = LottieObject::SolidFill;
|
||||
if (color.frames || opacity.frames) statical = false;
|
||||
}
|
||||
|
||||
void override(LottieObject* prop) override
|
||||
void override(LottieProperty* prop) override
|
||||
{
|
||||
this->color = static_cast<LottieSolid*>(prop)->color;
|
||||
this->color = *static_cast<LottieColor*>(prop);
|
||||
this->prepare();
|
||||
}
|
||||
|
||||
@ -382,99 +390,23 @@ struct LottieSolidFill : LottieSolid
|
||||
|
||||
struct LottieGradient : LottieObject
|
||||
{
|
||||
uint32_t populate(ColorStop& color)
|
||||
{
|
||||
uint32_t alphaCnt = (color.input->count - (colorStops.count * 4)) / 2;
|
||||
Array<Fill::ColorStop> output(colorStops.count + alphaCnt);
|
||||
uint32_t cidx = 0; //color count
|
||||
uint32_t clast = colorStops.count * 4;
|
||||
uint32_t aidx = clast; //alpha count
|
||||
Fill::ColorStop cs;
|
||||
|
||||
//merge color stops.
|
||||
for (uint32_t i = 0; i < color.input->count; ++i) {
|
||||
if (cidx == clast || aidx == color.input->count) break;
|
||||
if ((*color.input)[cidx] == (*color.input)[aidx]) {
|
||||
cs.offset = (*color.input)[cidx];
|
||||
cs.r = lroundf((*color.input)[cidx + 1] * 255.0f);
|
||||
cs.g = lroundf((*color.input)[cidx + 2] * 255.0f);
|
||||
cs.b = lroundf((*color.input)[cidx + 3] * 255.0f);
|
||||
cs.a = lroundf((*color.input)[aidx + 1] * 255.0f);
|
||||
cidx += 4;
|
||||
aidx += 2;
|
||||
} else if ((*color.input)[cidx] < (*color.input)[aidx]) {
|
||||
cs.offset = (*color.input)[cidx];
|
||||
cs.r = lroundf((*color.input)[cidx + 1] * 255.0f);
|
||||
cs.g = lroundf((*color.input)[cidx + 2] * 255.0f);
|
||||
cs.b = lroundf((*color.input)[cidx + 3] * 255.0f);
|
||||
//generate alpha value
|
||||
if (output.count > 0) {
|
||||
auto p = ((*color.input)[cidx] - output.last().offset) / ((*color.input)[aidx] - output.last().offset);
|
||||
cs.a = mathLerp<uint8_t>(output.last().a, lroundf((*color.input)[aidx + 1] * 255.0f), p);
|
||||
} else cs.a = 255;
|
||||
cidx += 4;
|
||||
} else {
|
||||
cs.offset = (*color.input)[aidx];
|
||||
cs.a = lroundf((*color.input)[aidx + 1] * 255.0f);
|
||||
//generate color value
|
||||
if (output.count > 0) {
|
||||
auto p = ((*color.input)[aidx] - output.last().offset) / ((*color.input)[cidx] - output.last().offset);
|
||||
cs.r = mathLerp<uint8_t>(output.last().r, lroundf((*color.input)[cidx + 1] * 255.0f), p);
|
||||
cs.g = mathLerp<uint8_t>(output.last().g, lroundf((*color.input)[cidx + 2] * 255.0f), p);
|
||||
cs.b = mathLerp<uint8_t>(output.last().b, lroundf((*color.input)[cidx + 3] * 255.0f), p);
|
||||
} else cs.r = cs.g = cs.b = 255;
|
||||
aidx += 2;
|
||||
}
|
||||
output.push(cs);
|
||||
}
|
||||
|
||||
//color remains
|
||||
while (cidx < clast) {
|
||||
cs.offset = (*color.input)[cidx];
|
||||
cs.r = lroundf((*color.input)[cidx + 1] * 255.0f);
|
||||
cs.g = lroundf((*color.input)[cidx + 2] * 255.0f);
|
||||
cs.b = lroundf((*color.input)[cidx + 3] * 255.0f);
|
||||
cs.a = (output.count > 0) ? output.last().a : 255;
|
||||
output.push(cs);
|
||||
cidx += 4;
|
||||
}
|
||||
|
||||
//alpha remains
|
||||
while (aidx < color.input->count) {
|
||||
cs.offset = (*color.input)[aidx];
|
||||
cs.a = lroundf((*color.input)[aidx + 1] * 255.0f);
|
||||
if (output.count > 0) {
|
||||
cs.r = output.last().r;
|
||||
cs.g = output.last().g;
|
||||
cs.b = output.last().b;
|
||||
} else cs.r = cs.g = cs.b = 255;
|
||||
output.push(cs);
|
||||
aidx += 2;
|
||||
}
|
||||
|
||||
color.data = output.data;
|
||||
output.data = nullptr;
|
||||
|
||||
color.input->reset();
|
||||
delete(color.input);
|
||||
|
||||
return output.count;
|
||||
}
|
||||
|
||||
bool prepare()
|
||||
{
|
||||
if (colorStops.frames) {
|
||||
for (auto v = colorStops.frames->begin(); v < colorStops.frames->end(); ++v) {
|
||||
colorStops.count = populate(v->value);
|
||||
if (!colorStops.populated) {
|
||||
if (colorStops.frames) {
|
||||
for (auto v = colorStops.frames->begin(); v < colorStops.frames->end(); ++v) {
|
||||
colorStops.count = populate(v->value);
|
||||
}
|
||||
} else {
|
||||
colorStops.count = populate(colorStops.value);
|
||||
}
|
||||
} else {
|
||||
colorStops.count = populate(colorStops.value);
|
||||
}
|
||||
if (start.frames || end.frames || height.frames || angle.frames || opacity.frames || colorStops.frames) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
Fill* fill(float frameNo);
|
||||
uint32_t populate(ColorStop& color);
|
||||
Fill* fill(float frameNo, LottieExpressions* exps);
|
||||
|
||||
LottiePoint start = Point{0.0f, 0.0f};
|
||||
LottiePoint end = Point{0.0f, 0.0f};
|
||||
@ -491,12 +423,12 @@ struct LottieGradientFill : LottieGradient
|
||||
void prepare()
|
||||
{
|
||||
LottieObject::type = LottieObject::GradientFill;
|
||||
if (LottieGradient::prepare()) statical = false;
|
||||
LottieGradient::prepare();
|
||||
}
|
||||
|
||||
void override(LottieObject* prop) override
|
||||
void override(LottieProperty* prop) override
|
||||
{
|
||||
this->colorStops = static_cast<LottieGradient*>(prop)->colorStops;
|
||||
this->colorStops = *static_cast<LottieColorStop*>(prop);
|
||||
this->prepare();
|
||||
}
|
||||
|
||||
@ -509,12 +441,12 @@ struct LottieGradientStroke : LottieGradient, LottieStroke
|
||||
void prepare()
|
||||
{
|
||||
LottieObject::type = LottieObject::GradientStroke;
|
||||
if (LottieGradient::prepare() || LottieStroke::dynamic()) statical = false;
|
||||
LottieGradient::prepare();
|
||||
}
|
||||
|
||||
void override(LottieObject* prop) override
|
||||
void override(LottieProperty* prop) override
|
||||
{
|
||||
this->colorStops = static_cast<LottieGradient*>(prop)->colorStops;
|
||||
this->colorStops = *static_cast<LottieColorStop*>(prop);
|
||||
this->prepare();
|
||||
}
|
||||
};
|
||||
@ -545,7 +477,6 @@ struct LottieRepeater : LottieObject
|
||||
void prepare()
|
||||
{
|
||||
LottieObject::type = LottieObject::Repeater;
|
||||
if (copies.frames || offset.frames || position.frames || rotation.frames || scale.frames || anchor.frames || startOpacity.frames || endOpacity.frames) statical = false;
|
||||
}
|
||||
|
||||
LottieFloat copies = 0.0f;
|
||||
@ -570,12 +501,29 @@ struct LottieGroup : LottieObject
|
||||
}
|
||||
|
||||
void prepare(LottieObject::Type type = LottieObject::Group);
|
||||
bool mergeable() override { return allowMerge; }
|
||||
|
||||
LottieObject* content(const char* id)
|
||||
{
|
||||
if (name && !strcmp(name, id)) return this;
|
||||
|
||||
//source has children, find recursively.
|
||||
for (auto c = children.begin(); c < children.end(); ++c) {
|
||||
auto child = *c;
|
||||
if (child->type == LottieObject::Type::Group || child->type == LottieObject::Type::Layer) {
|
||||
if (auto ret = static_cast<LottieGroup*>(child)->content(id)) return ret;
|
||||
} else if (child->name && !strcmp(child->name, id)) return child;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Scene* scene = nullptr; //tvg render data
|
||||
Array<LottieObject*> children;
|
||||
|
||||
bool reqFragment = false; //requirment to fragment the render context
|
||||
bool buildDone = false; //completed in building the composition.
|
||||
bool allowMerge = true; //if this group is consisted of simple (transformed) shapes.
|
||||
bool trimpath = false; //this group has a trimpath.
|
||||
};
|
||||
|
||||
|
||||
@ -592,8 +540,10 @@ struct LottieLayer : LottieGroup
|
||||
return transform->opacity(frameNo);
|
||||
}
|
||||
|
||||
bool mergeable() override { return false; }
|
||||
|
||||
void prepare();
|
||||
float remap(float frameNo);
|
||||
float remap(float frameNo, LottieExpressions* exp);
|
||||
|
||||
struct {
|
||||
CompositeMethod type = CompositeMethod::None;
|
||||
@ -609,7 +559,7 @@ struct LottieLayer : LottieGroup
|
||||
RGB24 color; //used by Solid layer
|
||||
|
||||
float timeStretch = 1.0f;
|
||||
uint32_t w = 0, h = 0;
|
||||
float w = 0.0f, h = 0.0f;
|
||||
float inFrame = 0.0f;
|
||||
float outFrame = 0.0f;
|
||||
float startFrame = 0.0f;
|
||||
@ -632,19 +582,32 @@ struct LottieLayer : LottieGroup
|
||||
|
||||
struct LottieSlot
|
||||
{
|
||||
char* sid;
|
||||
Array<LottieObject*> objs;
|
||||
LottieProperty::Type type;
|
||||
struct Pair {
|
||||
LottieObject* obj;
|
||||
LottieProperty* prop;
|
||||
};
|
||||
|
||||
void assign(LottieObject* target);
|
||||
void reset();
|
||||
|
||||
LottieSlot(char* sid, LottieObject* obj, LottieProperty::Type type) : sid(sid), type(type)
|
||||
{
|
||||
objs.push(obj);
|
||||
pairs.push({obj, 0});
|
||||
}
|
||||
|
||||
~LottieSlot()
|
||||
{
|
||||
free(sid);
|
||||
if (!overriden) return;
|
||||
for (auto pair = pairs.begin(); pair < pairs.end(); ++pair) {
|
||||
delete(pair->prop);
|
||||
}
|
||||
}
|
||||
|
||||
char* sid;
|
||||
Array<Pair> pairs;
|
||||
LottieProperty::Type type;
|
||||
bool overriden = false;
|
||||
};
|
||||
|
||||
|
||||
@ -664,21 +627,55 @@ struct LottieComposition
|
||||
return p * frameCnt();
|
||||
}
|
||||
|
||||
float timeAtFrame(float frameNo)
|
||||
{
|
||||
return (frameNo - startFrame) / frameRate;
|
||||
}
|
||||
|
||||
float frameCnt() const
|
||||
{
|
||||
return endFrame - startFrame;
|
||||
}
|
||||
|
||||
LottieLayer* layer(const char* name)
|
||||
{
|
||||
for (auto child = root->children.begin(); child < root->children.end(); ++child) {
|
||||
auto layer = static_cast<LottieLayer*>(*child);
|
||||
if (layer->name && !strcmp(layer->name, name)) return layer;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
LottieLayer* layer(int16_t id)
|
||||
{
|
||||
for (auto child = root->children.begin(); child < root->children.end(); ++child) {
|
||||
auto layer = static_cast<LottieLayer*>(*child);
|
||||
if (layer->id == id) return layer;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
LottieLayer* asset(const char* name)
|
||||
{
|
||||
for (auto asset = assets.begin(); asset < assets.end(); ++asset) {
|
||||
auto layer = static_cast<LottieLayer*>(*asset);
|
||||
if (layer->name && !strcmp(layer->name, name)) return layer;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
LottieLayer* root = nullptr;
|
||||
char* version = nullptr;
|
||||
char* name = nullptr;
|
||||
uint32_t w, h;
|
||||
float w, h;
|
||||
float startFrame, endFrame;
|
||||
float frameRate;
|
||||
Array<LottieObject*> assets;
|
||||
Array<LottieInterpolator*> interpolators;
|
||||
Array<LottieFont*> fonts;
|
||||
Array<LottieSlot*> slots;
|
||||
Array<LottieMarker*> markers;
|
||||
bool expressions = false;
|
||||
bool initiated = false;
|
||||
};
|
||||
|
||||
|
@ -27,12 +27,31 @@
|
||||
#include "tvgCompressor.h"
|
||||
#include "tvgLottieModel.h"
|
||||
#include "tvgLottieParser.h"
|
||||
#include "tvgLottieExpressions.h"
|
||||
|
||||
|
||||
/************************************************************************/
|
||||
/* Internal Class Implementation */
|
||||
/************************************************************************/
|
||||
|
||||
#define KEY_AS(name) !strcmp(key, name)
|
||||
|
||||
|
||||
static LottieExpression* _expression(char* code, LottieComposition* comp, LottieLayer* layer, LottieObject* object, LottieProperty* property, LottieProperty::Type type)
|
||||
{
|
||||
if (!comp->expressions) comp->expressions = true;
|
||||
|
||||
auto inst = new LottieExpression;
|
||||
inst->code = code;
|
||||
inst->comp = comp;
|
||||
inst->layer = layer;
|
||||
inst->object = object;
|
||||
inst->property = property;
|
||||
inst->type = type;
|
||||
inst->enabled = true;
|
||||
|
||||
return inst;
|
||||
}
|
||||
|
||||
|
||||
static char* _int2str(int num)
|
||||
@ -45,7 +64,10 @@ static char* _int2str(int num)
|
||||
|
||||
CompositeMethod LottieParser::getMaskMethod(bool inversed)
|
||||
{
|
||||
switch (getString()[0]) {
|
||||
auto mode = getString();
|
||||
if (!mode) return CompositeMethod::None;
|
||||
|
||||
switch (mode[0]) {
|
||||
case 'a': {
|
||||
if (inversed) return CompositeMethod::InvAlphaMask;
|
||||
else return CompositeMethod::AddMask;
|
||||
@ -160,19 +182,19 @@ void LottieParser::getValue(TextDocument& doc)
|
||||
{
|
||||
enterObject();
|
||||
while (auto key = nextObjectKey()) {
|
||||
if (!strcmp(key, "s")) doc.size = getFloat();
|
||||
else if (!strcmp(key, "f")) doc.name = getStringCopy();
|
||||
else if (!strcmp(key, "t")) doc.text = getStringCopy();
|
||||
else if (!strcmp(key, "j")) doc.justify = getInt();
|
||||
else if (!strcmp(key, "tr")) doc.tracking = getInt();
|
||||
else if (!strcmp(key, "lh")) doc.height = getFloat();
|
||||
else if (!strcmp(key, "ls")) doc.shift = getFloat();
|
||||
else if (!strcmp(key, "fc")) getValue(doc.color);
|
||||
else if (!strcmp(key, "ps")) getValue(doc.bbox.pos);
|
||||
else if (!strcmp(key, "sz")) getValue(doc.bbox.size);
|
||||
else if (!strcmp(key, "sc")) getValue(doc.stroke.color);
|
||||
else if (!strcmp(key, "sw")) doc.stroke.width = getFloat();
|
||||
else if (!strcmp(key, "of")) doc.stroke.render = getBool();
|
||||
if (KEY_AS("s")) doc.size = getFloat();
|
||||
else if (KEY_AS("f")) doc.name = getStringCopy();
|
||||
else if (KEY_AS("t")) doc.text = getStringCopy();
|
||||
else if (KEY_AS("j")) doc.justify = getInt();
|
||||
else if (KEY_AS("tr")) doc.tracking = getFloat() * 0.1f;
|
||||
else if (KEY_AS("lh")) doc.height = getFloat();
|
||||
else if (KEY_AS("ls")) doc.shift = getFloat();
|
||||
else if (KEY_AS("fc")) getValue(doc.color);
|
||||
else if (KEY_AS("ps")) getValue(doc.bbox.pos);
|
||||
else if (KEY_AS("sz")) getValue(doc.bbox.size);
|
||||
else if (KEY_AS("sc")) getValue(doc.stroke.color);
|
||||
else if (KEY_AS("sw")) doc.stroke.width = getFloat();
|
||||
else if (KEY_AS("of")) doc.stroke.render = getBool();
|
||||
else skip(key);
|
||||
}
|
||||
}
|
||||
@ -190,10 +212,10 @@ void LottieParser::getValue(PathSet& path)
|
||||
|
||||
enterObject();
|
||||
while (auto key = nextObjectKey()) {
|
||||
if (!strcmp(key, "i")) getValue(ins);
|
||||
else if (!strcmp(key, "o")) getValue(outs);
|
||||
else if (!strcmp(key, "v")) getValue(pts);
|
||||
else if (!strcmp(key, "c")) closed = getBool();
|
||||
if (KEY_AS("i")) getValue(ins);
|
||||
else if (KEY_AS("o")) getValue(outs);
|
||||
else if (KEY_AS("v")) getValue(pts);
|
||||
else if (KEY_AS("c")) closed = getBool();
|
||||
else skip(key);
|
||||
}
|
||||
|
||||
@ -255,7 +277,7 @@ void LottieParser::getValue(ColorStop& color)
|
||||
{
|
||||
if (peekType() == kArrayType) enterArray();
|
||||
|
||||
color.input = new Array<float>(context->gradient->colorStops.count);
|
||||
color.input = new Array<float>(static_cast<LottieGradient*>(context.parent)->colorStops.count);
|
||||
|
||||
while (nextArrayValue()) color.input->push(getFloat());
|
||||
}
|
||||
@ -332,17 +354,17 @@ void LottieParser::getInperpolatorPoint(Point& pt)
|
||||
{
|
||||
enterObject();
|
||||
while (auto key = nextObjectKey()) {
|
||||
if (!strcmp(key, "x")) getValue(pt.x);
|
||||
else if (!strcmp(key, "y")) getValue(pt.y);
|
||||
if (KEY_AS("x")) getValue(pt.x);
|
||||
else if (KEY_AS("y")) getValue(pt.y);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template<typename T>
|
||||
template<LottieProperty::Type type, typename T>
|
||||
void LottieParser::parseSlotProperty(T& prop)
|
||||
{
|
||||
while (auto key = nextObjectKey()) {
|
||||
if (!strcmp(key, "p")) parseProperty(prop);
|
||||
if (KEY_AS("p")) parseProperty<type>(prop);
|
||||
else skip(key);
|
||||
}
|
||||
}
|
||||
@ -351,10 +373,10 @@ void LottieParser::parseSlotProperty(T& prop)
|
||||
template<typename T>
|
||||
bool LottieParser::parseTangent(const char *key, LottieVectorFrame<T>& value)
|
||||
{
|
||||
if (!strcmp(key, "ti")) {
|
||||
if (KEY_AS("ti")) {
|
||||
value.hasTangent = true;
|
||||
getValue(value.inTangent);
|
||||
} else if (!strcmp(key, "to")) {
|
||||
} else if (KEY_AS("to")) {
|
||||
value.hasTangent = true;
|
||||
getValue(value.outTangent);
|
||||
} else return false;
|
||||
@ -408,12 +430,12 @@ void LottieParser::parseKeyFrame(T& prop)
|
||||
enterObject();
|
||||
|
||||
while (auto key = nextObjectKey()) {
|
||||
if (!strcmp(key, "i")) {
|
||||
if (KEY_AS("i")) {
|
||||
interpolator = true;
|
||||
getInperpolatorPoint(inTangent);
|
||||
} else if (!strcmp(key, "o")) {
|
||||
} else if (KEY_AS("o")) {
|
||||
getInperpolatorPoint(outTangent);
|
||||
} else if (!strcmp(key, "n")) {
|
||||
} else if (KEY_AS("n")) {
|
||||
if (peekType() == kStringType) {
|
||||
interpolatorKey = getString();
|
||||
} else {
|
||||
@ -423,18 +445,18 @@ void LottieParser::parseKeyFrame(T& prop)
|
||||
else skip(nullptr);
|
||||
}
|
||||
}
|
||||
} else if (!strcmp(key, "t")) {
|
||||
} else if (KEY_AS("t")) {
|
||||
frame.no = getFloat();
|
||||
} else if (!strcmp(key, "s")) {
|
||||
} else if (KEY_AS("s")) {
|
||||
getValue(frame.value);
|
||||
} else if (!strcmp(key, "e")) {
|
||||
} else if (KEY_AS("e")) {
|
||||
//current end frame and the next start frame is duplicated,
|
||||
//We propagate the end value to the next frame to avoid having duplicated values.
|
||||
auto& frame2 = prop.nextFrame();
|
||||
getValue(frame2.value);
|
||||
} else if (parseTangent(key, frame)) {
|
||||
continue;
|
||||
} else if (!strcmp(key, "h")) {
|
||||
} else if (KEY_AS("h")) {
|
||||
frame.hold = getInt();
|
||||
} else skip(key);
|
||||
}
|
||||
@ -475,17 +497,20 @@ void LottieParser::parseProperty(T& prop, LottieObject* obj)
|
||||
{
|
||||
enterObject();
|
||||
while (auto key = nextObjectKey()) {
|
||||
if (!strcmp(key, "k")) parsePropertyInternal(prop);
|
||||
else if (obj && !strcmp(key, "sid")) {
|
||||
if (KEY_AS("k")) parsePropertyInternal(prop);
|
||||
else if (obj && KEY_AS("sid")) {
|
||||
auto sid = getStringCopy();
|
||||
//append object if the slot already exists.
|
||||
for (auto slot = comp->slots.begin(); slot < comp->slots.end(); ++slot) {
|
||||
if (strcmp((*slot)->sid, sid)) continue;
|
||||
(*slot)->objs.push(obj);
|
||||
(*slot)->pairs.push({obj, 0});
|
||||
return;
|
||||
}
|
||||
comp->slots.push(new LottieSlot(sid, obj, type));
|
||||
} else skip(key);
|
||||
} else if (!strcmp(key, "x")) {
|
||||
prop.exp = _expression(getStringCopy(), comp, context.layer, context.parent, &prop, type);
|
||||
}
|
||||
else skip(key);
|
||||
}
|
||||
}
|
||||
|
||||
@ -495,12 +520,14 @@ LottieRect* LottieParser::parseRect()
|
||||
auto rect = new LottieRect;
|
||||
if (!rect) return nullptr;
|
||||
|
||||
context.parent = rect;
|
||||
|
||||
while (auto key = nextObjectKey()) {
|
||||
if (!strcmp(key, "s")) parseProperty(rect->size);
|
||||
else if (!strcmp(key, "p")) parseProperty(rect->position);
|
||||
else if (!strcmp(key, "r")) parseProperty(rect->radius);
|
||||
else if (!strcmp(key, "nm")) rect->name = getStringCopy();
|
||||
else if (!strcmp(key, "hd")) rect->hidden = getBool();
|
||||
if (KEY_AS("s")) parseProperty<LottieProperty::Type::Point>(rect->size);
|
||||
else if (KEY_AS("p"))parseProperty<LottieProperty::Type::Position>(rect->position);
|
||||
else if (KEY_AS("r")) parseProperty<LottieProperty::Type::Float>(rect->radius);
|
||||
else if (KEY_AS("nm")) rect->name = getStringCopy();
|
||||
else if (KEY_AS("hd")) rect->hidden = getBool();
|
||||
else skip(key);
|
||||
}
|
||||
rect->prepare();
|
||||
@ -513,11 +540,13 @@ LottieEllipse* LottieParser::parseEllipse()
|
||||
auto ellipse = new LottieEllipse;
|
||||
if (!ellipse) return nullptr;
|
||||
|
||||
context.parent = ellipse;
|
||||
|
||||
while (auto key = nextObjectKey()) {
|
||||
if (!strcmp(key, "nm")) ellipse->name = getStringCopy();
|
||||
else if (!strcmp(key, "p")) parseProperty(ellipse->position);
|
||||
else if (!strcmp(key, "s")) parseProperty(ellipse->size);
|
||||
else if (!strcmp(key, "hd")) ellipse->hidden = getBool();
|
||||
if (KEY_AS("nm")) ellipse->name = getStringCopy();
|
||||
else if (KEY_AS("p")) parseProperty<LottieProperty::Type::Position>(ellipse->position);
|
||||
else if (KEY_AS("s")) parseProperty<LottieProperty::Type::Point>(ellipse->size);
|
||||
else if (KEY_AS("hd")) ellipse->hidden = getBool();
|
||||
else skip(key);
|
||||
}
|
||||
ellipse->prepare();
|
||||
@ -530,37 +559,37 @@ LottieTransform* LottieParser::parseTransform(bool ddd)
|
||||
auto transform = new LottieTransform;
|
||||
if (!transform) return nullptr;
|
||||
|
||||
context.parent = transform;
|
||||
|
||||
if (ddd) {
|
||||
transform->rotationEx = new LottieTransform::RotationEx;
|
||||
TVGLOG("LOTTIE", "3D transform(ddd) is not totally compatible.");
|
||||
}
|
||||
|
||||
while (auto key = nextObjectKey()) {
|
||||
if (!strcmp(key, "p"))
|
||||
if (KEY_AS("p"))
|
||||
{
|
||||
enterObject();
|
||||
while (auto key = nextObjectKey()) {
|
||||
if (!strcmp(key, "k")) parsePropertyInternal(transform->position);
|
||||
else if (!strcmp(key, "s")) {
|
||||
if (getBool()) transform->coords = new LottieTransform::SeparateCoord;
|
||||
if (KEY_AS("k")) parsePropertyInternal(transform->position);
|
||||
else if (KEY_AS("s") && getBool()) transform->coords = new LottieTransform::SeparateCoord;
|
||||
//check separateCoord to figure out whether "x(expression)" / "x(coord)"
|
||||
} else if (transform->coords && !strcmp(key, "x")) {
|
||||
parseProperty(transform->coords->x);
|
||||
} else if (transform->coords && !strcmp(key, "y")) {
|
||||
parseProperty(transform->coords->y);
|
||||
} else skip(key);
|
||||
else if (transform->coords && KEY_AS("x")) parseProperty<LottieProperty::Type::Float>(transform->coords->x);
|
||||
else if (transform->coords && KEY_AS("y")) parseProperty<LottieProperty::Type::Float>(transform->coords->y);
|
||||
else if (KEY_AS("x")) transform->position.exp = _expression(getStringCopy(), comp, context.layer, context.parent, &transform->position, LottieProperty::Type::Position);
|
||||
else skip(key);
|
||||
}
|
||||
}
|
||||
else if (!strcmp(key, "a")) parseProperty(transform->anchor);
|
||||
else if (!strcmp(key, "s")) parseProperty(transform->scale);
|
||||
else if (!strcmp(key, "r")) parseProperty(transform->rotation);
|
||||
else if (!strcmp(key, "o")) parseProperty(transform->opacity);
|
||||
else if (transform->rotationEx && !strcmp(key, "rx")) parseProperty(transform->rotationEx->x);
|
||||
else if (transform->rotationEx && !strcmp(key, "ry")) parseProperty(transform->rotationEx->y);
|
||||
else if (transform->rotationEx && !strcmp(key, "rz")) parseProperty(transform->rotation);
|
||||
else if (!strcmp(key, "nm")) transform->name = getStringCopy();
|
||||
//else if (!strcmp(key, "sk")) //TODO: skew
|
||||
//else if (!strcmp(key, "sa")) //TODO: skew axis
|
||||
else if (KEY_AS("a")) parseProperty<LottieProperty::Type::Point>(transform->anchor);
|
||||
else if (KEY_AS("s")) parseProperty<LottieProperty::Type::Point>(transform->scale);
|
||||
else if (KEY_AS("r")) parseProperty<LottieProperty::Type::Float>(transform->rotation);
|
||||
else if (KEY_AS("o")) parseProperty<LottieProperty::Type::Opacity>(transform->opacity);
|
||||
else if (transform->rotationEx && KEY_AS("rx")) parseProperty<LottieProperty::Type::Float>(transform->rotationEx->x);
|
||||
else if (transform->rotationEx && KEY_AS("ry")) parseProperty<LottieProperty::Type::Float>(transform->rotationEx->y);
|
||||
else if (transform->rotationEx && KEY_AS("rz")) parseProperty<LottieProperty::Type::Float>(transform->rotation);
|
||||
else if (KEY_AS("nm")) transform->name = getStringCopy();
|
||||
else if (KEY_AS("sk")) parseProperty<LottieProperty::Type::Float>(transform->skewAngle);
|
||||
else if (KEY_AS("sa")) parseProperty<LottieProperty::Type::Float>(transform->skewAxis);
|
||||
else skip(key);
|
||||
}
|
||||
transform->prepare();
|
||||
@ -573,13 +602,15 @@ LottieSolidFill* LottieParser::parseSolidFill()
|
||||
auto fill = new LottieSolidFill;
|
||||
if (!fill) return nullptr;
|
||||
|
||||
context.parent = fill;
|
||||
|
||||
while (auto key = nextObjectKey()) {
|
||||
if (!strcmp(key, "nm")) fill->name = getStringCopy();
|
||||
else if (!strcmp(key, "c")) parseProperty<LottieProperty::Type::Color>(fill->color, fill);
|
||||
else if (!strcmp(key, "o")) parseProperty<LottieProperty::Type::Opacity>(fill->opacity, fill);
|
||||
else if (!strcmp(key, "fillEnabled")) fill->hidden |= !getBool();
|
||||
else if (!strcmp(key, "r")) fill->rule = getFillRule();
|
||||
else if (!strcmp(key, "hd")) fill->hidden = getBool();
|
||||
if (KEY_AS("nm")) fill->name = getStringCopy();
|
||||
else if (KEY_AS("c")) parseProperty<LottieProperty::Type::Color>(fill->color, fill);
|
||||
else if (KEY_AS("o")) parseProperty<LottieProperty::Type::Opacity>(fill->opacity, fill);
|
||||
else if (KEY_AS("fillEnabled")) fill->hidden |= !getBool();
|
||||
else if (KEY_AS("r")) fill->rule = getFillRule();
|
||||
else if (KEY_AS("hd")) fill->hidden = getBool();
|
||||
else skip(key);
|
||||
}
|
||||
fill->prepare();
|
||||
@ -594,13 +625,13 @@ void LottieParser::parseStrokeDash(LottieStroke* stroke)
|
||||
enterObject();
|
||||
int idx = 0;
|
||||
while (auto key = nextObjectKey()) {
|
||||
if (!strcmp(key, "n")) {
|
||||
if (KEY_AS("n")) {
|
||||
auto style = getString();
|
||||
if (!strcmp("o", style)) idx = 0; //offset
|
||||
else if (!strcmp("d", style)) idx = 1; //dash
|
||||
else if (!strcmp("g", style)) idx = 2; //gap
|
||||
} else if (!strcmp(key, "v")) {
|
||||
parseProperty(stroke->dash(idx));
|
||||
} else if (KEY_AS("v")) {
|
||||
parseProperty<LottieProperty::Type::Float>(stroke->dash(idx));
|
||||
} else skip(key);
|
||||
}
|
||||
}
|
||||
@ -612,17 +643,19 @@ LottieSolidStroke* LottieParser::parseSolidStroke()
|
||||
auto stroke = new LottieSolidStroke;
|
||||
if (!stroke) return nullptr;
|
||||
|
||||
context.parent = stroke;
|
||||
|
||||
while (auto key = nextObjectKey()) {
|
||||
if (!strcmp(key, "c")) parseProperty<LottieProperty::Type::Color>(stroke->color, stroke);
|
||||
else if (!strcmp(key, "o")) parseProperty<LottieProperty::Type::Opacity>(stroke->opacity, stroke);
|
||||
else if (!strcmp(key, "w")) parseProperty<LottieProperty::Type::Float>(stroke->width, stroke);
|
||||
else if (!strcmp(key, "lc")) stroke->cap = getStrokeCap();
|
||||
else if (!strcmp(key, "lj")) stroke->join = getStrokeJoin();
|
||||
else if (!strcmp(key, "ml")) stroke->miterLimit = getFloat();
|
||||
else if (!strcmp(key, "nm")) stroke->name = getStringCopy();
|
||||
else if (!strcmp(key, "hd")) stroke->hidden = getBool();
|
||||
else if (!strcmp(key, "fillEnabled")) stroke->hidden |= !getBool();
|
||||
else if (!strcmp(key, "d")) parseStrokeDash(stroke);
|
||||
if (KEY_AS("c")) parseProperty<LottieProperty::Type::Color>(stroke->color, stroke);
|
||||
else if (KEY_AS("o")) parseProperty<LottieProperty::Type::Opacity>(stroke->opacity, stroke);
|
||||
else if (KEY_AS("w")) parseProperty<LottieProperty::Type::Float>(stroke->width, stroke);
|
||||
else if (KEY_AS("lc")) stroke->cap = getStrokeCap();
|
||||
else if (KEY_AS("lj")) stroke->join = getStrokeJoin();
|
||||
else if (KEY_AS("ml")) stroke->miterLimit = getFloat();
|
||||
else if (KEY_AS("nm")) stroke->name = getStringCopy();
|
||||
else if (KEY_AS("hd")) stroke->hidden = getBool();
|
||||
else if (KEY_AS("fillEnabled")) stroke->hidden |= !getBool();
|
||||
else if (KEY_AS("d")) parseStrokeDash(stroke);
|
||||
else skip(key);
|
||||
}
|
||||
stroke->prepare();
|
||||
@ -630,17 +663,19 @@ LottieSolidStroke* LottieParser::parseSolidStroke()
|
||||
}
|
||||
|
||||
|
||||
void LottieParser::getPathSet(LottiePathSet& path)
|
||||
void LottieParser::getPathSet(LottiePathSet& path)
|
||||
{
|
||||
enterObject();
|
||||
while (auto key = nextObjectKey()) {
|
||||
if (!strcmp(key, "k")) {
|
||||
if (KEY_AS("k")) {
|
||||
if (peekType() == kArrayType) {
|
||||
enterArray();
|
||||
while (nextArrayValue()) parseKeyFrame(path);
|
||||
} else {
|
||||
getValue(path.value);
|
||||
}
|
||||
} else if (!strcmp(key, "x")) {
|
||||
path.exp = _expression(getStringCopy(), comp, context.layer, context.parent, &path, LottieProperty::Type::PathSet);
|
||||
} else skip(key);
|
||||
}
|
||||
}
|
||||
@ -652,9 +687,9 @@ LottiePath* LottieParser::parsePath()
|
||||
if (!path) return nullptr;
|
||||
|
||||
while (auto key = nextObjectKey()) {
|
||||
if (!strcmp(key, "nm")) path->name = getStringCopy();
|
||||
else if (!strcmp(key, "ks")) getPathSet(path->pathset);
|
||||
else if (!strcmp(key, "hd")) path->hidden = getBool();
|
||||
if (KEY_AS("nm")) path->name = getStringCopy();
|
||||
else if (KEY_AS("ks")) getPathSet(path->pathset);
|
||||
else if (KEY_AS("hd")) path->hidden = getBool();
|
||||
else skip(key);
|
||||
}
|
||||
path->prepare();
|
||||
@ -667,17 +702,19 @@ LottiePolyStar* LottieParser::parsePolyStar()
|
||||
auto star = new LottiePolyStar;
|
||||
if (!star) return nullptr;
|
||||
|
||||
context.parent = star;
|
||||
|
||||
while (auto key = nextObjectKey()) {
|
||||
if (!strcmp(key, "nm")) star->name = getStringCopy();
|
||||
else if (!strcmp(key, "p")) parseProperty(star->position);
|
||||
else if (!strcmp(key, "pt")) parseProperty(star->ptsCnt);
|
||||
else if (!strcmp(key, "ir")) parseProperty(star->innerRadius);
|
||||
else if (!strcmp(key, "is")) parseProperty(star->innerRoundness);
|
||||
else if (!strcmp(key, "or")) parseProperty(star->outerRadius);
|
||||
else if (!strcmp(key, "os")) parseProperty(star->outerRoundness);
|
||||
else if (!strcmp(key, "r")) parseProperty(star->rotation);
|
||||
else if (!strcmp(key, "sy")) star->type = (LottiePolyStar::Type) getInt();
|
||||
else if (!strcmp(key, "hd")) star->hidden = getBool();
|
||||
if (KEY_AS("nm")) star->name = getStringCopy();
|
||||
else if (KEY_AS("p")) parseProperty<LottieProperty::Type::Position>(star->position);
|
||||
else if (KEY_AS("pt")) parseProperty<LottieProperty::Type::Float>(star->ptsCnt);
|
||||
else if (KEY_AS("ir")) parseProperty<LottieProperty::Type::Float>(star->innerRadius);
|
||||
else if (KEY_AS("is")) parseProperty<LottieProperty::Type::Float>(star->innerRoundness);
|
||||
else if (KEY_AS("or")) parseProperty<LottieProperty::Type::Float>(star->outerRadius);
|
||||
else if (KEY_AS("os")) parseProperty<LottieProperty::Type::Float>(star->outerRoundness);
|
||||
else if (KEY_AS("r")) parseProperty<LottieProperty::Type::Float>(star->rotation);
|
||||
else if (KEY_AS("sy")) star->type = (LottiePolyStar::Type) getInt();
|
||||
else if (KEY_AS("hd")) star->hidden = getBool();
|
||||
else skip(key);
|
||||
}
|
||||
star->prepare();
|
||||
@ -690,10 +727,12 @@ LottieRoundedCorner* LottieParser::parseRoundedCorner()
|
||||
auto corner = new LottieRoundedCorner;
|
||||
if (!corner) return nullptr;
|
||||
|
||||
context.parent = corner;
|
||||
|
||||
while (auto key = nextObjectKey()) {
|
||||
if (!strcmp(key, "nm")) corner->name = getStringCopy();
|
||||
else if (!strcmp(key, "r")) parseProperty(corner->radius);
|
||||
else if (!strcmp(key, "hd")) corner->hidden = getBool();
|
||||
if (KEY_AS("nm")) corner->name = getStringCopy();
|
||||
else if (KEY_AS("r")) parseProperty<LottieProperty::Type::Float>(corner->radius);
|
||||
else if (KEY_AS("hd")) corner->hidden = getBool();
|
||||
else skip(key);
|
||||
}
|
||||
corner->prepare();
|
||||
@ -703,23 +742,21 @@ LottieRoundedCorner* LottieParser::parseRoundedCorner()
|
||||
|
||||
void LottieParser::parseGradient(LottieGradient* gradient, const char* key)
|
||||
{
|
||||
context->gradient = gradient;
|
||||
|
||||
if (!strcmp(key, "t")) gradient->id = getInt();
|
||||
else if (!strcmp(key, "o")) parseProperty<LottieProperty::Type::Opacity>(gradient->opacity, gradient);
|
||||
else if (!strcmp(key, "g"))
|
||||
if (KEY_AS("t")) gradient->id = getInt();
|
||||
else if (KEY_AS("o")) parseProperty<LottieProperty::Type::Opacity>(gradient->opacity, gradient);
|
||||
else if (KEY_AS("g"))
|
||||
{
|
||||
enterObject();
|
||||
while (auto key = nextObjectKey()) {
|
||||
if (!strcmp(key, "p")) gradient->colorStops.count = getInt();
|
||||
else if (!strcmp(key, "k")) parseProperty<LottieProperty::Type::ColorStop>(gradient->colorStops, gradient);
|
||||
if (KEY_AS("p")) gradient->colorStops.count = getInt();
|
||||
else if (KEY_AS("k")) parseProperty<LottieProperty::Type::ColorStop>(gradient->colorStops, gradient);
|
||||
else skip(key);
|
||||
}
|
||||
}
|
||||
else if (!strcmp(key, "s")) parseProperty<LottieProperty::Type::Point>(gradient->start, gradient);
|
||||
else if (!strcmp(key, "e")) parseProperty<LottieProperty::Type::Point>(gradient->end, gradient);
|
||||
else if (!strcmp(key, "h")) parseProperty<LottieProperty::Type::Float>(gradient->height, gradient);
|
||||
else if (!strcmp(key, "a")) parseProperty<LottieProperty::Type::Float>(gradient->angle, gradient);
|
||||
else if (KEY_AS("s")) parseProperty<LottieProperty::Type::Point>(gradient->start, gradient);
|
||||
else if (KEY_AS("e")) parseProperty<LottieProperty::Type::Point>(gradient->end, gradient);
|
||||
else if (KEY_AS("h")) parseProperty<LottieProperty::Type::Float>(gradient->height, gradient);
|
||||
else if (KEY_AS("a")) parseProperty<LottieProperty::Type::Float>(gradient->angle, gradient);
|
||||
else skip(key);
|
||||
}
|
||||
|
||||
@ -729,10 +766,12 @@ LottieGradientFill* LottieParser::parseGradientFill()
|
||||
auto fill = new LottieGradientFill;
|
||||
if (!fill) return nullptr;
|
||||
|
||||
context.parent = fill;
|
||||
|
||||
while (auto key = nextObjectKey()) {
|
||||
if (!strcmp(key, "nm")) fill->name = getStringCopy();
|
||||
else if (!strcmp(key, "r")) fill->rule = getFillRule();
|
||||
else if (!strcmp(key, "hd")) fill->hidden = getBool();
|
||||
if (KEY_AS("nm")) fill->name = getStringCopy();
|
||||
else if (KEY_AS("r")) fill->rule = getFillRule();
|
||||
else if (KEY_AS("hd")) fill->hidden = getBool();
|
||||
else parseGradient(fill, key);
|
||||
}
|
||||
|
||||
@ -747,14 +786,16 @@ LottieGradientStroke* LottieParser::parseGradientStroke()
|
||||
auto stroke = new LottieGradientStroke;
|
||||
if (!stroke) return nullptr;
|
||||
|
||||
context.parent = stroke;
|
||||
|
||||
while (auto key = nextObjectKey()) {
|
||||
if (!strcmp(key, "nm")) stroke->name = getStringCopy();
|
||||
else if (!strcmp(key, "lc")) stroke->cap = getStrokeCap();
|
||||
else if (!strcmp(key, "lj")) stroke->join = getStrokeJoin();
|
||||
else if (!strcmp(key, "ml")) stroke->miterLimit = getFloat();
|
||||
else if (!strcmp(key, "hd")) stroke->hidden = getBool();
|
||||
else if (!strcmp(key, "w")) parseProperty(stroke->width);
|
||||
else if (!strcmp(key, "d")) parseStrokeDash(stroke);
|
||||
if (KEY_AS("nm")) stroke->name = getStringCopy();
|
||||
else if (KEY_AS("lc")) stroke->cap = getStrokeCap();
|
||||
else if (KEY_AS("lj")) stroke->join = getStrokeJoin();
|
||||
else if (KEY_AS("ml")) stroke->miterLimit = getFloat();
|
||||
else if (KEY_AS("hd")) stroke->hidden = getBool();
|
||||
else if (KEY_AS("w")) parseProperty<LottieProperty::Type::Float>(stroke->width);
|
||||
else if (KEY_AS("d")) parseStrokeDash(stroke);
|
||||
else parseGradient(stroke, key);
|
||||
}
|
||||
stroke->prepare();
|
||||
@ -768,13 +809,15 @@ LottieTrimpath* LottieParser::parseTrimpath()
|
||||
auto trim = new LottieTrimpath;
|
||||
if (!trim) return nullptr;
|
||||
|
||||
context.parent = trim;
|
||||
|
||||
while (auto key = nextObjectKey()) {
|
||||
if (!strcmp(key, "nm")) trim->name = getStringCopy();
|
||||
else if (!strcmp(key, "s")) parseProperty(trim->start);
|
||||
else if (!strcmp(key, "e")) parseProperty(trim->end);
|
||||
else if (!strcmp(key, "o")) parseProperty(trim->offset);
|
||||
else if (!strcmp(key, "m")) trim->type = static_cast<LottieTrimpath::Type>(getInt());
|
||||
else if (!strcmp(key, "hd")) trim->hidden = getBool();
|
||||
if (KEY_AS("nm")) trim->name = getStringCopy();
|
||||
else if (KEY_AS("s")) parseProperty<LottieProperty::Type::Float>(trim->start);
|
||||
else if (KEY_AS("e")) parseProperty<LottieProperty::Type::Float>(trim->end);
|
||||
else if (KEY_AS("o")) parseProperty<LottieProperty::Type::Float>(trim->offset);
|
||||
else if (KEY_AS("m")) trim->type = static_cast<LottieTrimpath::Type>(getInt());
|
||||
else if (KEY_AS("hd")) trim->hidden = getBool();
|
||||
else skip(key);
|
||||
}
|
||||
trim->prepare();
|
||||
@ -788,25 +831,27 @@ LottieRepeater* LottieParser::parseRepeater()
|
||||
auto repeater = new LottieRepeater;
|
||||
if (!repeater) return nullptr;
|
||||
|
||||
context.parent = repeater;
|
||||
|
||||
while (auto key = nextObjectKey()) {
|
||||
if (!strcmp(key, "nm")) repeater->name = getStringCopy();
|
||||
else if (!strcmp(key, "c")) parseProperty(repeater->copies);
|
||||
else if (!strcmp(key, "o")) parseProperty(repeater->offset);
|
||||
else if (!strcmp(key, "m")) repeater->inorder = getInt();
|
||||
else if (!strcmp(key, "tr"))
|
||||
if (KEY_AS("nm")) repeater->name = getStringCopy();
|
||||
else if (KEY_AS("c")) parseProperty<LottieProperty::Type::Float>(repeater->copies);
|
||||
else if (KEY_AS("o")) parseProperty<LottieProperty::Type::Float>(repeater->offset);
|
||||
else if (KEY_AS("m")) repeater->inorder = getInt();
|
||||
else if (KEY_AS("tr"))
|
||||
{
|
||||
enterObject();
|
||||
while (auto key2 = nextObjectKey()) {
|
||||
if (!strcmp(key2, "a")) parseProperty(repeater->anchor);
|
||||
else if (!strcmp(key2, "p")) parseProperty(repeater->position);
|
||||
else if (!strcmp(key2, "r")) parseProperty(repeater->rotation);
|
||||
else if (!strcmp(key2, "s")) parseProperty(repeater->scale);
|
||||
else if (!strcmp(key2, "so")) parseProperty(repeater->startOpacity);
|
||||
else if (!strcmp(key2, "eo")) parseProperty(repeater->endOpacity);
|
||||
else skip(key2);
|
||||
while (auto key = nextObjectKey()) {
|
||||
if (KEY_AS("a")) parseProperty<LottieProperty::Type::Point>(repeater->anchor);
|
||||
else if (KEY_AS("p")) parseProperty<LottieProperty::Type::Position>(repeater->position);
|
||||
else if (KEY_AS("r")) parseProperty<LottieProperty::Type::Float>(repeater->rotation);
|
||||
else if (KEY_AS("s")) parseProperty<LottieProperty::Type::Point>(repeater->scale);
|
||||
else if (KEY_AS("so")) parseProperty<LottieProperty::Type::Opacity>(repeater->startOpacity);
|
||||
else if (KEY_AS("eo")) parseProperty<LottieProperty::Type::Opacity>(repeater->endOpacity);
|
||||
else skip(key);
|
||||
}
|
||||
}
|
||||
else if (!strcmp(key, "hd")) repeater->hidden = getBool();
|
||||
else if (KEY_AS("hd")) repeater->hidden = getBool();
|
||||
else skip(key);
|
||||
}
|
||||
repeater->prepare();
|
||||
@ -846,7 +891,7 @@ void LottieParser::parseObject(Array<LottieObject*>& parent)
|
||||
{
|
||||
enterObject();
|
||||
while (auto key = nextObjectKey()) {
|
||||
if (!strcmp(key, "ty")) {
|
||||
if (KEY_AS("ty")) {
|
||||
if (auto child = parseObject()) {
|
||||
if (child->hidden) delete(child);
|
||||
else parent.push(child);
|
||||
@ -898,7 +943,7 @@ LottieObject* LottieParser::parseAsset()
|
||||
auto embedded = false;
|
||||
|
||||
while (auto key = nextObjectKey()) {
|
||||
if (!strcmp(key, "id"))
|
||||
if (KEY_AS("id"))
|
||||
{
|
||||
if (peekType() == kStringType) {
|
||||
id = getStringCopy();
|
||||
@ -906,14 +951,15 @@ LottieObject* LottieParser::parseAsset()
|
||||
id = _int2str(getInt());
|
||||
}
|
||||
}
|
||||
else if (!strcmp(key, "layers")) obj = parseLayers();
|
||||
else if (!strcmp(key, "u")) subPath = getString();
|
||||
else if (!strcmp(key, "p")) data = getString();
|
||||
else if (!strcmp(key, "e")) embedded = getInt();
|
||||
else if (KEY_AS("layers")) obj = parseLayers();
|
||||
else if (KEY_AS("u")) subPath = getString();
|
||||
else if (KEY_AS("p")) data = getString();
|
||||
else if (KEY_AS("e")) embedded = getInt();
|
||||
else skip(key);
|
||||
}
|
||||
if (data) obj = parseImage(data, subPath, embedded);
|
||||
if (obj) obj->name = id;
|
||||
else free(id);
|
||||
return obj;
|
||||
}
|
||||
|
||||
@ -925,11 +971,11 @@ LottieFont* LottieParser::parseFont()
|
||||
auto font = new LottieFont;
|
||||
|
||||
while (auto key = nextObjectKey()) {
|
||||
if (!strcmp(key, "fName")) font->name = getStringCopy();
|
||||
else if (!strcmp(key, "fFamily")) font->family = getStringCopy();
|
||||
else if (!strcmp(key, "fStyle")) font->style = getStringCopy();
|
||||
else if (!strcmp(key, "ascent")) font->ascent = getFloat();
|
||||
else if (!strcmp(key, "origin")) font->origin = (LottieFont::Origin) getInt();
|
||||
if (KEY_AS("fName")) font->name = getStringCopy();
|
||||
else if (KEY_AS("fFamily")) font->family = getStringCopy();
|
||||
else if (KEY_AS("fStyle")) font->style = getStringCopy();
|
||||
else if (KEY_AS("ascent")) font->ascent = getFloat();
|
||||
else if (KEY_AS("origin")) font->origin = (LottieFont::Origin) getInt();
|
||||
else skip(key);
|
||||
}
|
||||
return font;
|
||||
@ -946,6 +992,29 @@ void LottieParser::parseAssets()
|
||||
}
|
||||
}
|
||||
|
||||
LottieMarker* LottieParser::parseMarker()
|
||||
{
|
||||
enterObject();
|
||||
|
||||
auto marker = new LottieMarker;
|
||||
|
||||
while (auto key = nextObjectKey()) {
|
||||
if (KEY_AS("cm")) marker->name = getStringCopy();
|
||||
else if (KEY_AS("tm")) marker->time = getFloat();
|
||||
else if (KEY_AS("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)
|
||||
{
|
||||
@ -955,16 +1024,16 @@ void LottieParser::parseChars(Array<LottieGlyph*>& glyphes)
|
||||
//a new glyph
|
||||
auto glyph = new LottieGlyph;
|
||||
while (auto key = nextObjectKey()) {
|
||||
if (!strcmp("ch", key)) glyph->code = getStringCopy();
|
||||
else if (!strcmp("size", key)) glyph->size = static_cast<uint16_t>(getFloat());
|
||||
else if (!strcmp("style", key)) glyph->style = getStringCopy();
|
||||
else if (!strcmp("w", key)) glyph->width = getFloat();
|
||||
else if (!strcmp("fFamily", key)) glyph->family = getStringCopy();
|
||||
else if (!strcmp("data", key))
|
||||
if (KEY_AS("ch")) glyph->code = getStringCopy();
|
||||
else if (KEY_AS("size")) glyph->size = static_cast<uint16_t>(getFloat());
|
||||
else if (KEY_AS("style")) glyph->style = getStringCopy();
|
||||
else if (KEY_AS("w")) glyph->width = getFloat();
|
||||
else if (KEY_AS("fFamily")) glyph->family = getStringCopy();
|
||||
else if (KEY_AS("data"))
|
||||
{ //glyph shapes
|
||||
enterObject();
|
||||
while (auto key = nextObjectKey()) {
|
||||
if (!strcmp(key, "shapes")) parseShapes(glyph->children);
|
||||
if (KEY_AS("shapes")) parseShapes(glyph->children);
|
||||
}
|
||||
} else skip(key);
|
||||
}
|
||||
@ -977,7 +1046,7 @@ void LottieParser::parseFonts()
|
||||
{
|
||||
enterObject();
|
||||
while (auto key = nextObjectKey()) {
|
||||
if (!strcmp("list", key)) {
|
||||
if (KEY_AS("list")) {
|
||||
enterArray();
|
||||
while (nextArrayValue()) {
|
||||
comp->fonts.push(parseFont());
|
||||
@ -993,9 +1062,9 @@ LottieObject* LottieParser::parseGroup()
|
||||
if (!group) return nullptr;
|
||||
|
||||
while (auto key = nextObjectKey()) {
|
||||
if (!strcmp(key, "nm")) {
|
||||
if (KEY_AS("nm")) {
|
||||
group->name = getStringCopy();
|
||||
} else if (!strcmp(key, "it")) {
|
||||
} else if (KEY_AS("it")) {
|
||||
enterArray();
|
||||
while (nextArrayValue()) parseObject(group->children);
|
||||
} else skip(key);
|
||||
@ -1012,7 +1081,7 @@ LottieObject* LottieParser::parseGroup()
|
||||
|
||||
void LottieParser::parseTimeRemap(LottieLayer* layer)
|
||||
{
|
||||
parseProperty(layer->timeRemap);
|
||||
parseProperty<LottieProperty::Type::Float>(layer->timeRemap);
|
||||
}
|
||||
|
||||
|
||||
@ -1034,12 +1103,12 @@ void LottieParser::parseShapes(Array<LottieObject*>& parent)
|
||||
direction = 0;
|
||||
enterObject();
|
||||
while (auto key = nextObjectKey()) {
|
||||
if (!strcmp(key, "it")) {
|
||||
if (KEY_AS("it")) {
|
||||
enterArray();
|
||||
while (nextArrayValue()) parseObject(parent);
|
||||
} else if (!strcmp(key, "d")) {
|
||||
} else if (KEY_AS("d")) {
|
||||
direction = getDirection();
|
||||
} else if (!strcmp(key, "ty")) {
|
||||
} else if (KEY_AS("ty")) {
|
||||
if (auto child = parseObject()) {
|
||||
if (child->hidden) delete(child);
|
||||
else parent.push(child);
|
||||
@ -1056,14 +1125,14 @@ void LottieParser::parseTextRange(LottieText* text)
|
||||
enterArray();
|
||||
while (nextArrayValue()) {
|
||||
enterObject();
|
||||
while (auto key2 = nextObjectKey()) {
|
||||
if (!strcmp(key2, "a")) { //text style
|
||||
while (auto key = nextObjectKey()) {
|
||||
if (KEY_AS("a")) { //text style
|
||||
enterObject();
|
||||
while (auto key3 = nextObjectKey()) {
|
||||
if (!strcmp(key3, "t")) parseProperty(text->spacing);
|
||||
else skip(key3);
|
||||
while (auto key = nextObjectKey()) {
|
||||
if (KEY_AS("t")) parseProperty<LottieProperty::Type::Float>(text->spacing);
|
||||
else skip(key);
|
||||
}
|
||||
} else skip(key2);
|
||||
} else skip(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1076,10 +1145,10 @@ void LottieParser::parseText(Array<LottieObject*>& parent)
|
||||
auto text = new LottieText;
|
||||
|
||||
while (auto key = nextObjectKey()) {
|
||||
if (!strcmp(key, "d")) parseProperty<LottieProperty::Type::TextDoc>(text->doc, text);
|
||||
else if (!strcmp(key, "a")) parseTextRange(text);
|
||||
//else if (!strcmp(key, "p")) TVGLOG("LOTTIE", "Text Follow Path (p) is not supported");
|
||||
//else if (!strcmp(key, "m")) TVGLOG("LOTTIE", "Text Alignment Option (m) is not supported");
|
||||
if (KEY_AS("d")) parseProperty<LottieProperty::Type::TextDoc>(text->doc, text);
|
||||
else if (KEY_AS("a")) parseTextRange(text);
|
||||
//else if (KEY_AS("p")) TVGLOG("LOTTIE", "Text Follow Path (p) is not supported");
|
||||
//else if (KEY_AS("m")) TVGLOG("LOTTIE", "Text Alignment Option (m) is not supported");
|
||||
else skip(key);
|
||||
}
|
||||
|
||||
@ -1088,14 +1157,14 @@ void LottieParser::parseText(Array<LottieObject*>& parent)
|
||||
}
|
||||
|
||||
|
||||
void LottieParser::getLayerSize(uint32_t& val)
|
||||
void LottieParser::getLayerSize(float& val)
|
||||
{
|
||||
if (val == 0) {
|
||||
val = getInt();
|
||||
if (val == 0.0f) {
|
||||
val = getFloat();
|
||||
} else {
|
||||
//layer might have both w(width) & sw(solid color width)
|
||||
//override one if the a new size is smaller.
|
||||
uint32_t w = getInt();
|
||||
auto w = getFloat();
|
||||
if (w < val) val = w;
|
||||
}
|
||||
}
|
||||
@ -1107,10 +1176,10 @@ LottieMask* LottieParser::parseMask()
|
||||
|
||||
enterObject();
|
||||
while (auto key = nextObjectKey()) {
|
||||
if (!strcmp(key, "inv")) mask->inverse = getBool();
|
||||
else if (!strcmp(key, "mode")) mask->method = getMaskMethod(mask->inverse);
|
||||
else if (!strcmp(key, "pt")) getPathSet(mask->pathset);
|
||||
else if (!strcmp(key, "o")) parseProperty(mask->opacity);
|
||||
if (KEY_AS("inv")) mask->inverse = getBool();
|
||||
else if (KEY_AS("mode")) mask->method = getMaskMethod(mask->inverse);
|
||||
else if (KEY_AS("pt")) getPathSet(mask->pathset);
|
||||
else if (KEY_AS("o")) parseProperty<LottieProperty::Type::Opacity>(mask->opacity);
|
||||
else skip(key);
|
||||
}
|
||||
|
||||
@ -1123,7 +1192,6 @@ void LottieParser::parseMasks(LottieLayer* layer)
|
||||
enterArray();
|
||||
while (nextArrayValue()) {
|
||||
auto mask = parseMask();
|
||||
if (mask->dynamic()) layer->statical = false;
|
||||
layer->masks.push(mask);
|
||||
}
|
||||
}
|
||||
@ -1135,41 +1203,41 @@ LottieLayer* LottieParser::parseLayer()
|
||||
if (!layer) return nullptr;
|
||||
|
||||
layer->comp = comp;
|
||||
context->layer = layer;
|
||||
context.layer = layer;
|
||||
|
||||
auto ddd = false;
|
||||
|
||||
enterObject();
|
||||
|
||||
while (auto key = nextObjectKey()) {
|
||||
if (!strcmp(key, "ddd")) ddd = getInt(); //3d layer
|
||||
else if (!strcmp(key, "ind")) layer->id = getInt();
|
||||
else if (!strcmp(key, "ty")) layer->type = (LottieLayer::Type) getInt();
|
||||
else if (!strcmp(key, "nm")) layer->name = getStringCopy();
|
||||
else if (!strcmp(key, "sr")) layer->timeStretch = getFloat();
|
||||
else if (!strcmp(key, "ks"))
|
||||
if (KEY_AS("ddd")) ddd = getInt(); //3d layer
|
||||
else if (KEY_AS("ind")) layer->id = getInt();
|
||||
else if (KEY_AS("ty")) layer->type = (LottieLayer::Type) getInt();
|
||||
else if (KEY_AS("nm")) layer->name = getStringCopy();
|
||||
else if (KEY_AS("sr")) layer->timeStretch = getFloat();
|
||||
else if (KEY_AS("ks"))
|
||||
{
|
||||
enterObject();
|
||||
layer->transform = parseTransform(ddd);
|
||||
}
|
||||
else if (!strcmp(key, "ao")) layer->autoOrient = getInt();
|
||||
else if (!strcmp(key, "shapes")) parseShapes(layer->children);
|
||||
else if (!strcmp(key, "ip")) layer->inFrame = getFloat();
|
||||
else if (!strcmp(key, "op")) layer->outFrame = getFloat();
|
||||
else if (!strcmp(key, "st")) layer->startFrame = getFloat();
|
||||
else if (!strcmp(key, "bm")) layer->blendMethod = getBlendMethod();
|
||||
else if (!strcmp(key, "parent")) layer->pid = getInt();
|
||||
else if (!strcmp(key, "tm")) parseTimeRemap(layer);
|
||||
else if (!strcmp(key, "w") || !strcmp(key, "sw")) getLayerSize(layer->w);
|
||||
else if (!strcmp(key, "h") || !strcmp(key, "sh")) getLayerSize(layer->h);
|
||||
else if (!strcmp(key, "sc")) layer->color = getColor(getString());
|
||||
else if (!strcmp(key, "tt")) layer->matte.type = getMatteType();
|
||||
else if (!strcmp(key, "masksProperties")) parseMasks(layer);
|
||||
else if (!strcmp(key, "hd")) layer->hidden = getBool();
|
||||
else if (!strcmp(key, "refId")) layer->refId = getStringCopy();
|
||||
else if (!strcmp(key, "td")) layer->matteSrc = getInt(); //used for matte layer
|
||||
else if (!strcmp(key, "t")) parseText(layer->children);
|
||||
else if (!strcmp(key, "ef"))
|
||||
else if (KEY_AS("ao")) layer->autoOrient = getInt();
|
||||
else if (KEY_AS("shapes")) parseShapes(layer->children);
|
||||
else if (KEY_AS("ip")) layer->inFrame = getFloat();
|
||||
else if (KEY_AS("op")) layer->outFrame = getFloat();
|
||||
else if (KEY_AS("st")) layer->startFrame = getFloat();
|
||||
else if (KEY_AS("bm")) layer->blendMethod = getBlendMethod();
|
||||
else if (KEY_AS("parent")) layer->pid = getInt();
|
||||
else if (KEY_AS("tm")) parseTimeRemap(layer);
|
||||
else if (KEY_AS("w") || KEY_AS("sw")) getLayerSize(layer->w);
|
||||
else if (KEY_AS("h") || KEY_AS("sh")) getLayerSize(layer->h);
|
||||
else if (KEY_AS("sc")) layer->color = getColor(getString());
|
||||
else if (KEY_AS("tt")) layer->matte.type = getMatteType();
|
||||
else if (KEY_AS("masksProperties")) parseMasks(layer);
|
||||
else if (KEY_AS("hd")) layer->hidden = getBool();
|
||||
else if (KEY_AS("refId")) layer->refId = getStringCopy();
|
||||
else if (KEY_AS("td")) layer->matteSrc = getInt(); //used for matte layer
|
||||
else if (KEY_AS("t")) parseText(layer->children);
|
||||
else if (KEY_AS("ef"))
|
||||
{
|
||||
TVGERR("LOTTIE", "layer effect(ef) is not supported!");
|
||||
skip(key);
|
||||
@ -1241,38 +1309,41 @@ void LottieParser::postProcess(Array<LottieGlyph*>& glyphes)
|
||||
/* External Class Implementation */
|
||||
/************************************************************************/
|
||||
|
||||
const char* LottieParser::sid()
|
||||
const char* LottieParser::sid(bool first)
|
||||
{
|
||||
//verify json
|
||||
if (!parseNext()) return nullptr;
|
||||
enterObject();
|
||||
if (first) {
|
||||
//verify json
|
||||
if (!parseNext()) return nullptr;
|
||||
enterObject();
|
||||
}
|
||||
return nextObjectKey();
|
||||
}
|
||||
|
||||
|
||||
bool LottieParser::parse(LottieSlot* slot)
|
||||
bool LottieParser::apply(LottieSlot* slot)
|
||||
{
|
||||
enterObject();
|
||||
|
||||
LottieParser::Context context;
|
||||
this->context = &context;
|
||||
//OPTIMIZE: we can create the property directly, without object
|
||||
LottieObject* obj = nullptr; //slot object
|
||||
|
||||
switch (slot->type) {
|
||||
case LottieProperty::Type::ColorStop: {
|
||||
obj = new LottieGradient;
|
||||
context.gradient = static_cast<LottieGradient*>(obj);
|
||||
parseSlotProperty(static_cast<LottieGradient*>(obj)->colorStops);
|
||||
context.parent = obj;
|
||||
parseSlotProperty<LottieProperty::Type::ColorStop>(static_cast<LottieGradient*>(obj)->colorStops);
|
||||
break;
|
||||
}
|
||||
case LottieProperty::Type::Color: {
|
||||
obj = new LottieSolid;
|
||||
parseSlotProperty(static_cast<LottieSolid*>(obj)->color);
|
||||
context.parent = obj;
|
||||
parseSlotProperty<LottieProperty::Type::Color>(static_cast<LottieSolid*>(obj)->color);
|
||||
break;
|
||||
}
|
||||
case LottieProperty::Type::TextDoc: {
|
||||
obj = new LottieText;
|
||||
parseSlotProperty(static_cast<LottieText*>(obj)->doc);
|
||||
context.parent = obj;
|
||||
parseSlotProperty<LottieProperty::Type::TextDoc>(static_cast<LottieText*>(obj)->doc);
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
@ -1280,10 +1351,7 @@ bool LottieParser::parse(LottieSlot* slot)
|
||||
|
||||
if (!obj || Invalid()) return false;
|
||||
|
||||
//apply slot object to all targets
|
||||
for (auto target = slot->objs.begin(); target < slot->objs.end(); ++target) {
|
||||
(*target)->override(obj);
|
||||
}
|
||||
slot->assign(obj);
|
||||
|
||||
delete(obj);
|
||||
|
||||
@ -1304,26 +1372,26 @@ bool LottieParser::parse()
|
||||
|
||||
Array<LottieGlyph*> glyphes;
|
||||
|
||||
//assign parsing context
|
||||
LottieParser::Context context;
|
||||
this->context = &context;
|
||||
|
||||
while (auto key = nextObjectKey()) {
|
||||
if (!strcmp(key, "v")) comp->version = getStringCopy();
|
||||
else if (!strcmp(key, "fr")) comp->frameRate = getFloat();
|
||||
else if (!strcmp(key, "ip")) comp->startFrame = getFloat();
|
||||
else if (!strcmp(key, "op")) comp->endFrame = getFloat();
|
||||
else if (!strcmp(key, "w")) comp->w = getInt();
|
||||
else if (!strcmp(key, "h")) comp->h = getInt();
|
||||
else if (!strcmp(key, "nm")) comp->name = getStringCopy();
|
||||
else if (!strcmp(key, "assets")) parseAssets();
|
||||
else if (!strcmp(key, "layers")) comp->root = parseLayers();
|
||||
else if (!strcmp(key, "fonts")) parseFonts();
|
||||
else if (!strcmp(key, "chars")) parseChars(glyphes);
|
||||
if (KEY_AS("v")) comp->version = getStringCopy();
|
||||
else if (KEY_AS("fr")) comp->frameRate = getFloat();
|
||||
else if (KEY_AS("ip")) comp->startFrame = getFloat();
|
||||
else if (KEY_AS("op")) comp->endFrame = getFloat();
|
||||
else if (KEY_AS("w")) comp->w = getFloat();
|
||||
else if (KEY_AS("h")) comp->h = getFloat();
|
||||
else if (KEY_AS("nm")) comp->name = getStringCopy();
|
||||
else if (KEY_AS("assets")) parseAssets();
|
||||
else if (KEY_AS("layers")) comp->root = parseLayers();
|
||||
else if (KEY_AS("fonts")) parseFonts();
|
||||
else if (KEY_AS("chars")) parseChars(glyphes);
|
||||
else if (KEY_AS("markers")) parseMarkers();
|
||||
else skip(key);
|
||||
}
|
||||
|
||||
if (Invalid() || !comp->root) return false;
|
||||
if (Invalid() || !comp->root) {
|
||||
delete(comp);
|
||||
return false;
|
||||
}
|
||||
|
||||
comp->root->inFrame = comp->startFrame;
|
||||
comp->root->outFrame = comp->endFrame;
|
||||
|
@ -39,8 +39,8 @@ public:
|
||||
}
|
||||
|
||||
bool parse();
|
||||
bool parse(LottieSlot* slot);
|
||||
const char* sid();
|
||||
bool apply(LottieSlot* slot);
|
||||
const char* sid(bool first = false);
|
||||
|
||||
LottieComposition* comp = nullptr;
|
||||
const char* dirName = nullptr; //base resource directory
|
||||
@ -58,7 +58,7 @@ private:
|
||||
|
||||
void getInperpolatorPoint(Point& pt);
|
||||
void getPathSet(LottiePathSet& path);
|
||||
void getLayerSize(uint32_t& val);
|
||||
void getLayerSize(float& val);
|
||||
void getValue(TextDocument& doc);
|
||||
void getValue(PathSet& path);
|
||||
void getValue(Array<Point>& pts);
|
||||
@ -73,7 +73,7 @@ private:
|
||||
template<typename T> void parseKeyFrame(T& prop);
|
||||
template<typename T> void parsePropertyInternal(T& prop);
|
||||
template<LottieProperty::Type type = LottieProperty::Type::Invalid, typename T> void parseProperty(T& prop, LottieObject* obj = nullptr);
|
||||
template<typename T> void parseSlotProperty(T& prop);
|
||||
template<LottieProperty::Type type = LottieProperty::Type::Invalid, typename T> void parseSlotProperty(T& prop);
|
||||
|
||||
LottieObject* parseObject();
|
||||
LottieObject* parseAsset();
|
||||
@ -95,6 +95,7 @@ private:
|
||||
LottieTrimpath* parseTrimpath();
|
||||
LottieRepeater* parseRepeater();
|
||||
LottieFont* parseFont();
|
||||
LottieMarker* parseMarker();
|
||||
|
||||
void parseObject(Array<LottieObject*>& parent);
|
||||
void parseShapes(Array<LottieObject*>& parent);
|
||||
@ -107,13 +108,14 @@ private:
|
||||
void parseAssets();
|
||||
void parseFonts();
|
||||
void parseChars(Array<LottieGlyph*>& glyphes);
|
||||
void parseMarkers();
|
||||
void postProcess(Array<LottieGlyph*>& glyphes);
|
||||
|
||||
//Current parsing context
|
||||
struct Context {
|
||||
LottieLayer* layer = nullptr;
|
||||
LottieGradient* gradient = nullptr;
|
||||
} *context;
|
||||
LottieObject* parent = nullptr;
|
||||
} context;
|
||||
};
|
||||
|
||||
#endif //_TVG_LOTTIE_PARSER_H_
|
||||
|
@ -51,8 +51,6 @@
|
||||
#include "rapidjson/document.h"
|
||||
#include "tvgCommon.h"
|
||||
|
||||
RAPIDJSON_DIAG_PUSH
|
||||
RAPIDJSON_DIAG_OFF(effc++)
|
||||
|
||||
using namespace rapidjson;
|
||||
|
||||
@ -133,7 +131,7 @@ struct LookaheadParserHandler
|
||||
}
|
||||
|
||||
bool RawNumber(const char *, SizeType, TVG_UNUSED bool)
|
||||
{
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -29,8 +29,16 @@
|
||||
#include "tvgCommon.h"
|
||||
#include "tvgArray.h"
|
||||
#include "tvgMath.h"
|
||||
#include "tvgBezier.h"
|
||||
#include "tvgLines.h"
|
||||
#include "tvgLottieInterpolator.h"
|
||||
#include "tvgLottieExpressions.h"
|
||||
|
||||
#define ROUNDNESS_EPSILON 1.0f
|
||||
|
||||
struct LottieFont;
|
||||
struct LottieLayer;
|
||||
struct LottieObject;
|
||||
|
||||
|
||||
struct PathSet
|
||||
{
|
||||
@ -54,8 +62,6 @@ struct ColorStop
|
||||
};
|
||||
|
||||
|
||||
struct LottieFont;
|
||||
|
||||
struct TextDocument
|
||||
{
|
||||
char* text = nullptr;
|
||||
@ -73,8 +79,8 @@ struct TextDocument
|
||||
} stroke;
|
||||
char* name = nullptr;
|
||||
float size;
|
||||
float tracking = 0.0f;
|
||||
uint8_t justify;
|
||||
uint8_t tracking;
|
||||
};
|
||||
|
||||
|
||||
@ -96,26 +102,6 @@ static inline RGB24 operator*(const RGB24& lhs, float rhs)
|
||||
}
|
||||
|
||||
|
||||
static void copy(PathSet& pathset, Array<Point>& outPts)
|
||||
{
|
||||
Array<Point> inPts;
|
||||
inPts.data = pathset.pts;
|
||||
inPts.count = pathset.ptsCnt;
|
||||
outPts.push(inPts);
|
||||
inPts.data = nullptr;
|
||||
}
|
||||
|
||||
|
||||
static void copy(PathSet& pathset, Array<PathCommand>& outCmds)
|
||||
{
|
||||
Array<PathCommand> inCmds;
|
||||
inCmds.data = pathset.cmds;
|
||||
inCmds.count = pathset.cmdsCnt;
|
||||
outCmds.push(inCmds);
|
||||
inCmds.data = nullptr;
|
||||
}
|
||||
|
||||
|
||||
template<typename T>
|
||||
struct LottieScalarFrame
|
||||
{
|
||||
@ -186,28 +172,208 @@ struct LottieVectorFrame
|
||||
};
|
||||
|
||||
|
||||
template<typename T>
|
||||
uint32_t bsearch(T* frames, float frameNo)
|
||||
//Property would have an either keyframes or single value.
|
||||
struct LottieProperty
|
||||
{
|
||||
uint32_t low = 0;
|
||||
uint32_t high = frames->count - 1;
|
||||
enum class Type : uint8_t { Point = 0, Float, Opacity, Color, PathSet, ColorStop, Position, TextDoc, Invalid };
|
||||
virtual ~LottieProperty() {}
|
||||
|
||||
LottieExpression* exp = nullptr;
|
||||
|
||||
//TODO: Apply common bodies?
|
||||
virtual uint32_t frameCnt() = 0;
|
||||
virtual uint32_t nearest(float time) = 0;
|
||||
virtual float frameNo(int32_t key) = 0;
|
||||
};
|
||||
|
||||
|
||||
struct LottieExpression
|
||||
{
|
||||
enum LoopMode : uint8_t { None = 0, InCycle = 1, InPingPong, InOffset, InContinue, OutCycle, OutPingPong, OutOffset, OutContinue };
|
||||
|
||||
char* code;
|
||||
LottieComposition* comp;
|
||||
LottieLayer* layer;
|
||||
LottieObject* object;
|
||||
LottieProperty* property;
|
||||
LottieProperty::Type type;
|
||||
|
||||
bool enabled;
|
||||
|
||||
struct {
|
||||
uint32_t key = 0; //the keyframe number repeating to
|
||||
float in = FLT_MAX; //looping duration in frame number
|
||||
LoopMode mode = None;
|
||||
} loop;
|
||||
;
|
||||
~LottieExpression()
|
||||
{
|
||||
free(code);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
static void _copy(PathSet* pathset, Array<Point>& outPts, Matrix* transform)
|
||||
{
|
||||
Array<Point> inPts;
|
||||
|
||||
if (transform) {
|
||||
for (int i = 0; i < pathset->ptsCnt; ++i) {
|
||||
Point pt = pathset->pts[i];
|
||||
mathMultiply(&pt, transform);
|
||||
outPts.push(pt);
|
||||
}
|
||||
} else {
|
||||
inPts.data = pathset->pts;
|
||||
inPts.count = pathset->ptsCnt;
|
||||
outPts.push(inPts);
|
||||
inPts.data = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void _copy(PathSet* pathset, Array<PathCommand>& outCmds)
|
||||
{
|
||||
Array<PathCommand> inCmds;
|
||||
inCmds.data = pathset->cmds;
|
||||
inCmds.count = pathset->cmdsCnt;
|
||||
outCmds.push(inCmds);
|
||||
inCmds.data = nullptr;
|
||||
}
|
||||
|
||||
|
||||
static void _roundCorner(Array<PathCommand>& cmds, Array<Point>& pts, const Point& prev, const Point& curr, const Point& next, float roundness)
|
||||
{
|
||||
auto lenPrev = mathLength(prev - curr);
|
||||
auto rPrev = lenPrev > 0.0f ? 0.5f * mathMin(lenPrev * 0.5f, roundness) / lenPrev : 0.0f;
|
||||
auto lenNext = mathLength(next - curr);
|
||||
auto rNext = lenNext > 0.0f ? 0.5f * mathMin(lenNext * 0.5f, roundness) / lenNext : 0.0f;
|
||||
|
||||
auto dPrev = rPrev * (curr - prev);
|
||||
auto dNext = rNext * (curr - next);
|
||||
|
||||
pts.push(curr - 2.0f * dPrev);
|
||||
pts.push(curr - dPrev);
|
||||
pts.push(curr - dNext);
|
||||
pts.push(curr - 2.0f * dNext);
|
||||
cmds.push(PathCommand::LineTo);
|
||||
cmds.push(PathCommand::CubicTo);
|
||||
}
|
||||
|
||||
|
||||
static bool _modifier(Point* inputPts, uint32_t inputPtsCnt, PathCommand* inputCmds, uint32_t inputCmdsCnt, Array<PathCommand>& cmds, Array<Point>& pts, Matrix* transform, float roundness)
|
||||
{
|
||||
cmds.reserve(inputCmdsCnt * 2);
|
||||
pts.reserve((uint16_t)(inputPtsCnt * 1.5));
|
||||
auto ptsCnt = pts.count;
|
||||
|
||||
auto startIndex = 0;
|
||||
for (uint32_t iCmds = 0, iPts = 0; iCmds < inputCmdsCnt; ++iCmds) {
|
||||
switch (inputCmds[iCmds]) {
|
||||
case PathCommand::MoveTo: {
|
||||
startIndex = pts.count;
|
||||
cmds.push(PathCommand::MoveTo);
|
||||
pts.push(inputPts[iPts++]);
|
||||
break;
|
||||
}
|
||||
case PathCommand::CubicTo: {
|
||||
auto& prev = inputPts[iPts - 1];
|
||||
auto& curr = inputPts[iPts + 2];
|
||||
if (iCmds < inputCmdsCnt - 1 &&
|
||||
mathZero(inputPts[iPts - 1] - inputPts[iPts]) &&
|
||||
mathZero(inputPts[iPts + 1] - inputPts[iPts + 2])) {
|
||||
if (inputCmds[iCmds + 1] == PathCommand::CubicTo &&
|
||||
mathZero(inputPts[iPts + 2] - inputPts[iPts + 3]) &&
|
||||
mathZero(inputPts[iPts + 4] - inputPts[iPts + 5])) {
|
||||
_roundCorner(cmds, pts, prev, curr, inputPts[iPts + 5], roundness);
|
||||
iPts += 3;
|
||||
break;
|
||||
} else if (inputCmds[iCmds + 1] == PathCommand::Close) {
|
||||
_roundCorner(cmds, pts, prev, curr, inputPts[2], roundness);
|
||||
pts[startIndex] = pts.last();
|
||||
iPts += 3;
|
||||
break;
|
||||
}
|
||||
}
|
||||
cmds.push(PathCommand::CubicTo);
|
||||
pts.push(inputPts[iPts++]);
|
||||
pts.push(inputPts[iPts++]);
|
||||
pts.push(inputPts[iPts++]);
|
||||
break;
|
||||
}
|
||||
case PathCommand::Close: {
|
||||
cmds.push(PathCommand::Close);
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
if (transform) {
|
||||
for (auto i = ptsCnt; i < pts.count; ++i)
|
||||
mathTransform(transform, &pts[i]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
template<typename T>
|
||||
uint32_t _bsearch(T* frames, float frameNo)
|
||||
{
|
||||
int32_t low = 0;
|
||||
int32_t high = int32_t(frames->count) - 1;
|
||||
|
||||
while (low <= high) {
|
||||
auto mid = low + (high - low) / 2;
|
||||
auto frame = frames->data + mid;
|
||||
if (mathEqual(frameNo, frame->no)) return mid;
|
||||
else if (frameNo < frame->no) high = mid - 1;
|
||||
if (frameNo < frame->no) high = mid - 1;
|
||||
else low = mid + 1;
|
||||
}
|
||||
if (high < low) low = high;
|
||||
if (low < 0) low = 0;
|
||||
return low;
|
||||
}
|
||||
|
||||
|
||||
struct LottieProperty
|
||||
template<typename T>
|
||||
uint32_t _nearest(T* frames, float frameNo)
|
||||
{
|
||||
enum class Type : uint8_t { Point = 0, Float, Opacity, Color, PathSet, ColorStop, Position, TextDoc, Invalid };
|
||||
};
|
||||
if (frames) {
|
||||
auto key = _bsearch(frames, frameNo);
|
||||
if (key == frames->count - 1) return key;
|
||||
return (fabsf(frames->data[key].no - frameNo) < fabsf(frames->data[key + 1].no - frameNo)) ? key : (key + 1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
template<typename T>
|
||||
float _frameNo(T* frames, int32_t key)
|
||||
{
|
||||
if (!frames) return 0.0f;
|
||||
if (key < 0) key = 0;
|
||||
if (key >= (int32_t) frames->count) key = (int32_t)(frames->count - 1);
|
||||
return (*frames)[key].no;
|
||||
}
|
||||
|
||||
|
||||
template<typename T>
|
||||
float _loop(T* frames, float frameNo, LottieExpression* exp)
|
||||
{
|
||||
if (frameNo >= exp->loop.in || frameNo < frames->first().no ||frameNo < frames->last().no) return frameNo;
|
||||
|
||||
switch (exp->loop.mode) {
|
||||
case LottieExpression::LoopMode::InCycle: {
|
||||
frameNo -= frames->first().no;
|
||||
return fmodf(frameNo, frames->last().no - frames->first().no) + (*frames)[exp->loop.key].no;
|
||||
}
|
||||
case LottieExpression::LoopMode::OutCycle: {
|
||||
frameNo -= frames->first().no;
|
||||
return fmodf(frameNo, (*frames)[frames->count - 1 - exp->loop.key].no - frames->first().no) + frames->first().no;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
return frameNo;
|
||||
}
|
||||
|
||||
|
||||
template<typename T>
|
||||
@ -218,10 +384,36 @@ struct LottieGenericProperty : LottieProperty
|
||||
T value;
|
||||
|
||||
LottieGenericProperty(T v) : value(v) {}
|
||||
LottieGenericProperty() {}
|
||||
|
||||
~LottieGenericProperty()
|
||||
{
|
||||
release();
|
||||
}
|
||||
|
||||
void release()
|
||||
{
|
||||
delete(frames);
|
||||
frames = nullptr;
|
||||
if (exp) {
|
||||
delete(exp);
|
||||
exp = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t nearest(float frameNo) override
|
||||
{
|
||||
return _nearest(frames, frameNo);
|
||||
}
|
||||
|
||||
uint32_t frameCnt() override
|
||||
{
|
||||
return frames ? frames->count : 1;
|
||||
}
|
||||
|
||||
float frameNo(int32_t key) override
|
||||
{
|
||||
return _frameNo(frames, key);
|
||||
}
|
||||
|
||||
LottieScalarFrame<T>& newFrame()
|
||||
@ -247,15 +439,24 @@ struct LottieGenericProperty : LottieProperty
|
||||
if (frames->count == 1 || frameNo <= frames->first().no) return frames->first().value;
|
||||
if (frameNo >= frames->last().no) return frames->last().value;
|
||||
|
||||
auto frame = frames->data + bsearch(frames, frameNo);
|
||||
if (frame->no == frameNo) return frame->value;
|
||||
auto frame = frames->data + _bsearch(frames, frameNo);
|
||||
if (mathEqual(frame->no, frameNo)) return frame->value;
|
||||
return frame->interpolate(frame + 1, frameNo);
|
||||
}
|
||||
|
||||
T operator()(float frameNo, LottieExpressions* exps)
|
||||
{
|
||||
if (exps && (exp && exp->enabled)) {
|
||||
T out{};
|
||||
if (exp->loop.mode != LottieExpression::LoopMode::None) frameNo = _loop(frames, frameNo, exp);
|
||||
if (exps->result<LottieGenericProperty<T>>(frameNo, out, exp)) return out;
|
||||
}
|
||||
return operator()(frameNo);
|
||||
}
|
||||
|
||||
T& operator=(const T& other)
|
||||
{
|
||||
//shallow copy, used for slot overriding
|
||||
delete(frames);
|
||||
if (other.frames) {
|
||||
frames = other.frames;
|
||||
const_cast<T&>(other).frames = nullptr;
|
||||
@ -275,10 +476,21 @@ struct LottiePathSet : LottieProperty
|
||||
|
||||
~LottiePathSet()
|
||||
{
|
||||
release();
|
||||
}
|
||||
|
||||
void release()
|
||||
{
|
||||
if (exp) {
|
||||
delete(exp);
|
||||
exp = nullptr;
|
||||
}
|
||||
|
||||
free(value.cmds);
|
||||
free(value.pts);
|
||||
|
||||
if (!frames) return;
|
||||
|
||||
for (auto p = frames->begin(); p < frames->end(); ++p) {
|
||||
free((*p).value.cmds);
|
||||
free((*p).value.pts);
|
||||
@ -287,6 +499,21 @@ struct LottiePathSet : LottieProperty
|
||||
free(frames);
|
||||
}
|
||||
|
||||
uint32_t nearest(float frameNo) override
|
||||
{
|
||||
return _nearest(frames, frameNo);
|
||||
}
|
||||
|
||||
uint32_t frameCnt() override
|
||||
{
|
||||
return frames ? frames->count : 1;
|
||||
}
|
||||
|
||||
float frameNo(int32_t key) override
|
||||
{
|
||||
return _frameNo(frames, key);
|
||||
}
|
||||
|
||||
LottieScalarFrame<PathSet>& newFrame()
|
||||
{
|
||||
if (!frames) {
|
||||
@ -306,55 +533,68 @@ struct LottiePathSet : LottieProperty
|
||||
return (*frames)[frames->count];
|
||||
}
|
||||
|
||||
bool operator()(float frameNo, Array<PathCommand>& cmds, Array<Point>& pts)
|
||||
bool operator()(float frameNo, Array<PathCommand>& cmds, Array<Point>& pts, Matrix* transform, float roundness)
|
||||
{
|
||||
if (!frames) {
|
||||
copy(value, cmds);
|
||||
copy(value, pts);
|
||||
return true;
|
||||
PathSet* path = nullptr;
|
||||
LottieScalarFrame<PathSet>* frame = nullptr;
|
||||
float t;
|
||||
bool interpolate = false;
|
||||
|
||||
if (!frames) path = &value;
|
||||
else if (frames->count == 1 || frameNo <= frames->first().no) path = &frames->first().value;
|
||||
else if (frameNo >= frames->last().no) path = &frames->last().value;
|
||||
else {
|
||||
frame = frames->data + _bsearch(frames, frameNo);
|
||||
if (mathEqual(frame->no, frameNo)) path = &frame->value;
|
||||
else {
|
||||
t = (frameNo - frame->no) / ((frame + 1)->no - frame->no);
|
||||
if (frame->interpolator) t = frame->interpolator->progress(t);
|
||||
if (frame->hold) path = &(frame + ((t < 1.0f) ? 0 : 1))->value;
|
||||
else interpolate = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (frames->count == 1 || frameNo <= frames->first().no) {
|
||||
copy(frames->first().value, cmds);
|
||||
copy(frames->first().value, pts);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (frameNo >= frames->last().no) {
|
||||
copy(frames->last().value, cmds);
|
||||
copy(frames->last().value, pts);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto frame = frames->data + bsearch(frames, frameNo);
|
||||
|
||||
if (frame->no == frameNo) {
|
||||
copy(frame->value, cmds);
|
||||
copy(frame->value, pts);
|
||||
return true;
|
||||
}
|
||||
|
||||
//interpolate
|
||||
copy(frame->value, cmds);
|
||||
|
||||
auto t = (frameNo - frame->no) / ((frame + 1)->no - frame->no);
|
||||
if (frame->interpolator) t = frame->interpolator->progress(t);
|
||||
|
||||
if (frame->hold) {
|
||||
if (t < 1.0f) copy(frame->value, pts);
|
||||
else copy((frame + 1)->value, pts);
|
||||
if (!interpolate) {
|
||||
if (roundness > ROUNDNESS_EPSILON) return _modifier(path->pts, path->ptsCnt, path->cmds, path->cmdsCnt, cmds, pts, transform, roundness);
|
||||
_copy(path, cmds);
|
||||
_copy(path, pts, transform);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto s = frame->value.pts;
|
||||
auto e = (frame + 1)->value.pts;
|
||||
|
||||
for (auto i = 0; i < frame->value.ptsCnt; ++i, ++s, ++e) {
|
||||
pts.push(mathLerp(*s, *e, t));
|
||||
if (roundness > ROUNDNESS_EPSILON) {
|
||||
auto interpPts = (Point*)malloc(frame->value.ptsCnt * sizeof(Point));
|
||||
auto p = interpPts;
|
||||
for (auto i = 0; i < frame->value.ptsCnt; ++i, ++s, ++e, ++p) {
|
||||
*p = mathLerp(*s, *e, t);
|
||||
if (transform) mathMultiply(p, transform);
|
||||
}
|
||||
_modifier(interpPts, frame->value.ptsCnt, frame->value.cmds, frame->value.cmdsCnt, cmds, pts, nullptr, roundness);
|
||||
free(interpPts);
|
||||
return true;
|
||||
} else {
|
||||
for (auto i = 0; i < frame->value.ptsCnt; ++i, ++s, ++e) {
|
||||
auto pt = mathLerp(*s, *e, t);
|
||||
if (transform) mathMultiply(&pt, transform);
|
||||
pts.push(pt);
|
||||
}
|
||||
_copy(&frame->value, cmds);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool operator()(float frameNo, Array<PathCommand>& cmds, Array<Point>& pts, Matrix* transform, float roundness, LottieExpressions* exps)
|
||||
{
|
||||
if (exps && (exp && exp->enabled)) {
|
||||
if (exp->loop.mode != LottieExpression::LoopMode::None) frameNo = _loop(frames, frameNo, exp);
|
||||
if (exps->result<LottiePathSet>(frameNo, cmds, pts, transform, roundness, exp)) return true;
|
||||
}
|
||||
return operator()(frameNo, cmds, pts, transform, roundness);
|
||||
}
|
||||
|
||||
void prepare() {}
|
||||
};
|
||||
|
||||
@ -364,6 +604,7 @@ struct LottieColorStop : LottieProperty
|
||||
Array<LottieScalarFrame<ColorStop>>* frames = nullptr;
|
||||
ColorStop value;
|
||||
uint16_t count = 0; //colorstop count
|
||||
bool populated = false;
|
||||
|
||||
~LottieColorStop()
|
||||
{
|
||||
@ -372,6 +613,11 @@ struct LottieColorStop : LottieProperty
|
||||
|
||||
void release()
|
||||
{
|
||||
if (exp) {
|
||||
delete(exp);
|
||||
exp = nullptr;
|
||||
}
|
||||
|
||||
if (value.data) {
|
||||
free(value.data);
|
||||
value.data = nullptr;
|
||||
@ -387,6 +633,21 @@ struct LottieColorStop : LottieProperty
|
||||
frames = nullptr;
|
||||
}
|
||||
|
||||
uint32_t nearest(float frameNo) override
|
||||
{
|
||||
return _nearest(frames, frameNo);
|
||||
}
|
||||
|
||||
uint32_t frameCnt() override
|
||||
{
|
||||
return frames ? frames->count : 1;
|
||||
}
|
||||
|
||||
float frameNo(int32_t key) override
|
||||
{
|
||||
return _frameNo(frames, key);
|
||||
}
|
||||
|
||||
LottieScalarFrame<ColorStop>& newFrame()
|
||||
{
|
||||
if (!frames) {
|
||||
@ -406,28 +667,23 @@ struct LottieColorStop : LottieProperty
|
||||
return (*frames)[frames->count];
|
||||
}
|
||||
|
||||
void operator()(float frameNo, Fill* fill)
|
||||
Result operator()(float frameNo, Fill* fill, LottieExpressions* exps)
|
||||
{
|
||||
if (!frames) {
|
||||
fill->colorStops(value.data, count);
|
||||
return;
|
||||
if (exps && (exp && exp->enabled)) {
|
||||
if (exp->loop.mode != LottieExpression::LoopMode::None) frameNo = _loop(frames, frameNo, exp);
|
||||
if (exps->result<LottieColorStop>(frameNo, fill, exp)) return Result::Success;
|
||||
}
|
||||
|
||||
if (!frames) return fill->colorStops(value.data, count);
|
||||
|
||||
if (frames->count == 1 || frameNo <= frames->first().no) {
|
||||
fill->colorStops(frames->first().value.data, count);
|
||||
return;
|
||||
return fill->colorStops(frames->first().value.data, count);
|
||||
}
|
||||
|
||||
if (frameNo >= frames->last().no) {
|
||||
fill->colorStops(frames->last().value.data, count);
|
||||
return;
|
||||
}
|
||||
if (frameNo >= frames->last().no) return fill->colorStops(frames->last().value.data, count);
|
||||
|
||||
auto frame = frames->data + bsearch(frames, frameNo);
|
||||
if (frame->no == frameNo) {
|
||||
fill->colorStops(frame->value.data, count);
|
||||
return;
|
||||
}
|
||||
auto frame = frames->data + _bsearch(frames, frameNo);
|
||||
if (mathEqual(frame->no, frameNo)) return fill->colorStops(frame->value.data, count);
|
||||
|
||||
//interpolate
|
||||
auto t = (frameNo - frame->no) / ((frame + 1)->no - frame->no);
|
||||
@ -451,21 +707,22 @@ struct LottieColorStop : LottieProperty
|
||||
auto a = mathLerp(s->a, e->a, t);
|
||||
result.push({offset, r, g, b, a});
|
||||
}
|
||||
fill->colorStops(result.data, count);
|
||||
return fill->colorStops(result.data, count);
|
||||
}
|
||||
|
||||
LottieColorStop& operator=(const LottieColorStop& other)
|
||||
{
|
||||
//shallow copy, used for slot overriding
|
||||
release();
|
||||
if (other.frames) {
|
||||
frames = other.frames;
|
||||
const_cast<LottieColorStop&>(other).frames = nullptr;
|
||||
} else {
|
||||
value = other.value;
|
||||
const_cast<LottieColorStop&>(other).value.data = nullptr;
|
||||
const_cast<LottieColorStop&>(other).value = {nullptr, nullptr};
|
||||
}
|
||||
populated = other.populated;
|
||||
count = other.count;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
@ -483,8 +740,34 @@ struct LottiePosition : LottieProperty
|
||||
}
|
||||
|
||||
~LottiePosition()
|
||||
{
|
||||
release();
|
||||
}
|
||||
|
||||
void release()
|
||||
{
|
||||
delete(frames);
|
||||
frames = nullptr;
|
||||
|
||||
if (exp) {
|
||||
delete(exp);
|
||||
exp = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t nearest(float frameNo) override
|
||||
{
|
||||
return _nearest(frames, frameNo);
|
||||
}
|
||||
|
||||
uint32_t frameCnt() override
|
||||
{
|
||||
return frames ? frames->count : 1;
|
||||
}
|
||||
|
||||
float frameNo(int32_t key) override
|
||||
{
|
||||
return _frameNo(frames, key);
|
||||
}
|
||||
|
||||
LottieVectorFrame<Point>& newFrame()
|
||||
@ -510,18 +793,28 @@ struct LottiePosition : LottieProperty
|
||||
if (frames->count == 1 || frameNo <= frames->first().no) return frames->first().value;
|
||||
if (frameNo >= frames->last().no) return frames->last().value;
|
||||
|
||||
auto frame = frames->data + bsearch(frames, frameNo);
|
||||
if (frame->no == frameNo) return frame->value;
|
||||
auto frame = frames->data + _bsearch(frames, frameNo);
|
||||
if (mathEqual(frame->no, frameNo)) return frame->value;
|
||||
return frame->interpolate(frame + 1, frameNo);
|
||||
}
|
||||
|
||||
Point operator()(float frameNo, LottieExpressions* exps)
|
||||
{
|
||||
Point out{};
|
||||
if (exps && (exp && exp->enabled)) {
|
||||
if (exp->loop.mode != LottieExpression::LoopMode::None) frameNo = _loop(frames, frameNo, exp);
|
||||
if (exps->result<LottiePosition>(frameNo, out, exp)) return out;
|
||||
}
|
||||
return operator()(frameNo);
|
||||
}
|
||||
|
||||
float angle(float frameNo)
|
||||
{
|
||||
if (!frames) return 0;
|
||||
if (frames->count == 1 || frameNo <= frames->first().no) return 0;
|
||||
if (frameNo >= frames->last().no) return 0;
|
||||
|
||||
auto frame = frames->data + bsearch(frames, frameNo);
|
||||
auto frame = frames->data + _bsearch(frames, frameNo);
|
||||
return frame->angle(frame + 1, frameNo);
|
||||
}
|
||||
|
||||
@ -547,6 +840,11 @@ struct LottieTextDoc : LottieProperty
|
||||
|
||||
void release()
|
||||
{
|
||||
if (exp) {
|
||||
delete(exp);
|
||||
exp = nullptr;
|
||||
}
|
||||
|
||||
if (value.text) {
|
||||
free(value.text);
|
||||
value.text = nullptr;
|
||||
@ -566,6 +864,21 @@ struct LottieTextDoc : LottieProperty
|
||||
frames = nullptr;
|
||||
}
|
||||
|
||||
uint32_t nearest(float frameNo) override
|
||||
{
|
||||
return _nearest(frames, frameNo);
|
||||
}
|
||||
|
||||
uint32_t frameCnt() override
|
||||
{
|
||||
return frames ? frames->count : 1;
|
||||
}
|
||||
|
||||
float frameNo(int32_t key) override
|
||||
{
|
||||
return _frameNo(frames, key);
|
||||
}
|
||||
|
||||
LottieScalarFrame<TextDocument>& newFrame()
|
||||
{
|
||||
if (!frames) frames = new Array<LottieScalarFrame<TextDocument>>;
|
||||
@ -589,14 +902,13 @@ struct LottieTextDoc : LottieProperty
|
||||
if (frames->count == 1 || frameNo <= frames->first().no) return frames->first().value;
|
||||
if (frameNo >= frames->last().no) return frames->last().value;
|
||||
|
||||
auto frame = frames->data + bsearch(frames, frameNo);
|
||||
auto frame = frames->data + _bsearch(frames, frameNo);
|
||||
return frame->value;
|
||||
}
|
||||
|
||||
LottieTextDoc& operator=(const LottieTextDoc& other)
|
||||
{
|
||||
//shallow copy, used for slot overriding
|
||||
release();
|
||||
if (other.frames) {
|
||||
frames = other.frames;
|
||||
const_cast<LottieTextDoc&>(other).frames = nullptr;
|
||||
|
@ -74,7 +74,7 @@ void mathRotate(Matrix* m, float degree)
|
||||
{
|
||||
if (degree == 0.0f) return;
|
||||
|
||||
auto radian = degree / 180.0f * M_PI;
|
||||
auto radian = degree / 180.0f * MATH_PI;
|
||||
auto cosVal = cosf(radian);
|
||||
auto sinVal = sinf(radian);
|
||||
|
||||
|
@ -34,6 +34,7 @@
|
||||
|
||||
#define MATH_PI 3.14159265358979323846f
|
||||
#define MATH_PI2 1.57079632679489661923f
|
||||
#define FLOAT_EPSILON 1.0e-06f //1.192092896e-07f
|
||||
#define PATH_KAPPA 0.552284f
|
||||
|
||||
#define mathMin(x, y) (((x) < (y)) ? (x) : (y))
|
||||
@ -47,15 +48,33 @@ bool mathIdentity(const Matrix* m);
|
||||
void mathMultiply(Point* pt, const Matrix* transform);
|
||||
|
||||
|
||||
static inline float mathDeg2Rad(float degree)
|
||||
{
|
||||
return degree * (MATH_PI / 180.0f);
|
||||
}
|
||||
|
||||
|
||||
static inline float mathRad2Deg(float radian)
|
||||
{
|
||||
return radian * (180.0f / MATH_PI);
|
||||
}
|
||||
|
||||
|
||||
static inline bool mathZero(float a)
|
||||
{
|
||||
return (fabsf(a) < FLT_EPSILON) ? true : false;
|
||||
return (fabsf(a) <= FLOAT_EPSILON) ? true : false;
|
||||
}
|
||||
|
||||
|
||||
static inline bool mathZero(const Point& p)
|
||||
{
|
||||
return mathZero(p.x) && mathZero(p.y);
|
||||
}
|
||||
|
||||
|
||||
static inline bool mathEqual(float a, float b)
|
||||
{
|
||||
return (fabsf(a - b) < FLT_EPSILON);
|
||||
return mathZero(a - b);
|
||||
}
|
||||
|
||||
|
||||
@ -73,14 +92,14 @@ static inline bool mathEqual(const Matrix& a, const Matrix& b)
|
||||
static inline bool mathRightAngle(const Matrix* m)
|
||||
{
|
||||
auto radian = fabsf(atan2f(m->e21, m->e11));
|
||||
if (radian < FLT_EPSILON || mathEqual(radian, float(M_PI_2)) || mathEqual(radian, float(M_PI))) return true;
|
||||
if (radian < FLOAT_EPSILON || mathEqual(radian, MATH_PI2) || mathEqual(radian, MATH_PI)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
static inline bool mathSkewed(const Matrix* m)
|
||||
{
|
||||
return (fabsf(m->e21 + m->e12) > FLT_EPSILON);
|
||||
return !mathZero(m->e21 + m->e12);
|
||||
}
|
||||
|
||||
|
||||
@ -160,6 +179,12 @@ static inline float mathLength(const Point* a, const Point* b)
|
||||
}
|
||||
|
||||
|
||||
static inline float mathLength(const Point& a)
|
||||
{
|
||||
return sqrtf(a.x * a.x + a.y * a.y);
|
||||
}
|
||||
|
||||
|
||||
static inline Point operator-(const Point& lhs, const Point& rhs)
|
||||
{
|
||||
return {lhs.x - rhs.x, lhs.y - rhs.y};
|
||||
|
@ -44,20 +44,26 @@
|
||||
}
|
||||
|
||||
|
||||
static bool _compFastTrack(Paint* cmpTarget, const RenderTransform* pTransform, RenderTransform* rTransform, RenderRegion& viewport)
|
||||
|
||||
static Result _compFastTrack(Paint* cmpTarget, const RenderTransform* pTransform, RenderTransform* rTransform, RenderRegion& viewport)
|
||||
{
|
||||
/* Access Shape class by Paint is bad... but it's ok still it's an internal usage. */
|
||||
auto shape = static_cast<Shape*>(cmpTarget);
|
||||
|
||||
//Rectangle Candidates?
|
||||
const Point* pts;
|
||||
if (shape->pathCoords(&pts) != 4) return false;
|
||||
auto ptsCnt = shape->pathCoords(&pts);
|
||||
|
||||
//nothing to clip
|
||||
if (ptsCnt == 0) return Result::InvalidArguments;
|
||||
|
||||
if (ptsCnt != 4) return Result::InsufficientCondition;
|
||||
|
||||
if (rTransform) rTransform->update();
|
||||
|
||||
//No rotation and no skewing
|
||||
if (pTransform && (!mathRightAngle(&pTransform->m) || mathSkewed(&pTransform->m))) return false;
|
||||
if (rTransform && (!mathRightAngle(&rTransform->m) || mathSkewed(&rTransform->m))) return false;
|
||||
if (pTransform && (!mathRightAngle(&pTransform->m) || mathSkewed(&pTransform->m))) return Result::InsufficientCondition;
|
||||
if (rTransform && (!mathRightAngle(&rTransform->m) || mathSkewed(&rTransform->m))) return Result::InsufficientCondition;
|
||||
|
||||
//Perpendicular Rectangle?
|
||||
auto pt1 = pts + 0;
|
||||
@ -102,10 +108,9 @@ static bool _compFastTrack(Paint* cmpTarget, const RenderTransform* pTransform,
|
||||
if (viewport.w < 0) viewport.w = 0;
|
||||
if (viewport.h < 0) viewport.h = 0;
|
||||
|
||||
return true;
|
||||
return Result::Success;
|
||||
}
|
||||
|
||||
return false;
|
||||
return Result::InsufficientCondition;
|
||||
}
|
||||
|
||||
|
||||
@ -238,7 +243,7 @@ RenderData Paint::Impl::update(RenderMethod* renderer, const RenderTransform* pT
|
||||
/* 1. Composition Pre Processing */
|
||||
RenderData trd = nullptr; //composite target render data
|
||||
RenderRegion viewport;
|
||||
bool compFastTrack = false;
|
||||
Result compFastTrack = Result::InsufficientCondition;
|
||||
bool childClipper = false;
|
||||
|
||||
if (compData) {
|
||||
@ -263,7 +268,7 @@ RenderData Paint::Impl::update(RenderMethod* renderer, const RenderTransform* pT
|
||||
}
|
||||
if (tryFastTrack) {
|
||||
RenderRegion viewport2;
|
||||
if ((compFastTrack = _compFastTrack(target, pTransform, target->pImpl->rTransform, viewport2))) {
|
||||
if ((compFastTrack = _compFastTrack(target, pTransform, target->pImpl->rTransform, viewport2)) == Result::Success) {
|
||||
viewport = renderer->viewport();
|
||||
viewport2.intersect(viewport);
|
||||
renderer->viewport(viewport2);
|
||||
@ -271,7 +276,7 @@ RenderData Paint::Impl::update(RenderMethod* renderer, const RenderTransform* pT
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!compFastTrack) {
|
||||
if (compFastTrack == Result::InsufficientCondition) {
|
||||
childClipper = compData->method == CompositeMethod::ClipPath ? true : false;
|
||||
trd = target->pImpl->update(renderer, pTransform, clips, 255, pFlag, childClipper);
|
||||
if (childClipper) clips.push(trd);
|
||||
@ -288,7 +293,7 @@ RenderData Paint::Impl::update(RenderMethod* renderer, const RenderTransform* pT
|
||||
PAINT_METHOD(rd, update(renderer, &outTransform, clips, opacity, newFlag, clipper));
|
||||
|
||||
/* 3. Composition Post Processing */
|
||||
if (compFastTrack) renderer->viewport(viewport);
|
||||
if (compFastTrack == Result::Success) renderer->viewport(viewport);
|
||||
else if (childClipper) clips.pop();
|
||||
|
||||
return rd;
|
||||
|
@ -32,7 +32,9 @@
|
||||
RenderUpdateFlag Picture::Impl::load()
|
||||
{
|
||||
if (loader) {
|
||||
if (!paint) {
|
||||
if (paint) {
|
||||
loader->sync();
|
||||
} else {
|
||||
paint = loader->paint();
|
||||
if (paint) {
|
||||
if (w != loader->w || h != loader->h) {
|
||||
@ -45,8 +47,7 @@ RenderUpdateFlag Picture::Impl::load()
|
||||
}
|
||||
return RenderUpdateFlag::None;
|
||||
}
|
||||
} else loader->sync();
|
||||
|
||||
}
|
||||
if (!surface) {
|
||||
if ((surface = loader->bitmap())) {
|
||||
return RenderUpdateFlag::Image;
|
||||
@ -136,7 +137,7 @@ Result Picture::Impl::load(ImageLoader* loader)
|
||||
if (!loader->read()) return Result::Unknown;
|
||||
|
||||
this->w = loader->w;
|
||||
this->h = loader->h;
|
||||
this->h = loader->h;
|
||||
|
||||
return Result::Success;
|
||||
}
|
||||
|
@ -64,5 +64,37 @@ RenderTransform::RenderTransform(const RenderTransform* lhs, const RenderTransfo
|
||||
else mathIdentity(&m);
|
||||
}
|
||||
|
||||
|
||||
void RenderRegion::intersect(const RenderRegion& rhs)
|
||||
{
|
||||
auto x1 = x + w;
|
||||
auto y1 = y + h;
|
||||
auto x2 = rhs.x + rhs.w;
|
||||
auto y2 = rhs.y + rhs.h;
|
||||
|
||||
x = (x > rhs.x) ? x : rhs.x;
|
||||
y = (y > rhs.y) ? y : rhs.y;
|
||||
w = ((x1 < x2) ? x1 : x2) - x;
|
||||
h = ((y1 < y2) ? y1 : y2) - y;
|
||||
|
||||
if (w < 0) w = 0;
|
||||
if (h < 0) h = 0;
|
||||
}
|
||||
|
||||
|
||||
void RenderRegion::add(const RenderRegion& rhs)
|
||||
{
|
||||
if (rhs.x < x) {
|
||||
w += (x - rhs.x);
|
||||
x = rhs.x;
|
||||
}
|
||||
if (rhs.y < y) {
|
||||
h += (y - rhs.y);
|
||||
y = rhs.y;
|
||||
}
|
||||
if (rhs.x + rhs.w > x + w) w = (rhs.x + rhs.w) - x;
|
||||
if (rhs.y + rhs.h > y + h) h = (rhs.y + rhs.h) - y;
|
||||
}
|
||||
|
||||
#endif /* LV_USE_THORVG_INTERNAL */
|
||||
|
||||
|
@ -103,34 +103,13 @@ struct RenderRegion
|
||||
{
|
||||
int32_t x, y, w, h;
|
||||
|
||||
void intersect(const RenderRegion& rhs)
|
||||
void intersect(const RenderRegion& rhs);
|
||||
void add(const RenderRegion& rhs);
|
||||
|
||||
bool operator==(const RenderRegion& rhs)
|
||||
{
|
||||
auto x1 = x + w;
|
||||
auto y1 = y + h;
|
||||
auto x2 = rhs.x + rhs.w;
|
||||
auto y2 = rhs.y + rhs.h;
|
||||
|
||||
x = (x > rhs.x) ? x : rhs.x;
|
||||
y = (y > rhs.y) ? y : rhs.y;
|
||||
w = ((x1 < x2) ? x1 : x2) - x;
|
||||
h = ((y1 < y2) ? y1 : y2) - y;
|
||||
|
||||
if (w < 0) w = 0;
|
||||
if (h < 0) h = 0;
|
||||
}
|
||||
|
||||
void add(const RenderRegion& rhs)
|
||||
{
|
||||
if (rhs.x < x) {
|
||||
w += (x - rhs.x);
|
||||
x = rhs.x;
|
||||
}
|
||||
if (rhs.y < y) {
|
||||
h += (y - rhs.y);
|
||||
y = rhs.y;
|
||||
}
|
||||
if (rhs.x + rhs.w > x + w) w = (rhs.x + rhs.w) - x;
|
||||
if (rhs.y + rhs.h > y + h) h = (rhs.y + rhs.h) - y;
|
||||
if (x == rhs.x && y == rhs.y && w == rhs.w && h == rhs.h) return true;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
@ -166,6 +145,7 @@ struct RenderStroke
|
||||
struct {
|
||||
float begin = 0.0f;
|
||||
float end = 1.0f;
|
||||
bool individual = false;
|
||||
} trim;
|
||||
|
||||
~RenderStroke()
|
||||
@ -295,6 +275,7 @@ public:
|
||||
virtual bool viewport(const RenderRegion& vp) = 0;
|
||||
virtual bool blend(BlendMethod method) = 0;
|
||||
virtual ColorSpace colorSpace() = 0;
|
||||
virtual const Surface* mainSurface() = 0;
|
||||
|
||||
virtual bool clear() = 0;
|
||||
virtual bool sync() = 0;
|
||||
|
@ -160,14 +160,17 @@ Result Saver::save(unique_ptr<Animation> animation, const string& path, uint32_t
|
||||
auto a = animation.release();
|
||||
if (!a) return Result::MemoryCorruption;
|
||||
|
||||
//animation holds the picture, it must be 1 at the bottom.
|
||||
auto remove = PP(a->picture())->refCnt <= 1 ? true : false;
|
||||
|
||||
if (mathZero(a->totalFrame())) {
|
||||
delete(a);
|
||||
if (remove) delete(a);
|
||||
return Result::InsufficientCondition;
|
||||
}
|
||||
|
||||
//Already on saving an other resource.
|
||||
if (pImpl->saveModule) {
|
||||
delete(a);
|
||||
if (remove) delete(a);
|
||||
return Result::InsufficientCondition;
|
||||
}
|
||||
|
||||
@ -176,12 +179,12 @@ Result Saver::save(unique_ptr<Animation> animation, const string& path, uint32_t
|
||||
pImpl->saveModule = saveModule;
|
||||
return Result::Success;
|
||||
} else {
|
||||
delete(a);
|
||||
if (remove) delete(a);
|
||||
delete(saveModule);
|
||||
return Result::Unknown;
|
||||
}
|
||||
}
|
||||
delete(a);
|
||||
if (remove) delete(a);
|
||||
return Result::NonSupport;
|
||||
}
|
||||
|
||||
|
@ -136,7 +136,6 @@ struct Scene::Impl
|
||||
if (needComp) {
|
||||
cmp = renderer->target(bounds(renderer), renderer->colorSpace());
|
||||
renderer->beginComposite(cmp, CompositeMethod::None, opacity);
|
||||
needComp = false;
|
||||
}
|
||||
|
||||
for (auto paint : paints) {
|
||||
|
@ -148,13 +148,15 @@ Result Shape::appendArc(float cx, float cy, float radius, float startAngle, floa
|
||||
//just circle
|
||||
if (sweep >= 360.0f || sweep <= -360.0f) return appendCircle(cx, cy, radius, radius);
|
||||
|
||||
startAngle = (startAngle * MATH_PI) / 180.0f;
|
||||
sweep = sweep * MATH_PI / 180.0f;
|
||||
const float arcPrecision = 1e-5f;
|
||||
startAngle = mathDeg2Rad(startAngle);
|
||||
sweep = mathDeg2Rad(sweep);
|
||||
|
||||
auto nCurves = ceil(fabsf(sweep / MATH_PI2));
|
||||
auto nCurves = static_cast<int>(fabsf(sweep / MATH_PI2));
|
||||
if (fabsf(sweep / MATH_PI2) - nCurves > arcPrecision) ++nCurves;
|
||||
auto sweepSign = (sweep < 0 ? -1 : 1);
|
||||
auto fract = fmodf(sweep, MATH_PI2);
|
||||
fract = (mathZero(fract)) ? MATH_PI2 * sweepSign : fract;
|
||||
fract = (fabsf(fract) < arcPrecision) ? MATH_PI2 * sweepSign : fract;
|
||||
|
||||
//Start from here
|
||||
Point start = {radius * cosf(startAngle), radius * sinf(startAngle)};
|
||||
@ -167,7 +169,7 @@ Result Shape::appendArc(float cx, float cy, float radius, float startAngle, floa
|
||||
}
|
||||
|
||||
for (int i = 0; i < nCurves; ++i) {
|
||||
auto endAngle = startAngle + ((i != nCurves - 1) ? float(M_PI_2) * sweepSign : fract);
|
||||
auto endAngle = startAngle + ((i != nCurves - 1) ? MATH_PI2 * sweepSign : fract);
|
||||
Point end = {radius * cosf(endAngle), radius * sinf(endAngle)};
|
||||
|
||||
//variables needed to calculate bezier control points
|
||||
|
@ -59,7 +59,6 @@ struct Shape::Impl
|
||||
if (needComp) {
|
||||
cmp = renderer->target(bounds(renderer), renderer->colorSpace());
|
||||
renderer->beginComposite(cmp, CompositeMethod::None, opacity);
|
||||
needComp = false;
|
||||
}
|
||||
ret = renderer->renderShape(rd);
|
||||
if (cmp) renderer->endComposite(cmp);
|
||||
@ -71,7 +70,7 @@ struct Shape::Impl
|
||||
if (opacity == 0) return false;
|
||||
|
||||
//Shape composition is only necessary when stroking & fill are valid.
|
||||
if (!rs.stroke || rs.stroke->width < FLT_EPSILON || (!rs.stroke->fill && rs.stroke->color[3] == 0)) return false;
|
||||
if (!rs.stroke || rs.stroke->width < FLOAT_EPSILON || (!rs.stroke->fill && rs.stroke->color[3] == 0)) return false;
|
||||
if (!rs.fill && rs.color[3] == 0) return false;
|
||||
|
||||
//translucent fill & stroke
|
||||
@ -80,17 +79,30 @@ struct Shape::Impl
|
||||
//Composition test
|
||||
const Paint* target;
|
||||
auto method = shape->composite(&target);
|
||||
if (!target || method == tvg::CompositeMethod::ClipPath) return false;
|
||||
if (target->pImpl->opacity == 255 || target->pImpl->opacity == 0) return false;
|
||||
if (!target || method == CompositeMethod::ClipPath) return false;
|
||||
if (target->pImpl->opacity == 255 || target->pImpl->opacity == 0) {
|
||||
if (target->identifier() == TVG_CLASS_ID_SHAPE) {
|
||||
auto shape = static_cast<const Shape*>(target);
|
||||
if (!shape->fill()) {
|
||||
uint8_t r, g, b, a;
|
||||
shape->fillColor(&r, &g, &b, &a);
|
||||
if (a == 0 || a == 255) {
|
||||
if (method == CompositeMethod::LumaMask || method == CompositeMethod::InvLumaMask) {
|
||||
if ((r == 255 && g == 255 && b == 255) || (r == 0 && g == 0 && b == 0)) return false;
|
||||
} else return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
RenderData update(RenderMethod* renderer, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper)
|
||||
{
|
||||
{
|
||||
if ((needComp = needComposition(opacity))) {
|
||||
/* Overriding opacity value. If this scene is half-translucent,
|
||||
It must do intermeidate composition with that opacity value. */
|
||||
It must do intermeidate composition with that opacity value. */
|
||||
this->opacity = opacity;
|
||||
opacity = 255;
|
||||
}
|
||||
@ -207,7 +219,7 @@ struct Shape::Impl
|
||||
return true;
|
||||
}
|
||||
|
||||
bool strokeTrim(float begin, float end)
|
||||
bool strokeTrim(float begin, float end, bool individual)
|
||||
{
|
||||
if (!rs.stroke) {
|
||||
if (begin == 0.0f && end == 1.0f) return true;
|
||||
@ -218,6 +230,7 @@ struct Shape::Impl
|
||||
|
||||
rs.stroke->trim.begin = begin;
|
||||
rs.stroke->trim.end = end;
|
||||
rs.stroke->trim.individual = individual;
|
||||
flag |= RenderUpdateFlag::Stroke;
|
||||
|
||||
return true;
|
||||
@ -291,7 +304,7 @@ struct Shape::Impl
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < cnt; i++) {
|
||||
if (pattern[i] < FLT_EPSILON) return Result::InvalidArguments;
|
||||
if (pattern[i] < FLOAT_EPSILON) return Result::InvalidArguments;
|
||||
}
|
||||
|
||||
//Reset dash
|
||||
|
@ -24,6 +24,7 @@
|
||||
#if LV_USE_THORVG_INTERNAL
|
||||
|
||||
#include "config.h"
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <memory.h>
|
||||
#include "tvgMath.h"
|
||||
@ -200,6 +201,8 @@ float strToFloat(const char *nPtr, char **endPtr)
|
||||
|
||||
success:
|
||||
if (endPtr) *endPtr = (char *)(a);
|
||||
if (!std::isfinite(val)) return 0.0f;
|
||||
|
||||
return minus * val;
|
||||
|
||||
error:
|
||||
|
@ -52,6 +52,22 @@ static void _copyStyle(SvgStyleProperty* to, const SvgStyleProperty* from)
|
||||
to->flagsImportance = (to->flagsImportance | SvgStyleFlags::Color);
|
||||
}
|
||||
}
|
||||
if (((from->flags & SvgStyleFlags::PaintOrder) && !(to->flags & SvgStyleFlags::PaintOrder)) ||
|
||||
_isImportanceApplicable(to->flagsImportance, from->flagsImportance, SvgStyleFlags::PaintOrder)) {
|
||||
to->paintOrder = from->paintOrder;
|
||||
to->flags = (to->flags | SvgStyleFlags::PaintOrder);
|
||||
if (from->flagsImportance & SvgStyleFlags::PaintOrder) {
|
||||
to->flagsImportance = (to->flagsImportance | SvgStyleFlags::PaintOrder);
|
||||
}
|
||||
}
|
||||
if (((from->flags & SvgStyleFlags::Display) && !(to->flags & SvgStyleFlags::Display)) ||
|
||||
_isImportanceApplicable(to->flagsImportance, from->flagsImportance, SvgStyleFlags::Display)) {
|
||||
to->display = from->display;
|
||||
to->flags = (to->flags | SvgStyleFlags::Display);
|
||||
if (from->flagsImportance & SvgStyleFlags::Display) {
|
||||
to->flagsImportance = (to->flagsImportance | SvgStyleFlags::Display);
|
||||
}
|
||||
}
|
||||
//Fill
|
||||
if (((from->fill.flags & SvgFillFlags::Paint) && !(to->flags & SvgStyleFlags::Fill)) ||
|
||||
_isImportanceApplicable(to->flagsImportance, from->flagsImportance, SvgStyleFlags::Fill)) {
|
||||
|
@ -106,15 +106,19 @@ static const char* _skipComma(const char* content)
|
||||
}
|
||||
|
||||
|
||||
static bool _parseNumber(const char** content, float* number)
|
||||
static bool _parseNumber(const char** content, const char** end, float* number)
|
||||
{
|
||||
char* end = nullptr;
|
||||
const char* _end = end ? *end : nullptr;
|
||||
|
||||
*number = strToFloat(*content, &end);
|
||||
*number = strToFloat(*content, (char**)&_end);
|
||||
//If the start of string is not number
|
||||
if ((*content) == end) return false;
|
||||
if ((*content) == _end) {
|
||||
if (end) *end = _end;
|
||||
return false;
|
||||
}
|
||||
//Skip comma if any
|
||||
*content = _skipComma(end);
|
||||
*content = _skipComma(_end);
|
||||
if (end) *end = _end;
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -579,7 +583,82 @@ static constexpr struct
|
||||
};
|
||||
|
||||
|
||||
static void _toColor(const char* str, uint8_t* r, uint8_t* g, uint8_t* b, char** ref)
|
||||
static bool _hslToRgb(float hue, float satuation, float brightness, uint8_t* red, uint8_t* green, uint8_t* blue)
|
||||
{
|
||||
if (!red || !green || !blue) return false;
|
||||
|
||||
float sv, vsf, f, p, q, t, v;
|
||||
float _red = 0, _green = 0, _blue = 0;
|
||||
uint32_t i = 0;
|
||||
|
||||
if (mathZero(satuation)) _red = _green = _blue = brightness;
|
||||
else {
|
||||
if (mathEqual(hue, 360.0)) hue = 0.0f;
|
||||
hue /= 60.0f;
|
||||
|
||||
v = (brightness <= 0.5f) ? (brightness * (1.0f + satuation)) : (brightness + satuation - (brightness * satuation));
|
||||
p = brightness + brightness - v;
|
||||
|
||||
if (!mathZero(v)) sv = (v - p) / v;
|
||||
else sv = 0;
|
||||
|
||||
i = static_cast<uint8_t>(hue);
|
||||
f = hue - i;
|
||||
|
||||
vsf = v * sv * f;
|
||||
|
||||
t = p + vsf;
|
||||
q = v - vsf;
|
||||
|
||||
switch (i) {
|
||||
case 0: {
|
||||
_red = v;
|
||||
_green = t;
|
||||
_blue = p;
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
_red = q;
|
||||
_green = v;
|
||||
_blue = p;
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
_red = p;
|
||||
_green = v;
|
||||
_blue = t;
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
_red = p;
|
||||
_green = q;
|
||||
_blue = v;
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
_red = t;
|
||||
_green = p;
|
||||
_blue = v;
|
||||
break;
|
||||
}
|
||||
case 5: {
|
||||
_red = v;
|
||||
_green = p;
|
||||
_blue = q;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*red = static_cast<uint8_t>(roundf(_red * 255.0f));
|
||||
*green = static_cast<uint8_t>(roundf(_green * 255.0f));
|
||||
*blue = static_cast<uint8_t>(roundf(_blue * 255.0f));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static bool _toColor(const char* str, uint8_t* r, uint8_t* g, uint8_t* b, char** ref)
|
||||
{
|
||||
unsigned int len = strlen(str);
|
||||
char *red, *green, *blue;
|
||||
@ -599,6 +678,7 @@ static void _toColor(const char* str, uint8_t* r, uint8_t* g, uint8_t* b, char**
|
||||
tmp[1] = str[3];
|
||||
*b = strtol(tmp, nullptr, 16);
|
||||
}
|
||||
return true;
|
||||
} else if (len == 7 && str[0] == '#') {
|
||||
if (isxdigit(str[1]) && isxdigit(str[2]) && isxdigit(str[3]) && isxdigit(str[4]) && isxdigit(str[5]) && isxdigit(str[6])) {
|
||||
char tmp[3] = { '\0', '\0', '\0' };
|
||||
@ -612,6 +692,7 @@ static void _toColor(const char* str, uint8_t* r, uint8_t* g, uint8_t* b, char**
|
||||
tmp[1] = str[6];
|
||||
*b = strtol(tmp, nullptr, 16);
|
||||
}
|
||||
return true;
|
||||
} else if (len >= 10 && (str[0] == 'r' || str[0] == 'R') && (str[1] == 'g' || str[1] == 'G') && (str[2] == 'b' || str[2] == 'B') && str[3] == '(' && str[len - 1] == ')') {
|
||||
tr = _parseColor(str + 4, &red);
|
||||
if (red && *red == ',') {
|
||||
@ -625,9 +706,35 @@ static void _toColor(const char* str, uint8_t* r, uint8_t* g, uint8_t* b, char**
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else if (ref && len >= 3 && !strncmp(str, "url", 3)) {
|
||||
if (*ref) free(*ref);
|
||||
*ref = _idFromUrl((const char*)(str + 3));
|
||||
return true;
|
||||
} else if (len >= 10 && (str[0] == 'h' || str[0] == 'H') && (str[1] == 's' || str[1] == 'S') && (str[2] == 'l' || str[2] == 'L') && str[3] == '(' && str[len - 1] == ')') {
|
||||
float th, ts, tb;
|
||||
const char *content, *hue, *satuation, *brightness;
|
||||
content = str + 4;
|
||||
content = _skipSpace(content, nullptr);
|
||||
if (_parseNumber(&content, &hue, &th) && hue) {
|
||||
th = float(uint32_t(th) % 360);
|
||||
hue = _skipSpace(hue, nullptr);
|
||||
hue = (char*)_skipComma(hue);
|
||||
hue = _skipSpace(hue, nullptr);
|
||||
if (_parseNumber(&hue, &satuation, &ts) && satuation && *satuation == '%') {
|
||||
ts /= 100.0f;
|
||||
satuation = _skipSpace(satuation + 1, nullptr);
|
||||
satuation = (char*)_skipComma(satuation);
|
||||
satuation = _skipSpace(satuation, nullptr);
|
||||
if (_parseNumber(&satuation, &brightness, &tb) && brightness && *brightness == '%') {
|
||||
tb /= 100.0f;
|
||||
brightness = _skipSpace(brightness + 1, nullptr);
|
||||
if (brightness && brightness[0] == ')' && brightness[1] == '\0') {
|
||||
return _hslToRgb(th, ts, tb, r, g, b);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//Handle named color
|
||||
for (unsigned int i = 0; i < (sizeof(colors) / sizeof(colors[0])); i++) {
|
||||
@ -635,10 +742,11 @@ static void _toColor(const char* str, uint8_t* r, uint8_t* g, uint8_t* b, char**
|
||||
*r = (((uint8_t*)(&(colors[i].value)))[2]);
|
||||
*g = (((uint8_t*)(&(colors[i].value)))[1]);
|
||||
*b = (((uint8_t*)(&(colors[i].value)))[0]);
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@ -746,10 +854,10 @@ static Matrix* _parseTransformationMatrix(const char* value)
|
||||
} else goto error;
|
||||
} else if (state == MatrixState::Rotate) {
|
||||
//Transform to signed.
|
||||
points[0] = fmod(points[0], 360);
|
||||
if (points[0] < 0) points[0] += 360;
|
||||
auto c = cosf(points[0] * (M_PI / 180.0));
|
||||
auto s = sinf(points[0] * (M_PI / 180.0));
|
||||
points[0] = fmodf(points[0], 360.0f);
|
||||
if (points[0] < 0) points[0] += 360.0f;
|
||||
auto c = cosf(mathDeg2Rad(points[0]));
|
||||
auto s = sinf(mathDeg2Rad(points[0]));
|
||||
if (ptCount == 1) {
|
||||
Matrix tmp = { c, -s, 0, s, c, 0, 0, 0, 1 };
|
||||
*matrix = mathMultiply(matrix, &tmp);
|
||||
@ -772,12 +880,12 @@ static Matrix* _parseTransformationMatrix(const char* value)
|
||||
*matrix = mathMultiply(matrix, &tmp);
|
||||
} else if (state == MatrixState::SkewX) {
|
||||
if (ptCount != 1) goto error;
|
||||
auto deg = tanf(points[0] * (M_PI / 180.0));
|
||||
auto deg = tanf(mathDeg2Rad(points[0]));
|
||||
Matrix tmp = { 1, deg, 0, 0, 1, 0, 0, 0, 1 };
|
||||
*matrix = mathMultiply(matrix, &tmp);
|
||||
} else if (state == MatrixState::SkewY) {
|
||||
if (ptCount != 1) goto error;
|
||||
auto deg = tanf(points[0] * (M_PI / 180.0));
|
||||
auto deg = tanf(mathDeg2Rad(points[0]));
|
||||
Matrix tmp = { 1, 0, 0, deg, 1, 0, 0, 0, 1 };
|
||||
*matrix = mathMultiply(matrix, &tmp);
|
||||
}
|
||||
@ -857,10 +965,10 @@ static bool _attrParseSvgNode(void* data, const char* key, const char* value)
|
||||
doc->viewFlag = (doc->viewFlag | SvgViewFlag::Height);
|
||||
}
|
||||
} else if (!strcmp(key, "viewBox")) {
|
||||
if (_parseNumber(&value, &doc->vx)) {
|
||||
if (_parseNumber(&value, &doc->vy)) {
|
||||
if (_parseNumber(&value, &doc->vw)) {
|
||||
if (_parseNumber(&value, &doc->vh)) {
|
||||
if (_parseNumber(&value, nullptr, &doc->vx)) {
|
||||
if (_parseNumber(&value, nullptr, &doc->vy)) {
|
||||
if (_parseNumber(&value, nullptr, &doc->vw)) {
|
||||
if (_parseNumber(&value, nullptr, &doc->vh)) {
|
||||
doc->viewFlag = (doc->viewFlag | SvgViewFlag::Viewbox);
|
||||
loader->svgParse->global.h = doc->vh;
|
||||
}
|
||||
@ -883,7 +991,7 @@ static bool _attrParseSvgNode(void* data, const char* key, const char* value)
|
||||
} else if (!strcmp(key, "style")) {
|
||||
return simpleXmlParseW3CAttribute(value, strlen(value), _parseStyleAttr, loader);
|
||||
#ifdef THORVG_LOG_ENABLED
|
||||
} else if ((!strcmp(key, "x") || !strcmp(key, "y")) && fabsf(strToFloat(value, nullptr)) > FLT_EPSILON) {
|
||||
} else if ((!strcmp(key, "x") || !strcmp(key, "y")) && fabsf(strToFloat(value, nullptr)) > FLOAT_EPSILON) {
|
||||
TVGLOG("SVG", "Unsupported attributes used [Elements type: Svg][Attribute: %s][Value: %s]", key, value);
|
||||
#endif
|
||||
} else {
|
||||
@ -901,20 +1009,21 @@ static void _handlePaintAttr(SvgPaint* paint, const char* value)
|
||||
paint->none = true;
|
||||
return;
|
||||
}
|
||||
paint->none = false;
|
||||
if (!strcmp(value, "currentColor")) {
|
||||
paint->curColor = true;
|
||||
paint->none = false;
|
||||
return;
|
||||
}
|
||||
_toColor(value, &paint->color.r, &paint->color.g, &paint->color.b, &paint->url);
|
||||
if (_toColor(value, &paint->color.r, &paint->color.g, &paint->color.b, &paint->url)) paint->none = false;
|
||||
}
|
||||
|
||||
|
||||
static void _handleColorAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value)
|
||||
{
|
||||
SvgStyleProperty* style = node->style;
|
||||
style->curColorSet = true;
|
||||
_toColor(value, &style->color.r, &style->color.g, &style->color.b, nullptr);
|
||||
if (_toColor(value, &style->color.r, &style->color.g, &style->color.b, nullptr)) {
|
||||
style->curColorSet = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -998,6 +1107,7 @@ static void _handleFillRuleAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node,
|
||||
|
||||
static void _handleOpacityAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value)
|
||||
{
|
||||
node->style->flags = (node->style->flags | SvgStyleFlags::Opacity);
|
||||
node->style->opacity = _toOpacity(value);
|
||||
}
|
||||
|
||||
@ -1049,8 +1159,9 @@ static void _handleDisplayAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node,
|
||||
// The default is "inline" which means visible and "none" means invisible.
|
||||
// Depending on the type of node, additional functionality may be required.
|
||||
// refer to https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/display
|
||||
if (!strcmp(value, "none")) node->display = false;
|
||||
else node->display = true;
|
||||
node->style->flags = (node->style->flags | SvgStyleFlags::Display);
|
||||
if (!strcmp(value, "none")) node->style->display = false;
|
||||
else node->style->display = true;
|
||||
}
|
||||
|
||||
|
||||
@ -1270,8 +1381,8 @@ static bool _attrParseSymbolNode(void* data, const char* key, const char* value)
|
||||
SvgSymbolNode* symbol = &(node->node.symbol);
|
||||
|
||||
if (!strcmp(key, "viewBox")) {
|
||||
if (!_parseNumber(&value, &symbol->vx) || !_parseNumber(&value, &symbol->vy)) return false;
|
||||
if (!_parseNumber(&value, &symbol->vw) || !_parseNumber(&value, &symbol->vh)) return false;
|
||||
if (!_parseNumber(&value, nullptr, &symbol->vx) || !_parseNumber(&value, nullptr, &symbol->vy)) return false;
|
||||
if (!_parseNumber(&value, nullptr, &symbol->vw) || !_parseNumber(&value, nullptr, &symbol->vh)) return false;
|
||||
symbol->hasViewBox = true;
|
||||
} else if (!strcmp(key, "width")) {
|
||||
symbol->w = _toFloat(loader->svgParse, value, SvgParserLengthType::Horizontal);
|
||||
@ -1335,7 +1446,7 @@ static SvgNode* _createNode(SvgNode* parent, SvgNodeType type)
|
||||
node->style->paintOrder = _toPaintOrder("fill stroke");
|
||||
|
||||
//Default display is true("inline").
|
||||
node->display = true;
|
||||
node->style->display = true;
|
||||
|
||||
node->parent = parent;
|
||||
node->type = type;
|
||||
@ -1411,7 +1522,7 @@ static SvgNode* _createClipPathNode(SvgLoaderData* loader, SvgNode* parent, cons
|
||||
loader->svgParse->node = _createNode(parent, SvgNodeType::ClipPath);
|
||||
if (!loader->svgParse->node) return nullptr;
|
||||
|
||||
loader->svgParse->node->display = false;
|
||||
loader->svgParse->node->style->display = false;
|
||||
loader->svgParse->node->node.clip.userSpace = true;
|
||||
|
||||
func(buf, bufLength, _attrParseClipPathNode, loader);
|
||||
@ -1436,7 +1547,6 @@ static SvgNode* _createSymbolNode(SvgLoaderData* loader, SvgNode* parent, const
|
||||
loader->svgParse->node = _createNode(parent, SvgNodeType::Symbol);
|
||||
if (!loader->svgParse->node) return nullptr;
|
||||
|
||||
loader->svgParse->node->display = false;
|
||||
loader->svgParse->node->node.symbol.align = AspectRatioAlign::XMidYMid;
|
||||
loader->svgParse->node->node.symbol.meetOrSlice = AspectRatioMeetOrSlice::Meet;
|
||||
loader->svgParse->node->node.symbol.overflowVisible = false;
|
||||
@ -1618,8 +1728,11 @@ static SvgNode* _createEllipseNode(SvgLoaderData* loader, SvgNode* parent, const
|
||||
|
||||
static bool _attrParsePolygonPoints(const char* str, SvgPolygonNode* polygon)
|
||||
{
|
||||
float num;
|
||||
while (_parseNumber(&str, &num)) polygon->pts.push(num);
|
||||
float num_x, num_y;
|
||||
while (_parseNumber(&str, nullptr, &num_x) && _parseNumber(&str, nullptr, &num_y)) {
|
||||
polygon->pts.push(num_x);
|
||||
polygon->pts.push(num_y);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1714,8 +1827,8 @@ static bool _attrParseRectNode(void* data, const char* key, const char* value)
|
||||
if (!strncmp(rectTags[i].tag, "rx", sz)) rect->hasRx = true;
|
||||
if (!strncmp(rectTags[i].tag, "ry", sz)) rect->hasRy = true;
|
||||
|
||||
if ((rect->rx >= FLT_EPSILON) && (rect->ry < FLT_EPSILON) && rect->hasRx && !rect->hasRy) rect->ry = rect->rx;
|
||||
if ((rect->ry >= FLT_EPSILON) && (rect->rx < FLT_EPSILON) && !rect->hasRx && rect->hasRy) rect->rx = rect->ry;
|
||||
if ((rect->rx >= FLOAT_EPSILON) && (rect->ry < FLOAT_EPSILON) && rect->hasRx && !rect->hasRy) rect->ry = rect->rx;
|
||||
if ((rect->ry >= FLOAT_EPSILON) && (rect->rx < FLOAT_EPSILON) && !rect->hasRx && rect->hasRy) rect->rx = rect->ry;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
@ -1922,6 +2035,19 @@ static SvgNode* _findNodeById(SvgNode *node, const char* id)
|
||||
}
|
||||
|
||||
|
||||
static SvgNode* _findParentById(SvgNode* node, char* id, SvgNode* doc)
|
||||
{
|
||||
SvgNode *parent = node->parent;
|
||||
while (parent != nullptr && parent != doc) {
|
||||
if (parent->id && !strcmp(parent->id, id)) {
|
||||
return parent;
|
||||
}
|
||||
parent = parent->parent;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
static constexpr struct
|
||||
{
|
||||
const char* tag;
|
||||
@ -1962,8 +2088,12 @@ static bool _attrParseUseNode(void* data, const char* key, const char* value)
|
||||
defs = _getDefsNode(node);
|
||||
nodeFrom = _findNodeById(defs, id);
|
||||
if (nodeFrom) {
|
||||
_cloneNode(nodeFrom, node, 0);
|
||||
if (nodeFrom->type == SvgNodeType::Symbol) use->symbol = nodeFrom;
|
||||
if (!_findParentById(node, id, loader->doc)) {
|
||||
_cloneNode(nodeFrom, node, 0);
|
||||
if (nodeFrom->type == SvgNodeType::Symbol) use->symbol = nodeFrom;
|
||||
} else {
|
||||
TVGLOG("SVG", "%s is ancestor element. This reference is invalid.", id);
|
||||
}
|
||||
free(id);
|
||||
} else {
|
||||
//some svg export software include <defs> element at the end of the file
|
||||
@ -2364,8 +2494,9 @@ static bool _attrParseStopsStyle(void* data, const char* key, const char* value)
|
||||
stop->a = _toOpacity(value);
|
||||
loader->svgParse->flags = (loader->svgParse->flags | SvgStopStyleFlags::StopOpacity);
|
||||
} else if (!strcmp(key, "stop-color")) {
|
||||
_toColor(value, &stop->r, &stop->g, &stop->b, nullptr);
|
||||
loader->svgParse->flags = (loader->svgParse->flags | SvgStopStyleFlags::StopColor);
|
||||
if (_toColor(value, &stop->r, &stop->g, &stop->b, nullptr)) {
|
||||
loader->svgParse->flags = (loader->svgParse->flags | SvgStopStyleFlags::StopColor);
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@ -2672,7 +2803,7 @@ static void _inheritGradient(SvgLoaderData* loader, SvgStyleGradient* to, SvgSty
|
||||
if (to->transform) memcpy(to->transform, from->transform, sizeof(Matrix));
|
||||
}
|
||||
|
||||
if (to->type == SvgGradientType::Linear && from->type == SvgGradientType::Linear) {
|
||||
if (to->type == SvgGradientType::Linear) {
|
||||
for (unsigned int i = 0; i < sizeof(linear_tags) / sizeof(linear_tags[0]); i++) {
|
||||
bool coordSet = to->flags & linear_tags[i].flag;
|
||||
if (!(to->flags & linear_tags[i].flag) && (from->flags & linear_tags[i].flag)) {
|
||||
@ -2689,7 +2820,7 @@ static void _inheritGradient(SvgLoaderData* loader, SvgStyleGradient* to, SvgSty
|
||||
linear_tags[i].tagInheritedRecalc(loader, to->linear, to->userSpace);
|
||||
}
|
||||
}
|
||||
} else if (to->type == SvgGradientType::Radial && from->type == SvgGradientType::Radial) {
|
||||
} else if (to->type == SvgGradientType::Radial) {
|
||||
for (unsigned int i = 0; i < sizeof(radialTags) / sizeof(radialTags[0]); i++) {
|
||||
bool coordSet = (to->flags & radialTags[i].flag);
|
||||
if (!(to->flags & radialTags[i].flag) && (from->flags & radialTags[i].flag)) {
|
||||
@ -2699,10 +2830,16 @@ static void _inheritGradient(SvgLoaderData* loader, SvgStyleGradient* to, SvgSty
|
||||
//GradUnits not set directly, coord set
|
||||
if (!gradUnitSet && coordSet) {
|
||||
radialTags[i].tagRecalc(loader, to->radial, to->userSpace);
|
||||
//If fx and fy are not set, set cx and cy.
|
||||
if (!strcmp(radialTags[i].tag, "cx") && !(to->flags & SvgGradientFlags::Fx)) to->radial->fx = to->radial->cx;
|
||||
if (!strcmp(radialTags[i].tag, "cy") && !(to->flags & SvgGradientFlags::Fy)) to->radial->fy = to->radial->cy;
|
||||
}
|
||||
//GradUnits set, coord not set directly
|
||||
if (to->userSpace == from->userSpace) continue;
|
||||
if (gradUnitSet && !coordSet) {
|
||||
//If fx and fx are not set, do not call recalc.
|
||||
if (!strcmp(radialTags[i].tag, "fx") && !(to->flags & SvgGradientFlags::Fx)) continue;
|
||||
if (!strcmp(radialTags[i].tag, "fy") && !(to->flags & SvgGradientFlags::Fy)) continue;
|
||||
radialTags[i].tagInheritedRecalc(loader, to->radial, to->userSpace);
|
||||
}
|
||||
}
|
||||
@ -2829,9 +2966,15 @@ static void _styleCopy(SvgStyleProperty* to, const SvgStyleProperty* from)
|
||||
to->color = from->color;
|
||||
to->curColorSet = true;
|
||||
}
|
||||
if (from->flags & SvgStyleFlags::Opacity) {
|
||||
to->opacity = from->opacity;
|
||||
}
|
||||
if (from->flags & SvgStyleFlags::PaintOrder) {
|
||||
to->paintOrder = from->paintOrder;
|
||||
}
|
||||
if (from->flags & SvgStyleFlags::Display) {
|
||||
to->display = from->display;
|
||||
}
|
||||
//Fill
|
||||
to->fill.flags = (to->fill.flags | from->fill.flags);
|
||||
if (from->fill.flags & SvgFillFlags::Paint) {
|
||||
@ -3021,9 +3164,13 @@ static void _clonePostponedNodes(Array<SvgNodeIdPair>* cloneNodes, SvgNode* doc)
|
||||
auto defs = _getDefsNode(nodeIdPair.node);
|
||||
auto nodeFrom = _findNodeById(defs, nodeIdPair.id);
|
||||
if (!nodeFrom) nodeFrom = _findNodeById(doc, nodeIdPair.id);
|
||||
_cloneNode(nodeFrom, nodeIdPair.node, 0);
|
||||
if (nodeFrom && nodeFrom->type == SvgNodeType::Symbol && nodeIdPair.node->type == SvgNodeType::Use) {
|
||||
nodeIdPair.node->node.use.symbol = nodeFrom;
|
||||
if (!_findParentById(nodeIdPair.node, nodeIdPair.id, doc)) {
|
||||
_cloneNode(nodeFrom, nodeIdPair.node, 0);
|
||||
if (nodeFrom && nodeFrom->type == SvgNodeType::Symbol && nodeIdPair.node->type == SvgNodeType::Use) {
|
||||
nodeIdPair.node->node.use.symbol = nodeFrom;
|
||||
}
|
||||
} else {
|
||||
TVGLOG("SVG", "%s is ancestor element. This reference is invalid.", nodeIdPair.id);
|
||||
}
|
||||
free(nodeIdPair.id);
|
||||
}
|
||||
@ -3056,6 +3203,14 @@ static void _svgLoaderParserXmlClose(SvgLoaderData* loader, const char* content)
|
||||
}
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < sizeof(graphicsTags) / sizeof(graphicsTags[0]); i++) {
|
||||
if (!strncmp(content, graphicsTags[i].tag, graphicsTags[i].sz - 1)) {
|
||||
loader->currentGraphicsNode = nullptr;
|
||||
loader->stack.pop();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
loader->level--;
|
||||
}
|
||||
|
||||
@ -3122,6 +3277,11 @@ static void _svgLoaderParserXmlOpen(SvgLoaderData* loader, const char* content,
|
||||
if (loader->stack.count > 0) parent = loader->stack.last();
|
||||
else parent = loader->doc;
|
||||
node = method(loader, parent, attrs, attrsLength, simpleXmlParseAttributes);
|
||||
if (node && !empty) {
|
||||
auto defs = _createDefsNode(loader, nullptr, nullptr, 0, nullptr);
|
||||
loader->stack.push(defs);
|
||||
loader->currentGraphicsNode = node;
|
||||
}
|
||||
} else if ((gradientMethod = _findGradientFactory(tagName))) {
|
||||
SvgStyleGradient* gradient;
|
||||
gradient = gradientMethod(loader, attrs, attrsLength);
|
||||
@ -3233,7 +3393,7 @@ static void _inefficientNodeCheck(TVG_UNUSED SvgNode* node)
|
||||
#ifdef THORVG_LOG_ENABLED
|
||||
auto type = simpleXmlNodeTypeToString(node->type);
|
||||
|
||||
if (!node->display && node->type != SvgNodeType::ClipPath && node->type != SvgNodeType::Symbol) TVGLOG("SVG", "Inefficient elements used [Display is none][Node Type : %s]", type);
|
||||
if (!node->style->display && node->type != SvgNodeType::ClipPath) TVGLOG("SVG", "Inefficient elements used [Display is none][Node Type : %s]", type);
|
||||
if (node->style->opacity == 0) TVGLOG("SVG", "Inefficient elements used [Opacity is zero][Node Type : %s]", type);
|
||||
if (node->style->fill.opacity == 0 && node->style->stroke.opacity == 0) TVGLOG("SVG", "Inefficient elements used [Fill opacity and stroke opacity are zero][Node Type : %s]", type);
|
||||
|
||||
@ -3551,7 +3711,7 @@ SvgLoader::~SvgLoader()
|
||||
void SvgLoader::run(unsigned tid)
|
||||
{
|
||||
//According to the SVG standard the value of the width/height of the viewbox set to 0 disables rendering
|
||||
if ((viewFlag & SvgViewFlag::Viewbox) && (fabsf(vw) <= FLT_EPSILON || fabsf(vh) <= FLT_EPSILON)) {
|
||||
if ((viewFlag & SvgViewFlag::Viewbox) && (fabsf(vw) <= FLOAT_EPSILON || fabsf(vh) <= FLOAT_EPSILON)) {
|
||||
TVGLOG("SVG", "The <viewBox> width and/or height set to 0 - rendering disabled.");
|
||||
root = Scene::gen().release();
|
||||
return;
|
||||
@ -3675,10 +3835,11 @@ bool SvgLoader::open(const char* data, uint32_t size, bool copy)
|
||||
clear();
|
||||
|
||||
if (copy) {
|
||||
content = (char*)malloc(size);
|
||||
content = (char*)malloc(size + 1);
|
||||
if (!content) return false;
|
||||
memcpy((char*)content, data, size);
|
||||
} else content = data;
|
||||
content[size] = '\0';
|
||||
} else content = (char*)data;
|
||||
|
||||
this->size = size;
|
||||
this->copy = copy;
|
||||
@ -3702,7 +3863,7 @@ bool SvgLoader::open(const string& path)
|
||||
|
||||
if (filePath.empty()) return false;
|
||||
|
||||
content = filePath.c_str();
|
||||
content = (char*)filePath.c_str();
|
||||
size = filePath.size();
|
||||
|
||||
return header();
|
||||
|
@ -34,7 +34,7 @@ class SvgLoader : public ImageLoader, public Task
|
||||
public:
|
||||
string filePath;
|
||||
string svgPath = "";
|
||||
const char* content = nullptr;
|
||||
char* content = nullptr;
|
||||
uint32_t size = 0;
|
||||
|
||||
SvgLoaderData loaderData;
|
||||
|
@ -488,11 +488,12 @@ struct SvgStyleProperty
|
||||
SvgComposite mask;
|
||||
int opacity;
|
||||
SvgColor color;
|
||||
bool curColorSet;
|
||||
char* cssClass;
|
||||
bool paintOrder; //true if default (fill, stroke), false otherwise
|
||||
SvgStyleFlags flags;
|
||||
SvgStyleFlags flagsImportance; //indicates the importance of the flag - if set, higher priority is applied (https://drafts.csswg.org/css-cascade-4/#importance)
|
||||
bool curColorSet;
|
||||
bool paintOrder; //true if default (fill, stroke), false otherwise
|
||||
bool display;
|
||||
};
|
||||
|
||||
struct SvgNode
|
||||
@ -521,7 +522,6 @@ struct SvgNode
|
||||
SvgCssStyleNode cssStyle;
|
||||
SvgSymbolNode symbol;
|
||||
} node;
|
||||
bool display;
|
||||
~SvgNode();
|
||||
};
|
||||
|
||||
@ -563,6 +563,7 @@ struct SvgLoaderData
|
||||
int level = 0;
|
||||
bool result = false;
|
||||
bool style = false;
|
||||
SvgNode* currentGraphicsNode = nullptr;
|
||||
};
|
||||
|
||||
struct Box
|
||||
|
@ -125,14 +125,11 @@ void _pathAppendArcTo(Array<PathCommand>* cmds, Array<Point>* pts, Point* cur, P
|
||||
sx = cur->x;
|
||||
sy = cur->y;
|
||||
|
||||
//If start and end points are identical, then no arc is drawn
|
||||
if ((fabsf(x - sx) < (1.0f / 256.0f)) && (fabsf(y - sy) < (1.0f / 256.0f))) return;
|
||||
|
||||
//Correction of out-of-range radii, see F6.6.1 (step 2)
|
||||
rx = fabsf(rx);
|
||||
ry = fabsf(ry);
|
||||
|
||||
angle = angle * M_PI / 180.0f;
|
||||
angle = mathDeg2Rad(angle);
|
||||
cosPhi = cosf(angle);
|
||||
sinPhi = sinf(angle);
|
||||
dx2 = (sx - x) / 2.0f;
|
||||
@ -201,24 +198,24 @@ void _pathAppendArcTo(Array<PathCommand>* cmds, Array<Point>* pts, Point* cur, P
|
||||
//http://www.euclideanspace.com/maths/algebra/vectors/angleBetween/index.htm
|
||||
//Note: atan2 (0.0, 1.0) == 0.0
|
||||
at = atan2(((y1p - cyp) / ry), ((x1p - cxp) / rx));
|
||||
theta1 = (at < 0.0f) ? 2.0f * M_PI + at : at;
|
||||
theta1 = (at < 0.0f) ? 2.0f * MATH_PI + at : at;
|
||||
|
||||
nat = atan2(((-y1p - cyp) / ry), ((-x1p - cxp) / rx));
|
||||
deltaTheta = (nat < at) ? 2.0f * M_PI - at + nat : nat - at;
|
||||
deltaTheta = (nat < at) ? 2.0f * MATH_PI - at + nat : nat - at;
|
||||
|
||||
if (sweep) {
|
||||
//Ensure delta theta < 0 or else add 360 degrees
|
||||
if (deltaTheta < 0.0f) deltaTheta += (float)(2.0f * M_PI);
|
||||
if (deltaTheta < 0.0f) deltaTheta += 2.0f * MATH_PI;
|
||||
} else {
|
||||
//Ensure delta theta > 0 or else substract 360 degrees
|
||||
if (deltaTheta > 0.0f) deltaTheta -= (float)(2.0f * M_PI);
|
||||
if (deltaTheta > 0.0f) deltaTheta -= 2.0f * MATH_PI;
|
||||
}
|
||||
|
||||
//Add several cubic bezier to approximate the arc
|
||||
//(smaller than 90 degrees)
|
||||
//We add one extra segment because we want something
|
||||
//Smaller than 90deg (i.e. not 90 itself)
|
||||
segments = static_cast<int>(fabsf(deltaTheta / float(M_PI_2)) + 1.0f);
|
||||
segments = static_cast<int>(fabsf(deltaTheta / MATH_PI2) + 1.0f);
|
||||
delta = deltaTheta / segments;
|
||||
|
||||
//http://www.stillhq.com/ctpfaq/2001/comp.text.pdf-faq-2001-04.txt (section 2.13)
|
||||
@ -317,7 +314,7 @@ static int _numberCount(char cmd)
|
||||
}
|
||||
|
||||
|
||||
static bool _processCommand(Array<PathCommand>* cmds, Array<Point>* pts, char cmd, float* arr, int count, Point* cur, Point* curCtl, Point* startPoint, bool *isQuadratic)
|
||||
static bool _processCommand(Array<PathCommand>* cmds, Array<Point>* pts, char cmd, float* arr, int count, Point* cur, Point* curCtl, Point* startPoint, bool *isQuadratic, bool* closed)
|
||||
{
|
||||
switch (cmd) {
|
||||
case 'm':
|
||||
@ -470,6 +467,7 @@ static bool _processCommand(Array<PathCommand>* cmds, Array<Point>* pts, char cm
|
||||
case 'Z': {
|
||||
cmds->push(PathCommand::Close);
|
||||
*cur = *startPoint;
|
||||
*closed = true;
|
||||
break;
|
||||
}
|
||||
case 'a':
|
||||
@ -494,7 +492,7 @@ static bool _processCommand(Array<PathCommand>* cmds, Array<Point>* pts, char cm
|
||||
}
|
||||
|
||||
|
||||
static char* _nextCommand(char* path, char* cmd, float* arr, int* count)
|
||||
static char* _nextCommand(char* path, char* cmd, float* arr, int* count, bool* closed)
|
||||
{
|
||||
int large, sweep;
|
||||
|
||||
@ -506,6 +504,9 @@ static char* _nextCommand(char* path, char* cmd, float* arr, int* count)
|
||||
} else {
|
||||
if (*cmd == 'm') *cmd = 'l';
|
||||
else if (*cmd == 'M') *cmd = 'L';
|
||||
else {
|
||||
if (*closed) return nullptr;
|
||||
}
|
||||
}
|
||||
if (*count == 7) {
|
||||
//Special case for arc command
|
||||
@ -554,6 +555,7 @@ bool svgPathToShape(const char* svgPath, Shape* shape)
|
||||
Point startPoint = { 0, 0 };
|
||||
char cmd = 0;
|
||||
bool isQuadratic = false;
|
||||
bool closed = false;
|
||||
char* path = (char*)svgPath;
|
||||
|
||||
auto& pts = P(shape)->rs.path.pts;
|
||||
@ -561,9 +563,10 @@ bool svgPathToShape(const char* svgPath, Shape* shape)
|
||||
auto lastCmds = cmds.count;
|
||||
|
||||
while ((path[0] != '\0')) {
|
||||
path = _nextCommand(path, &cmd, numberArray, &numberCount);
|
||||
path = _nextCommand(path, &cmd, numberArray, &numberCount, &closed);
|
||||
if (!path) break;
|
||||
if (!_processCommand(&cmds, &pts, cmd, numberArray, numberCount, &cur, &curCtl, &startPoint, &isQuadratic)) break;
|
||||
closed = false;
|
||||
if (!_processCommand(&cmds, &pts, cmd, numberArray, numberCount, &cur, &curCtl, &startPoint, &isQuadratic, &closed)) break;
|
||||
}
|
||||
|
||||
if (cmds.count > lastCmds && cmds[lastCmds] != PathCommand::MoveTo) return false;
|
||||
|
@ -313,7 +313,7 @@ static void _applyProperty(SvgLoaderData& loaderData, SvgNode* node, Shape* vg,
|
||||
|
||||
//Clip transformation is applied directly to the path in the _appendClipShape function
|
||||
if (node->transform && !clip) vg->transform(*node->transform);
|
||||
if (node->type == SvgNodeType::Doc || !node->display) return;
|
||||
if (node->type == SvgNodeType::Doc || !node->style->display) return;
|
||||
|
||||
//If fill property is nullptr then do nothing
|
||||
if (style->fill.paint.none) {
|
||||
@ -560,7 +560,7 @@ static bool _isValidImageMimeTypeAndEncoding(const char** href, const char** mim
|
||||
|
||||
static unique_ptr<Picture> _imageBuildHelper(SvgLoaderData& loaderData, SvgNode* node, const Box& vBox, const string& svgPath)
|
||||
{
|
||||
if (!node->node.image.href) return nullptr;
|
||||
if (!node->node.image.href || !strlen(node->node.image.href)) return nullptr;
|
||||
auto picture = Picture::gen();
|
||||
|
||||
TaskScheduler::async(false); //force to load a picture on the same thread
|
||||
@ -785,13 +785,13 @@ static unique_ptr<Scene> _sceneBuildHelper(SvgLoaderData& loaderData, const SvgN
|
||||
// For a Symbol node, the viewBox transformation has to be applied first - see _useBuildHelper()
|
||||
if (!mask && node->transform && node->type != SvgNodeType::Symbol) scene->transform(*node->transform);
|
||||
|
||||
if (node->display && node->style->opacity != 0) {
|
||||
if (node->style->display && node->style->opacity != 0) {
|
||||
auto child = node->child.data;
|
||||
for (uint32_t i = 0; i < node->child.count; ++i, ++child) {
|
||||
if (_isGroupType((*child)->type)) {
|
||||
if ((*child)->type == SvgNodeType::Use)
|
||||
scene->push(_useBuildHelper(loaderData, *child, vBox, svgPath, depth + 1, isMaskWhite));
|
||||
else
|
||||
else if (!((*child)->type == SvgNodeType::Symbol && node->type != SvgNodeType::Use))
|
||||
scene->push(_sceneBuildHelper(loaderData, *child, vBox, svgPath, false, depth + 1, isMaskWhite));
|
||||
} else if ((*child)->type == SvgNodeType::Image) {
|
||||
auto image = _imageBuildHelper(loaderData, *child, vBox, svgPath);
|
||||
|
@ -50,7 +50,6 @@ size_t svgUtilURLDecode(const char *src, char** dst)
|
||||
if (length == 0) return 0;
|
||||
|
||||
char* decoded = (char*)malloc(sizeof(char) * length + 1);
|
||||
decoded[length] = '\0';
|
||||
|
||||
char a, b;
|
||||
int idx =0;
|
||||
@ -67,9 +66,10 @@ size_t svgUtilURLDecode(const char *src, char** dst)
|
||||
decoded[idx++] = *src++;
|
||||
}
|
||||
}
|
||||
decoded[idx] = '\0';
|
||||
|
||||
*dst = decoded;
|
||||
return length + 1;
|
||||
return idx + 1;
|
||||
}
|
||||
|
||||
#endif /* LV_USE_THORVG_INTERNAL */
|
||||
|
@ -90,6 +90,8 @@ Result SwCanvas::target(uint32_t* buffer, uint32_t stride, uint32_t w, uint32_t
|
||||
if (!renderer) return Result::MemoryCorruption;
|
||||
|
||||
if (!renderer->target(buffer, stride, w, h, static_cast<ColorSpace>(cs))) return Result::InvalidArguments;
|
||||
Canvas::pImpl->vport = {0, 0, (int32_t)w, (int32_t)h};
|
||||
renderer->viewport(Canvas::pImpl->vport);
|
||||
|
||||
//Paints must be updated again with this new target.
|
||||
Canvas::pImpl->needRefresh();
|
||||
|
@ -143,7 +143,7 @@ bool _prepareLinear(SwFill* fill, const LinearGradient* linear, const Matrix* tr
|
||||
fill->linear.dy = y2 - y1;
|
||||
fill->linear.len = fill->linear.dx * fill->linear.dx + fill->linear.dy * fill->linear.dy;
|
||||
|
||||
if (fill->linear.len < FLT_EPSILON) return true;
|
||||
if (fill->linear.len < FLOAT_EPSILON) return true;
|
||||
|
||||
fill->linear.dx /= fill->linear.len;
|
||||
fill->linear.dy /= fill->linear.len;
|
||||
@ -185,7 +185,7 @@ bool _prepareRadial(SwFill* fill, const RadialGradient* radial, const Matrix* tr
|
||||
auto fy = P(radial)->fy;
|
||||
auto fr = P(radial)->fr;
|
||||
|
||||
if (r < FLT_EPSILON) return true;
|
||||
if (r < FLOAT_EPSILON) return true;
|
||||
|
||||
fill->radial.dr = r - fr;
|
||||
fill->radial.dx = cx - fx;
|
||||
|
@ -377,7 +377,7 @@ static bool _rasterMattedRect(SwSurface* surface, const SwBBox& region, uint8_t
|
||||
auto alpha = surface->alpha(surface->compositor->method);
|
||||
|
||||
TVGLOG("SW_ENGINE", "Matted(%d) Rect [Region: %lu %lu %u %u]", (int)surface->compositor->method, region.min.x, region.min.y, w, h);
|
||||
|
||||
|
||||
//32bits channels
|
||||
if (surface->channelSize == sizeof(uint32_t)) {
|
||||
auto color = surface->join(r, g, b, a);
|
||||
@ -719,7 +719,7 @@ static bool _rasterDirectScaledMaskedRleImage(SwSurface* surface, const SwImage*
|
||||
auto span = image->rle->spans;
|
||||
int32_t miny = 0, maxy = 0;
|
||||
|
||||
for (uint32_t i = 0; i < image->rle->size; ++i, ++span) {
|
||||
for (uint32_t i = 0; i < image->rle->size; ++i, ++span) {
|
||||
SCALED_IMAGE_RANGE_Y(span->y)
|
||||
auto cmp = &surface->compositor->image.buf8[span->y * surface->compositor->image.stride + span->x];
|
||||
auto dst = &surface->buf8[span->y * surface->stride + span->x];
|
||||
@ -1556,7 +1556,7 @@ static bool _rasterSolidGradientRect(SwSurface* surface, const SwBBox& region, c
|
||||
|
||||
static bool _rasterLinearGradientRect(SwSurface* surface, const SwBBox& region, const SwFill* fill)
|
||||
{
|
||||
if (fill->linear.len < FLT_EPSILON) return false;
|
||||
if (fill->linear.len < FLOAT_EPSILON) return false;
|
||||
|
||||
if (_compositing(surface)) {
|
||||
if (_matting(surface)) return _rasterGradientMattedRect<FillLinear>(surface, region, fill);
|
||||
@ -1758,8 +1758,13 @@ static bool _rasterRadialGradientRle(SwSurface* surface, const SwRleData* rle, c
|
||||
|
||||
void rasterGrayscale8(uint8_t *dst, uint8_t val, uint32_t offset, int32_t len)
|
||||
{
|
||||
//OPTIMIZE_ME: Support SIMD
|
||||
#if defined(THORVG_AVX_VECTOR_SUPPORT)
|
||||
avxRasterGrayscale8(dst, val, offset, len);
|
||||
#elif defined(THORVG_NEON_VECTOR_SUPPORT)
|
||||
neonRasterGrayscale8(dst, val, offset, len);
|
||||
#else
|
||||
cRasterPixels(dst, val, offset, len);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
/*
|
||||
/*
|
||||
* Copyright (c) 2021 - 2024 the ThorVG project. All rights reserved.
|
||||
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
@ -65,6 +65,23 @@ static inline __m128i ALPHA_BLEND(__m128i c, __m128i a)
|
||||
}
|
||||
|
||||
|
||||
static void avxRasterGrayscale8(uint8_t* dst, uint8_t val, uint32_t offset, int32_t len)
|
||||
{
|
||||
dst += offset;
|
||||
|
||||
__m256i vecVal = _mm256_set1_epi8(val);
|
||||
|
||||
int32_t i = 0;
|
||||
for (; i <= len - 32; i += 32) {
|
||||
_mm256_storeu_si256((__m256i*)(dst + i), vecVal);
|
||||
}
|
||||
|
||||
for (; i < len; ++i) {
|
||||
dst[i] = val;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void avxRasterPixel32(uint32_t *dst, uint32_t val, uint32_t offset, int32_t len)
|
||||
{
|
||||
//1. calculate how many iterations we need to cover the length
|
||||
|
@ -27,6 +27,15 @@
|
||||
|
||||
#include <arm_neon.h>
|
||||
|
||||
//TODO : need to support windows ARM
|
||||
|
||||
#if defined(__ARM_64BIT_STATE) || defined(_M_ARM64)
|
||||
#define TVG_AARCH64 1
|
||||
#else
|
||||
#define TVG_AARCH64 0
|
||||
#endif
|
||||
|
||||
|
||||
static inline uint8x8_t ALPHA_BLEND(uint8x8_t c, uint8x8_t a)
|
||||
{
|
||||
uint16x8_t t = vmull_u8(c, a);
|
||||
@ -34,19 +43,50 @@ static inline uint8x8_t ALPHA_BLEND(uint8x8_t c, uint8x8_t a)
|
||||
}
|
||||
|
||||
|
||||
static void neonRasterGrayscale8(uint8_t* dst, uint8_t val, uint32_t offset, int32_t len)
|
||||
{
|
||||
dst += offset;
|
||||
|
||||
int32_t i = 0;
|
||||
const uint8x16_t valVec = vdupq_n_u8(val);
|
||||
#if TVG_AARCH64
|
||||
uint8x16x4_t valQuad = {valVec, valVec, valVec, valVec};
|
||||
for (; i <= len - 16 * 4; i += 16 * 4) {
|
||||
vst1q_u8_x4(dst + i, valQuad);
|
||||
}
|
||||
#else
|
||||
for (; i <= len - 16; i += 16) {
|
||||
vst1q_u8(dst + i, valVec);
|
||||
}
|
||||
#endif
|
||||
for (; i < len; i++) {
|
||||
dst[i] = val;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void neonRasterPixel32(uint32_t *dst, uint32_t val, uint32_t offset, int32_t len)
|
||||
{
|
||||
dst += offset;
|
||||
|
||||
uint32x4_t vectorVal = vdupq_n_u32(val);
|
||||
|
||||
#if TVG_AARCH64
|
||||
uint32_t iterations = len / 16;
|
||||
uint32_t neonFilled = iterations * 16;
|
||||
uint32x4x4_t valQuad = {vectorVal, vectorVal, vectorVal, vectorVal};
|
||||
for (uint32_t i = 0; i < iterations; ++i) {
|
||||
vst4q_u32(dst, valQuad);
|
||||
dst += 16;
|
||||
}
|
||||
#else
|
||||
uint32_t iterations = len / 4;
|
||||
uint32_t neonFilled = iterations * 4;
|
||||
|
||||
dst += offset;
|
||||
uint32x4_t vectorVal = {val, val, val, val};
|
||||
|
||||
for (uint32_t i = 0; i < iterations; ++i) {
|
||||
vst1q_u32(dst, vectorVal);
|
||||
dst += 4;
|
||||
}
|
||||
|
||||
#endif
|
||||
int32_t leftovers = len - neonFilled;
|
||||
while (leftovers--) *dst++ = val;
|
||||
}
|
||||
@ -59,7 +99,7 @@ static bool neonRasterTranslucentRle(SwSurface* surface, const SwRleData* rle, u
|
||||
return false;
|
||||
}
|
||||
|
||||
auto color = surface->blender.join(r, g, b, a);
|
||||
auto color = surface->join(r, g, b, a);
|
||||
auto span = rle->spans;
|
||||
uint32_t src;
|
||||
uint8x8_t *vDst = nullptr;
|
||||
@ -70,9 +110,9 @@ static bool neonRasterTranslucentRle(SwSurface* surface, const SwRleData* rle, u
|
||||
else src = color;
|
||||
|
||||
auto dst = &surface->buf32[span->y * surface->stride + span->x];
|
||||
auto ialpha = IALPHA(src);
|
||||
auto ialpha = IA(src);
|
||||
|
||||
if ((((uint32_t) dst) & 0x7) != 0) {
|
||||
if ((((uintptr_t) dst) & 0x7) != 0) {
|
||||
//fill not aligned byte
|
||||
*dst = src + ALPHA_BLEND(*dst, ialpha);
|
||||
vDst = (uint8x8_t*)(dst + 1);
|
||||
@ -104,7 +144,7 @@ static bool neonRasterTranslucentRect(SwSurface* surface, const SwBBox& region,
|
||||
return false;
|
||||
}
|
||||
|
||||
auto color = surface->blender.join(r, g, b, a);
|
||||
auto color = surface->join(r, g, b, a);
|
||||
auto buffer = surface->buf32 + (region.min.y * surface->stride) + region.min.x;
|
||||
auto h = static_cast<uint32_t>(region.max.y - region.min.y);
|
||||
auto w = static_cast<uint32_t>(region.max.x - region.min.x);
|
||||
@ -119,7 +159,7 @@ static bool neonRasterTranslucentRect(SwSurface* surface, const SwBBox& region,
|
||||
for (uint32_t y = 0; y < h; ++y) {
|
||||
auto dst = &buffer[y * surface->stride];
|
||||
|
||||
if ((((uint32_t) dst) & 0x7) != 0) {
|
||||
if ((((uintptr_t) dst) & 0x7) != 0) {
|
||||
//fill not aligned byte
|
||||
*dst = color + ALPHA_BLEND(*dst, ialpha);
|
||||
vDst = (uint8x8_t*) (dst + 1);
|
||||
|
@ -437,10 +437,6 @@ bool SwRenderer::target(pixel_t* data, uint32_t stride, uint32_t w, uint32_t h,
|
||||
surface->channelSize = CHANNEL_SIZE(cs);
|
||||
surface->premultiplied = true;
|
||||
|
||||
vport.x = vport.y = 0;
|
||||
vport.w = surface->w;
|
||||
vport.h = surface->h;
|
||||
|
||||
return rasterCompositor(surface);
|
||||
}
|
||||
|
||||
@ -614,6 +610,12 @@ bool SwRenderer::mempool(bool shared)
|
||||
}
|
||||
|
||||
|
||||
const Surface* SwRenderer::mainSurface()
|
||||
{
|
||||
return surface;
|
||||
}
|
||||
|
||||
|
||||
Compositor* SwRenderer::target(const RenderRegion& region, ColorSpace cs)
|
||||
{
|
||||
auto x = region.x;
|
||||
|
@ -52,6 +52,7 @@ public:
|
||||
bool viewport(const RenderRegion& vp) override;
|
||||
bool blend(BlendMethod method) override;
|
||||
ColorSpace colorSpace() override;
|
||||
const Surface* mainSurface() override;
|
||||
|
||||
bool clear() override;
|
||||
bool sync() override;
|
||||
|
@ -25,56 +25,41 @@
|
||||
|
||||
#include "tvgSwCommon.h"
|
||||
#include "tvgMath.h"
|
||||
#include "tvgBezier.h"
|
||||
#include "tvgLines.h"
|
||||
|
||||
/************************************************************************/
|
||||
/* Internal Class Implementation */
|
||||
/************************************************************************/
|
||||
|
||||
struct Line
|
||||
static bool _outlineBegin(SwOutline& outline)
|
||||
{
|
||||
Point pt1;
|
||||
Point pt2;
|
||||
};
|
||||
|
||||
|
||||
static float _lineLength(const Point& pt1, const Point& pt2)
|
||||
{
|
||||
Point diff = {pt2.x - pt1.x, pt2.y - pt1.y};
|
||||
return sqrtf(diff.x * diff.x + diff.y * diff.y);
|
||||
}
|
||||
|
||||
|
||||
static void _lineSplitAt(const Line& cur, float at, Line& left, Line& right)
|
||||
{
|
||||
auto len = _lineLength(cur.pt1, cur.pt2);
|
||||
auto dx = ((cur.pt2.x - cur.pt1.x) / len) * at;
|
||||
auto dy = ((cur.pt2.y - cur.pt1.y) / len) * at;
|
||||
left.pt1 = cur.pt1;
|
||||
left.pt2.x = left.pt1.x + dx;
|
||||
left.pt2.y = left.pt1.y + dy;
|
||||
right.pt1 = left.pt2;
|
||||
right.pt2 = cur.pt2;
|
||||
}
|
||||
|
||||
|
||||
static void _outlineEnd(SwOutline& outline)
|
||||
{
|
||||
if (outline.pts.empty()) return;
|
||||
//Make a contour if lineTo/curveTo without calling close or moveTo beforehand.
|
||||
if (outline.pts.empty()) return false;
|
||||
outline.cntrs.push(outline.pts.count - 1);
|
||||
outline.closed.push(false);
|
||||
outline.pts.push(outline.pts[outline.cntrs.last()]);
|
||||
outline.types.push(SW_CURVE_TYPE_POINT);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
static void _outlineMoveTo(SwOutline& outline, const Point* to, const Matrix* transform)
|
||||
static bool _outlineEnd(SwOutline& outline)
|
||||
{
|
||||
if (outline.pts.count > 0) {
|
||||
outline.cntrs.push(outline.pts.count - 1);
|
||||
outline.closed.push(false);
|
||||
}
|
||||
if (outline.pts.empty()) return false;
|
||||
outline.cntrs.push(outline.pts.count - 1);
|
||||
outline.closed.push(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
static bool _outlineMoveTo(SwOutline& outline, const Point* to, const Matrix* transform, bool closed = false)
|
||||
{
|
||||
//make it a contour, if the last contour is not closed yet.
|
||||
if (!closed) _outlineEnd(outline);
|
||||
|
||||
outline.pts.push(mathTransform(to, transform));
|
||||
outline.types.push(SW_CURVE_TYPE_POINT);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@ -91,37 +76,40 @@ static void _outlineCubicTo(SwOutline& outline, const Point* ctrl1, const Point*
|
||||
outline.types.push(SW_CURVE_TYPE_CUBIC);
|
||||
|
||||
outline.pts.push(mathTransform(ctrl2, transform));
|
||||
outline.types.push(SW_CURVE_TYPE_CUBIC);
|
||||
outline.types.push(SW_CURVE_TYPE_CUBIC);
|
||||
|
||||
outline.pts.push(mathTransform(to, transform));
|
||||
outline.types.push(SW_CURVE_TYPE_POINT);
|
||||
}
|
||||
|
||||
|
||||
static void _outlineClose(SwOutline& outline)
|
||||
static bool _outlineClose(SwOutline& outline)
|
||||
{
|
||||
uint32_t i = 0;
|
||||
|
||||
uint32_t i;
|
||||
if (outline.cntrs.count > 0) i = outline.cntrs.last() + 1;
|
||||
else i = 0; //First Path
|
||||
else i = 0;
|
||||
|
||||
//Make sure there is at least one point in the current path
|
||||
if (outline.pts.count == i) return;
|
||||
if (outline.pts.count == i) return false;
|
||||
|
||||
//Close the path
|
||||
outline.pts.push(outline.pts[i]);
|
||||
outline.cntrs.push(outline.pts.count - 1);
|
||||
outline.types.push(SW_CURVE_TYPE_POINT);
|
||||
outline.closed.push(true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static void _dashLineTo(SwDashStroke& dash, const Point* to, const Matrix* transform)
|
||||
{
|
||||
Line cur = {dash.ptCur, *to};
|
||||
auto len = _lineLength(cur.pt1, cur.pt2);
|
||||
auto len = lineLength(cur.pt1, cur.pt2);
|
||||
|
||||
if (mathZero(len)) {
|
||||
_outlineMoveTo(*dash.outline, &dash.ptCur, transform);
|
||||
//draw the current line fully
|
||||
} else if (len < dash.curLen) {
|
||||
dash.curLen -= len;
|
||||
if (!dash.curOpGap) {
|
||||
@ -131,14 +119,15 @@ static void _dashLineTo(SwDashStroke& dash, const Point* to, const Matrix* trans
|
||||
}
|
||||
_outlineLineTo(*dash.outline, to, transform);
|
||||
}
|
||||
//draw the current line partially
|
||||
} else {
|
||||
while (len - dash.curLen > 0.0001f) {
|
||||
Line left, right;
|
||||
if (dash.curLen > 0) {
|
||||
len -= dash.curLen;
|
||||
_lineSplitAt(cur, dash.curLen, left, right);
|
||||
lineSplitAt(cur, dash.curLen, left, right);
|
||||
if (!dash.curOpGap) {
|
||||
if (dash.move || dash.pattern[dash.curIdx] - dash.curLen < FLT_EPSILON) {
|
||||
if (dash.move || dash.pattern[dash.curIdx] - dash.curLen < FLOAT_EPSILON) {
|
||||
_outlineMoveTo(*dash.outline, &left.pt1, transform);
|
||||
dash.move = false;
|
||||
}
|
||||
@ -179,6 +168,7 @@ static void _dashCubicTo(SwDashStroke& dash, const Point* ctrl1, const Point* ct
|
||||
Bezier cur = {dash.ptCur, *ctrl1, *ctrl2, *to};
|
||||
auto len = bezLength(cur);
|
||||
|
||||
//draw the current line fully
|
||||
if (mathZero(len)) {
|
||||
_outlineMoveTo(*dash.outline, &dash.ptCur, transform);
|
||||
} else if (len < dash.curLen) {
|
||||
@ -190,6 +180,7 @@ static void _dashCubicTo(SwDashStroke& dash, const Point* ctrl1, const Point* ct
|
||||
}
|
||||
_outlineCubicTo(*dash.outline, ctrl1, ctrl2, to, transform);
|
||||
}
|
||||
//draw the current line partially
|
||||
} else {
|
||||
while ((len - dash.curLen) > 0.0001f) {
|
||||
Bezier left, right;
|
||||
@ -197,7 +188,7 @@ static void _dashCubicTo(SwDashStroke& dash, const Point* ctrl1, const Point* ct
|
||||
len -= dash.curLen;
|
||||
bezSplitAt(cur, dash.curLen, left, right);
|
||||
if (!dash.curOpGap) {
|
||||
if (dash.move || dash.pattern[dash.curIdx] - dash.curLen < FLT_EPSILON) {
|
||||
if (dash.move || dash.pattern[dash.curIdx] - dash.curLen < FLOAT_EPSILON) {
|
||||
_outlineMoveTo(*dash.outline, &left.start, transform);
|
||||
dash.move = false;
|
||||
}
|
||||
@ -239,7 +230,15 @@ static void _dashClose(SwDashStroke& dash, const Matrix* transform)
|
||||
}
|
||||
|
||||
|
||||
static void _dashMoveTo(SwDashStroke& dash, uint32_t offIdx, float offset, const Point* pts, const Matrix* transform)
|
||||
static void _dashMoveTo(SwDashStroke& dash, const Point* pts)
|
||||
{
|
||||
dash.ptCur = *pts;
|
||||
dash.ptStart = *pts;
|
||||
dash.move = true;
|
||||
}
|
||||
|
||||
|
||||
static void _dashMoveTo(SwDashStroke& dash, uint32_t offIdx, float offset, const Point* pts)
|
||||
{
|
||||
dash.curIdx = offIdx % dash.cnt;
|
||||
dash.curLen = dash.pattern[dash.curIdx] - offset;
|
||||
@ -274,7 +273,7 @@ static SwOutline* _genDashOutline(const RenderShape* rshape, const Matrix* trans
|
||||
|
||||
//default
|
||||
if (end > begin) {
|
||||
if (begin > 0) dash.cnt += 4;
|
||||
if (begin > 0.0f) dash.cnt += 4;
|
||||
else dash.cnt += 2;
|
||||
//looping
|
||||
} else dash.cnt += 3;
|
||||
@ -292,7 +291,7 @@ static SwOutline* _genDashOutline(const RenderShape* rshape, const Matrix* trans
|
||||
dash.pattern[0] = 0; //zero dash to start with a space.
|
||||
dash.pattern[1] = begin;
|
||||
dash.pattern[2] = end - begin;
|
||||
dash.pattern[3] = length - (end - begin);
|
||||
dash.pattern[3] = length - end;
|
||||
}
|
||||
|
||||
trimmed = true;
|
||||
@ -309,7 +308,7 @@ static SwOutline* _genDashOutline(const RenderShape* rshape, const Matrix* trans
|
||||
bool isOdd = dash.cnt % 2;
|
||||
if (isOdd) patternLength *= 2;
|
||||
|
||||
offset = fmod(offset, patternLength);
|
||||
offset = fmodf(offset, patternLength);
|
||||
if (offset < 0) offset += patternLength;
|
||||
|
||||
for (size_t i = 0; i < dash.cnt * (1 + (size_t)isOdd); ++i, ++offIdx) {
|
||||
@ -321,14 +320,22 @@ static SwOutline* _genDashOutline(const RenderShape* rshape, const Matrix* trans
|
||||
|
||||
dash.outline = mpoolReqDashOutline(mpool, tid);
|
||||
|
||||
while (cmdCnt-- > 0) {
|
||||
//must begin with moveTo
|
||||
if (cmds[0] == PathCommand::MoveTo) {
|
||||
_dashMoveTo(dash, offIdx, offset, pts);
|
||||
cmds++;
|
||||
pts++;
|
||||
}
|
||||
|
||||
while (--cmdCnt > 0) {
|
||||
switch (*cmds) {
|
||||
case PathCommand::Close: {
|
||||
_dashClose(dash, transform);
|
||||
break;
|
||||
}
|
||||
case PathCommand::MoveTo: {
|
||||
_dashMoveTo(dash, offIdx, offset, pts, transform);
|
||||
if (rshape->stroke->trim.individual) _dashMoveTo(dash, pts);
|
||||
else _dashMoveTo(dash, offIdx, offset, pts);
|
||||
++pts;
|
||||
break;
|
||||
}
|
||||
@ -366,13 +373,19 @@ static float _outlineLength(const RenderShape* rshape)
|
||||
|
||||
const Point* close = nullptr;
|
||||
auto length = 0.0f;
|
||||
auto slength = -1.0f;
|
||||
auto simultaneous = !rshape->stroke->trim.individual;
|
||||
|
||||
//Compute the whole length
|
||||
while (cmdCnt-- > 0) {
|
||||
switch (*cmds) {
|
||||
case PathCommand::Close: {
|
||||
length += mathLength(pts - 1, close);
|
||||
++pts;
|
||||
//retrieve the max length of the shape if the simultaneous mode.
|
||||
if (simultaneous) {
|
||||
if (slength < length) slength = length;
|
||||
length = 0.0f;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PathCommand::MoveTo: {
|
||||
@ -393,7 +406,8 @@ static float _outlineLength(const RenderShape* rshape)
|
||||
}
|
||||
++cmds;
|
||||
}
|
||||
return length;
|
||||
if (simultaneous && slength > length) return slength;
|
||||
else return length;
|
||||
}
|
||||
|
||||
|
||||
@ -428,25 +442,28 @@ static bool _genOutline(SwShape* shape, const RenderShape* rshape, const Matrix*
|
||||
|
||||
shape->outline = mpoolReqOutline(mpool, tid);
|
||||
auto outline = shape->outline;
|
||||
auto closed = false;
|
||||
|
||||
//Generate Outlines
|
||||
while (cmdCnt-- > 0) {
|
||||
switch (*cmds) {
|
||||
case PathCommand::Close: {
|
||||
_outlineClose(*outline);
|
||||
if (!closed) closed = _outlineClose(*outline);
|
||||
break;
|
||||
}
|
||||
case PathCommand::MoveTo: {
|
||||
_outlineMoveTo(*outline, pts, transform);
|
||||
closed = _outlineMoveTo(*outline, pts, transform, closed);
|
||||
++pts;
|
||||
break;
|
||||
}
|
||||
case PathCommand::LineTo: {
|
||||
if (closed) closed = _outlineBegin(*outline);
|
||||
_outlineLineTo(*outline, pts, transform);
|
||||
++pts;
|
||||
break;
|
||||
}
|
||||
case PathCommand::CubicTo: {
|
||||
if (closed) closed = _outlineBegin(*outline);
|
||||
_outlineCubicTo(*outline, pts, pts + 1, pts + 2, transform);
|
||||
pts += 3;
|
||||
break;
|
||||
@ -455,7 +472,7 @@ static bool _genOutline(SwShape* shape, const RenderShape* rshape, const Matrix*
|
||||
++cmds;
|
||||
}
|
||||
|
||||
_outlineEnd(*outline);
|
||||
if (!closed) _outlineEnd(*outline);
|
||||
|
||||
outline->fillRule = rshape->rule;
|
||||
shape->outline = outline;
|
||||
|
@ -66,6 +66,8 @@ Result WgCanvas::target(void* window, uint32_t w, uint32_t h) noexcept
|
||||
if (!renderer) return Result::MemoryCorruption;
|
||||
|
||||
if (!renderer->target(window, w, h)) return Result::Unknown;
|
||||
Canvas::pImpl->vport = {0, 0, (int32_t)w, (int32_t)h};
|
||||
renderer->viewport(Canvas::pImpl->vport);
|
||||
|
||||
//Paints must be updated again with this new target.
|
||||
Canvas::pImpl->needRefresh();
|
||||
|
@ -317,7 +317,10 @@ bool simpleXmlParseAttributes(const char* buf, unsigned bufLength, simpleXMLAttr
|
||||
if ((*keyEnd == '=') || (isspace((unsigned char)*keyEnd))) break;
|
||||
}
|
||||
if (keyEnd == itrEnd) goto error;
|
||||
if (keyEnd == key) continue;
|
||||
if (keyEnd == key) { // There is no key. This case is invalid, but explores the following syntax.
|
||||
itr = keyEnd + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (*keyEnd == '=') value = keyEnd + 1;
|
||||
else {
|
||||
|
@ -28,9 +28,9 @@
|
||||
|
||||
#include "tvgSvgLoaderCommon.h"
|
||||
|
||||
#define NUMBER_OF_XML_ENTITIES 8
|
||||
const char* const xmlEntity[] = {""", " ", "'", "&", "<", ">", "#", "'"};
|
||||
const int xmlEntityLength[] = {6, 6, 6, 5, 4, 4, 6, 6};
|
||||
#define NUMBER_OF_XML_ENTITIES 9
|
||||
const char* const xmlEntity[] = {" ", """, " ", "'", "&", "<", ">", "#", "'"};
|
||||
const int xmlEntityLength[] = {5, 6, 6, 6, 5, 4, 4, 6, 6};
|
||||
|
||||
enum class SimpleXMLType
|
||||
{
|
||||
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 6.6 KiB |
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 7.1 KiB |