1
0
mirror of https://github.com/lvgl/lvgl.git synced 2025-01-28 07:03:00 +08:00

feat(thorvg): update thorvg version to 0.13.5 (#6274)

Signed-off-by: lhdjply <lhdjply@126.com>
This commit is contained in:
lhdjply 2024-05-28 10:21:39 +08:00 committed by GitHub
parent b9278aed00
commit 18dfa7b7ed
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
63 changed files with 4071 additions and 1089 deletions

View File

@ -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"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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_ */

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

File diff suppressed because it is too large Load Diff

View 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 */

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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_

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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) {

View File

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

View File

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

View File

@ -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:

View File

@ -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)) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 {

View File

@ -28,9 +28,9 @@
#include "tvgSvgLoaderCommon.h"
#define NUMBER_OF_XML_ENTITIES 8
const char* const xmlEntity[] = {"&quot;", "&nbsp;", "&apos;", "&amp;", "&lt;", "&gt;", "&#035;", "&#039;"};
const int xmlEntityLength[] = {6, 6, 6, 5, 4, 4, 6, 6};
#define NUMBER_OF_XML_ENTITIES 9
const char* const xmlEntity[] = {"&#10;", "&quot;", "&nbsp;", "&apos;", "&amp;", "&lt;", "&gt;", "&#035;", "&#039;"};
const int xmlEntityLength[] = {5, 6, 6, 6, 5, 4, 4, 6, 6};
enum class SimpleXMLType
{

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB