Update ThorVG to v0.10.0

- Release Notes: https://github.com/thorvg/thorvg/releases/tag/v0.10.0
- API change ARGB8888_STRAIGHT -> ARGB8888S
- SVG-SCsub: Enable static ThorVG object linking
- SVG-SCsub: avoid building unused ThorVG parts
- update-thorvg.sh: add v0.10.0 and copy only the Godot relevant code
This commit is contained in:
Martin Capitanio 2023-08-08 22:22:11 +02:00
parent 11ea4dc466
commit ac9e259d3e
70 changed files with 4148 additions and 7212 deletions

View File

@ -38,9 +38,6 @@ thirdparty_sources = [
"src/lib/tvgShape.cpp", "src/lib/tvgShape.cpp",
"src/lib/tvgSwCanvas.cpp", "src/lib/tvgSwCanvas.cpp",
"src/lib/tvgTaskScheduler.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/raw/tvgRawLoader.cpp",
"src/loaders/svg/tvgSvgCssStyle.cpp", "src/loaders/svg/tvgSvgCssStyle.cpp",
"src/loaders/svg/tvgSvgLoader.cpp", "src/loaders/svg/tvgSvgLoader.cpp",
@ -48,27 +45,23 @@ thirdparty_sources = [
"src/loaders/svg/tvgSvgSceneBuilder.cpp", "src/loaders/svg/tvgSvgSceneBuilder.cpp",
"src/loaders/svg/tvgSvgUtil.cpp", "src/loaders/svg/tvgSvgUtil.cpp",
"src/loaders/svg/tvgXmlParser.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] thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources]
env_svg.Prepend(CPPPATH=[thirdparty_dir + "inc"]) 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 = env_svg.Clone()
env_thirdparty.disable_warnings() env_thirdparty.disable_warnings()
env_thirdparty.Prepend( env_thirdparty.Prepend(
CPPPATH=[ CPPPATH=[
thirdparty_dir + "src/lib", thirdparty_dir + "src/lib",
thirdparty_dir + "src/lib/sw_engine", 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/raw",
thirdparty_dir + "src/loaders/svg", thirdparty_dir + "src/loaders/svg",
thirdparty_dir + "src/loaders/tvg",
thirdparty_dir + "src/savers/tvg",
] ]
) )
# Also requires libpng headers # 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. // Note: memalloc here, be sure to memfree before any return.
uint32_t *buffer = (uint32_t *)memalloc(sizeof(uint32_t) * width * height); 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) { if (res != tvg::Result::Success) {
memfree(buffer); memfree(buffer);
ERR_FAIL_V_MSG(FAILED, "ImageLoaderSVG: Couldn't set target on ThorVG canvas."); ERR_FAIL_V_MSG(FAILED, "ImageLoaderSVG: Couldn't set target on ThorVG canvas.");

View File

@ -271,7 +271,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(); 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) { if (res != tvg::Result::Success) {
ERR_FAIL_V_MSG(FT_Err_Invalid_Outline, "Failed to create SVG canvas."); 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(); 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) { if (res != tvg::Result::Success) {
ERR_FAIL_V_MSG(FT_Err_Invalid_Outline, "Failed to create SVG canvas."); 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 ## thorvg
- Upstream: https://github.com/thorvg/thorvg - Upstream: https://github.com/thorvg/thorvg
- Version: 0.9.0 (a744006aa1edb918bacf0a415d0a57ca058e25f4, 2023) - Version: 0.10.0 (b8c605583fd7de73209a93a1238e1ba72cce2e8f, 2023)
- License: MIT - License: MIT
Files extracted from upstream source: Files extracted from upstream source:

View File

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

View File

@ -1,17 +1,9 @@
#ifndef THORVG_CONFIG_H #ifndef THORVG_CONFIG_H
#define 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_VERSION_STRING "0.10.0"
#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"
#endif #endif

View File

@ -18,43 +18,48 @@
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <string> #include <string>
#include <list>
#ifdef TVG_API #ifdef TVG_API
#undef TVG_API #undef TVG_API
#endif #endif
#if defined(_WIN32) && !defined(__clang__) #ifndef TVG_STATIC
#if TVG_BUILD #ifdef _WIN32
#if TVG_EXPORT #if TVG_BUILD
#define TVG_API __declspec(dllexport) #define TVG_API __declspec(dllexport)
#else #else
#define TVG_API #define TVG_API __declspec(dllimport)
#endif #endif
#elif (defined(__SUNPRO_C) || defined(__SUNPRO_CC))
#define TVG_API __global
#else #else
#define TVG_API #if (defined(__GNUC__) && __GNUC__ >= 4) || defined(__INTEL_COMPILER)
#endif #define TVG_API __attribute__ ((visibility("default")))
#define TVG_DEPRECATED __declspec(deprecated)
#else
#if TVG_BUILD
#if TVG_EXPORT
#define TVG_API __attribute__ ((visibility ("default")))
#else #else
#define TVG_API #define TVG_API
#endif #endif
#else
#define TVG_API
#endif #endif
#define TVG_DEPRECATED __attribute__ ((__deprecated__)) #else
#define TVG_API
#endif #endif
#ifdef __cplusplus #ifdef TVG_DEPRECATED
extern "C" { #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 #endif
#define _TVG_DECLARE_PRIVATE(A) \ #define _TVG_DECLARE_PRIVATE(A) \
protected: \
struct Impl; \ struct Impl; \
Impl* pImpl; \ Impl* pImpl; \
protected: \
A(const A&) = delete; \ A(const A&) = delete; \
const A& operator=(const A&) = delete; \ const A& operator=(const A&) = delete; \
A() A()
@ -63,23 +68,14 @@ protected: \
A() = delete; \ A() = delete; \
~A() = delete ~A() = delete
#define _TVG_DECLARE_ACCESSOR() \ #define _TVG_DECLARE_ACCESSOR(A) \
friend Canvas; \ friend A
friend Scene; \
friend Picture; \
friend Accessor; \
friend IteratorAccessor
namespace tvg namespace tvg
{ {
class RenderMethod; class RenderMethod;
class IteratorAccessor; class Animation;
class Scene;
class Picture;
class Canvas;
class Accessor;
/** /**
* @defgroup ThorVG ThorVG * @defgroup ThorVG ThorVG
@ -102,6 +98,7 @@ enum class Result
Unknown ///< The value returned in all other cases. Unknown ///< The value returned in all other cases.
}; };
/** /**
* @brief Enumeration specifying the values of the path commands accepted by TVG. * @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. 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. * @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. 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. * @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. 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. * @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. 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. * @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. 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. * @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 enum class CompositeMethod
{ {
None = 0, ///< No composition is applied. 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. 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. AlphaMask, ///< Alpha Masking using the compositing target's pixels as an alpha value.
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. InvAlphaMask, ///< Alpha Masking using the complement to the compositing target's pixels as an alpha value.
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 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. * @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(), * 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. * 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 * @since 0.4
*/ */
@ -321,6 +361,21 @@ public:
*/ */
Result composite(std::unique_ptr<Paint> target, CompositeMethod method) noexcept; 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. * @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. * @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); * @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; 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; 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. * @brief Return the unique id value of the paint instance.
* *
@ -389,7 +454,6 @@ public:
*/ */
uint32_t identifier() const noexcept; uint32_t identifier() const noexcept;
_TVG_DECLARE_ACCESSOR();
_TVG_DECLARE_PRIVATE(Paint); _TVG_DECLARE_PRIVATE(Paint);
}; };
@ -525,14 +589,25 @@ public:
* *
* @return Result::Success when succeed. * @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. * @brief Passes drawing elements to the Canvas using Paint objects.
* *
* Only pushed paints in the canvas will be drawing targets. * Only pushed paints in the canvas will be drawing targets.
* They are retained by the canvas until you call Canvas::clear(). * 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. * @param[in] paint A Paint object to be drawn.
* *
@ -541,7 +616,7 @@ public:
* @retval Result::InsufficientCondition An internal error. * @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. * @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() * @see Canvas::clear()
*/ */
virtual Result push(std::unique_ptr<Paint> paint) noexcept; virtual Result push(std::unique_ptr<Paint> paint) noexcept;
@ -555,6 +630,8 @@ public:
* @return Result::Success when succeed, Result::InsufficientCondition otherwise. * @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. * @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; 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. * @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. * @brief Appends an ellipse to the path.
@ -905,7 +982,7 @@ public:
* *
* @return Result::Success when succeed, Result::FailedAllocation otherwise. * @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. * @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; 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. * @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 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) * @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. * @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). * @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. * @return Result::Success when succeed, Result::FailedAllocation otherwise.
* @BETA_API *
* @since 0.10
*/ */
Result order(bool strokeFirst) noexcept; Result order(bool strokeFirst) noexcept;
@ -1039,7 +1129,7 @@ public:
* *
* @return Result::Success when succeed. * @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. * @brief Gets the fill rule value.
@ -1065,7 +1155,7 @@ public:
* *
* @return Result::Success when succeed, Result::InsufficientCondition otherwise. * @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. * @brief Gets the pointer to the gradient fill of the stroke.
@ -1097,6 +1187,15 @@ public:
*/ */
StrokeJoin strokeJoin() const noexcept; 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. * @brief Creates a new Shape object.
* *
@ -1120,10 +1219,11 @@ public:
/** /**
* @class Picture * @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. * 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 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 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] 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. * @param[in] triangleCnt The number of Polygons(triangles) provided, or 0 to remove the mesh.
* *
* @return Result::Success When succeed. * @retval Result::Success When succeed.
* @return Result::Unknown If fails * @retval Result::Unknown If fails
* *
* @note The Polygons are copied internally, so modifying them after calling Mesh::mesh has no affect. * @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. * @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; 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. * @brief Creates a new Picture object.
* *
@ -1289,6 +1380,7 @@ public:
*/ */
static uint32_t identifier() noexcept; static uint32_t identifier() noexcept;
_TVG_DECLARE_ACCESSOR(Animation);
_TVG_DECLARE_PRIVATE(Picture); _TVG_DECLARE_PRIVATE(Picture);
}; };
@ -1314,14 +1406,14 @@ public:
* *
* Only the paints pushed into the scene will be the drawn targets. * Only the paints pushed into the scene will be the drawn targets.
* The paints are retained by the scene until Scene::clear() is called. * 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. * @param[in] paint A Paint object to be drawn.
* *
* @return Result::Success when succeed, Result::MemoryCorruption otherwise. * @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. * @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; Result push(std::unique_ptr<Paint> paint) noexcept;
@ -1335,7 +1427,21 @@ public:
* *
* @return Result::Success when succeed, Result::FailedAllocation otherwise. * @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. * @brief Sets the total number of the paints pushed into the scene to be zero.
@ -1386,10 +1492,10 @@ public:
*/ */
enum Colorspace enum Colorspace
{ {
ABGR8888 = 0, ///< The channels are joined in the order: alpha, blue, green, red. Colors are 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. ARGB8888, ///< The channels are joined in the order: alpha, red, green, blue. Colors are alpha-premultiplied. (a << 24 | r << 16 | g << 8 | b)
ABGR8888_STRAIGHT, ///< @BETA_API The channels are joined in the order: alpha, blue, green, red. Colors are un-alpha-premultiplied. ABGR8888S, ///< @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. 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 * @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. * @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 class TVG_API Accessor final
{ {
@ -1645,8 +1846,6 @@ public:
* @return Return the given @p picture instance. * @return Return the given @p picture instance.
* *
* @note The bitmap based picture might not have the scene-tree. * @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; 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. * @brief Creates a new Accessor object.
* *
* @return A new Accessor object. * @return A new Accessor object.
*
* @BETA_API
*/ */
static std::unique_ptr<Accessor> gen() noexcept; static std::unique_ptr<Accessor> gen() noexcept;
_TVG_DECLARE_PRIVATE(Accessor); _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 } //namespace
#ifdef __cplusplus
}
#endif
#endif //_THORVG_H_ #endif //_THORVG_H_

View File

@ -99,15 +99,11 @@ struct SwSize
struct SwOutline struct SwOutline
{ {
SwPoint* pts; //the outline's points Array<SwPoint> pts; //the outline's points
uint32_t ptsCnt; //number of points in the glyph Array<uint32_t> cntrs; //the contour end points
uint32_t reservedPtsCnt; Array<uint8_t> types; //curve type
uint32_t* cntrs; //the contour end points Array<bool> closed; //opened or closed path?
uint16_t cntrsCnt; //number of contours in glyph FillRule fillRule;
uint16_t reservedCntrsCnt;
uint8_t* types; //curve type
bool* closed; //opened or closed path?
FillRule fillRule;
}; };
struct SwSpan struct SwSpan
@ -180,6 +176,7 @@ struct SwStroke
SwPoint ptStartSubPath; SwPoint ptStartSubPath;
SwFixed subPathLineLength; SwFixed subPathLineLength;
SwFixed width; SwFixed width;
SwFixed miterlimit;
StrokeCap cap; StrokeCap cap;
StrokeJoin join; StrokeJoin join;
@ -238,18 +235,25 @@ struct SwImage
bool scaled = false; //draw scaled image bool scaled = false; //draw scaled image
}; };
struct SwBlender 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
uint32_t (*join)(uint8_t r, uint8_t g, uint8_t b, uint8_t a); typedef uint8_t(*SwAlpha)(uint8_t*); //blending alpha
uint8_t (*luma)(uint8_t* c);
};
struct SwCompositor; struct SwCompositor;
struct SwSurface : Surface 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) 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 struct SwCompositor : Compositor
@ -273,15 +277,25 @@ static inline SwCoord TO_SWCOORD(float val)
return SwCoord(val * 64.0f); 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) static inline uint32_t ALPHA_BLEND(uint32_t c, uint32_t a)
{ {
return (((((c >> 8) & 0x00ff00ff) * a + 0x00ff00ff) & 0xff00ff00) + return (((((c >> 8) & 0x00ff00ff) * a + 0x00ff00ff) & 0xff00ff00) +
((((c & 0x00ff00ff) * a + 0x00ff00ff) >> 8) & 0x00ff00ff)); ((((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) static inline SwCoord HALF_STROKE(float width)
@ -289,6 +303,207 @@ static inline SwCoord HALF_STROKE(float width)
return TO_SWCOORD(width * 0.5f); 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 mathMultiply(int64_t a, int64_t b);
int64_t mathDivide(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); 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); bool shapeGenStrokeRle(SwShape* shape, const RenderShape* rshape, const Matrix* transform, const SwBBox& clipRegion, SwBBox& renderRegion, SwMpool* mpool, unsigned tid);
void shapeFree(SwShape* shape); void shapeFree(SwShape* shape);
void shapeDelStroke(SwShape* shape); void shapeDelStroke(SwShape* shape);
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);
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);
void shapeResetFill(SwShape* shape); void shapeResetFill(SwShape* shape);
void shapeResetStrokeFill(SwShape* shape); void shapeResetStrokeFill(SwShape* shape);
void shapeDelFill(SwShape* shape); void shapeDelFill(SwShape* shape);
@ -333,11 +548,16 @@ void imageDelOutline(SwImage* image, SwMpool* mpool, uint32_t tid);
void imageReset(SwImage* image); void imageReset(SwImage* image);
void imageFree(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 fillReset(SwFill* fill);
void fillFree(SwFill* fill); void fillFree(SwFill* fill);
void fillFetchLinear(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len); //OPTIMIZE_ME: Skip the function pointer access
void fillFetchRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len); 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(SwRleData* rle, const SwOutline* outline, const SwBBox& renderRegion, bool antiAlias);
SwRleData* rleRender(const SwBBox* bbox); SwRleData* rleRender(const SwBBox* bbox);
@ -358,11 +578,12 @@ void mpoolRetStrokeOutline(SwMpool* mpool, unsigned idx);
bool rasterCompositor(SwSurface* surface); bool rasterCompositor(SwSurface* surface);
bool rasterGradientShape(SwSurface* surface, SwShape* shape, unsigned id); 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 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 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 rasterGradientStroke(SwSurface* surface, SwShape* shape, unsigned id);
bool rasterClear(SwSurface* surface, uint32_t x, uint32_t y, uint32_t w, uint32_t h); 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 rasterUnpremultiply(Surface* surface);
void rasterPremultiply(Surface* surface); void rasterPremultiply(Surface* surface);
bool rasterConvertCS(Surface* surface, ColorSpace to); bool rasterConvertCS(Surface* surface, ColorSpace to);

View File

@ -33,7 +33,7 @@
#define FIXPT_SIZE (1<<FIXPT_BITS) #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) { if (!fill->ctable) {
fill->ctable = static_cast<uint32_t*>(malloc(GRADIENT_STOP_SIZE * sizeof(uint32_t))); 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 pColors = colors;
auto a = (pColors->a * opacity) / 255; auto a = MULTIPLY(pColors->a, opacity);
if (a < 255) fill->translucent = true; if (a < 255) fill->translucent = true;
auto r = pColors->r; auto r = pColors->r;
auto g = pColors->g; auto g = pColors->g;
auto b = pColors->b; 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 inc = 1.0f / static_cast<float>(GRADIENT_STOP_SIZE);
auto pos = 1.5f * inc; auto pos = 1.5f * inc;
@ -70,17 +70,17 @@ static bool _updateColorTable(SwFill* fill, const Fill* fdata, const SwSurface*
auto curr = colors + j; auto curr = colors + j;
auto next = curr + 1; auto next = curr + 1;
auto delta = 1.0f / (next->offset - curr->offset); 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; 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) { while (pos < next->offset && i < GRADIENT_STOP_SIZE) {
auto t = (pos - curr->offset) * delta; auto t = (pos - curr->offset) * delta;
auto dist = static_cast<int32_t>(255 * t); auto dist = static_cast<int32_t>(255 * t);
auto dist2 = 255 - dist; 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)); fill->ctable[i] = ALPHA_BLEND((color | 0xff000000), (color >> 24));
++i; ++i;
@ -233,7 +233,7 @@ static inline uint32_t _pixel(const SwFill* fill, float pos)
/* External Class Implementation */ /* 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 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; 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 detFirstDerivative = 2.0f * (fill->radial.a11 * rx + fill->radial.a21 * ry) + 0.5f * detSecondDerivative;
auto det = rx * rx + ry * ry; auto det = rx * rx + ry * ry;
for (uint32_t i = 0 ; i < len ; ++i) { if (opacity == 255) {
*dst = _pixel(fill, sqrtf(det)); for (uint32_t i = 0 ; i < len ; ++i, ++dst, cmp += csize) {
++dst; *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; det += detFirstDerivative;
detFirstDerivative += detSecondDerivative; 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 //Rotation
float rx = x + 0.5f; 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)) { if (mathZero(inc)) {
auto color = _fixedPixel(fill, static_cast<int32_t>(t * FIXPT_SIZE)); 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; return;
} }
@ -275,16 +407,15 @@ void fillFetchLinear(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x,
if (v < vMax && v > vMin) { if (v < vMax && v > vMin) {
auto t2 = static_cast<int32_t>(t * FIXPT_SIZE); auto t2 = static_cast<int32_t>(t * FIXPT_SIZE);
auto inc2 = static_cast<int32_t>(inc * FIXPT_SIZE); auto inc2 = static_cast<int32_t>(inc * FIXPT_SIZE);
for (uint32_t j = 0; j < len; ++j) { for (uint32_t j = 0; j < len; ++j, ++dst) {
*dst = _fixedPixel(fill, t2); *dst = op(_fixedPixel(fill, t2), *dst, a);
++dst;
t2 += inc2; t2 += inc2;
} }
//we have to fallback to float math //we have to fallback to float math
} else { } else {
uint32_t counter = 0; uint32_t counter = 0;
while (counter++ < len) { while (counter++ < len) {
*dst = _pixel(fill, t / GRADIENT_STOP_SIZE); *dst = op(_pixel(fill, t / GRADIENT_STOP_SIZE), *dst, a);
++dst; ++dst;
t += inc; 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; 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); image->outline = mpoolReqOutline(mpool, tid);
auto outline = image->outline; auto outline = image->outline;
if (outline->reservedPtsCnt < 5) { outline->pts.reserve(5);
outline->reservedPtsCnt = 5; outline->types.reserve(5);
outline->pts = static_cast<SwPoint*>(realloc(outline->pts, outline->reservedPtsCnt * sizeof(SwPoint))); outline->cntrs.reserve(1);
outline->types = static_cast<uint8_t*>(realloc(outline->types, outline->reservedPtsCnt * sizeof(uint8_t))); outline->closed.reserve(1);
}
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;
}
Point to[4]; Point to[4];
if (mesh->triangleCnt > 0) { 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++) { for (int i = 0; i < 4; i++) {
outline->pts[outline->ptsCnt] = mathTransform(&to[i], transform); outline->pts.push(mathTransform(&to[i], transform));
outline->types[outline->ptsCnt] = SW_CURVE_TYPE_POINT; outline->types.push(SW_CURVE_TYPE_POINT);
++outline->ptsCnt;
} }
outline->pts[outline->ptsCnt] = outline->pts[0]; outline->pts.push(outline->pts.data[0]);
outline->types[outline->ptsCnt] = SW_CURVE_TYPE_POINT; outline->types.push(SW_CURVE_TYPE_POINT);
++outline->ptsCnt; outline->cntrs.push(outline->pts.count - 1);
outline->closed.push(true);
outline->cntrs[outline->cntrsCnt] = outline->ptsCnt - 1;
++outline->cntrsCnt;
image->outline = outline; image->outline = outline;

View File

@ -465,9 +465,9 @@ bool mathUpdateOutlineBBox(const SwOutline* outline, const SwBBox& clipRegion, S
{ {
if (!outline) return false; 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(); renderRegion.reset();
return false; return false;
} }
@ -477,9 +477,7 @@ bool mathUpdateOutlineBBox(const SwOutline* outline, const SwBBox& clipRegion, S
auto yMin = pt->y; auto yMin = pt->y;
auto yMax = pt->y; auto yMax = pt->y;
++pt; for (++pt; pt < outline->pts.end(); ++pt) {
for (uint32_t i = 1; i < outline->ptsCnt; ++i, ++pt) {
if (xMin > pt->x) xMin = pt->x; if (xMin > pt->x) xMin = pt->x;
if (xMax < pt->x) xMax = pt->x; if (xMax < pt->x) xMax = pt->x;
if (yMin > pt->y) yMin = pt->y; 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) void mpoolRetOutline(SwMpool* mpool, unsigned idx)
{ {
mpool->outline[idx].cntrsCnt = 0; mpool->outline[idx].pts.clear();
mpool->outline[idx].ptsCnt = 0; 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) void mpoolRetStrokeOutline(SwMpool* mpool, unsigned idx)
{ {
mpool->strokeOutline[idx].cntrsCnt = 0; mpool->strokeOutline[idx].pts.clear();
mpool->strokeOutline[idx].ptsCnt = 0; 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; SwOutline* p;
for (unsigned i = 0; i < mpool->allocSize; ++i) { for (unsigned i = 0; i < mpool->allocSize; ++i) {
//Outline //Outline
p = &mpool->outline[i]; p = &mpool->outline[i];
p->pts.reset();
free(p->cntrs); p->cntrs.reset();
p->cntrs = nullptr; p->types.reset();
p->closed.reset();
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;
//StrokeOutline //StrokeOutline
p = &mpool->strokeOutline[i]; p = &mpool->strokeOutline[i];
p->pts.reset();
free(p->cntrs); p->cntrs.reset();
p->cntrs = nullptr; p->types.reset();
p->closed.reset();
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;
} }
return true; 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 //1. calculate how many iterations we need to cover the length
uint32_t iterations = len / N_32BITS_IN_256REG; uint32_t iterations = len / N_32BITS_IN_256REG;
@ -89,12 +89,12 @@ static bool avxRasterTranslucentRect(SwSurface* surface, const SwBBox& region, u
return false; 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 buffer = surface->buf32 + (region.min.y * surface->stride) + region.min.x;
auto h = static_cast<uint32_t>(region.max.y - region.min.y); 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 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 avxColor = _mm_set1_epi32(color);
auto avxIalpha = _mm_set1_epi8(ialpha); auto avxIalpha = _mm_set1_epi8(ialpha);
@ -138,7 +138,7 @@ static bool avxRasterTranslucentRle(SwSurface* surface, const SwRleData* rle, ui
return false; return false;
} }
auto color = surface->blender.join(r, g, b, a); auto color = surface->join(r, g, b, a);
auto span = rle->spans; auto span = rle->spans;
uint32_t src; 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); if (span->coverage < 255) src = ALPHA_BLEND(color, span->coverage);
else src = color; 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) //1. fill the not aligned memory (for 128-bit registers a 16-bytes alignment is required)
auto notAligned = ((uintptr_t)dst & 0xf) / 4; auto notAligned = ((uintptr_t)dst & 0xf) / 4;

View File

@ -21,9 +21,41 @@
*/ */
template<typename PIXEL_T> 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; 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; while (len--) *dst++ = val;
} }
@ -34,14 +66,15 @@ static bool inline cRasterTranslucentRle(SwSurface* surface, const SwRleData* rl
//32bit channels //32bit channels
if (surface->channelSize == sizeof(uint32_t)) { 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; uint32_t src;
for (uint32_t i = 0; i < rle->size; ++i, ++span) { for (uint32_t i = 0; i < rle->size; ++i, ++span) {
auto dst = &surface->buf32[span->y * surface->stride + span->x]; auto dst = &surface->buf32[span->y * surface->stride + span->x];
if (span->coverage < 255) src = ALPHA_BLEND(color, span->coverage); if (span->coverage < 255) src = ALPHA_BLEND(color, span->coverage);
else src = color; else src = color;
auto ialpha = IA(src);
for (uint32_t x = 0; x < span->len; ++x, ++dst) { 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 //8bit grayscale
@ -49,10 +82,11 @@ static bool inline cRasterTranslucentRle(SwSurface* surface, const SwRleData* rl
uint8_t src; uint8_t src;
for (uint32_t i = 0; i < rle->size; ++i, ++span) { for (uint32_t i = 0; i < rle->size; ++i, ++span) {
auto dst = &surface->buf8[span->y * surface->stride + span->x]; 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; else src = a;
auto ialpha = ~a;
for (uint32_t x = 0; x < span->len; ++x, ++dst) { 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 //32bits channels
if (surface->channelSize == sizeof(uint32_t)) { 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 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) { for (uint32_t y = 0; y < h; ++y) {
auto dst = &buffer[y * surface->stride]; auto dst = &buffer[y * surface->stride];
for (uint32_t x = 0; x < w; ++x, ++dst) { for (uint32_t x = 0; x < w; ++x, ++dst) {
@ -79,10 +113,11 @@ static bool inline cRasterTranslucentRect(SwSurface* surface, const SwBBox& regi
//8bit grayscale //8bit grayscale
} else if (surface->channelSize == sizeof(uint8_t)) { } else if (surface->channelSize == sizeof(uint8_t)) {
auto buffer = surface->buf8 + (region.min.y * surface->stride) + region.min.x; auto buffer = surface->buf8 + (region.min.y * surface->stride) + region.min.x;
auto ialpha = ~a;
for (uint32_t y = 0; y < h; ++y) { for (uint32_t y = 0; y < h; ++y) {
auto dst = &buffer[y * surface->stride]; auto dst = &buffer[y * surface->stride];
for (uint32_t x = 0; x < w; ++x, ++dst) { 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); TVGLOG("SW_ENGINE", "Convert ColorSpace ABGR - ARGB [Size: %d x %d]", surface->w, surface->h);
auto buffer = surface->buf32; //64bits faster converting
for (uint32_t y = 0; y < surface->h; ++y, buffer += surface->stride) { if (surface->w % 2 == 0) {
auto dst = buffer; auto buffer = reinterpret_cast<uint64_t*>(surface->buf32);
for (uint32_t x = 0; x < surface->w; ++x, ++dst) { for (uint32_t y = 0; y < surface->h; ++y, buffer += surface->stride / 2) {
auto c = *dst; auto dst = buffer;
//flip Blue, Red channels for (uint32_t x = 0; x < surface->w / 2; ++x, ++dst) {
*dst = (c & 0xff000000) + ((c & 0x00ff0000) >> 16) + (c & 0x0000ff00) + ((c & 0x000000ff) << 16); 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; 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 iterations = len / 4;
uint32_t neonFilled = iterations * 4; uint32_t neonFilled = iterations * 4;
@ -67,7 +67,7 @@ static bool neonRasterTranslucentRle(SwSurface* surface, const SwRleData* rle, u
else src = color; else src = color;
auto dst = &surface->buf32[span->y * surface->stride + span->x]; 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) { if ((((uint32_t) dst) & 0x7) != 0) {
//fill not aligned byte //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 buffer = surface->buf32 + (region.min.y * surface->stride) + region.min.x;
auto h = static_cast<uint32_t>(region.max.y - region.min.y); 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 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 vColor = vdup_n_u32(color);
auto vIalpha = vdup_n_u8((uint8_t) ialpha); 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; b = tmp;
} }
//Careful! Shared resource, No support threading //Careful! Shared resource, No support threading
static float dudx, dvdx; static float dudx, dvdx;
static float dxdya, dxdyb, dudya, dvdya; 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 float _dudx = dudx, _dvdx = dvdx;
#define TEXMAP_MASKING float _dxdya = dxdya, _dxdyb = dxdyb, _dudya = dudya, _dvdya = dvdya;
#include "tvgSwRasterTexmapInternal.h" float _xa = xa, _xb = xb, _ua = ua, _va = va;
#undef TEXMAP_MASKING auto sbuf = image->buf32;
#undef TEXMAP_TRANSLUCENT 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 float _dudx = dudx, _dvdx = dvdx;
#include "tvgSwRasterTexmapInternal.h" float _dxdya = dxdya, _dxdyb = dxdyb, _dudya = dudya, _dvdya = dvdya;
#undef TEXMAP_MASKING 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 if (surface->compositor->method == CompositeMethod::IntersectMask) {
#include "tvgSwRasterTexmapInternal.h" _rasterMaskedPolygonImageSegmentInt(surface, image, region, yStart, yEnd, aaSpans, opacity, dirFlag);
#undef TEXMAP_TRANSLUCENT } 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. */ /* 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 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}; 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]; 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 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 //Longer edge is on the left side
if (!side) { if (!side) {
@ -190,14 +897,14 @@ static void _rasterPolygonImage(SwSurface* surface, const SwImage* image, const
dxdyb = dxdy[0]; dxdyb = dxdy[0];
xb = x[0] + dy * dxdyb + (off_y * dxdyb); xb = x[0] + dy * dxdyb + (off_y * dxdyb);
if (blender) { if (compositing) {
if (opacity == 255) _rasterPolygonImageSegment(surface, image, region, yi[0], yi[1], blender, aaSpans); if (_matting(surface)) _rasterPolygonImageSegment(surface, image, region, yi[0], yi[1], aaSpans, opacity, true);
else _rasterPolygonImageSegment(surface, image, region, yi[0], yi[1], opacity, blender, aaSpans); 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 { } else {
if (opacity == 255) _rasterPolygonImageSegment(surface, image, region, yi[0], yi[1], aaSpans); _rasterPolygonImageSegment(surface, image, region, yi[0], yi[1], aaSpans, opacity, false);
else _rasterPolygonImageSegment(surface, image, region, yi[0], yi[1], opacity, aaSpans);
} }
upper = true; upper = true;
} }
//Draw lower segment if possibly visible //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 // Set right edge X-slope and perform subpixel pre-stepping
dxdyb = dxdy[2]; dxdyb = dxdy[2];
xb = x[1] + (1 - (y[1] - yi[1])) * dxdyb + (off_y * dxdyb); xb = x[1] + (1 - (y[1] - yi[1])) * dxdyb + (off_y * dxdyb);
if (blender) { if (compositing) {
if (opacity == 255) _rasterPolygonImageSegment(surface, image, region, yi[1], yi[2], blender, aaSpans); if (_matting(surface)) _rasterPolygonImageSegment(surface, image, region, yi[1], yi[2], aaSpans, opacity, true);
else _rasterPolygonImageSegment(surface, image, region, yi[1], yi[2], opacity, blender, aaSpans); 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 { } else {
if (opacity == 255) _rasterPolygonImageSegment(surface, image, region, yi[1], yi[2], aaSpans); _rasterPolygonImageSegment(surface, image, region, yi[1], yi[2], aaSpans, opacity, false);
else _rasterPolygonImageSegment(surface, image, region, yi[1], yi[2], opacity, aaSpans);
} }
} }
//Longer edge is on the right side //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); ua = u[0] + dy * dudya + (off_y * dudya);
va = v[0] + dy * dvdya + (off_y * dvdya); va = v[0] + dy * dvdya + (off_y * dvdya);
if (blender) { if (compositing) {
if (opacity == 255) _rasterPolygonImageSegment(surface, image, region, yi[0], yi[1], blender, aaSpans); if (_matting(surface)) _rasterPolygonImageSegment(surface, image, region, yi[0], yi[1], aaSpans, opacity, true);
else _rasterPolygonImageSegment(surface, image, region, yi[0], yi[1], opacity, blender, aaSpans); 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 { } else {
if (opacity == 255) _rasterPolygonImageSegment(surface, image, region, yi[0], yi[1], aaSpans); _rasterPolygonImageSegment(surface, image, region, yi[0], yi[1], aaSpans, opacity, false);
else _rasterPolygonImageSegment(surface, image, region, yi[0], yi[1], opacity, aaSpans);
} }
upper = true; upper = true;
} }
//Draw lower segment if possibly visible //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); ua = u[1] + dy * dudya + (off_y * dudya);
va = v[1] + dy * dvdya + (off_y * dvdya); va = v[1] + dy * dvdya + (off_y * dvdya);
if (blender) { if (compositing) {
if (opacity == 255) _rasterPolygonImageSegment(surface, image, region, yi[1], yi[2], blender, aaSpans); if (_matting(surface)) _rasterPolygonImageSegment(surface, image, region, yi[1], yi[2], aaSpans, opacity, true);
else _rasterPolygonImageSegment(surface, image, region, yi[1], yi[2], opacity, blender, aaSpans); 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 { } else {
if (opacity == 255) _rasterPolygonImageSegment(surface, image, region, yi[1], yi[2], aaSpans); _rasterPolygonImageSegment(surface, image, region, yi[1], yi[2], aaSpans, opacity, false);
else _rasterPolygonImageSegment(surface, image, region, yi[1], yi[2], opacity, aaSpans);
} }
} }
} }
@ -508,7 +1217,7 @@ static bool _apply(SwSurface* surface, AASpans* aaSpans)
pos = 1; pos = 1;
while (pos <= line->length[0]) { while (pos <= line->length[0]) {
*dst = INTERPOLATE((line->coverage[0] * pos), *dst, pixel); *dst = INTERPOLATE(*dst, pixel, line->coverage[0] * pos);
++dst; ++dst;
++pos; ++pos;
} }
@ -520,7 +1229,7 @@ static bool _apply(SwSurface* surface, AASpans* aaSpans)
pos = width; pos = width;
while ((int32_t)(width - line->length[1]) < pos) { 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; --dst;
--pos; --pos;
} }
@ -545,7 +1254,7 @@ static bool _apply(SwSurface* surface, AASpans* aaSpans)
| / | | / |
3 -- 2 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? //Exceptions: No dedicated drawing area?
if ((!image->rle && !region) || (image->rle && image->rle->size == 0)) return false; 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[1] = vertices[1];
polygon.vertex[2] = vertices[3]; polygon.vertex[2] = vertices[3];
_rasterPolygonImage(surface, image, region, opacity, polygon, blender, aaSpans); _rasterPolygonImage(surface, image, region, polygon, aaSpans, opacity);
//Draw the second polygon //Draw the second polygon
polygon.vertex[0] = vertices[1]; polygon.vertex[0] = vertices[1];
polygon.vertex[1] = vertices[2]; polygon.vertex[1] = vertices[2];
polygon.vertex[2] = vertices[3]; polygon.vertex[2] = vertices[3];
_rasterPolygonImage(surface, image, region, opacity, polygon, blender, aaSpans); _rasterPolygonImage(surface, image, region, polygon, aaSpans, opacity);
return _apply(surface, aaSpans); 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. Should provide two Polygons, one for each triangle.
// TODO: region? // 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? //Exceptions: No dedicated drawing area?
if ((!image->rle && !region) || (image->rle && image->rle->size == 0)) return false; 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); auto aaSpans = _AASpans(ys, ye, image, region);
if (aaSpans) { if (aaSpans) {
for (uint32_t i = 0; i < mesh->triangleCnt; i++) { 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 to surface (note: frees the AA spans)
_apply(surface, aaSpans); _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 struct SwTask : Task
{ {
Matrix* transform = nullptr;
SwSurface* surface = nullptr; SwSurface* surface = nullptr;
SwMpool* mpool = nullptr; SwMpool* mpool = nullptr;
RenderUpdateFlag flags = RenderUpdateFlag::None;
Array<RenderData> clips;
uint32_t opacity;
SwBBox bbox = {{0, 0}, {0, 0}}; //Whole Rendering Region 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 pushed = false; //Pushed into task list?
bool disposed = false; //Disposed task? bool disposed = false; //Disposed task?
@ -106,7 +106,7 @@ struct SwShapeTask : SwTask
if (HALF_STROKE(rshape->strokeWidth()) > 0) { if (HALF_STROKE(rshape->strokeWidth()) > 0) {
rshape->strokeColor(nullptr, nullptr, nullptr, &strokeAlpha); 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. //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) { if (flags & (RenderUpdateFlag::Path | RenderUpdateFlag::Transform) || prepareShape) {
uint8_t alpha = 0; uint8_t alpha = 0;
rshape->fillColor(nullptr, nullptr, nullptr, &alpha); 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); visibleFill = (alpha > 0 || rshape->fill);
if (visibleFill || visibleStroke || clipper) { if (visibleFill || visibleStroke || clipper) {
shapeReset(&shape); shapeReset(&shape);
@ -125,10 +125,6 @@ struct SwShapeTask : SwTask
} }
} }
//Decide Stroking Composition
if (visibleStroke && visibleFill && opacity < 255) cmpStroking = true;
else cmpStroking = false;
//Fill //Fill
if (flags & (RenderUpdateFlag::Gradient | RenderUpdateFlag::Transform | RenderUpdateFlag::Color)) { if (flags & (RenderUpdateFlag::Gradient | RenderUpdateFlag::Transform | RenderUpdateFlag::Color)) {
if (visibleFill || clipper) { if (visibleFill || clipper) {
@ -143,7 +139,7 @@ struct SwShapeTask : SwTask
if (auto fill = rshape->fill) { if (auto fill = rshape->fill) {
auto ctable = (flags & RenderUpdateFlag::Gradient) ? true : false; auto ctable = (flags & RenderUpdateFlag::Gradient) ? true : false;
if (ctable) shapeResetFill(&shape); 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 { } else {
shapeDelFill(&shape); shapeDelFill(&shape);
} }
@ -158,7 +154,7 @@ struct SwShapeTask : SwTask
if (auto fill = rshape->strokeFill()) { if (auto fill = rshape->strokeFill()) {
auto ctable = (flags & RenderUpdateFlag::GradientStroke) ? true : false; auto ctable = (flags & RenderUpdateFlag::GradientStroke) ? true : false;
if (ctable) shapeResetStrokeFill(&shape); 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 { } else {
shapeDelStrokeFill(&shape); shapeDelStrokeFill(&shape);
} }
@ -171,7 +167,7 @@ struct SwShapeTask : SwTask
shapeDelOutline(&shape, mpool, tid); shapeDelOutline(&shape, mpool, tid);
//Clip Path //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); auto clipper = static_cast<SwTask*>(*clip);
//Clip shape rle //Clip shape rle
if (shape.rle && !clipper->clip(shape.rle)) goto err; if (shape.rle && !clipper->clip(shape.rle)) goto err;
@ -232,7 +228,7 @@ struct SwSceneTask : SwTask
rleMerge(sceneRle, clipper1->rle(), clipper2->rle()); rleMerge(sceneRle, clipper1->rle(), clipper2->rle());
//Unify the remained clippers //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); auto clipper = static_cast<SwTask*>(*rd);
rleMerge(sceneRle, sceneRle, clipper->rle()); rleMerge(sceneRle, sceneRle, clipper->rle());
} }
@ -294,7 +290,7 @@ struct SwImageTask : SwTask
if (image.rle) { if (image.rle) {
//Clear current task memorypool here if the clippers would use the same memory pool //Clear current task memorypool here if the clippers would use the same memory pool
imageDelOutline(&image, mpool, tid); 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); auto clipper = static_cast<SwTask*>(*clip);
if (!clipper->clip(image.rle)) goto err; 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; uint8_t r, g, b, a;
if (auto fill = task->rshape->fill) { if (auto fill = task->rshape->fill) {
rasterGradientShape(surface, &task->shape, fill->identifier()); rasterGradientShape(surface, &task->shape, fill->identifier());
} else { } else {
task->rshape->fillColor(&r, &g, &b, &a); 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); 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; uint8_t r, g, b, a;
if (auto strokeFill = task->rshape->strokeFill()) { if (auto strokeFill = task->rshape->strokeFill()) {
rasterGradientStroke(surface, &task->shape, strokeFill->identifier()); rasterGradientStroke(surface, &task->shape, strokeFill->identifier());
} else { } else {
if (task->rshape->strokeColor(&r, &g, &b, &a)) { 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); if (a > 0) rasterStroke(surface, &task->shape, r, g, b, a);
} }
} }
@ -359,7 +355,7 @@ SwRenderer::~SwRenderer()
{ {
clearCompositors(); clearCompositors();
if (surface) delete(surface); delete(surface);
if (!sharedMpool) mpoolTerm(mpool); if (!sharedMpool) mpoolTerm(mpool);
@ -371,7 +367,7 @@ SwRenderer::~SwRenderer()
bool SwRenderer::clear() 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) { if ((*task)->disposed) {
delete(*task); delete(*task);
} else { } else {
@ -444,7 +440,7 @@ bool SwRenderer::preRender()
void SwRenderer::clearCompositors() void SwRenderer::clearCompositors()
{ {
//Free Composite Caches //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); free((*comp)->compositor->image.data);
delete((*comp)->compositor); delete((*comp)->compositor);
delete(*comp); delete(*comp);
@ -460,8 +456,9 @@ bool SwRenderer::postRender()
rasterUnpremultiply(surface); rasterUnpremultiply(surface);
} }
for (auto task = tasks.data; task < (tasks.data + tasks.count); ++task) { for (auto task = tasks.data; task < tasks.end(); ++task) {
(*task)->pushed = false; if ((*task)->disposed) delete(*task);
else (*task)->pushed = false;
} }
tasks.clear(); tasks.clear();
@ -490,41 +487,79 @@ bool SwRenderer::renderShape(RenderData data)
if (task->opacity == 0) return true; 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 //Main raster stage
if (task->rshape->stroke && task->rshape->stroke->strokeFirst) { if (task->rshape->stroke && task->rshape->stroke->strokeFirst) {
_renderStroke(task, surface, opacity); _renderStroke(task, surface, task->opacity);
_renderFill(task, surface, opacity); _renderFill(task, surface, task->opacity);
} else { } else {
_renderFill(task, surface, opacity); _renderFill(task, surface, task->opacity);
_renderStroke(task, surface, opacity); _renderStroke(task, surface, task->opacity);
} }
if (task->cmpStroking) endComposite(cmp);
return true; 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) RenderRegion SwRenderer::region(RenderData data)
{ {
return static_cast<SwTask*>(data)->bounds(); 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; if (!cmp) return false;
auto p = static_cast<SwCompositor*>(cmp); auto p = static_cast<SwCompositor*>(cmp);
@ -579,7 +614,7 @@ Compositor* SwRenderer::target(const RenderRegion& region, ColorSpace cs)
auto reqChannelSize = CHANNEL_SIZE(cs); auto reqChannelSize = CHANNEL_SIZE(cs);
//Use cached data //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) { if ((*p)->compositor->valid && (*p)->compositor->image.channelSize == reqChannelSize) {
cmp = *p; cmp = *p;
break; 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 (!surface) return task;
if (flags == RenderUpdateFlag::None) 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. //TODO: Failed threading them. It would be better if it's possible.
//See: https://github.com/thorvg/thorvg/issues/1409 //See: https://github.com/thorvg/thorvg/issues/1409
//Guarantee composition targets get ready. //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(); 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 //prepare task
auto task = static_cast<SwImageTask*>(data); auto task = static_cast<SwImageTask*>(data);
@ -728,11 +763,11 @@ RenderData SwRenderer::prepare(Surface* surface, const RenderMesh* mesh, RenderD
task->source = surface; task->source = surface;
task->mesh = mesh; 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 //prepare task
auto task = static_cast<SwSceneTask*>(data); 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. //TODO: Failed threading them. It would be better if it's possible.
//See: https://github.com/thorvg/thorvg/issues/1409 //See: https://github.com/thorvg/thorvg/issues/1409
//Guarantee composition targets get ready. //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(); 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 //prepare task
auto task = static_cast<SwShapeTask*>(data); auto task = static_cast<SwShapeTask*>(data);
@ -759,7 +794,7 @@ RenderData SwRenderer::prepare(const RenderShape& rshape, RenderData data, const
} }
task->clipper = clipper; 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 class SwRenderer : public RenderMethod
{ {
public: 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 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, uint32_t opacity, Array<RenderData>& clips, RenderUpdateFlag flags) 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, uint32_t opacity, Array<RenderData>& clips, 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 preRender() override;
bool renderShape(RenderData data) override; bool renderShape(RenderData data) override;
bool renderImage(RenderData data) override; bool renderImage(RenderData data) override;
@ -47,6 +47,7 @@ public:
RenderRegion region(RenderData data) override; RenderRegion region(RenderData data) override;
RenderRegion viewport() override; RenderRegion viewport() override;
bool viewport(const RenderRegion& vp) override; bool viewport(const RenderRegion& vp) override;
bool blend(BlendMethod method) override;
ColorSpace colorSpace() override; ColorSpace colorSpace() override;
bool clear() override; bool clear() override;
@ -55,7 +56,7 @@ public:
bool mempool(bool shared); bool mempool(bool shared);
Compositor* target(const RenderRegion& region, ColorSpace cs) override; 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; bool endComposite(Compositor* cmp) override;
void clearCompositors(); void clearCompositors();
@ -70,13 +71,12 @@ private:
Array<SwSurface*> compositors; //render targets cache list Array<SwSurface*> compositors; //render targets cache list
SwMpool* mpool; //private memory pool SwMpool* mpool; //private memory pool
RenderRegion vport; //viewport RenderRegion vport; //viewport
bool sharedMpool = true; //memory-pool behavior policy bool sharedMpool = true; //memory-pool behavior policy
SwRenderer(); SwRenderer();
~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 outline = rw.outline;
auto first = 0; //index of first point in contour auto first = 0; //index of first point in contour
for (uint32_t n = 0; n < outline->cntrsCnt; ++n) { for (auto cntr = outline->cntrs.data; cntr < outline->cntrs.end(); ++cntr) {
auto last = outline->cntrs[n]; auto last = *cntr;
auto limit = outline->pts + last; auto limit = outline->pts.data + last;
auto start = UPSCALE(outline->pts[first]); auto start = UPSCALE(outline->pts.data[first]);
auto pt = outline->pts + first; auto pt = outline->pts.data + first;
auto types = outline->types + first; auto types = outline->types.data + first;
/* A contour cannot start with a cubic control point! */ _moveTo(rw, UPSCALE(outline->pts.data[first]));
if (types[0] == SW_CURVE_TYPE_CUBIC) goto invalid_outline;
_moveTo(rw, UPSCALE(outline->pts[first]));
while (pt < limit) { while (pt < limit) {
++pt; ++pt;
@ -734,9 +731,6 @@ static bool _decomposeOutline(RleWorker& rw)
_lineTo(rw, UPSCALE(*pt)); _lineTo(rw, UPSCALE(*pt));
//types cubic //types cubic
} else { } else {
if (pt + 1 > limit || types[1] != SW_CURVE_TYPE_CUBIC)
goto invalid_outline;
pt += 2; pt += 2;
types += 2; types += 2;
@ -752,22 +746,15 @@ static bool _decomposeOutline(RleWorker& rw)
close: close:
first = last + 1; first = last + 1;
} }
return true;
invalid_outline:
TVGERR("SW_ENGINE", "Invalid Outline!");
return false;
} }
static int _genRle(RleWorker& rw) static int _genRle(RleWorker& rw)
{ {
if (setjmp(rw.jmpBuf) == 0) { if (setjmp(rw.jmpBuf) == 0) {
auto ret = _decomposeOutline(rw); _decomposeOutline(rw);
if (!rw.invalid) _recordCell(rw); if (!rw.invalid) _recordCell(rw);
if (ret) return 0; //success return 0;
else return 1; //fail
} }
return -1; //lack of cell memory 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) static void _outlineEnd(SwOutline& outline)
{ {
if (outline.ptsCnt == 0) return; if (outline.pts.empty()) return;
outline.cntrs.push(outline.pts.count - 1);
_growOutlineContour(outline, 1);
outline.cntrs[outline.cntrsCnt] = outline.ptsCnt - 1;
++outline.cntrsCnt;
} }
static void _outlineMoveTo(SwOutline& outline, const Point* to, const Matrix* transform) 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.pts.push(mathTransform(to, transform));
outline.types[outline.ptsCnt] = SW_CURVE_TYPE_POINT; outline.types.push(SW_CURVE_TYPE_POINT);
if (outline.ptsCnt > 0) {
_growOutlineContour(outline, 1);
outline.cntrs[outline.cntrsCnt] = outline.ptsCnt - 1;
++outline.cntrsCnt;
}
++outline.ptsCnt;
} }
static void _outlineLineTo(SwOutline& outline, const Point* to, const Matrix* transform) static void _outlineLineTo(SwOutline& outline, const Point* to, const Matrix* transform)
{ {
_growOutlinePoint(outline, 1); outline.pts.push(mathTransform(to, transform));
outline.types.push(SW_CURVE_TYPE_POINT);
outline.pts[outline.ptsCnt] = mathTransform(to, transform);
outline.types[outline.ptsCnt] = SW_CURVE_TYPE_POINT;
++outline.ptsCnt;
} }
static void _outlineCubicTo(SwOutline& outline, const Point* ctrl1, const Point* ctrl2, const Point* to, const Matrix* transform) 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.pts.push(mathTransform(ctrl2, transform));
outline.types[outline.ptsCnt] = SW_CURVE_TYPE_CUBIC; outline.types.push(SW_CURVE_TYPE_CUBIC);
++outline.ptsCnt;
outline.pts[outline.ptsCnt] = mathTransform(ctrl2, transform); outline.pts.push(mathTransform(to, transform));
outline.types[outline.ptsCnt] = SW_CURVE_TYPE_CUBIC; outline.types.push(SW_CURVE_TYPE_POINT);
++outline.ptsCnt;
outline.pts[outline.ptsCnt] = mathTransform(to, transform);
outline.types[outline.ptsCnt] = SW_CURVE_TYPE_POINT;
++outline.ptsCnt;
} }
@ -153,30 +101,21 @@ static void _outlineClose(SwOutline& outline)
{ {
uint32_t i = 0; uint32_t i = 0;
if (outline.cntrsCnt > 0) { if (outline.cntrs.count > 0) i = outline.cntrs.last() + 1;
i = outline.cntrs[outline.cntrsCnt - 1] + 1; else i = 0; //First Path
} else {
i = 0; //First Path
}
//Make sure there is at least one point in the current 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 //Close the path
_growOutlinePoint(outline, 1); outline.pts.push(outline.pts.data[i]);
outline.types.push(SW_CURVE_TYPE_POINT);
outline.pts[outline.ptsCnt] = outline.pts[i]; outline.closed.push(true);
outline.types[outline.ptsCnt] = SW_CURVE_TYPE_POINT;
++outline.ptsCnt;
outline.closed[outline.cntrsCnt] = true;
} }
static void _dashLineTo(SwDashStroke& dash, const Point* to, const Matrix* transform) 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}; Line cur = {dash.ptCur, *to};
auto len = _lineLength(cur.pt1, cur.pt2); 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) 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}; Bezier cur = {dash.ptCur, *ctrl1, *ctrl2, *to};
auto len = bezLength(cur); 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) static SwOutline* _genDashOutline(const RenderShape* rshape, const Matrix* transform)
{ {
const PathCommand* cmds = rshape->path.cmds; const PathCommand* cmds = rshape->path.cmds.data;
auto cmdCnt = rshape->path.cmdCnt; auto cmdCnt = rshape->path.cmds.count;
const Point* pts = rshape->path.pts; const Point* pts = rshape->path.pts.data;
auto ptsCnt = rshape->path.ptsCnt; auto ptsCnt = rshape->path.pts.count;
//No actual shape data //No actual shape data
if (cmdCnt == 0 || ptsCnt == 0) return nullptr; if (cmdCnt == 0 || ptsCnt == 0) return nullptr;
@ -323,8 +259,9 @@ static SwOutline* _genDashOutline(const RenderShape* rshape, const Matrix* trans
++outlineCntrsCnt; //for end ++outlineCntrsCnt; //for end
//No idea exact count.... Reserve Approximitely 20x... //No idea exact count.... Reserve Approximitely 20x...
_growOutlinePoint(*dash.outline, outlinePtsCnt * 20); dash.outline->pts.grow(20 * outlinePtsCnt);
_growOutlineContour(*dash.outline, outlineCntrsCnt * 20); dash.outline->types.grow(20 * outlinePtsCnt);
dash.outline->cntrs.grow(20 * outlineCntrsCnt);
while (cmdCnt-- > 0) { while (cmdCnt-- > 0) {
switch (*cmds) { switch (*cmds) {
@ -364,12 +301,12 @@ static SwOutline* _genDashOutline(const RenderShape* rshape, const Matrix* trans
static bool _axisAlignedRect(const SwOutline* outline) static bool _axisAlignedRect(const SwOutline* outline)
{ {
//Fast Track: axis-aligned rectangle? //Fast Track: axis-aligned rectangle?
if (outline->ptsCnt != 5) return false; if (outline->pts.count != 5) return false;
auto pt1 = outline->pts + 0; auto pt1 = outline->pts.data + 0;
auto pt2 = outline->pts + 1; auto pt2 = outline->pts.data + 1;
auto pt3 = outline->pts + 2; auto pt3 = outline->pts.data + 2;
auto pt4 = outline->pts + 3; auto pt4 = outline->pts.data + 3;
auto a = SwPoint{pt1->x, pt3->y}; auto a = SwPoint{pt1->x, pt3->y};
auto b = SwPoint{pt3->x, pt1->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) static bool _genOutline(SwShape* shape, const RenderShape* rshape, const Matrix* transform, SwMpool* mpool, unsigned tid, bool hasComposite)
{ {
const PathCommand* cmds = rshape->path.cmds; const PathCommand* cmds = rshape->path.cmds.data;
auto cmdCnt = rshape->path.cmdCnt; auto cmdCnt = rshape->path.cmds.count;
const Point* pts = rshape->path.pts; const Point* pts = rshape->path.pts.data;
auto ptsCnt = rshape->path.ptsCnt; auto ptsCnt = rshape->path.pts.count;
//No actual shape data //No actual shape data
if (cmdCnt == 0 || ptsCnt == 0) return false; 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); shape->outline = mpoolReqOutline(mpool, tid);
auto outline = shape->outline; auto outline = shape->outline;
_growOutlinePoint(*outline, outlinePtsCnt); outline->pts.grow(outlinePtsCnt);
outline->types.grow(outlinePtsCnt);
outline->cntrs.grow(outlineCntrsCnt);
if (_growOutlineContour(*outline, outlineCntrsCnt)) { //Dash outlines are always opened.
_reserveOutlineClose(*outline); //Only normal outlines use this information, it sholud be same to their contour counts.
} else { outline->closed.reserve(outline->cntrs.reserved);
_resetOutlineClose(*outline);
} memset(outline->closed.data, 0x0, sizeof(bool) * outline->closed.reserved);
//Generate Outlines //Generate Outlines
while (cmdCnt-- > 0) { while (cmdCnt-- > 0) {
@ -605,10 +543,10 @@ bool shapeGenStrokeRle(SwShape* shape, const RenderShape* rshape, const Matrix*
fail: fail:
if (freeOutline) { if (freeOutline) {
if (shapeOutline->cntrs) free(shapeOutline->cntrs); free(shapeOutline->cntrs.data);
if (shapeOutline->pts) free(shapeOutline->pts); free(shapeOutline->pts.data);
if (shapeOutline->types) free(shapeOutline->types); free(shapeOutline->types.data);
if (shapeOutline->closed) free(shapeOutline->closed); free(shapeOutline->closed.data);
free(shapeOutline); free(shapeOutline);
} }
mpoolRetStrokeOutline(mpool, tid); 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); 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); 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; tag[2] = SW_STROKE_TAG_POINT;
border->ptsCnt += 3; border->ptsCnt += 3;
border->movable = false; border->movable = false;
} }
@ -193,7 +192,6 @@ static void _borderLineTo(SwStrokeBorder* border, const SwPoint& to, bool movabl
//move last point //move last point
border->pts[border->ptsCnt - 1] = to; border->pts[border->ptsCnt - 1] = to;
} else { } else {
//don't add zero-length line_to //don't add zero-length line_to
if (border->ptsCnt > 0 && (border->pts[border->ptsCnt - 1] - to).small()) return; 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) static void _outside(SwStroke& stroke, int32_t side, SwFixed lineLength)
{ {
constexpr SwFixed MITER_LIMIT = 4 * (1 << 16);
auto border = stroke.borders + side; auto border = stroke.borders + side;
if (stroke.join == StrokeJoin::Round) { if (stroke.join == StrokeJoin::Round) {
@ -257,7 +253,7 @@ static void _outside(SwStroke& stroke, int32_t side, SwFixed lineLength)
} }
thcos = mathCos(theta); thcos = mathCos(theta);
auto sigma = mathMultiply(MITER_LIMIT, thcos); auto sigma = mathMultiply(stroke.miterlimit, thcos);
//is miter limit exceeded? //is miter limit exceeded?
if (sigma < 0x10000L) bevel = true; 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) 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; //if all control points are coincident, this is a no-op; avoid creating a spurious corner
avoid creating a spurious corner */
if ((stroke.center - ctrl1).small() && (ctrl1 - ctrl2).small() && (ctrl2 - to).small()) { if ((stroke.center - ctrl1).small() && (ctrl1 - ctrl2).small() && (ctrl2 - to).small()) {
stroke.center = to; stroke.center = to;
return; return;
@ -499,8 +494,7 @@ static void _cubicTo(SwStroke& stroke, const SwPoint& ctrl1, const SwPoint& ctrl
auto border = stroke.borders; auto border = stroke.borders;
int32_t side = 0; int32_t side = 0;
while (side <= 1) while (side < 2) {
{
auto rotate = SIDE_TO_ROTATE(side); auto rotate = SIDE_TO_ROTATE(side);
//compute control points //compute control points
@ -521,7 +515,6 @@ static void _cubicTo(SwStroke& stroke, const SwPoint& ctrl1, const SwPoint& ctrl
_end += arc[0]; _end += arc[0];
if (stroke.handleWideStrokes) { if (stroke.handleWideStrokes) {
/* determine whether the border radius is greater than the radius of /* determine whether the border radius is greater than the radius of
curvature of the original arc */ curvature of the original arc */
auto _start = border->pts[border->ptsCnt - 1]; auto _start = border->pts[border->ptsCnt - 1];
@ -556,8 +549,6 @@ static void _cubicTo(SwStroke& stroke, const SwPoint& ctrl1, const SwPoint& ctrl
++border; ++border;
continue; continue;
} }
//else fall through
} }
_borderCubicTo(border, _ctrl1, _ctrl2, _end); _borderCubicTo(border, _ctrl1, _ctrl2, _end);
++side; ++side;
@ -653,7 +644,6 @@ static void _addReverseLeft(SwStroke& stroke, bool opened)
if (ttag == SW_STROKE_TAG_BEGIN || ttag == SW_STROKE_TAG_END) if (ttag == SW_STROKE_TAG_BEGIN || ttag == SW_STROKE_TAG_END)
dstTag[0] ^= (SW_STROKE_TAG_BEGIN | SW_STROKE_TAG_END); dstTag[0] ^= (SW_STROKE_TAG_BEGIN | SW_STROKE_TAG_END);
} }
--srcPt; --srcPt;
--srcTag; --srcTag;
++dstPt; ++dstPt;
@ -779,36 +769,27 @@ fail:
static void _exportBorderOutline(const SwStroke& stroke, SwOutline* outline, uint32_t side) static void _exportBorderOutline(const SwStroke& stroke, SwOutline* outline, uint32_t side)
{ {
auto border = stroke.borders + side; auto border = stroke.borders + side;
if (border->ptsCnt == 0) return;
if (border->ptsCnt == 0) return; //invalid border memcpy(outline->pts.data + outline->pts.count, border->pts, border->ptsCnt * sizeof(SwPoint));
memcpy(outline->pts + outline->ptsCnt, border->pts, border->ptsCnt * sizeof(SwPoint));
auto cnt = border->ptsCnt; auto cnt = border->ptsCnt;
auto src = border->tags; auto src = border->tags;
auto tags = outline->types + outline->ptsCnt; auto tags = outline->types.data + outline->types.count;
auto cntrs = outline->cntrs + outline->cntrsCnt; auto idx = outline->pts.count;
auto idx = outline->ptsCnt;
while (cnt > 0) { while (cnt > 0) {
if (*src & SW_STROKE_TAG_POINT) *tags = SW_CURVE_TYPE_POINT; if (*src & SW_STROKE_TAG_POINT) *tags = SW_CURVE_TYPE_POINT;
else if (*src & SW_STROKE_TAG_CUBIC) *tags = SW_CURVE_TYPE_CUBIC; else if (*src & SW_STROKE_TAG_CUBIC) *tags = SW_CURVE_TYPE_CUBIC;
else { else TVGERR("SW_ENGINE", "Invalid stroke tag was given! = %d", *src);
//LOG: What type of stroke outline?? if (*src & SW_STROKE_TAG_END) outline->cntrs.push(idx);
}
if (*src & SW_STROKE_TAG_END) {
*cntrs = idx;
++cntrs;
++outline->cntrsCnt;
}
++src; ++src;
++tags; ++tags;
++idx; ++idx;
--cnt; --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->width = HALF_STROKE(rshape->strokeWidth());
stroke->cap = rshape->strokeCap(); stroke->cap = rshape->strokeCap();
stroke->miterlimit = static_cast<SwFixed>(rshape->strokeMiterlimit()) << 16;
//Save line join: it can be temporarily changed when stroking curves... //Save line join: it can be temporarily changed when stroking curves...
stroke->joinSaved = stroke->join = rshape->strokeJoin(); 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) bool strokeParseOutline(SwStroke* stroke, const SwOutline& outline)
{ {
uint32_t first = 0; uint32_t first = 0;
uint32_t i = 0;
for (uint32_t i = 0; i < outline.cntrsCnt; ++i) { for (auto cntr = outline.cntrs.data; cntr < outline.cntrs.end(); ++cntr, ++i) {
auto last = outline.cntrs[i]; //index of last point in contour auto last = *cntr; //index of last point in contour
auto limit = outline.pts + last; auto limit = outline.pts.data + last;
//Skip empty points //Skip empty points
if (last <= first) { if (last <= first) {
@ -869,15 +852,15 @@ bool strokeParseOutline(SwStroke* stroke, const SwOutline& outline)
continue; continue;
} }
auto start = outline.pts[first]; auto start = outline.pts.data[first];
auto pt = outline.pts + first; auto pt = outline.pts.data + first;
auto types = outline.types + first; auto types = outline.types.data + first;
auto type = types[0]; auto type = types[0];
//A contour cannot start with a cubic control point //A contour cannot start with a cubic control point
if (type == SW_CURVE_TYPE_CUBIC) return false; 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); _beginSubPath(*stroke, start, closed);
@ -903,7 +886,6 @@ bool strokeParseOutline(SwStroke* stroke, const SwOutline& outline)
goto close; goto close;
} }
} }
close: close:
if (!stroke->firstPt) _endSubPath(*stroke); if (!stroke->firstPt) _endSubPath(*stroke);
first = last + 1; first = last + 1;
@ -923,15 +905,9 @@ SwOutline* strokeExportOutline(SwStroke* stroke, SwMpool* mpool, unsigned tid)
auto cntrsCnt = count2 + count4; auto cntrsCnt = count2 + count4;
auto outline = mpoolReqStrokeOutline(mpool, tid); auto outline = mpoolReqStrokeOutline(mpool, tid);
if (outline->reservedPtsCnt < ptsCnt) { outline->pts.reserve(ptsCnt);
outline->pts = static_cast<SwPoint*>(realloc(outline->pts, sizeof(SwPoint) * ptsCnt)); outline->types.reserve(ptsCnt);
outline->types = static_cast<uint8_t*>(realloc(outline->types, sizeof(uint8_t) * ptsCnt)); outline->cntrs.reserve(cntrsCnt);
outline->reservedPtsCnt = ptsCnt;
}
if (outline->reservedCntrsCnt < cntrsCnt) {
outline->cntrs = static_cast<uint32_t*>(realloc(outline->cntrs, sizeof(uint32_t) * cntrsCnt));
outline->reservedCntrsCnt = cntrsCnt;
}
_exportBorderOutline(*stroke, outline, 0); //left _exportBorderOutline(*stroke, outline, 0); //left
_exportBorderOutline(*stroke, outline, 1); //right _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 count = 0;
uint32_t reserved = 0; uint32_t reserved = 0;
Array(){}
Array(const Array& rhs)
{
reset();
*this = rhs;
}
void push(T element) void push(T element)
{ {
if (count + 1 > reserved) { if (count + 1 > reserved) {
reserved = (count + 1) * 2; reserved = count + (count + 2) / 2;
auto p = data;
data = static_cast<T*>(realloc(data, sizeof(T) * reserved)); data = static_cast<T*>(realloc(data, sizeof(T) * reserved));
if (!data) {
data = p;
return;
}
} }
data[count++] = element; 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) bool reserve(uint32_t size)
{ {
if (size > reserved) { if (size > reserved) {
reserved = size; reserved = size;
auto p = data;
data = static_cast<T*>(realloc(data, sizeof(T) * reserved)); data = static_cast<T*>(realloc(data, sizeof(T) * reserved));
if (!data) {
data = p;
return false;
}
} }
return true; return true;
} }
@ -68,11 +73,21 @@ struct Array
return reserve(count + size); return reserve(count + size);
} }
T* ptr() T* end() const
{ {
return data + count; return data + count;
} }
T& last()
{
return data[count - 1];
}
T& first()
{
return data[0];
}
void pop() void pop()
{ {
if (count > 0) --count; if (count > 0) --count;
@ -80,10 +95,8 @@ struct Array
void reset() void reset()
{ {
if (data) { free(data);
free(data); data = nullptr;
data = nullptr;
}
count = reserved = 0; count = reserved = 0;
} }
@ -92,16 +105,21 @@ struct Array
count = 0; count = 0;
} }
bool empty() const
{
return count == 0;
}
void operator=(const Array& rhs) void operator=(const Array& rhs)
{ {
reserve(rhs.count); 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; count = rhs.count;
} }
~Array() ~Array()
{ {
if (data) free(data); free(data);
} }
}; };

View File

@ -20,10 +20,11 @@
* SOFTWARE. * SOFTWARE.
*/ */
#include <float.h> #include "tvgMath.h"
#include <math.h>
#include "tvgBezier.h" #include "tvgBezier.h"
#define BEZIER_EPSILON 1e-4f
/************************************************************************/ /************************************************************************/
/* Internal Class Implementation */ /* 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 biggest = 1.0f;
auto smallest = 0.0f; auto smallest = 0.0f;
auto t = 0.5f; auto t = 0.5f;
//just in case to prevent an infinite loop //just in case to prevent an infinite loop
if (at <= 0) return 0.0f; if (at <= 0) return 0.0f;
if (at >= length) return length;
if (at >= len) return 1.0f;
while (true) { while (true) {
auto right = bz; auto right = bz;
Bezier left; Bezier left;
bezSplitLeft(right, t, left); bezSplitLeft(right, t, left);
len = bezLength(left); length = bezLength(left);
if (fabsf(length - at) < BEZIER_EPSILON || fabsf(smallest - biggest) < BEZIER_EPSILON) {
if (fabsf(len - at) < BEZIER_EPSILON || fabsf(smallest - biggest) < BEZIER_EPSILON) {
break; break;
} }
if (length < at) {
if (len < at) {
smallest = t; smallest = t;
t = (t + biggest) * 0.5f; t = (t + biggest) * 0.5f;
} else { } else {
@ -144,8 +141,53 @@ float bezAt(const Bezier& bz, float at)
void bezSplitAt(const Bezier& cur, float at, Bezier& left, Bezier& right) void bezSplitAt(const Bezier& cur, float at, Bezier& left, Bezier& right)
{ {
right = cur; right = cur;
auto t = bezAt(right, at); auto t = bezAt(right, at, bezLength(right));
bezSplitLeft(right, t, left); 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 namespace tvg
{ {
#define BEZIER_EPSILON 1e-4f
struct Bezier struct Bezier
{ {
Point start; Point start;
@ -41,8 +39,10 @@ struct Bezier
void bezSplit(const Bezier&cur, Bezier& left, Bezier& right); void bezSplit(const Bezier&cur, Bezier& left, Bezier& right);
float bezLength(const Bezier& cur); float bezLength(const Bezier& cur);
void bezSplitLeft(Bezier& cur, float at, Bezier& left); 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); 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... /* TODO: Need to consider whether uin8_t is enough size for extension...
Rather than optimal data, we can use enough size and data compress? */ 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 TvgBinByte = uint8_t;
using TvgBinCounter = uint32_t; using TvgBinCounter = uint32_t;
using TvgBinTag = TvgBinByte; 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_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 "ThorVG"
#define TVG_HEADER_SIGNATURE_LENGTH 6 #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_VERSION_LENGTH 6
#define TVG_HEADER_RESERVED_LENGTH 1 //Storing flags for extensions #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 #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 #define TVG_TAG_PAINT_CMP_METHOD (TvgBinTag)0x20
//TODO: Keep this for the compatibility, Remove in TVG 1.0 release
//Scene //Scene
#define TVG_TAG_SCENE_RESERVEDCNT (TvgBinTag)0x30 #define TVG_TAG_SCENE_RESERVEDCNT (TvgBinTag)0x30
//Shape //Shape
@ -73,14 +71,17 @@ using TvgBinFlag = TvgBinByte;
#define TVG_TAG_SHAPE_FILL (TvgBinTag)0x42 #define TVG_TAG_SHAPE_FILL (TvgBinTag)0x42
#define TVG_TAG_SHAPE_COLOR (TvgBinTag)0x43 #define TVG_TAG_SHAPE_COLOR (TvgBinTag)0x43
#define TVG_TAG_SHAPE_FILLRULE (TvgBinTag)0x44 #define TVG_TAG_SHAPE_FILLRULE (TvgBinTag)0x44
#define TVG_TAG_SHAPE_STROKE_CAP (TvgBinTag)0x50
#define TVG_TAG_SHAPE_STROKE_JOIN (TvgBinTag)0x51
//Stroke //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_WIDTH (TvgBinTag)0x52
#define TVG_TAG_SHAPE_STROKE_COLOR (TvgBinTag)0x53 #define TVG_TAG_SHAPE_STROKE_COLOR (TvgBinTag)0x53
#define TVG_TAG_SHAPE_STROKE_FILL (TvgBinTag)0x54 #define TVG_TAG_SHAPE_STROKE_FILL (TvgBinTag)0x54
#define TVG_TAG_SHAPE_STROKE_DASHPTRN (TvgBinTag)0x55 #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 //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::NonSupport;
return Result::Success; }
list<Paint*>& Canvas::paints() noexcept
{
return pImpl->paints;
} }
Result Canvas::push(unique_ptr<Paint> paint) noexcept 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 struct Canvas::Impl
{ {
Array<Paint*> paints; list<Paint*> paints;
RenderMethod* renderer; RenderMethod* renderer;
bool refresh = false; //if all paints should be updated by force. bool refresh = false; //if all paints should be updated by force.
bool drawing = false; //on drawing condition? bool drawing = false; //on drawing condition?
@ -53,7 +53,7 @@ struct Canvas::Impl
auto p = paint.release(); auto p = paint.release();
if (!p) return Result::MemoryCorruption; if (!p) return Result::MemoryCorruption;
paints.push(p); paints.push_back(p);
return update(p, true); return update(p, true);
} }
@ -64,9 +64,9 @@ struct Canvas::Impl
if (!renderer || !renderer->clear()) return Result::InsufficientCondition; if (!renderer || !renderer->clear()) return Result::InsufficientCondition;
//Free paints //Free paints
for (auto paint = paints.data; paint < (paints.data + paints.count); ++paint) { for (auto paint : paints) {
(*paint)->pImpl->dispose(*renderer); paint->pImpl->dispose(*renderer);
if (free) delete(*paint); if (free) delete(paint);
} }
paints.clear(); paints.clear();
@ -83,7 +83,7 @@ struct Canvas::Impl
Result update(Paint* paint, bool force) 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; Array<RenderData> clips;
auto flag = RenderUpdateFlag::None; auto flag = RenderUpdateFlag::None;
@ -92,17 +92,17 @@ struct Canvas::Impl
//Update single paint node //Update single paint node
if (paint) { if (paint) {
//Optimize Me: Can we skip the searching? //Optimize Me: Can we skip the searching?
for (auto paint2 = paints.data; paint2 < (paints.data + paints.count); ++paint2) { for (auto paint2 : paints) {
if ((*paint2) == paint) { if (paint2 == paint) {
paint->pImpl->update(*renderer, nullptr, 255, clips, flag); paint->pImpl->update(*renderer, nullptr, clips, 255, flag);
return Result::Success; return Result::Success;
} }
} }
return Result::InvalidArguments; return Result::InvalidArguments;
//Update all retained paint nodes //Update all retained paint nodes
} else { } else {
for (auto paint = paints.data; paint < (paints.data + paints.count); ++paint) { for (auto paint : paints) {
(*paint)->pImpl->update(*renderer, nullptr, 255, clips, flag); paint->pImpl->update(*renderer, nullptr, clips, 255, flag);
} }
} }
@ -113,11 +113,11 @@ struct Canvas::Impl
Result draw() 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; bool rendered = false;
for (auto paint = paints.data; paint < (paints.data + paints.count); ++paint) { for (auto paint : paints) {
if ((*paint)->pImpl->render(*renderer)) rendered = true; if (paint->pImpl->render(*renderer)) rendered = true;
} }
if (!rendered || !renderer->postRender()) return Result::InsufficientCondition; 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_LINEAR 4
#define TVG_CLASS_ID_RADIAL 5 #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 #ifdef THORVG_LOG_ENABLED
constexpr auto ErrorColor = "\033[31m"; //red constexpr auto ErrorColor = "\033[31m"; //red

View File

@ -55,11 +55,11 @@ struct Fill::Impl
uint32_t cnt = 0; uint32_t cnt = 0;
FillSpread spread; FillSpread spread;
DuplicateMethod<Fill>* dup = nullptr; DuplicateMethod<Fill>* dup = nullptr;
uint32_t id; uint8_t id;
~Impl() ~Impl()
{ {
if (dup) delete(dup); delete(dup);
free(colorStops); free(colorStops);
free(transform); 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 * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@ -20,28 +20,28 @@
* SOFTWARE. * SOFTWARE.
*/ */
#ifndef _TVG_PNG_LOADER_H_ #ifndef _TVG_FRAME_MODULE_H_
#define _TVG_PNG_LOADER_H_ #define _TVG_FRAME_MODULE_H_
#include <png.h> #include "tvgLoadModule.h"
class PngLoader : public LoadModule namespace tvg
{
class FrameModule: public LoadModule
{ {
public: public:
PngLoader(); virtual ~FrameModule() {}
~PngLoader();
using LoadModule::open; virtual bool frame(uint32_t frameNo) = 0; //set the current frame number
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; 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: virtual bool animatable() override { return true; }
png_imagep image = nullptr;
uint32_t* content = nullptr;
}; };
#endif //_TVG_PNG_LOADER_H_ }
#endif //_TVG_FRAME_MODULE_H_

View File

@ -31,11 +31,6 @@ namespace tvg
class LoadModule class LoadModule
{ {
public: 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 float w = 0, h = 0; //default image size
ColorSpace cs = ColorSpace::Unsupported; //must be clarified at open() ColorSpace cs = ColorSpace::Unsupported; //must be clarified at open()
@ -48,8 +43,12 @@ public:
//Override this if the vector-format has own resizing policy. //Override this if the vector-format has own resizing policy.
virtual bool resize(Paint* paint, float w, float h) { return false; } 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 read() = 0;
virtual bool close() = 0; virtual bool close() = 0;
virtual unique_ptr<Surface> bitmap() { return nullptr; } virtual unique_ptr<Surface> bitmap() { return nullptr; }
virtual unique_ptr<Paint> paint() { return nullptr; } virtual unique_ptr<Paint> paint() { return nullptr; }
}; };

View File

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

View File

@ -45,6 +45,15 @@ static inline bool mathEqual(float a, float b)
return (fabsf(a - b) < FLT_EPSILON); 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) 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->e11 *= sx;
m->e22 = scale; m->e22 *= sy;
} }
static inline void mathTranslate(Matrix* m, float x, float y) static inline void mathTranslate(Matrix* m, float x, float y)
{ {
m->e13 = x; m->e13 += x;
m->e23 = y; 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_ #endif //_TVG_MATH_H_

View File

@ -166,6 +166,7 @@ bool Paint::Impl::render(RenderMethod& renderer)
Create a composition image. */ Create a composition image. */
if (compData && compData->method != CompositeMethod::ClipPath && !(compData->target->pImpl->ctxFlag & ContextFlag::FastTrack)) { if (compData && compData->method != CompositeMethod::ClipPath && !(compData->target->pImpl->ctxFlag & ContextFlag::FastTrack)) {
auto region = smethod->bounds(renderer); 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; if (region.w == 0 || region.h == 0) return true;
cmp = renderer.target(region, COMPOSITE_TO_COLORSPACE(renderer, compData->method)); cmp = renderer.target(region, COMPOSITE_TO_COLORSPACE(renderer, compData->method));
if (renderer.beginComposite(cmp, CompositeMethod::None, 255)) { 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); if (cmp) renderer.beginComposite(cmp, compData->method, compData->target->pImpl->opacity);
renderer.blend(blendMethod);
auto ret = smethod->render(renderer); auto ret = smethod->render(renderer);
if (cmp) renderer.endComposite(cmp); 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 (renderFlag & RenderUpdateFlag::Transform) {
if (!rTransform) return nullptr; if (!rTransform) return nullptr;
@ -209,11 +211,18 @@ RenderData Paint::Impl::update(RenderMethod& renderer, const RenderTransform* pT
auto tryFastTrack = false; auto tryFastTrack = false;
if (target->identifier() == TVG_CLASS_ID_SHAPE) { if (target->identifier() == TVG_CLASS_ID_SHAPE) {
if (method == CompositeMethod::ClipPath) tryFastTrack = true; if (method == CompositeMethod::ClipPath) tryFastTrack = true;
//OPTIMIZE HERE: Actually, this condition AlphaMask is useless. We can skip it?
else if (method == CompositeMethod::AlphaMask) { else if (method == CompositeMethod::AlphaMask) {
auto shape = static_cast<Shape*>(target); auto shape = static_cast<Shape*>(target);
uint8_t a; uint8_t a;
shape->fillColor(nullptr, nullptr, nullptr, &a); shape->fillColor(nullptr, nullptr, nullptr, &a);
if (a == 255 && shape->opacity() == 255 && !shape->fill()) tryFastTrack = true; 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) { if (tryFastTrack) {
RenderRegion viewport2; RenderRegion viewport2;
@ -227,7 +236,7 @@ RenderData Paint::Impl::update(RenderMethod& renderer, const RenderTransform* pT
} }
if (!compFastTrack) { if (!compFastTrack) {
childClipper = compData->method == CompositeMethod::ClipPath ? true : false; 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); if (childClipper) clips.push(trd);
} }
} }
@ -236,14 +245,14 @@ RenderData Paint::Impl::update(RenderMethod& renderer, const RenderTransform* pT
RenderData rd = nullptr; RenderData rd = nullptr;
auto newFlag = static_cast<RenderUpdateFlag>(pFlag | renderFlag); auto newFlag = static_cast<RenderUpdateFlag>(pFlag | renderFlag);
renderFlag = RenderUpdateFlag::None; renderFlag = RenderUpdateFlag::None;
opacity = (opacity * this->opacity) / 255; opacity = MULTIPLY(opacity, this->opacity);
if (rTransform && pTransform) { if (rTransform && pTransform) {
RenderTransform outTransform(pTransform, rTransform); RenderTransform outTransform(pTransform, rTransform);
rd = smethod->update(renderer, &outTransform, opacity, clips, newFlag, clipper); rd = smethod->update(renderer, &outTransform, clips, opacity, newFlag, clipper);
} else { } else {
auto outTransform = pTransform ? pTransform : rTransform; 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 */ /* 3. Composition Post Processing */
@ -371,7 +380,7 @@ Result Paint::composite(std::unique_ptr<Paint> target, CompositeMethod method) n
{ {
auto p = target.release(); auto p = target.release();
if (pImpl->composite(this, p, method)) return Result::Success; if (pImpl->composite(this, p, method)) return Result::Success;
if (p) delete(p); delete(p);
return Result::InvalidArguments; return Result::InvalidArguments;
} }
@ -409,3 +418,17 @@ uint32_t Paint::identifier() const noexcept
{ {
return pImpl->id; 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 namespace tvg
{ {
enum ContextFlag {Invalid = 0, FastTrack = 1}; enum ContextFlag : uint8_t {Invalid = 0, FastTrack = 1};
struct Iterator struct Iterator
{ {
@ -43,7 +43,7 @@ namespace tvg
virtual ~StrategyMethod() {} virtual ~StrategyMethod() {}
virtual bool dispose(RenderMethod& renderer) = 0; 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 render(RenderMethod& renderer) = 0;
virtual bool bounds(float* x, float* y, float* w, float* h) = 0; virtual bool bounds(float* x, float* y, float* w, float* h) = 0;
virtual RenderRegion bounds(RenderMethod& renderer) const = 0; virtual RenderRegion bounds(RenderMethod& renderer) const = 0;
@ -63,9 +63,10 @@ namespace tvg
StrategyMethod* smethod = nullptr; StrategyMethod* smethod = nullptr;
RenderTransform* rTransform = nullptr; RenderTransform* rTransform = nullptr;
Composite* compData = nullptr; Composite* compData = nullptr;
uint32_t renderFlag = RenderUpdateFlag::None; BlendMethod blendMethod = BlendMethod::Normal; //uint8_t
uint32_t ctxFlag = ContextFlag::Invalid; uint8_t renderFlag = RenderUpdateFlag::None;
uint32_t id; uint8_t ctxFlag = ContextFlag::Invalid;
uint8_t id;
uint8_t opacity = 255; uint8_t opacity = 255;
~Impl() ~Impl()
@ -74,8 +75,8 @@ namespace tvg
delete(compData->target); delete(compData->target);
free(compData); free(compData);
} }
if (smethod) delete(smethod); delete(smethod);
if (rTransform) delete(rTransform); delete(rTransform);
} }
void method(StrategyMethod* method) void method(StrategyMethod* method)
@ -147,7 +148,7 @@ namespace tvg
bool scale(float factor); bool scale(float factor);
bool translate(float x, float y); bool translate(float x, float y);
bool bounds(float* x, float* y, float* w, float* h, bool transformed); 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); bool render(RenderMethod& renderer);
Paint* duplicate(); Paint* duplicate();
}; };
@ -176,9 +177,9 @@ namespace tvg
return inst->dispose(renderer); 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 bool render(RenderMethod& renderer) override

View File

@ -26,7 +26,7 @@
/* External Class Implementation */ /* External Class Implementation */
/************************************************************************/ /************************************************************************/
Picture::Picture() : pImpl(new Impl) Picture::Picture() : pImpl(new Impl(this))
{ {
Paint::pImpl->id = TVG_CLASS_ID_PICTURE; Paint::pImpl->id = TVG_CLASS_ID_PICTURE;
Paint::pImpl->method(new PaintMethod<Picture::Impl>(pImpl)); 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 Result Picture::size(float w, float h) noexcept
{ {
if (pImpl->size(w, h)) return Result::Success; if (pImpl->size(w, h)) return Result::Success;

View File

@ -67,11 +67,18 @@ struct Picture::Impl
RenderData rd = nullptr; //engine data RenderData rd = nullptr; //engine data
float w = 0, h = 0; float w = 0, h = 0;
RenderMesh rm; //mesh data RenderMesh rm; //mesh data
Picture* picture = nullptr;
bool resizing = false; bool resizing = false;
bool needComp = false; //need composition
bool animated = false; //picture is belonged to Animation
Impl(Picture* p) : picture(p)
{
}
~Impl() ~Impl()
{ {
if (paint) delete(paint); delete(paint);
delete(surface); delete(surface);
} }
@ -85,7 +92,7 @@ struct Picture::Impl
return ret; return ret;
} }
uint32_t load() RenderUpdateFlag load()
{ {
if (loader) { if (loader) {
if (!paint) { if (!paint) {
@ -102,7 +109,8 @@ struct Picture::Impl
} }
if (paint) return RenderUpdateFlag::None; if (paint) return RenderUpdateFlag::None;
} }
} } else loader->sync();
if (!surface) { if (!surface) {
if ((surface = loader->bitmap().release())) { if ((surface = loader->bitmap().release())) {
loader->close(); loader->close();
@ -127,38 +135,52 @@ struct Picture::Impl
else return RenderTransform(pTransform, &tmp); 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(); auto flag = load();
if (surface) { if (surface) {
auto transform = resizeTransform(pTransform); 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) { } else if (paint) {
if (resizing) { if (resizing) {
loader->resize(paint, w, h); loader->resize(paint, w, h);
resizing = false; 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; return rd;
} }
bool render(RenderMethod &renderer) bool render(RenderMethod &renderer)
{ {
bool ret = false;
if (surface) return renderer.renderImage(rd); if (surface) return renderer.renderImage(rd);
else if (paint) return paint->pImpl->render(renderer); else if (paint) {
return false; Compositor* cmp = nullptr;
} if (needComp) {
cmp = renderer.target(bounds(renderer), renderer.colorSpace());
bool viewbox(float* x, float* y, float* w, float* h) renderer.beginComposite(cmp, CompositeMethod::None, 255);
{ }
if (!loader) return false; ret = paint->pImpl->render(renderer);
if (x) *x = loader->vx; if (cmp) renderer.endComposite(cmp);
if (y) *y = loader->vy; }
if (w) *w = loader->vw; return ret;
if (h) *h = loader->vh;
return true;
} }
bool size(float w, float h) bool size(float w, float h)

View File

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

View File

@ -32,7 +32,7 @@ namespace tvg
using RenderData = void*; using RenderData = void*;
using pixel_t = uint32_t; 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; struct Surface;
@ -65,7 +65,7 @@ struct Surface
struct Compositor struct Compositor
{ {
CompositeMethod method; CompositeMethod method;
uint32_t opacity; uint8_t opacity;
}; };
struct RenderMesh struct RenderMesh
@ -98,6 +98,20 @@ struct RenderRegion
if (w < 0) w = 0; if (w < 0) w = 0;
if (h < 0) h = 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 struct RenderTransform
@ -125,12 +139,13 @@ struct RenderStroke
uint32_t dashCnt = 0; uint32_t dashCnt = 0;
StrokeCap cap = StrokeCap::Square; StrokeCap cap = StrokeCap::Square;
StrokeJoin join = StrokeJoin::Bevel; StrokeJoin join = StrokeJoin::Bevel;
float miterlimit = 4.0f;
bool strokeFirst = false; bool strokeFirst = false;
~RenderStroke() ~RenderStroke()
{ {
free(dashPattern); free(dashPattern);
if (fill) delete(fill); delete(fill);
} }
}; };
@ -138,13 +153,8 @@ struct RenderShape
{ {
struct struct
{ {
PathCommand* cmds = nullptr; Array<PathCommand> cmds;
uint32_t cmdCnt = 0; Array<Point> pts;
uint32_t reservedCmdCnt = 0;
Point *pts = nullptr;
uint32_t ptsCnt = 0;
uint32_t reservedPtsCnt = 0;
} path; } path;
Fill *fill = nullptr; Fill *fill = nullptr;
@ -154,11 +164,8 @@ struct RenderShape
~RenderShape() ~RenderShape()
{ {
free(path.cmds); delete(fill);
free(path.pts); delete(stroke);
if (fill) delete(fill);
if (stroke) delete(stroke);
} }
void fillColor(uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a) const 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; if (!stroke) return StrokeJoin::Bevel;
return stroke->join; return stroke->join;
} }
float strokeMiterlimit() const
{
if (!stroke) return 4.0f;
return stroke->miterlimit;;
}
}; };
class RenderMethod class RenderMethod
{ {
public: public:
virtual ~RenderMethod() {} 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 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, uint32_t opacity, Array<RenderData>& clips, RenderUpdateFlag flags) = 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, uint32_t opacity, Array<RenderData>& clips, 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 preRender() = 0;
virtual bool renderShape(RenderData data) = 0; virtual bool renderShape(RenderData data) = 0;
virtual bool renderImage(RenderData data) = 0; virtual bool renderImage(RenderData data) = 0;
@ -228,16 +242,36 @@ public:
virtual RenderRegion region(RenderData data) = 0; virtual RenderRegion region(RenderData data) = 0;
virtual RenderRegion viewport() = 0; virtual RenderRegion viewport() = 0;
virtual bool viewport(const RenderRegion& vp) = 0; virtual bool viewport(const RenderRegion& vp) = 0;
virtual bool blend(BlendMethod method) = 0;
virtual ColorSpace colorSpace() = 0; virtual ColorSpace colorSpace() = 0;
virtual bool clear() = 0; virtual bool clear() = 0;
virtual bool sync() = 0; virtual bool sync() = 0;
virtual Compositor* target(const RenderRegion& region, ColorSpace cs) = 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; 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) static inline uint8_t CHANNEL_SIZE(ColorSpace cs)
{ {
switch(cs) { switch(cs) {
@ -262,6 +296,11 @@ static inline ColorSpace COMPOSITE_TO_COLORSPACE(RenderMethod& renderer, Composi
case CompositeMethod::InvAlphaMask: case CompositeMethod::InvAlphaMask:
return ColorSpace::Grayscale8; return ColorSpace::Grayscale8;
case CompositeMethod::LumaMask: case CompositeMethod::LumaMask:
case CompositeMethod::InvLumaMask:
case CompositeMethod::AddMask:
case CompositeMethod::SubtractMask:
case CompositeMethod::IntersectMask:
case CompositeMethod::DifferenceMask:
return renderer.colorSpace(); return renderer.colorSpace();
default: default:
TVGERR("COMMON", "Unsupported Composite Size! = %d", (int)method); 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_ #endif //_TVG_RENDER_H_

View File

@ -36,7 +36,7 @@ struct Saver::Impl
SaveModule* saveModule = nullptr; SaveModule* saveModule = nullptr;
~Impl() ~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(); auto p = paint.release();
if (!p) return Result::MemoryCorruption; if (!p) return Result::MemoryCorruption;
pImpl->paints.push(p); pImpl->paints.push_back(p);
return Result::Success; 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::NonSupport;
return Result::Success;
} }
@ -75,3 +73,9 @@ Result Scene::clear(bool free) noexcept
return Result::Success; return Result::Success;
} }
list<Paint*>& Scene::paints() noexcept
{
return pImpl->paints;
}

View File

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

View File

@ -32,7 +32,7 @@ constexpr auto PATH_KAPPA = 0.552284f;
/* External Class Implementation */ /* External Class Implementation */
/************************************************************************/ /************************************************************************/
Shape :: Shape() : pImpl(new Impl()) Shape :: Shape() : pImpl(new Impl(this))
{ {
Paint::pImpl->id = TVG_CLASS_ID_SHAPE; Paint::pImpl->id = TVG_CLASS_ID_SHAPE;
Paint::pImpl->method(new PaintMethod<Shape::Impl>(pImpl)); Paint::pImpl->method(new PaintMethod<Shape::Impl>(pImpl));
@ -59,7 +59,10 @@ uint32_t Shape::identifier() noexcept
Result Shape::reset() noexcept Result Shape::reset() noexcept
{ {
pImpl->reset(); pImpl->rs.path.cmds.clear();
pImpl->rs.path.pts.clear();
pImpl->flag = RenderUpdateFlag::Path;
return Result::Success; return Result::Success;
} }
@ -69,9 +72,8 @@ uint32_t Shape::pathCommands(const PathCommand** cmds) const noexcept
{ {
if (!cmds) return 0; if (!cmds) return 0;
*cmds = pImpl->rs.path.cmds; *cmds = pImpl->rs.path.cmds.data;
return pImpl->rs.path.cmds.count;
return pImpl->rs.path.cmdCnt;
} }
@ -79,9 +81,8 @@ uint32_t Shape::pathCoords(const Point** pts) const noexcept
{ {
if (!pts) return 0; if (!pts) return 0;
*pts = pImpl->rs.path.pts; *pts = pImpl->rs.path.pts.data;
return pImpl->rs.path.pts.count;
return pImpl->rs.path.ptsCnt;
} }
@ -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 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) { if (pImpl->rs.fill) {
delete(pImpl->rs.fill); delete(pImpl->rs.fill);
pImpl->rs.fill = nullptr; pImpl->rs.fill = nullptr;
pImpl->flag |= RenderUpdateFlag::Gradient; 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; 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 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; 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 StrokeCap Shape::strokeCap() const noexcept
{ {
@ -386,6 +399,11 @@ StrokeJoin Shape::strokeJoin() const noexcept
return pImpl->rs.strokeJoin(); return pImpl->rs.strokeJoin();
} }
float Shape::strokeMiterlimit() const noexcept
{
return pImpl->rs.strokeMiterlimit();
}
Result Shape::fill(FillRule r) noexcept Result Shape::fill(FillRule r) noexcept
{ {

View File

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

View File

@ -67,7 +67,7 @@ Result SwCanvas::mempool(MempoolPolicy policy) noexcept
if (!renderer) return Result::MemoryCorruption; if (!renderer) return Result::MemoryCorruption;
//It can't change the policy during the running. //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); if (policy == MempoolPolicy::Individual) renderer->mempool(false);
else renderer->mempool(true); else renderer->mempool(true);

View File

@ -85,3 +85,4 @@ private:
} }
#endif //_TVG_TASK_SCHEDULER_H_ #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 */ /* 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) static void _copyStyle(SvgStyleProperty* to, const SvgStyleProperty* from)
{ {
if (from == nullptr) return; if (from == nullptr) return;
//Copy the properties of 'from' only if they were explicitly set (not the default ones). //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->color = from->color;
to->curColorSet = true; to->curColorSet = true;
to->flags = (to->flags | SvgStyleFlags::Color); to->flags = (to->flags | SvgStyleFlags::Color);
if (from->flagsImportance & SvgStyleFlags::Color) {
to->flagsImportance = (to->flagsImportance | SvgStyleFlags::Color);
}
} }
//Fill //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.color = from->fill.paint.color;
to->fill.paint.none = from->fill.paint.none; to->fill.paint.none = from->fill.paint.none;
to->fill.paint.curColor = from->fill.paint.curColor; 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->fill.flags = (to->fill.flags | SvgFillFlags::Paint);
to->flags = (to->flags | SvgStyleFlags::Fill); 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.opacity = from->fill.opacity;
to->fill.flags = (to->fill.flags | SvgFillFlags::Opacity); to->fill.flags = (to->fill.flags | SvgFillFlags::Opacity);
to->flags = (to->flags | SvgStyleFlags::FillOpacity); 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.fillRule = from->fill.fillRule;
to->fill.flags = (to->fill.flags | SvgFillFlags::FillRule); to->fill.flags = (to->fill.flags | SvgFillFlags::FillRule);
to->flags = (to->flags | SvgStyleFlags::FillRule); to->flags = (to->flags | SvgStyleFlags::FillRule);
if (from->flagsImportance & SvgStyleFlags::FillRule) {
to->flagsImportance = (to->flagsImportance | SvgStyleFlags::FillRule);
}
} }
//Stroke //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.color = from->stroke.paint.color;
to->stroke.paint.none = from->stroke.paint.none; to->stroke.paint.none = from->stroke.paint.none;
to->stroke.paint.curColor = from->stroke.paint.curColor; 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->stroke.flags = (to->stroke.flags | SvgStrokeFlags::Paint);
to->flags = (to->flags | SvgStyleFlags::Stroke); 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.opacity = from->stroke.opacity;
to->stroke.flags = (to->stroke.flags | SvgStrokeFlags::Opacity); to->stroke.flags = (to->stroke.flags | SvgStrokeFlags::Opacity);
to->flags = (to->flags | SvgStyleFlags::StrokeOpacity); 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.width = from->stroke.width;
to->stroke.flags = (to->stroke.flags | SvgStrokeFlags::Width); to->stroke.flags = (to->stroke.flags | SvgStrokeFlags::Width);
to->flags = (to->flags | SvgStyleFlags::StrokeWidth); 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) { if (from->stroke.dash.array.count > 0) {
to->stroke.dash.array.clear(); to->stroke.dash.array.clear();
to->stroke.dash.array.reserve(from->stroke.dash.array.count); 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->stroke.flags = (to->stroke.flags | SvgStrokeFlags::Dash);
to->flags = (to->flags | SvgStyleFlags::StrokeDashArray); 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.cap = from->stroke.cap;
to->stroke.flags = (to->stroke.flags | SvgStrokeFlags::Cap); to->stroke.flags = (to->stroke.flags | SvgStrokeFlags::Cap);
to->flags = (to->flags | SvgStyleFlags::StrokeLineCap); 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.join = from->stroke.join;
to->stroke.flags = (to->stroke.flags | SvgStrokeFlags::Join); to->stroke.flags = (to->stroke.flags | SvgStrokeFlags::Join);
to->flags = (to->flags | SvgStyleFlags::StrokeLineJoin); to->flags = (to->flags | SvgStyleFlags::StrokeLineJoin);
if (from->flagsImportance & SvgStyleFlags::StrokeLineJoin) {
to->flagsImportance = (to->flagsImportance | SvgStyleFlags::StrokeLineJoin);
}
} }
//Opacity //Opacity
//TODO: it can be set to be 255 and shouldn't be changed by attribute '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->opacity = from->opacity;
to->flags = (to->flags | SvgStyleFlags::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; int i = 0;
while (url[i] > ' ' && url[i] != ')' && url[i] != '\'') ++i; while (url[i] > ' ' && url[i] != ')' && url[i] != '\'') ++i;
//custom strndup() for portability return svgUtilStrndup(url, i);
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);
} }
@ -983,6 +976,22 @@ static void _handleStrokeLineJoinAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode*
node->style->stroke.join = _toLineJoin(value); 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) 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, Stroke, SvgStyleFlags::Stroke),
STYLE_DEF(stroke-width, StrokeWidth, SvgStyleFlags::StrokeWidth), STYLE_DEF(stroke-width, StrokeWidth, SvgStyleFlags::StrokeWidth),
STYLE_DEF(stroke-linejoin, StrokeLineJoin, SvgStyleFlags::StrokeLineJoin), STYLE_DEF(stroke-linejoin, StrokeLineJoin, SvgStyleFlags::StrokeLineJoin),
STYLE_DEF(stroke-miterlimit, StrokeMiterlimit, SvgStyleFlags::StrokeMiterlimit),
STYLE_DEF(stroke-linecap, StrokeLineCap, SvgStyleFlags::StrokeLineCap), STYLE_DEF(stroke-linecap, StrokeLineCap, SvgStyleFlags::StrokeLineCap),
STYLE_DEF(stroke-opacity, StrokeOpacity, SvgStyleFlags::StrokeOpacity), STYLE_DEF(stroke-opacity, StrokeOpacity, SvgStyleFlags::StrokeOpacity),
STYLE_DEF(stroke-dasharray, StrokeDashArray, SvgStyleFlags::StrokeDashArray), 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); sz = strlen(key);
for (unsigned int i = 0; i < sizeof(styleTags) / sizeof(styleTags[0]); i++) { for (unsigned int i = 0; i < sizeof(styleTags) / sizeof(styleTags[0]); i++) {
if (styleTags[i].sz - 1 == sz && !strncmp(styleTags[i].tag, key, sz)) { 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) { if (style) {
styleTags[i].tagHandler(loader, node, value); if (importance || !(node->style->flagsImportance & styleTags[i].flag)) {
node->style->flags = (node->style->flags | 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)) { } else if (!(node->style->flags & styleTags[i].flag)) {
styleTags[i].tagHandler(loader, node, value); styleTags[i].tagHandler(loader, node, value);
} }
if (importance) {
node->style->flagsImportance = (node->style->flags | styleTags[i].flag);
free(const_cast<char*>(value));
}
return true; return true;
} }
} }
@ -1307,6 +1332,7 @@ static SvgNode* _createNode(SvgNode* parent, SvgNodeType type)
node->style->stroke.cap = StrokeCap::Butt; node->style->stroke.cap = StrokeCap::Butt;
//Default line join is miter //Default line join is miter
node->style->stroke.join = StrokeJoin::Miter; node->style->stroke.join = StrokeJoin::Miter;
node->style->stroke.miterlimit = 4.0f;
node->style->stroke.scale = 1.0; node->style->stroke.scale = 1.0;
node->style->paintOrder = _toPaintOrder("fill stroke"); 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 num;
float *pointArray = nullptr, *tmpArray; while (_parseNumber(&str, &num)) polygon->pts.push(num);
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;
return true; 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); else polygon = &(node->node.polyline);
if (!strcmp(key, "points")) { if (!strcmp(key, "points")) {
return _attrParsePolygonPoints(value, &polygon->points, &polygon->pointsCount); return _attrParsePolygonPoints(value, polygon);
} else if (!strcmp(key, "style")) { } else if (!strcmp(key, "style")) {
return simpleXmlParseW3CAttribute(value, strlen(value), _parseStyleAttr, loader); return simpleXmlParseW3CAttribute(value, strlen(value), _parseStyleAttr, loader);
} else if (!strcmp(key, "clip-path")) { } 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::Doc) return node->node.doc.defs;
if (node->type == SvgNodeType::Defs) return node;
return nullptr; 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)) { if (!(child->stroke.flags & SvgStrokeFlags::Join)) {
child->stroke.join = parent->stroke.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) { if (from->stroke.flags & SvgStrokeFlags::Join) {
to->stroke.join = from->stroke.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; break;
} }
case SvgNodeType::Polygon: { case SvgNodeType::Polygon: {
if ((to->node.polygon.pointsCount = from->node.polygon.pointsCount)) { if ((to->node.polygon.pts.count = from->node.polygon.pts.count)) {
to->node.polygon.points = (float*)malloc(to->node.polygon.pointsCount * sizeof(float)); to->node.polygon.pts = from->node.polygon.pts;
memcpy(to->node.polygon.points, from->node.polygon.points, to->node.polygon.pointsCount * sizeof(float));
} }
break; break;
} }
case SvgNodeType::Polyline: { case SvgNodeType::Polyline: {
if ((to->node.polyline.pointsCount = from->node.polyline.pointsCount)) { if ((to->node.polyline.pts.count = from->node.polyline.pts.count)) {
to->node.polyline.points = (float*)malloc(to->node.polyline.pointsCount * sizeof(float)); to->node.polyline.pts = from->node.polyline.pts;
memcpy(to->node.polyline.points, from->node.polyline.points, to->node.polyline.pointsCount * sizeof(float));
} }
break; break;
} }
@ -2934,6 +2938,16 @@ static void _copyAttr(SvgNode* to, const SvgNode* from)
} }
break; 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: { default: {
break; break;
} }
@ -3200,7 +3214,7 @@ static void _inefficientNodeCheck(TVG_UNUSED SvgNode* node)
} }
case SvgNodeType::Polygon: case SvgNodeType::Polygon:
case SvgNodeType::Polyline: { 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; break;
} }
case SvgNodeType::Circle: { case SvgNodeType::Circle: {
@ -3356,11 +3370,11 @@ static void _freeNode(SvgNode* node)
break; break;
} }
case SvgNodeType::Polygon: { case SvgNodeType::Polygon: {
free(node->node.polygon.points); free(node->node.polygon.pts.data);
break; break;
} }
case SvgNodeType::Polyline: { case SvgNodeType::Polyline: {
free(node->node.polyline.points); free(node->node.polyline.pts.data);
break; break;
} }
case SvgNodeType::Doc: { case SvgNodeType::Doc: {
@ -3497,6 +3511,7 @@ void SvgLoader::run(unsigned tid)
if (defs) _updateComposite(loaderData.doc, defs); if (defs) _updateComposite(loaderData.doc, defs);
_updateStyle(loaderData.doc, nullptr); _updateStyle(loaderData.doc, nullptr);
if (defs) _updateStyle(defs, nullptr);
if (loaderData.gradients.count > 0) _updateGradient(&loaderData, loaderData.doc, &loaderData.gradients); if (loaderData.gradients.count > 0) _updateGradient(&loaderData, loaderData.doc, &loaderData.gradients);
if (defs) _updateGradient(&loaderData, loaderData.doc, &defs->node.defs.gradients); if (defs) _updateGradient(&loaderData, loaderData.doc, &defs->node.defs.gradients);
@ -3683,6 +3698,5 @@ bool SvgLoader::close()
unique_ptr<Paint> SvgLoader::paint() unique_ptr<Paint> SvgLoader::paint()
{ {
this->done(); this->done();
if (root) return move(root); return std::move(root);
else return nullptr;
} }

View File

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

View File

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

View File

@ -263,7 +263,7 @@ static void _applyComposition(Paint* paint, const SvgNode* node, const Box& vBox
comp->transform(m); 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; 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 (node->transform) comp->transform(*node->transform);
if (compNode->node.mask.type == SvgMaskType::Luminance && !isMaskWhite) { if (compNode->node.mask.type == SvgMaskType::Luminance && !isMaskWhite) {
paint->composite(move(comp), CompositeMethod::LumaMask); paint->composite(std::move(comp), CompositeMethod::LumaMask);
} else { } 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) { if (style->fill.paint.gradient->type == SvgGradientType::Linear) {
auto linear = _applyLinearGradientProperty(style->fill.paint.gradient, vg, bBox, style->fill.opacity); 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) { } else if (style->fill.paint.gradient->type == SvgGradientType::Radial) {
auto radial = _applyRadialGradientProperty(style->fill.paint.gradient, vg, bBox, style->fill.opacity); 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) { } else if (style->fill.paint.url) {
//TODO: Apply the color pointed by 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.width);
vg->stroke(style->stroke.cap); vg->stroke(style->stroke.cap);
vg->stroke(style->stroke.join); vg->stroke(style->stroke.join);
vg->strokeMiterlimit(style->stroke.miterlimit);
if (style->stroke.dash.array.count > 0) { if (style->stroke.dash.array.count > 0) {
vg->stroke(style->stroke.dash.array.data, style->stroke.dash.array.count); 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) { if (style->stroke.paint.gradient->type == SvgGradientType::Linear) {
auto linear = _applyLinearGradientProperty(style->stroke.paint.gradient, vg, bBox, style->stroke.opacity); 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) { } else if (style->stroke.paint.gradient->type == SvgGradientType::Radial) {
auto radial = _applyRadialGradientProperty(style->stroke.paint.gradient, vg, bBox, style->stroke.opacity); 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) { } else if (style->stroke.paint.url) {
//TODO: Apply the color pointed by 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; break;
} }
case SvgNodeType::Polygon: { case SvgNodeType::Polygon: {
if (node->node.polygon.pointsCount < 2) break; if (node->node.polygon.pts.count < 2) break;
shape->moveTo(node->node.polygon.points[0], node->node.polygon.points[1]); auto pts = node->node.polygon.pts.data;
for (int i = 2; i < node->node.polygon.pointsCount - 1; i += 2) { shape->moveTo(pts[0], pts[1]);
shape->lineTo(node->node.polygon.points[i], node->node.polygon.points[i + 1]); for (pts += 2; pts < node->node.polygon.pts.end(); pts += 2) {
shape->lineTo(pts[0], pts[1]);
} }
shape->close(); shape->close();
break; break;
} }
case SvgNodeType::Polyline: { case SvgNodeType::Polyline: {
if (node->node.polygon.pointsCount < 2) break; if (node->node.polyline.pts.count < 2) break;
shape->moveTo(node->node.polygon.points[0], node->node.polygon.points[1]); auto pts = node->node.polyline.pts.data;
for (int i = 2; i < node->node.polygon.pointsCount - 1; i += 2) { shape->moveTo(pts[0], pts[1]);
shape->lineTo(node->node.polygon.points[i], node->node.polygon.points[i + 1]); for (pts += 2; pts < node->node.polyline.pts.end(); pts += 2) {
shape->lineTo(pts[0], pts[1]);
} }
break; break;
} }
@ -676,7 +679,7 @@ static unique_ptr<Scene> _useBuildHelper(const SvgNode* node, const Box& vBox, c
scene->transform(mSceneTransform); scene->transform(mSceneTransform);
if (node->node.use.symbol->node.symbol.overflowVisible) { if (node->node.use.symbol->node.symbol.overflowVisible) {
finalScene = move(scene); finalScene = std::move(scene);
} else { } else {
auto viewBoxClip = Shape::gen(); auto viewBoxClip = Shape::gen();
viewBoxClip->appendRect(0, 0, width, height, 0, 0); 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); viewBoxClip->transform(mClipTransform);
auto compositeLayer = Scene::gen(); auto compositeLayer = Scene::gen();
compositeLayer->composite(move(viewBoxClip), CompositeMethod::ClipPath); compositeLayer->composite(std::move(viewBoxClip), CompositeMethod::ClipPath);
compositeLayer->push(move(scene)); compositeLayer->push(std::move(scene));
auto root = Scene::gen(); auto root = Scene::gen();
root->push(move(compositeLayer)); root->push(std::move(compositeLayer));
finalScene = move(root); finalScene = std::move(root);
} }
} else { } else {
if (!mathIdentity((const Matrix*)(&mUseTransform))) scene->transform(mUseTransform); if (!mathIdentity((const Matrix*)(&mUseTransform))) scene->transform(mUseTransform);
finalScene = move(scene); finalScene = std::move(scene);
} }
return finalScene; return finalScene;
@ -731,7 +734,7 @@ static unique_ptr<Scene> _sceneBuildHelper(const SvgNode* node, const Box& vBox,
} else if ((*child)->type == SvgNodeType::Image) { } else if ((*child)->type == SvgNodeType::Image) {
auto image = _imageBuildHelper(*child, vBox, svgPath); auto image = _imageBuildHelper(*child, vBox, svgPath);
if (image) { if (image) {
scene->push(move(image)); scene->push(std::move(image));
if (isMaskWhite) *isMaskWhite = false; if (isMaskWhite) *isMaskWhite = false;
} }
} else if ((*child)->type != SvgNodeType::Mask) { } else if ((*child)->type != SvgNodeType::Mask) {
@ -739,13 +742,13 @@ static unique_ptr<Scene> _sceneBuildHelper(const SvgNode* node, const Box& vBox,
if (shape) { if (shape) {
if (isMaskWhite) { if (isMaskWhite) {
uint8_t r, g, b; 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() || 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; *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(); auto viewBoxClip = Shape::gen();
viewBoxClip->appendRect(0, 0, w, h, 0, 0); viewBoxClip->appendRect(0, 0, w, h, 0, 0);
viewBoxClip->fill(0, 0, 0, 255); viewBoxClip->fill(0, 0, 0);
auto compositeLayer = Scene::gen(); auto compositeLayer = Scene::gen();
compositeLayer->composite(move(viewBoxClip), CompositeMethod::ClipPath); compositeLayer->composite(std::move(viewBoxClip), CompositeMethod::ClipPath);
compositeLayer->push(move(docNode)); compositeLayer->push(std::move(docNode));
auto root = Scene::gen(); auto root = Scene::gen();
root->push(move(compositeLayer)); root->push(std::move(compositeLayer));
loaderData.doc->node.doc.vx = vBox.x; loaderData.doc->node.doc.vx = vBox.x;
loaderData.doc->node.doc.vy = vBox.y; loaderData.doc->node.doc.vy = vBox.y;

View File

@ -275,3 +275,16 @@ string svgUtilBase64Decode(const char *src)
} }
return decoded; 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 svgUtilURLDecode(const char *src);
string svgUtilBase64Decode(const char *src); string svgUtilBase64Decode(const char *src);
char* svgUtilStrndup(const char* str, size_t n);
#endif //_TVG_SVG_UTIL_H_ #endif //_TVG_SVG_UTIL_H_

View File

@ -33,6 +33,7 @@
#endif #endif
#include "tvgXmlParser.h" #include "tvgXmlParser.h"
#include "tvgSvgUtil.h"
/************************************************************************/ /************************************************************************/
/* Internal Class Implementation */ /* 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 */ /* External Class Implementation */
/************************************************************************/ /************************************************************************/
@ -564,10 +557,10 @@ const char* simpleXmlParseCSSAttribute(const char* buf, unsigned bufLength, char
} }
if (p == itr) *tag = strdup("all"); if (p == itr) *tag = strdup("all");
else *tag = _strndup(itr, p - itr); else *tag = svgUtilStrndup(itr, p - itr);
if (p == itrEnd) *name = nullptr; 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); 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 #!/bin/bash -e
rm -rf AUTHORS inc LICENSE src *.zip
curl -L -O https://github.com/thorvg/thorvg/archive/v$VERSION.zip VERSION=0.10.0
bsdtar --strip-components=1 -xvf *.zip
rm *.zip rm -rf AUTHORS LICENSE inc/ src/ *.zip *.tar.gz tmp/
rm -rf .github docs pc res test tools tvgcompat .git* *.md *.txt wasm_build.sh CODEOWNERS
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 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 # Fix newline at end of file.
cat << EOF > inc/config.h 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 #ifndef THORVG_CONFIG_H
#define 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 "$VERSION" #define THORVG_VERSION_STRING "$VERSION"
#endif #endif
EOF EOF
for source in $(find ./ -type f \( -iname \*.h -o -iname \*.cpp \)); do
sed -i -e '$a\' $source mkdir ../src
done 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/