Merge pull request #80095 from capnm/update_thorvg_0.10.0

Update ThorVG to v0.10.0
This commit is contained in:
Rémi Verschelde 2023-08-17 12:29:06 +02:00
commit 08690d6af5
No known key found for this signature in database
GPG key ID: C3336907360768E1
70 changed files with 4148 additions and 7212 deletions

View file

@ -38,9 +38,6 @@ thirdparty_sources = [
"src/lib/tvgShape.cpp",
"src/lib/tvgSwCanvas.cpp",
"src/lib/tvgTaskScheduler.cpp",
"src/loaders/external_png/tvgPngLoader.cpp",
"src/loaders/jpg/tvgJpgd.cpp",
"src/loaders/jpg/tvgJpgLoader.cpp",
"src/loaders/raw/tvgRawLoader.cpp",
"src/loaders/svg/tvgSvgCssStyle.cpp",
"src/loaders/svg/tvgSvgLoader.cpp",
@ -48,27 +45,23 @@ thirdparty_sources = [
"src/loaders/svg/tvgSvgSceneBuilder.cpp",
"src/loaders/svg/tvgSvgUtil.cpp",
"src/loaders/svg/tvgXmlParser.cpp",
"src/loaders/tvg/tvgTvgBinInterpreter.cpp",
"src/loaders/tvg/tvgTvgLoader.cpp",
"src/savers/tvg/tvgTvgSaver.cpp",
]
thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources]
env_svg.Prepend(CPPPATH=[thirdparty_dir + "inc"])
# Enable ThorVG static object linking.
env_svg.Append(CPPDEFINES=["TVG_STATIC"])
env_thirdparty = env_svg.Clone()
env_thirdparty.disable_warnings()
env_thirdparty.Prepend(
CPPPATH=[
thirdparty_dir + "src/lib",
thirdparty_dir + "src/lib/sw_engine",
thirdparty_dir + "src/loaders/external_png",
thirdparty_dir + "src/loaders/jpg",
thirdparty_dir + "src/loaders/raw",
thirdparty_dir + "src/loaders/svg",
thirdparty_dir + "src/loaders/tvg",
thirdparty_dir + "src/savers/tvg",
]
)
# Also requires libpng headers

View file

@ -107,7 +107,7 @@ Error ImageLoaderSVG::create_image_from_utf8_buffer(Ref<Image> p_image, const ui
// Note: memalloc here, be sure to memfree before any return.
uint32_t *buffer = (uint32_t *)memalloc(sizeof(uint32_t) * width * height);
tvg::Result res = sw_canvas->target(buffer, width, width, height, tvg::SwCanvas::ARGB8888_STRAIGHT);
tvg::Result res = sw_canvas->target(buffer, width, width, height, tvg::SwCanvas::ARGB8888S);
if (res != tvg::Result::Success) {
memfree(buffer);
ERR_FAIL_V_MSG(FAILED, "ImageLoaderSVG: Couldn't set target on ThorVG canvas.");

View file

@ -256,7 +256,7 @@ FT_Error tvg_svg_in_ot_render(FT_GlyphSlot p_slot, FT_Pointer *p_state) {
}
std::unique_ptr<tvg::SwCanvas> sw_canvas = tvg::SwCanvas::gen();
res = sw_canvas->target((uint32_t *)p_slot->bitmap.buffer, (int)p_slot->bitmap.width, (int)p_slot->bitmap.width, (int)p_slot->bitmap.rows, tvg::SwCanvas::ARGB8888_STRAIGHT);
res = sw_canvas->target((uint32_t *)p_slot->bitmap.buffer, (int)p_slot->bitmap.width, (int)p_slot->bitmap.width, (int)p_slot->bitmap.rows, tvg::SwCanvas::ARGB8888S);
if (res != tvg::Result::Success) {
ERR_FAIL_V_MSG(FT_Err_Invalid_Outline, "Failed to create SVG canvas.");
}

View file

@ -256,7 +256,7 @@ FT_Error tvg_svg_in_ot_render(FT_GlyphSlot p_slot, FT_Pointer *p_state) {
}
std::unique_ptr<tvg::SwCanvas> sw_canvas = tvg::SwCanvas::gen();
res = sw_canvas->target((uint32_t *)p_slot->bitmap.buffer, (int)p_slot->bitmap.width, (int)p_slot->bitmap.width, (int)p_slot->bitmap.rows, tvg::SwCanvas::ARGB8888_STRAIGHT);
res = sw_canvas->target((uint32_t *)p_slot->bitmap.buffer, (int)p_slot->bitmap.width, (int)p_slot->bitmap.width, (int)p_slot->bitmap.rows, tvg::SwCanvas::ARGB8888S);
if (res != tvg::Result::Success) {
ERR_FAIL_V_MSG(FT_Err_Invalid_Outline, "Failed to create SVG canvas.");
}

View file

@ -713,7 +713,7 @@ instead of `miniz.h` as an external dependency.
## thorvg
- Upstream: https://github.com/thorvg/thorvg
- Version: 0.9.0 (a744006aa1edb918bacf0a415d0a57ca058e25f4, 2023)
- Version: 0.10.0 (b8c605583fd7de73209a93a1238e1ba72cce2e8f, 2023)
- License: MIT
Files extracted from upstream source:

View file

@ -20,3 +20,4 @@ Vincenzo Pupillo <vincenzo.pupillo@unimi.it>
EunSik Jeong <rinechran@outlook.jp>
Samsung Electronics Co., Ltd
Rafał Mikrut <mikrutrafal@protonmail.com>
Martin Capitanio <capnm@capitanio.org>

View file

@ -1,17 +1,9 @@
#ifndef THORVG_CONFIG_H
#define THORVG_CONFIG_H
#define THORVG_SW_RASTER_SUPPORT 1
#define THORVG_SW_RASTER_SUPPORT
#define THORVG_SVG_LOADER_SUPPORT 1
#define THORVG_SVG_LOADER_SUPPORT
#define THORVG_PNG_LOADER_SUPPORT 1
#define THORVG_TVG_LOADER_SUPPORT 1
#define THORVG_TVG_SAVER_SUPPORT 1
#define THORVG_JPG_LOADER_SUPPORT 1
#define THORVG_VERSION_STRING "0.9.0"
#define THORVG_VERSION_STRING "0.10.0"
#endif

View file

@ -18,43 +18,48 @@
#include <functional>
#include <memory>
#include <string>
#include <list>
#ifdef TVG_API
#undef TVG_API
#endif
#if defined(_WIN32) && !defined(__clang__)
#if TVG_BUILD
#if TVG_EXPORT
#ifndef TVG_STATIC
#ifdef _WIN32
#if TVG_BUILD
#define TVG_API __declspec(dllexport)
#else
#define TVG_API
#define TVG_API __declspec(dllimport)
#endif
#elif (defined(__SUNPRO_C) || defined(__SUNPRO_CC))
#define TVG_API __global
#else
#define TVG_API
#endif
#define TVG_DEPRECATED __declspec(deprecated)
#else
#if TVG_BUILD
#if TVG_EXPORT
#define TVG_API __attribute__ ((visibility ("default")))
#if (defined(__GNUC__) && __GNUC__ >= 4) || defined(__INTEL_COMPILER)
#define TVG_API __attribute__ ((visibility("default")))
#else
#define TVG_API
#endif
#else
#define TVG_API
#endif
#define TVG_DEPRECATED __attribute__ ((__deprecated__))
#else
#define TVG_API
#endif
#ifdef __cplusplus
extern "C" {
#ifdef TVG_DEPRECATED
#undef TVG_DEPRECATED
#endif
#ifdef _WIN32
#define TVG_DEPRECATED __declspec(deprecated)
#elif __GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1)
#define TVG_DEPRECATED __attribute__ ((__deprecated__))
#else
#define TVG_DEPRECATED
#endif
#define _TVG_DECLARE_PRIVATE(A) \
protected: \
struct Impl; \
Impl* pImpl; \
protected: \
A(const A&) = delete; \
const A& operator=(const A&) = delete; \
A()
@ -63,23 +68,14 @@ protected: \
A() = delete; \
~A() = delete
#define _TVG_DECLARE_ACCESSOR() \
friend Canvas; \
friend Scene; \
friend Picture; \
friend Accessor; \
friend IteratorAccessor
#define _TVG_DECLARE_ACCESSOR(A) \
friend A
namespace tvg
{
class RenderMethod;
class IteratorAccessor;
class Scene;
class Picture;
class Canvas;
class Accessor;
class Animation;
/**
* @defgroup ThorVG ThorVG
@ -102,6 +98,7 @@ enum class Result
Unknown ///< The value returned in all other cases.
};
/**
* @brief Enumeration specifying the values of the path commands accepted by TVG.
*
@ -116,6 +113,7 @@ enum class PathCommand
CubicTo ///< Draws a cubic Bezier curve from the current point to the given point using two given control points and sets a new value of the current point. This command expects 3 points: the 1st control-point, the 2nd control-point, the end-point of the curve.
};
/**
* @brief Enumeration determining the ending type of a stroke in the open sub-paths.
*/
@ -126,6 +124,7 @@ enum class StrokeCap
Butt ///< The stroke ends exactly at each of the two end-points of a sub-path. For zero length sub-paths no stroke is rendered.
};
/**
* @brief Enumeration determining the style used at the corners of joined stroked path segments.
*/
@ -136,6 +135,7 @@ enum class StrokeJoin
Miter ///< The outer corner of the joined path segments is spiked. The spike is created by extension beyond the join point of the outer edges of the stroke until they intersect. In case the extension goes beyond the limit, the join style is converted to the Bevel style.
};
/**
* @brief Enumeration specifying how to fill the area outside the gradient bounds.
*/
@ -146,6 +146,7 @@ enum class FillSpread
Repeat ///< The gradient pattern is repeated continuously beyond the gradient area until the expected region is filled.
};
/**
* @brief Enumeration specifying the algorithm used to establish which parts of the shape are treated as the inside of the shape.
*/
@ -155,18 +156,57 @@ enum class FillRule
EvenOdd ///< A line from the point to a location outside the shape is drawn and its intersections with the path segments of the shape are counted. If the number of intersections is an odd number, the point is inside the shape.
};
/**
* @brief Enumeration indicating the method used in the composition of two objects - the target and the source.
*
* Notation: S(Source), T(Target), SA(Source Alpha), TA(Target Alpha)
*
* @see Paint::composite()
*/
enum class CompositeMethod
{
None = 0, ///< No composition is applied.
ClipPath, ///< The intersection of the source and the target is determined and only the resulting pixels from the source are rendered.
AlphaMask, ///< The pixels of the source and the target are alpha blended. As a result, only the part of the source, which alpha intersects with the target is visible.
InvAlphaMask, ///< The pixels of the source and the complement to the target's pixels are alpha blended. As a result, only the part of the source which alpha is not covered by the target is visible.
LumaMask ///< The source pixels are converted to the grayscale (luma value) and alpha blended with the target. As a result, only the part of the source, which intersects with the target is visible. @since 0.9
None = 0, ///< No composition is applied.
ClipPath, ///< The intersection of the source and the target is determined and only the resulting pixels from the source are rendered.
AlphaMask, ///< Alpha Masking using the compositing target's pixels as an alpha value.
InvAlphaMask, ///< Alpha Masking using the complement to the compositing target's pixels as an alpha value.
LumaMask, ///< Alpha Masking using the grayscale (0.2125R + 0.7154G + 0.0721*B) of the compositing target's pixels. @since 0.9
InvLumaMask, ///< Alpha Masking using the grayscale (0.2125R + 0.7154G + 0.0721*B) of the complement to the compositing target's pixels. @BETA_API
AddMask, ///< Combines the target and source objects pixels using target alpha. (T * TA) + (S * (255 - TA)) @BETA_API
SubtractMask, ///< Subtracts the source color from the target color while considering their respective target alpha. (T * TA) - (S * (255 - TA)) @BETA_API
IntersectMask, ///< Computes the result by taking the minimum value between the target alpha and the source alpha and multiplies it with the target color. (T * min(TA, SA)) @BETA_API
DifferenceMask ///< Calculates the absolute difference between the target color and the source color multiplied by the complement of the target alpha. abs(T - S * (255 - TA)) @BETA_API
};
/**
* @brief Enumeration indicates the method used for blending paint. Please refer to the respective formulas for each method.
*
* Notation: S(source paint as the top layer), D(destination as the bottom layer), Sa(source paint alpha), Da(destination alpha)
*
* @see Paint::blend()
*
* @BETA_API
*/
enum class BlendMethod : uint8_t
{
Normal = 0, ///< Perform the alpha blending(default). S if (Sa == 255), otherwise (Sa * S) + (255 - Sa) * D
Add, ///< Simply adds pixel values of one layer with the other. (S + D)
Screen, ///< The values of the pixels in the two layers are inverted, multiplied, and then inverted again. (S + D) - (S * D)
Multiply, ///< Takes the RGB channel values from 0 to 255 of each pixel in the top layer and multiples them with the values for the corresponding pixel from the bottom layer. (S * D)
Overlay, ///< Combines Multiply and Screen blend modes. (2 * S * D) if (2 * D < Da), otherwise (Sa * Da) - 2 * (Da - S) * (Sa - D)
Difference, ///< Subtracts the bottom layer from the top layer or the other way around, to always get a non-negative value. (S - D) if (S > D), otherwise (D - S)
Exclusion, ///< The result is twice the product of the top and bottom layers, subtracted from their sum. s + d - (2 * s * d)
SrcOver, ///< Replace the bottom layer with the top layer.
Darken, ///< Creates a pixel that retains the smallest components of the top and bottom layer pixels. min(S, D)
Lighten, ///< Only has the opposite action of Darken Only. max(S, D)
ColorDodge, ///< Divides the bottom layer by the inverted top layer. D / (255 - S)
ColorBurn, ///< Divides the inverted bottom layer by the top layer, and then inverts the result. 255 - (255 - D) / S
HardLight, ///< The same as Overlay but with the color roles reversed. (2 * S * D) if (S < Sa), otherwise (Sa * Da) - 2 * (Da - S) * (Sa - D)
SoftLight ///< The same as Overlay but with applying pure black or white does not result in pure black or white. (1 - 2 * S) * (D ^ 2) + (2 * S * D)
};
/**
* @brief Enumeration specifying the engine type used for the graphics backend. For multiple backends bitwise operation is allowed.
*/
@ -293,7 +333,7 @@ public:
* The values of the matrix can be set by the transform() API, as well by the translate(),
* scale() and rotate(). In case no transformation was applied, the identity matrix is returned.
*
* @retval The augmented transformation matrix.
* @return The augmented transformation matrix.
*
* @since 0.4
*/
@ -321,6 +361,21 @@ public:
*/
Result composite(std::unique_ptr<Paint> target, CompositeMethod method) noexcept;
/**
* @brief Sets the blending method for the paint object.
*
* The blending feature allows you to combine colors to create visually appealing effects, including transparency, lighting, shading, and color mixing, among others.
* its process involves the combination of colors or images from the source paint object with the destination (the lower layer image) using blending operations.
* The blending operation is determined by the chosen @p BlendMethod, which specifies how the colors or images are combined.
*
* @param[in] method The blending method to be set.
*
* @return Result::Success when the blending method is successfully set.
*
* @BETA_API
*/
Result blend(BlendMethod method) const noexcept;
/**
* @brief Gets the bounding box of the paint object before any transformation.
*
@ -333,6 +388,7 @@ public:
*
* @note The bounding box doesn't indicate the final rendered region. It's the smallest rectangle that encloses the object.
* @see Paint::bounds(float* x, float* y, float* w, float* h, bool transformed);
* @deprecated Use bounds(float* x, float* y, float* w, float* h, bool transformed) instead
*/
TVG_DEPRECATED Result bounds(float* x, float* y, float* w, float* h) const noexcept;
@ -380,6 +436,15 @@ public:
*/
CompositeMethod composite(const Paint** target) const noexcept;
/**
* @brief Gets the blending method of the object.
*
* @return The blending method
*
* @BETA_API
*/
BlendMethod blend() const noexcept;
/**
* @brief Return the unique id value of the paint instance.
*
@ -389,7 +454,6 @@ public:
*/
uint32_t identifier() const noexcept;
_TVG_DECLARE_ACCESSOR();
_TVG_DECLARE_PRIVATE(Paint);
};
@ -525,14 +589,25 @@ public:
*
* @return Result::Success when succeed.
*/
Result reserve(uint32_t n) noexcept;
TVG_DEPRECATED Result reserve(uint32_t n) noexcept;
/**
* @brief Returns the list of the paints that currently held by the Canvas.
*
* This function provides the list of paint nodes, allowing users a direct opportunity to modify the scene tree.
*
* @warning Please avoid accessing the paints during Canvas update/draw. You can access them after calling sync().
* @see Canvas::sync()
*
* @BETA_API
*/
std::list<Paint*>& paints() noexcept;
/**
* @brief Passes drawing elements to the Canvas using Paint objects.
*
* Only pushed paints in the canvas will be drawing targets.
* They are retained by the canvas until you call Canvas::clear().
* If you know the number of the pushed objects in advance, please call Canvas::reserve().
*
* @param[in] paint A Paint object to be drawn.
*
@ -541,7 +616,7 @@ public:
* @retval Result::InsufficientCondition An internal error.
*
* @note The rendering order of the paints is the same as the order as they were pushed into the canvas. Consider sorting the paints before pushing them if you intend to use layering.
* @see Canvas::reserve()
* @see Canvas::paints()
* @see Canvas::clear()
*/
virtual Result push(std::unique_ptr<Paint> paint) noexcept;
@ -555,6 +630,8 @@ public:
* @return Result::Success when succeed, Result::InsufficientCondition otherwise.
*
* @warning If you don't free the paints they become dangled. They are supposed to be reused, otherwise you are responsible for their lives. Thus please use the @p free argument only when you know how it works, otherwise it's not recommended.
* @see Canvas::push()
* @see Canvas::paints()
*/
virtual Result clear(bool free = true) noexcept;
@ -829,7 +906,7 @@ public:
*
* @note For @p rx and @p ry greater than or equal to the half of @p w and the half of @p h, respectively, the shape become an ellipse.
*/
Result appendRect(float x, float y, float w, float h, float rx, float ry) noexcept;
Result appendRect(float x, float y, float w, float h, float rx = 0, float ry = 0) noexcept;
/**
* @brief Appends an ellipse to the path.
@ -905,7 +982,7 @@ public:
*
* @return Result::Success when succeed, Result::FailedAllocation otherwise.
*/
Result stroke(uint8_t r, uint8_t g, uint8_t b, uint8_t a) noexcept;
Result stroke(uint8_t r, uint8_t g, uint8_t b, uint8_t a = 255) noexcept;
/**
* @brief Sets the gradient fill of the stroke for all of the figures from the path.
@ -953,6 +1030,18 @@ public:
*/
Result stroke(StrokeJoin join) noexcept;
/**
* @brief Sets the stroke miterlimit.
*
* @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.
*
* @return Result::Success when succeed, Result::NonSupport unsupported value, Result::FailedAllocation otherwise.
*
* @BETA_API
*/
Result strokeMiterlimit(float miterlimit) noexcept;
/**
* @brief Sets the solid color for all of the figures from the path.
*
@ -968,7 +1057,7 @@ public:
* @note Either a solid color or a gradient fill is applied, depending on what was set as last.
* @note ClipPath won't use the fill values. (see: enum class CompositeMethod::ClipPath)
*/
Result fill(uint8_t r, uint8_t g, uint8_t b, uint8_t a) noexcept;
Result fill(uint8_t r, uint8_t g, uint8_t b, uint8_t a = 255) noexcept;
/**
* @brief Sets the gradient fill for all of the figures from the path.
@ -999,7 +1088,8 @@ public:
* @param[in] strokeFirst If @c true the stroke is rendered before the fill, otherwise the stroke is rendered as the second one (the default option).
*
* @return Result::Success when succeed, Result::FailedAllocation otherwise.
* @BETA_API
*
* @since 0.10
*/
Result order(bool strokeFirst) noexcept;
@ -1039,7 +1129,7 @@ public:
*
* @return Result::Success when succeed.
*/
Result fillColor(uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a) const noexcept;
Result fillColor(uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a = nullptr) const noexcept;
/**
* @brief Gets the fill rule value.
@ -1065,7 +1155,7 @@ public:
*
* @return Result::Success when succeed, Result::InsufficientCondition otherwise.
*/
Result strokeColor(uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a) const noexcept;
Result strokeColor(uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a = nullptr) const noexcept;
/**
* @brief Gets the pointer to the gradient fill of the stroke.
@ -1097,6 +1187,15 @@ public:
*/
StrokeJoin strokeJoin() const noexcept;
/**
* @brief Gets the stroke miterlimit.
*
* @return The stroke miterlimit value when succeed, 4 if no stroke was set.
*
* @BETA_API
*/
float strokeMiterlimit() const noexcept;
/**
* @brief Creates a new Shape object.
*
@ -1120,10 +1219,11 @@ public:
/**
* @class Picture
*
* @brief A class representing an image read in one of the supported formats: raw, svg, png, jpg and etc.
* @brief A class representing an image read in one of the supported formats: raw, svg, png, jpg, lottie(json) and etc.
* Besides the methods inherited from the Paint, it provides methods to load & draw images on the canvas.
*
* @note Supported formats are depended on the available TVG loaders.
* @note See Animation class if the picture data is animatable.
*/
class TVG_API Picture final : public Paint
{
@ -1240,8 +1340,8 @@ public:
* @param[in] triangles An array of Polygons(triangles) that make up the mesh, or null to remove the mesh.
* @param[in] triangleCnt The number of Polygons(triangles) provided, or 0 to remove the mesh.
*
* @return Result::Success When succeed.
* @return Result::Unknown If fails
* @retval Result::Success When succeed.
* @retval Result::Unknown If fails
*
* @note The Polygons are copied internally, so modifying them after calling Mesh::mesh has no affect.
* @warning Please do not use it, this API is not official one. It could be modified in the next version.
@ -1264,15 +1364,6 @@ public:
*/
uint32_t mesh(const Polygon** triangles) const noexcept;
/**
* @brief Gets the position and the size of the loaded SVG picture.
*
* @warning Please do not use it, this API is not official one. It could be modified in the next version.
*
* @BETA_API
*/
Result viewbox(float* x, float* y, float* w, float* h) const noexcept;
/**
* @brief Creates a new Picture object.
*
@ -1289,6 +1380,7 @@ public:
*/
static uint32_t identifier() noexcept;
_TVG_DECLARE_ACCESSOR(Animation);
_TVG_DECLARE_PRIVATE(Picture);
};
@ -1314,14 +1406,14 @@ public:
*
* Only the paints pushed into the scene will be the drawn targets.
* The paints are retained by the scene until Scene::clear() is called.
* If you know the number of the pushed objects in advance, please call Scene::reserve().
*
* @param[in] paint A Paint object to be drawn.
*
* @return Result::Success when succeed, Result::MemoryCorruption otherwise.
*
* @note The rendering order of the paints is the same as the order as they were pushed. Consider sorting the paints before pushing them if you intend to use layering.
* @see Scene::reserve()
* @see Scene::paints()
* @see Scene::clear()
*/
Result push(std::unique_ptr<Paint> paint) noexcept;
@ -1335,7 +1427,21 @@ public:
*
* @return Result::Success when succeed, Result::FailedAllocation otherwise.
*/
Result reserve(uint32_t size) noexcept;
TVG_DEPRECATED Result reserve(uint32_t size) noexcept;
/**
* @brief Returns the list of the paints that currently held by the Scene.
*
* This function provides the list of paint nodes, allowing users a direct opportunity to modify the scene tree.
*
* @warning Please avoid accessing the paints during Scene update/draw. You can access them after calling Canvas::sync().
* @see Canvas::sync()
* @see Scene::push()
* @see Scene::clear()
*
* @BETA_API
*/
std::list<Paint*>& paints() noexcept;
/**
* @brief Sets the total number of the paints pushed into the scene to be zero.
@ -1386,10 +1492,10 @@ public:
*/
enum Colorspace
{
ABGR8888 = 0, ///< The channels are joined in the order: alpha, blue, green, red. Colors are alpha-premultiplied.
ARGB8888, ///< The channels are joined in the order: alpha, red, green, blue. Colors are alpha-premultiplied.
ABGR8888_STRAIGHT, ///< @BETA_API The channels are joined in the order: alpha, blue, green, red. Colors are un-alpha-premultiplied.
ARGB8888_STRAIGHT, ///< @BETA_API The channels are joined in the order: alpha, red, green, blue. Colors are un-alpha-premultiplied.
ABGR8888 = 0, ///< The channels are joined in the order: alpha, blue, green, red. Colors are alpha-premultiplied. (a << 24 | b << 16 | g << 8 | r)
ARGB8888, ///< The channels are joined in the order: alpha, red, green, blue. Colors are alpha-premultiplied. (a << 24 | r << 16 | g << 8 | b)
ABGR8888S, ///< @BETA_API The channels are joined in the order: alpha, blue, green, red. Colors are un-alpha-premultiplied.
ARGB8888S, ///< @BETA_API The channels are joined in the order: alpha, red, green, blue. Colors are un-alpha-premultiplied.
};
/**
@ -1544,6 +1650,101 @@ public:
};
/**
* @class Animation
*
* @brief The Animation class enables manipulation of animatable images.
*
* This class supports the display and control of animation frames.
*
* @BETA_API
*/
class TVG_API Animation
{
public:
~Animation();
/**
* @brief Specifies the current frame in the animation.
*
* @param[in] no The index of the animation frame to be displayed. The index should be less than the totalFrame().
*
* @retval Result::Success Successfully set the frame.
* @retval Result::InsufficientCondition No animatable data loaded from the Picture.
* @retval Result::NonSupport The Picture data does not support animations.
*
* @see totalFrame()
*
* @BETA_API
*/
Result frame(uint32_t no) noexcept;
/**
* @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
* with this Animation instance.
*
* @return A picture instance that is tied to this animation.
*
* @warning The picture instance is owned by Animation. It should not be deleted manually.
*
* @BETA_API
*/
Picture* picture() const noexcept;
/**
* @brief Retrieves the current frame number of the animation.
*
* @return The current frame number of the animation, between 0 and totalFrame() - 1.
*
* @note If the Picture is not properly configured, this function will return 0.
*
* @see Animation::frame(uint32_t no)
* @see Animation::totalFrame()
*
* @BETA_API
*/
uint32_t curFrame() const noexcept;
/**
* @brief Retrieves the total number of frames in the animation.
*
* @return The total number of frames in the animation.
*
* @note Frame numbering starts from 0.
* @note If the Picture is not properly configured, this function will return 0.
*
* @BETA_API
*/
uint32_t totalFrame() const noexcept;
/**
* @brief Retrieves the duration of the animation in seconds.
*
* @return The duration of the animation in seconds.
*
* @note If the Picture is not properly configured, this function will return 0.
*
* @BETA_API
*/
float duration() const noexcept;
/**
* @brief Creates a new Animation object.
*
* @return A new Animation object.
*
* @BETA_API
*/
static std::unique_ptr<Animation> gen() noexcept;
_TVG_DECLARE_PRIVATE(Animation);
};
/**
* @class Saver
*
@ -1629,7 +1830,7 @@ public:
*
* @warning We strongly warn you not to change the paints of a scene unless you really know the design-structure.
*
* @BETA_API
* @since 0.10
*/
class TVG_API Accessor final
{
@ -1645,8 +1846,6 @@ public:
* @return Return the given @p picture instance.
*
* @note The bitmap based picture might not have the scene-tree.
*
* @BETA_API
*/
std::unique_ptr<Picture> set(std::unique_ptr<Picture> picture, std::function<bool(const Paint* paint)> func) noexcept;
@ -1654,20 +1853,39 @@ public:
* @brief Creates a new Accessor object.
*
* @return A new Accessor object.
*
* @BETA_API
*/
static std::unique_ptr<Accessor> gen() noexcept;
_TVG_DECLARE_PRIVATE(Accessor);
};
/**
* @brief The cast() function is a utility function used to cast a 'Paint' to type 'T'.
*
* @BETA_API
*/
template<typename T>
std::unique_ptr<T> cast(Paint* paint)
{
return std::unique_ptr<T>(static_cast<T*>(paint));
}
/**
* @brief The cast() function is a utility function used to cast a 'Fill' to type 'T'.
*
* @BETA_API
*/
template<typename T>
std::unique_ptr<T> cast(Fill* fill)
{
return std::unique_ptr<T>(static_cast<T*>(fill));
}
/** @}*/
} //namespace
#ifdef __cplusplus
}
#endif
#endif //_THORVG_H_

View file

@ -99,15 +99,11 @@ struct SwSize
struct SwOutline
{
SwPoint* pts; //the outline's points
uint32_t ptsCnt; //number of points in the glyph
uint32_t reservedPtsCnt;
uint32_t* cntrs; //the contour end points
uint16_t cntrsCnt; //number of contours in glyph
uint16_t reservedCntrsCnt;
uint8_t* types; //curve type
bool* closed; //opened or closed path?
FillRule fillRule;
Array<SwPoint> pts; //the outline's points
Array<uint32_t> cntrs; //the contour end points
Array<uint8_t> types; //curve type
Array<bool> closed; //opened or closed path?
FillRule fillRule;
};
struct SwSpan
@ -180,6 +176,7 @@ struct SwStroke
SwPoint ptStartSubPath;
SwFixed subPathLineLength;
SwFixed width;
SwFixed miterlimit;
StrokeCap cap;
StrokeJoin join;
@ -238,18 +235,25 @@ struct SwImage
bool scaled = false; //draw scaled image
};
struct SwBlender
{
uint32_t (*join)(uint8_t r, uint8_t g, uint8_t b, uint8_t a);
uint8_t (*luma)(uint8_t* c);
};
typedef uint32_t(*SwBlender)(uint32_t s, uint32_t d, uint8_t a); //src, dst, alpha
typedef uint32_t(*SwJoin)(uint8_t r, uint8_t g, uint8_t b, uint8_t a); //color channel join
typedef uint8_t(*SwAlpha)(uint8_t*); //blending alpha
struct SwCompositor;
struct SwSurface : Surface
{
SwBlender blender; //mandatory
SwJoin join;
SwAlpha alphas[4]; //Alpha:2, InvAlpha:3, Luma:4, InvLuma:5
SwBlender blender = nullptr; //blender (optional)
SwCompositor* compositor = nullptr; //compositor (optional)
BlendMethod blendMethod; //blending method (uint8_t)
SwAlpha alpha(CompositeMethod method)
{
auto idx = (int)(method) - 2; //0: None, 1: ClipPath
return alphas[idx > 3 ? 0 : idx]; //CompositeMethod has only four Matting methods.
}
};
struct SwCompositor : Compositor
@ -273,15 +277,25 @@ static inline SwCoord TO_SWCOORD(float val)
return SwCoord(val * 64.0f);
}
static inline uint32_t JOIN(uint8_t c0, uint8_t c1, uint8_t c2, uint8_t c3)
{
return (c0 << 24 | c1 << 16 | c2 << 8 | c3);
}
static inline uint32_t ALPHA_BLEND(uint32_t c, uint32_t a)
{
return (((((c >> 8) & 0x00ff00ff) * a + 0x00ff00ff) & 0xff00ff00) +
((((c & 0x00ff00ff) * a + 0x00ff00ff) >> 8) & 0x00ff00ff));
}
static inline uint32_t INTERPOLATE(uint32_t a, uint32_t c0, uint32_t c1)
static inline uint32_t INTERPOLATE(uint32_t s, uint32_t d, uint8_t a)
{
return (((((((c0 >> 8) & 0xff00ff) - ((c1 >> 8) & 0xff00ff)) * a) + (c1 & 0xff00ff00)) & 0xff00ff00) + ((((((c0 & 0xff00ff) - (c1 & 0xff00ff)) * a) >> 8) + (c1 & 0xff00ff)) & 0xff00ff));
return (((((((s >> 8) & 0xff00ff) - ((d >> 8) & 0xff00ff)) * a) + (d & 0xff00ff00)) & 0xff00ff00) + ((((((s & 0xff00ff) - (d & 0xff00ff)) * a) >> 8) + (d & 0xff00ff)) & 0xff00ff));
}
static inline uint8_t INTERPOLATE8(uint8_t s, uint8_t d, uint8_t a)
{
return ((s * a + 0xff) >> 8) + ((d * ~a + 0xff) >> 8);
}
static inline SwCoord HALF_STROKE(float width)
@ -289,6 +303,207 @@ static inline SwCoord HALF_STROKE(float width)
return TO_SWCOORD(width * 0.5f);
}
static inline uint8_t A(uint32_t c)
{
return ((c) >> 24);
}
static inline uint8_t IA(uint32_t c)
{
return (~(c) >> 24);
}
static inline uint8_t C1(uint32_t c)
{
return ((c) >> 16);
}
static inline uint8_t C2(uint32_t c)
{
return ((c) >> 8);
}
static inline uint8_t C3(uint32_t c)
{
return (c);
}
static inline uint32_t opBlendInterp(uint32_t s, uint32_t d, uint8_t a)
{
return INTERPOLATE(s, d, a);
}
static inline uint32_t opBlendNormal(uint32_t s, uint32_t d, uint8_t a)
{
auto t = ALPHA_BLEND(s, a);
return t + ALPHA_BLEND(d, IA(t));
}
static inline uint32_t opBlendPreNormal(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a)
{
return s + ALPHA_BLEND(d, IA(s));
}
static inline uint32_t opBlendSrcOver(uint32_t s, TVG_UNUSED uint32_t d, TVG_UNUSED uint8_t a)
{
return s;
}
//TODO: BlendMethod could remove the alpha parameter.
static inline uint32_t opBlendDifference(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a)
{
//if (s > d) => s - d
//else => d - s
auto c1 = (C1(s) > C1(d)) ? (C1(s) - C1(d)) : (C1(d) - C1(s));
auto c2 = (C2(s) > C2(d)) ? (C2(s) - C2(d)) : (C2(d) - C2(s));
auto c3 = (C3(s) > C3(d)) ? (C3(s) - C3(d)) : (C3(d) - C3(s));
return JOIN(255, c1, c2, c3);
}
static inline uint32_t opBlendExclusion(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a)
{
//A + B - 2AB
auto c1 = min(255, C1(s) + C1(d) - min(255, (C1(s) * C1(d)) << 1));
auto c2 = min(255, C2(s) + C2(d) - min(255, (C2(s) * C2(d)) << 1));
auto c3 = min(255, C3(s) + C3(d) - min(255, (C3(s) * C3(d)) << 1));
return JOIN(255, c1, c2, c3);
}
static inline uint32_t opBlendAdd(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a)
{
// s + d
auto c1 = min(C1(s) + C1(d), 255);
auto c2 = min(C2(s) + C2(d), 255);
auto c3 = min(C3(s) + C3(d), 255);
return JOIN(255, c1, c2, c3);
}
static inline uint32_t opBlendScreen(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a)
{
// s + d - s * d
auto c1 = C1(s) + C1(d) - MULTIPLY(C1(s), C1(d));
auto c2 = C2(s) + C2(d) - MULTIPLY(C2(s), C2(d));
auto c3 = C3(s) + C3(d) - MULTIPLY(C3(s), C3(d));
return JOIN(255, c1, c2, c3);
}
static inline uint32_t opBlendMultiply(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a)
{
// s * d
auto c1 = MULTIPLY(C1(s), C1(d));
auto c2 = MULTIPLY(C2(s), C2(d));
auto c3 = MULTIPLY(C3(s), C3(d));
return JOIN(255, c1, c2, c3);
}
static inline uint32_t opBlendOverlay(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a)
{
// if (2 * d < da) => 2 * s * d,
// else => 1 - 2 * (1 - s) * (1 - d)
auto c1 = (C1(d) < 128) ? min(255, 2 * MULTIPLY(C1(s), C1(d))) : (255 - min(255, 2 * MULTIPLY(255 - C1(s), 255 - C1(d))));
auto c2 = (C2(d) < 128) ? min(255, 2 * MULTIPLY(C2(s), C2(d))) : (255 - min(255, 2 * MULTIPLY(255 - C2(s), 255 - C2(d))));
auto c3 = (C3(d) < 128) ? min(255, 2 * MULTIPLY(C3(s), C3(d))) : (255 - min(255, 2 * MULTIPLY(255 - C3(s), 255 - C3(d))));
return JOIN(255, c1, c2, c3);
}
static inline uint32_t opBlendDarken(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a)
{
// min(s, d)
auto c1 = min(C1(s), C1(d));
auto c2 = min(C2(s), C2(d));
auto c3 = min(C3(s), C3(d));
return JOIN(255, c1, c2, c3);
}
static inline uint32_t opBlendLighten(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a)
{
// max(s, d)
auto c1 = max(C1(s), C1(d));
auto c2 = max(C2(s), C2(d));
auto c3 = max(C3(s), C3(d));
return JOIN(255, c1, c2, c3);
}
static inline uint32_t opBlendColorDodge(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a)
{
// d / (1 - s)
auto is = 0xffffffff - s;
auto c1 = (C1(is) > 0) ? (C1(d) / C1(is)) : C1(d);
auto c2 = (C2(is) > 0) ? (C2(d) / C2(is)) : C2(d);
auto c3 = (C3(is) > 0) ? (C3(d) / C3(is)) : C3(d);
return JOIN(255, c1, c2, c3);
}
static inline uint32_t opBlendColorBurn(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a)
{
// 1 - (1 - d) / s
auto id = 0xffffffff - d;
auto c1 = 255 - ((C1(s) > 0) ? (C1(id) / C1(s)) : C1(id));
auto c2 = 255 - ((C2(s) > 0) ? (C2(id) / C2(s)) : C2(id));
auto c3 = 255 - ((C3(s) > 0) ? (C3(id) / C3(s)) : C3(id));
return JOIN(255, c1, c2, c3);
}
static inline uint32_t opBlendHardLight(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a)
{
auto c1 = (C1(s) < 128) ? min(255, 2 * MULTIPLY(C1(s), C1(d))) : (255 - min(255, 2 * MULTIPLY(255 - C1(s), 255 - C1(d))));
auto c2 = (C2(s) < 128) ? min(255, 2 * MULTIPLY(C2(s), C2(d))) : (255 - min(255, 2 * MULTIPLY(255 - C2(s), 255 - C2(d))));
auto c3 = (C3(s) < 128) ? min(255, 2 * MULTIPLY(C3(s), C3(d))) : (255 - min(255, 2 * MULTIPLY(255 - C3(s), 255 - C3(d))));
return JOIN(255, c1, c2, c3);
}
static inline uint32_t opBlendSoftLight(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a)
{
//(255 - 2 * s) * (d * d) + (2 * s * b)
auto c1 = min(255, MULTIPLY(255 - min(255, 2 * C1(s)), MULTIPLY(C1(d), C1(d))) + 2 * MULTIPLY(C1(s), C1(d)));
auto c2 = min(255, MULTIPLY(255 - min(255, 2 * C2(s)), MULTIPLY(C2(d), C2(d))) + 2 * MULTIPLY(C2(s), C2(d)));
auto c3 = min(255, MULTIPLY(255 - min(255, 2 * C3(s)), MULTIPLY(C3(d), C3(d))) + 2 * MULTIPLY(C3(s), C3(d)));
return JOIN(255, c1, c2, c3);
}
static inline uint32_t opMaskAdd(uint32_t s, uint32_t d, uint8_t a)
{
return opBlendNormal(s, d, a);
}
static inline uint32_t opMaskSubtract(uint32_t s, uint32_t d, uint8_t a)
{
return ALPHA_BLEND(d, MULTIPLY(IA(s), a));
}
static inline uint32_t opMaskDifference(uint32_t s, uint32_t d, uint8_t a)
{
auto t = ALPHA_BLEND(s, a);
return ALPHA_BLEND(t, IA(d)) + ALPHA_BLEND(d, IA(t));
}
static inline uint32_t opMaskIntersect(uint32_t s, uint32_t d, uint8_t a)
{
return ALPHA_BLEND(d, MULTIPLY(IA(s), a));
}
static inline uint32_t opMaskPreAdd(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a)
{
return opBlendPreNormal(s, d, a);
}
static inline uint32_t opMaskPreSubtract(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a)
{
return ALPHA_BLEND(d, IA(s));
}
static inline uint32_t opMaskPreDifference(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a)
{
return ALPHA_BLEND(s, IA(d)) + ALPHA_BLEND(d, IA(s));
}
static inline uint32_t opMaskPreIntersect(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a)
{
return ALPHA_BLEND(d, MULTIPLY(a, IA(s)));
}
int64_t mathMultiply(int64_t a, int64_t b);
int64_t mathDivide(int64_t a, int64_t b);
int64_t mathMulDiv(int64_t a, int64_t b, int64_t c);
@ -315,8 +530,8 @@ void shapeResetStroke(SwShape* shape, const RenderShape* rshape, const Matrix* t
bool shapeGenStrokeRle(SwShape* shape, const RenderShape* rshape, const Matrix* transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid);
void shapeFree(SwShape* shape);
void shapeDelStroke(SwShape* shape);
bool shapeGenFillColors(SwShape* shape, const Fill* fill, const Matrix* transform, SwSurface* surface, uint32_t opacity, bool ctable);
bool shapeGenStrokeFillColors(SwShape* shape, const Fill* fill, const Matrix* transform, SwSurface* surface, uint32_t opacity, bool ctable);
bool shapeGenFillColors(SwShape* shape, const Fill* fill, const Matrix* transform, SwSurface* surface, uint8_t opacity, bool ctable);
bool shapeGenStrokeFillColors(SwShape* shape, const Fill* fill, const Matrix* transform, SwSurface* surface, uint8_t opacity, bool ctable);
void shapeResetFill(SwShape* shape);
void shapeResetStrokeFill(SwShape* shape);
void shapeDelFill(SwShape* shape);
@ -333,11 +548,16 @@ void imageDelOutline(SwImage* image, SwMpool* mpool, uint32_t tid);
void imageReset(SwImage* image);
void imageFree(SwImage* image);
bool fillGenColorTable(SwFill* fill, const Fill* fdata, const Matrix* transform, SwSurface* surface, uint32_t opacity, bool ctable);
bool fillGenColorTable(SwFill* fill, const Fill* fdata, const Matrix* transform, SwSurface* surface, uint8_t opacity, bool ctable);
void fillReset(SwFill* fill);
void fillFree(SwFill* fill);
void fillFetchLinear(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len);
void fillFetchRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len);
//OPTIMIZE_ME: Skip the function pointer access
void fillLinear(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, SwBlender op, uint8_t a); //blending ver.
void fillLinear(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, SwBlender op, SwBlender op2, uint8_t a); //blending + BlendingMethod(op2) ver.
void fillLinear(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, uint8_t* cmp, SwAlpha alpha, uint8_t csize, uint8_t opacity); //masking ver.
void fillRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, SwBlender op, uint8_t a); //blending ver.
void fillRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, SwBlender op, SwBlender op2, uint8_t a); //blending + BlendingMethod(op2) ver.
void fillRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, uint8_t* cmp, SwAlpha alpha, uint8_t csize, uint8_t opacity); //masking ver.
SwRleData* rleRender(SwRleData* rle, const SwOutline* outline, const SwBBox& renderRegion, bool antiAlias);
SwRleData* rleRender(const SwBBox* bbox);
@ -358,11 +578,12 @@ void mpoolRetStrokeOutline(SwMpool* mpool, unsigned idx);
bool rasterCompositor(SwSurface* surface);
bool rasterGradientShape(SwSurface* surface, SwShape* shape, unsigned id);
bool rasterShape(SwSurface* surface, SwShape* shape, uint8_t r, uint8_t g, uint8_t b, uint8_t a);
bool rasterImage(SwSurface* surface, SwImage* image, const RenderMesh* mesh, const Matrix* transform, const SwBBox& bbox, uint32_t opacity);
bool rasterImage(SwSurface* surface, SwImage* image, const RenderMesh* mesh, const Matrix* transform, const SwBBox& bbox, uint8_t opacity);
bool rasterStroke(SwSurface* surface, SwShape* shape, uint8_t r, uint8_t g, uint8_t b, uint8_t a);
bool rasterGradientStroke(SwSurface* surface, SwShape* shape, unsigned id);
bool rasterClear(SwSurface* surface, uint32_t x, uint32_t y, uint32_t w, uint32_t h);
void rasterRGBA32(uint32_t *dst, uint32_t val, uint32_t offset, int32_t len);
void rasterPixel32(uint32_t *dst, uint32_t val, uint32_t offset, int32_t len);
void rasterGrayscale8(uint8_t *dst, uint8_t val, uint32_t offset, int32_t len);
void rasterUnpremultiply(Surface* surface);
void rasterPremultiply(Surface* surface);
bool rasterConvertCS(Surface* surface, ColorSpace to);

View file

@ -33,7 +33,7 @@
#define FIXPT_SIZE (1<<FIXPT_BITS)
static bool _updateColorTable(SwFill* fill, const Fill* fdata, const SwSurface* surface, uint32_t opacity)
static bool _updateColorTable(SwFill* fill, const Fill* fdata, const SwSurface* surface, uint8_t opacity)
{
if (!fill->ctable) {
fill->ctable = static_cast<uint32_t*>(malloc(GRADIENT_STOP_SIZE * sizeof(uint32_t)));
@ -46,13 +46,13 @@ static bool _updateColorTable(SwFill* fill, const Fill* fdata, const SwSurface*
auto pColors = colors;
auto a = (pColors->a * opacity) / 255;
auto a = MULTIPLY(pColors->a, opacity);
if (a < 255) fill->translucent = true;
auto r = pColors->r;
auto g = pColors->g;
auto b = pColors->b;
auto rgba = surface->blender.join(r, g, b, a);
auto rgba = surface->join(r, g, b, a);
auto inc = 1.0f / static_cast<float>(GRADIENT_STOP_SIZE);
auto pos = 1.5f * inc;
@ -70,17 +70,17 @@ static bool _updateColorTable(SwFill* fill, const Fill* fdata, const SwSurface*
auto curr = colors + j;
auto next = curr + 1;
auto delta = 1.0f / (next->offset - curr->offset);
auto a2 = (next->a * opacity) / 255;
auto a2 = MULTIPLY(next->a, opacity);
if (!fill->translucent && a2 < 255) fill->translucent = true;
auto rgba2 = surface->blender.join(next->r, next->g, next->b, a2);
auto rgba2 = surface->join(next->r, next->g, next->b, a2);
while (pos < next->offset && i < GRADIENT_STOP_SIZE) {
auto t = (pos - curr->offset) * delta;
auto dist = static_cast<int32_t>(255 * t);
auto dist2 = 255 - dist;
auto color = INTERPOLATE(dist2, rgba, rgba2);
auto color = INTERPOLATE(rgba, rgba2, dist2);
fill->ctable[i] = ALPHA_BLEND((color | 0xff000000), (color >> 24));
++i;
@ -233,7 +233,7 @@ static inline uint32_t _pixel(const SwFill* fill, float pos)
/* External Class Implementation */
/************************************************************************/
void fillFetchRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len)
void fillRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, uint8_t* cmp, SwAlpha alpha, uint8_t csize, uint8_t opacity)
{
auto rx = (x + 0.5f) * fill->radial.a11 + (y + 0.5f) * fill->radial.a12 + fill->radial.shiftX;
auto ry = (x + 0.5f) * fill->radial.a21 + (y + 0.5f) * fill->radial.a22 + fill->radial.shiftY;
@ -244,16 +244,146 @@ void fillFetchRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x,
auto detFirstDerivative = 2.0f * (fill->radial.a11 * rx + fill->radial.a21 * ry) + 0.5f * detSecondDerivative;
auto det = rx * rx + ry * ry;
for (uint32_t i = 0 ; i < len ; ++i) {
*dst = _pixel(fill, sqrtf(det));
++dst;
if (opacity == 255) {
for (uint32_t i = 0 ; i < len ; ++i, ++dst, cmp += csize) {
*dst = opBlendNormal(_pixel(fill, sqrtf(det)), *dst, alpha(cmp));
det += detFirstDerivative;
detFirstDerivative += detSecondDerivative;
}
} else {
for (uint32_t i = 0 ; i < len ; ++i, ++dst, cmp += csize) {
*dst = opBlendNormal(_pixel(fill, sqrtf(det)), *dst, MULTIPLY(opacity, alpha(cmp)));
det += detFirstDerivative;
detFirstDerivative += detSecondDerivative;
}
}
}
void fillRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, SwBlender op, uint8_t a)
{
auto rx = (x + 0.5f) * fill->radial.a11 + (y + 0.5f) * fill->radial.a12 + fill->radial.shiftX;
auto ry = (x + 0.5f) * fill->radial.a21 + (y + 0.5f) * fill->radial.a22 + fill->radial.shiftY;
// detSecondDerivative = d(detFirstDerivative)/dx = d( d(det)/dx )/dx
auto detSecondDerivative = fill->radial.detSecDeriv;
// detFirstDerivative = d(det)/dx
auto detFirstDerivative = 2.0f * (fill->radial.a11 * rx + fill->radial.a21 * ry) + 0.5f * detSecondDerivative;
auto det = rx * rx + ry * ry;
for (uint32_t i = 0 ; i < len ; ++i, ++dst) {
*dst = op(_pixel(fill, sqrtf(det)), *dst, a);
det += detFirstDerivative;
detFirstDerivative += detSecondDerivative;
}
}
void fillFetchLinear(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len)
void fillRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, SwBlender op, SwBlender op2, uint8_t a)
{
auto rx = (x + 0.5f) * fill->radial.a11 + (y + 0.5f) * fill->radial.a12 + fill->radial.shiftX;
auto ry = (x + 0.5f) * fill->radial.a21 + (y + 0.5f) * fill->radial.a22 + fill->radial.shiftY;
// detSecondDerivative = d(detFirstDerivative)/dx = d( d(det)/dx )/dx
auto detSecondDerivative = fill->radial.detSecDeriv;
// detFirstDerivative = d(det)/dx
auto detFirstDerivative = 2.0f * (fill->radial.a11 * rx + fill->radial.a21 * ry) + 0.5f * detSecondDerivative;
auto det = rx * rx + ry * ry;
if (a == 255) {
for (uint32_t i = 0 ; i < len ; ++i, ++dst) {
auto tmp = op(_pixel(fill, sqrtf(det)), *dst, 255);
*dst = op2(tmp, *dst, 255);
det += detFirstDerivative;
detFirstDerivative += detSecondDerivative;
}
} else {
for (uint32_t i = 0 ; i < len ; ++i, ++dst) {
auto tmp = op(_pixel(fill, sqrtf(det)), *dst, 255);
auto tmp2 = op2(tmp, *dst, 255);
*dst = INTERPOLATE(tmp2, *dst, a);
det += detFirstDerivative;
detFirstDerivative += detSecondDerivative;
}
}
}
void fillLinear(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, uint8_t* cmp, SwAlpha alpha, uint8_t csize, uint8_t opacity)
{
//Rotation
float rx = x + 0.5f;
float ry = y + 0.5f;
float t = (fill->linear.dx * rx + fill->linear.dy * ry + fill->linear.offset) * (GRADIENT_STOP_SIZE - 1);
float inc = (fill->linear.dx) * (GRADIENT_STOP_SIZE - 1);
if (opacity == 255) {
if (mathZero(inc)) {
auto color = _fixedPixel(fill, static_cast<int32_t>(t * FIXPT_SIZE));
for (uint32_t i = 0; i < len; ++i, ++dst, cmp += csize) {
*dst = opBlendNormal(color, *dst, alpha(cmp));
}
return;
}
auto vMax = static_cast<float>(INT32_MAX >> (FIXPT_BITS + 1));
auto vMin = -vMax;
auto v = t + (inc * len);
//we can use fixed point math
if (v < vMax && v > vMin) {
auto t2 = static_cast<int32_t>(t * FIXPT_SIZE);
auto inc2 = static_cast<int32_t>(inc * FIXPT_SIZE);
for (uint32_t j = 0; j < len; ++j, ++dst, cmp += csize) {
*dst = opBlendNormal(_fixedPixel(fill, t2), *dst, alpha(cmp));
t2 += inc2;
}
//we have to fallback to float math
} else {
uint32_t counter = 0;
while (counter++ < len) {
*dst = opBlendNormal(_pixel(fill, t / GRADIENT_STOP_SIZE), *dst, alpha(cmp));
++dst;
t += inc;
cmp += csize;
}
}
} else {
if (mathZero(inc)) {
auto color = _fixedPixel(fill, static_cast<int32_t>(t * FIXPT_SIZE));
for (uint32_t i = 0; i < len; ++i, ++dst, cmp += csize) {
*dst = opBlendNormal(color, *dst, MULTIPLY(alpha(cmp), opacity));
}
return;
}
auto vMax = static_cast<float>(INT32_MAX >> (FIXPT_BITS + 1));
auto vMin = -vMax;
auto v = t + (inc * len);
//we can use fixed point math
if (v < vMax && v > vMin) {
auto t2 = static_cast<int32_t>(t * FIXPT_SIZE);
auto inc2 = static_cast<int32_t>(inc * FIXPT_SIZE);
for (uint32_t j = 0; j < len; ++j, ++dst, cmp += csize) {
*dst = opBlendNormal(_fixedPixel(fill, t2), *dst, MULTIPLY(alpha(cmp), opacity));
t2 += inc2;
}
//we have to fallback to float math
} else {
uint32_t counter = 0;
while (counter++ < len) {
*dst = opBlendNormal(_pixel(fill, t / GRADIENT_STOP_SIZE), *dst, MULTIPLY(opacity, alpha(cmp)));
++dst;
t += inc;
cmp += csize;
}
}
}
}
void fillLinear(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, SwBlender op, uint8_t a)
{
//Rotation
float rx = x + 0.5f;
@ -263,7 +393,9 @@ void fillFetchLinear(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x,
if (mathZero(inc)) {
auto color = _fixedPixel(fill, static_cast<int32_t>(t * FIXPT_SIZE));
rasterRGBA32(dst, color, 0, len);
for (uint32_t i = 0; i < len; ++i, ++dst) {
*dst = op(color, *dst, a);
}
return;
}
@ -275,16 +407,15 @@ void fillFetchLinear(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x,
if (v < vMax && v > vMin) {
auto t2 = static_cast<int32_t>(t * FIXPT_SIZE);
auto inc2 = static_cast<int32_t>(inc * FIXPT_SIZE);
for (uint32_t j = 0; j < len; ++j) {
*dst = _fixedPixel(fill, t2);
++dst;
for (uint32_t j = 0; j < len; ++j, ++dst) {
*dst = op(_fixedPixel(fill, t2), *dst, a);
t2 += inc2;
}
//we have to fallback to float math
} else {
uint32_t counter = 0;
while (counter++ < len) {
*dst = _pixel(fill, t / GRADIENT_STOP_SIZE);
*dst = op(_pixel(fill, t / GRADIENT_STOP_SIZE), *dst, a);
++dst;
t += inc;
}
@ -292,7 +423,82 @@ void fillFetchLinear(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x,
}
bool fillGenColorTable(SwFill* fill, const Fill* fdata, const Matrix* transform, SwSurface* surface, uint32_t opacity, bool ctable)
void fillLinear(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, SwBlender op, SwBlender op2, uint8_t a)
{
//Rotation
float rx = x + 0.5f;
float ry = y + 0.5f;
float t = (fill->linear.dx * rx + fill->linear.dy * ry + fill->linear.offset) * (GRADIENT_STOP_SIZE - 1);
float inc = (fill->linear.dx) * (GRADIENT_STOP_SIZE - 1);
if (mathZero(inc)) {
auto color = _fixedPixel(fill, static_cast<int32_t>(t * FIXPT_SIZE));
if (a == 255) {
for (uint32_t i = 0; i < len; ++i, ++dst) {
auto tmp = op(color, *dst, a);
*dst = op2(tmp, *dst, 255);
}
} else {
for (uint32_t i = 0; i < len; ++i, ++dst) {
auto tmp = op(color, *dst, a);
auto tmp2 = op2(tmp, *dst, 255);
*dst = INTERPOLATE(tmp2, *dst, a);
}
}
return;
}
auto vMax = static_cast<float>(INT32_MAX >> (FIXPT_BITS + 1));
auto vMin = -vMax;
auto v = t + (inc * len);
if (a == 255) {
//we can use fixed point math
if (v < vMax && v > vMin) {
auto t2 = static_cast<int32_t>(t * FIXPT_SIZE);
auto inc2 = static_cast<int32_t>(inc * FIXPT_SIZE);
for (uint32_t j = 0; j < len; ++j, ++dst) {
auto tmp = op(_fixedPixel(fill, t2), *dst, 255);
*dst = op2(tmp, *dst, 255);
t2 += inc2;
}
//we have to fallback to float math
} else {
uint32_t counter = 0;
while (counter++ < len) {
auto tmp = op(_pixel(fill, t / GRADIENT_STOP_SIZE), *dst, 255);
*dst = op2(tmp, *dst, 255);
++dst;
t += inc;
}
}
} else {
//we can use fixed point math
if (v < vMax && v > vMin) {
auto t2 = static_cast<int32_t>(t * FIXPT_SIZE);
auto inc2 = static_cast<int32_t>(inc * FIXPT_SIZE);
for (uint32_t j = 0; j < len; ++j, ++dst) {
auto tmp = op(_fixedPixel(fill, t2), *dst, 255);
auto tmp2 = op2(tmp, *dst, 255);
*dst = INTERPOLATE(tmp2, *dst, a);
t2 += inc2;
}
//we have to fallback to float math
} else {
uint32_t counter = 0;
while (counter++ < len) {
auto tmp = op(_pixel(fill, t / GRADIENT_STOP_SIZE), *dst, 255);
auto tmp2 = op2(tmp, *dst, 255);
*dst = INTERPOLATE(tmp2, *dst, a);
++dst;
t += inc;
}
}
}
}
bool fillGenColorTable(SwFill* fill, const Fill* fdata, const Matrix* transform, SwSurface* surface, uint8_t opacity, bool ctable)
{
if (!fill) return false;

View file

@ -39,18 +39,10 @@ static bool _genOutline(SwImage* image, const RenderMesh* mesh, const Matrix* tr
image->outline = mpoolReqOutline(mpool, tid);
auto outline = image->outline;
if (outline->reservedPtsCnt < 5) {
outline->reservedPtsCnt = 5;
outline->pts = static_cast<SwPoint*>(realloc(outline->pts, outline->reservedPtsCnt * sizeof(SwPoint)));
outline->types = static_cast<uint8_t*>(realloc(outline->types, outline->reservedPtsCnt * sizeof(uint8_t)));
}
if (outline->reservedCntrsCnt < 1) {
outline->reservedCntrsCnt = 1;
outline->cntrs = static_cast<uint32_t*>(realloc(outline->cntrs, outline->reservedCntrsCnt * sizeof(uint32_t)));
outline->closed = static_cast<bool*>(realloc(outline->closed, outline->reservedCntrsCnt * sizeof(bool)));
outline->closed[0] = true;
}
outline->pts.reserve(5);
outline->types.reserve(5);
outline->cntrs.reserve(1);
outline->closed.reserve(1);
Point to[4];
if (mesh->triangleCnt > 0) {
@ -97,17 +89,14 @@ static bool _genOutline(SwImage* image, const RenderMesh* mesh, const Matrix* tr
}
for (int i = 0; i < 4; i++) {
outline->pts[outline->ptsCnt] = mathTransform(&to[i], transform);
outline->types[outline->ptsCnt] = SW_CURVE_TYPE_POINT;
++outline->ptsCnt;
outline->pts.push(mathTransform(&to[i], transform));
outline->types.push(SW_CURVE_TYPE_POINT);
}
outline->pts[outline->ptsCnt] = outline->pts[0];
outline->types[outline->ptsCnt] = SW_CURVE_TYPE_POINT;
++outline->ptsCnt;
outline->cntrs[outline->cntrsCnt] = outline->ptsCnt - 1;
++outline->cntrsCnt;
outline->pts.push(outline->pts.data[0]);
outline->types.push(SW_CURVE_TYPE_POINT);
outline->cntrs.push(outline->pts.count - 1);
outline->closed.push(true);
image->outline = outline;

View file

@ -465,9 +465,9 @@ bool mathUpdateOutlineBBox(const SwOutline* outline, const SwBBox& clipRegion, S
{
if (!outline) return false;
auto pt = outline->pts;
auto pt = outline->pts.data;
if (outline->ptsCnt == 0 || outline->cntrsCnt <= 0) {
if (outline->pts.empty() || outline->cntrs.empty()) {
renderRegion.reset();
return false;
}
@ -477,9 +477,7 @@ bool mathUpdateOutlineBBox(const SwOutline* outline, const SwBBox& clipRegion, S
auto yMin = pt->y;
auto yMax = pt->y;
++pt;
for (uint32_t i = 1; i < outline->ptsCnt; ++i, ++pt) {
for (++pt; pt < outline->pts.end(); ++pt) {
if (xMin > pt->x) xMin = pt->x;
if (xMax < pt->x) xMax = pt->x;
if (yMin > pt->y) yMin = pt->y;

View file

@ -40,8 +40,10 @@ SwOutline* mpoolReqOutline(SwMpool* mpool, unsigned idx)
void mpoolRetOutline(SwMpool* mpool, unsigned idx)
{
mpool->outline[idx].cntrsCnt = 0;
mpool->outline[idx].ptsCnt = 0;
mpool->outline[idx].pts.clear();
mpool->outline[idx].cntrs.clear();
mpool->outline[idx].types.clear();
mpool->outline[idx].closed.clear();
}
@ -53,8 +55,10 @@ SwOutline* mpoolReqStrokeOutline(SwMpool* mpool, unsigned idx)
void mpoolRetStrokeOutline(SwMpool* mpool, unsigned idx)
{
mpool->strokeOutline[idx].cntrsCnt = 0;
mpool->strokeOutline[idx].ptsCnt = 0;
mpool->strokeOutline[idx].pts.clear();
mpool->strokeOutline[idx].cntrs.clear();
mpool->strokeOutline[idx].types.clear();
mpool->strokeOutline[idx].closed.clear();
}
@ -93,42 +97,19 @@ bool mpoolClear(SwMpool* mpool)
SwOutline* p;
for (unsigned i = 0; i < mpool->allocSize; ++i) {
//Outline
p = &mpool->outline[i];
free(p->cntrs);
p->cntrs = nullptr;
free(p->pts);
p->pts = nullptr;
free(p->types);
p->types = nullptr;
free(p->closed);
p->closed = nullptr;
p->cntrsCnt = p->reservedCntrsCnt = 0;
p->ptsCnt = p->reservedPtsCnt = 0;
p->pts.reset();
p->cntrs.reset();
p->types.reset();
p->closed.reset();
//StrokeOutline
p = &mpool->strokeOutline[i];
free(p->cntrs);
p->cntrs = nullptr;
free(p->pts);
p->pts = nullptr;
free(p->types);
p->types = nullptr;
free(p->closed);
p->closed = nullptr;
p->cntrsCnt = p->reservedCntrsCnt = 0;
p->ptsCnt = p->reservedPtsCnt = 0;
p->pts.reset();
p->cntrs.reset();
p->types.reset();
p->closed.reset();
}
return true;

File diff suppressed because it is too large Load diff

View file

@ -62,7 +62,7 @@ static inline __m128i ALPHA_BLEND(__m128i c, __m128i a)
}
static void avxRasterRGBA32(uint32_t *dst, uint32_t val, uint32_t offset, int32_t len)
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
uint32_t iterations = len / N_32BITS_IN_256REG;
@ -89,12 +89,12 @@ static bool avxRasterTranslucentRect(SwSurface* surface, const SwBBox& region, u
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);
auto ialpha = 255 - static_cast<uint8_t>(_alpha(color));
uint32_t ialpha = 255 - a;
auto avxColor = _mm_set1_epi32(color);
auto avxIalpha = _mm_set1_epi8(ialpha);
@ -138,7 +138,7 @@ static bool avxRasterTranslucentRle(SwSurface* surface, const SwRleData* rle, ui
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;
@ -148,7 +148,7 @@ static bool avxRasterTranslucentRle(SwSurface* surface, const SwRleData* rle, ui
if (span->coverage < 255) src = ALPHA_BLEND(color, span->coverage);
else src = color;
auto ialpha = 255 - static_cast<uint8_t>(_alpha(src));
auto ialpha = IA(src);
//1. fill the not aligned memory (for 128-bit registers a 16-bytes alignment is required)
auto notAligned = ((uintptr_t)dst & 0xf) / 4;

View file

@ -21,9 +21,41 @@
*/
template<typename PIXEL_T>
static void inline cRasterPixels(PIXEL_T* dst, uint32_t val, uint32_t offset, int32_t len)
static void inline cRasterPixels(PIXEL_T* dst, PIXEL_T val, uint32_t offset, int32_t len)
{
dst += offset;
//fix the misaligned memory
auto alignOffset = (long long) dst % 8;
if (alignOffset > 0) {
if (sizeof(PIXEL_T) == 4) alignOffset /= 4;
else if (sizeof(PIXEL_T) == 1) alignOffset = 8 - alignOffset;
while (alignOffset > 0 && len > 0) {
*dst++ = val;
--len;
--alignOffset;
}
}
//64bits faster clear
if ((sizeof(PIXEL_T) == 4)) {
auto val64 = (uint64_t(val) << 32) | uint64_t(val);
while (len > 1) {
*reinterpret_cast<uint64_t*>(dst) = val64;
len -= 2;
dst += 2;
}
} else if (sizeof(PIXEL_T) == 1) {
auto val32 = (uint32_t(val) << 24) | (uint32_t(val) << 16) | (uint32_t(val) << 8) | uint32_t(val);
auto val64 = (uint64_t(val32) << 32) | val32;
while (len > 7) {
*reinterpret_cast<uint64_t*>(dst) = val64;
len -= 8;
dst += 8;
}
}
//leftovers
while (len--) *dst++ = val;
}
@ -34,14 +66,15 @@ static bool inline cRasterTranslucentRle(SwSurface* surface, const SwRleData* rl
//32bit channels
if (surface->channelSize == sizeof(uint32_t)) {
auto color = surface->blender.join(r, g, b, a);
auto color = surface->join(r, g, b, a);
uint32_t src;
for (uint32_t i = 0; i < rle->size; ++i, ++span) {
auto dst = &surface->buf32[span->y * surface->stride + span->x];
if (span->coverage < 255) src = ALPHA_BLEND(color, span->coverage);
else src = color;
auto ialpha = IA(src);
for (uint32_t x = 0; x < span->len; ++x, ++dst) {
*dst = src + ALPHA_BLEND(*dst, _ialpha(src));
*dst = src + ALPHA_BLEND(*dst, ialpha);
}
}
//8bit grayscale
@ -49,10 +82,11 @@ static bool inline cRasterTranslucentRle(SwSurface* surface, const SwRleData* rl
uint8_t src;
for (uint32_t i = 0; i < rle->size; ++i, ++span) {
auto dst = &surface->buf8[span->y * surface->stride + span->x];
if (span->coverage < 255) src = _multiply<uint8_t>(span->coverage, a);
if (span->coverage < 255) src = MULTIPLY(span->coverage, a);
else src = a;
auto ialpha = ~a;
for (uint32_t x = 0; x < span->len; ++x, ++dst) {
*dst = src + _multiply<uint8_t>(*dst, ~src);
*dst = src + MULTIPLY(*dst, ialpha);
}
}
}
@ -67,9 +101,9 @@ static bool inline cRasterTranslucentRect(SwSurface* surface, const SwBBox& regi
//32bits channels
if (surface->channelSize == sizeof(uint32_t)) {
auto color = surface->blender.join(r, g, b, a);
auto color = surface->join(r, g, b, 255);
auto buffer = surface->buf32 + (region.min.y * surface->stride) + region.min.x;
auto ialpha = _ialpha(color);
auto ialpha = 255 - a;
for (uint32_t y = 0; y < h; ++y) {
auto dst = &buffer[y * surface->stride];
for (uint32_t x = 0; x < w; ++x, ++dst) {
@ -79,10 +113,11 @@ static bool inline cRasterTranslucentRect(SwSurface* surface, const SwBBox& regi
//8bit grayscale
} else if (surface->channelSize == sizeof(uint8_t)) {
auto buffer = surface->buf8 + (region.min.y * surface->stride) + region.min.x;
auto ialpha = ~a;
for (uint32_t y = 0; y < h; ++y) {
auto dst = &buffer[y * surface->stride];
for (uint32_t x = 0; x < w; ++x, ++dst) {
*dst = a + _multiply<uint8_t>(*dst, ~a);
*dst = a + MULTIPLY(*dst, ialpha);
}
}
}
@ -94,13 +129,27 @@ static bool inline cRasterABGRtoARGB(Surface* surface)
{
TVGLOG("SW_ENGINE", "Convert ColorSpace ABGR - ARGB [Size: %d x %d]", surface->w, surface->h);
auto buffer = surface->buf32;
for (uint32_t y = 0; y < surface->h; ++y, buffer += surface->stride) {
auto dst = buffer;
for (uint32_t x = 0; x < surface->w; ++x, ++dst) {
auto c = *dst;
//flip Blue, Red channels
*dst = (c & 0xff000000) + ((c & 0x00ff0000) >> 16) + (c & 0x0000ff00) + ((c & 0x000000ff) << 16);
//64bits faster converting
if (surface->w % 2 == 0) {
auto buffer = reinterpret_cast<uint64_t*>(surface->buf32);
for (uint32_t y = 0; y < surface->h; ++y, buffer += surface->stride / 2) {
auto dst = buffer;
for (uint32_t x = 0; x < surface->w / 2; ++x, ++dst) {
auto c = *dst;
//flip Blue, Red channels
*dst = (c & 0xff000000ff000000) + ((c & 0x00ff000000ff0000) >> 16) + (c & 0x0000ff000000ff00) + ((c & 0x000000ff000000ff) << 16);
}
}
//default converting
} else {
auto buffer = surface->buf32;
for (uint32_t y = 0; y < surface->h; ++y, buffer += surface->stride) {
auto dst = buffer;
for (uint32_t x = 0; x < surface->w; ++x, ++dst) {
auto c = *dst;
//flip Blue, Red channels
*dst = (c & 0xff000000) + ((c & 0x00ff0000) >> 16) + (c & 0x0000ff00) + ((c & 0x000000ff) << 16);
}
}
}
return true;

View file

@ -31,7 +31,7 @@ static inline uint8x8_t ALPHA_BLEND(uint8x8_t c, uint8x8_t a)
}
static void neonRasterRGBA32(uint32_t *dst, uint32_t val, uint32_t offset, int32_t len)
static void neonRasterPixel32(uint32_t *dst, uint32_t val, uint32_t offset, int32_t len)
{
uint32_t iterations = len / 4;
uint32_t neonFilled = iterations * 4;
@ -67,7 +67,7 @@ static bool neonRasterTranslucentRle(SwSurface* surface, const SwRleData* rle, u
else src = color;
auto dst = &surface->buf32[span->y * surface->stride + span->x];
auto ialpha = 255 - _alpha(src);
auto ialpha = IALPHA(src);
if ((((uint32_t) dst) & 0x7) != 0) {
//fill not aligned byte
@ -105,7 +105,7 @@ static bool neonRasterTranslucentRect(SwSurface* surface, const SwBBox& region,
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);
auto ialpha = 255 - _alpha(color);
auto ialpha = 255 - a;
auto vColor = vdup_n_u32(color);
auto vIalpha = vdup_n_u8((uint8_t) ialpha);

View file

@ -41,6 +41,7 @@ static inline void _swap(float& a, float& b, float& tmp)
b = tmp;
}
//Careful! Shared resource, No support threading
static float dudx, dvdx;
static float dxdya, dxdyb, dudya, dvdya;
@ -69,40 +70,744 @@ static bool _arrange(const SwImage* image, const SwBBox* region, int& yStart, in
}
static void _rasterPolygonImageSegment(SwSurface* surface, const SwImage* image, const SwBBox* region, int yStart, int yEnd, uint32_t opacity, uint8_t(*blender)(uint8_t*), AASpans* aaSpans)
static void _rasterMaskedPolygonImageSegmentInt(SwSurface* surface, const SwImage* image, const SwBBox* region, int yStart, int yEnd, AASpans* aaSpans, uint8_t opacity, uint8_t dirFlag)
{
#define TEXMAP_TRANSLUCENT
#define TEXMAP_MASKING
#include "tvgSwRasterTexmapInternal.h"
#undef TEXMAP_MASKING
#undef TEXMAP_TRANSLUCENT
float _dudx = dudx, _dvdx = dvdx;
float _dxdya = dxdya, _dxdyb = dxdyb, _dudya = dudya, _dvdya = dvdya;
float _xa = xa, _xb = xb, _ua = ua, _va = va;
auto sbuf = image->buf32;
int32_t sw = static_cast<int32_t>(image->stride);
int32_t sh = image->h;
int32_t x1, x2, ar, ab, iru, irv, px, ay;
int32_t vv = 0, uu = 0;
int32_t minx = INT32_MAX, maxx = INT32_MIN;
float dx, u, v, iptr;
auto cbuffer = surface->compositor->image.buf32;
SwSpan* span = nullptr; //used only when rle based.
if (!_arrange(image, region, yStart, yEnd)) return;
//Clear out of the Polygon vertical ranges
auto size = surface->compositor->bbox.max.x - surface->compositor->bbox.min.x;
if (dirFlag == 1) { //left top case.
for(int y = surface->compositor->bbox.min.y; y < yStart; ++y) {
rasterPixel32(surface->compositor->image.buf32 + y * surface->compositor->image.stride, 0, surface->compositor->bbox.min.x, size);
}
}
if (dirFlag == 4) { //right bottom case.
for(int y = yEnd; y < surface->compositor->bbox.max.y; ++y) {
rasterPixel32(surface->compositor->image.buf32 + y * surface->compositor->image.stride, 0, surface->compositor->bbox.min.x, size);
}
}
//Loop through all lines in the segment
uint32_t spanIdx = 0;
if (region) {
minx = region->min.x;
maxx = region->max.x;
} else {
span = image->rle->spans;
while (span->y < yStart) {
++span;
++spanIdx;
}
}
for (int32_t y = yStart; y < yEnd; ++y) {
auto cmp = &cbuffer[y * surface->compositor->image.stride];
x1 = (int32_t)_xa;
x2 = (int32_t)_xb;
if (!region) {
minx = INT32_MAX;
maxx = INT32_MIN;
//one single row, could be consisted of multiple spans.
while (span->y == y && spanIdx < image->rle->size) {
if (minx > span->x) minx = span->x;
if (maxx < span->x + span->len) maxx = span->x + span->len;
++span;
++spanIdx;
}
}
if (x1 < minx) x1 = minx;
if (x2 > maxx) x2 = maxx;
//Anti-Aliasing frames
//FIXME: this aa must be applied before masking op
ay = y - aaSpans->yStart;
if (aaSpans->lines[ay].x[0] > x1) aaSpans->lines[ay].x[0] = x1;
if (aaSpans->lines[ay].x[1] < x2) aaSpans->lines[ay].x[1] = x2;
//Range allowed
if ((x2 - x1) >= 1 && (x1 < maxx) && (x2 > minx)) {
for (int32_t x = surface->compositor->bbox.min.x; x < surface->compositor->bbox.max.x; ++x) {
//Range allowed
if (x >= x1 && x < x2) {
//Perform subtexel pre-stepping on UV
dx = 1 - (_xa - x1);
u = _ua + dx * _dudx;
v = _va + dx * _dvdx;
if ((uint32_t)v >= image->h) {
cmp[x] = 0;
} else {
if (opacity == 255) {
uu = (int) u;
if (uu >= sw) continue;
vv = (int) v;
if (vv >= sh) continue;
ar = (int)(255 * (1 - modff(u, &iptr)));
ab = (int)(255 * (1 - modff(v, &iptr)));
iru = uu + 1;
irv = vv + 1;
px = *(sbuf + (vv * sw) + uu);
/* horizontal interpolate */
if (iru < sw) {
/* right pixel */
int px2 = *(sbuf + (vv * sw) + iru);
px = INTERPOLATE(px, px2, ar);
}
/* vertical interpolate */
if (irv < sh) {
/* bottom pixel */
int px2 = *(sbuf + (irv * sw) + uu);
/* horizontal interpolate */
if (iru < sw) {
/* bottom right pixel */
int px3 = *(sbuf + (irv * sw) + iru);
px2 = INTERPOLATE(px2, px3, ar);
}
px = INTERPOLATE(px, px2, ab);
}
cmp[x] = ALPHA_BLEND(cmp[x], A(px));
//Step UV horizontally
u += _dudx;
v += _dvdx;
} else {
uu = (int) u;
if (uu >= sw) continue;
vv = (int) v;
if (vv >= sh) continue;
ar = (int)(255 * (1 - modff(u, &iptr)));
ab = (int)(255 * (1 - modff(v, &iptr)));
iru = uu + 1;
irv = vv + 1;
px = *(sbuf + (vv * sw) + uu);
/* horizontal interpolate */
if (iru < sw) {
/* right pixel */
int px2 = *(sbuf + (vv * sw) + iru);
px = INTERPOLATE(px, px2, ar);
}
/* vertical interpolate */
if (irv < sh) {
/* bottom pixel */
int px2 = *(sbuf + (irv * sw) + uu);
/* horizontal interpolate */
if (iru < sw) {
/* bottom right pixel */
int px3 = *(sbuf + (irv * sw) + iru);
px2 = INTERPOLATE(px2, px3, ar);
}
px = INTERPOLATE(px, px2, ab);
}
cmp[x] = ALPHA_BLEND(cmp[x], MULTIPLY(A(px), opacity));
//Step UV horizontally
u += _dudx;
v += _dvdx;
}
}
} else {
//Clear out of polygon horizontal range
if (x < x1 && (dirFlag == 1 || dirFlag == 2)) cmp[x] = 0;
else if (x >= x2 && (dirFlag == 3 || dirFlag == 4)) cmp[x] = 0;
}
}
}
//Step along both edges
_xa += _dxdya;
_xb += _dxdyb;
_ua += _dudya;
_va += _dvdya;
}
xa = _xa;
xb = _xb;
ua = _ua;
va = _va;
}
static void _rasterPolygonImageSegment(SwSurface* surface, const SwImage* image, const SwBBox* region, int yStart, int yEnd, uint8_t(*blender)(uint8_t*), AASpans* aaSpans)
static void _rasterMaskedPolygonImageSegmentDup(SwSurface* surface, const SwImage* image, const SwBBox* region, SwBlender maskOp, SwBlender amaskOp, int yStart, int yEnd, AASpans* aaSpans, uint8_t opacity)
{
#define TEXMAP_MASKING
#include "tvgSwRasterTexmapInternal.h"
#undef TEXMAP_MASKING
float _dudx = dudx, _dvdx = dvdx;
float _dxdya = dxdya, _dxdyb = dxdyb, _dudya = dudya, _dvdya = dvdya;
float _xa = xa, _xb = xb, _ua = ua, _va = va;
auto sbuf = image->buf32;
int32_t sw = static_cast<int32_t>(image->stride);
int32_t sh = image->h;
int32_t x1, x2, x, y, ar, ab, iru, irv, px, ay;
int32_t vv = 0, uu = 0;
int32_t minx = INT32_MAX, maxx = INT32_MIN;
float dx, u, v, iptr;
SwSpan* span = nullptr; //used only when rle based.
if (!_arrange(image, region, yStart, yEnd)) return;
//Loop through all lines in the segment
uint32_t spanIdx = 0;
if (region) {
minx = region->min.x;
maxx = region->max.x;
} else {
span = image->rle->spans;
while (span->y < yStart) {
++span;
++spanIdx;
}
}
y = yStart;
while (y < yEnd) {
x1 = (int32_t)_xa;
x2 = (int32_t)_xb;
if (!region) {
minx = INT32_MAX;
maxx = INT32_MIN;
//one single row, could be consisted of multiple spans.
while (span->y == y && spanIdx < image->rle->size) {
if (minx > span->x) minx = span->x;
if (maxx < span->x + span->len) maxx = span->x + span->len;
++span;
++spanIdx;
}
}
if (x1 < minx) x1 = minx;
if (x2 > maxx) x2 = maxx;
//Anti-Aliasing frames
ay = y - aaSpans->yStart;
if (aaSpans->lines[ay].x[0] > x1) aaSpans->lines[ay].x[0] = x1;
if (aaSpans->lines[ay].x[1] < x2) aaSpans->lines[ay].x[1] = x2;
//Range allowed
if ((x2 - x1) >= 1 && (x1 < maxx) && (x2 > minx)) {
//Perform subtexel pre-stepping on UV
dx = 1 - (_xa - x1);
u = _ua + dx * _dudx;
v = _va + dx * _dvdx;
x = x1;
auto cmp = &surface->compositor->image.buf32[y * surface->compositor->image.stride + x1];
if (opacity == 255) {
//Draw horizontal line
while (x++ < x2) {
uu = (int) u;
if (uu >= sw) continue;
vv = (int) v;
if (vv >= sh) continue;
ar = (int)(255 * (1 - modff(u, &iptr)));
ab = (int)(255 * (1 - modff(v, &iptr)));
iru = uu + 1;
irv = vv + 1;
px = *(sbuf + (vv * sw) + uu);
/* horizontal interpolate */
if (iru < sw) {
/* right pixel */
int px2 = *(sbuf + (vv * sw) + iru);
px = INTERPOLATE(px, px2, ar);
}
/* vertical interpolate */
if (irv < sh) {
/* bottom pixel */
int px2 = *(sbuf + (irv * sw) + uu);
/* horizontal interpolate */
if (iru < sw) {
/* bottom right pixel */
int px3 = *(sbuf + (irv * sw) + iru);
px2 = INTERPOLATE(px2, px3, ar);
}
px = INTERPOLATE(px, px2, ab);
}
*cmp = maskOp(px, *cmp, IA(px));
++cmp;
//Step UV horizontally
u += _dudx;
v += _dvdx;
//range over?
if ((uint32_t)v >= image->h) break;
}
} else {
//Draw horizontal line
while (x++ < x2) {
uu = (int) u;
if (uu >= sw) continue;
vv = (int) v;
if (vv >= sh) continue;
ar = (int)(255 * (1 - modff(u, &iptr)));
ab = (int)(255 * (1 - modff(v, &iptr)));
iru = uu + 1;
irv = vv + 1;
px = *(sbuf + (vv * sw) + uu);
/* horizontal interpolate */
if (iru < sw) {
/* right pixel */
int px2 = *(sbuf + (vv * sw) + iru);
px = INTERPOLATE(px, px2, ar);
}
/* vertical interpolate */
if (irv < sh) {
/* bottom pixel */
int px2 = *(sbuf + (irv * sw) + uu);
/* horizontal interpolate */
if (iru < sw) {
/* bottom right pixel */
int px3 = *(sbuf + (irv * sw) + iru);
px2 = INTERPOLATE(px2, px3, ar);
}
px = INTERPOLATE(px, px2, ab);
}
*cmp = amaskOp(px, *cmp, opacity);
++cmp;
//Step UV horizontally
u += _dudx;
v += _dvdx;
//range over?
if ((uint32_t)v >= image->h) break;
}
}
}
//Step along both edges
_xa += _dxdya;
_xb += _dxdyb;
_ua += _dudya;
_va += _dvdya;
if (!region && spanIdx >= image->rle->size) break;
++y;
}
xa = _xa;
xb = _xb;
ua = _ua;
va = _va;
}
static void _rasterPolygonImageSegment(SwSurface* surface, const SwImage* image, const SwBBox* region, int yStart, int yEnd, uint32_t opacity, AASpans* aaSpans)
static void _rasterMaskedPolygonImageSegment(SwSurface* surface, const SwImage* image, const SwBBox* region, int yStart, int yEnd, AASpans* aaSpans, uint8_t opacity, uint8_t dirFlag = 0)
{
#define TEXMAP_TRANSLUCENT
#include "tvgSwRasterTexmapInternal.h"
#undef TEXMAP_TRANSLUCENT
if (surface->compositor->method == CompositeMethod::IntersectMask) {
_rasterMaskedPolygonImageSegmentInt(surface, image, region, yStart, yEnd, aaSpans, opacity, dirFlag);
} else if (auto opMask = _getMaskOp(surface->compositor->method)) {
//Other Masking operations: Add, Subtract, Difference ...
_rasterMaskedPolygonImageSegmentDup(surface, image, region, opMask, _getAMaskOp(surface->compositor->method), yStart, yEnd, aaSpans, opacity);
}
}
static void _rasterPolygonImageSegment(SwSurface* surface, const SwImage* image, const SwBBox* region, int yStart, int yEnd, AASpans* aaSpans)
static void _rasterBlendingPolygonImageSegment(SwSurface* surface, const SwImage* image, const SwBBox* region, int yStart, int yEnd, AASpans* aaSpans, uint8_t opacity)
{
#include "tvgSwRasterTexmapInternal.h"
float _dudx = dudx, _dvdx = dvdx;
float _dxdya = dxdya, _dxdyb = dxdyb, _dudya = dudya, _dvdya = dvdya;
float _xa = xa, _xb = xb, _ua = ua, _va = va;
auto sbuf = image->buf32;
auto dbuf = surface->buf32;
int32_t sw = static_cast<int32_t>(image->stride);
int32_t sh = image->h;
int32_t dw = surface->stride;
int32_t x1, x2, x, y, ar, ab, iru, irv, px, ay;
int32_t vv = 0, uu = 0;
int32_t minx = INT32_MAX, maxx = INT32_MIN;
float dx, u, v, iptr;
uint32_t* buf;
SwSpan* span = nullptr; //used only when rle based.
if (!_arrange(image, region, yStart, yEnd)) return;
//Loop through all lines in the segment
uint32_t spanIdx = 0;
if (region) {
minx = region->min.x;
maxx = region->max.x;
} else {
span = image->rle->spans;
while (span->y < yStart) {
++span;
++spanIdx;
}
}
y = yStart;
while (y < yEnd) {
x1 = (int32_t)_xa;
x2 = (int32_t)_xb;
if (!region) {
minx = INT32_MAX;
maxx = INT32_MIN;
//one single row, could be consisted of multiple spans.
while (span->y == y && spanIdx < image->rle->size) {
if (minx > span->x) minx = span->x;
if (maxx < span->x + span->len) maxx = span->x + span->len;
++span;
++spanIdx;
}
}
if (x1 < minx) x1 = minx;
if (x2 > maxx) x2 = maxx;
//Anti-Aliasing frames
ay = y - aaSpans->yStart;
if (aaSpans->lines[ay].x[0] > x1) aaSpans->lines[ay].x[0] = x1;
if (aaSpans->lines[ay].x[1] < x2) aaSpans->lines[ay].x[1] = x2;
//Range allowed
if ((x2 - x1) >= 1 && (x1 < maxx) && (x2 > minx)) {
//Perform subtexel pre-stepping on UV
dx = 1 - (_xa - x1);
u = _ua + dx * _dudx;
v = _va + dx * _dvdx;
buf = dbuf + ((y * dw) + x1);
x = x1;
if (opacity == 255) {
//Draw horizontal line
while (x++ < x2) {
uu = (int) u;
if (uu >= sw) continue;
vv = (int) v;
if (vv >= sh) continue;
ar = (int)(255 * (1 - modff(u, &iptr)));
ab = (int)(255 * (1 - modff(v, &iptr)));
iru = uu + 1;
irv = vv + 1;
px = *(sbuf + (vv * sw) + uu);
/* horizontal interpolate */
if (iru < sw) {
/* right pixel */
int px2 = *(sbuf + (vv * sw) + iru);
px = INTERPOLATE(px, px2, ar);
}
/* vertical interpolate */
if (irv < sh) {
/* bottom pixel */
int px2 = *(sbuf + (irv * sw) + uu);
/* horizontal interpolate */
if (iru < sw) {
/* bottom right pixel */
int px3 = *(sbuf + (irv * sw) + iru);
px2 = INTERPOLATE(px2, px3, ar);
}
px = INTERPOLATE(px, px2, ab);
}
*buf = surface->blender(px, *buf, IA(px));
++buf;
//Step UV horizontally
u += _dudx;
v += _dvdx;
//range over?
if ((uint32_t)v >= image->h) break;
}
} else {
//Draw horizontal line
while (x++ < x2) {
uu = (int) u;
if (uu >= sw) continue;
vv = (int) v;
if (vv >= sh) continue;
ar = (int)(255 * (1 - modff(u, &iptr)));
ab = (int)(255 * (1 - modff(v, &iptr)));
iru = uu + 1;
irv = vv + 1;
px = *(sbuf + (vv * sw) + uu);
/* horizontal interpolate */
if (iru < sw) {
/* right pixel */
int px2 = *(sbuf + (vv * sw) + iru);
px = INTERPOLATE(px, px2, ar);
}
/* vertical interpolate */
if (irv < sh) {
/* bottom pixel */
int px2 = *(sbuf + (irv * sw) + uu);
/* horizontal interpolate */
if (iru < sw) {
/* bottom right pixel */
int px3 = *(sbuf + (irv * sw) + iru);
px2 = INTERPOLATE(px2, px3, ar);
}
px = INTERPOLATE(px, px2, ab);
}
auto src = ALPHA_BLEND(px, opacity);
*buf = surface->blender(src, *buf, IA(src));
++buf;
//Step UV horizontally
u += _dudx;
v += _dvdx;
//range over?
if ((uint32_t)v >= image->h) break;
}
}
}
//Step along both edges
_xa += _dxdya;
_xb += _dxdyb;
_ua += _dudya;
_va += _dvdya;
if (!region && spanIdx >= image->rle->size) break;
++y;
}
xa = _xa;
xb = _xb;
ua = _ua;
va = _va;
}
static void _rasterPolygonImageSegment(SwSurface* surface, const SwImage* image, const SwBBox* region, int yStart, int yEnd, AASpans* aaSpans, uint8_t opacity, bool matting)
{
float _dudx = dudx, _dvdx = dvdx;
float _dxdya = dxdya, _dxdyb = dxdyb, _dudya = dudya, _dvdya = dvdya;
float _xa = xa, _xb = xb, _ua = ua, _va = va;
auto sbuf = image->buf32;
auto dbuf = surface->buf32;
int32_t sw = static_cast<int32_t>(image->stride);
int32_t sh = image->h;
int32_t dw = surface->stride;
int32_t x1, x2, x, y, ar, ab, iru, irv, px, ay;
int32_t vv = 0, uu = 0;
int32_t minx = INT32_MAX, maxx = INT32_MIN;
float dx, u, v, iptr;
uint32_t* buf;
SwSpan* span = nullptr; //used only when rle based.
//for matting(composition)
auto csize = matting ? surface->compositor->image.channelSize: 0;
auto alpha = matting ? surface->alpha(surface->compositor->method) : nullptr;
uint8_t* cmp = nullptr;
if (!_arrange(image, region, yStart, yEnd)) return;
//Loop through all lines in the segment
uint32_t spanIdx = 0;
if (region) {
minx = region->min.x;
maxx = region->max.x;
} else {
span = image->rle->spans;
while (span->y < yStart) {
++span;
++spanIdx;
}
}
y = yStart;
while (y < yEnd) {
x1 = (int32_t)_xa;
x2 = (int32_t)_xb;
if (!region) {
minx = INT32_MAX;
maxx = INT32_MIN;
//one single row, could be consisted of multiple spans.
while (span->y == y && spanIdx < image->rle->size) {
if (minx > span->x) minx = span->x;
if (maxx < span->x + span->len) maxx = span->x + span->len;
++span;
++spanIdx;
}
}
if (x1 < minx) x1 = minx;
if (x2 > maxx) x2 = maxx;
//Anti-Aliasing frames
ay = y - aaSpans->yStart;
if (aaSpans->lines[ay].x[0] > x1) aaSpans->lines[ay].x[0] = x1;
if (aaSpans->lines[ay].x[1] < x2) aaSpans->lines[ay].x[1] = x2;
//Range allowed
if ((x2 - x1) >= 1 && (x1 < maxx) && (x2 > minx)) {
//Perform subtexel pre-stepping on UV
dx = 1 - (_xa - x1);
u = _ua + dx * _dudx;
v = _va + dx * _dvdx;
buf = dbuf + ((y * dw) + x1);
x = x1;
if (matting) cmp = &surface->compositor->image.buf8[(y * surface->compositor->image.stride + x1) * csize];
if (opacity == 255) {
//Draw horizontal line
while (x++ < x2) {
uu = (int) u;
if (uu >= sw) continue;
vv = (int) v;
if (vv >= sh) continue;
ar = (int)(255 * (1 - modff(u, &iptr)));
ab = (int)(255 * (1 - modff(v, &iptr)));
iru = uu + 1;
irv = vv + 1;
px = *(sbuf + (vv * sw) + uu);
/* horizontal interpolate */
if (iru < sw) {
/* right pixel */
int px2 = *(sbuf + (vv * sw) + iru);
px = INTERPOLATE(px, px2, ar);
}
/* vertical interpolate */
if (irv < sh) {
/* bottom pixel */
int px2 = *(sbuf + (irv * sw) + uu);
/* horizontal interpolate */
if (iru < sw) {
/* bottom right pixel */
int px3 = *(sbuf + (irv * sw) + iru);
px2 = INTERPOLATE(px2, px3, ar);
}
px = INTERPOLATE(px, px2, ab);
}
uint32_t src;
if (matting) {
src = ALPHA_BLEND(px, alpha(cmp));
cmp += csize;
} else {
src = px;
}
*buf = src + ALPHA_BLEND(*buf, IA(src));
++buf;
//Step UV horizontally
u += _dudx;
v += _dvdx;
//range over?
if ((uint32_t)v >= image->h) break;
}
} else {
//Draw horizontal line
while (x++ < x2) {
uu = (int) u;
vv = (int) v;
ar = (int)(255 * (1 - modff(u, &iptr)));
ab = (int)(255 * (1 - modff(v, &iptr)));
iru = uu + 1;
irv = vv + 1;
if (vv >= sh) continue;
px = *(sbuf + (vv * sw) + uu);
/* horizontal interpolate */
if (iru < sw) {
/* right pixel */
int px2 = *(sbuf + (vv * sw) + iru);
px = INTERPOLATE(px, px2, ar);
}
/* vertical interpolate */
if (irv < sh) {
/* bottom pixel */
int px2 = *(sbuf + (irv * sw) + uu);
/* horizontal interpolate */
if (iru < sw) {
/* bottom right pixel */
int px3 = *(sbuf + (irv * sw) + iru);
px2 = INTERPOLATE(px2, px3, ar);
}
px = INTERPOLATE(px, px2, ab);
}
uint32_t src;
if (matting) {
src = ALPHA_BLEND(px, MULTIPLY(opacity, alpha(cmp)));
cmp += csize;
} else {
src = ALPHA_BLEND(px, opacity);
}
*buf = src + ALPHA_BLEND(*buf, IA(src));
++buf;
//Step UV horizontally
u += _dudx;
v += _dvdx;
//range over?
if ((uint32_t)v >= image->h) break;
}
}
}
//Step along both edges
_xa += _dxdya;
_xb += _dxdyb;
_ua += _dudya;
_va += _dvdya;
if (!region && spanIdx >= image->rle->size) break;
++y;
}
xa = _xa;
xb = _xb;
ua = _ua;
va = _va;
}
/* This mapping algorithm is based on Mikael Kalms's. */
static void _rasterPolygonImage(SwSurface* surface, const SwImage* image, const SwBBox* region, uint32_t opacity, Polygon& polygon, uint8_t(*blender)(uint8_t*), AASpans* aaSpans)
static void _rasterPolygonImage(SwSurface* surface, const SwImage* image, const SwBBox* region, Polygon& polygon, AASpans* aaSpans, uint8_t opacity)
{
float x[3] = {polygon.vertex[0].pt.x, polygon.vertex[1].pt.x, polygon.vertex[2].pt.x};
float y[3] = {polygon.vertex[0].pt.y, polygon.vertex[1].pt.y, polygon.vertex[2].pt.y};
@ -165,6 +870,8 @@ static void _rasterPolygonImage(SwSurface* surface, const SwImage* image, const
if (mathEqual(y[1], y[2])) side = x[2] > x[1];
auto regionTop = region ? region->min.y : image->rle->spans->y; //Normal Image or Rle Image?
auto compositing = _compositing(surface); //Composition required
auto blending = _blending(surface); //Blending required
//Longer edge is on the left side
if (!side) {
@ -190,14 +897,14 @@ static void _rasterPolygonImage(SwSurface* surface, const SwImage* image, const
dxdyb = dxdy[0];
xb = x[0] + dy * dxdyb + (off_y * dxdyb);
if (blender) {
if (opacity == 255) _rasterPolygonImageSegment(surface, image, region, yi[0], yi[1], blender, aaSpans);
else _rasterPolygonImageSegment(surface, image, region, yi[0], yi[1], opacity, blender, aaSpans);
if (compositing) {
if (_matting(surface)) _rasterPolygonImageSegment(surface, image, region, yi[0], yi[1], aaSpans, opacity, true);
else _rasterMaskedPolygonImageSegment(surface, image, region, yi[0], yi[1], aaSpans, opacity, 1);
} else if (blending) {
_rasterBlendingPolygonImageSegment(surface, image, region, yi[0], yi[1], aaSpans, opacity);
} else {
if (opacity == 255) _rasterPolygonImageSegment(surface, image, region, yi[0], yi[1], aaSpans);
else _rasterPolygonImageSegment(surface, image, region, yi[0], yi[1], opacity, aaSpans);
_rasterPolygonImageSegment(surface, image, region, yi[0], yi[1], aaSpans, opacity, false);
}
upper = true;
}
//Draw lower segment if possibly visible
@ -211,12 +918,13 @@ static void _rasterPolygonImage(SwSurface* surface, const SwImage* image, const
// Set right edge X-slope and perform subpixel pre-stepping
dxdyb = dxdy[2];
xb = x[1] + (1 - (y[1] - yi[1])) * dxdyb + (off_y * dxdyb);
if (blender) {
if (opacity == 255) _rasterPolygonImageSegment(surface, image, region, yi[1], yi[2], blender, aaSpans);
else _rasterPolygonImageSegment(surface, image, region, yi[1], yi[2], opacity, blender, aaSpans);
if (compositing) {
if (_matting(surface)) _rasterPolygonImageSegment(surface, image, region, yi[1], yi[2], aaSpans, opacity, true);
else _rasterMaskedPolygonImageSegment(surface, image, region, yi[1], yi[2], aaSpans, opacity, 2);
} else if (blending) {
_rasterBlendingPolygonImageSegment(surface, image, region, yi[1], yi[2], aaSpans, opacity);
} else {
if (opacity == 255) _rasterPolygonImageSegment(surface, image, region, yi[1], yi[2], aaSpans);
else _rasterPolygonImageSegment(surface, image, region, yi[1], yi[2], opacity, aaSpans);
_rasterPolygonImageSegment(surface, image, region, yi[1], yi[2], aaSpans, opacity, false);
}
}
//Longer edge is on the right side
@ -240,14 +948,14 @@ static void _rasterPolygonImage(SwSurface* surface, const SwImage* image, const
ua = u[0] + dy * dudya + (off_y * dudya);
va = v[0] + dy * dvdya + (off_y * dvdya);
if (blender) {
if (opacity == 255) _rasterPolygonImageSegment(surface, image, region, yi[0], yi[1], blender, aaSpans);
else _rasterPolygonImageSegment(surface, image, region, yi[0], yi[1], opacity, blender, aaSpans);
if (compositing) {
if (_matting(surface)) _rasterPolygonImageSegment(surface, image, region, yi[0], yi[1], aaSpans, opacity, true);
else _rasterMaskedPolygonImageSegment(surface, image, region, yi[0], yi[1], aaSpans, opacity, 3);
} else if (blending) {
_rasterBlendingPolygonImageSegment(surface, image, region, yi[0], yi[1], aaSpans, opacity);
} else {
if (opacity == 255) _rasterPolygonImageSegment(surface, image, region, yi[0], yi[1], aaSpans);
else _rasterPolygonImageSegment(surface, image, region, yi[0], yi[1], opacity, aaSpans);
_rasterPolygonImageSegment(surface, image, region, yi[0], yi[1], aaSpans, opacity, false);
}
upper = true;
}
//Draw lower segment if possibly visible
@ -264,12 +972,13 @@ static void _rasterPolygonImage(SwSurface* surface, const SwImage* image, const
ua = u[1] + dy * dudya + (off_y * dudya);
va = v[1] + dy * dvdya + (off_y * dvdya);
if (blender) {
if (opacity == 255) _rasterPolygonImageSegment(surface, image, region, yi[1], yi[2], blender, aaSpans);
else _rasterPolygonImageSegment(surface, image, region, yi[1], yi[2], opacity, blender, aaSpans);
if (compositing) {
if (_matting(surface)) _rasterPolygonImageSegment(surface, image, region, yi[1], yi[2], aaSpans, opacity, true);
else _rasterMaskedPolygonImageSegment(surface, image, region, yi[1], yi[2], aaSpans, opacity, 4);
} else if (blending) {
_rasterBlendingPolygonImageSegment(surface, image, region, yi[1], yi[2], aaSpans, opacity);
} else {
if (opacity == 255) _rasterPolygonImageSegment(surface, image, region, yi[1], yi[2], aaSpans);
else _rasterPolygonImageSegment(surface, image, region, yi[1], yi[2], opacity, aaSpans);
_rasterPolygonImageSegment(surface, image, region, yi[1], yi[2], aaSpans, opacity, false);
}
}
}
@ -508,7 +1217,7 @@ static bool _apply(SwSurface* surface, AASpans* aaSpans)
pos = 1;
while (pos <= line->length[0]) {
*dst = INTERPOLATE((line->coverage[0] * pos), *dst, pixel);
*dst = INTERPOLATE(*dst, pixel, line->coverage[0] * pos);
++dst;
++pos;
}
@ -520,7 +1229,7 @@ static bool _apply(SwSurface* surface, AASpans* aaSpans)
pos = width;
while ((int32_t)(width - line->length[1]) < pos) {
*dst = INTERPOLATE(255 - (line->coverage[1] * (line->length[1] - (width - pos))), *dst, pixel);
*dst = INTERPOLATE(*dst, pixel, 255 - (line->coverage[1] * (line->length[1] - (width - pos))));
--dst;
--pos;
}
@ -545,7 +1254,7 @@ static bool _apply(SwSurface* surface, AASpans* aaSpans)
| / |
3 -- 2
*/
static bool _rasterTexmapPolygon(SwSurface* surface, const SwImage* image, const Matrix* transform, const SwBBox* region, uint32_t opacity, uint8_t(*blender)(uint8_t*))
static bool _rasterTexmapPolygon(SwSurface* surface, const SwImage* image, const Matrix* transform, const SwBBox* region, uint8_t opacity)
{
//Exceptions: No dedicated drawing area?
if ((!image->rle && !region) || (image->rle && image->rle->size == 0)) return false;
@ -576,14 +1285,14 @@ static bool _rasterTexmapPolygon(SwSurface* surface, const SwImage* image, const
polygon.vertex[1] = vertices[1];
polygon.vertex[2] = vertices[3];
_rasterPolygonImage(surface, image, region, opacity, polygon, blender, aaSpans);
_rasterPolygonImage(surface, image, region, polygon, aaSpans, opacity);
//Draw the second polygon
polygon.vertex[0] = vertices[1];
polygon.vertex[1] = vertices[2];
polygon.vertex[2] = vertices[3];
_rasterPolygonImage(surface, image, region, opacity, polygon, blender, aaSpans);
_rasterPolygonImage(surface, image, region, polygon, aaSpans, opacity);
return _apply(surface, aaSpans);
}
@ -602,7 +1311,7 @@ static bool _rasterTexmapPolygon(SwSurface* surface, const SwImage* image, const
Should provide two Polygons, one for each triangle.
// TODO: region?
*/
static bool _rasterTexmapPolygonMesh(SwSurface* surface, const SwImage* image, const RenderMesh* mesh, const Matrix* transform, const SwBBox* region, uint32_t opacity, uint8_t(*blender)(uint8_t*))
static bool _rasterTexmapPolygonMesh(SwSurface* surface, const SwImage* image, const RenderMesh* mesh, const Matrix* transform, const SwBBox* region, uint8_t opacity)
{
//Exceptions: No dedicated drawing area?
if ((!image->rle && !region) || (image->rle && image->rle->size == 0)) return false;
@ -636,7 +1345,7 @@ static bool _rasterTexmapPolygonMesh(SwSurface* surface, const SwImage* image, c
auto aaSpans = _AASpans(ys, ye, image, region);
if (aaSpans) {
for (uint32_t i = 0; i < mesh->triangleCnt; i++) {
_rasterPolygonImage(surface, image, region, opacity, transformedTris[i], blender, aaSpans);
_rasterPolygonImage(surface, image, region, transformedTris[i], aaSpans, opacity);
}
// Apply to surface (note: frees the AA spans)
_apply(surface, aaSpans);

View file

@ -1,168 +0,0 @@
/*
* Copyright (c) 2021 - 2023 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.
*/
{
float _dudx = dudx, _dvdx = dvdx;
float _dxdya = dxdya, _dxdyb = dxdyb, _dudya = dudya, _dvdya = dvdya;
float _xa = xa, _xb = xb, _ua = ua, _va = va;
auto sbuf = image->buf32;
auto dbuf = surface->buf32;
int32_t sw = static_cast<int32_t>(image->stride);
int32_t sh = image->h;
int32_t dw = surface->stride;
int32_t x1, x2, x, y, ar, ab, iru, irv, px, ay;
int32_t vv = 0, uu = 0;
int32_t minx = INT32_MAX, maxx = INT32_MIN;
float dx, u, v, iptr;
uint32_t* buf;
SwSpan* span = nullptr; //used only when rle based.
#ifdef TEXMAP_MASKING
uint8_t* cmp;
auto csize = surface->compositor->image.channelSize;
#endif
if (!_arrange(image, region, yStart, yEnd)) return;
//Loop through all lines in the segment
uint32_t spanIdx = 0;
if (region) {
minx = region->min.x;
maxx = region->max.x;
} else {
span = image->rle->spans;
while (span->y < yStart) {
++span;
++spanIdx;
}
}
y = yStart;
while (y < yEnd) {
x1 = (int32_t)_xa;
x2 = (int32_t)_xb;
if (!region) {
minx = INT32_MAX;
maxx = INT32_MIN;
//one single row, could be consisted of multiple spans.
while (span->y == y && spanIdx < image->rle->size) {
if (minx > span->x) minx = span->x;
if (maxx < span->x + span->len) maxx = span->x + span->len;
++span;
++spanIdx;
}
}
if (x1 < minx) x1 = minx;
if (x2 > maxx) x2 = maxx;
//Anti-Aliasing frames
ay = y - aaSpans->yStart;
if (aaSpans->lines[ay].x[0] > x1) aaSpans->lines[ay].x[0] = x1;
if (aaSpans->lines[ay].x[1] < x2) aaSpans->lines[ay].x[1] = x2;
//Range exception
if ((x2 - x1) < 1 || (x1 >= maxx) || (x2 <= minx)) goto next;
//Perform subtexel pre-stepping on UV
dx = 1 - (_xa - x1);
u = _ua + dx * _dudx;
v = _va + dx * _dvdx;
buf = dbuf + ((y * dw) + x1);
x = x1;
#ifdef TEXMAP_MASKING
cmp = &surface->compositor->image.buf8[(y * surface->compositor->image.stride + x1) * csize];
#endif
//Draw horizontal line
while (x++ < x2) {
uu = (int) u;
vv = (int) v;
ar = (int)(255 * (1 - modff(u, &iptr)));
ab = (int)(255 * (1 - modff(v, &iptr)));
iru = uu + 1;
irv = vv + 1;
if (vv >= sh) continue;
px = *(sbuf + (vv * sw) + uu);
/* horizontal interpolate */
if (iru < sw) {
/* right pixel */
int px2 = *(sbuf + (vv * sw) + iru);
px = INTERPOLATE(ar, px, px2);
}
/* vertical interpolate */
if (irv < sh) {
/* bottom pixel */
int px2 = *(sbuf + (irv * sw) + uu);
/* horizontal interpolate */
if (iru < sw) {
/* bottom right pixel */
int px3 = *(sbuf + (irv * sw) + iru);
px2 = INTERPOLATE(ar, px2, px3);
}
px = INTERPOLATE(ab, px, px2);
}
#if defined(TEXMAP_MASKING) && defined(TEXMAP_TRANSLUCENT)
auto src = ALPHA_BLEND(px, _multiply<uint32_t>(opacity, blender(cmp)));
#elif defined(TEXMAP_MASKING)
auto src = ALPHA_BLEND(px, blender(cmp));
#elif defined(TEXMAP_TRANSLUCENT)
auto src = ALPHA_BLEND(px, opacity);
#else
auto src = px;
#endif
*buf = src + ALPHA_BLEND(*buf, _ialpha(src));
++buf;
#ifdef TEXMAP_MASKING
cmp += csize;
#endif
//Step UV horizontally
u += _dudx;
v += _dvdx;
//range over?
if ((uint32_t)v >= image->h) break;
}
next:
//Step along both edges
_xa += _dxdya;
_xb += _dxdyb;
_ua += _dudya;
_va += _dvdya;
if (!region && spanIdx >= image->rle->size) break;
++y;
}
xa = _xa;
xb = _xb;
ua = _ua;
va = _va;
}

View file

@ -35,13 +35,13 @@ static uint32_t threadsCnt = 0;
struct SwTask : Task
{
Matrix* transform = nullptr;
SwSurface* surface = nullptr;
SwMpool* mpool = nullptr;
RenderUpdateFlag flags = RenderUpdateFlag::None;
Array<RenderData> clips;
uint32_t opacity;
SwBBox bbox = {{0, 0}, {0, 0}}; //Whole Rendering Region
Matrix* transform = nullptr;
Array<RenderData> clips;
RenderUpdateFlag flags = RenderUpdateFlag::None;
uint8_t opacity;
bool pushed = false; //Pushed into task list?
bool disposed = false; //Disposed task?
@ -106,7 +106,7 @@ struct SwShapeTask : SwTask
if (HALF_STROKE(rshape->strokeWidth()) > 0) {
rshape->strokeColor(nullptr, nullptr, nullptr, &strokeAlpha);
visibleStroke = rshape->strokeFill() || (static_cast<uint32_t>(strokeAlpha * opacity / 255) > 0);
visibleStroke = rshape->strokeFill() || (MULTIPLY(strokeAlpha, opacity) > 0);
}
//This checks also for the case, if the invisible shape turned to visible by alpha.
@ -117,7 +117,7 @@ struct SwShapeTask : SwTask
if (flags & (RenderUpdateFlag::Path | RenderUpdateFlag::Transform) || prepareShape) {
uint8_t alpha = 0;
rshape->fillColor(nullptr, nullptr, nullptr, &alpha);
alpha = static_cast<uint8_t>(static_cast<uint32_t>(alpha) * opacity / 255);
alpha = MULTIPLY(alpha, opacity);
visibleFill = (alpha > 0 || rshape->fill);
if (visibleFill || visibleStroke || clipper) {
shapeReset(&shape);
@ -125,10 +125,6 @@ struct SwShapeTask : SwTask
}
}
//Decide Stroking Composition
if (visibleStroke && visibleFill && opacity < 255) cmpStroking = true;
else cmpStroking = false;
//Fill
if (flags & (RenderUpdateFlag::Gradient | RenderUpdateFlag::Transform | RenderUpdateFlag::Color)) {
if (visibleFill || clipper) {
@ -143,7 +139,7 @@ struct SwShapeTask : SwTask
if (auto fill = rshape->fill) {
auto ctable = (flags & RenderUpdateFlag::Gradient) ? true : false;
if (ctable) shapeResetFill(&shape);
if (!shapeGenFillColors(&shape, fill, transform, surface, cmpStroking ? 255 : opacity, ctable)) goto err;
if (!shapeGenFillColors(&shape, fill, transform, surface, opacity, ctable)) goto err;
} else {
shapeDelFill(&shape);
}
@ -158,7 +154,7 @@ struct SwShapeTask : SwTask
if (auto fill = rshape->strokeFill()) {
auto ctable = (flags & RenderUpdateFlag::GradientStroke) ? true : false;
if (ctable) shapeResetStrokeFill(&shape);
if (!shapeGenStrokeFillColors(&shape, fill, transform, surface, cmpStroking ? 255 : opacity, ctable)) goto err;
if (!shapeGenStrokeFillColors(&shape, fill, transform, surface, opacity, ctable)) goto err;
} else {
shapeDelStrokeFill(&shape);
}
@ -171,7 +167,7 @@ struct SwShapeTask : SwTask
shapeDelOutline(&shape, mpool, tid);
//Clip Path
for (auto clip = clips.data; clip < (clips.data + clips.count); ++clip) {
for (auto clip = clips.data; clip < clips.end(); ++clip) {
auto clipper = static_cast<SwTask*>(*clip);
//Clip shape rle
if (shape.rle && !clipper->clip(shape.rle)) goto err;
@ -232,7 +228,7 @@ struct SwSceneTask : SwTask
rleMerge(sceneRle, clipper1->rle(), clipper2->rle());
//Unify the remained clippers
for (auto rd = scene.data + 2; rd < (scene.data + scene.count); ++rd) {
for (auto rd = scene.data + 2; rd < scene.end(); ++rd) {
auto clipper = static_cast<SwTask*>(*rd);
rleMerge(sceneRle, sceneRle, clipper->rle());
}
@ -294,7 +290,7 @@ struct SwImageTask : SwTask
if (image.rle) {
//Clear current task memorypool here if the clippers would use the same memory pool
imageDelOutline(&image, mpool, tid);
for (auto clip = clips.data; clip < (clips.data + clips.count); ++clip) {
for (auto clip = clips.data; clip < clips.end(); ++clip) {
auto clipper = static_cast<SwTask*>(*clip);
if (!clipper->clip(image.rle)) goto err;
}
@ -326,26 +322,26 @@ static void _termEngine()
}
static void _renderFill(SwShapeTask* task, SwSurface* surface, uint32_t opacity)
static void _renderFill(SwShapeTask* task, SwSurface* surface, uint8_t opacity)
{
uint8_t r, g, b, a;
if (auto fill = task->rshape->fill) {
rasterGradientShape(surface, &task->shape, fill->identifier());
} else {
task->rshape->fillColor(&r, &g, &b, &a);
a = static_cast<uint8_t>((opacity * (uint32_t) a) / 255);
a = MULTIPLY(opacity, a);
if (a > 0) rasterShape(surface, &task->shape, r, g, b, a);
}
}
static void _renderStroke(SwShapeTask* task, SwSurface* surface, uint32_t opacity)
static void _renderStroke(SwShapeTask* task, SwSurface* surface, uint8_t opacity)
{
uint8_t r, g, b, a;
if (auto strokeFill = task->rshape->strokeFill()) {
rasterGradientStroke(surface, &task->shape, strokeFill->identifier());
} else {
if (task->rshape->strokeColor(&r, &g, &b, &a)) {
a = static_cast<uint8_t>((opacity * (uint32_t) a) / 255);
a = MULTIPLY(opacity, a);
if (a > 0) rasterStroke(surface, &task->shape, r, g, b, a);
}
}
@ -359,7 +355,7 @@ SwRenderer::~SwRenderer()
{
clearCompositors();
if (surface) delete(surface);
delete(surface);
if (!sharedMpool) mpoolTerm(mpool);
@ -371,7 +367,7 @@ SwRenderer::~SwRenderer()
bool SwRenderer::clear()
{
for (auto task = tasks.data; task < (tasks.data + tasks.count); ++task) {
for (auto task = tasks.data; task < tasks.end(); ++task) {
if ((*task)->disposed) {
delete(*task);
} else {
@ -444,7 +440,7 @@ bool SwRenderer::preRender()
void SwRenderer::clearCompositors()
{
//Free Composite Caches
for (auto comp = compositors.data; comp < (compositors.data + compositors.count); ++comp) {
for (auto comp = compositors.data; comp < compositors.end(); ++comp) {
free((*comp)->compositor->image.data);
delete((*comp)->compositor);
delete(*comp);
@ -460,8 +456,9 @@ bool SwRenderer::postRender()
rasterUnpremultiply(surface);
}
for (auto task = tasks.data; task < (tasks.data + tasks.count); ++task) {
(*task)->pushed = false;
for (auto task = tasks.data; task < tasks.end(); ++task) {
if ((*task)->disposed) delete(*task);
else (*task)->pushed = false;
}
tasks.clear();
@ -490,41 +487,79 @@ bool SwRenderer::renderShape(RenderData data)
if (task->opacity == 0) return true;
uint32_t opacity;
Compositor* cmp = nullptr;
//Do Stroking Composition
if (task->cmpStroking) {
opacity = 255;
cmp = target(task->bounds(), colorSpace());
beginComposite(cmp, CompositeMethod::None, task->opacity);
//No Stroking Composition
} else {
opacity = task->opacity;
}
//Main raster stage
if (task->rshape->stroke && task->rshape->stroke->strokeFirst) {
_renderStroke(task, surface, opacity);
_renderFill(task, surface, opacity);
_renderStroke(task, surface, task->opacity);
_renderFill(task, surface, task->opacity);
} else {
_renderFill(task, surface, opacity);
_renderStroke(task, surface, opacity);
_renderFill(task, surface, task->opacity);
_renderStroke(task, surface, task->opacity);
}
if (task->cmpStroking) endComposite(cmp);
return true;
}
bool SwRenderer::blend(BlendMethod method)
{
if (surface->blendMethod == method) return true;
surface->blendMethod = method;
switch (method) {
case BlendMethod::Add:
surface->blender = opBlendAdd;
break;
case BlendMethod::Screen:
surface->blender = opBlendScreen;
break;
case BlendMethod::Multiply:
surface->blender = opBlendMultiply;
break;
case BlendMethod::Overlay:
surface->blender = opBlendOverlay;
break;
case BlendMethod::Difference:
surface->blender = opBlendDifference;
break;
case BlendMethod::Exclusion:
surface->blender = opBlendExclusion;
break;
case BlendMethod::SrcOver:
surface->blender = opBlendSrcOver;
break;
case BlendMethod::Darken:
surface->blender = opBlendDarken;
break;
case BlendMethod::Lighten:
surface->blender = opBlendLighten;
break;
case BlendMethod::ColorDodge:
surface->blender = opBlendColorDodge;
break;
case BlendMethod::ColorBurn:
surface->blender = opBlendColorBurn;
break;
case BlendMethod::HardLight:
surface->blender = opBlendHardLight;
break;
case BlendMethod::SoftLight:
surface->blender = opBlendSoftLight;
break;
default:
surface->blender = nullptr;
break;
}
return false;
}
RenderRegion SwRenderer::region(RenderData data)
{
return static_cast<SwTask*>(data)->bounds();
}
bool SwRenderer::beginComposite(Compositor* cmp, CompositeMethod method, uint32_t opacity)
bool SwRenderer::beginComposite(Compositor* cmp, CompositeMethod method, uint8_t opacity)
{
if (!cmp) return false;
auto p = static_cast<SwCompositor*>(cmp);
@ -579,7 +614,7 @@ Compositor* SwRenderer::target(const RenderRegion& region, ColorSpace cs)
auto reqChannelSize = CHANNEL_SIZE(cs);
//Use cached data
for (auto p = compositors.data; p < (compositors.data + compositors.count); ++p) {
for (auto p = compositors.data; p < compositors.end(); ++p) {
if ((*p)->compositor->valid && (*p)->compositor->image.channelSize == reqChannelSize) {
cmp = *p;
break;
@ -674,7 +709,7 @@ bool SwRenderer::dispose(RenderData data)
}
void* SwRenderer::prepareCommon(SwTask* task, const RenderTransform* transform, uint32_t opacity, const Array<RenderData>& clips, RenderUpdateFlag flags)
void* SwRenderer::prepareCommon(SwTask* task, const RenderTransform* transform, const Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags)
{
if (!surface) return task;
if (flags == RenderUpdateFlag::None) return task;
@ -685,7 +720,7 @@ void* SwRenderer::prepareCommon(SwTask* task, const RenderTransform* transform,
//TODO: Failed threading them. It would be better if it's possible.
//See: https://github.com/thorvg/thorvg/issues/1409
//Guarantee composition targets get ready.
for (auto clip = clips.data; clip < (clips.data + clips.count); ++clip) {
for (auto clip = clips.data; clip < clips.end(); ++clip) {
static_cast<SwTask*>(*clip)->done();
}
@ -719,7 +754,7 @@ void* SwRenderer::prepareCommon(SwTask* task, const RenderTransform* transform,
}
RenderData SwRenderer::prepare(Surface* surface, const RenderMesh* mesh, RenderData data, const RenderTransform* transform, uint32_t opacity, Array<RenderData>& clips, RenderUpdateFlag flags)
RenderData SwRenderer::prepare(Surface* surface, const RenderMesh* mesh, RenderData data, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags)
{
//prepare task
auto task = static_cast<SwImageTask*>(data);
@ -728,11 +763,11 @@ RenderData SwRenderer::prepare(Surface* surface, const RenderMesh* mesh, RenderD
task->source = surface;
task->mesh = mesh;
}
return prepareCommon(task, transform, opacity, clips, flags);
return prepareCommon(task, transform, clips, opacity, flags);
}
RenderData SwRenderer::prepare(const Array<RenderData>& scene, RenderData data, const RenderTransform* transform, uint32_t opacity, Array<RenderData>& clips, RenderUpdateFlag flags)
RenderData SwRenderer::prepare(const Array<RenderData>& scene, RenderData data, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags)
{
//prepare task
auto task = static_cast<SwSceneTask*>(data);
@ -742,14 +777,14 @@ RenderData SwRenderer::prepare(const Array<RenderData>& scene, RenderData data,
//TODO: Failed threading them. It would be better if it's possible.
//See: https://github.com/thorvg/thorvg/issues/1409
//Guarantee composition targets get ready.
for (auto task = scene.data; task < (scene.data + scene.count); ++task) {
for (auto task = scene.data; task < scene.end(); ++task) {
static_cast<SwTask*>(*task)->done();
}
return prepareCommon(task, transform, opacity, clips, flags);
return prepareCommon(task, transform, clips, opacity, flags);
}
RenderData SwRenderer::prepare(const RenderShape& rshape, RenderData data, const RenderTransform* transform, uint32_t opacity, Array<RenderData>& clips, RenderUpdateFlag flags, bool clipper)
RenderData SwRenderer::prepare(const RenderShape& rshape, RenderData data, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags, bool clipper)
{
//prepare task
auto task = static_cast<SwShapeTask*>(data);
@ -759,7 +794,7 @@ RenderData SwRenderer::prepare(const RenderShape& rshape, RenderData data, const
}
task->clipper = clipper;
return prepareCommon(task, transform, opacity, clips, flags);
return prepareCommon(task, transform, clips, opacity, flags);
}

View file

@ -36,9 +36,9 @@ namespace tvg
class SwRenderer : public RenderMethod
{
public:
RenderData prepare(const RenderShape& rshape, RenderData data, const RenderTransform* transform, uint32_t opacity, Array<RenderData>& clips, RenderUpdateFlag flags, bool clipper) override;
RenderData prepare(const Array<RenderData>& scene, RenderData data, const RenderTransform* transform, uint32_t opacity, Array<RenderData>& clips, RenderUpdateFlag flags) override;
RenderData prepare(Surface* surface, const RenderMesh* mesh, RenderData data, const RenderTransform* transform, uint32_t opacity, Array<RenderData>& clips, RenderUpdateFlag flags) override;
RenderData prepare(const RenderShape& rshape, RenderData data, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags, bool clipper) override;
RenderData prepare(const Array<RenderData>& scene, RenderData data, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags) override;
RenderData prepare(Surface* surface, const RenderMesh* mesh, RenderData data, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags) override;
bool preRender() override;
bool renderShape(RenderData data) override;
bool renderImage(RenderData data) override;
@ -47,6 +47,7 @@ public:
RenderRegion region(RenderData data) override;
RenderRegion viewport() override;
bool viewport(const RenderRegion& vp) override;
bool blend(BlendMethod method) override;
ColorSpace colorSpace() override;
bool clear() override;
@ -55,7 +56,7 @@ public:
bool mempool(bool shared);
Compositor* target(const RenderRegion& region, ColorSpace cs) override;
bool beginComposite(Compositor* cmp, CompositeMethod method, uint32_t opacity) override;
bool beginComposite(Compositor* cmp, CompositeMethod method, uint8_t opacity) override;
bool endComposite(Compositor* cmp) override;
void clearCompositors();
@ -70,13 +71,12 @@ private:
Array<SwSurface*> compositors; //render targets cache list
SwMpool* mpool; //private memory pool
RenderRegion vport; //viewport
bool sharedMpool = true; //memory-pool behavior policy
SwRenderer();
~SwRenderer();
RenderData prepareCommon(SwTask* task, const RenderTransform* transform, uint32_t opacity, const Array<RenderData>& clips, RenderUpdateFlag flags);
RenderData prepareCommon(SwTask* task, const RenderTransform* transform, const Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags);
};
}

View file

@ -708,22 +708,19 @@ static void _cubicTo(RleWorker& rw, const SwPoint& ctrl1, const SwPoint& ctrl2,
}
static bool _decomposeOutline(RleWorker& rw)
static void _decomposeOutline(RleWorker& rw)
{
auto outline = rw.outline;
auto first = 0; //index of first point in contour
for (uint32_t n = 0; n < outline->cntrsCnt; ++n) {
auto last = outline->cntrs[n];
auto limit = outline->pts + last;
auto start = UPSCALE(outline->pts[first]);
auto pt = outline->pts + first;
auto types = outline->types + first;
for (auto cntr = outline->cntrs.data; cntr < outline->cntrs.end(); ++cntr) {
auto last = *cntr;
auto limit = outline->pts.data + last;
auto start = UPSCALE(outline->pts.data[first]);
auto pt = outline->pts.data + first;
auto types = outline->types.data + first;
/* A contour cannot start with a cubic control point! */
if (types[0] == SW_CURVE_TYPE_CUBIC) goto invalid_outline;
_moveTo(rw, UPSCALE(outline->pts[first]));
_moveTo(rw, UPSCALE(outline->pts.data[first]));
while (pt < limit) {
++pt;
@ -734,9 +731,6 @@ static bool _decomposeOutline(RleWorker& rw)
_lineTo(rw, UPSCALE(*pt));
//types cubic
} else {
if (pt + 1 > limit || types[1] != SW_CURVE_TYPE_CUBIC)
goto invalid_outline;
pt += 2;
types += 2;
@ -752,22 +746,15 @@ static bool _decomposeOutline(RleWorker& rw)
close:
first = last + 1;
}
return true;
invalid_outline:
TVGERR("SW_ENGINE", "Invalid Outline!");
return false;
}
static int _genRle(RleWorker& rw)
{
if (setjmp(rw.jmpBuf) == 0) {
auto ret = _decomposeOutline(rw);
_decomposeOutline(rw);
if (!rw.invalid) _recordCell(rw);
if (ret) return 0; //success
else return 1; //fail
return 0;
}
return -1; //lack of cell memory
}

View file

@ -61,91 +61,39 @@ static void _lineSplitAt(const Line& cur, float at, Line& left, Line& right)
}
static bool _growOutlineContour(SwOutline& outline, uint32_t n)
{
if (outline.reservedCntrsCnt >= outline.cntrsCnt + n) return false;
outline.reservedCntrsCnt = outline.cntrsCnt + n;
outline.cntrs = static_cast<uint32_t*>(realloc(outline.cntrs, outline.reservedCntrsCnt * sizeof(uint32_t)));
return true;
}
static void _reserveOutlineClose(SwOutline& outline)
{
//Dash outlines are always opened.
//Only normal outlines use this information, it sholud be same to their contour counts.
if (outline.closed) free(outline.closed);
outline.closed = static_cast<bool*>(calloc(outline.reservedCntrsCnt, sizeof(bool)));
}
static void _resetOutlineClose(SwOutline& outline)
{
memset(outline.closed, 0x0, outline.reservedCntrsCnt * sizeof(bool));
}
static void _growOutlinePoint(SwOutline& outline, uint32_t n)
{
if (outline.reservedPtsCnt >= outline.ptsCnt + n) return;
outline.reservedPtsCnt = outline.ptsCnt + n;
outline.pts = static_cast<SwPoint*>(realloc(outline.pts, outline.reservedPtsCnt * sizeof(SwPoint)));
outline.types = static_cast<uint8_t*>(realloc(outline.types, outline.reservedPtsCnt * sizeof(uint8_t)));
}
static void _outlineEnd(SwOutline& outline)
{
if (outline.ptsCnt == 0) return;
_growOutlineContour(outline, 1);
outline.cntrs[outline.cntrsCnt] = outline.ptsCnt - 1;
++outline.cntrsCnt;
if (outline.pts.empty()) return;
outline.cntrs.push(outline.pts.count - 1);
}
static void _outlineMoveTo(SwOutline& outline, const Point* to, const Matrix* transform)
{
_growOutlinePoint(outline, 1);
if (outline.pts.count > 0) outline.cntrs.push(outline.pts.count - 1);
outline.pts[outline.ptsCnt] = mathTransform(to, transform);
outline.types[outline.ptsCnt] = SW_CURVE_TYPE_POINT;
if (outline.ptsCnt > 0) {
_growOutlineContour(outline, 1);
outline.cntrs[outline.cntrsCnt] = outline.ptsCnt - 1;
++outline.cntrsCnt;
}
++outline.ptsCnt;
outline.pts.push(mathTransform(to, transform));
outline.types.push(SW_CURVE_TYPE_POINT);
}
static void _outlineLineTo(SwOutline& outline, const Point* to, const Matrix* transform)
{
_growOutlinePoint(outline, 1);
outline.pts[outline.ptsCnt] = mathTransform(to, transform);
outline.types[outline.ptsCnt] = SW_CURVE_TYPE_POINT;
++outline.ptsCnt;
outline.pts.push(mathTransform(to, transform));
outline.types.push(SW_CURVE_TYPE_POINT);
}
static void _outlineCubicTo(SwOutline& outline, const Point* ctrl1, const Point* ctrl2, const Point* to, const Matrix* transform)
{
_growOutlinePoint(outline, 3);
outline.pts.push(mathTransform(ctrl1, transform));
outline.types.push(SW_CURVE_TYPE_CUBIC);
outline.pts[outline.ptsCnt] = mathTransform(ctrl1, transform);
outline.types[outline.ptsCnt] = SW_CURVE_TYPE_CUBIC;
++outline.ptsCnt;
outline.pts.push(mathTransform(ctrl2, transform));
outline.types.push(SW_CURVE_TYPE_CUBIC);
outline.pts[outline.ptsCnt] = mathTransform(ctrl2, transform);
outline.types[outline.ptsCnt] = SW_CURVE_TYPE_CUBIC;
++outline.ptsCnt;
outline.pts[outline.ptsCnt] = mathTransform(to, transform);
outline.types[outline.ptsCnt] = SW_CURVE_TYPE_POINT;
++outline.ptsCnt;
outline.pts.push(mathTransform(to, transform));
outline.types.push(SW_CURVE_TYPE_POINT);
}
@ -153,30 +101,21 @@ static void _outlineClose(SwOutline& outline)
{
uint32_t i = 0;
if (outline.cntrsCnt > 0) {
i = outline.cntrs[outline.cntrsCnt - 1] + 1;
} else {
i = 0; //First Path
}
if (outline.cntrs.count > 0) i = outline.cntrs.last() + 1;
else i = 0; //First Path
//Make sure there is at least one point in the current path
if (outline.ptsCnt == i) return;
if (outline.pts.count == i) return;
//Close the path
_growOutlinePoint(outline, 1);
outline.pts[outline.ptsCnt] = outline.pts[i];
outline.types[outline.ptsCnt] = SW_CURVE_TYPE_POINT;
++outline.ptsCnt;
outline.closed[outline.cntrsCnt] = true;
outline.pts.push(outline.pts.data[i]);
outline.types.push(SW_CURVE_TYPE_POINT);
outline.closed.push(true);
}
static void _dashLineTo(SwDashStroke& dash, const Point* to, const Matrix* transform)
{
_growOutlinePoint(*dash.outline, dash.outline->ptsCnt >> 1);
_growOutlineContour(*dash.outline, dash.outline->cntrsCnt >> 1);
Line cur = {dash.ptCur, *to};
auto len = _lineLength(cur.pt1, cur.pt2);
@ -220,9 +159,6 @@ static void _dashLineTo(SwDashStroke& dash, const Point* to, const Matrix* trans
static void _dashCubicTo(SwDashStroke& dash, const Point* ctrl1, const Point* ctrl2, const Point* to, const Matrix* transform)
{
_growOutlinePoint(*dash.outline, dash.outline->ptsCnt >> 1);
_growOutlineContour(*dash.outline, dash.outline->cntrsCnt >> 1);
Bezier cur = {dash.ptCur, *ctrl1, *ctrl2, *to};
auto len = bezLength(cur);
@ -269,11 +205,11 @@ static void _dashCubicTo(SwDashStroke& dash, const Point* ctrl1, const Point* ct
static SwOutline* _genDashOutline(const RenderShape* rshape, const Matrix* transform)
{
const PathCommand* cmds = rshape->path.cmds;
auto cmdCnt = rshape->path.cmdCnt;
const PathCommand* cmds = rshape->path.cmds.data;
auto cmdCnt = rshape->path.cmds.count;
const Point* pts = rshape->path.pts;
auto ptsCnt = rshape->path.ptsCnt;
const Point* pts = rshape->path.pts.data;
auto ptsCnt = rshape->path.pts.count;
//No actual shape data
if (cmdCnt == 0 || ptsCnt == 0) return nullptr;
@ -323,8 +259,9 @@ static SwOutline* _genDashOutline(const RenderShape* rshape, const Matrix* trans
++outlineCntrsCnt; //for end
//No idea exact count.... Reserve Approximitely 20x...
_growOutlinePoint(*dash.outline, outlinePtsCnt * 20);
_growOutlineContour(*dash.outline, outlineCntrsCnt * 20);
dash.outline->pts.grow(20 * outlinePtsCnt);
dash.outline->types.grow(20 * outlinePtsCnt);
dash.outline->cntrs.grow(20 * outlineCntrsCnt);
while (cmdCnt-- > 0) {
switch (*cmds) {
@ -364,12 +301,12 @@ static SwOutline* _genDashOutline(const RenderShape* rshape, const Matrix* trans
static bool _axisAlignedRect(const SwOutline* outline)
{
//Fast Track: axis-aligned rectangle?
if (outline->ptsCnt != 5) return false;
if (outline->pts.count != 5) return false;
auto pt1 = outline->pts + 0;
auto pt2 = outline->pts + 1;
auto pt3 = outline->pts + 2;
auto pt4 = outline->pts + 3;
auto pt1 = outline->pts.data + 0;
auto pt2 = outline->pts.data + 1;
auto pt3 = outline->pts.data + 2;
auto pt4 = outline->pts.data + 3;
auto a = SwPoint{pt1->x, pt3->y};
auto b = SwPoint{pt3->x, pt1->y};
@ -380,14 +317,13 @@ static bool _axisAlignedRect(const SwOutline* outline)
}
static bool _genOutline(SwShape* shape, const RenderShape* rshape, const Matrix* transform, SwMpool* mpool, unsigned tid, bool hasComposite)
{
const PathCommand* cmds = rshape->path.cmds;
auto cmdCnt = rshape->path.cmdCnt;
const PathCommand* cmds = rshape->path.cmds.data;
auto cmdCnt = rshape->path.cmds.count;
const Point* pts = rshape->path.pts;
auto ptsCnt = rshape->path.ptsCnt;
const Point* pts = rshape->path.pts.data;
auto ptsCnt = rshape->path.pts.count;
//No actual shape data
if (cmdCnt == 0 || ptsCnt == 0) return false;
@ -431,13 +367,15 @@ static bool _genOutline(SwShape* shape, const RenderShape* rshape, const Matrix*
shape->outline = mpoolReqOutline(mpool, tid);
auto outline = shape->outline;
_growOutlinePoint(*outline, outlinePtsCnt);
outline->pts.grow(outlinePtsCnt);
outline->types.grow(outlinePtsCnt);
outline->cntrs.grow(outlineCntrsCnt);
if (_growOutlineContour(*outline, outlineCntrsCnt)) {
_reserveOutlineClose(*outline);
} else {
_resetOutlineClose(*outline);
}
//Dash outlines are always opened.
//Only normal outlines use this information, it sholud be same to their contour counts.
outline->closed.reserve(outline->cntrs.reserved);
memset(outline->closed.data, 0x0, sizeof(bool) * outline->closed.reserved);
//Generate Outlines
while (cmdCnt-- > 0) {
@ -605,10 +543,10 @@ bool shapeGenStrokeRle(SwShape* shape, const RenderShape* rshape, const Matrix*
fail:
if (freeOutline) {
if (shapeOutline->cntrs) free(shapeOutline->cntrs);
if (shapeOutline->pts) free(shapeOutline->pts);
if (shapeOutline->types) free(shapeOutline->types);
if (shapeOutline->closed) free(shapeOutline->closed);
free(shapeOutline->cntrs.data);
free(shapeOutline->pts.data);
free(shapeOutline->types.data);
free(shapeOutline->closed.data);
free(shapeOutline);
}
mpoolRetStrokeOutline(mpool, tid);
@ -617,13 +555,13 @@ fail:
}
bool shapeGenFillColors(SwShape* shape, const Fill* fill, const Matrix* transform, SwSurface* surface, uint32_t opacity, bool ctable)
bool shapeGenFillColors(SwShape* shape, const Fill* fill, const Matrix* transform, SwSurface* surface, uint8_t opacity, bool ctable)
{
return fillGenColorTable(shape->fill, fill, transform, surface, opacity, ctable);
}
bool shapeGenStrokeFillColors(SwShape* shape, const Fill* fill, const Matrix* transform, SwSurface* surface, uint32_t opacity, bool ctable)
bool shapeGenStrokeFillColors(SwShape* shape, const Fill* fill, const Matrix* transform, SwSurface* surface, uint8_t opacity, bool ctable)
{
return fillGenColorTable(shape->stroke->fill, fill, transform, surface, opacity, ctable);
}

View file

@ -129,7 +129,6 @@ static void _borderCubicTo(SwStrokeBorder* border, const SwPoint& ctrl1, const S
tag[2] = SW_STROKE_TAG_POINT;
border->ptsCnt += 3;
border->movable = false;
}
@ -193,7 +192,6 @@ static void _borderLineTo(SwStrokeBorder* border, const SwPoint& to, bool movabl
//move last point
border->pts[border->ptsCnt - 1] = to;
} else {
//don't add zero-length line_to
if (border->ptsCnt > 0 && (border->pts[border->ptsCnt - 1] - to).small()) return;
@ -233,8 +231,6 @@ static void _arcTo(SwStroke& stroke, int32_t side)
static void _outside(SwStroke& stroke, int32_t side, SwFixed lineLength)
{
constexpr SwFixed MITER_LIMIT = 4 * (1 << 16);
auto border = stroke.borders + side;
if (stroke.join == StrokeJoin::Round) {
@ -257,7 +253,7 @@ static void _outside(SwStroke& stroke, int32_t side, SwFixed lineLength)
}
thcos = mathCos(theta);
auto sigma = mathMultiply(MITER_LIMIT, thcos);
auto sigma = mathMultiply(stroke.miterlimit, thcos);
//is miter limit exceeded?
if (sigma < 0x10000L) bevel = true;
@ -432,8 +428,7 @@ static void _lineTo(SwStroke& stroke, const SwPoint& to)
static void _cubicTo(SwStroke& stroke, const SwPoint& ctrl1, const SwPoint& ctrl2, const SwPoint& to)
{
/* if all control points are coincident, this is a no-op;
avoid creating a spurious corner */
//if all control points are coincident, this is a no-op; avoid creating a spurious corner
if ((stroke.center - ctrl1).small() && (ctrl1 - ctrl2).small() && (ctrl2 - to).small()) {
stroke.center = to;
return;
@ -499,8 +494,7 @@ static void _cubicTo(SwStroke& stroke, const SwPoint& ctrl1, const SwPoint& ctrl
auto border = stroke.borders;
int32_t side = 0;
while (side <= 1)
{
while (side < 2) {
auto rotate = SIDE_TO_ROTATE(side);
//compute control points
@ -521,7 +515,6 @@ static void _cubicTo(SwStroke& stroke, const SwPoint& ctrl1, const SwPoint& ctrl
_end += arc[0];
if (stroke.handleWideStrokes) {
/* determine whether the border radius is greater than the radius of
curvature of the original arc */
auto _start = border->pts[border->ptsCnt - 1];
@ -556,8 +549,6 @@ static void _cubicTo(SwStroke& stroke, const SwPoint& ctrl1, const SwPoint& ctrl
++border;
continue;
}
//else fall through
}
_borderCubicTo(border, _ctrl1, _ctrl2, _end);
++side;
@ -653,7 +644,6 @@ static void _addReverseLeft(SwStroke& stroke, bool opened)
if (ttag == SW_STROKE_TAG_BEGIN || ttag == SW_STROKE_TAG_END)
dstTag[0] ^= (SW_STROKE_TAG_BEGIN | SW_STROKE_TAG_END);
}
--srcPt;
--srcTag;
++dstPt;
@ -779,36 +769,27 @@ fail:
static void _exportBorderOutline(const SwStroke& stroke, SwOutline* outline, uint32_t side)
{
auto border = stroke.borders + side;
if (border->ptsCnt == 0) return;
if (border->ptsCnt == 0) return; //invalid border
memcpy(outline->pts + outline->ptsCnt, border->pts, border->ptsCnt * sizeof(SwPoint));
memcpy(outline->pts.data + outline->pts.count, border->pts, border->ptsCnt * sizeof(SwPoint));
auto cnt = border->ptsCnt;
auto src = border->tags;
auto tags = outline->types + outline->ptsCnt;
auto cntrs = outline->cntrs + outline->cntrsCnt;
auto idx = outline->ptsCnt;
auto tags = outline->types.data + outline->types.count;
auto idx = outline->pts.count;
while (cnt > 0) {
if (*src & SW_STROKE_TAG_POINT) *tags = SW_CURVE_TYPE_POINT;
else if (*src & SW_STROKE_TAG_CUBIC) *tags = SW_CURVE_TYPE_CUBIC;
else {
//LOG: What type of stroke outline??
}
if (*src & SW_STROKE_TAG_END) {
*cntrs = idx;
++cntrs;
++outline->cntrsCnt;
}
else TVGERR("SW_ENGINE", "Invalid stroke tag was given! = %d", *src);
if (*src & SW_STROKE_TAG_END) outline->cntrs.push(idx);
++src;
++tags;
++idx;
--cnt;
}
outline->ptsCnt = outline->ptsCnt + border->ptsCnt;
outline->pts.count += border->ptsCnt;
outline->types.count += border->ptsCnt;
}
@ -844,6 +825,7 @@ void strokeReset(SwStroke* stroke, const RenderShape* rshape, const Matrix* tran
stroke->width = HALF_STROKE(rshape->strokeWidth());
stroke->cap = rshape->strokeCap();
stroke->miterlimit = static_cast<SwFixed>(rshape->strokeMiterlimit()) << 16;
//Save line join: it can be temporarily changed when stroking curves...
stroke->joinSaved = stroke->join = rshape->strokeJoin();
@ -858,10 +840,11 @@ void strokeReset(SwStroke* stroke, const RenderShape* rshape, const Matrix* tran
bool strokeParseOutline(SwStroke* stroke, const SwOutline& outline)
{
uint32_t first = 0;
uint32_t i = 0;
for (uint32_t i = 0; i < outline.cntrsCnt; ++i) {
auto last = outline.cntrs[i]; //index of last point in contour
auto limit = outline.pts + last;
for (auto cntr = outline.cntrs.data; cntr < outline.cntrs.end(); ++cntr, ++i) {
auto last = *cntr; //index of last point in contour
auto limit = outline.pts.data + last;
//Skip empty points
if (last <= first) {
@ -869,15 +852,15 @@ bool strokeParseOutline(SwStroke* stroke, const SwOutline& outline)
continue;
}
auto start = outline.pts[first];
auto pt = outline.pts + first;
auto types = outline.types + first;
auto start = outline.pts.data[first];
auto pt = outline.pts.data + first;
auto types = outline.types.data + first;
auto type = types[0];
//A contour cannot start with a cubic control point
if (type == SW_CURVE_TYPE_CUBIC) return false;
auto closed = outline.closed ? outline.closed[i]: false;
auto closed = outline.closed.data ? outline.closed.data[i]: false;
_beginSubPath(*stroke, start, closed);
@ -903,7 +886,6 @@ bool strokeParseOutline(SwStroke* stroke, const SwOutline& outline)
goto close;
}
}
close:
if (!stroke->firstPt) _endSubPath(*stroke);
first = last + 1;
@ -923,15 +905,9 @@ SwOutline* strokeExportOutline(SwStroke* stroke, SwMpool* mpool, unsigned tid)
auto cntrsCnt = count2 + count4;
auto outline = mpoolReqStrokeOutline(mpool, tid);
if (outline->reservedPtsCnt < ptsCnt) {
outline->pts = static_cast<SwPoint*>(realloc(outline->pts, sizeof(SwPoint) * ptsCnt));
outline->types = static_cast<uint8_t*>(realloc(outline->types, sizeof(uint8_t) * ptsCnt));
outline->reservedPtsCnt = ptsCnt;
}
if (outline->reservedCntrsCnt < cntrsCnt) {
outline->cntrs = static_cast<uint32_t*>(realloc(outline->cntrs, sizeof(uint32_t) * cntrsCnt));
outline->reservedCntrsCnt = cntrsCnt;
}
outline->pts.reserve(ptsCnt);
outline->types.reserve(ptsCnt);
outline->cntrs.reserve(cntrsCnt);
_exportBorderOutline(*stroke, outline, 0); //left
_exportBorderOutline(*stroke, outline, 1); //right

View file

@ -0,0 +1,108 @@
/*
* Copyright (c) 2023 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 "tvgAnimationImpl.h"
#include "tvgCommon.h"
#include "tvgFrameModule.h"
#include "tvgPictureImpl.h"
/************************************************************************/
/* Internal Class Implementation */
/************************************************************************/
struct Animation::Impl
{
//TODO: Memory Safety
Picture picture;
};
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
Animation::~Animation()
{
}
Animation::Animation() : pImpl(new Impl)
{
pImpl->picture.pImpl->animated = true;
}
Result Animation::frame(uint32_t no) noexcept
{
auto loader = pImpl->picture.pImpl->loader.get();
if (!loader) return Result::InsufficientCondition;
if (!loader->animatable()) return Result::NonSupport;
if (static_cast<FrameModule*>(loader)->frame(no)) return Result::Success;
return Result::InsufficientCondition;
}
Picture* Animation::picture() const noexcept
{
return &pImpl->picture;
}
uint32_t Animation::curFrame() const noexcept
{
auto loader = pImpl->picture.pImpl->loader.get();
if (!loader) return 0;
if (!loader->animatable()) return 0;
return static_cast<FrameModule*>(loader)->curFrame();
}
uint32_t Animation::totalFrame() const noexcept
{
auto loader = pImpl->picture.pImpl->loader.get();
if (!loader) return 0;
if (!loader->animatable()) return 0;
return static_cast<FrameModule*>(loader)->totalFrame();
}
float Animation::duration() const noexcept
{
auto loader = pImpl->picture.pImpl->loader.get();
if (!loader) return 0;
if (!loader->animatable()) return 0;
return static_cast<FrameModule*>(loader)->duration();
}
unique_ptr<Animation> Animation::gen() noexcept
{
return unique_ptr<Animation>(new Animation);
}

View file

@ -35,30 +35,35 @@ struct Array
uint32_t count = 0;
uint32_t reserved = 0;
Array(){}
Array(const Array& rhs)
{
reset();
*this = rhs;
}
void push(T element)
{
if (count + 1 > reserved) {
reserved = (count + 1) * 2;
auto p = data;
reserved = count + (count + 2) / 2;
data = static_cast<T*>(realloc(data, sizeof(T) * reserved));
if (!data) {
data = p;
return;
}
}
data[count++] = element;
}
void push(Array<T>& rhs)
{
grow(rhs.count);
memcpy(data + count, rhs.data, rhs.count * sizeof(T));
count += rhs.count;
}
bool reserve(uint32_t size)
{
if (size > reserved) {
reserved = size;
auto p = data;
data = static_cast<T*>(realloc(data, sizeof(T) * reserved));
if (!data) {
data = p;
return false;
}
}
return true;
}
@ -68,11 +73,21 @@ struct Array
return reserve(count + size);
}
T* ptr()
T* end() const
{
return data + count;
}
T& last()
{
return data[count - 1];
}
T& first()
{
return data[0];
}
void pop()
{
if (count > 0) --count;
@ -80,10 +95,8 @@ struct Array
void reset()
{
if (data) {
free(data);
data = nullptr;
}
free(data);
data = nullptr;
count = reserved = 0;
}
@ -92,16 +105,21 @@ struct Array
count = 0;
}
bool empty() const
{
return count == 0;
}
void operator=(const Array& rhs)
{
reserve(rhs.count);
if (rhs.count > 0) memcpy(data, rhs.data, sizeof(T) * reserved);
if (rhs.count > 0) memcpy(data, rhs.data, sizeof(T) * rhs.count);
count = rhs.count;
}
~Array()
{
if (data) free(data);
free(data);
}
};

View file

@ -20,10 +20,11 @@
* SOFTWARE.
*/
#include <float.h>
#include <math.h>
#include "tvgMath.h"
#include "tvgBezier.h"
#define BEZIER_EPSILON 1e-4f
/************************************************************************/
/* Internal Class Implementation */
/************************************************************************/
@ -107,29 +108,25 @@ void bezSplitLeft(Bezier& cur, float at, Bezier& left)
}
float bezAt(const Bezier& bz, float at)
float bezAt(const Bezier& bz, float at, float length)
{
auto len = bezLength(bz);
auto biggest = 1.0f;
auto smallest = 0.0f;
auto t = 0.5f;
//just in case to prevent an infinite loop
if (at <= 0) return 0.0f;
if (at >= len) return 1.0f;
if (at >= length) return length;
while (true) {
auto right = bz;
Bezier left;
bezSplitLeft(right, t, left);
len = bezLength(left);
if (fabsf(len - at) < BEZIER_EPSILON || fabsf(smallest - biggest) < BEZIER_EPSILON) {
length = bezLength(left);
if (fabsf(length - at) < BEZIER_EPSILON || fabsf(smallest - biggest) < BEZIER_EPSILON) {
break;
}
if (len < at) {
if (length < at) {
smallest = t;
t = (t + biggest) * 0.5f;
} else {
@ -144,8 +141,53 @@ float bezAt(const Bezier& bz, float at)
void bezSplitAt(const Bezier& cur, float at, Bezier& left, Bezier& right)
{
right = cur;
auto t = bezAt(right, at);
auto t = bezAt(right, at, bezLength(right));
bezSplitLeft(right, t, left);
}
Point bezPointAt(const Bezier& bz, float t)
{
Point cur;
auto it = 1.0f - t;
auto ax = bz.start.x * it + bz.ctrl1.x * t;
auto bx = bz.ctrl1.x * it + bz.ctrl2.x * t;
auto cx = bz.ctrl2.x * it + bz.end.x * t;
ax = ax * it + bx * t;
bx = bx * it + cx * t;
cur.x = ax * it + bx * t;
float ay = bz.start.y * it + bz.ctrl1.y * t;
float by = bz.ctrl1.y * it + bz.ctrl2.y * t;
float cy = bz.ctrl2.y * it + bz.end.y * t;
ay = ay * it + by * t;
by = by * it + cy * t;
cur.y = ay * it + by * t;
return cur;
}
float bezAngleAt(const Bezier& bz, float t)
{
if (t < 0 || t > 1) return 0;
//derivate
// p'(t) = 3 * (-(1-2t+t^2) * p0 + (1 - 4 * t + 3 * t^2) * p1 + (2 * t - 3 *
// t^2) * p2 + t^2 * p3)
float mt = 1.0f - t;
float d = t * t;
float a = -mt * mt;
float b = 1 - 4 * t + 3 * d;
float c = 2 * t - 3 * d;
Point pt ={a * bz.start.x + b * bz.ctrl1.x + c * bz.ctrl2.x + d * bz.end.x, a * bz.start.y + b * bz.ctrl1.y + c * bz.ctrl2.y + d * bz.end.y};
pt.x *= 3;
pt.y *= 3;
return atan2(pt.x, pt.y) * 180.0f / 3.141592f;
}
}

View file

@ -28,8 +28,6 @@
namespace tvg
{
#define BEZIER_EPSILON 1e-4f
struct Bezier
{
Point start;
@ -41,8 +39,10 @@ struct Bezier
void bezSplit(const Bezier&cur, Bezier& left, Bezier& right);
float bezLength(const Bezier& cur);
void bezSplitLeft(Bezier& cur, float at, Bezier& left);
float bezAt(const Bezier& bz, float at);
float bezAt(const Bezier& bz, float at, float length);
void bezSplitAt(const Bezier& cur, float at, Bezier& left, Bezier& right);
Point bezPointAt(const Bezier& bz, float t);
float bezAngleAt(const Bezier& bz, float t);
}

View file

@ -26,9 +26,6 @@
/* TODO: Need to consider whether uin8_t is enough size for extension...
Rather than optimal data, we can use enough size and data compress? */
/* Data types, do not change data types once Tvg Format is officially released,
That would occur the abi break. */
using TvgBinByte = uint8_t;
using TvgBinCounter = uint32_t;
using TvgBinTag = TvgBinByte;
@ -39,7 +36,7 @@ using TvgBinFlag = TvgBinByte;
#define TVG_HEADER_SIZE 33 //TVG_HEADER_SIGNATURE_LENGTH + TVG_HEADER_VERSION_LENGTH + 2*SIZE(float) + TVG_HEADER_RESERVED_LENGTH + TVG_HEADER_COMPRESS_SIZE
#define TVG_HEADER_SIGNATURE "ThorVG"
#define TVG_HEADER_SIGNATURE_LENGTH 6
#define TVG_HEADER_VERSION "000400" //Major 00, Minor 04, Micro 00
#define TVG_HEADER_VERSION "001000" //Major 00, Minor 10, Micro 00
#define TVG_HEADER_VERSION_LENGTH 6
#define TVG_HEADER_RESERVED_LENGTH 1 //Storing flags for extensions
#define TVG_HEADER_COMPRESS_SIZE 12 //TVG_HEADER_UNCOMPRESSED_SIZE + TVG_HEADER_COMPRESSED_SIZE + TVG_HEADER_COMPRESSED_SIZE_BITS
@ -63,8 +60,9 @@ using TvgBinFlag = TvgBinByte;
#define TVG_TAG_PAINT_CMP_METHOD (TvgBinTag)0x20
//TODO: Keep this for the compatibility, Remove in TVG 1.0 release
//Scene
#define TVG_TAG_SCENE_RESERVEDCNT (TvgBinTag)0x30
#define TVG_TAG_SCENE_RESERVEDCNT (TvgBinTag)0x30
//Shape
@ -73,14 +71,17 @@ using TvgBinFlag = TvgBinByte;
#define TVG_TAG_SHAPE_FILL (TvgBinTag)0x42
#define TVG_TAG_SHAPE_COLOR (TvgBinTag)0x43
#define TVG_TAG_SHAPE_FILLRULE (TvgBinTag)0x44
#define TVG_TAG_SHAPE_STROKE_CAP (TvgBinTag)0x50
#define TVG_TAG_SHAPE_STROKE_JOIN (TvgBinTag)0x51
//Stroke
#define TVG_TAG_SHAPE_STROKE_CAP (TvgBinTag)0x50
#define TVG_TAG_SHAPE_STROKE_JOIN (TvgBinTag)0x51
#define TVG_TAG_SHAPE_STROKE_WIDTH (TvgBinTag)0x52
#define TVG_TAG_SHAPE_STROKE_COLOR (TvgBinTag)0x53
#define TVG_TAG_SHAPE_STROKE_FILL (TvgBinTag)0x54
#define TVG_TAG_SHAPE_STROKE_DASHPTRN (TvgBinTag)0x55
#define TVG_TAG_SHAPE_STROKE_MITERLIMIT (TvgBinTag)0x56
#define TVG_TAG_SHAPE_STROKE_ORDER (TvgBinTag)0x57
//Fill

View file

@ -37,16 +37,21 @@ Canvas::~Canvas()
}
Result Canvas::reserve(uint32_t n) noexcept
Result Canvas::reserve(TVG_UNUSED uint32_t n) noexcept
{
if (!pImpl->paints.reserve(n)) return Result::FailedAllocation;
return Result::Success;
return Result::NonSupport;
}
list<Paint*>& Canvas::paints() noexcept
{
return pImpl->paints;
}
Result Canvas::push(unique_ptr<Paint> paint) noexcept
{
return pImpl->push(move(paint));
return pImpl->push(std::move(paint));
}

View file

@ -31,7 +31,7 @@
struct Canvas::Impl
{
Array<Paint*> paints;
list<Paint*> paints;
RenderMethod* renderer;
bool refresh = false; //if all paints should be updated by force.
bool drawing = false; //on drawing condition?
@ -53,7 +53,7 @@ struct Canvas::Impl
auto p = paint.release();
if (!p) return Result::MemoryCorruption;
paints.push(p);
paints.push_back(p);
return update(p, true);
}
@ -64,9 +64,9 @@ struct Canvas::Impl
if (!renderer || !renderer->clear()) return Result::InsufficientCondition;
//Free paints
for (auto paint = paints.data; paint < (paints.data + paints.count); ++paint) {
(*paint)->pImpl->dispose(*renderer);
if (free) delete(*paint);
for (auto paint : paints) {
paint->pImpl->dispose(*renderer);
if (free) delete(paint);
}
paints.clear();
@ -83,7 +83,7 @@ struct Canvas::Impl
Result update(Paint* paint, bool force)
{
if (paints.count == 0 || drawing || !renderer) return Result::InsufficientCondition;
if (paints.empty() || drawing || !renderer) return Result::InsufficientCondition;
Array<RenderData> clips;
auto flag = RenderUpdateFlag::None;
@ -92,17 +92,17 @@ struct Canvas::Impl
//Update single paint node
if (paint) {
//Optimize Me: Can we skip the searching?
for (auto paint2 = paints.data; paint2 < (paints.data + paints.count); ++paint2) {
if ((*paint2) == paint) {
paint->pImpl->update(*renderer, nullptr, 255, clips, flag);
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
} else {
for (auto paint = paints.data; paint < (paints.data + paints.count); ++paint) {
(*paint)->pImpl->update(*renderer, nullptr, 255, clips, flag);
for (auto paint : paints) {
paint->pImpl->update(*renderer, nullptr, clips, 255, flag);
}
}
@ -113,11 +113,11 @@ struct Canvas::Impl
Result draw()
{
if (drawing || paints.count == 0 || !renderer || !renderer->preRender()) return Result::InsufficientCondition;
if (drawing || paints.empty() || !renderer || !renderer->preRender()) return Result::InsufficientCondition;
bool rendered = false;
for (auto paint = paints.data; paint < (paints.data + paints.count); ++paint) {
if ((*paint)->pImpl->render(*renderer)) rendered = true;
for (auto paint : paints) {
if (paint->pImpl->render(*renderer)) rendered = true;
}
if (!rendered || !renderer->postRender()) return Result::InsufficientCondition;

View file

@ -62,7 +62,9 @@ using namespace tvg;
#define TVG_CLASS_ID_LINEAR 4
#define TVG_CLASS_ID_RADIAL 5
enum class FileType { Tvg = 0, Svg, Raw, Png, Jpg, Unknown };
enum class FileType { Tvg = 0, Svg, Lottie, Raw, Png, Jpg, Webp, Unknown };
using Size = Point;
#ifdef THORVG_LOG_ENABLED
constexpr auto ErrorColor = "\033[31m"; //red

View file

@ -55,11 +55,11 @@ struct Fill::Impl
uint32_t cnt = 0;
FillSpread spread;
DuplicateMethod<Fill>* dup = nullptr;
uint32_t id;
uint8_t id;
~Impl()
{
if (dup) delete(dup);
delete(dup);
free(colorStops);
free(transform);
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 - 2023 the ThorVG project. All rights reserved.
* Copyright (c) 2023 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
@ -20,28 +20,28 @@
* SOFTWARE.
*/
#ifndef _TVG_PNG_LOADER_H_
#define _TVG_PNG_LOADER_H_
#ifndef _TVG_FRAME_MODULE_H_
#define _TVG_FRAME_MODULE_H_
#include <png.h>
#include "tvgLoadModule.h"
class PngLoader : public LoadModule
namespace tvg
{
class FrameModule: public LoadModule
{
public:
PngLoader();
~PngLoader();
virtual ~FrameModule() {}
using LoadModule::open;
bool open(const string& path) override;
bool open(const char* data, uint32_t size, bool copy) override;
bool read() override;
bool close() override;
virtual bool frame(uint32_t frameNo) = 0; //set the current frame number
unique_ptr<Surface> bitmap() override;
virtual uint32_t totalFrame() = 0; //return the total frame count
virtual uint32_t curFrame() = 0; //return the current frame number
virtual float duration() = 0; //return the animation duration in seconds
private:
png_imagep image = nullptr;
uint32_t* content = nullptr;
virtual bool animatable() override { return true; }
};
#endif //_TVG_PNG_LOADER_H_
}
#endif //_TVG_FRAME_MODULE_H_

View file

@ -31,11 +31,6 @@ namespace tvg
class LoadModule
{
public:
//default view box, if any.
float vx = 0;
float vy = 0;
float vw = 0;
float vh = 0;
float w = 0, h = 0; //default image size
ColorSpace cs = ColorSpace::Unsupported; //must be clarified at open()
@ -48,8 +43,12 @@ public:
//Override this if the vector-format has own resizing policy.
virtual bool resize(Paint* paint, float w, float h) { return false; }
virtual bool animatable() { return false; } //true if this loader supports animation.
virtual void sync() {}; //finish immediately if any async update jobs.
virtual bool read() = 0;
virtual bool close() = 0;
virtual unique_ptr<Surface> bitmap() { return nullptr; }
virtual unique_ptr<Paint> paint() { return nullptr; }
};

View file

@ -38,6 +38,14 @@
#include "tvgJpgLoader.h"
#endif
#ifdef THORVG_WEBP_LOADER_SUPPORT
#include "tvgWebpLoader.h"
#endif
#ifdef THORVG_LOTTIE_LOADER_SUPPORT
#include "tvgLottieLoader.h"
#endif
#include "tvgRawLoader.h"
/************************************************************************/
@ -56,6 +64,12 @@ static LoadModule* _find(FileType type)
case FileType::Svg: {
#ifdef THORVG_SVG_LOADER_SUPPORT
return new SvgLoader;
#endif
break;
}
case FileType::Lottie: {
#ifdef THORVG_LOTTIE_LOADER_SUPPORT
return new LottieLoader;
#endif
break;
}
@ -72,6 +86,12 @@ static LoadModule* _find(FileType type)
case FileType::Jpg: {
#ifdef THORVG_JPG_LOADER_SUPPORT
return new JpgLoader;
#endif
break;
}
case FileType::Webp: {
#ifdef THORVG_WEBP_LOADER_SUPPORT
return new WebpLoader;
#endif
break;
}
@ -91,6 +111,10 @@ static LoadModule* _find(FileType type)
format = "SVG";
break;
}
case FileType::Lottie: {
format = "lottie(json)";
break;
}
case FileType::Raw: {
format = "RAW";
break;
@ -103,6 +127,10 @@ static LoadModule* _find(FileType type)
format = "JPG";
break;
}
case FileType::Webp: {
format = "WEBP";
break;
}
default: {
format = "???";
break;
@ -119,8 +147,11 @@ static LoadModule* _findByPath(const string& path)
auto ext = path.substr(path.find_last_of(".") + 1);
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);
return nullptr;
}
@ -133,9 +164,11 @@ static LoadModule* _findByType(const string& mimeType)
if (mimeType == "tvg") type = FileType::Tvg;
else if (mimeType == "svg" || mimeType == "svg+xml") type = FileType::Svg;
else if (mimeType == "lottie" || mimeType == "json") type = FileType::Lottie;
else if (mimeType == "raw") type = FileType::Raw;
else if (mimeType == "png") type = FileType::Png;
else if (mimeType == "jpg" || mimeType == "jpeg") type = FileType::Jpg;
else if (mimeType == "webp") type = FileType::Webp;
else {
TVGLOG("LOADER", "Given mimetype is unknown = \"%s\".", mimeType.c_str());
return nullptr;

View file

@ -45,6 +45,15 @@ static inline bool mathEqual(float a, float b)
return (fabsf(a - b) < FLT_EPSILON);
}
static inline bool mathEqual(const Matrix& a, const Matrix& b)
{
if (!mathEqual(a.e11, b.e11) || !mathEqual(a.e12, b.e12) || !mathEqual(a.e13, b.e13) ||
!mathEqual(a.e21, b.e21) || !mathEqual(a.e22, b.e22) || !mathEqual(a.e23, b.e23) ||
!mathEqual(a.e31, b.e31) || !mathEqual(a.e32, b.e32) || !mathEqual(a.e33, b.e33)) {
return false;
}
return true;
}
static inline bool mathRightAngle(const Matrix* m)
{
@ -109,17 +118,17 @@ static inline void mathIdentity(Matrix* m)
}
static inline void mathScale(Matrix* m, float scale)
static inline void mathScale(Matrix* m, float sx, float sy)
{
m->e11 = scale;
m->e22 = scale;
m->e11 *= sx;
m->e22 *= sy;
}
static inline void mathTranslate(Matrix* m, float x, float y)
{
m->e13 = x;
m->e23 = y;
m->e13 += x;
m->e23 += y;
}
@ -165,4 +174,29 @@ static inline Matrix mathMultiply(const Matrix* lhs, const Matrix* rhs)
}
static inline Point operator-(const Point& lhs, const Point& rhs)
{
return {lhs.x - rhs.x, lhs.y - rhs.y};
}
static inline Point operator+(const Point& lhs, const Point& rhs)
{
return {lhs.x + rhs.x, lhs.y + rhs.y};
}
static inline Point operator*(const Point& lhs, float rhs)
{
return {lhs.x * rhs, lhs.y * rhs};
}
template <typename T>
static inline T mathLerp(const T &start, const T &end, float t)
{
return static_cast<T>(start + (end - start) * t);
}
#endif //_TVG_MATH_H_

View file

@ -166,6 +166,7 @@ bool Paint::Impl::render(RenderMethod& renderer)
Create a composition image. */
if (compData && compData->method != CompositeMethod::ClipPath && !(compData->target->pImpl->ctxFlag & ContextFlag::FastTrack)) {
auto region = smethod->bounds(renderer);
if (MASK_OPERATION(compData->method)) region.add(compData->target->pImpl->smethod->bounds(renderer));
if (region.w == 0 || region.h == 0) return true;
cmp = renderer.target(region, COMPOSITE_TO_COLORSPACE(renderer, compData->method));
if (renderer.beginComposite(cmp, CompositeMethod::None, 255)) {
@ -175,6 +176,7 @@ bool Paint::Impl::render(RenderMethod& renderer)
if (cmp) renderer.beginComposite(cmp, compData->method, compData->target->pImpl->opacity);
renderer.blend(blendMethod);
auto ret = smethod->render(renderer);
if (cmp) renderer.endComposite(cmp);
@ -183,7 +185,7 @@ bool Paint::Impl::render(RenderMethod& renderer)
}
RenderData Paint::Impl::update(RenderMethod& renderer, const RenderTransform* pTransform, uint32_t opacity, Array<RenderData>& clips, uint32_t pFlag, bool clipper)
RenderData Paint::Impl::update(RenderMethod& renderer, const RenderTransform* pTransform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper)
{
if (renderFlag & RenderUpdateFlag::Transform) {
if (!rTransform) return nullptr;
@ -209,11 +211,18 @@ RenderData Paint::Impl::update(RenderMethod& renderer, const RenderTransform* pT
auto tryFastTrack = false;
if (target->identifier() == TVG_CLASS_ID_SHAPE) {
if (method == CompositeMethod::ClipPath) tryFastTrack = true;
//OPTIMIZE HERE: Actually, this condition AlphaMask is useless. We can skip it?
else if (method == CompositeMethod::AlphaMask) {
auto shape = static_cast<Shape*>(target);
uint8_t a;
shape->fillColor(nullptr, nullptr, nullptr, &a);
if (a == 255 && shape->opacity() == 255 && !shape->fill()) tryFastTrack = true;
//OPTIMIZE HERE: Actually, this condition InvAlphaMask is useless. We can skip it?
} else if (method == CompositeMethod::InvAlphaMask) {
auto shape = static_cast<Shape*>(target);
uint8_t a;
shape->fillColor(nullptr, nullptr, nullptr, &a);
if ((a == 0 || shape->opacity() == 0) && !shape->fill()) tryFastTrack = true;
}
if (tryFastTrack) {
RenderRegion viewport2;
@ -227,7 +236,7 @@ RenderData Paint::Impl::update(RenderMethod& renderer, const RenderTransform* pT
}
if (!compFastTrack) {
childClipper = compData->method == CompositeMethod::ClipPath ? true : false;
trd = target->pImpl->update(renderer, pTransform, 255, clips, pFlag, childClipper);
trd = target->pImpl->update(renderer, pTransform, clips, 255, pFlag, childClipper);
if (childClipper) clips.push(trd);
}
}
@ -236,14 +245,14 @@ RenderData Paint::Impl::update(RenderMethod& renderer, const RenderTransform* pT
RenderData rd = nullptr;
auto newFlag = static_cast<RenderUpdateFlag>(pFlag | renderFlag);
renderFlag = RenderUpdateFlag::None;
opacity = (opacity * this->opacity) / 255;
opacity = MULTIPLY(opacity, this->opacity);
if (rTransform && pTransform) {
RenderTransform outTransform(pTransform, rTransform);
rd = smethod->update(renderer, &outTransform, opacity, clips, newFlag, clipper);
rd = smethod->update(renderer, &outTransform, clips, opacity, newFlag, clipper);
} else {
auto outTransform = pTransform ? pTransform : rTransform;
rd = smethod->update(renderer, outTransform, opacity, clips, newFlag, clipper);
rd = smethod->update(renderer, outTransform, clips, opacity, newFlag, clipper);
}
/* 3. Composition Post Processing */
@ -371,7 +380,7 @@ Result Paint::composite(std::unique_ptr<Paint> target, CompositeMethod method) n
{
auto p = target.release();
if (pImpl->composite(this, p, method)) return Result::Success;
if (p) delete(p);
delete(p);
return Result::InvalidArguments;
}
@ -409,3 +418,17 @@ uint32_t Paint::identifier() const noexcept
{
return pImpl->id;
}
Result Paint::blend(BlendMethod method) const noexcept
{
pImpl->blendMethod = method;
return Result::Success;
}
BlendMethod Paint::blend() const noexcept
{
return pImpl->blendMethod;
}

View file

@ -28,7 +28,7 @@
namespace tvg
{
enum ContextFlag {Invalid = 0, FastTrack = 1};
enum ContextFlag : uint8_t {Invalid = 0, FastTrack = 1};
struct Iterator
{
@ -43,7 +43,7 @@ namespace tvg
virtual ~StrategyMethod() {}
virtual bool dispose(RenderMethod& renderer) = 0;
virtual void* update(RenderMethod& renderer, const RenderTransform* transform, uint32_t opacity, Array<RenderData>& clips, RenderUpdateFlag pFlag, bool clipper) = 0; //Return engine data if it has.
virtual void* update(RenderMethod& renderer, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper) = 0; //Return engine data if it has.
virtual bool render(RenderMethod& renderer) = 0;
virtual bool bounds(float* x, float* y, float* w, float* h) = 0;
virtual RenderRegion bounds(RenderMethod& renderer) const = 0;
@ -63,9 +63,10 @@ namespace tvg
StrategyMethod* smethod = nullptr;
RenderTransform* rTransform = nullptr;
Composite* compData = nullptr;
uint32_t renderFlag = RenderUpdateFlag::None;
uint32_t ctxFlag = ContextFlag::Invalid;
uint32_t id;
BlendMethod blendMethod = BlendMethod::Normal; //uint8_t
uint8_t renderFlag = RenderUpdateFlag::None;
uint8_t ctxFlag = ContextFlag::Invalid;
uint8_t id;
uint8_t opacity = 255;
~Impl()
@ -74,8 +75,8 @@ namespace tvg
delete(compData->target);
free(compData);
}
if (smethod) delete(smethod);
if (rTransform) delete(rTransform);
delete(smethod);
delete(rTransform);
}
void method(StrategyMethod* method)
@ -147,7 +148,7 @@ namespace tvg
bool scale(float factor);
bool translate(float x, float y);
bool bounds(float* x, float* y, float* w, float* h, bool transformed);
RenderData update(RenderMethod& renderer, const RenderTransform* pTransform, uint32_t opacity, Array<RenderData>& clips, uint32_t pFlag, bool clipper = false);
RenderData update(RenderMethod& renderer, const RenderTransform* pTransform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper = false);
bool render(RenderMethod& renderer);
Paint* duplicate();
};
@ -176,9 +177,9 @@ namespace tvg
return inst->dispose(renderer);
}
RenderData update(RenderMethod& renderer, const RenderTransform* transform, uint32_t opacity, Array<RenderData>& clips, RenderUpdateFlag renderFlag, bool clipper) override
RenderData update(RenderMethod& renderer, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag renderFlag, bool clipper) override
{
return inst->update(renderer, transform, opacity, clips, renderFlag, clipper);
return inst->update(renderer, transform, clips, opacity, renderFlag, clipper);
}
bool render(RenderMethod& renderer) override

View file

@ -26,7 +26,7 @@
/* External Class Implementation */
/************************************************************************/
Picture::Picture() : pImpl(new Impl)
Picture::Picture() : pImpl(new Impl(this))
{
Paint::pImpl->id = TVG_CLASS_ID_PICTURE;
Paint::pImpl->method(new PaintMethod<Picture::Impl>(pImpl));
@ -81,13 +81,6 @@ Result Picture::load(uint32_t* data, uint32_t w, uint32_t h, bool copy) noexcept
}
Result Picture::viewbox(float* x, float* y, float* w, float* h) const noexcept
{
if (pImpl->viewbox(x, y, w, h)) return Result::Success;
return Result::InsufficientCondition;
}
Result Picture::size(float w, float h) noexcept
{
if (pImpl->size(w, h)) return Result::Success;

View file

@ -67,11 +67,18 @@ struct Picture::Impl
RenderData rd = nullptr; //engine data
float w = 0, h = 0;
RenderMesh rm; //mesh data
Picture* picture = nullptr;
bool resizing = false;
bool needComp = false; //need composition
bool animated = false; //picture is belonged to Animation
Impl(Picture* p) : picture(p)
{
}
~Impl()
{
if (paint) delete(paint);
delete(paint);
delete(surface);
}
@ -85,7 +92,7 @@ struct Picture::Impl
return ret;
}
uint32_t load()
RenderUpdateFlag load()
{
if (loader) {
if (!paint) {
@ -102,7 +109,8 @@ struct Picture::Impl
}
if (paint) return RenderUpdateFlag::None;
}
}
} else loader->sync();
if (!surface) {
if ((surface = loader->bitmap().release())) {
loader->close();
@ -127,38 +135,52 @@ struct Picture::Impl
else return RenderTransform(pTransform, &tmp);
}
RenderData update(RenderMethod &renderer, const RenderTransform* pTransform, uint32_t opacity, Array<RenderData>& clips, RenderUpdateFlag pFlag, bool clipper)
bool needComposition(uint8_t opacity)
{
//In this case, paint(scene) would try composition itself.
if (opacity < 255) return false;
//Composition test
const Paint* target;
auto method = picture->composite(&target);
if (!target || method == tvg::CompositeMethod::ClipPath) return false;
if (target->pImpl->opacity == 255 || target->pImpl->opacity == 0) return false;
return true;
}
RenderData update(RenderMethod &renderer, const RenderTransform* pTransform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, bool clipper)
{
auto flag = load();
if (surface) {
auto transform = resizeTransform(pTransform);
rd = renderer.prepare(surface, &rm, rd, &transform, opacity, clips, static_cast<RenderUpdateFlag>(pFlag | flag));
rd = renderer.prepare(surface, &rm, rd, &transform, clips, opacity, static_cast<RenderUpdateFlag>(pFlag | flag));
} else if (paint) {
if (resizing) {
loader->resize(paint, w, h);
resizing = false;
}
rd = paint->pImpl->update(renderer, pTransform, opacity, clips, static_cast<RenderUpdateFlag>(pFlag | flag), clipper);
needComp = needComposition(opacity) ? true : false;
rd = paint->pImpl->update(renderer, pTransform, clips, opacity, static_cast<RenderUpdateFlag>(pFlag | flag), clipper);
}
return rd;
}
bool render(RenderMethod &renderer)
{
bool ret = false;
if (surface) return renderer.renderImage(rd);
else if (paint) return paint->pImpl->render(renderer);
return false;
}
bool viewbox(float* x, float* y, float* w, float* h)
{
if (!loader) return false;
if (x) *x = loader->vx;
if (y) *y = loader->vy;
if (w) *w = loader->vw;
if (h) *h = loader->vh;
return true;
else if (paint) {
Compositor* cmp = nullptr;
if (needComp) {
cmp = renderer.target(bounds(renderer), renderer.colorSpace());
renderer.beginComposite(cmp, CompositeMethod::None, 255);
}
ret = paint->pImpl->render(renderer);
if (cmp) renderer.endComposite(cmp);
}
return ret;
}
bool size(float w, float h)

View file

@ -53,7 +53,7 @@ bool RenderTransform::update()
mathIdentity(&m);
mathScale(&m, scale);
mathScale(&m, scale, scale);
if (!mathZero(degree)) mathRotate(&m, degree);

View file

@ -32,7 +32,7 @@ namespace tvg
using RenderData = void*;
using pixel_t = uint32_t;
enum RenderUpdateFlag {None = 0, Path = 1, Color = 2, Gradient = 4, Stroke = 8, Transform = 16, Image = 32, GradientStroke = 64, All = 255};
enum RenderUpdateFlag : uint8_t {None = 0, Path = 1, Color = 2, Gradient = 4, Stroke = 8, Transform = 16, Image = 32, GradientStroke = 64, All = 255};
struct Surface;
@ -65,7 +65,7 @@ struct Surface
struct Compositor
{
CompositeMethod method;
uint32_t opacity;
uint8_t opacity;
};
struct RenderMesh
@ -98,6 +98,20 @@ struct RenderRegion
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;
}
};
struct RenderTransform
@ -125,12 +139,13 @@ struct RenderStroke
uint32_t dashCnt = 0;
StrokeCap cap = StrokeCap::Square;
StrokeJoin join = StrokeJoin::Bevel;
float miterlimit = 4.0f;
bool strokeFirst = false;
~RenderStroke()
{
free(dashPattern);
if (fill) delete(fill);
delete(fill);
}
};
@ -138,13 +153,8 @@ struct RenderShape
{
struct
{
PathCommand* cmds = nullptr;
uint32_t cmdCnt = 0;
uint32_t reservedCmdCnt = 0;
Point *pts = nullptr;
uint32_t ptsCnt = 0;
uint32_t reservedPtsCnt = 0;
Array<PathCommand> cmds;
Array<Point> pts;
} path;
Fill *fill = nullptr;
@ -154,11 +164,8 @@ struct RenderShape
~RenderShape()
{
free(path.cmds);
free(path.pts);
if (fill) delete(fill);
if (stroke) delete(stroke);
delete(fill);
delete(stroke);
}
void fillColor(uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a) const
@ -211,15 +218,22 @@ struct RenderShape
if (!stroke) return StrokeJoin::Bevel;
return stroke->join;
}
float strokeMiterlimit() const
{
if (!stroke) return 4.0f;
return stroke->miterlimit;;
}
};
class RenderMethod
{
public:
virtual ~RenderMethod() {}
virtual RenderData prepare(const RenderShape& rshape, RenderData data, const RenderTransform* transform, uint32_t opacity, Array<RenderData>& clips, RenderUpdateFlag flags, bool clipper) = 0;
virtual RenderData prepare(const Array<RenderData>& scene, RenderData data, const RenderTransform* transform, uint32_t opacity, Array<RenderData>& clips, RenderUpdateFlag flags) = 0;
virtual RenderData prepare(Surface* surface, const RenderMesh* mesh, RenderData data, const RenderTransform* transform, uint32_t opacity, Array<RenderData>& clips, RenderUpdateFlag flags) = 0;
virtual RenderData prepare(const RenderShape& rshape, RenderData data, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags, bool clipper) = 0;
virtual RenderData prepare(const Array<RenderData>& scene, RenderData data, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags) = 0;
virtual RenderData prepare(Surface* surface, const RenderMesh* mesh, RenderData data, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flags) = 0;
virtual bool preRender() = 0;
virtual bool renderShape(RenderData data) = 0;
virtual bool renderImage(RenderData data) = 0;
@ -228,16 +242,36 @@ public:
virtual RenderRegion region(RenderData data) = 0;
virtual RenderRegion viewport() = 0;
virtual bool viewport(const RenderRegion& vp) = 0;
virtual bool blend(BlendMethod method) = 0;
virtual ColorSpace colorSpace() = 0;
virtual bool clear() = 0;
virtual bool sync() = 0;
virtual Compositor* target(const RenderRegion& region, ColorSpace cs) = 0;
virtual bool beginComposite(Compositor* cmp, CompositeMethod method, uint32_t opacity) = 0;
virtual bool beginComposite(Compositor* cmp, CompositeMethod method, uint8_t opacity) = 0;
virtual bool endComposite(Compositor* cmp) = 0;
};
static inline bool MASK_OPERATION(CompositeMethod method)
{
switch(method) {
case CompositeMethod::AlphaMask:
case CompositeMethod::InvAlphaMask:
case CompositeMethod::LumaMask:
case CompositeMethod::InvLumaMask:
return false;
case CompositeMethod::AddMask:
case CompositeMethod::SubtractMask:
case CompositeMethod::IntersectMask:
case CompositeMethod::DifferenceMask:
return true;
default:
TVGERR("COMMON", "Unsupported Composite Size! = %d", (int)method);
return false;
}
}
static inline uint8_t CHANNEL_SIZE(ColorSpace cs)
{
switch(cs) {
@ -262,6 +296,11 @@ static inline ColorSpace COMPOSITE_TO_COLORSPACE(RenderMethod& renderer, Composi
case CompositeMethod::InvAlphaMask:
return ColorSpace::Grayscale8;
case CompositeMethod::LumaMask:
case CompositeMethod::InvLumaMask:
case CompositeMethod::AddMask:
case CompositeMethod::SubtractMask:
case CompositeMethod::IntersectMask:
case CompositeMethod::DifferenceMask:
return renderer.colorSpace();
default:
TVGERR("COMMON", "Unsupported Composite Size! = %d", (int)method);
@ -269,6 +308,12 @@ static inline ColorSpace COMPOSITE_TO_COLORSPACE(RenderMethod& renderer, Composi
}
}
static inline uint8_t MULTIPLY(uint8_t c, uint8_t a)
{
return (((c) * (a) + 0xff) >> 8);
}
}
#endif //_TVG_RENDER_H_

View file

@ -36,7 +36,7 @@ struct Saver::Impl
SaveModule* saveModule = nullptr;
~Impl()
{
if (saveModule) delete(saveModule);
delete(saveModule);
}
};

View file

@ -55,17 +55,15 @@ Result Scene::push(unique_ptr<Paint> paint) noexcept
{
auto p = paint.release();
if (!p) return Result::MemoryCorruption;
pImpl->paints.push(p);
pImpl->paints.push_back(p);
return Result::Success;
}
Result Scene::reserve(uint32_t size) noexcept
Result Scene::reserve(TVG_UNUSED uint32_t size) noexcept
{
if (!pImpl->paints.reserve(size)) return Result::FailedAllocation;
return Result::Success;
return Result::NonSupport;
}
@ -75,3 +73,9 @@ Result Scene::clear(bool free) noexcept
return Result::Success;
}
list<Paint*>& Scene::paints() noexcept
{
return pImpl->paints;
}

View file

@ -32,37 +32,41 @@
struct SceneIterator : Iterator
{
Array<Paint*>* paints;
uint32_t idx = 0;
list<Paint*>* paints;
list<Paint*>::iterator itr;
SceneIterator(Array<Paint*>* p) : paints(p)
SceneIterator(list<Paint*>* p) : paints(p)
{
begin();
}
const Paint* next() override
{
if (idx >= paints->count) return nullptr;
return paints->data[idx++];
if (itr == paints->end()) return nullptr;
auto paint = *itr;
++itr;
return paint;
}
uint32_t count() override
{
return paints->count;
return paints->size();
}
void begin() override
{
idx = 0;
itr = paints->begin();
}
};
struct Scene::Impl
{
Array<Paint*> paints;
uint8_t opacity; //for composition
list<Paint*> paints;
RenderMethod* renderer = nullptr; //keep it for explicit clear
RenderData rd = nullptr;
Scene* scene = nullptr;
uint8_t opacity; //for composition
bool needComp; //composite or not
Impl(Scene* s) : scene(s)
{
@ -70,15 +74,15 @@ struct Scene::Impl
~Impl()
{
for (auto paint = paints.data; paint < (paints.data + paints.count); ++paint) {
delete(*paint);
for (auto paint : paints) {
delete(paint);
}
}
bool dispose(RenderMethod& renderer)
{
for (auto paint = paints.data; paint < (paints.data + paints.count); ++paint) {
(*paint)->pImpl->dispose(renderer);
for (auto paint : paints) {
paint->pImpl->dispose(renderer);
}
auto ret = renderer.dispose(rd);
@ -88,43 +92,50 @@ struct Scene::Impl
return ret;
}
bool needComposition(uint32_t opacity)
bool needComposition(uint8_t opacity)
{
if (opacity == 0 || paints.count == 0) return false;
if (opacity == 0 || paints.empty()) return false;
//Masking may require composition (even if opacity == 255)
auto compMethod = scene->composite(nullptr);
if (compMethod != CompositeMethod::None && compMethod != CompositeMethod::ClipPath) return true;
//Blending may require composition (even if opacity == 255)
if (scene->blend() != BlendMethod::Normal) return true;
//Half translucent requires intermediate composition.
if (opacity == 255) return false;
//If scene has several children or only scene, it may require composition.
if (paints.count > 1) return true;
if (paints.count == 1 && (*paints.data)->identifier() == TVG_CLASS_ID_SCENE) return true;
return false;
//OPTIMIZE: the bitmap type of the picture would not need the composition.
//OPTIMIZE: a single paint of a scene would not need the composition.
if (paints.size() == 1 && paints.front()->identifier() == TVG_CLASS_ID_SHAPE) return false;
return true;
}
RenderData update(RenderMethod &renderer, const RenderTransform* transform, uint32_t opacity, Array<RenderData>& clips, RenderUpdateFlag flag, bool clipper)
RenderData update(RenderMethod &renderer, const RenderTransform* transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag flag, bool clipper)
{
/* Overriding opacity value. If this scene is half-translucent,
It must do intermeidate composition with that opacity value. */
this->opacity = static_cast<uint8_t>(opacity);
if (needComposition(opacity)) opacity = 255;
if ((needComp = needComposition(opacity))) {
/* Overriding opacity value. If this scene is half-translucent,
It must do intermeidate composition with that opacity value. */
this->opacity = opacity;
opacity = 255;
}
this->renderer = &renderer;
if (clipper) {
Array<RenderData> rds;
rds.reserve(paints.count);
for (auto paint = paints.data; paint < (paints.data + paints.count); ++paint) {
rds.push((*paint)->pImpl->update(renderer, transform, opacity, clips, flag, true));
rds.reserve(paints.size());
for (auto paint : paints) {
rds.push(paint->pImpl->update(renderer, transform, clips, opacity, flag, true));
}
rd = renderer.prepare(rds, rd, transform, opacity, clips, flag);
rd = renderer.prepare(rds, rd, transform, clips, opacity, flag);
return rd;
} else {
for (auto paint = paints.data; paint < (paints.data + paints.count); ++paint) {
(*paint)->pImpl->update(renderer, transform, opacity, clips, flag, false);
for (auto paint : paints) {
paint->pImpl->update(renderer, transform, clips, opacity, flag, false);
}
return nullptr;
}
@ -134,13 +145,13 @@ struct Scene::Impl
{
Compositor* cmp = nullptr;
if (needComposition(opacity)) {
if (needComp) {
cmp = renderer.target(bounds(renderer), renderer.colorSpace());
renderer.beginComposite(cmp, CompositeMethod::None, opacity);
}
for (auto paint = paints.data; paint < (paints.data + paints.count); ++paint) {
if (!(*paint)->pImpl->render(renderer)) return false;
for (auto paint : paints) {
if (!paint->pImpl->render(renderer)) return false;
}
if (cmp) renderer.endComposite(cmp);
@ -150,15 +161,15 @@ struct Scene::Impl
RenderRegion bounds(RenderMethod& renderer) const
{
if (paints.count == 0) return {0, 0, 0, 0};
if (paints.empty()) return {0, 0, 0, 0};
int32_t x1 = INT32_MAX;
int32_t y1 = INT32_MAX;
int32_t x2 = 0;
int32_t y2 = 0;
for (auto paint = paints.data; paint < (paints.data + paints.count); ++paint) {
auto region = (*paint)->pImpl->bounds(renderer);
for (auto paint : paints) {
auto region = paint->pImpl->bounds(renderer);
//Merge regions
if (region.x < x1) x1 = region.x;
@ -172,20 +183,20 @@ struct Scene::Impl
bool bounds(float* px, float* py, float* pw, float* ph)
{
if (paints.count == 0) return false;
if (paints.empty()) return false;
auto x1 = FLT_MAX;
auto y1 = FLT_MAX;
auto x2 = -FLT_MAX;
auto y2 = -FLT_MAX;
for (auto paint = paints.data; paint < (paints.data + paints.count); ++paint) {
for (auto paint : paints) {
auto x = FLT_MAX;
auto y = FLT_MAX;
auto w = 0.0f;
auto h = 0.0f;
if ((*paint)->bounds(&x, &y, &w, &h, true) != tvg::Result::Success) continue;
if (paint->bounds(&x, &y, &w, &h, true) != tvg::Result::Success) continue;
//Merge regions
if (x < x1) x1 = x;
@ -208,10 +219,8 @@ struct Scene::Impl
auto dup = ret.get()->pImpl;
dup->paints.reserve(paints.count);
for (auto paint = paints.data; paint < (paints.data + paints.count); ++paint) {
dup->paints.push((*paint)->duplicate());
for (auto paint : paints) {
dup->paints.push_back(paint->duplicate());
}
return ret.release();
@ -221,9 +230,9 @@ struct Scene::Impl
{
auto dispose = renderer ? true : false;
for (auto paint = paints.data; paint < (paints.data + paints.count); ++paint) {
if (dispose) (*paint)->pImpl->dispose(*renderer);
if (free) delete(*paint);
for (auto paint : paints) {
if (dispose) paint->pImpl->dispose(*renderer);
if (free) delete(paint);
}
paints.clear();
renderer = nullptr;

View file

@ -32,7 +32,7 @@ constexpr auto PATH_KAPPA = 0.552284f;
/* External Class Implementation */
/************************************************************************/
Shape :: Shape() : pImpl(new Impl())
Shape :: Shape() : pImpl(new Impl(this))
{
Paint::pImpl->id = TVG_CLASS_ID_SHAPE;
Paint::pImpl->method(new PaintMethod<Shape::Impl>(pImpl));
@ -59,7 +59,10 @@ uint32_t Shape::identifier() noexcept
Result Shape::reset() noexcept
{
pImpl->reset();
pImpl->rs.path.cmds.clear();
pImpl->rs.path.pts.clear();
pImpl->flag = RenderUpdateFlag::Path;
return Result::Success;
}
@ -69,9 +72,8 @@ uint32_t Shape::pathCommands(const PathCommand** cmds) const noexcept
{
if (!cmds) return 0;
*cmds = pImpl->rs.path.cmds;
return pImpl->rs.path.cmdCnt;
*cmds = pImpl->rs.path.cmds.data;
return pImpl->rs.path.cmds.count;
}
@ -79,9 +81,8 @@ uint32_t Shape::pathCoords(const Point** pts) const noexcept
{
if (!pts) return 0;
*pts = pImpl->rs.path.pts;
return pImpl->rs.path.ptsCnt;
*pts = pImpl->rs.path.pts.data;
return pImpl->rs.path.pts.count;
}
@ -242,21 +243,22 @@ Result Shape::appendRect(float x, float y, float w, float h, float rx, float ry)
}
//TODO: kill alpha at TVG 1.0, because we also have opacity
Result Shape::fill(uint8_t r, uint8_t g, uint8_t b, uint8_t a) noexcept
{
pImpl->rs.color[0] = r;
pImpl->rs.color[1] = g;
pImpl->rs.color[2] = b;
pImpl->rs.color[3] = a;
pImpl->flag |= RenderUpdateFlag::Color;
if (pImpl->rs.fill) {
delete(pImpl->rs.fill);
pImpl->rs.fill = nullptr;
pImpl->flag |= RenderUpdateFlag::Gradient;
}
if (r == pImpl->rs.color[0] && g == pImpl->rs.color[1] && b == pImpl->rs.color[2] && a == pImpl->rs.color[3]) return Result::Success;
pImpl->rs.color[0] = r;
pImpl->rs.color[1] = g;
pImpl->rs.color[2] = b;
pImpl->rs.color[3] = a;
pImpl->flag |= RenderUpdateFlag::Color;
return Result::Success;
}
@ -328,7 +330,7 @@ Result Shape::strokeColor(uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a) const
Result Shape::stroke(unique_ptr<Fill> f) noexcept
{
return pImpl->strokeFill(move(f));
return pImpl->strokeFill(std::move(f));
}
@ -374,6 +376,17 @@ Result Shape::stroke(StrokeJoin join) noexcept
return Result::Success;
}
Result Shape::strokeMiterlimit(float miterlimit) noexcept
{
// https://www.w3.org/TR/SVG2/painting.html#LineJoin
// - A negative value for stroke-miterlimit must be treated as an illegal value.
if (miterlimit < 0.0f) return Result::NonSupport;
// TODO Find out a reasonable max value.
if (!pImpl->strokeMiterlimit(miterlimit)) return Result::FailedAllocation;
return Result::Success;
}
StrokeCap Shape::strokeCap() const noexcept
{
@ -386,6 +399,11 @@ StrokeJoin Shape::strokeJoin() const noexcept
return pImpl->rs.strokeJoin();
}
float Shape::strokeMiterlimit() const noexcept
{
return pImpl->rs.strokeMiterlimit();
}
Result Shape::fill(FillRule r) noexcept
{

View file

@ -24,6 +24,7 @@
#define _TVG_SHAPE_IMPL_H_
#include <memory.h>
#include "tvgMath.h"
#include "tvgPaint.h"
/************************************************************************/
@ -34,7 +35,14 @@ struct Shape::Impl
{
RenderShape rs; //shape data
RenderData rd = nullptr; //engine data
uint32_t flag = RenderUpdateFlag::None;
Shape* shape;
uint8_t flag = RenderUpdateFlag::None;
uint8_t opacity; //for composition
bool needComp; //composite or not
Impl(Shape* s) : shape(s)
{
}
bool dispose(RenderMethod& renderer)
{
@ -45,12 +53,48 @@ struct Shape::Impl
bool render(RenderMethod& renderer)
{
return renderer.renderShape(rd);
Compositor* cmp = nullptr;
bool ret;
if (needComp) {
cmp = renderer.target(bounds(renderer), renderer.colorSpace());
renderer.beginComposite(cmp, CompositeMethod::None, opacity);
}
ret = renderer.renderShape(rd);
if (cmp) renderer.endComposite(cmp);
return ret;
}
RenderData update(RenderMethod& renderer, const RenderTransform* transform, uint32_t opacity, Array<RenderData>& clips, RenderUpdateFlag pFlag, bool clipper)
bool needComposition(uint8_t opacity)
{
rd = renderer.prepare(rs, rd, transform, opacity, clips, static_cast<RenderUpdateFlag>(pFlag | flag), clipper);
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->color[3] == 0) return false;
if (!rs.fill && rs.color[3] == 0) return false;
//translucent fill & stroke
if (opacity < 255) return true;
//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;
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. */
this->opacity = opacity;
opacity = 255;
}
rd = renderer.prepare(rs, rd, transform, clips, opacity, static_cast<RenderUpdateFlag>(pFlag | flag), clipper);
flag = RenderUpdateFlag::None;
return rd;
}
@ -63,15 +107,16 @@ struct Shape::Impl
bool bounds(float* x, float* y, float* w, float* h)
{
//Path bounding size
if (rs.path.ptsCnt > 0 ) {
Point min = { rs.path.pts[0].x, rs.path.pts[0].y };
Point max = { rs.path.pts[0].x, rs.path.pts[0].y };
if (rs.path.pts.count > 0 ) {
auto pts = rs.path.pts.data;
Point min = { pts->x, pts->y };
Point max = { pts->x, pts->y };
for (uint32_t i = 1; i < rs.path.ptsCnt; ++i) {
if (rs.path.pts[i].x < min.x) min.x = rs.path.pts[i].x;
if (rs.path.pts[i].y < min.y) min.y = rs.path.pts[i].y;
if (rs.path.pts[i].x > max.x) max.x = rs.path.pts[i].x;
if (rs.path.pts[i].y > max.y) max.y = rs.path.pts[i].y;
for (auto pts2 = pts + 1; pts2 < rs.path.pts.end(); ++pts2) {
if (pts2->x < min.x) min.x = pts2->x;
if (pts2->y < min.y) min.y = pts2->y;
if (pts2->x > max.x) max.x = pts2->x;
if (pts2->y > max.y) max.y = pts2->y;
}
if (x) *x = min.x;
@ -87,88 +132,67 @@ struct Shape::Impl
if (w) *w += rs.stroke->width;
if (h) *h += rs.stroke->width;
}
return rs.path.ptsCnt > 0 ? true : false;
return rs.path.pts.count > 0 ? true : false;
}
void reserveCmd(uint32_t cmdCnt)
{
if (cmdCnt <= rs.path.reservedCmdCnt) return;
rs.path.reservedCmdCnt = cmdCnt;
rs.path.cmds = static_cast<PathCommand*>(realloc(rs.path.cmds, sizeof(PathCommand) * rs.path.reservedCmdCnt));
rs.path.cmds.reserve(cmdCnt);
}
void reservePts(uint32_t ptsCnt)
{
if (ptsCnt <= rs.path.reservedPtsCnt) return;
rs.path.reservedPtsCnt = ptsCnt;
rs.path.pts = static_cast<Point*>(realloc(rs.path.pts, sizeof(Point) * rs.path.reservedPtsCnt));
rs.path.pts.reserve(ptsCnt);
}
void grow(uint32_t cmdCnt, uint32_t ptsCnt)
{
reserveCmd(rs.path.cmdCnt + cmdCnt);
reservePts(rs.path.ptsCnt + ptsCnt);
}
void reset()
{
rs.path.cmdCnt = 0;
rs.path.ptsCnt = 0;
flag = RenderUpdateFlag::Path;
rs.path.cmds.grow(cmdCnt);
rs.path.pts.grow(ptsCnt);
}
void append(const PathCommand* cmds, uint32_t cmdCnt, const Point* pts, uint32_t ptsCnt)
{
memcpy(rs.path.cmds + rs.path.cmdCnt, cmds, sizeof(PathCommand) * cmdCnt);
memcpy(rs.path.pts + rs.path.ptsCnt, pts, sizeof(Point) * ptsCnt);
rs.path.cmdCnt += cmdCnt;
rs.path.ptsCnt += ptsCnt;
memcpy(rs.path.cmds.end(), cmds, sizeof(PathCommand) * cmdCnt);
memcpy(rs.path.pts.end(), pts, sizeof(Point) * ptsCnt);
rs.path.cmds.count += cmdCnt;
rs.path.pts.count += ptsCnt;
flag |= RenderUpdateFlag::Path;
}
void moveTo(float x, float y)
{
if (rs.path.cmdCnt + 1 > rs.path.reservedCmdCnt) reserveCmd((rs.path.cmdCnt + 1) * 2);
if (rs.path.ptsCnt + 2 > rs.path.reservedPtsCnt) reservePts((rs.path.ptsCnt + 2) * 2);
rs.path.cmds[rs.path.cmdCnt++] = PathCommand::MoveTo;
rs.path.pts[rs.path.ptsCnt++] = {x, y};
rs.path.cmds.push(PathCommand::MoveTo);
rs.path.pts.push({x, y});
flag |= RenderUpdateFlag::Path;
}
void lineTo(float x, float y)
{
if (rs.path.cmdCnt + 1 > rs.path.reservedCmdCnt) reserveCmd((rs.path.cmdCnt + 1) * 2);
if (rs.path.ptsCnt + 2 > rs.path.reservedPtsCnt) reservePts((rs.path.ptsCnt + 2) * 2);
rs.path.cmds[rs.path.cmdCnt++] = PathCommand::LineTo;
rs.path.pts[rs.path.ptsCnt++] = {x, y};
rs.path.cmds.push(PathCommand::LineTo);
rs.path.pts.push({x, y});
flag |= RenderUpdateFlag::Path;
}
void cubicTo(float cx1, float cy1, float cx2, float cy2, float x, float y)
{
if (rs.path.cmdCnt + 1 > rs.path.reservedCmdCnt) reserveCmd((rs.path.cmdCnt + 1) * 2);
if (rs.path.ptsCnt + 3 > rs.path.reservedPtsCnt) reservePts((rs.path.ptsCnt + 3) * 2);
rs.path.cmds[rs.path.cmdCnt++] = PathCommand::CubicTo;
rs.path.pts[rs.path.ptsCnt++] = {cx1, cy1};
rs.path.pts[rs.path.ptsCnt++] = {cx2, cy2};
rs.path.pts[rs.path.ptsCnt++] = {x, y};
rs.path.cmds.push(PathCommand::CubicTo);
rs.path.pts.push({cx1, cy1});
rs.path.pts.push({cx2, cy2});
rs.path.pts.push({x, y});
flag |= RenderUpdateFlag::Path;
}
void close()
{
if (rs.path.cmdCnt > 0 && rs.path.cmds[rs.path.cmdCnt - 1] == PathCommand::Close) return;
//Don't close multiple times.
if (rs.path.cmds.count > 0 && rs.path.cmds.last() == PathCommand::Close) return;
if (rs.path.cmdCnt + 1 > rs.path.reservedCmdCnt) reserveCmd((rs.path.cmdCnt + 1) * 2);
rs.path.cmds[rs.path.cmdCnt++] = PathCommand::Close;
rs.path.cmds.push(PathCommand::Close);
flag |= RenderUpdateFlag::Path;
}
@ -202,6 +226,15 @@ struct Shape::Impl
return true;
}
bool strokeMiterlimit(float miterlimit)
{
if (!rs.stroke) rs.stroke = new RenderStroke();
rs.stroke->miterlimit = miterlimit;
flag |= RenderUpdateFlag::Stroke;
return true;
}
bool strokeColor(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
{
if (!rs.stroke) rs.stroke = new RenderStroke();
@ -262,6 +295,12 @@ struct Shape::Impl
return true;
}
bool strokeFirst()
{
if (!rs.stroke) return true;
return rs.stroke->strokeFirst;
}
bool strokeFirst(bool strokeFirst)
{
if (!rs.stroke) rs.stroke = new RenderStroke();
@ -271,6 +310,11 @@ struct Shape::Impl
return true;
}
void update(RenderUpdateFlag flag)
{
this->flag |= flag;
}
Paint* duplicate()
{
auto ret = Shape::gen();
@ -283,19 +327,11 @@ struct Shape::Impl
dup->flag = RenderUpdateFlag::Color;
//Path
if (rs.path.cmdCnt > 0 && rs.path.ptsCnt > 0) {
dup->rs.path.cmdCnt = rs.path.cmdCnt;
dup->rs.path.reservedCmdCnt = rs.path.reservedCmdCnt;
dup->rs.path.ptsCnt = rs.path.ptsCnt;
dup->rs.path.reservedPtsCnt = rs.path.reservedPtsCnt;
dup->rs.path.cmds = static_cast<PathCommand*>(malloc(sizeof(PathCommand) * dup->rs.path.reservedCmdCnt));
if (dup->rs.path.cmds) memcpy(dup->rs.path.cmds, rs.path.cmds, sizeof(PathCommand) * dup->rs.path.cmdCnt);
dup->rs.path.pts = static_cast<Point*>(malloc(sizeof(Point) * dup->rs.path.reservedPtsCnt));
if (dup->rs.path.pts) memcpy(dup->rs.path.pts, rs.path.pts, sizeof(Point) * dup->rs.path.ptsCnt);
if (rs.path.cmds.count > 0 && rs.path.pts.count > 0) {
dup->rs.path.cmds = rs.path.cmds;
dup->rs.path.pts = rs.path.pts;
dup->flag |= RenderUpdateFlag::Path;
}
dup->flag |= RenderUpdateFlag::Path;
//Stroke
if (rs.stroke) {

View file

@ -67,7 +67,7 @@ Result SwCanvas::mempool(MempoolPolicy policy) noexcept
if (!renderer) return Result::MemoryCorruption;
//It can't change the policy during the running.
if (Canvas::pImpl->paints.count > 0) return Result::InsufficientCondition;
if (!Canvas::pImpl->paints.empty()) return Result::InsufficientCondition;
if (policy == MempoolPolicy::Individual) renderer->mempool(false);
else renderer->mempool(true);

View file

@ -85,3 +85,4 @@ private:
}
#endif //_TVG_TASK_SCHEDULER_H_

View file

@ -1,120 +0,0 @@
/*
* Copyright (c) 2020 - 2023 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 "tvgLoader.h"
#include "tvgPngLoader.h"
/************************************************************************/
/* Internal Class Implementation */
/************************************************************************/
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
PngLoader::PngLoader()
{
image = static_cast<png_imagep>(calloc(1, sizeof(png_image)));
image->version = PNG_IMAGE_VERSION;
image->opaque = NULL;
}
PngLoader::~PngLoader()
{
if (content) {
free((void*)content);
content = nullptr;
}
free(image);
}
bool PngLoader::open(const string& path)
{
image->opaque = NULL;
if (!png_image_begin_read_from_file(image, path.c_str())) return false;
w = (float)image->width;
h = (float)image->height;
cs = ColorSpace::ARGB8888;
return true;
}
bool PngLoader::open(const char* data, uint32_t size, bool copy)
{
image->opaque = NULL;
if (!png_image_begin_read_from_memory(image, data, size)) return false;
w = (float)image->width;
h = (float)image->height;
cs = ColorSpace::ARGB8888;
return true;
}
bool PngLoader::read()
{
png_bytep buffer;
image->format = PNG_FORMAT_BGRA;
buffer = static_cast<png_bytep>(malloc(PNG_IMAGE_SIZE((*image))));
if (!buffer) {
//out of memory, only time when libpng doesnt free its data
png_image_free(image);
return false;
}
if (!png_image_finish_read(image, NULL, buffer, 0, NULL)) {
free(buffer);
return false;
}
content = reinterpret_cast<uint32_t*>(buffer);
return true;
}
bool PngLoader::close()
{
png_image_free(image);
return true;
}
unique_ptr<Surface> PngLoader::bitmap()
{
if (!content) return nullptr;
//TODO: It's better to keep this surface instance in the loader side
auto surface = new Surface;
surface->buf32 = content;
surface->stride = w;
surface->w = w;
surface->h = h;
surface->cs = cs;
surface->channelSize = sizeof(uint32_t);
surface->owner = true;
surface->premultiplied = false;
return unique_ptr<Surface>(surface);
}

View file

@ -1,143 +0,0 @@
/*
* Copyright (c) 2021 - 2023 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 <memory.h>
#include "tvgLoader.h"
#include "tvgJpgLoader.h"
/************************************************************************/
/* Internal Class Implementation */
/************************************************************************/
void JpgLoader::clear()
{
jpgdDelete(decoder);
if (freeData) free(data);
decoder = nullptr;
data = nullptr;
freeData = false;
}
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
JpgLoader::~JpgLoader()
{
jpgdDelete(decoder);
if (freeData) free(data);
free(image);
}
bool JpgLoader::open(const string& path)
{
clear();
int width, height;
decoder = jpgdHeader(path.c_str(), &width, &height);
if (!decoder) return false;
w = static_cast<float>(width);
h = static_cast<float>(height);
cs = ColorSpace::ARGB8888;
return true;
}
bool JpgLoader::open(const char* data, uint32_t size, bool copy)
{
clear();
if (copy) {
this->data = (char *) malloc(size);
if (!this->data) return false;
memcpy((char *)this->data, data, size);
freeData = true;
} else {
this->data = (char *) data;
freeData = false;
}
int width, height;
decoder = jpgdHeader(this->data, size, &width, &height);
if (!decoder) return false;
w = static_cast<float>(width);
h = static_cast<float>(height);
cs = ColorSpace::ARGB8888;
return true;
}
bool JpgLoader::read()
{
if (!decoder || w <= 0 || h <= 0) return false;
TaskScheduler::request(this);
return true;
}
bool JpgLoader::close()
{
this->done();
clear();
return true;
}
unique_ptr<Surface> JpgLoader::bitmap()
{
this->done();
if (!image) return nullptr;
//TODO: It's better to keep this surface instance in the loader side
auto surface = new Surface;
surface->buf8 = image;
surface->stride = static_cast<uint32_t>(w);
surface->w = static_cast<uint32_t>(w);
surface->h = static_cast<uint32_t>(h);
surface->cs = cs;
surface->channelSize = sizeof(uint32_t);
surface->premultiplied = true;
surface->owner = true;
return unique_ptr<Surface>(surface);
}
void JpgLoader::run(unsigned tid)
{
if (image) {
free(image);
image = nullptr;
}
image = jpgdDecompress(decoder);
}

View file

@ -1,52 +0,0 @@
/*
* Copyright (c) 2021 - 2023 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.
*/
#ifndef _TVG_JPG_LOADER_H_
#define _TVG_JPG_LOADER_H_
#include "tvgTaskScheduler.h"
#include "tvgJpgd.h"
class JpgLoader : public LoadModule, public Task
{
private:
jpeg_decoder* decoder = nullptr;
char* data = nullptr;
unsigned char *image = nullptr;
bool freeData = false;
void clear();
public:
~JpgLoader();
using LoadModule::open;
bool open(const string& path) override;
bool open(const char* data, uint32_t size, bool copy) override;
bool read() override;
bool close() override;
unique_ptr<Surface> bitmap() override;
void run(unsigned tid) override;
};
#endif //_TVG_JPG_LOADER_H_

File diff suppressed because it is too large Load diff

View file

@ -1,35 +0,0 @@
/*
* Copyright (c) 2021 - 2023 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.
*/
// jpgd.h - C++ class for JPEG decompression.
// Public domain, Rich Geldreich <richgel99@gmail.com>
#ifndef _TVG_JPGD_H_
#define _TVG_JPGD_H_
class jpeg_decoder;
jpeg_decoder* jpgdHeader(const char* data, int size, int* width, int* height);
jpeg_decoder* jpgdHeader(const char* filename, int* width, int* height);
unsigned char* jpgdDecompress(jpeg_decoder* decoder);
void jpgdDelete(jpeg_decoder* decoder);
#endif //_TVG_JPGD_H_

View file

@ -28,17 +28,30 @@
/* Internal Class Implementation */
/************************************************************************/
static bool _isImportanceApplicable(SvgStyleFlags &toFlagsImportance, SvgStyleFlags fromFlagsImportance, SvgStyleFlags flag)
{
if (!(toFlagsImportance & flag) && (fromFlagsImportance & flag)) {
return true;
}
return false;
}
static void _copyStyle(SvgStyleProperty* to, const SvgStyleProperty* from)
{
if (from == nullptr) return;
//Copy the properties of 'from' only if they were explicitly set (not the default ones).
if (from->curColorSet && !(to->flags & SvgStyleFlags::Color)) {
if ((from->curColorSet && !(to->flags & SvgStyleFlags::Color)) ||
_isImportanceApplicable(to->flagsImportance, from->flagsImportance, SvgStyleFlags::Color)) {
to->color = from->color;
to->curColorSet = true;
to->flags = (to->flags | SvgStyleFlags::Color);
if (from->flagsImportance & SvgStyleFlags::Color) {
to->flagsImportance = (to->flagsImportance | SvgStyleFlags::Color);
}
}
//Fill
if ((from->fill.flags & SvgFillFlags::Paint) && !(to->flags & SvgStyleFlags::Fill)) {
if (((from->fill.flags & SvgFillFlags::Paint) && !(to->flags & SvgStyleFlags::Fill)) ||
_isImportanceApplicable(to->flagsImportance, from->flagsImportance, SvgStyleFlags::Fill)) {
to->fill.paint.color = from->fill.paint.color;
to->fill.paint.none = from->fill.paint.none;
to->fill.paint.curColor = from->fill.paint.curColor;
@ -48,19 +61,31 @@ static void _copyStyle(SvgStyleProperty* to, const SvgStyleProperty* from)
}
to->fill.flags = (to->fill.flags | SvgFillFlags::Paint);
to->flags = (to->flags | SvgStyleFlags::Fill);
if (from->flagsImportance & SvgStyleFlags::Fill) {
to->flagsImportance = (to->flagsImportance | SvgStyleFlags::Fill);
}
}
if ((from->fill.flags & SvgFillFlags::Opacity) && !(to->flags & SvgStyleFlags::FillOpacity)) {
if (((from->fill.flags & SvgFillFlags::Opacity) && !(to->flags & SvgStyleFlags::FillOpacity)) ||
_isImportanceApplicable(to->flagsImportance, from->flagsImportance, SvgStyleFlags::FillOpacity)) {
to->fill.opacity = from->fill.opacity;
to->fill.flags = (to->fill.flags | SvgFillFlags::Opacity);
to->flags = (to->flags | SvgStyleFlags::FillOpacity);
if (from->flagsImportance & SvgStyleFlags::FillOpacity) {
to->flagsImportance = (to->flagsImportance | SvgStyleFlags::FillOpacity);
}
}
if ((from->fill.flags & SvgFillFlags::FillRule) && !(to->flags & SvgStyleFlags::FillRule)) {
if (((from->fill.flags & SvgFillFlags::FillRule) && !(to->flags & SvgStyleFlags::FillRule)) ||
_isImportanceApplicable(to->flagsImportance, from->flagsImportance, SvgStyleFlags::FillRule)) {
to->fill.fillRule = from->fill.fillRule;
to->fill.flags = (to->fill.flags | SvgFillFlags::FillRule);
to->flags = (to->flags | SvgStyleFlags::FillRule);
if (from->flagsImportance & SvgStyleFlags::FillRule) {
to->flagsImportance = (to->flagsImportance | SvgStyleFlags::FillRule);
}
}
//Stroke
if ((from->stroke.flags & SvgStrokeFlags::Paint) && !(to->flags & SvgStyleFlags::Stroke)) {
if (((from->stroke.flags & SvgStrokeFlags::Paint) && !(to->flags & SvgStyleFlags::Stroke)) ||
_isImportanceApplicable(to->flagsImportance, from->flagsImportance, SvgStyleFlags::Stroke)) {
to->stroke.paint.color = from->stroke.paint.color;
to->stroke.paint.none = from->stroke.paint.none;
to->stroke.paint.curColor = from->stroke.paint.curColor;
@ -70,18 +95,30 @@ static void _copyStyle(SvgStyleProperty* to, const SvgStyleProperty* from)
}
to->stroke.flags = (to->stroke.flags | SvgStrokeFlags::Paint);
to->flags = (to->flags | SvgStyleFlags::Stroke);
if (from->flagsImportance & SvgStyleFlags::Stroke) {
to->flagsImportance = (to->flagsImportance | SvgStyleFlags::Stroke);
}
}
if ((from->stroke.flags & SvgStrokeFlags::Opacity) && !(to->flags & SvgStyleFlags::StrokeOpacity)) {
if (((from->stroke.flags & SvgStrokeFlags::Opacity) && !(to->flags & SvgStyleFlags::StrokeOpacity)) ||
_isImportanceApplicable(to->flagsImportance, from->flagsImportance, SvgStyleFlags::StrokeOpacity)) {
to->stroke.opacity = from->stroke.opacity;
to->stroke.flags = (to->stroke.flags | SvgStrokeFlags::Opacity);
to->flags = (to->flags | SvgStyleFlags::StrokeOpacity);
if (from->flagsImportance & SvgStyleFlags::StrokeOpacity) {
to->flagsImportance = (to->flagsImportance | SvgStyleFlags::StrokeOpacity);
}
}
if ((from->stroke.flags & SvgStrokeFlags::Width) && !(to->flags & SvgStyleFlags::StrokeWidth)) {
if (((from->stroke.flags & SvgStrokeFlags::Width) && !(to->flags & SvgStyleFlags::StrokeWidth)) ||
_isImportanceApplicable(to->flagsImportance, from->flagsImportance, SvgStyleFlags::StrokeWidth)) {
to->stroke.width = from->stroke.width;
to->stroke.flags = (to->stroke.flags | SvgStrokeFlags::Width);
to->flags = (to->flags | SvgStyleFlags::StrokeWidth);
if (from->flagsImportance & SvgStyleFlags::StrokeWidth) {
to->flagsImportance = (to->flagsImportance | SvgStyleFlags::StrokeWidth);
}
}
if ((from->stroke.flags & SvgStrokeFlags::Dash) && !(to->flags & SvgStyleFlags::StrokeDashArray)) {
if (((from->stroke.flags & SvgStrokeFlags::Dash) && !(to->flags & SvgStyleFlags::StrokeDashArray)) ||
_isImportanceApplicable(to->flagsImportance, from->flagsImportance, SvgStyleFlags::StrokeDashArray)) {
if (from->stroke.dash.array.count > 0) {
to->stroke.dash.array.clear();
to->stroke.dash.array.reserve(from->stroke.dash.array.count);
@ -90,23 +127,38 @@ static void _copyStyle(SvgStyleProperty* to, const SvgStyleProperty* from)
}
to->stroke.flags = (to->stroke.flags | SvgStrokeFlags::Dash);
to->flags = (to->flags | SvgStyleFlags::StrokeDashArray);
if (from->flagsImportance & SvgStyleFlags::StrokeDashArray) {
to->flagsImportance = (to->flagsImportance | SvgStyleFlags::StrokeDashArray);
}
}
}
if ((from->stroke.flags & SvgStrokeFlags::Cap) && !(to->flags & SvgStyleFlags::StrokeLineCap)) {
if (((from->stroke.flags & SvgStrokeFlags::Cap) && !(to->flags & SvgStyleFlags::StrokeLineCap)) ||
_isImportanceApplicable(to->flagsImportance, from->flagsImportance, SvgStyleFlags::StrokeLineCap)) {
to->stroke.cap = from->stroke.cap;
to->stroke.flags = (to->stroke.flags | SvgStrokeFlags::Cap);
to->flags = (to->flags | SvgStyleFlags::StrokeLineCap);
if (from->flagsImportance & SvgStyleFlags::StrokeLineCap) {
to->flagsImportance = (to->flagsImportance | SvgStyleFlags::StrokeLineCap);
}
}
if ((from->stroke.flags & SvgStrokeFlags::Join) && !(to->flags & SvgStyleFlags::StrokeLineJoin)) {
if (((from->stroke.flags & SvgStrokeFlags::Join) && !(to->flags & SvgStyleFlags::StrokeLineJoin)) ||
_isImportanceApplicable(to->flagsImportance, from->flagsImportance, SvgStyleFlags::StrokeLineJoin)) {
to->stroke.join = from->stroke.join;
to->stroke.flags = (to->stroke.flags | SvgStrokeFlags::Join);
to->flags = (to->flags | SvgStyleFlags::StrokeLineJoin);
if (from->flagsImportance & SvgStyleFlags::StrokeLineJoin) {
to->flagsImportance = (to->flagsImportance | SvgStyleFlags::StrokeLineJoin);
}
}
//Opacity
//TODO: it can be set to be 255 and shouldn't be changed by attribute 'opacity'
if (from->opacity < 255 && !(to->flags & SvgStyleFlags::Opacity)) {
if ((from->opacity < 255 && !(to->flags & SvgStyleFlags::Opacity)) ||
_isImportanceApplicable(to->flagsImportance, from->flagsImportance, SvgStyleFlags::Opacity)) {
to->opacity = from->opacity;
to->flags = (to->flags | SvgStyleFlags::Opacity);
if (from->flagsImportance & SvgStyleFlags::Opacity) {
to->flagsImportance = (to->flagsImportance | SvgStyleFlags::Opacity);
}
}
}

View file

@ -392,15 +392,8 @@ static char* _idFromUrl(const char* url)
int i = 0;
while (url[i] > ' ' && url[i] != ')' && url[i] != '\'') ++i;
//custom strndup() for portability
int len = strlen(url);
if (i < len) len = i;
auto ret = (char*) malloc(len + 1);
if (!ret) return 0;
ret[len] = '\0';
return (char*) memcpy(ret, url, len);
return svgUtilStrndup(url, i);
}
@ -983,6 +976,22 @@ static void _handleStrokeLineJoinAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode*
node->style->stroke.join = _toLineJoin(value);
}
static void _handleStrokeMiterlimitAttr(SvgLoaderData* loader, SvgNode* node, const char* value)
{
char* end = nullptr;
const float miterlimit = svgUtilStrtof(value, &end);
// https://www.w3.org/TR/SVG2/painting.html#LineJoin
// - A negative value for stroke-miterlimit must be treated as an illegal value.
if (miterlimit < 0.0f) {
TVGERR("SVG", "A stroke-miterlimit change (%f <- %f) with a negative value is omitted.",
node->style->stroke.miterlimit, miterlimit);
return;
}
node->style->stroke.flags = (node->style->stroke.flags | SvgStrokeFlags::Miterlimit);
node->style->stroke.miterlimit = miterlimit;
}
static void _handleFillRuleAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value)
{
@ -1099,6 +1108,7 @@ static constexpr struct
STYLE_DEF(stroke, Stroke, SvgStyleFlags::Stroke),
STYLE_DEF(stroke-width, StrokeWidth, SvgStyleFlags::StrokeWidth),
STYLE_DEF(stroke-linejoin, StrokeLineJoin, SvgStyleFlags::StrokeLineJoin),
STYLE_DEF(stroke-miterlimit, StrokeMiterlimit, SvgStyleFlags::StrokeMiterlimit),
STYLE_DEF(stroke-linecap, StrokeLineCap, SvgStyleFlags::StrokeLineCap),
STYLE_DEF(stroke-opacity, StrokeOpacity, SvgStyleFlags::StrokeOpacity),
STYLE_DEF(stroke-dasharray, StrokeDashArray, SvgStyleFlags::StrokeDashArray),
@ -1125,12 +1135,27 @@ static bool _parseStyleAttr(void* data, const char* key, const char* value, bool
sz = strlen(key);
for (unsigned int i = 0; i < sizeof(styleTags) / sizeof(styleTags[0]); i++) {
if (styleTags[i].sz - 1 == sz && !strncmp(styleTags[i].tag, key, sz)) {
bool importance = false;
if (auto ptr = strstr(value, "!important")) {
size_t size = ptr - value;
while (size > 0 && isspace(value[size - 1])) {
size--;
}
value = svgUtilStrndup(value, size);
importance = true;
}
if (style) {
styleTags[i].tagHandler(loader, node, value);
node->style->flags = (node->style->flags | styleTags[i].flag);
if (importance || !(node->style->flagsImportance & styleTags[i].flag)) {
styleTags[i].tagHandler(loader, node, value);
node->style->flags = (node->style->flags | styleTags[i].flag);
}
} else if (!(node->style->flags & styleTags[i].flag)) {
styleTags[i].tagHandler(loader, node, value);
}
if (importance) {
node->style->flagsImportance = (node->style->flags | styleTags[i].flag);
free(const_cast<char*>(value));
}
return true;
}
}
@ -1307,6 +1332,7 @@ static SvgNode* _createNode(SvgNode* parent, SvgNodeType type)
node->style->stroke.cap = StrokeCap::Butt;
//Default line join is miter
node->style->stroke.join = StrokeJoin::Miter;
node->style->stroke.miterlimit = 4.0f;
node->style->stroke.scale = 1.0;
node->style->paintOrder = _toPaintOrder("fill stroke");
@ -1593,39 +1619,11 @@ static SvgNode* _createEllipseNode(SvgLoaderData* loader, SvgNode* parent, const
}
static bool _attrParsePolygonPoints(const char* str, float** points, int* ptCount)
static bool _attrParsePolygonPoints(const char* str, SvgPolygonNode* polygon)
{
float tmp[50];
int tmpCount = 0;
int count = 0;
float num;
float *pointArray = nullptr, *tmpArray;
while (_parseNumber(&str, &num)) {
tmp[tmpCount++] = num;
if (tmpCount == 50) {
tmpArray = (float*)realloc(pointArray, (count + tmpCount) * sizeof(float));
if (!tmpArray) goto error_alloc;
pointArray = tmpArray;
memcpy(&pointArray[count], tmp, tmpCount * sizeof(float));
count += tmpCount;
tmpCount = 0;
}
}
if (tmpCount > 0) {
tmpArray = (float*)realloc(pointArray, (count + tmpCount) * sizeof(float));
if (!tmpArray) goto error_alloc;
pointArray = tmpArray;
memcpy(&pointArray[count], tmp, tmpCount * sizeof(float));
count += tmpCount;
}
*ptCount = count;
*points = pointArray;
while (_parseNumber(&str, &num)) polygon->pts.push(num);
return true;
error_alloc:
return false;
}
@ -1642,7 +1640,7 @@ static bool _attrParsePolygonNode(void* data, const char* key, const char* value
else polygon = &(node->node.polyline);
if (!strcmp(key, "points")) {
return _attrParsePolygonPoints(value, &polygon->points, &polygon->pointsCount);
return _attrParsePolygonPoints(value, polygon);
} else if (!strcmp(key, "style")) {
return simpleXmlParseW3CAttribute(value, strlen(value), _parseStyleAttr, loader);
} else if (!strcmp(key, "clip-path")) {
@ -1903,6 +1901,7 @@ static SvgNode* _getDefsNode(SvgNode* node)
}
if (node->type == SvgNodeType::Doc) return node->node.doc.defs;
if (node->type == SvgNodeType::Defs) return node;
return nullptr;
}
@ -2680,7 +2679,7 @@ static void _inheritGradient(SvgLoaderData* loader, SvgStyleGradient* to, SvgSty
}
}
if (to->stops.count == 0) _cloneGradStops(to->stops, from->stops);
if (to->stops.empty()) _cloneGradStops(to->stops, from->stops);
}
@ -2784,6 +2783,9 @@ static void _styleInherit(SvgStyleProperty* child, const SvgStyleProperty* paren
if (!(child->stroke.flags & SvgStrokeFlags::Join)) {
child->stroke.join = parent->stroke.join;
}
if (!(child->stroke.flags & SvgStrokeFlags::Miterlimit)) {
child->stroke.miterlimit = parent->stroke.miterlimit;
}
}
@ -2847,6 +2849,10 @@ static void _styleCopy(SvgStyleProperty* to, const SvgStyleProperty* from)
if (from->stroke.flags & SvgStrokeFlags::Join) {
to->stroke.join = from->stroke.join;
}
if (from->stroke.flags & SvgStrokeFlags::Miterlimit) {
to->stroke.miterlimit = from->stroke.miterlimit;
}
}
@ -2910,16 +2916,14 @@ static void _copyAttr(SvgNode* to, const SvgNode* from)
break;
}
case SvgNodeType::Polygon: {
if ((to->node.polygon.pointsCount = from->node.polygon.pointsCount)) {
to->node.polygon.points = (float*)malloc(to->node.polygon.pointsCount * sizeof(float));
memcpy(to->node.polygon.points, from->node.polygon.points, to->node.polygon.pointsCount * sizeof(float));
if ((to->node.polygon.pts.count = from->node.polygon.pts.count)) {
to->node.polygon.pts = from->node.polygon.pts;
}
break;
}
case SvgNodeType::Polyline: {
if ((to->node.polyline.pointsCount = from->node.polyline.pointsCount)) {
to->node.polyline.points = (float*)malloc(to->node.polyline.pointsCount * sizeof(float));
memcpy(to->node.polyline.points, from->node.polyline.points, to->node.polyline.pointsCount * sizeof(float));
if ((to->node.polyline.pts.count = from->node.polyline.pts.count)) {
to->node.polyline.pts = from->node.polyline.pts;
}
break;
}
@ -2934,6 +2938,16 @@ static void _copyAttr(SvgNode* to, const SvgNode* from)
}
break;
}
case SvgNodeType::Use: {
to->node.use.x = from->node.use.x;
to->node.use.y = from->node.use.y;
to->node.use.w = from->node.use.w;
to->node.use.h = from->node.use.h;
to->node.use.isWidthSet = from->node.use.isWidthSet;
to->node.use.isHeightSet = from->node.use.isHeightSet;
to->node.use.symbol = from->node.use.symbol;
break;
}
default: {
break;
}
@ -3200,7 +3214,7 @@ static void _inefficientNodeCheck(TVG_UNUSED SvgNode* node)
}
case SvgNodeType::Polygon:
case SvgNodeType::Polyline: {
if (node->node.polygon.pointsCount < 2) TVGLOG("SVG", "Inefficient elements used [Invalid Polygon][Node Type : %s]", type);
if (node->node.polygon.pts.count < 2) TVGLOG("SVG", "Inefficient elements used [Invalid Polygon][Node Type : %s]", type);
break;
}
case SvgNodeType::Circle: {
@ -3356,11 +3370,11 @@ static void _freeNode(SvgNode* node)
break;
}
case SvgNodeType::Polygon: {
free(node->node.polygon.points);
free(node->node.polygon.pts.data);
break;
}
case SvgNodeType::Polyline: {
free(node->node.polyline.points);
free(node->node.polyline.pts.data);
break;
}
case SvgNodeType::Doc: {
@ -3497,6 +3511,7 @@ void SvgLoader::run(unsigned tid)
if (defs) _updateComposite(loaderData.doc, defs);
_updateStyle(loaderData.doc, nullptr);
if (defs) _updateStyle(defs, nullptr);
if (loaderData.gradients.count > 0) _updateGradient(&loaderData, loaderData.doc, &loaderData.gradients);
if (defs) _updateGradient(&loaderData, loaderData.doc, &defs->node.defs.gradients);
@ -3683,6 +3698,5 @@ bool SvgLoader::close()
unique_ptr<Paint> SvgLoader::paint()
{
this->done();
if (root) return move(root);
else return nullptr;
return std::move(root);
}

View file

@ -48,12 +48,17 @@ public:
bool resize(Paint* paint, float w, float h) override;
bool read() override;
bool close() override;
unique_ptr<Paint> paint() override;
private:
SvgViewFlag viewFlag = SvgViewFlag::None;
AspectRatioAlign align = AspectRatioAlign::XMidYMid;
AspectRatioMeetOrSlice meetOrSlice = AspectRatioMeetOrSlice::Meet;
float vx = 0;
float vy = 0;
float vw = 0;
float vh = 0;
bool header();
void clear();

View file

@ -100,6 +100,7 @@ enum class SvgStrokeFlags
Cap = 0x20,
Join = 0x40,
Dash = 0x80,
Miterlimit = 0x100
};
constexpr bool operator &(SvgStrokeFlags a, SvgStrokeFlags b)
@ -137,7 +138,8 @@ enum class SvgStyleFlags
Mask = 0x2000,
MaskType = 0x4000,
Display = 0x8000,
PaintOrder = 0x10000
PaintOrder = 0x10000,
StrokeMiterlimit = 0x20000
};
constexpr bool operator &(SvgStyleFlags a, SvgStyleFlags b)
@ -351,8 +353,7 @@ struct SvgPathNode
struct SvgPolygonNode
{
int pointsCount;
float* points;
Array<float> pts;
};
struct SvgClipNode
@ -466,6 +467,7 @@ struct SvgStyleStroke
float centered;
StrokeCap cap;
StrokeJoin join;
float miterlimit;
SvgDash dash;
int dashCount;
};
@ -482,6 +484,7 @@ struct SvgStyleProperty
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)
};
struct SvgNode
@ -539,7 +542,7 @@ struct SvgNodeIdPair
struct SvgLoaderData
{
Array<SvgNode*> stack = {nullptr, 0, 0};
Array<SvgNode*> stack;
SvgNode* doc = nullptr;
SvgNode* def = nullptr;
SvgNode* cssStyle = nullptr;

View file

@ -263,7 +263,7 @@ static void _applyComposition(Paint* paint, const SvgNode* node, const Box& vBox
comp->transform(m);
}
if (valid) paint->composite(move(comp), CompositeMethod::ClipPath);
if (valid) paint->composite(std::move(comp), CompositeMethod::ClipPath);
node->style->clipPath.applying = false;
}
@ -285,9 +285,9 @@ static void _applyComposition(Paint* paint, const SvgNode* node, const Box& vBox
if (node->transform) comp->transform(*node->transform);
if (compNode->node.mask.type == SvgMaskType::Luminance && !isMaskWhite) {
paint->composite(move(comp), CompositeMethod::LumaMask);
paint->composite(std::move(comp), CompositeMethod::LumaMask);
} else {
paint->composite(move(comp), CompositeMethod::AlphaMask);
paint->composite(std::move(comp), CompositeMethod::AlphaMask);
}
}
@ -313,10 +313,10 @@ static void _applyProperty(SvgNode* node, Shape* vg, const Box& vBox, const stri
if (style->fill.paint.gradient->type == SvgGradientType::Linear) {
auto linear = _applyLinearGradientProperty(style->fill.paint.gradient, vg, bBox, style->fill.opacity);
vg->fill(move(linear));
vg->fill(std::move(linear));
} else if (style->fill.paint.gradient->type == SvgGradientType::Radial) {
auto radial = _applyRadialGradientProperty(style->fill.paint.gradient, vg, bBox, style->fill.opacity);
vg->fill(move(radial));
vg->fill(std::move(radial));
}
} else if (style->fill.paint.url) {
//TODO: Apply the color pointed by url
@ -342,6 +342,7 @@ static void _applyProperty(SvgNode* node, Shape* vg, const Box& vBox, const stri
vg->stroke(style->stroke.width);
vg->stroke(style->stroke.cap);
vg->stroke(style->stroke.join);
vg->strokeMiterlimit(style->stroke.miterlimit);
if (style->stroke.dash.array.count > 0) {
vg->stroke(style->stroke.dash.array.data, style->stroke.dash.array.count);
}
@ -355,10 +356,10 @@ static void _applyProperty(SvgNode* node, Shape* vg, const Box& vBox, const stri
if (style->stroke.paint.gradient->type == SvgGradientType::Linear) {
auto linear = _applyLinearGradientProperty(style->stroke.paint.gradient, vg, bBox, style->stroke.opacity);
vg->stroke(move(linear));
vg->stroke(std::move(linear));
} else if (style->stroke.paint.gradient->type == SvgGradientType::Radial) {
auto radial = _applyRadialGradientProperty(style->stroke.paint.gradient, vg, bBox, style->stroke.opacity);
vg->stroke(move(radial));
vg->stroke(std::move(radial));
}
} else if (style->stroke.paint.url) {
//TODO: Apply the color pointed by url
@ -401,19 +402,21 @@ static bool _appendShape(SvgNode* node, Shape* shape, const Box& vBox, const str
break;
}
case SvgNodeType::Polygon: {
if (node->node.polygon.pointsCount < 2) break;
shape->moveTo(node->node.polygon.points[0], node->node.polygon.points[1]);
for (int i = 2; i < node->node.polygon.pointsCount - 1; i += 2) {
shape->lineTo(node->node.polygon.points[i], node->node.polygon.points[i + 1]);
if (node->node.polygon.pts.count < 2) break;
auto pts = node->node.polygon.pts.data;
shape->moveTo(pts[0], pts[1]);
for (pts += 2; pts < node->node.polygon.pts.end(); pts += 2) {
shape->lineTo(pts[0], pts[1]);
}
shape->close();
break;
}
case SvgNodeType::Polyline: {
if (node->node.polygon.pointsCount < 2) break;
shape->moveTo(node->node.polygon.points[0], node->node.polygon.points[1]);
for (int i = 2; i < node->node.polygon.pointsCount - 1; i += 2) {
shape->lineTo(node->node.polygon.points[i], node->node.polygon.points[i + 1]);
if (node->node.polyline.pts.count < 2) break;
auto pts = node->node.polyline.pts.data;
shape->moveTo(pts[0], pts[1]);
for (pts += 2; pts < node->node.polyline.pts.end(); pts += 2) {
shape->lineTo(pts[0], pts[1]);
}
break;
}
@ -676,7 +679,7 @@ static unique_ptr<Scene> _useBuildHelper(const SvgNode* node, const Box& vBox, c
scene->transform(mSceneTransform);
if (node->node.use.symbol->node.symbol.overflowVisible) {
finalScene = move(scene);
finalScene = std::move(scene);
} else {
auto viewBoxClip = Shape::gen();
viewBoxClip->appendRect(0, 0, width, height, 0, 0);
@ -689,17 +692,17 @@ static unique_ptr<Scene> _useBuildHelper(const SvgNode* node, const Box& vBox, c
viewBoxClip->transform(mClipTransform);
auto compositeLayer = Scene::gen();
compositeLayer->composite(move(viewBoxClip), CompositeMethod::ClipPath);
compositeLayer->push(move(scene));
compositeLayer->composite(std::move(viewBoxClip), CompositeMethod::ClipPath);
compositeLayer->push(std::move(scene));
auto root = Scene::gen();
root->push(move(compositeLayer));
root->push(std::move(compositeLayer));
finalScene = move(root);
finalScene = std::move(root);
}
} else {
if (!mathIdentity((const Matrix*)(&mUseTransform))) scene->transform(mUseTransform);
finalScene = move(scene);
finalScene = std::move(scene);
}
return finalScene;
@ -731,7 +734,7 @@ static unique_ptr<Scene> _sceneBuildHelper(const SvgNode* node, const Box& vBox,
} else if ((*child)->type == SvgNodeType::Image) {
auto image = _imageBuildHelper(*child, vBox, svgPath);
if (image) {
scene->push(move(image));
scene->push(std::move(image));
if (isMaskWhite) *isMaskWhite = false;
}
} else if ((*child)->type != SvgNodeType::Mask) {
@ -739,13 +742,13 @@ static unique_ptr<Scene> _sceneBuildHelper(const SvgNode* node, const Box& vBox,
if (shape) {
if (isMaskWhite) {
uint8_t r, g, b;
shape->fillColor(&r, &g, &b, nullptr);
shape->fillColor(&r, &g, &b);
if (shape->fill() || r < 255 || g < 255 || b < 255 || shape->strokeFill() ||
(shape->strokeColor(&r, &g, &b, nullptr) == Result::Success && (r < 255 || g < 255 || b < 255))) {
(shape->strokeColor(&r, &g, &b) == Result::Success && (r < 255 || g < 255 || b < 255))) {
*isMaskWhite = false;
}
}
scene->push(move(shape));
scene->push(std::move(shape));
}
}
}
@ -801,14 +804,14 @@ unique_ptr<Scene> svgSceneBuild(SvgLoaderData& loaderData, Box vBox, float w, fl
auto viewBoxClip = Shape::gen();
viewBoxClip->appendRect(0, 0, w, h, 0, 0);
viewBoxClip->fill(0, 0, 0, 255);
viewBoxClip->fill(0, 0, 0);
auto compositeLayer = Scene::gen();
compositeLayer->composite(move(viewBoxClip), CompositeMethod::ClipPath);
compositeLayer->push(move(docNode));
compositeLayer->composite(std::move(viewBoxClip), CompositeMethod::ClipPath);
compositeLayer->push(std::move(docNode));
auto root = Scene::gen();
root->push(move(compositeLayer));
root->push(std::move(compositeLayer));
loaderData.doc->node.doc.vx = vBox.x;
loaderData.doc->node.doc.vy = vBox.y;

View file

@ -275,3 +275,16 @@ string svgUtilBase64Decode(const char *src)
}
return decoded;
}
char* svgUtilStrndup(const char* str, size_t n)
{
auto len = strlen(str);
if (len < n) n = len;
auto ret = (char*)malloc(n + 1);
if (!ret) return nullptr;
ret[n] = '\0';
return (char*)memcpy(ret, str, n);
}

View file

@ -30,4 +30,6 @@ float svgUtilStrtof(const char *nPtr, char **endPtr);
string svgUtilURLDecode(const char *src);
string svgUtilBase64Decode(const char *src);
char* svgUtilStrndup(const char* str, size_t n);
#endif //_TVG_SVG_UTIL_H_

View file

@ -33,6 +33,7 @@
#endif
#include "tvgXmlParser.h"
#include "tvgSvgUtil.h"
/************************************************************************/
/* Internal Class Implementation */
@ -238,14 +239,6 @@ static SimpleXMLType _getXMLType(const char* itr, const char* itrEnd, size_t &to
}
static char* _strndup(const char* src, unsigned len)
{
auto ret = (char*)malloc(len + 1);
if (!ret) return nullptr;
ret[len] = '\0';
return (char*)memcpy(ret, src, len);
}
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
@ -564,10 +557,10 @@ const char* simpleXmlParseCSSAttribute(const char* buf, unsigned bufLength, char
}
if (p == itr) *tag = strdup("all");
else *tag = _strndup(itr, p - itr);
else *tag = svgUtilStrndup(itr, p - itr);
if (p == itrEnd) *name = nullptr;
else *name = _strndup(p + 1, itrEnd - p - 1);
else *name = svgUtilStrndup(p + 1, itrEnd - p - 1);
return (nextElement ? nextElement + 1 : nullptr);
}

View file

@ -1,471 +0,0 @@
/*
* Copyright (c) 2021 - 2023 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 <memory.h>
#ifdef _WIN32
#include <malloc.h>
#elif defined(__linux__)
#include <alloca.h>
#else
#include <stdlib.h>
#endif
#include "tvgTvgCommon.h"
/************************************************************************/
/* Internal Class Implementation */
/************************************************************************/
struct TvgBinBlock
{
TvgBinTag type;
TvgBinCounter length;
const char* data;
const char* end;
};
static Paint* _parsePaint(TvgBinBlock baseBlock);
static TvgBinBlock _readBlock(const char *ptr)
{
TvgBinBlock block;
block.type = *ptr;
READ_UI32(&block.length, ptr + SIZE(TvgBinTag));
block.data = ptr + SIZE(TvgBinTag) + SIZE(TvgBinCounter);
block.end = block.data + block.length;
return block;
}
static bool _parseCmpTarget(const char *ptr, const char *end, Paint *paint)
{
auto block = _readBlock(ptr);
if (block.end > end) return false;
if (block.type != TVG_TAG_PAINT_CMP_METHOD) return false;
if (block.length != SIZE(TvgBinFlag)) return false;
auto cmpMethod = static_cast<CompositeMethod>(*block.data);
ptr = block.end;
auto cmpBlock = _readBlock(ptr);
if (cmpBlock.end > end) return false;
paint->composite(unique_ptr<Paint>(_parsePaint(cmpBlock)), cmpMethod);
return true;
}
static bool _parsePaintProperty(TvgBinBlock block, Paint *paint)
{
switch (block.type) {
case TVG_TAG_PAINT_OPACITY: {
if (block.length != SIZE(uint8_t)) return false;
paint->opacity(*block.data);
return true;
}
case TVG_TAG_PAINT_TRANSFORM: {
if (block.length != SIZE(Matrix)) return false;
Matrix matrix;
memcpy(&matrix, block.data, SIZE(Matrix));
paint->transform(matrix);
return true;
}
case TVG_TAG_PAINT_CMP_TARGET: {
if (block.length < SIZE(TvgBinTag) + SIZE(TvgBinCounter)) return false;
return _parseCmpTarget(block.data, block.end, paint);
}
}
return false;
}
static bool _parseScene(TvgBinBlock block, Paint *paint)
{
auto scene = static_cast<Scene*>(paint);
//Case1: scene reserve count
if (block.type == TVG_TAG_SCENE_RESERVEDCNT) {
if (block.length != SIZE(uint32_t)) return false;
uint32_t reservedCnt;
READ_UI32(&reservedCnt, block.data);
scene->reserve(reservedCnt);
return true;
}
//Case2: Base Paint Properties
if (_parsePaintProperty(block, scene)) return true;
//Case3: A Child paint
if (auto paint = _parsePaint(block)) {
scene->push(unique_ptr<Paint>(paint));
return true;
}
return false;
}
static bool _parseShapePath(const char *ptr, const char *end, Shape *shape)
{
uint32_t cmdCnt, ptsCnt;
READ_UI32(&cmdCnt, ptr);
ptr += SIZE(cmdCnt);
READ_UI32(&ptsCnt, ptr);
ptr += SIZE(ptsCnt);
auto cmds = (TvgBinFlag*) ptr;
ptr += SIZE(TvgBinFlag) * cmdCnt;
auto pts = (Point*) ptr;
ptr += SIZE(Point) * ptsCnt;
if (ptr > end) return false;
/* Recover to PathCommand(4 bytes) from TvgBinFlag(1 byte) */
PathCommand* inCmds = (PathCommand*)alloca(sizeof(PathCommand) * cmdCnt);
for (uint32_t i = 0; i < cmdCnt; ++i) {
inCmds[i] = static_cast<PathCommand>(cmds[i]);
}
shape->appendPath(inCmds, cmdCnt, pts, ptsCnt);
return true;
}
static unique_ptr<Fill> _parseShapeFill(const char *ptr, const char *end)
{
unique_ptr<Fill> fillGrad;
while (ptr < end) {
auto block = _readBlock(ptr);
if (block.end > end) return nullptr;
switch (block.type) {
case TVG_TAG_FILL_RADIAL_GRADIENT: {
if (block.length != 3 * SIZE(float)) return nullptr;
auto ptr = block.data;
float x, y, radius;
READ_FLOAT(&x, ptr);
ptr += SIZE(float);
READ_FLOAT(&y, ptr);
ptr += SIZE(float);
READ_FLOAT(&radius, ptr);
auto fillGradRadial = RadialGradient::gen();
fillGradRadial->radial(x, y, radius);
fillGrad = move(fillGradRadial);
break;
}
case TVG_TAG_FILL_LINEAR_GRADIENT: {
if (block.length != 4 * SIZE(float)) return nullptr;
auto ptr = block.data;
float x1, y1, x2, y2;
READ_FLOAT(&x1, ptr);
ptr += SIZE(float);
READ_FLOAT(&y1, ptr);
ptr += SIZE(float);
READ_FLOAT(&x2, ptr);
ptr += SIZE(float);
READ_FLOAT(&y2, ptr);
auto fillGradLinear = LinearGradient::gen();
fillGradLinear->linear(x1, y1, x2, y2);
fillGrad = move(fillGradLinear);
break;
}
case TVG_TAG_FILL_FILLSPREAD: {
if (!fillGrad) return nullptr;
if (block.length != SIZE(TvgBinFlag)) return nullptr;
fillGrad->spread((FillSpread) *block.data);
break;
}
case TVG_TAG_FILL_COLORSTOPS: {
if (!fillGrad) return nullptr;
if (block.length == 0 || block.length & 0x07) return nullptr;
uint32_t stopsCnt = block.length >> 3; // 8 bytes per ColorStop
if (stopsCnt > 1023) return nullptr;
Fill::ColorStop* stops = (Fill::ColorStop*)alloca(sizeof(Fill::ColorStop) * stopsCnt);
auto p = block.data;
for (uint32_t i = 0; i < stopsCnt; i++, p += 8) {
READ_FLOAT(&stops[i].offset, p);
stops[i].r = p[4];
stops[i].g = p[5];
stops[i].b = p[6];
stops[i].a = p[7];
}
fillGrad->colorStops(stops, stopsCnt);
break;
}
case TVG_TAG_FILL_TRANSFORM: {
if (!fillGrad || block.length != SIZE(Matrix)) return nullptr;
Matrix gradTransform;
memcpy(&gradTransform, block.data, SIZE(Matrix));
fillGrad->transform(gradTransform);
break;
}
default: {
TVGLOG("TVG", "Unsupported tag %d (0x%x) used as one of the fill properties, %d bytes skipped", block.type, block.type, block.length);
break;
}
}
ptr = block.end;
}
return fillGrad;
}
static bool _parseShapeStrokeDashPattern(const char *ptr, const char *end, Shape *shape)
{
uint32_t dashPatternCnt;
READ_UI32(&dashPatternCnt, ptr);
ptr += SIZE(uint32_t);
if (dashPatternCnt > 0) {
float* dashPattern = static_cast<float*>(malloc(sizeof(float) * dashPatternCnt));
if (!dashPattern) return false;
memcpy(dashPattern, ptr, sizeof(float) * dashPatternCnt);
ptr += SIZE(float) * dashPatternCnt;
if (ptr > end) {
free(dashPattern);
return false;
}
shape->stroke(dashPattern, dashPatternCnt);
free(dashPattern);
}
return true;
}
static bool _parseShapeStroke(const char *ptr, const char *end, Shape *shape)
{
while (ptr < end) {
auto block = _readBlock(ptr);
if (block.end > end) return false;
switch (block.type) {
case TVG_TAG_SHAPE_STROKE_CAP: {
if (block.length != SIZE(TvgBinFlag)) return false;
shape->stroke((StrokeCap) *block.data);
break;
}
case TVG_TAG_SHAPE_STROKE_JOIN: {
if (block.length != SIZE(TvgBinFlag)) return false;
shape->stroke((StrokeJoin) *block.data);
break;
}
case TVG_TAG_SHAPE_STROKE_WIDTH: {
if (block.length != SIZE(float)) return false;
float width;
READ_FLOAT(&width, block.data);
shape->stroke(width);
break;
}
case TVG_TAG_SHAPE_STROKE_COLOR: {
if (block.length != 4) return false;
shape->stroke(block.data[0], block.data[1], block.data[2], block.data[3]);
break;
}
case TVG_TAG_SHAPE_STROKE_FILL: {
auto fill = _parseShapeFill(block.data, block.end);
if (!fill) return false;
shape->stroke(move(move(fill)));
break;
}
case TVG_TAG_SHAPE_STROKE_DASHPTRN: {
if (!_parseShapeStrokeDashPattern(block.data, block.end, shape)) return false;
break;
}
default: {
TVGLOG("TVG", "Unsupported tag %d (0x%x) used as one of stroke properties, %d bytes skipped", block.type, block.type, block.length);
break;
}
}
ptr = block.end;
}
return true;
}
static bool _parseShape(TvgBinBlock block, Paint* paint)
{
auto shape = static_cast<Shape*>(paint);
//Case1: Shape specific properties
switch (block.type) {
case TVG_TAG_SHAPE_PATH: {
return _parseShapePath(block.data, block.end, shape);
}
case TVG_TAG_SHAPE_STROKE: {
return _parseShapeStroke(block.data, block.end, shape);
}
case TVG_TAG_SHAPE_FILL: {
auto fill = _parseShapeFill(block.data, block.end);
if (!fill) return false;
shape->fill(move(fill));
return true;
}
case TVG_TAG_SHAPE_COLOR: {
if (block.length != 4) return false;
shape->fill(block.data[0], block.data[1], block.data[2], block.data[3]);
return true;
}
case TVG_TAG_SHAPE_FILLRULE: {
if (block.length != SIZE(TvgBinFlag)) return false;
shape->fill((FillRule)*block.data);
return true;
}
}
//Case2: Base Paint Properties
return _parsePaintProperty(block, shape);
}
static bool _parsePicture(TvgBinBlock block, Paint* paint)
{
auto picture = static_cast<Picture*>(paint);
switch (block.type) {
case TVG_TAG_PICTURE_RAW_IMAGE: {
if (block.length < 2 * SIZE(uint32_t)) return false;
auto ptr = block.data;
uint32_t w, h;
READ_UI32(&w, ptr);
ptr += SIZE(uint32_t);
READ_UI32(&h, ptr);
ptr += SIZE(uint32_t);
auto size = w * h * SIZE(uint32_t);
if (block.length != 2 * SIZE(uint32_t) + size) return false;
picture->load((uint32_t*) ptr, w, h, true);
return true;
}
case TVG_TAG_PICTURE_MESH: {
if (block.length < 1 * SIZE(uint32_t)) return false;
auto ptr = block.data;
uint32_t meshCnt;
READ_UI32(&meshCnt, ptr);
ptr += SIZE(uint32_t);
auto size = meshCnt * SIZE(Polygon);
if (block.length != SIZE(uint32_t) + size) return false;
picture->mesh((Polygon*) ptr, meshCnt);
return true;
}
//Base Paint Properties
default: {
if (_parsePaintProperty(block, picture)) return true;
}
}
//Vector Picture won't be requested since Saver replaces it with the Scene
return false;
}
static Paint* _parsePaint(TvgBinBlock baseBlock)
{
bool (*parser)(TvgBinBlock, Paint*);
Paint *paint;
//1. Decide the type of paint.
switch (baseBlock.type) {
case TVG_TAG_CLASS_SCENE: {
paint = Scene::gen().release();
parser = _parseScene;
break;
}
case TVG_TAG_CLASS_SHAPE: {
paint = Shape::gen().release();
parser = _parseShape;
break;
}
case TVG_TAG_CLASS_PICTURE: {
paint = Picture::gen().release();
parser = _parsePicture;
break;
}
default: {
TVGERR("TVG", "Invalid Paint Type %d (0x%x)", baseBlock.type, baseBlock.type);
return nullptr;
}
}
auto ptr = baseBlock.data;
//2. Read Subsequent properties of the current paint.
while (ptr < baseBlock.end) {
auto block = _readBlock(ptr);
if (block.end > baseBlock.end) return paint;
if (!parser(block, paint)) {
TVGERR("TVG", "Encountered the wrong paint properties... Paint Class %d (0x%x)", baseBlock.type, baseBlock.type);
return paint;
}
ptr = block.end;
}
return paint;
}
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
unique_ptr<Scene> TvgBinInterpreter::run(const char *ptr, const char* end)
{
auto scene = Scene::gen();
if (!scene) return nullptr;
while (ptr < end) {
auto block = _readBlock(ptr);
if (block.end > end) {
TVGERR("TVG", "Corrupted tvg file.");
return nullptr;
}
scene->push(unique_ptr<Paint>(_parsePaint(block)));
ptr = block.end;
}
return scene;
}

View file

@ -1,54 +0,0 @@
/*
* Copyright (c) 2021 - 2023 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.
*/
#ifndef _TVG_TVG_COMMON_H_
#define _TVG_TVG_COMMON_H_
#include "tvgCommon.h"
#include "tvgBinaryDesc.h"
#define SIZE(A) sizeof(A)
#define READ_UI32(dst, src) memcpy(dst, (src), sizeof(uint32_t))
#define READ_FLOAT(dst, src) memcpy(dst, (src), sizeof(float))
/* Interface for Tvg Binary Interpreter */
class TvgBinInterpreterBase
{
public:
virtual ~TvgBinInterpreterBase() {}
/* ptr: points the tvg binary body (after header)
end: end of the tvg binary data */
virtual unique_ptr<Scene> run(const char* ptr, const char* end) = 0;
};
/* Version 0 */
class TvgBinInterpreter : public TvgBinInterpreterBase
{
public:
unique_ptr<Scene> run(const char* ptr, const char* end) override;
};
#endif //_TVG_TVG_COMMON_H_

View file

@ -1,233 +0,0 @@
/*
* Copyright (c) 2021 - 2023 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 <memory.h>
#include <fstream>
#include "tvgLoader.h"
#include "tvgTvgLoader.h"
#include "tvgLzw.h"
/************************************************************************/
/* Internal Class Implementation */
/************************************************************************/
void TvgLoader::clear()
{
if (copy) free((char*)data);
ptr = data = nullptr;
size = 0;
copy = false;
if (interpreter) {
delete(interpreter);
interpreter = nullptr;
}
}
/* WARNING: Header format shall not change! */
bool TvgLoader::readHeader()
{
if (!ptr) return false;
//Make sure the size is large enough to hold the header
if (size < TVG_HEADER_SIZE) return false;
//1. Signature
if (memcmp(ptr, TVG_HEADER_SIGNATURE, TVG_HEADER_SIGNATURE_LENGTH)) return false;
ptr += TVG_HEADER_SIGNATURE_LENGTH;
//2. Version
char version[TVG_HEADER_VERSION_LENGTH + 1];
memcpy(version, ptr, TVG_HEADER_VERSION_LENGTH);
version[TVG_HEADER_VERSION_LENGTH - 1] = '\0';
ptr += TVG_HEADER_VERSION_LENGTH;
this->version = atoi(version);
if (this->version > THORVG_VERSION_NUMBER()) {
TVGLOG("TVG", "This TVG file expects a higher version(%d) of ThorVG symbol(%d)", this->version, THORVG_VERSION_NUMBER());
}
//3. View Size
READ_FLOAT(&w, ptr);
ptr += SIZE(float);
READ_FLOAT(&h, ptr);
ptr += SIZE(float);
//4. Reserved
if (*ptr & TVG_HEAD_FLAG_COMPRESSED) compressed = true;
ptr += TVG_HEADER_RESERVED_LENGTH;
//5. Compressed Size if any
if (compressed) {
auto p = ptr;
//TVG_HEADER_UNCOMPRESSED_SIZE
memcpy(&uncompressedSize, p, sizeof(uint32_t));
p += SIZE(uint32_t);
//TVG_HEADER_COMPRESSED_SIZE
memcpy(&compressedSize, p, sizeof(uint32_t));
p += SIZE(uint32_t);
//TVG_HEADER_COMPRESSED_SIZE_BITS
memcpy(&compressedSizeBits, p, sizeof(uint32_t));
}
ptr += TVG_HEADER_COMPRESS_SIZE;
//Decide the proper Tvg Binary Interpreter based on the current file version
if (this->version >= 0) interpreter = new TvgBinInterpreter;
return true;
}
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
TvgLoader::~TvgLoader()
{
close();
}
bool TvgLoader::open(const string &path)
{
clear();
ifstream f;
f.open(path, ifstream::in | ifstream::binary | ifstream::ate);
if (!f.is_open()) return false;
size = f.tellg();
f.seekg(0, ifstream::beg);
copy = true;
data = (char*)malloc(size);
if (!data) {
clear();
f.close();
return false;
}
if (!f.read((char*)data, size))
{
clear();
f.close();
return false;
}
f.close();
ptr = data;
return readHeader();
}
bool TvgLoader::open(const char *data, uint32_t size, bool copy)
{
clear();
if (copy) {
this->data = (char*)malloc(size);
if (!this->data) return false;
memcpy((char*)this->data, data, size);
} else this->data = data;
this->ptr = this->data;
this->size = size;
this->copy = copy;
return readHeader();
}
bool TvgLoader::resize(Paint* paint, float w, float h)
{
if (!paint) return false;
auto sx = w / this->w;
auto sy = h / this->h;
//Scale
auto scale = sx < sy ? sx : sy;
paint->scale(scale);
//Align
float tx = 0, ty = 0;
auto sw = this->w * scale;
auto sh = this->h * scale;
if (sw > sh) ty -= (h - sh) * 0.5f;
else tx -= (w - sw) * 0.5f;
paint->translate(-tx, -ty);
return true;
}
bool TvgLoader::read()
{
if (!ptr || size == 0) return false;
TaskScheduler::request(this);
return true;
}
bool TvgLoader::close()
{
this->done();
clear();
return true;
}
void TvgLoader::run(unsigned tid)
{
if (root) root.reset();
auto data = const_cast<char*>(ptr);
if (compressed) {
data = (char*) lzwDecode((uint8_t*) data, compressedSize, compressedSizeBits, uncompressedSize);
root = interpreter->run(data, data + uncompressedSize);
free(data);
} else {
root = interpreter->run(data, this->data + size);
}
if (!root) clear();
}
unique_ptr<Paint> TvgLoader::paint()
{
this->done();
if (root) return move(root);
return nullptr;
}

View file

@ -1,61 +0,0 @@
/*
* Copyright (c) 2021 - 2023 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.
*/
#ifndef _TVG_TVG_LOADER_H_
#define _TVG_TVG_LOADER_H_
#include "tvgTaskScheduler.h"
#include "tvgTvgCommon.h"
class TvgLoader : public LoadModule, public Task
{
public:
const char* data = nullptr;
const char* ptr = nullptr;
uint32_t size = 0;
uint16_t version = 0;
unique_ptr<Scene> root = nullptr;
TvgBinInterpreterBase* interpreter = nullptr;
uint32_t uncompressedSize = 0;
uint32_t compressedSize = 0;
uint32_t compressedSizeBits = 0;
bool copy = false;
bool compressed = false;
~TvgLoader();
using LoadModule::open;
bool open(const string &path) override;
bool open(const char *data, uint32_t size, bool copy) override;
bool read() override;
bool close() override;
bool resize(Paint* paint, float w, float h) override;
unique_ptr<Paint> paint() override;
private:
bool readHeader();
void run(unsigned tid) override;
void clear();
};
#endif //_TVG_TVG_LOADER_H_

View file

@ -1,792 +0,0 @@
/*
* Copyright (c) 2021 - 2023 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 "tvgMath.h"
#include "tvgSaveModule.h"
#include "tvgTvgSaver.h"
#include "tvgLzw.h"
#include <cstring>
#ifdef _WIN32
#include <malloc.h>
#elif defined(__linux__)
#include <alloca.h>
#else
#include <stdlib.h>
#endif
static FILE* _fopen(const char* filename, const char* mode)
{
#if defined(_MSC_VER) && defined(__clang__)
FILE *fp;
auto err = fopen_s(&fp, filename, mode);
if (err != 0) return nullptr;
return fp;
#else
auto fp = fopen(filename, mode);
if (!fp) return nullptr;
return fp;
#endif
}
#define SIZE(A) sizeof(A)
/************************************************************************/
/* Internal Class Implementation */
/************************************************************************/
static inline TvgBinCounter SERIAL_DONE(TvgBinCounter cnt)
{
return SIZE(TvgBinTag) + SIZE(TvgBinCounter) + cnt;
}
/* if the properties are identical, we can merge the shapes. */
static bool _merge(Shape* from, Shape* to)
{
uint8_t r, g, b, a;
uint8_t r2, g2, b2, a2;
//fill
if (from->fill() || to->fill()) return false;
r = g = b = a = r2 = g2 = b2 = a2 = 0;
from->fillColor(&r, &g, &b, &a);
to->fillColor(&r2, &g2, &b2, &a2);
if (r != r2 || g != g2 || b != b2 || a != a2) return false;
//composition
if (from->composite(nullptr) != CompositeMethod::None) return false;
if (to->composite(nullptr) != CompositeMethod::None) return false;
//opacity
if (from->opacity() != to->opacity()) return false;
//transform
auto t1 = from->transform();
auto t2 = to->transform();
if (!mathEqual(t1.e11, t2.e11) || !mathEqual(t1.e12, t2.e12) || !mathEqual(t1.e13, t2.e13) ||
!mathEqual(t1.e21, t2.e21) || !mathEqual(t1.e22, t2.e22) || !mathEqual(t1.e23, t2.e23) ||
!mathEqual(t1.e31, t2.e31) || !mathEqual(t1.e32, t2.e32) || !mathEqual(t1.e33, t2.e33)) {
return false;
}
//stroke
r = g = b = a = r2 = g2 = b2 = a2 = 0;
from->strokeColor(&r, &g, &b, &a);
to->strokeColor(&r2, &g2, &b2, &a2);
if (r != r2 || g != g2 || b != b2 || a != a2) return false;
if (fabs(from->strokeWidth() - to->strokeWidth()) > FLT_EPSILON) return false;
//OPTIMIZE: Yet we can't merge outlining shapes unless we can support merging shapes feature.
if (from->strokeWidth() > 0 || to->strokeWidth() > 0) return false;
if (from->strokeCap() != to->strokeCap()) return false;
if (from->strokeJoin() != to->strokeJoin()) return false;
if (from->strokeDash(nullptr) > 0 || to->strokeDash(nullptr) > 0) return false;
if (from->strokeFill() || to->strokeFill()) return false;
//fill rule
if (from->fillRule() != to->fillRule()) return false;
//Good, identical shapes, we can merge them.
const PathCommand* cmds = nullptr;
auto cmdCnt = from->pathCommands(&cmds);
const Point* pts = nullptr;
auto ptsCnt = from->pathCoords(&pts);
to->appendPath(cmds, cmdCnt, pts, ptsCnt);
return true;
}
bool TvgSaver::saveEncoding(const std::string& path)
{
if (!compress) return flushTo(path);
//Try encoding
auto uncompressed = buffer.data + headerSize;
auto uncompressedSize = buffer.count - headerSize;
uint32_t compressedSize, compressedSizeBits;
auto compressed = lzwEncode(uncompressed, uncompressedSize, &compressedSize, &compressedSizeBits);
//Failed compression.
if (!compressed) return flushTo(path);
//Optimization is ineffective.
if (compressedSize >= uncompressedSize) {
free(compressed);
return flushTo(path);
}
TVGLOG("TVG_SAVER", "%s, compressed: %d -> %d, saved rate: %3.2f%%", path.c_str(), uncompressedSize, compressedSize, (1 - ((float) compressedSize / (float) uncompressedSize)) * 100);
//Update compress size in the header.
uncompressed -= (TVG_HEADER_COMPRESS_SIZE + TVG_HEADER_RESERVED_LENGTH);
//Compression Flag
*uncompressed |= TVG_HEAD_FLAG_COMPRESSED;
uncompressed += TVG_HEADER_RESERVED_LENGTH;
//Uncompressed Size
memcpy(uncompressed, &uncompressedSize, TVG_HEADER_UNCOMPRESSED_SIZE);
uncompressed += TVG_HEADER_UNCOMPRESSED_SIZE;
//Comprssed Size
memcpy(uncompressed, &compressedSize, TVG_HEADER_COMPRESSED_SIZE);
uncompressed += TVG_HEADER_COMPRESSED_SIZE;
//Compressed Size Bits
memcpy(uncompressed, &compressedSizeBits, TVG_HEADER_COMPRESSED_SIZE_BITS);
//Good optimization, flush to file.
auto fp = _fopen(path.c_str(), "wb+");
if (!fp) goto fail;
//write header
if (fwrite(buffer.data, SIZE(uint8_t), headerSize, fp) == 0) goto fail;
//write compressed data
if (fwrite(compressed, SIZE(uint8_t), compressedSize, fp) == 0) goto fail;
fclose(fp);
free(compressed);
return true;
fail:
if (fp) fclose(fp);
if (compressed) free(compressed);
return false;
}
bool TvgSaver::flushTo(const std::string& path)
{
auto fp = _fopen(path.c_str(), "wb+");
if (!fp) return false;
if (fwrite(buffer.data, SIZE(uint8_t), buffer.count, fp) == 0) {
fclose(fp);
return false;
}
fclose(fp);
return true;
}
/* WARNING: Header format shall not changed! */
bool TvgSaver::writeHeader()
{
headerSize = TVG_HEADER_SIGNATURE_LENGTH + TVG_HEADER_VERSION_LENGTH + SIZE(vsize) + TVG_HEADER_RESERVED_LENGTH + TVG_HEADER_COMPRESS_SIZE;
buffer.grow(headerSize);
//1. Signature
auto ptr = buffer.ptr();
memcpy(ptr, TVG_HEADER_SIGNATURE, TVG_HEADER_SIGNATURE_LENGTH);
ptr += TVG_HEADER_SIGNATURE_LENGTH;
//2. Version
memcpy(ptr, TVG_HEADER_VERSION, TVG_HEADER_VERSION_LENGTH);
ptr += TVG_HEADER_VERSION_LENGTH;
buffer.count += (TVG_HEADER_SIGNATURE_LENGTH + TVG_HEADER_VERSION_LENGTH);
//3. View Size
writeData(vsize, SIZE(vsize));
ptr += SIZE(vsize);
//4. Reserved data + Compress size
memset(ptr, 0x00, TVG_HEADER_RESERVED_LENGTH + TVG_HEADER_COMPRESS_SIZE);
buffer.count += (TVG_HEADER_RESERVED_LENGTH + TVG_HEADER_COMPRESS_SIZE);
return true;
}
void TvgSaver::writeTag(TvgBinTag tag)
{
buffer.grow(SIZE(TvgBinTag));
memcpy(buffer.ptr(), &tag, SIZE(TvgBinTag));
buffer.count += SIZE(TvgBinTag);
}
void TvgSaver::writeCount(TvgBinCounter cnt)
{
buffer.grow(SIZE(TvgBinCounter));
memcpy(buffer.ptr(), &cnt, SIZE(TvgBinCounter));
buffer.count += SIZE(TvgBinCounter);
}
void TvgSaver::writeReservedCount(TvgBinCounter cnt)
{
memcpy(buffer.ptr() - cnt - SIZE(TvgBinCounter), &cnt, SIZE(TvgBinCounter));
}
void TvgSaver::reserveCount()
{
buffer.grow(SIZE(TvgBinCounter));
buffer.count += SIZE(TvgBinCounter);
}
TvgBinCounter TvgSaver::writeData(const void* data, TvgBinCounter cnt)
{
buffer.grow(cnt);
memcpy(buffer.ptr(), data, cnt);
buffer.count += cnt;
return cnt;
}
TvgBinCounter TvgSaver::writeTagProperty(TvgBinTag tag, TvgBinCounter cnt, const void* data)
{
auto growCnt = SERIAL_DONE(cnt);
buffer.grow(growCnt);
auto ptr = buffer.ptr();
*ptr = tag;
++ptr;
memcpy(ptr, &cnt, SIZE(TvgBinCounter));
ptr += SIZE(TvgBinCounter);
memcpy(ptr, data, cnt);
ptr += cnt;
buffer.count += growCnt;
return growCnt;
}
TvgBinCounter TvgSaver::writeTransform(const Matrix* transform, TvgBinTag tag)
{
if (!mathIdentity(transform)) return writeTagProperty(tag, SIZE(Matrix), transform);
return 0;
}
TvgBinCounter TvgSaver::serializePaint(const Paint* paint, const Matrix* pTransform)
{
TvgBinCounter cnt = 0;
//opacity
auto opacity = paint->opacity();
if (opacity < 255) {
cnt += writeTagProperty(TVG_TAG_PAINT_OPACITY, SIZE(opacity), &opacity);
}
//composite
const Paint* cmpTarget = nullptr;
auto cmpMethod = paint->composite(&cmpTarget);
if (cmpMethod != CompositeMethod::None && cmpTarget) {
cnt += serializeComposite(cmpTarget, cmpMethod, pTransform);
}
return cnt;
}
/* Propagate parents properties to the child so that we can skip saving the parent. */
TvgBinCounter TvgSaver::serializeChild(const Paint* parent, const Paint* child, const Matrix* transform)
{
const Paint* compTarget = nullptr;
auto compMethod = parent->composite(&compTarget);
/* If the parent & the only child have composition, we can't skip the parent...
Or if the parent has the transform and composition, we can't skip the parent... */
if (compMethod != CompositeMethod::None) {
if (transform || child->composite(nullptr) != CompositeMethod::None) return 0;
}
//propagate opacity
uint32_t opacity = parent->opacity();
if (opacity < 255) {
uint32_t tmp = (child->opacity() * opacity);
if (tmp > 0) tmp /= 255;
const_cast<Paint*>(child)->opacity(tmp);
}
//propagate composition
if (compTarget) const_cast<Paint*>(child)->composite(unique_ptr<Paint>(compTarget->duplicate()), compMethod);
return serialize(child, transform);
}
TvgBinCounter TvgSaver::serializeScene(const Scene* scene, const Matrix* pTransform, const Matrix* cTransform)
{
auto it = IteratorAccessor::iterator(scene);
if (it->count() == 0) {
delete(it);
return 0;
}
//Case - Only Child: Skip saving this scene.
if (it->count() == 1) {
auto cnt = serializeChild(scene, it->next(), cTransform);
if (cnt > 0) {
delete(it);
return cnt;
}
}
it->begin();
//Case - Delegator Scene: This scene is just a delegator, we can skip this:
if (scene->composite(nullptr) == CompositeMethod::None && scene->opacity() == 255) {
auto ret = serializeChildren(it, cTransform, false);
delete(it);
return ret;
}
//Case - Serialize Scene & its children
writeTag(TVG_TAG_CLASS_SCENE);
reserveCount();
auto cnt = serializeChildren(it, cTransform, true) + serializePaint(scene, pTransform);
delete(it);
writeReservedCount(cnt);
return SERIAL_DONE(cnt);
}
TvgBinCounter TvgSaver::serializeFill(const Fill* fill, TvgBinTag tag, const Matrix* pTransform)
{
const Fill::ColorStop* stops = nullptr;
auto stopsCnt = fill->colorStops(&stops);
if (!stops || stopsCnt == 0) return 0;
writeTag(tag);
reserveCount();
TvgBinCounter cnt = 0;
//radial fill
if (fill->identifier() == TVG_CLASS_ID_RADIAL) {
float args[3];
static_cast<const RadialGradient*>(fill)->radial(args, args + 1, args + 2);
cnt += writeTagProperty(TVG_TAG_FILL_RADIAL_GRADIENT, SIZE(args), args);
//linear fill
} else {
float args[4];
static_cast<const LinearGradient*>(fill)->linear(args, args + 1, args + 2, args + 3);
cnt += writeTagProperty(TVG_TAG_FILL_LINEAR_GRADIENT, SIZE(args), args);
}
if (auto flag = static_cast<TvgBinFlag>(fill->spread()))
cnt += writeTagProperty(TVG_TAG_FILL_FILLSPREAD, SIZE(TvgBinFlag), &flag);
cnt += writeTagProperty(TVG_TAG_FILL_COLORSTOPS, stopsCnt * SIZE(Fill::ColorStop), stops);
auto gTransform = fill->transform();
if (pTransform) gTransform = mathMultiply(pTransform, &gTransform);
cnt += writeTransform(&gTransform, TVG_TAG_FILL_TRANSFORM);
writeReservedCount(cnt);
return SERIAL_DONE(cnt);
}
TvgBinCounter TvgSaver::serializeStroke(const Shape* shape, const Matrix* pTransform, bool preTransform)
{
writeTag(TVG_TAG_SHAPE_STROKE);
reserveCount();
//width
auto width = shape->strokeWidth();
if (preTransform) width *= sqrtf(powf(pTransform->e11, 2.0f) + powf(pTransform->e21, 2.0f)); //we know x/y scaling factors are same.
auto cnt = writeTagProperty(TVG_TAG_SHAPE_STROKE_WIDTH, SIZE(width), &width);
//cap
if (auto flag = static_cast<TvgBinFlag>(shape->strokeCap()))
cnt += writeTagProperty(TVG_TAG_SHAPE_STROKE_CAP, SIZE(TvgBinFlag), &flag);
//join
if (auto flag = static_cast<TvgBinFlag>(shape->strokeJoin()))
cnt += writeTagProperty(TVG_TAG_SHAPE_STROKE_JOIN, SIZE(TvgBinFlag), &flag);
//fill
if (auto fill = shape->strokeFill()) {
cnt += serializeFill(fill, TVG_TAG_SHAPE_STROKE_FILL, (preTransform ? pTransform : nullptr));
} else {
uint8_t color[4] = {0, 0, 0, 0};
shape->strokeColor(color, color + 1, color + 2, color + 3);
cnt += writeTagProperty(TVG_TAG_SHAPE_STROKE_COLOR, SIZE(color), &color);
}
//dash
const float* dashPattern = nullptr;
auto dashCnt = shape->strokeDash(&dashPattern);
if (dashPattern && dashCnt > 0) {
TvgBinCounter dashCntSize = SIZE(dashCnt);
TvgBinCounter dashPtrnSize = dashCnt * SIZE(dashPattern[0]);
writeTag(TVG_TAG_SHAPE_STROKE_DASHPTRN);
writeCount(dashCntSize + dashPtrnSize);
cnt += writeData(&dashCnt, dashCntSize);
cnt += writeData(dashPattern, dashPtrnSize);
cnt += SIZE(TvgBinTag) + SIZE(TvgBinCounter);
}
writeReservedCount(cnt);
return SERIAL_DONE(cnt);
}
TvgBinCounter TvgSaver::serializePath(const Shape* shape, const Matrix* transform, bool preTransform)
{
const PathCommand* cmds = nullptr;
auto cmdCnt = shape->pathCommands(&cmds);
const Point* pts = nullptr;
auto ptsCnt = shape->pathCoords(&pts);
if (!cmds || !pts || cmdCnt == 0 || ptsCnt == 0) return 0;
writeTag(TVG_TAG_SHAPE_PATH);
reserveCount();
/* Reduce the binary size.
Convert PathCommand(4 bytes) to TvgBinFlag(1 byte) */
TvgBinFlag* outCmds = (TvgBinFlag*)alloca(SIZE(TvgBinFlag) * cmdCnt);
for (uint32_t i = 0; i < cmdCnt; ++i) {
outCmds[i] = static_cast<TvgBinFlag>(cmds[i]);
}
auto cnt = writeData(&cmdCnt, SIZE(cmdCnt));
cnt += writeData(&ptsCnt, SIZE(ptsCnt));
cnt += writeData(outCmds, SIZE(TvgBinFlag) * cmdCnt);
//transform?
if (preTransform) {
if (!mathEqual(transform->e11, 1.0f) || !mathZero(transform->e12) || !mathZero(transform->e13) ||
!mathZero(transform->e21) || !mathEqual(transform->e22, 1.0f) || !mathZero(transform->e23) ||
!mathZero(transform->e31) || !mathZero(transform->e32) || !mathEqual(transform->e33, 1.0f)) {
auto p = const_cast<Point*>(pts);
for (uint32_t i = 0; i < ptsCnt; ++i) mathMultiply(p++, transform);
}
}
cnt += writeData(pts, ptsCnt * SIZE(pts[0]));
writeReservedCount(cnt);
return SERIAL_DONE(cnt);
}
TvgBinCounter TvgSaver::serializeShape(const Shape* shape, const Matrix* pTransform, const Matrix* cTransform)
{
writeTag(TVG_TAG_CLASS_SHAPE);
reserveCount();
TvgBinCounter cnt = 0;
//fill rule
if (auto flag = static_cast<TvgBinFlag>(shape->fillRule())) {
cnt = writeTagProperty(TVG_TAG_SHAPE_FILLRULE, SIZE(TvgBinFlag), &flag);
}
//the pre-transformation can't be applied in the case when the stroke is dashed or irregulary scaled
bool preTransform = true;
//stroke
if (shape->strokeWidth() > 0) {
uint8_t color[4] = {0, 0, 0, 0};
shape->strokeColor(color, color + 1, color + 2, color + 3);
auto fill = shape->strokeFill();
if (fill || color[3] > 0) {
if (!mathEqual(cTransform->e11, cTransform->e22) || (mathZero(cTransform->e11) && !mathEqual(cTransform->e12, cTransform->e21)) || shape->strokeDash(nullptr) > 0) preTransform = false;
cnt += serializeStroke(shape, cTransform, preTransform);
}
}
//fill
if (auto fill = shape->fill()) {
cnt += serializeFill(fill, TVG_TAG_SHAPE_FILL, (preTransform ? cTransform : nullptr));
} else {
uint8_t color[4] = {0, 0, 0, 0};
shape->fillColor(color, color + 1, color + 2, color + 3);
if (color[3] > 0) cnt += writeTagProperty(TVG_TAG_SHAPE_COLOR, SIZE(color), color);
}
cnt += serializePath(shape, cTransform, preTransform);
if (!preTransform) cnt += writeTransform(cTransform, TVG_TAG_PAINT_TRANSFORM);
cnt += serializePaint(shape, pTransform);
writeReservedCount(cnt);
return SERIAL_DONE(cnt);
}
/* Picture has either a vector scene or a bitmap. */
TvgBinCounter TvgSaver::serializePicture(const Picture* picture, const Matrix* pTransform, const Matrix* cTransform)
{
auto it = IteratorAccessor::iterator(picture);
//Case - Vector Scene:
if (it->count() == 1) {
auto cnt = serializeChild(picture, it->next(), cTransform);
//Only child, Skip to save Picture...
if (cnt > 0) {
delete(it);
return cnt;
/* Unfortunately, we can't skip the Picture because it might have a compositor,
Serialize Scene(instead of the Picture) & its scene. */
} else {
writeTag(TVG_TAG_CLASS_SCENE);
reserveCount();
auto cnt = serializeChildren(it, cTransform, true) + serializePaint(picture, pTransform);
writeReservedCount(cnt);
delete(it);
return SERIAL_DONE(cnt);
}
}
delete(it);
//Case - Bitmap Image:
uint32_t w, h;
auto pixels = picture->data(&w, &h);
if (!pixels) return 0;
writeTag(TVG_TAG_CLASS_PICTURE);
reserveCount();
TvgBinCounter cnt = 0;
TvgBinCounter sizeCnt = SIZE(w);
TvgBinCounter imgSize = w * h * SIZE(pixels[0]);
writeTag(TVG_TAG_PICTURE_RAW_IMAGE);
writeCount(2 * sizeCnt + imgSize);
cnt += writeData(&w, sizeCnt);
cnt += writeData(&h, sizeCnt);
cnt += writeData(pixels, imgSize);
cnt += SIZE(TvgBinTag) + SIZE(TvgBinCounter);
//mesh: currently only available in bitmap image.
const Polygon* triangles = nullptr;
auto triangleCnt = picture->mesh(&triangles);
if (triangles && triangleCnt > 0) {
TvgBinCounter triangleCntSize = SIZE(triangleCnt);
TvgBinCounter trianglesSize = triangleCnt * SIZE(triangles[0]);
writeTag(TVG_TAG_PICTURE_MESH);
writeCount(triangleCntSize + trianglesSize);
cnt += writeData(&triangleCnt, triangleCntSize);
cnt += writeData(triangles, trianglesSize);
cnt += SIZE(TvgBinTag) + SIZE(TvgBinCounter);
}
//Bitmap picture needs the transform info.
cnt += writeTransform(cTransform, TVG_TAG_PAINT_TRANSFORM);
cnt += serializePaint(picture, pTransform);
writeReservedCount(cnt);
return SERIAL_DONE(cnt);
}
TvgBinCounter TvgSaver::serializeComposite(const Paint* cmpTarget, CompositeMethod cmpMethod, const Matrix* pTransform)
{
writeTag(TVG_TAG_PAINT_CMP_TARGET);
reserveCount();
auto flag = static_cast<TvgBinFlag>(cmpMethod);
auto cnt = writeTagProperty(TVG_TAG_PAINT_CMP_METHOD, SIZE(TvgBinFlag), &flag);
cnt += serialize(cmpTarget, pTransform, true);
writeReservedCount(cnt);
return SERIAL_DONE(cnt);
}
TvgBinCounter TvgSaver::serializeChildren(Iterator* it, const Matrix* pTransform, bool reserved)
{
TvgBinCounter cnt = 0;
//Merging shapes. the result is written in the children.
Array<const Paint*> children;
children.reserve(it->count());
children.push(it->next());
while (auto child = it->next()) {
if (child->identifier() == TVG_CLASS_ID_SHAPE) {
//only dosable if the previous child is a shape.
auto target = children.ptr() - 1;
if ((*target)->identifier() == TVG_CLASS_ID_SHAPE) {
if (_merge((Shape*)child, (Shape*)*target)) {
continue;
}
}
}
children.push(child);
}
//The children of a reserved scene
if (reserved && children.count > 1) {
cnt += writeTagProperty(TVG_TAG_SCENE_RESERVEDCNT, SIZE(children.count), &children.count);
}
//Serialize merged children.
auto child = children.data;
for (uint32_t i = 0; i < children.count; ++i, ++child) {
cnt += serialize(*child, pTransform);
}
return cnt;
}
TvgBinCounter TvgSaver::serialize(const Paint* paint, const Matrix* pTransform, bool compTarget)
{
if (!paint) return 0;
//Invisible paint, no point to save it if the paint is not the composition target...
if (!compTarget && paint->opacity() == 0) return 0;
auto transform = const_cast<Paint*>(paint)->transform();
if (pTransform) transform = mathMultiply(pTransform, &transform);
switch (paint->identifier()) {
case TVG_CLASS_ID_SHAPE: return serializeShape(static_cast<const Shape*>(paint), pTransform, &transform);
case TVG_CLASS_ID_SCENE: return serializeScene(static_cast<const Scene*>(paint), pTransform, &transform);
case TVG_CLASS_ID_PICTURE: return serializePicture(static_cast<const Picture*>(paint), pTransform, &transform);
}
return 0;
}
void TvgSaver::run(unsigned tid)
{
if (!writeHeader()) return;
//Serialize Root Paint, without its transform.
Matrix transform = {1, 0, 0, 0, 1, 0, 0, 0, 1};
if (paint->opacity() > 0) {
switch (paint->identifier()) {
case TVG_CLASS_ID_SHAPE: {
serializeShape(static_cast<const Shape*>(paint), nullptr, &transform);
break;
}
case TVG_CLASS_ID_SCENE: {
serializeScene(static_cast<const Scene*>(paint), nullptr, &transform);
break;
}
case TVG_CLASS_ID_PICTURE: {
serializePicture(static_cast<const Picture*>(paint), nullptr, &transform);
break;
}
}
}
if (!saveEncoding(path)) return;
}
/************************************************************************/
/* External Class Implementation */
/************************************************************************/
TvgSaver::~TvgSaver()
{
close();
}
bool TvgSaver::close()
{
this->done();
if (paint) {
delete(paint);
paint = nullptr;
}
if (path) {
free(path);
path = nullptr;
}
buffer.reset();
return true;
}
bool TvgSaver::save(Paint* paint, const string& path, bool compress)
{
close();
float x, y;
x = y = 0;
paint->bounds(&x, &y, &vsize[0], &vsize[1], false);
//cut off the negative space
if (x < 0) vsize[0] += x;
if (y < 0) vsize[1] += y;
if (vsize[0] < FLT_EPSILON || vsize[1] < FLT_EPSILON) {
TVGLOG("TVG_SAVER", "Saving paint(%p) has zero view size.", paint);
return false;
}
this->path = strdup(path.c_str());
if (!this->path) return false;
this->paint = paint;
this->compress = compress;
TaskScheduler::request(this);
return true;
}

View file

@ -1,78 +0,0 @@
/*
* Copyright (c) 2021 - 2023 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.
*/
#ifndef _TVG_TVGSAVER_H_
#define _TVG_TVGSAVER_H_
#include "tvgArray.h"
#include "tvgBinaryDesc.h"
#include "tvgTaskScheduler.h"
namespace tvg
{
class TvgSaver : public SaveModule, public Task
{
private:
Array<TvgBinByte> buffer;
Paint* paint = nullptr;
char *path = nullptr;
uint32_t headerSize;
float vsize[2] = {0.0f, 0.0f};
bool compress;
bool flushTo(const std::string& path);
bool saveEncoding(const std::string& path);
void reserveCount();
bool writeHeader();
bool writeViewSize();
void writeTag(TvgBinTag tag);
void writeCount(TvgBinCounter cnt);
void writeReservedCount(TvgBinCounter cnt);
TvgBinCounter writeData(const void* data, TvgBinCounter cnt);
TvgBinCounter writeTagProperty(TvgBinTag tag, TvgBinCounter cnt, const void* data);
TvgBinCounter writeTransform(const Matrix* transform, TvgBinTag tag);
TvgBinCounter serialize(const Paint* paint, const Matrix* pTransform, bool compTarget = false);
TvgBinCounter serializeScene(const Scene* scene, const Matrix* pTransform, const Matrix* cTransform);
TvgBinCounter serializeShape(const Shape* shape, const Matrix* pTransform, const Matrix* cTransform);
TvgBinCounter serializePicture(const Picture* picture, const Matrix* pTransform, const Matrix* cTransform);
TvgBinCounter serializePaint(const Paint* paint, const Matrix* pTransform);
TvgBinCounter serializeFill(const Fill* fill, TvgBinTag tag, const Matrix* pTransform);
TvgBinCounter serializeStroke(const Shape* shape, const Matrix* pTransform, bool preTransform);
TvgBinCounter serializePath(const Shape* shape, const Matrix* transform, bool preTransform);
TvgBinCounter serializeComposite(const Paint* cmpTarget, CompositeMethod cmpMethod, const Matrix* pTransform);
TvgBinCounter serializeChildren(Iterator* it, const Matrix* transform, bool reserved);
TvgBinCounter serializeChild(const Paint* parent, const Paint* child, const Matrix* pTransform);
public:
~TvgSaver();
bool save(Paint* paint, const string& path, bool compress) override;
bool close() override;
void run(unsigned tid) override;
};
}
#endif //_TVG_SAVE_MODULE_H_

View file

@ -1,31 +1,47 @@
VERSION=0.9.0
rm -rf AUTHORS inc LICENSE src *.zip
curl -L -O https://github.com/thorvg/thorvg/archive/v$VERSION.zip
bsdtar --strip-components=1 -xvf *.zip
rm *.zip
rm -rf .github docs pc res test tools tvgcompat .git* *.md *.txt wasm_build.sh CODEOWNERS
#!/bin/bash -e
VERSION=0.10.0
rm -rf AUTHORS LICENSE inc/ src/ *.zip *.tar.gz tmp/
mkdir tmp/ && pushd tmp/
curl -L -O https://github.com/thorvg/thorvg/archive/v$VERSION.tar.gz
tar --strip-components=1 -xvf *.tar.gz
rm *.tar.gz
find . -type f -name 'meson.build' -delete
rm -rf src/bin src/bindings src/examples src/wasm
rm -rf src/lib/gl_engine src/loaders/external_jpg src/loaders/png
cat << EOF > inc/config.h
# Fix newline at end of file.
for source in $(find ./ -type f \( -iname \*.h -o -iname \*.cpp \)); do
sed -i -e '$a\' $source
done
cp -v AUTHORS LICENSE ..
cp -rv inc ../
cat << EOF > ../inc/config.h
#ifndef THORVG_CONFIG_H
#define THORVG_CONFIG_H
#define THORVG_SW_RASTER_SUPPORT 1
#define THORVG_SW_RASTER_SUPPORT
#define THORVG_SVG_LOADER_SUPPORT 1
#define THORVG_PNG_LOADER_SUPPORT 1
#define THORVG_TVG_LOADER_SUPPORT 1
#define THORVG_TVG_SAVER_SUPPORT 1
#define THORVG_JPG_LOADER_SUPPORT 1
#define THORVG_SVG_LOADER_SUPPORT
#define THORVG_VERSION_STRING "$VERSION"
#endif
EOF
for source in $(find ./ -type f \( -iname \*.h -o -iname \*.cpp \)); do
sed -i -e '$a\' $source
done
mkdir ../src
cp -rv src/lib ../src/
# Only sw_engine is enabled.
rm -rfv ../src/lib/gl_engine
# Only svg loader is enabled.
mkdir ../src/loaders
cp -rv src/loaders/svg src/loaders/raw ../src/loaders/
# Future versions
# cp -rv src/utils ../src
popd
rm -rf tmp/