diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..53a53b5b --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ +build +.vscode +*.swp +testShape +testMultiShapes +testBoundary +testPath +testPathCopy +testBlending +testUpdate +testDirectUpdate +testScene +testTransform +testCustomTransform +testSceneTransform +testStroke +testStrokeLine +testLinearGradient +testRadialGradient +testGradientTransform +testSvg +testSvg2 +testAsync +testCapi +testArc diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 00000000..a600a625 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,7 @@ +Hermet Park +Prudhvi Raj Vasireddi +Junsu Choi +Pranay Samanta +Mateusz Palkowski +Subhransu Mohanty +Mira Grudzinska diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..8becbac1 --- /dev/null +++ b/LICENSE @@ -0,0 +1,7 @@ +Copyright 2020 (see AUTHORS) + +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. diff --git a/README.md b/README.md index c97586d3..23628d0a 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,62 @@ -# thorvg -This is a lightweight vector graphics engine which provides a full set of shape drawing functionalities. +# ThorVG + +ThorVG is a platform independent lightweight standalone C++ library for drawing vector-based shapes and SVG. + +# +## Contents +- [Building ThorVG](#building-thorvg) + - [Meson Build](#meson-build) +- [Quick Start](#quick-start) +- [Issues or Feature Requests?](#issues-or-feature-requests) +# +## Building ThorVG +thorvg supports [meson](https://mesonbuild.com/) build system. +# +### Meson Build +install [meson](http://mesonbuild.com/Getting-meson.html) and [ninja](https://ninja-build.org/) if not already installed. + +Run meson to configure ThorVG. +``` +meson build +``` +Run ninja to build & install ThorVG. +``` +ninja -C build install +``` +[Back to contents](#contents) +# +## Quick Start +ThorVG renders vector shapes on a given canvas buffer. + +You can initialize ThorVG engine first: +```cpp +tvg::Initializer::init(tvg::CanvasEngine::Sw); +``` +You can prepare a empty canvas for drawing on it. +```cpp +static uint32_t buffer[WIDTH * HEIGHT]; //canvas target buffer + +auto canvas = tvg::SwCanvas::gen(); //generate a canvas +canvas->target(buffer, WIDTH, WIDTH, HEIGHT); //stride, w, h +``` + +Next you can draw shapes onto the canvas. +```cpp +auto shape = tvg::Shape::gen(); //generate a shape +shape->appendRect(0, 0, 200, 200, 0, 0); //x, y, w, h, rx, ry +shape->appendCircle(400, 400, 100, 100); //cx, cy, radiusW, radiusH +shape->fill(255, 255, 0, 255); //r, g, b, a + +canvas->push(move(shape)); //push shape drawing command +``` +Begin rendering & finish it at a particular time. +```cpp +canvas->draw(); +canvas->sync(); +``` +Lastly, you can acquire the rendered image in buffer memory. + +[Back to contents](#contents) +# +## Issues or Feature Requests? +For immidiate assistant or support please reach us in [Gitter](https://gitter.im/thorvg-dev/community#) diff --git a/inc/meson.build b/inc/meson.build new file mode 100644 index 00000000..90e3fc4c --- /dev/null +++ b/inc/meson.build @@ -0,0 +1,7 @@ +header_files = ['thorvg.h'] + +if get_option('bindings').contains('capi') == true + header_files += ['thorvg_capi.h'] +endif + +install_headers(header_files) diff --git a/inc/thorvg.h b/inc/thorvg.h new file mode 100644 index 00000000..16252fe9 --- /dev/null +++ b/inc/thorvg.h @@ -0,0 +1,381 @@ +#ifndef _THORVG_H_ +#define _THORVG_H_ + +#include + +#ifdef TVG_BUILD + #define TVG_EXPORT __attribute__ ((visibility ("default"))) +#else + #define TVG_EXPORT +#endif + +#ifdef LOG_TAG +#undef LOG_TAG +#endif +#define LOG_TAG "TVG" + + +#ifdef __cplusplus +extern "C" { +#endif + + +#define _TVG_DECLARE_PRIVATE(A) \ +protected: \ + struct Impl; \ + std::unique_ptr pImpl; \ + A(const A&) = delete; \ + const A& operator=(const A&) = delete; \ + A() + +#define _TVG_DISABLE_CTOR(A) \ + A() = delete; \ + ~A() = delete + +#define _TVG_DECLARE_ACCESSOR() \ + friend Canvas; \ + friend Scene; \ + friend Picture + +#define _TVG_DECALRE_IDENTIFIER() \ + auto id() const { return _id; } \ +protected: \ + unsigned _id + +namespace tvg +{ + +class RenderMethod; +class Scene; +class Picture; +class Canvas; + + +enum class TVG_EXPORT Result { Success = 0, InvalidArguments, InsufficientCondition, FailedAllocation, MemoryCorruption, NonSupport, Unknown }; +enum class TVG_EXPORT PathCommand { Close = 0, MoveTo, LineTo, CubicTo }; +enum class TVG_EXPORT StrokeCap { Square = 0, Round, Butt }; +enum class TVG_EXPORT StrokeJoin { Bevel = 0, Round, Miter }; +enum class TVG_EXPORT FillSpread { Pad = 0, Reflect, Repeat }; +enum class TVG_EXPORT CanvasEngine { Sw = (1 << 1), Gl = (1 << 2)}; + + +struct Point +{ + float x, y; +}; + + +struct Matrix +{ + float e11, e12, e13; + float e21, e22, e23; + float e31, e32, e33; +}; + + +/** + * @class Paint + * + * @ingroup ThorVG + * + * @brief description... + * + */ +class TVG_EXPORT Paint +{ +public: + virtual ~Paint(); + + Result rotate(float degree) noexcept; + Result scale(float factor) noexcept; + Result translate(float x, float y) noexcept; + Result transform(const Matrix& m) noexcept; + Result bounds(float* x, float* y, float* w, float* h) const noexcept; + + _TVG_DECLARE_ACCESSOR(); + _TVG_DECLARE_PRIVATE(Paint); +}; + + +/** + * @class Fill + * + * @ingroup ThorVG + * + * @brief description... + * + */ +class TVG_EXPORT Fill +{ +public: + struct ColorStop + { + float offset; + uint8_t r, g, b, a; + }; + + virtual ~Fill(); + + Result colorStops(const ColorStop* colorStops, uint32_t cnt) noexcept; + Result spread(FillSpread s) noexcept; + + uint32_t colorStops(const ColorStop** colorStops) const noexcept; + FillSpread spread() const noexcept; + + _TVG_DECALRE_IDENTIFIER(); + _TVG_DECLARE_PRIVATE(Fill); +}; + + +/** + * @class Canvas + * + * @ingroup ThorVG + * + * @brief description... + * + */ +class TVG_EXPORT Canvas +{ +public: + Canvas(RenderMethod*); + virtual ~Canvas(); + + Result reserve(uint32_t n) noexcept; + virtual Result push(std::unique_ptr paint) noexcept; + virtual Result clear() noexcept; + virtual Result update(Paint* paint) noexcept; + virtual Result draw() noexcept; + virtual Result sync() noexcept; + + _TVG_DECLARE_PRIVATE(Canvas); +}; + + +/** + * @class LinearGradient + * + * @ingroup ThorVG + * + * @brief description... + * + */ +class TVG_EXPORT LinearGradient final : public Fill +{ +public: + ~LinearGradient(); + + Result linear(float x1, float y1, float x2, float y2) noexcept; + Result linear(float* x1, float* y1, float* x2, float* y2) const noexcept; + + static std::unique_ptr gen() noexcept; + + _TVG_DECLARE_PRIVATE(LinearGradient); +}; + + +/** + * @class RadialGradient + * + * @ingroup ThorVG + * + * @brief description... + * + */ +class TVG_EXPORT RadialGradient final : public Fill +{ +public: + ~RadialGradient(); + + Result radial(float cx, float cy, float radius) noexcept; + Result radial(float* cx, float* cy, float* radius) const noexcept; + + static std::unique_ptr gen() noexcept; + + _TVG_DECLARE_PRIVATE(RadialGradient); +}; + + + +/** + * @class Shape + * + * @ingroup ThorVG + * + * @brief description... + * + */ +class TVG_EXPORT Shape final : public Paint +{ +public: + ~Shape(); + + Result reset() noexcept; + + //Path + Result moveTo(float x, float y) noexcept; + Result lineTo(float x, float y) noexcept; + Result cubicTo(float cx1, float cy1, float cx2, float cy2, float x, float y) noexcept; + Result close() noexcept; + + //Shape + Result appendRect(float x, float y, float w, float h, float rx, float ry) noexcept; + Result appendCircle(float cx, float cy, float rx, float ry) noexcept; + Result appendArc(float cx, float cy, float radius, float startAngle, float sweep, bool pie) noexcept; + Result appendPath(const PathCommand* cmds, uint32_t cmdCnt, const Point* pts, uint32_t ptsCnt) noexcept; + + //Stroke + Result stroke(float width) noexcept; + Result stroke(uint8_t r, uint8_t g, uint8_t b, uint8_t a) noexcept; + Result stroke(const float* dashPattern, uint32_t cnt) noexcept; + Result stroke(StrokeCap cap) noexcept; + Result stroke(StrokeJoin join) noexcept; + + //Fill + Result fill(uint8_t r, uint8_t g, uint8_t b, uint8_t a) noexcept; + Result fill(std::unique_ptr f) noexcept; + + //Getters + uint32_t pathCommands(const PathCommand** cmds) const noexcept; + uint32_t pathCoords(const Point** pts) const noexcept; + Result fill(uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a) const noexcept; + const Fill* fill() const noexcept; + + float strokeWidth() const noexcept; + Result strokeColor(uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a) const noexcept; + uint32_t strokeDash(const float** dashPattern) const noexcept; + StrokeCap strokeCap() const noexcept; + StrokeJoin strokeJoin() const noexcept; + + static std::unique_ptr gen() noexcept; + + _TVG_DECLARE_PRIVATE(Shape); +}; + + +/** + * @class Picture + * + * @ingroup ThorVG + * + * @brief description... + * + */ +class TVG_EXPORT Picture final : public Paint +{ +public: + ~Picture(); + + Result load(const std::string& path) noexcept; + Result load(const char* data, uint32_t size) noexcept; + Result viewbox(float* x, float* y, float* w, float* h) const noexcept; + + static std::unique_ptr gen() noexcept; + + _TVG_DECLARE_PRIVATE(Picture); +}; + + +/** + * @class Scene + * + * @ingroup ThorVG + * + * @brief description... + * + */ +class TVG_EXPORT Scene final : public Paint +{ +public: + ~Scene(); + + Result push(std::unique_ptr paint) noexcept; + Result reserve(uint32_t size) noexcept; + + static std::unique_ptr gen() noexcept; + + _TVG_DECLARE_PRIVATE(Scene); +}; + + +/** + * @class SwCanvas + * + * @ingroup ThorVG + * + @brief description... + * + */ +class TVG_EXPORT SwCanvas final : public Canvas +{ +public: + ~SwCanvas(); + + enum Colorspace { ABGR8888 = 0, ARGB8888 }; + + Result target(uint32_t* buffer, uint32_t stride, uint32_t w, uint32_t h, Colorspace cs) noexcept; + + static std::unique_ptr gen() noexcept; + + _TVG_DECLARE_PRIVATE(SwCanvas); +}; + + +/** + * @class GlCanvas + * + * @ingroup ThorVG + * + * @brief description... + * + */ +class TVG_EXPORT GlCanvas final : public Canvas +{ +public: + ~GlCanvas(); + + //TODO: Gl Specific methods. Need gl backend configuration methods as well. + Result target(uint32_t* buffer, uint32_t stride, uint32_t w, uint32_t h) noexcept; + + static std::unique_ptr gen() noexcept; + + _TVG_DECLARE_PRIVATE(GlCanvas); +}; + + +/** + * @class Engine + * + * @ingroup ThorVG + * + * @brief description... + * + */ +class TVG_EXPORT Initializer final +{ +public: + + /** + * @brief ... + * + * @param[in] arg ... + * + * @note ... + * + * @return ... + * + * @see ... + */ + static Result init(CanvasEngine engine, uint32_t threads) noexcept; + static Result term(CanvasEngine engine) noexcept; + + _TVG_DISABLE_CTOR(Initializer); +}; + +} //namespace + +#ifdef __cplusplus +} +#endif + +#endif //_THORVG_H_ diff --git a/inc/thorvg_capi.h b/inc/thorvg_capi.h new file mode 100644 index 00000000..afc294a5 --- /dev/null +++ b/inc/thorvg_capi.h @@ -0,0 +1,171 @@ +#ifndef __THORVG_CAPI_H__ +#define __THORVG_CAPI_H__ + +#ifdef TVG_EXPORT + #undef TVG_EXPORT +#endif + +#ifdef TVG_BUILD + #define TVG_EXPORT __attribute__ ((visibility ("default"))) +#else + #define TVG_EXPORT +#endif + + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct _Tvg_Canvas Tvg_Canvas; +typedef struct _Tvg_Paint Tvg_Paint; +typedef struct _Tvg_Gradient Tvg_Gradient; + +#define TVG_ENGINE_SW (1 << 1) +#define TVG_ENGINE_GL (1 << 2) + +#define TVG_COLORSPACE_ABGR8888 0 +#define TVG_COLORSPACE_ARGB8888 1 + +typedef enum { + TVG_RESULT_SUCCESS = 0, + TVG_RESULT_INVALID_ARGUMENT, + TVG_RESULT_INSUFFICIENT_CONDITION, + TVG_RESULT_FAILED_ALLOCATION, + TVG_RESULT_MEMORY_CORRUPTION, + TVG_RESULT_NOT_SUPPORTED, + TVG_RESULT_UNKNOWN +} Tvg_Result; + + +typedef enum { + TVG_PATH_COMMAND_CLOSE = 0, + TVG_PATH_COMMAND_MOVE_TO, + TVG_PATH_COMMAND_LINE_TO, + TVG_PATH_COMMAND_CUBIC_TO +} Tvg_Path_Command; + + +typedef enum { + TVG_STROKE_CAP_SQUARE = 0, + TVG_STROKE_CAP_ROUND, + TVG_STROKE_CAP_BUTT +} Tvg_Stroke_Cap; + + +typedef enum { + TVG_STROKE_JOIN_BEVEL = 0, + TVG_STROKE_JOIN_ROUND, + TVG_STROKE_JOIN_MITER +} Tvg_Stroke_Join; + + +typedef enum { + TVG_STROKE_FILL_PAD = 0, + TVG_STROKE_FILL_REFLECT, + TVG_STROKE_FILL_REPEAT +} Tvg_Stroke_Fill; + + +typedef struct +{ + float x, y; +} Tvg_Point; + + +typedef struct +{ + float e11, e12, e13; + float e21, e22, e23; + float e31, e32, e33; +} Tvg_Matrix; + +typedef struct +{ + float offset; + uint8_t r, g, b, a; +} Tvg_Color_Stop; + +/************************************************************************/ +/* Engine API */ +/************************************************************************/ +TVG_EXPORT Tvg_Result tvg_engine_init(unsigned engine_method, unsigned threads); +TVG_EXPORT Tvg_Result tvg_engine_term(unsigned engine_method); + + +/************************************************************************/ +/* SwCanvas API */ +/************************************************************************/ +TVG_EXPORT Tvg_Canvas* tvg_swcanvas_create(); +TVG_EXPORT Tvg_Result tvg_swcanvas_set_target(Tvg_Canvas* canvas, uint32_t* buffer, uint32_t stride, uint32_t w, uint32_t h, uint32_t cs); + + +/************************************************************************/ +/* Common Canvas API */ +/************************************************************************/ +TVG_EXPORT Tvg_Result tvg_canvas_destroy(Tvg_Canvas* canvas); +TVG_EXPORT Tvg_Result tvg_canvas_push(Tvg_Canvas* canvas, Tvg_Paint* paint); +TVG_EXPORT Tvg_Result tvg_canvas_reserve(Tvg_Canvas* canvas, uint32_t n); +TVG_EXPORT Tvg_Result tvg_canvas_clear(Tvg_Canvas* canvas); +TVG_EXPORT Tvg_Result tvg_canvas_update(Tvg_Canvas* canvas); +TVG_EXPORT Tvg_Result tvg_canvas_update_paint(Tvg_Canvas* canvas, Tvg_Paint* paint); +TVG_EXPORT Tvg_Result tvg_canvas_draw(Tvg_Canvas* canvas); +TVG_EXPORT Tvg_Result tvg_canvas_sync(Tvg_Canvas* canvas); + + +/************************************************************************/ +/* Paint API */ +/************************************************************************/ +TVG_EXPORT Tvg_Result tvg_paint_del(Tvg_Paint* paint); +TVG_EXPORT Tvg_Result tvg_paint_scale(Tvg_Paint* paint, float factor); +TVG_EXPORT Tvg_Result tvg_paint_rotate(Tvg_Paint* paint, float degree); +TVG_EXPORT Tvg_Result tvg_paint_translate(Tvg_Paint* paint, float x, float y); +TVG_EXPORT Tvg_Result tvg_paint_transform(Tvg_Paint* paint, const Tvg_Matrix* m); + + +/************************************************************************/ +/* Shape API */ +/************************************************************************/ +TVG_EXPORT Tvg_Paint* tvg_shape_new(); +TVG_EXPORT Tvg_Result tvg_shape_reset(Tvg_Paint* paint); +TVG_EXPORT Tvg_Result tvg_shape_move_to(Tvg_Paint* paint, float x, float y); +TVG_EXPORT Tvg_Result tvg_shape_line_to(Tvg_Paint* paint, float x, float y); +TVG_EXPORT Tvg_Result tvg_shape_cubic_to(Tvg_Paint* paint, float cx1, float cy1, float cx2, float cy2, float x, float y); +TVG_EXPORT Tvg_Result tvg_shape_close(Tvg_Paint* paint); +TVG_EXPORT Tvg_Result tvg_shape_append_rect(Tvg_Paint* paint, float x, float y, float w, float h, float rx, float ry); +TVG_EXPORT Tvg_Result tvg_shape_append_circle(Tvg_Paint* paint, float cx, float cy, float rx, float ry); +TVG_EXPORT Tvg_Result tvg_shape_append_arc(Tvg_Paint* paint, float cx, float cy, float radius, float startAngle, float sweep, uint8_t pie); +TVG_EXPORT Tvg_Result tvg_shape_append_path(Tvg_Paint* paint, const Tvg_Path_Command* cmds, uint32_t cmdCnt, const Tvg_Point* pts, uint32_t ptsCnt); +TVG_EXPORT Tvg_Result tvg_shape_set_stroke_width(Tvg_Paint* paint, float width); +TVG_EXPORT Tvg_Result tvg_shape_set_stroke_color(Tvg_Paint* paint, uint8_t r, uint8_t g, uint8_t b, uint8_t a); +TVG_EXPORT Tvg_Result tvg_shape_set_stroke_dash(Tvg_Paint* paint, const float* dashPattern, uint32_t cnt); +TVG_EXPORT Tvg_Result tvg_shape_set_stroke_cap(Tvg_Paint* paint, Tvg_Stroke_Cap cap); +TVG_EXPORT Tvg_Result tvg_shape_set_stroke_join(Tvg_Paint* paint, Tvg_Stroke_Join join); +TVG_EXPORT Tvg_Result tvg_shape_fill_color(Tvg_Paint* paint, uint8_t r, uint8_t g, uint8_t b, uint8_t a); +TVG_EXPORT Tvg_Result tvg_shape_linear_gradient_set(Tvg_Paint* paint, Tvg_Gradient *grad); +TVG_EXPORT Tvg_Result tvg_shape_radial_gradient_set(Tvg_Paint* paint, Tvg_Gradient *grad); + + +/************************************************************************/ +/* Gradient API */ +/************************************************************************/ +TVG_EXPORT Tvg_Gradient* tvg_linear_gradient_new(); +TVG_EXPORT Tvg_Gradient* tvg_radial_gradient_new(); +TVG_EXPORT Tvg_Result tvg_gradient_del(Tvg_Gradient* grad); +TVG_EXPORT Tvg_Result tvg_linear_gradient_set(Tvg_Gradient* grad, float x1, float y1, float x2, float y2); +TVG_EXPORT Tvg_Result tvg_radial_gradient_set(Tvg_Gradient* grad, float cx, float cy, float radius); +TVG_EXPORT Tvg_Result tvg_gradient_color_stops(Tvg_Gradient* grad, const Tvg_Color_Stop* color_stop, uint32_t cnt); +TVG_EXPORT Tvg_Result tvg_gradient_spread(Tvg_Gradient* grad, const Tvg_Stroke_Fill); + + +/************************************************************************/ +/* Picture API */ +/************************************************************************/ +TVG_EXPORT Tvg_Paint* tvg_picture_new(); +TVG_EXPORT Tvg_Result tvg_picture_load(Tvg_Paint* paint, const char* path); +TVG_EXPORT Tvg_Result tvg_picture_get_viewbox(Tvg_Paint* paint, float* x, float* y, float* w, float* h); + +#ifdef __cplusplus +} +#endif + +#endif //_THORVG_CAPI_H_ diff --git a/meson.build b/meson.build new file mode 100644 index 00000000..981e7535 --- /dev/null +++ b/meson.build @@ -0,0 +1,51 @@ +project('thorvg', + 'cpp', + default_options : ['buildtype=debugoptimized', 'werror=false', 'optimization=s'], + version : '0.1.0', + license : 'MIT') + +config_h = configuration_data() + +if get_option('engines').contains('sw') == true + config_h.set10('THORVG_SW_RASTER_SUPPORT', true) +endif + +if get_option('engines').contains('gl') == true + config_h.set10('THORVG_GL_RASTER_SUPPORT', true) +endif + +if get_option('loaders').contains('svg') == true + config_h.set10('THORVG_SVG_LOADER_SUPPORT', true) +endif + +if get_option('vectors').contains('avx') == true + config_h.set10('THORVG_AVX_VECTOR_SUPPORT', true) +endif + +if get_option('bindings').contains('capi') == true + config_h.set10('THORVG_CAPI_BINDING_SUPPORT', true) +endif + +configure_file( + output: 'config.h', + configuration: config_h +) + +headers = [include_directories('inc'), include_directories('.')] + +subdir('inc') +subdir('src') + +summary = ''' + +Summary: + thorvg version : @0@ + Build type : @1@ + Prefix : @2@ +'''.format( + meson.project_version(), + get_option('buildtype'), + get_option('prefix'), + ) + +message(summary) diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 00000000..3990b177 --- /dev/null +++ b/meson_options.txt @@ -0,0 +1,23 @@ +option('engines', + type: 'array', + choices: ['sw', 'gl'], + value: ['sw'], + description: 'Enable Rasterizer Engine in thorvg') + +option('loaders', + type: 'array', + choices: ['', 'svg'], + value: ['svg'], + description: 'Enable Vector File Loader in thorvg') + +option('vectors', + type: 'array', + choices: ['', 'avx'], + value: [''], + description: 'Enable CPU Vectorization(SIMD) in thorvg') + +option('bindings', + type: 'array', + choices: ['', 'capi'], + value: ['capi'], + description: 'Enable C API binding') diff --git a/packaging/thorvg.manifest b/packaging/thorvg.manifest new file mode 100644 index 00000000..017d22d3 --- /dev/null +++ b/packaging/thorvg.manifest @@ -0,0 +1,5 @@ + + + + + diff --git a/packaging/thorvg.spec b/packaging/thorvg.spec new file mode 100644 index 00000000..8b7f6f54 --- /dev/null +++ b/packaging/thorvg.spec @@ -0,0 +1,64 @@ +Name: thorvg +Summary: Thor Vector Graphics Library +Version: 0.0.1 +Release: 1 +Group: Graphics System/Rendering Engine +License: MIT +URL: https://github.com/samsung/thorvg +Source0: %{name}-%{version}.tar.gz + +BuildRequires: pkgconfig +BuildRequires: pkgconfig(glesv2) + +BuildRequires: meson +BuildRequires: ninja +Requires(post): /sbin/ldconfig +Requires(postun): /sbin/ldconfig + +%description +Thor Vector Graphics Library + + +%package devel +Summary: Thor Vector Graphics Library (devel) +Group: Development/Libraries +Requires: %{name} = %{version}-%{release} + + +%description devel +Thor Vector Graphics Library (devel) + + +%prep +%setup -q + + +%build + +export DESTDIR=%{buildroot} + +meson setup \ + --prefix /usr \ + --libdir %{_libdir} \ + builddir 2>&1 +ninja \ + -C builddir \ + -j %(echo "`/usr/bin/getconf _NPROCESSORS_ONLN`") + +%install + +export DESTDIR=%{buildroot} + +ninja -C builddir install + +%files +%defattr(-,root,root,-) +%{_libdir}/libthorvg.so.* +%manifest packaging/thorvg.manifest + +%files devel +%defattr(-,root,root,-) +%{_includedir}/*.h +%{_libdir}/libthorvg.so + +%{_libdir}/pkgconfig/thorvg.pc diff --git a/pc/thorvg.pc.in b/pc/thorvg.pc.in new file mode 100644 index 00000000..53235fe3 --- /dev/null +++ b/pc/thorvg.pc.in @@ -0,0 +1,10 @@ +prefix=@PREFIX@ +libdir=@LIBDIR@ +includedir=@INCDIR@ + +Name: Thor Vector Graphics +Description: Thor Vector Graphics Library +Version: @VERSION@ +Requires: +Libs: -L${libdir} -lthorvg +Cflags: -I${includedir}/thorvg diff --git a/src/bindings/capi/meson.build b/src/bindings/capi/meson.build new file mode 100644 index 00000000..919442ae --- /dev/null +++ b/src/bindings/capi/meson.build @@ -0,0 +1,8 @@ +source_file = [ + 'tvgCapi.cpp', +] + +subbinding_dep += [declare_dependency( + include_directories : include_directories('.'), + sources : source_file +)] diff --git a/src/bindings/capi/tvgCapi.cpp b/src/bindings/capi/tvgCapi.cpp new file mode 100644 index 00000000..01551969 --- /dev/null +++ b/src/bindings/capi/tvgCapi.cpp @@ -0,0 +1,349 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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 +#include "thorvg_capi.h" + +using namespace std; +using namespace tvg; + +#ifdef __cplusplus +extern "C" { +#endif + +struct _Tvg_Canvas +{ + //Dummy for Direct Casting +}; + +struct _Tvg_Paint +{ + //Dummy for Direct Casting +}; + +struct _Tvg_Gradient +{ + //Dummy for Direct Casting +}; + + +/************************************************************************/ +/* Engine API */ +/************************************************************************/ + +TVG_EXPORT Tvg_Result tvg_engine_init(unsigned engine_method, unsigned threads) { + Result ret = Result::Success; + + if (engine_method & TVG_ENGINE_SW) ret = tvg::Initializer::init(tvg::CanvasEngine::Sw, threads); + if (ret != Result::Success) return (Tvg_Result) ret; + + if (engine_method & TVG_ENGINE_GL) ret = tvg::Initializer::init(tvg::CanvasEngine::Gl, threads); + return (Tvg_Result) ret; +} + + +TVG_EXPORT Tvg_Result tvg_engine_term(unsigned engine_method) { + Result ret = Result::Success; + + if (engine_method & TVG_ENGINE_SW) ret = tvg::Initializer::term(tvg::CanvasEngine::Sw); + if (ret != Result::Success) return (Tvg_Result) ret; + + if (engine_method & TVG_ENGINE_GL) ret = tvg::Initializer::term(tvg::CanvasEngine::Gl); + return (Tvg_Result) ret; +} + +/************************************************************************/ +/* Canvas API */ +/************************************************************************/ + +TVG_EXPORT Tvg_Canvas* tvg_swcanvas_create() +{ + return (Tvg_Canvas*) SwCanvas::gen().release(); +} + + +TVG_EXPORT Tvg_Result tvg_canvas_destroy(Tvg_Canvas* canvas) +{ + delete(canvas); + return TVG_RESULT_SUCCESS; +} + + +TVG_EXPORT Tvg_Result tvg_swcanvas_set_target(Tvg_Canvas* canvas, uint32_t* buffer, uint32_t stride, uint32_t w, uint32_t h, uint32_t cs) +{ + return (Tvg_Result) reinterpret_cast(canvas)->target(buffer, stride, w, h, static_cast(cs)); +} + + +TVG_EXPORT Tvg_Result tvg_canvas_push(Tvg_Canvas* canvas, Tvg_Paint* paint) +{ + return (Tvg_Result) reinterpret_cast(canvas)->push(unique_ptr((Paint*)paint)); +} + + +TVG_EXPORT Tvg_Result tvg_canvas_reserve(Tvg_Canvas* canvas, uint32_t n) +{ + return (Tvg_Result) reinterpret_cast(canvas)->reserve(n); +} + + +TVG_EXPORT Tvg_Result tvg_canvas_clear(Tvg_Canvas* canvas) +{ + return (Tvg_Result) reinterpret_cast(canvas)->clear(); +} + + +TVG_EXPORT Tvg_Result tvg_canvas_update(Tvg_Canvas* canvas) +{ + return (Tvg_Result) reinterpret_cast(canvas)->update(nullptr); +} + + +TVG_EXPORT Tvg_Result tvg_canvas_update_paint(Tvg_Canvas* canvas, Tvg_Paint* paint) +{ + return (Tvg_Result) reinterpret_cast(canvas)->update((Paint*) paint); +} + + +TVG_EXPORT Tvg_Result tvg_canvas_draw(Tvg_Canvas* canvas) +{ + return (Tvg_Result) reinterpret_cast(canvas)->draw(); +} + + +TVG_EXPORT Tvg_Result tvg_canvas_sync(Tvg_Canvas* canvas) +{ + return (Tvg_Result) reinterpret_cast(canvas)->sync(); +} + + +/************************************************************************/ +/* Paint API */ +/************************************************************************/ + +TVG_EXPORT Tvg_Result tvg_paint_del(Tvg_Paint* paint) +{ + delete(paint); + return TVG_RESULT_SUCCESS; +} + + +TVG_EXPORT Tvg_Result tvg_paint_scale(Tvg_Paint* paint, float factor) +{ + return (Tvg_Result) reinterpret_cast(paint)->scale(factor); +} + + +TVG_EXPORT Tvg_Result tvg_paint_rotate(Tvg_Paint* paint, float degree) +{ + return (Tvg_Result) reinterpret_cast(paint)->rotate(degree); +} + + +TVG_EXPORT Tvg_Result tvg_paint_translate(Tvg_Paint* paint, float x, float y) +{ + return (Tvg_Result) reinterpret_cast(paint)->translate(x, y); +} + + +TVG_EXPORT Tvg_Result tvg_paint_transform(Tvg_Paint* paint, const Tvg_Matrix* m) +{ + return (Tvg_Result) reinterpret_cast(paint)->transform(*(reinterpret_cast(m))); +} + + +/************************************************************************/ +/* Shape API */ +/************************************************************************/ + +TVG_EXPORT Tvg_Paint* tvg_shape_new() +{ + return (Tvg_Paint*) Shape::gen().release(); +} + + +TVG_EXPORT Tvg_Result tvg_shape_reset(Tvg_Paint* paint) +{ + return (Tvg_Result) reinterpret_cast(paint)->reset(); +} + + +TVG_EXPORT Tvg_Result tvg_shape_move_to(Tvg_Paint* paint, float x, float y) +{ + return (Tvg_Result) reinterpret_cast(paint)->moveTo(x, y); +} + + +TVG_EXPORT Tvg_Result tvg_shape_line_to(Tvg_Paint* paint, float x, float y) +{ + return (Tvg_Result) reinterpret_cast(paint)->lineTo(x, y); +} + + +TVG_EXPORT Tvg_Result tvg_shape_cubic_to(Tvg_Paint* paint, float cx1, float cy1, float cx2, float cy2, float x, float y) +{ + return (Tvg_Result) reinterpret_cast(paint)->cubicTo(cx1, cy1, cx2, cy2, x, y); +} + + +TVG_EXPORT Tvg_Result tvg_shape_close(Tvg_Paint* paint) +{ + return (Tvg_Result) reinterpret_cast(paint)->close(); +} + + +TVG_EXPORT Tvg_Result tvg_shape_append_rect(Tvg_Paint* paint, float x, float y, float w, float h, float rx, float ry) +{ + return (Tvg_Result) reinterpret_cast(paint)->appendRect(x, y, w, h, rx, ry); +} + +TVG_EXPORT Tvg_Result tvg_shape_append_arc(Tvg_Paint* paint, float cx, float cy, float radius, float startAngle, float sweep, uint8_t pie) +{ + return (Tvg_Result) reinterpret_cast(paint)->appendArc(cx, cy, radius, startAngle, sweep, pie); +} + +TVG_EXPORT Tvg_Result tvg_shape_append_circle(Tvg_Paint* paint, float cx, float cy, float rx, float ry) +{ + return (Tvg_Result) reinterpret_cast(paint)->appendCircle(cx, cy, rx, ry); +} + + +TVG_EXPORT Tvg_Result tvg_shape_append_path(Tvg_Paint* paint, const Tvg_Path_Command* cmds, uint32_t cmdCnt, const Tvg_Point* pts, uint32_t ptsCnt) +{ + return (Tvg_Result) reinterpret_cast(paint)->appendPath((PathCommand*)cmds, cmdCnt, (Point*)pts, ptsCnt); +} + + +TVG_EXPORT Tvg_Result tvg_shape_set_stroke_width(Tvg_Paint* paint, float width) +{ + return (Tvg_Result) reinterpret_cast(paint)->stroke(width); +} + + +TVG_EXPORT Tvg_Result tvg_shape_set_stroke_color(Tvg_Paint* paint, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ + return (Tvg_Result) reinterpret_cast(paint)->stroke(r, g, b, a); +} + + +TVG_EXPORT Tvg_Result tvg_shape_set_stroke_dash(Tvg_Paint* paint, const float* dashPattern, uint32_t cnt) +{ + return (Tvg_Result) reinterpret_cast(paint)->stroke(dashPattern, cnt); +} + + +TVG_EXPORT Tvg_Result tvg_shape_set_stroke_cap(Tvg_Paint* paint, Tvg_Stroke_Cap cap) +{ + return (Tvg_Result) reinterpret_cast(paint)->stroke((StrokeCap)cap); +} + + +TVG_EXPORT Tvg_Result tvg_shape_set_stroke_join(Tvg_Paint* paint, Tvg_Stroke_Join join) +{ + return (Tvg_Result) reinterpret_cast(paint)->stroke((StrokeJoin)join); +} + + +TVG_EXPORT Tvg_Result tvg_shape_fill_color(Tvg_Paint* paint, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ + return (Tvg_Result) reinterpret_cast(paint)->fill(r, g, b, a); +} + + +TVG_EXPORT Tvg_Result tvg_shape_linear_gradient_set(Tvg_Paint* paint, Tvg_Gradient *gradient) +{ + return (Tvg_Result) reinterpret_cast(paint)->fill(unique_ptr((LinearGradient*)(gradient))); +} + + +TVG_EXPORT Tvg_Result tvg_shape_radial_gradient_set(Tvg_Paint* paint, Tvg_Gradient *gradient) +{ + return (Tvg_Result) reinterpret_cast(paint)->fill(unique_ptr((RadialGradient*)(gradient))); +} + + +/************************************************************************/ +/* Picture API */ +/************************************************************************/ + +TVG_EXPORT Tvg_Paint* tvg_picture_new() +{ + return (Tvg_Paint*) Picture::gen().release(); +} + + +TVG_EXPORT Tvg_Result tvg_picture_load(Tvg_Paint* paint, const char* path) +{ + return (Tvg_Result) reinterpret_cast(paint)->load(path); +} + + +TVG_EXPORT Tvg_Result tvg_picture_get_viewbox(Tvg_Paint* paint, float* x, float* y, float* w, float* h) +{ + return (Tvg_Result) reinterpret_cast(paint)->viewbox(x, y, w, h); +} + + +/************************************************************************/ +/* Gradient API */ +/************************************************************************/ +TVG_EXPORT Tvg_Gradient* tvg_linear_gradient_new() +{ + return (Tvg_Gradient*)LinearGradient::gen().release(); +} + +TVG_EXPORT Tvg_Gradient* tvg_radial_gradient_new() +{ + return (Tvg_Gradient*)RadialGradient::gen().release(); + +} + +TVG_EXPORT Tvg_Result tvg_gradient_del(Tvg_Gradient* grad) +{ + delete(grad); + return TVG_RESULT_SUCCESS; +} + +TVG_EXPORT Tvg_Result tvg_linear_gradient_set(Tvg_Gradient* grad, float x1, float y1, float x2, float y2) +{ + return (Tvg_Result) reinterpret_cast(grad)->linear(x1, y1, x2, y2); +} + +TVG_EXPORT Tvg_Result tvg_radial_gradient_set(Tvg_Gradient* grad, float cx, float cy, float radius) +{ + return (Tvg_Result) reinterpret_cast(grad)->radial(cx, cy, radius); +} + +TVG_EXPORT Tvg_Result tvg_gradient_color_stops(Tvg_Gradient* grad, const Tvg_Color_Stop* color_stop, uint32_t cnt) +{ + return (Tvg_Result) reinterpret_cast(grad)->colorStops(reinterpret_cast(color_stop), cnt); +} + +TVG_EXPORT Tvg_Result tvg_gradient_spread(Tvg_Gradient* grad, const Tvg_Stroke_Fill spread) +{ + return (Tvg_Result) reinterpret_cast(grad)->spread((FillSpread)spread); +} + + +#ifdef __cplusplus +} +#endif diff --git a/src/bindings/meson.build b/src/bindings/meson.build new file mode 100644 index 00000000..2b3a1506 --- /dev/null +++ b/src/bindings/meson.build @@ -0,0 +1,11 @@ +subbinding_dep = [] + +if get_option('bindings').contains('capi') == true + subdir('capi') + message('Enable CAPI Bindings') +endif + +binding_dep = declare_dependency( + dependencies: subbinding_dep, + include_directories : include_directories('.'), +) diff --git a/src/examples/main.cpp b/src/examples/main.cpp new file mode 100644 index 00000000..8bd265a2 --- /dev/null +++ b/src/examples/main.cpp @@ -0,0 +1,11 @@ +#include + +using namespace std; + +int +main(int argc, char *argv[]) +{ + cout << "test thorvg!" << endl; + + return 0; +} diff --git a/src/examples/meson.build b/src/examples/meson.build new file mode 100644 index 00000000..4ba6e9be --- /dev/null +++ b/src/examples/meson.build @@ -0,0 +1 @@ +executable('thorvg_sample', 'main.cpp') diff --git a/src/lib/gl_engine/meson.build b/src/lib/gl_engine/meson.build new file mode 100644 index 00000000..63c7ee89 --- /dev/null +++ b/src/lib/gl_engine/meson.build @@ -0,0 +1,25 @@ +source_file = [ + 'tvgGlCommon.h', + 'tvgGlGeometry.h', + 'tvgGlGpuBuffer.h', + 'tvgGlProgram.h', + 'tvgGlRenderer.h', + 'tvgGlShader.h', + 'tvgGlShaderSrc.h', + 'tvgGlGeometry.cpp', + 'tvgGlGpuBuffer.cpp', + 'tvgGlProgram.cpp', + 'tvgGlRenderer.cpp', + 'tvgGlShader.cpp', + 'tvgGlShaderSrc.cpp', +] + +egl_dep = meson.get_compiler('cpp').find_library('EGL') +gles_dep = meson.get_compiler('cpp').find_library('GLESv2') +external_dep = [egl_dep, gles_dep] + +engine_dep += [declare_dependency( + dependencies : external_dep, + include_directories : include_directories('.'), + sources : source_file, +)] diff --git a/src/lib/gl_engine/tvgGlCommon.h b/src/lib/gl_engine/tvgGlCommon.h new file mode 100644 index 00000000..db137f22 --- /dev/null +++ b/src/lib/gl_engine/tvgGlCommon.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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_GL_COMMON_H_ +#define _TVG_GL_COMMON_H_ + +#include "tvgCommon.h" + + +#define GL_CHECK(x) \ + x; \ + do { \ + GLenum glError = glGetError(); \ + if(glError != GL_NO_ERROR) { \ + printf("glGetError() = %i (0x%.8x) at line %s : %i\n", glError, glError, __FILE__, __LINE__); \ + assert(0); \ + } \ + } while(0) + +#define EGL_CHECK(x) \ + x; \ + do { \ + EGLint eglError = eglGetError(); \ + if(eglError != EGL_SUCCESS) { \ + printf("eglGetError() = %i (0x%.8x) at line %s : %i\n", eglError, eglError, __FILE__, __LINE__); \ + assert(0); \ + } \ + } while(0) + + +class GlGeometry; + +struct GlShape +{ + float viewWd; + float viewHt; + RenderUpdateFlag updateFlag; + unique_ptr geometry; +}; + + +#endif /* _TVG_GL_COMMON_H_ */ diff --git a/src/lib/gl_engine/tvgGlGeometry.cpp b/src/lib/gl_engine/tvgGlGeometry.cpp new file mode 100644 index 00000000..e89077d7 --- /dev/null +++ b/src/lib/gl_engine/tvgGlGeometry.cpp @@ -0,0 +1,342 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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 "tvgGlGpuBuffer.h" +#include "tvgGlGeometry.h" +#include "tvgGlCommon.h" + +#include + + +uint32_t GlGeometry::getPrimitiveCount() +{ + return mPrimitives.size(); +} + + +bool GlGeometry::decomposeOutline(const Shape &shape) +{ + const PathCommand *cmds = nullptr; + auto cmdCnt = shape.pathCommands(&cmds); + + Point *pts = nullptr; + auto ptsCnt = shape.pathCoords(const_cast(&pts)); + + //No actual shape data + if (cmdCnt == 0 || ptsCnt == 0) + return false; + + GlPrimitive* curPrimitive = nullptr; + for (size_t i = 0; i < cmdCnt; ++i) + { + switch (*(cmds + i)) + { + case PathCommand::Close: + { + if (curPrimitive && curPrimitive->mAAPoints.size() > 0 && + (curPrimitive->mAAPoints[0].orgPt != curPrimitive->mAAPoints.back().orgPt) ) + { + curPrimitive->mAAPoints.push_back(curPrimitive->mAAPoints[0].orgPt); + } + break; + } + case PathCommand::MoveTo: + mPrimitives.push_back(GlPrimitive()); + curPrimitive = &mPrimitives.back(); + __attribute__ ((fallthrough)); + case PathCommand::LineTo: + { + if (curPrimitive) + { + addPoint(*curPrimitive, pts[0]); + } + pts++; + break; + } + case PathCommand::CubicTo: + { + if (curPrimitive) + { + decomposeCubicCurve(*curPrimitive, curPrimitive->mAAPoints.back().orgPt, pts[0], pts[1], pts[2]); + } + pts += 3; + break; + } + } + } + return true; +} + +bool GlGeometry::generateAAPoints(TVG_UNUSED const Shape &shape, float strokeWd, RenderUpdateFlag flag) +{ + for (auto& shapeGeometry : mPrimitives) + { + std::vector normalInfo; + constexpr float blurDir = -1.0f; + float antiAliasWidth = 1.0f; + vector& aaPts = shapeGeometry.mAAPoints; + + const float stroke = (strokeWd > 1) ? strokeWd - antiAliasWidth : strokeWd; + + size_t nPoints = aaPts.size(); + if (nPoints < 2) + { + return false; + } + + normalInfo.resize(nPoints); + + size_t fPoint = 0; + size_t sPoint = 1; + for (size_t i = 0; i < nPoints; ++i) + { + fPoint = i; + sPoint = i + 1; + if (fPoint == nPoints - 1) + sPoint = 0; + + GlPoint normal = getNormal(aaPts[fPoint].orgPt, aaPts[sPoint].orgPt); + + normalInfo[fPoint].normal1 = normal; + normalInfo[sPoint].normal2 = normal; + } + normalInfo[0].normal2 = normalInfo[0].normal1; + normalInfo[nPoints - 1].normal1 = normalInfo[nPoints - 1].normal2; + + for (uint32_t i = 0; i < nPoints; ++i) + { + normalInfo[i].normalF = normalInfo[i].normal1 + normalInfo[i].normal2; + normalInfo[i].normalF.normalize(); + + float angle = dotProduct(normalInfo[i].normal2, normalInfo[i].normalF); + if (angle != 0) + normalInfo[i].normalF = normalInfo[i].normalF / angle; + else + normalInfo[i].normalF = GlPoint(0, 0); + + if (flag & RenderUpdateFlag::Color) + { + aaPts[i].fillOuterBlur = extendEdge(aaPts[i].orgPt, normalInfo[i].normalF, blurDir * stroke); + aaPts[i].fillOuter = extendEdge(aaPts[i].fillOuterBlur, normalInfo[i].normalF, blurDir*antiAliasWidth); + } + if (flag & RenderUpdateFlag::Stroke) + { + aaPts[i].strokeOuterBlur = aaPts[i].orgPt; + aaPts[i].strokeOuter = extendEdge(aaPts[i].strokeOuterBlur, normalInfo[i].normalF, blurDir*antiAliasWidth); + aaPts[i].strokeInner = extendEdge(aaPts[i].strokeOuter, normalInfo[i].normalF, blurDir * stroke); + aaPts[i].strokeInnerBlur = extendEdge(aaPts[i].strokeInner, normalInfo[i].normalF, blurDir*antiAliasWidth); + } + } + } + + return true; +} + +bool GlGeometry::tesselate(TVG_UNUSED const Shape &shape, float viewWd, float viewHt, RenderUpdateFlag flag) +{ + for (auto& shapeGeometry : mPrimitives) + { + constexpr float opaque = 1.0f; + constexpr float transparent = 0.0f; + vector& aaPts = shapeGeometry.mAAPoints; + VertexDataArray& fill = shapeGeometry.mFill; + VertexDataArray& stroke = shapeGeometry.mStroke; + + if (flag & RenderUpdateFlag::Color) + { + uint32_t i = 0; + for (size_t pt = 0; pt < aaPts.size(); ++pt) + { + addGeometryPoint(fill, aaPts[pt].fillOuter, viewWd, viewHt, opaque); + if (i > 1) + { + addTriangleFanIndices(i, fill.indices); + } + ++i; + } + for (size_t pt = 1; pt < aaPts.size(); ++pt) + { + addGeometryPoint(fill, aaPts[pt - 1].fillOuterBlur, viewWd, viewHt, transparent); + addGeometryPoint(fill, aaPts[pt - 1].fillOuter, viewWd, viewHt, opaque); + addGeometryPoint(fill, aaPts[pt].fillOuterBlur, viewWd, viewHt, transparent); + addGeometryPoint(fill, aaPts[pt].fillOuter, viewWd, viewHt, opaque); + addQuadIndices(i, fill.indices); + } + } + if (flag & RenderUpdateFlag::Stroke) + { + uint32_t i = 0; + for (size_t pt = 1; pt < aaPts.size(); ++pt) + { + addGeometryPoint(stroke, aaPts[pt - 1].strokeOuter, viewWd, viewHt, opaque); + addGeometryPoint(stroke, aaPts[pt - 1].strokeInner, viewWd, viewHt, opaque); + addGeometryPoint(stroke, aaPts[pt].strokeOuter, viewWd, viewHt, opaque); + addGeometryPoint(stroke, aaPts[pt].strokeInner, viewWd, viewHt, opaque); + addQuadIndices(i, stroke.indices); + } + for (size_t pt = 1; pt < aaPts.size(); ++pt) + { + addGeometryPoint(stroke, aaPts[pt - 1].strokeOuterBlur, viewWd, viewHt, transparent); + addGeometryPoint(stroke, aaPts[pt - 1].strokeOuter, viewWd, viewHt, opaque); + addGeometryPoint(stroke, aaPts[pt].strokeOuterBlur, viewWd, viewHt, transparent); + addGeometryPoint(stroke, aaPts[pt].strokeOuter, viewWd, viewHt, opaque); + addQuadIndices(i, stroke.indices); + } + for (size_t pt = 1; pt < aaPts.size(); ++pt) + { + addGeometryPoint(stroke, aaPts[pt - 1].strokeInner, viewWd, viewHt, opaque); + addGeometryPoint(stroke, aaPts[pt - 1].strokeInnerBlur, viewWd, viewHt, transparent); + addGeometryPoint(stroke, aaPts[pt].strokeInner, viewWd, viewHt, opaque); + addGeometryPoint(stroke, aaPts[pt].strokeInnerBlur, viewWd, viewHt, transparent); + addQuadIndices(i, stroke.indices); + } + } + aaPts.clear(); + } + return true; +} + + +void GlGeometry::disableVertex(uint32_t location) +{ + GL_CHECK(glDisableVertexAttribArray(location)); + mGpuBuffer->unbind(GlGpuBuffer::Target::ARRAY_BUFFER); +} + + +void GlGeometry::draw(const uint32_t location, const uint32_t primitiveIndex, RenderUpdateFlag flag) +{ + if (primitiveIndex >= mPrimitives.size()) + { + return; + } + VertexDataArray& geometry = (flag == RenderUpdateFlag::Color) ? mPrimitives[primitiveIndex].mFill : mPrimitives[primitiveIndex].mStroke; + + updateBuffer(location, geometry); + GL_CHECK(glDrawElements(GL_TRIANGLES, geometry.indices.size(), GL_UNSIGNED_INT, geometry.indices.data())); +} + + +void GlGeometry::updateBuffer(uint32_t location, const VertexDataArray& vertexArray) +{ + if (mGpuBuffer.get() == nullptr) + { + mGpuBuffer = make_unique(); + } + mGpuBuffer->updateBufferData(GlGpuBuffer::Target::ARRAY_BUFFER, vertexArray.vertices.size() * sizeof(VertexData), vertexArray.vertices.data()); + GL_CHECK(glVertexAttribPointer(location, 3, GL_FLOAT, GL_FALSE, sizeof(VertexData), 0)); + GL_CHECK(glEnableVertexAttribArray(location)); +} + + +GlPoint GlGeometry::normalizePoint(const GlPoint &pt, float viewWd, float viewHt) +{ + GlPoint p; + p.x = (pt.x * 2.0f / viewWd) - 1.0f; + p.y = -1.0f * ((pt.y * 2.0f / viewHt) - 1.0f); + return p; +} + +void GlGeometry::addGeometryPoint(VertexDataArray &geometry, const GlPoint &pt, float viewWd, float viewHt, float opacity) +{ + VertexData tv = { normalizePoint(pt, viewWd, viewHt), opacity}; + geometry.vertices.push_back(tv); +} + +GlPoint GlGeometry::getNormal(const GlPoint &p1, const GlPoint &p2) +{ + GlPoint normal = p1 - p2; + normal.normalize(); + return GlPoint(-normal.y, normal.x); +} + +float GlGeometry::dotProduct(const GlPoint &p1, const GlPoint &p2) +{ + return (p1.x * p2.x + p1.y * p2.y); +} + +GlPoint GlGeometry::extendEdge(const GlPoint &pt, const GlPoint &normal, float scalar) +{ + GlPoint tmp = (normal * scalar); + return (pt + tmp); +} + +void GlGeometry::addPoint(GlPrimitive& primitve, const GlPoint &pt) +{ + primitve.mAAPoints.push_back(GlPoint(pt.x, pt.y)); +} + +void GlGeometry::addTriangleFanIndices(uint32_t &curPt, std::vector &indices) +{ + indices.push_back(0); + indices.push_back(curPt - 1); + indices.push_back(curPt); +} + +void GlGeometry::addQuadIndices(uint32_t &curPt, std::vector &indices) +{ + indices.push_back(curPt); + indices.push_back(curPt + 1); + indices.push_back(curPt + 2); + indices.push_back(curPt + 1); + indices.push_back(curPt + 3); + indices.push_back(curPt + 2); + curPt += 4; +} + +bool GlGeometry::isBezierFlat(const GlPoint &p1, const GlPoint &c1, const GlPoint &c2, const GlPoint &p2) +{ + GlPoint diff1 = (c1 * 3.0f) - (p1 * 2.0f) - p2; + GlPoint diff2 = (c2 * 3.0f) - (p2 * 2.0f) - p1; + + diff1.mod(); + diff2.mod(); + if (diff1.x < diff2.x) + diff1.x = diff2.x; + if (diff1.y < diff2.y) + diff1.y = diff2.y; + + if (diff1.x + diff1.y <= 0.5f) + return true; + return false; +} + +void GlGeometry::decomposeCubicCurve(GlPrimitive& primitve, const GlPoint &pt1, const GlPoint &cpt1, const GlPoint &cpt2, const GlPoint &pt2) +{ + if (isBezierFlat(pt1, cpt1, cpt2, pt2)) + { + addPoint(primitve, pt2); + return; + } + GlPoint p12 = (pt1 + cpt1) * 0.5f; + GlPoint p23 = (cpt1 + cpt2) * 0.5f; + GlPoint p34 = (cpt2 + pt2) * 0.5f; + + GlPoint p123 = (p12 + p23) * 0.5f; + GlPoint p234 = (p23 + p34) * 0.5f; + + GlPoint p1234 = (p123 + p234) * 0.5f; + + decomposeCubicCurve(primitve, pt1, p12, p123, p1234); + decomposeCubicCurve(primitve, p1234, p234, p34, pt2); +} + diff --git a/src/lib/gl_engine/tvgGlGeometry.h b/src/lib/gl_engine/tvgGlGeometry.h new file mode 100644 index 00000000..e5f1b033 --- /dev/null +++ b/src/lib/gl_engine/tvgGlGeometry.h @@ -0,0 +1,210 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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_GL_GEOMETRY_H_ +#define _TVG_GL_GEOMETRY_H_ + +#include "tvgGlCommon.h" + +class GlPoint +{ +public: + float x = 0.0f; + float y = 0.0f; + + GlPoint() = default; + + GlPoint(float pX, float pY):x(pX), y(pY) + {} + + GlPoint(const Point& rhs):GlPoint(rhs.x, rhs.y) + {} + + GlPoint(const GlPoint& rhs) = default; + GlPoint(GlPoint&& rhs) = default; + + GlPoint& operator= (const GlPoint& rhs) = default; + GlPoint& operator= (GlPoint&& rhs) = default; + + GlPoint& operator= (const Point& rhs) + { + x = rhs.x; + y = rhs.y; + return *this; + } + + bool operator== (const GlPoint& rhs) + { + if (&rhs == this) + return true; + if (rhs.x == this->x && rhs.y == this->y) + return true; + return false; + } + + bool operator!= (const GlPoint& rhs) + { + if (&rhs == this) + return true; + if (rhs.x != this->x || rhs.y != this->y) + return true; + return false; + } + + GlPoint operator+ (const GlPoint& rhs) const + { + return GlPoint(x + rhs.x, y + rhs.y); + } + + GlPoint operator+ (const float c) const + { + return GlPoint(x + c, y + c); + } + + GlPoint operator- (const GlPoint& rhs) const + { + return GlPoint(x - rhs.x, y - rhs.y); + } + + GlPoint operator- (const float c) const + { + return GlPoint(x - c, y - c); + } + + GlPoint operator* (const GlPoint& rhs) const + { + return GlPoint(x * rhs.x, y * rhs.y); + } + + GlPoint operator* (const float c) const + { + return GlPoint(x * c, y * c); + } + + GlPoint operator/ (const GlPoint& rhs) const + { + return GlPoint(x / rhs.x, y / rhs.y); + } + + GlPoint operator/ (const float c) const + { + return GlPoint(x / c, y / c); + } + + void mod() + { + x = fabsf(x); + y = fabsf(y); + } + + void normalize() + { + auto length = sqrtf( (x * x) + (y * y) ); + if (length != 0.0f) + { + const auto inverseLen = 1.0f / length; + x *= inverseLen; + y *= inverseLen; + } + } +}; + +struct SmoothPoint +{ + GlPoint orgPt; + GlPoint fillOuterBlur; + GlPoint fillOuter; + GlPoint strokeOuterBlur; + GlPoint strokeOuter; + GlPoint strokeInnerBlur; + GlPoint strokeInner; + + SmoothPoint(GlPoint pt) + :orgPt(pt), + fillOuterBlur(pt), + fillOuter(pt), + strokeOuterBlur(pt), + strokeOuter(pt), + strokeInnerBlur(pt), + strokeInner(pt) + { + } +}; + +struct PointNormals +{ + GlPoint normal1; + GlPoint normal2; + GlPoint normalF; +}; + +struct VertexData +{ + GlPoint point; + float opacity = 0.0f; +}; + +struct VertexDataArray +{ + vector vertices; + vector indices; +}; + +struct GlPrimitive +{ + vector mAAPoints; + VertexDataArray mFill; + VertexDataArray mStroke; +}; + +class GlGpuBuffer; + +class GlGeometry +{ +public: + + uint32_t getPrimitiveCount(); + bool decomposeOutline(const Shape& shape); + bool generateAAPoints(const Shape& shape, float strokeWd, RenderUpdateFlag flag); + bool tesselate(const Shape &shape, float viewWd, float viewHt, RenderUpdateFlag flag); + void disableVertex(uint32_t location); + void draw(const uint32_t location, const uint32_t primitiveIndex, RenderUpdateFlag flag); + +private: + GlPoint normalizePoint(const GlPoint &pt, float viewWd, float viewHt); + void addGeometryPoint(VertexDataArray &geometry, const GlPoint &pt, float viewWd, float viewHt, float opacity); + GlPoint getNormal(const GlPoint &p1, const GlPoint &p2); + float dotProduct(const GlPoint &p1, const GlPoint &p2); + GlPoint extendEdge(const GlPoint &pt, const GlPoint &normal, float scalar); + + void addPoint(GlPrimitive& primitve, const GlPoint &pt); + void addTriangleFanIndices(uint32_t &curPt, vector &indices); + void addQuadIndices(uint32_t &curPt, vector &indices); + bool isBezierFlat(const GlPoint &p1, const GlPoint &c1, const GlPoint &c2, const GlPoint &p2); + void decomposeCubicCurve(GlPrimitive& primitve, const GlPoint &pt1, const GlPoint &cpt1, const GlPoint &cpt2, const GlPoint &pt2); + void updateBuffer(const uint32_t location, const VertexDataArray& vertexArray); + + unique_ptr mGpuBuffer; + vector mPrimitives; +}; + +#endif /* _TVG_GL_GEOMETRY_H_ */ diff --git a/src/lib/gl_engine/tvgGlGpuBuffer.cpp b/src/lib/gl_engine/tvgGlGpuBuffer.cpp new file mode 100644 index 00000000..f4024252 --- /dev/null +++ b/src/lib/gl_engine/tvgGlGpuBuffer.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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 "tvgGlCommon.h" +#include "tvgGlGpuBuffer.h" + +#include + +GlGpuBuffer::GlGpuBuffer() +{ + GL_CHECK(glGenBuffers(1, &mGlBufferId)); + assert(mGlBufferId != GL_INVALID_VALUE); +} + + +GlGpuBuffer::~GlGpuBuffer() +{ + if (mGlBufferId) + { + GL_CHECK(glDeleteBuffers(1, &mGlBufferId)); + } +} + + +void GlGpuBuffer::updateBufferData(Target target, uint32_t size, const void* data) +{ + GL_CHECK(glBindBuffer(static_cast(target), mGlBufferId)); + GL_CHECK(glBufferData(static_cast(target), size, data, GL_STATIC_DRAW)); +} + + +void GlGpuBuffer::unbind(Target target) +{ + GL_CHECK(glBindBuffer(static_cast(target), 0)); +} \ No newline at end of file diff --git a/src/lib/gl_engine/tvgGlGpuBuffer.h b/src/lib/gl_engine/tvgGlGpuBuffer.h new file mode 100644 index 00000000..e3ed97cf --- /dev/null +++ b/src/lib/gl_engine/tvgGlGpuBuffer.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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_GL_GPU_BUFFER_H_ +#define _TVG_GL_GPU_BUFFER_H_ + +#include +#include + +class GlGpuBuffer +{ +public: + enum class Target + { + ARRAY_BUFFER = GL_ARRAY_BUFFER, + ELEMENT_ARRAY_BUFFER = GL_ARRAY_BUFFER + }; + + GlGpuBuffer(); + ~GlGpuBuffer(); + void updateBufferData(Target target, uint32_t size, const void* data); + void unbind(Target target); +private: + uint32_t mGlBufferId = 0; + +}; + +#endif /* _TVG_GL_GPU_BUFFER_H_ */ + diff --git a/src/lib/gl_engine/tvgGlProgram.cpp b/src/lib/gl_engine/tvgGlProgram.cpp new file mode 100644 index 00000000..9d03849b --- /dev/null +++ b/src/lib/gl_engine/tvgGlProgram.cpp @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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 "tvgGlCommon.h" +#include "tvgGlProgram.h" + +#include + + +static std::vector gStdAttributes = { + "aLocation" +}; + +static std::vector gStdUniforms = { + "uColor" +}; + +uint32_t GlProgram::mCurrentProgram = 0; +map GlProgram::mAttributeBuffer; +map GlProgram::mUniformBuffer; + + +unique_ptr GlProgram::gen(std::shared_ptr shader) +{ + return make_unique(shader); +} + + +GlProgram::GlProgram(std::shared_ptr shader) +{ + linkProgram(shader); + load(); + + for (auto name : gStdAttributes) + { + getAttributeLocation(name.c_str()); + } + for (auto name : gStdUniforms) + { + getUniformLocation(name.c_str()); + } + +} + + +GlProgram::~GlProgram() +{ + if (mCurrentProgram == mProgramObj) + { + unload(); + } + glDeleteProgram(mProgramObj); +} + + +void GlProgram::load() +{ + if (mCurrentProgram == mProgramObj) + { + return; + } + + mCurrentProgram = mProgramObj; + GL_CHECK(glUseProgram(mProgramObj)); + +} + + +void GlProgram::unload() +{ + mCurrentProgram = 0; +} + + +int32_t GlProgram::getAttributeLocation(const char* name) +{ + if (mAttributeBuffer.find(name) != mAttributeBuffer.end()) + { + return mAttributeBuffer[name]; + } + GL_CHECK(int32_t location = glGetAttribLocation(mCurrentProgram, name)); + if (location != -1) + { + mAttributeBuffer[name] = location; + } + return location; +} + + +int32_t GlProgram::getUniformLocation(const char* name) +{ + if (mUniformBuffer.find(name) != mUniformBuffer.end()) + { + return mUniformBuffer[name]; + } + GL_CHECK(int32_t location = glGetUniformLocation(mCurrentProgram, name)); + if (location != -1) + { + mUniformBuffer[name] = location; + } + return location; + +} + + +void GlProgram::setUniformValue(int32_t location, float r, float g, float b, float a) +{ + glUniform4f(location, r, g, b, a); +} + + +void GlProgram::linkProgram(std::shared_ptr shader) +{ + GLint linked; + + // Create the program object + uint32_t progObj = glCreateProgram(); + assert(progObj); + + glAttachShader(progObj, shader->getVertexShader()); + glAttachShader(progObj, shader->getFragmentShader()); + + // Link the program + glLinkProgram(progObj); + + // Check the link status + glGetProgramiv(progObj, GL_LINK_STATUS, &linked); + + if (!linked) + { + GLint infoLen = 0; + glGetProgramiv(progObj, GL_INFO_LOG_LENGTH, &infoLen); + if (infoLen > 0) + { + char* infoLog = new char[infoLen]; + glGetProgramInfoLog(progObj, infoLen, NULL, infoLog); + std::cout << "Error linking shader: " << infoLog << std::endl; + delete[] infoLog; + + } + glDeleteProgram(progObj); + progObj = 0; + assert(0); + } + mProgramObj = progObj; +} + diff --git a/src/lib/gl_engine/tvgGlProgram.h b/src/lib/gl_engine/tvgGlProgram.h new file mode 100644 index 00000000..ccdb258c --- /dev/null +++ b/src/lib/gl_engine/tvgGlProgram.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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_GL_PROGRAM_H_ +#define _TVG_GL_PROGRAM_H_ + +#include "tvgGlShader.h" + +#include +#include + +class GlProgram +{ +public: + static std::unique_ptr gen(std::shared_ptr shader); + GlProgram(std::shared_ptr shader); + ~GlProgram(); + + void load(); + void unload(); + int32_t getAttributeLocation(const char* name); + int32_t getUniformLocation(const char* name); + void setUniformValue(int32_t location, float r, float g, float b, float a); + +private: + + void linkProgram(std::shared_ptr shader); + uint32_t mProgramObj; + static uint32_t mCurrentProgram; + + static std::map mAttributeBuffer; + static std::map mUniformBuffer; +}; + +#endif /* _TVG_GL_PROGRAM_H_ */ diff --git a/src/lib/gl_engine/tvgGlRenderer.cpp b/src/lib/gl_engine/tvgGlRenderer.cpp new file mode 100644 index 00000000..f6b31d41 --- /dev/null +++ b/src/lib/gl_engine/tvgGlRenderer.cpp @@ -0,0 +1,215 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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 "tvgGlShaderSrc.h" +#include "tvgGlGpuBuffer.h" +#include "tvgGlGeometry.h" +#include "tvgGlCommon.h" +#include "tvgGlRenderer.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +static RenderInitializer renderInit; + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +bool GlRenderer::clear() +{ + //TODO: (Request) to clear target + // Will be adding glClearColor for input buffer + return true; +} + + +bool GlRenderer::target(TVG_UNUSED uint32_t* buffer, uint32_t stride, uint32_t w, uint32_t h) +{ + assert(w > 0 && h > 0); + + surface.stride = stride; + surface.w = w; + surface.h = h; + + return true; +} + + +bool GlRenderer::flush() +{ + GL_CHECK(glFinish()); + mColorProgram->unload(); + + return true; +} + + +bool GlRenderer::preRender() +{ + // Blend function for pre multiplied alpha + GL_CHECK(glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA)); + GL_CHECK(glEnable(GL_BLEND)); + return true; +} + + +bool GlRenderer::postRender() +{ + //TODO: called just after render() + + return true; +} + + +bool GlRenderer::render(const Shape& shape, void *data) +{ + GlShape* sdata = static_cast(data); + if (!sdata) return false; + + uint8_t r, g, b, a; + size_t flags = static_cast(sdata->updateFlag); + + GL_CHECK(glViewport(0, 0, sdata->viewWd, sdata->viewHt)); + + uint32_t geometryCnt = sdata->geometry->getPrimitiveCount(); + for (uint32_t i = 0; i < geometryCnt; ++i) + { + mColorProgram->load(); + if (flags & RenderUpdateFlag::Color) + { + shape.fill(&r, &g, &b, &a); + drawPrimitive(*(sdata->geometry), (float)r / 255.0f, (float)g / 255.0f, (float)b / 255.0f, (float)a / 255.0f, i, RenderUpdateFlag::Color); + } + if (flags & RenderUpdateFlag::Stroke) + { + shape.strokeColor(&r, &g, &b, &a); + drawPrimitive(*(sdata->geometry), (float)r / 255.0f, (float)g / 255.0f, (float)b / 255.0f, (float)a / 255.0f, i, RenderUpdateFlag::Stroke); + } + } + + return true; +} + + +bool GlRenderer::dispose(TVG_UNUSED const Shape& shape, void *data) +{ + GlShape* sdata = static_cast(data); + if (!sdata) return false; + + delete sdata; + return true; +} + + +void* GlRenderer::prepare(const Shape& shape, void* data, TVG_UNUSED const RenderTransform* transform, RenderUpdateFlag flags) +{ + //prepare shape data + GlShape* sdata = static_cast(data); + if (!sdata) { + sdata = new GlShape; + if (!sdata) return nullptr; + } + + sdata->viewWd = static_cast(surface.w); + sdata->viewHt = static_cast(surface.h); + sdata->updateFlag = flags; + + if (sdata->updateFlag == RenderUpdateFlag::None) return sdata; + + initShaders(); + + sdata->geometry = make_unique(); + + //invisible? + uint8_t alphaF, alphaS; + shape.fill(nullptr, nullptr, nullptr, &alphaF); + shape.strokeColor(nullptr, nullptr, nullptr, &alphaS); + auto strokeWd = shape.strokeWidth(); + + if (alphaF == 0 && alphaS == 0) return sdata; + + if (sdata->updateFlag & (RenderUpdateFlag::Color | RenderUpdateFlag::Stroke) ) + { + if (!sdata->geometry->decomposeOutline(shape)) return sdata; + if (!sdata->geometry->generateAAPoints(shape, static_cast(strokeWd), sdata->updateFlag)) return sdata; + if (!sdata->geometry->tesselate(shape, sdata->viewWd, sdata->viewHt, sdata->updateFlag)) return sdata; + } + return sdata; +} + + +int GlRenderer::init() +{ + return RenderInitializer::init(renderInit, new GlRenderer); +} + + +int GlRenderer::term() +{ + if (inst()->mColorProgram.get()) + { + inst()->mColorProgram.reset(nullptr); + } + return RenderInitializer::term(renderInit); +} + + +uint32_t GlRenderer::unref() +{ + return RenderInitializer::unref(renderInit); +} + + +uint32_t GlRenderer::ref() +{ + return RenderInitializer::ref(renderInit); +} + + +GlRenderer* GlRenderer::inst() +{ + //We know renderer type, avoid dynamic_cast for performance. + return static_cast(RenderInitializer::inst(renderInit)); +} + + +void GlRenderer::initShaders() +{ + if (!mColorProgram.get()) + { + shared_ptr shader = GlShader::gen(COLOR_VERT_SHADER, COLOR_FRAG_SHADER); + mColorProgram = GlProgram::gen(shader); + } + mColorProgram->load(); + mColorUniformLoc = mColorProgram->getUniformLocation("uColor"); + mVertexAttrLoc = mColorProgram->getAttributeLocation("aLocation"); +} + + +void GlRenderer::drawPrimitive(GlGeometry& geometry, float r, float g, float b, float a, uint32_t primitiveIndex, RenderUpdateFlag flag) +{ + mColorProgram->setUniformValue(mColorUniformLoc, r, g, b, a); + geometry.draw(mVertexAttrLoc, primitiveIndex, flag); + geometry.disableVertex(mVertexAttrLoc); + +} \ No newline at end of file diff --git a/src/lib/gl_engine/tvgGlRenderer.h b/src/lib/gl_engine/tvgGlRenderer.h new file mode 100644 index 00000000..9a86bae1 --- /dev/null +++ b/src/lib/gl_engine/tvgGlRenderer.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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_GL_RENDERER_H_ +#define _TVG_GL_RENDERER_H_ + +#include "tvgGlCommon.h" +#include "tvgGlProgram.h" + + +class GlRenderer : public RenderMethod +{ +public: + Surface surface = {nullptr, 0, 0, 0}; + + void* prepare(const Shape& shape, void* data, const RenderTransform* transform, RenderUpdateFlag flags) override; + bool dispose(const Shape& shape, void *data) override; + bool preRender() override; + bool render(const Shape& shape, void *data) override; + bool postRender() override; + bool target(uint32_t* buffer, uint32_t stride, uint32_t w, uint32_t h); + bool flush() override; + bool clear() override; + uint32_t ref() override; + uint32_t unref() override; + + static GlRenderer* inst(); + static int init(); + static int term(); + +private: + GlRenderer(){}; + ~GlRenderer(){}; + + void initShaders(); + void drawPrimitive(GlGeometry& geometry, float r, float g, float b, float a, uint32_t primitiveIndex, RenderUpdateFlag flag); + + unique_ptr mColorProgram = nullptr; + int32_t mColorUniformLoc = 0; + uint32_t mVertexAttrLoc = 0; +}; + +#endif /* _TVG_GL_RENDERER_H_ */ diff --git a/src/lib/gl_engine/tvgGlShader.cpp b/src/lib/gl_engine/tvgGlShader.cpp new file mode 100644 index 00000000..62c69ed7 --- /dev/null +++ b/src/lib/gl_engine/tvgGlShader.cpp @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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 "tvgGlCommon.h" +#include "tvgGlShader.h" + +#include + + +shared_ptr GlShader::gen(const char * vertSrc, const char * fragSrc) +{ + shared_ptr shader = make_shared(); + shader->createShader(vertSrc, fragSrc); + return shader; +} + + +GlShader::~GlShader() +{ + glDeleteShader(mVtShader); + glDeleteShader(mFrShader); +} + +uint32_t GlShader::getVertexShader() +{ + return mVtShader; +} + + +uint32_t GlShader::getFragmentShader() +{ + return mFrShader; +} + + +void GlShader::createShader(const char* vertSrc, const char* fragSrc) +{ + mVtShader = complileShader(GL_VERTEX_SHADER, const_cast(vertSrc)); + mFrShader = complileShader(GL_FRAGMENT_SHADER, const_cast(fragSrc)); +} + + +uint32_t GlShader::complileShader(uint32_t type, char* shaderSrc) +{ + GLuint shader; + GLint compiled; + + // Create the shader object + shader = glCreateShader(type); + assert(shader); + + // Load the shader source + glShaderSource(shader, 1, &shaderSrc, NULL); + + // Compile the shader + glCompileShader(shader); + + // Check the compile status + glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); + + if (!compiled) + { + GLint infoLen = 0; + + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen); + + if (infoLen > 0) + { + char* infoLog = new char[infoLen]; + glGetShaderInfoLog(shader, infoLen, NULL, infoLog); + std::cout << "Error compiling shader: " << infoLog << std::endl; + delete[] infoLog; + } + glDeleteShader(shader); + assert(0); + } + + return shader; +} + diff --git a/src/lib/gl_engine/tvgGlShader.h b/src/lib/gl_engine/tvgGlShader.h new file mode 100644 index 00000000..75c8f377 --- /dev/null +++ b/src/lib/gl_engine/tvgGlShader.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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_GL_SHADER_H_ +#define _TVG_GL_SHADER_H_ + +#include + +class GlShader +{ +public: + static std::shared_ptr gen(const char * vertSrc, const char * fragSrc); + ~GlShader(); + + uint32_t getVertexShader(); + uint32_t getFragmentShader(); + +private: + void createShader(const char* vertSrc, const char* fragSrc); + uint32_t complileShader(uint32_t type, char* shaderSrc); + + uint32_t mVtShader; + uint32_t mFrShader; +}; + +#endif /* _TVG_GL_SHADER_H_ */ diff --git a/src/lib/gl_engine/tvgGlShaderSrc.cpp b/src/lib/gl_engine/tvgGlShaderSrc.cpp new file mode 100644 index 00000000..135ef435 --- /dev/null +++ b/src/lib/gl_engine/tvgGlShaderSrc.cpp @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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 "tvgGlShaderSrc.h" + +const char* COLOR_VERT_SHADER = +"attribute highp vec4 aLocation; \n" +"uniform highp vec4 uColor; \n" +"varying highp vec4 vcolor; \n" +"varying highp float vOpacity; \n" +"void main() \n" +"{ \n" +" gl_Position = vec4(aLocation.xy, 0.0, 1.0); \n" +" vcolor = uColor; \n" +" vOpacity = aLocation.z; \n" +"} \n"; + +const char* COLOR_FRAG_SHADER = +"varying highp vec4 vcolor; \n" +"varying highp float vOpacity; \n" +"void main() \n" +"{ \n" +" gl_FragColor = vec4(vcolor.xyz, vcolor.w*vOpacity); \n" +"} \n"; + diff --git a/src/lib/gl_engine/tvgGlShaderSrc.h b/src/lib/gl_engine/tvgGlShaderSrc.h new file mode 100644 index 00000000..22993547 --- /dev/null +++ b/src/lib/gl_engine/tvgGlShaderSrc.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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_GL_SHADERSRC_H_ +#define _TVG_GL_SHADERSRC_H_ + +extern const char* COLOR_VERT_SHADER; +extern const char* COLOR_FRAG_SHADER; + +#endif /* _TVG_GL_SHADERSRC_H_ */ diff --git a/src/lib/meson.build b/src/lib/meson.build new file mode 100644 index 00000000..bc0d9592 --- /dev/null +++ b/src/lib/meson.build @@ -0,0 +1,46 @@ +engine_dep = [] + +if get_option('engines').contains('sw') == true + subdir('sw_engine') + message('Enable SW Raster Engine') +endif + +if get_option('engines').contains('gl') == true + subdir('gl_engine') + message('Enable GL Raster Engine') +endif + +source_file = [ + 'tvgCanvasImpl.h', + 'tvgCommon.h', + 'tvgBezier.h', + 'tvgLoader.h', + 'tvgLoaderMgr.h', + 'tvgPictureImpl.h', + 'tvgRender.h', + 'tvgSceneImpl.h', + 'tvgShapePath.h', + 'tvgShapeImpl.h', + 'tvgTaskScheduler.h', + 'tvgBezier.cpp', + 'tvgCanvas.cpp', + 'tvgFill.cpp', + 'tvgGlCanvas.cpp', + 'tvgInitializer.cpp', + 'tvgLinearGradient.cpp', + 'tvgLoaderMgr.cpp', + 'tvgPaint.cpp', + 'tvgPicture.cpp', + 'tvgRadialGradient.cpp', + 'tvgRender.cpp', + 'tvgScene.cpp', + 'tvgShape.cpp', + 'tvgSwCanvas.cpp', + 'tvgTaskScheduler.cpp', +] + +common_dep = declare_dependency( + dependencies : engine_dep, + include_directories : include_directories('.'), + sources : source_file +) diff --git a/src/lib/sw_engine/meson.build b/src/lib/sw_engine/meson.build new file mode 100644 index 00000000..66b1e0d1 --- /dev/null +++ b/src/lib/sw_engine/meson.build @@ -0,0 +1,16 @@ +source_file = [ + 'tvgSwCommon.h', + 'tvgSwFill.cpp', + 'tvgSwMath.cpp', + 'tvgSwRenderer.h', + 'tvgSwRaster.cpp', + 'tvgSwRenderer.cpp', + 'tvgSwRle.cpp', + 'tvgSwShape.cpp', + 'tvgSwStroke.cpp', +] + +engine_dep += [declare_dependency( + include_directories : include_directories('.'), + sources : source_file +)] diff --git a/src/lib/sw_engine/tvgSwCommon.h b/src/lib/sw_engine/tvgSwCommon.h new file mode 100644 index 00000000..a600cb9d --- /dev/null +++ b/src/lib/sw_engine/tvgSwCommon.h @@ -0,0 +1,323 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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_SW_COMMON_H_ +#define _TVG_SW_COMMON_H_ + +#include "tvgCommon.h" + +#ifdef THORVG_AVX_VECTOR_SUPPORT + #include +#endif + +#if 0 +#include +static double timeStamp() +{ + struct timeval tv; + gettimeofday(&tv, NULL); + return (tv.tv_sec + tv.tv_usec / 1000000.0); +} +#endif + +#define SW_CURVE_TYPE_POINT 0 +#define SW_CURVE_TYPE_CUBIC 1 +#define SW_OUTLINE_FILL_WINDING 0 +#define SW_OUTLINE_FILL_EVEN_ODD 1 +#define SW_ANGLE_PI (180L << 16) +#define SW_ANGLE_2PI (SW_ANGLE_PI << 1) +#define SW_ANGLE_PI2 (SW_ANGLE_PI >> 1) +#define SW_ANGLE_PI4 (SW_ANGLE_PI >> 2) + +using SwCoord = signed long; +using SwFixed = signed long long; + +struct SwPoint +{ + SwCoord x, y; + + SwPoint& operator+=(const SwPoint& rhs) { + x += rhs.x; + y += rhs.y; + return *this; + } + + SwPoint operator+(const SwPoint& rhs) const { + return {x + rhs.x, y + rhs.y}; + } + + SwPoint operator-(const SwPoint& rhs) const { + return {x - rhs.x, y - rhs.y}; + } + + bool operator==(const SwPoint& rhs ) const { + return (x == rhs.x && y == rhs.y); + } + + bool operator!=(const SwPoint& rhs) const { + return (x != rhs.x || y != rhs.y); + } + + bool zero() + { + if (x == 0 && y == 0) return true; + else return false; + } + + bool small() + { + //2 is epsilon... + if (abs(x) < 2 && abs(y) < 2) return true; + else return false; + } + +}; + +struct SwSize +{ + SwCoord w, h; +}; + +struct SwOutline +{ + uint32_t* cntrs; //the contour end points + uint32_t cntrsCnt; //number of contours in glyph + uint32_t reservedCntrsCnt; + SwPoint* pts; //the outline's points + uint32_t ptsCnt; //number of points in the glyph + uint32_t reservedPtsCnt; + uint8_t* types; //curve type + uint8_t fillMode; //outline fill mode + bool opened; //opened path? +}; + +struct SwSpan +{ + int16_t x, y; + uint16_t len; + uint8_t coverage; +}; + +struct SwRleData +{ + SwSpan *spans; + uint32_t alloc; + uint32_t size; +}; + +struct SwBBox +{ + SwPoint min, max; +}; + +struct SwStrokeBorder +{ + uint32_t ptsCnt; + uint32_t maxPts; + SwPoint* pts; + uint8_t* tags; + int32_t start; //index of current sub-path start point + bool movable; //true: for ends of lineto borders + bool valid; +}; + +struct SwStroke +{ + SwFixed angleIn; + SwFixed angleOut; + SwPoint center; + SwFixed lineLength; + SwFixed subPathAngle; + SwPoint ptStartSubPath; + SwFixed subPathLineLength; + SwFixed width; + + StrokeCap cap; + StrokeJoin join; + StrokeJoin joinSaved; + + SwStrokeBorder borders[2]; + + float sx, sy; + + bool firstPt; + bool openSubPath; + bool handleWideStrokes; +}; + +struct SwDashStroke +{ + SwOutline* outline; + int32_t curLen; + int32_t curIdx; + Point ptStart; + Point ptCur; + float* pattern; + uint32_t cnt; + bool curOpGap; +}; + +struct SwFill +{ + struct SwLinear { + float dx, dy; + float len; + float offset; + }; + + struct SwRadial { + float cx, cy; + float a; + float inv2a; + }; + + union { + SwLinear linear; + SwRadial radial; + }; + + uint32_t* ctable; + FillSpread spread; + float sx, sy; + + bool translucent; +}; + +struct SwShape +{ + SwOutline* outline; + SwStroke* stroke; + SwFill* fill; + SwRleData* rle; + SwRleData* strokeRle; + SwBBox bbox; + + bool rect; //Fast Track: Othogonal rectangle? +}; + +struct SwCompositor +{ + uint32_t (*join)(uint8_t r, uint8_t g, uint8_t b, uint8_t a); + uint32_t (*alpha)(uint32_t rgba); +}; + +struct SwSurface : Surface +{ + SwCompositor comp; +}; + +static inline SwCoord TO_SWCOORD(float val) +{ + return SwCoord(val * 64); +} + +static inline uint32_t ALPHA_BLEND(uint32_t c, uint32_t a) +{ + return (((((c >> 8) & 0x00ff00ff) * a) & 0xff00ff00) + + ((((c & 0x00ff00ff) * a) >> 8) & 0x00ff00ff)); +} + +static inline uint32_t COLOR_INTERPOLATE(uint32_t c1, uint32_t a1, uint32_t c2, uint32_t a2) +{ + auto t = (((c1 & 0xff00ff) * a1 + (c2 & 0xff00ff) * a2) >> 8) & 0xff00ff; + c1 = (((c1 >> 8) & 0xff00ff) * a1 + ((c2 >> 8) & 0xff00ff) * a2) & 0xff00ff00; + return (c1 |= t); +} + +static inline uint8_t ALPHA_MULTIPLY(uint32_t c, uint32_t a) +{ + return (c * a) >> 8; +} + +int64_t mathMultiply(int64_t a, int64_t b); +int64_t mathDivide(int64_t a, int64_t b); +int64_t mathMulDiv(int64_t a, int64_t b, int64_t c); +void mathRotate(SwPoint& pt, SwFixed angle); +SwFixed mathTan(SwFixed angle); +SwFixed mathAtan(const SwPoint& pt); +SwFixed mathCos(SwFixed angle); +SwFixed mathSin(SwFixed angle); +void mathSplitCubic(SwPoint* base); +SwFixed mathDiff(SwFixed angle1, SwFixed angle2); +SwFixed mathLength(SwPoint& pt); +bool mathSmallCubic(SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, SwFixed& angleOut); +SwFixed mathMean(SwFixed angle1, SwFixed angle2); + +void shapeReset(SwShape* shape); +bool shapeGenOutline(SwShape* shape, const Shape* sdata, const Matrix* transform); +bool shapePrepare(SwShape* shape, const Shape* sdata, const SwSize& clip, const Matrix* transform); +bool shapeGenRle(SwShape* shape, const Shape* sdata, const SwSize& clip, bool antiAlias); +void shapeDelOutline(SwShape* shape); +void shapeResetStroke(SwShape* shape, const Shape* sdata, const Matrix* transform); +bool shapeGenStrokeRle(SwShape* shape, const Shape* sdata, const Matrix* transform, const SwSize& clip); +void shapeFree(SwShape* shape); +void shapeDelStroke(SwShape* shape); +bool shapeGenFillColors(SwShape* shape, const Fill* fill, const Matrix* transform, SwSurface* surface, bool ctable); +void shapeResetFill(SwShape* shape); +void shapeDelFill(SwShape* shape); + +void strokeReset(SwStroke* stroke, const Shape* shape, const Matrix* transform); +bool strokeParseOutline(SwStroke* stroke, const SwOutline& outline); +SwOutline* strokeExportOutline(SwStroke* stroke); +void strokeFree(SwStroke* stroke); + +bool fillGenColorTable(SwFill* fill, const Fill* fdata, const Matrix* transform, SwSurface* surface, bool ctable); +void fillReset(SwFill* fill); +void fillFree(SwFill* fill); +void fillFetchLinear(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t offset, uint32_t len); +void fillFetchRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len); + +SwRleData* rleRender(const SwOutline* outline, const SwBBox& bbox, const SwSize& clip, bool antiAlias); +void rleFree(SwRleData* rle); + +bool rasterCompositor(SwSurface* surface); +bool rasterGradientShape(SwSurface* surface, SwShape* shape, unsigned id); +bool rasterSolidShape(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 rasterClear(SwSurface* surface); + + +static inline void rasterRGBA32(uint32_t *dst, uint32_t val, uint32_t offset, int32_t len) +{ +#ifdef THORVG_AVX_VECTOR_SUPPORT + int32_t align = (8 - (offset % 8)) % 8; + //Vectorization + auto avxDst = (__m256i*)(dst + offset + align); + int32_t i = (len - align); + for (;i > 7; i -= 8, ++avxDst) { + *avxDst = _mm256_set1_epi32(val); + } + //Alignment + if (align > 0) { + if (align > len) align -= (align - len); + auto tmp = dst + offset; + for (; align > 0; --align, ++tmp) *tmp = val; + } + //Pack Leftovers + dst += offset + (len - i); + while (i-- > 0) *(dst++) = val; +#else + dst += offset; + while (len--) *dst++ = val; +#endif +} + +#endif /* _TVG_SW_COMMON_H_ */ diff --git a/src/lib/sw_engine/tvgSwFill.cpp b/src/lib/sw_engine/tvgSwFill.cpp new file mode 100644 index 00000000..91a064de --- /dev/null +++ b/src/lib/sw_engine/tvgSwFill.cpp @@ -0,0 +1,314 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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 "tvgSwCommon.h" + + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +#define GRADIENT_STOP_SIZE 1024 +#define FIXPT_BITS 8 +#define FIXPT_SIZE (1<ctable) { + fill->ctable = static_cast(malloc(GRADIENT_STOP_SIZE * sizeof(uint32_t))); + if (!fill->ctable) return false; + } + + const Fill::ColorStop* colors; + auto cnt = fdata->colorStops(&colors); + if (cnt == 0 || !colors) return false; + + auto pColors = colors; + + if (pColors->a < 255) fill->translucent = true; + + auto r = ALPHA_MULTIPLY(pColors->r, pColors->a); + auto g = ALPHA_MULTIPLY(pColors->g, pColors->a); + auto b = ALPHA_MULTIPLY(pColors->b, pColors->a); + + auto rgba = surface->comp.join(r, g, b, pColors->a); + auto inc = 1.0f / static_cast(GRADIENT_STOP_SIZE); + auto pos = 1.5f * inc; + uint32_t i = 0; + + fill->ctable[i++] = rgba; + + while (pos <= pColors->offset) { + fill->ctable[i] = fill->ctable[i - 1]; + ++i; + pos += inc; + } + + for (uint32_t j = 0; j < cnt - 1; ++j) { + auto curr = colors + j; + auto next = curr + 1; + auto delta = 1.0f / (next->offset - curr->offset); + if (next->a < 255) fill->translucent = true; + + auto r = ALPHA_MULTIPLY(next->r, next->a); + auto g = ALPHA_MULTIPLY(next->g, next->a); + auto b = ALPHA_MULTIPLY(next->b, next->a); + + auto rgba2 = surface->comp.join(r, g, b, next->a); + + while (pos < next->offset && i < GRADIENT_STOP_SIZE) { + auto t = (pos - curr->offset) * delta; + auto dist = static_cast(256 * t); + auto dist2 = 256 - dist; + fill->ctable[i] = COLOR_INTERPOLATE(rgba, dist2, rgba2, dist); + ++i; + pos += inc; + } + rgba = rgba2; + } + + for (; i < GRADIENT_STOP_SIZE; ++i) + fill->ctable[i] = rgba; + + //Make sure the lat color stop is represented at the end of the table + fill->ctable[GRADIENT_STOP_SIZE - 1] = rgba; + + return true; +} + + +bool _prepareLinear(SwFill* fill, const LinearGradient* linear, const Matrix* transform) +{ + float x1, x2, y1, y2; + if (linear->linear(&x1, &y1, &x2, &y2) != Result::Success) return false; + + if (transform) { + auto sx = sqrt(pow(transform->e11, 2) + pow(transform->e21, 2)); + auto sy = sqrt(pow(transform->e12, 2) + pow(transform->e22, 2)); + auto cx = (x2 - x1) * 0.5f + x1; + auto cy = (y2 - y1) * 0.5f + y1; + auto dx = x1 - cx; + auto dy = y1 - cy; + x1 = dx * transform->e11 + dy * transform->e12 + transform->e13 + (cx * sx); + y1 = dx * transform->e21 + dy * transform->e22 + transform->e23 + (cy * sy); + dx = x2 - cx; + dy = y2 - cy; + x2 = dx * transform->e11 + dy * transform->e12 + transform->e13 + (cx * sx); + y2 = dx * transform->e21 + dy * transform->e22 + transform->e23 + (cy * sy); + } + + fill->linear.dx = x2 - x1; + fill->linear.dy = y2 - y1; + fill->linear.len = fill->linear.dx * fill->linear.dx + fill->linear.dy * fill->linear.dy; + + if (fill->linear.len < FLT_EPSILON) return true; + + fill->linear.dx /= fill->linear.len; + fill->linear.dy /= fill->linear.len; + fill->linear.offset = -fill->linear.dx * x1 -fill->linear.dy * y1; + + return true; +} + + +bool _prepareRadial(SwFill* fill, const RadialGradient* radial, const Matrix* transform) +{ + float radius; + if (radial->radial(&fill->radial.cx, &fill->radial.cy, &radius) != Result::Success) return false; + if (radius < FLT_EPSILON) return true; + + fill->sx = 1.0f; + fill->sy = 1.0f; + + if (transform) { + auto tx = fill->radial.cx * transform->e11 + fill->radial.cy * transform->e12 + transform->e13; + auto ty = fill->radial.cx * transform->e21 + fill->radial.cy * transform->e22 + transform->e23; + fill->radial.cx = tx; + fill->radial.cy = ty; + + auto sx = sqrt(pow(transform->e11, 2) + pow(transform->e21, 2)); + auto sy = sqrt(pow(transform->e12, 2) + pow(transform->e22, 2)); + + //FIXME; Scale + Rotation is not working properly + radius *= sx; + + if (fabsf(sx - sy) > FLT_EPSILON) { + fill->sx = sx; + fill->sy = sy; + } + } + + fill->radial.a = radius * radius; + fill->radial.inv2a = pow(1 / (2 * fill->radial.a), 2); + + return true; +} + + +static inline uint32_t _clamp(const SwFill* fill, int32_t pos) +{ + switch (fill->spread) { + case FillSpread::Pad: { + if (pos >= GRADIENT_STOP_SIZE) pos = GRADIENT_STOP_SIZE - 1; + else if (pos < 0) pos = 0; + break; + } + case FillSpread::Repeat: { + if (pos < 0) pos = GRADIENT_STOP_SIZE + pos; + pos = pos % GRADIENT_STOP_SIZE; + break; + } + case FillSpread::Reflect: { + auto limit = GRADIENT_STOP_SIZE * 2; + pos = pos % limit; + if (pos < 0) pos = limit + pos; + if (pos >= GRADIENT_STOP_SIZE) pos = (limit - pos - 1); + break; + } + } + return pos; +} + + +static inline uint32_t _fixedPixel(const SwFill* fill, int32_t pos) +{ + int32_t i = (pos + (FIXPT_SIZE / 2)) >> FIXPT_BITS; + return fill->ctable[_clamp(fill, i)]; +} + + +static inline uint32_t _pixel(const SwFill* fill, float pos) +{ + auto i = static_cast(pos * (GRADIENT_STOP_SIZE - 1) + 0.5f); + return fill->ctable[_clamp(fill, i)]; +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +void fillFetchRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len) +{ + if (fill->radial.a < FLT_EPSILON) return; + + //Rotation + auto rx = (x + 0.5f - fill->radial.cx) * fill->sy; + auto ry = (y + 0.5f - fill->radial.cy) * fill->sx; + auto inv2a = fill->radial.inv2a; + auto rxy = rx * rx + ry * ry; + auto rxryPlus = 2 * rx; + auto det = (-4 * fill->radial.a * -rxy) * inv2a; + auto detDelta = (4 * fill->radial.a * (rxryPlus + 1.0f)) * inv2a; + auto detDelta2 = (4 * fill->radial.a * 2.0f) * inv2a; + + for (uint32_t i = 0 ; i < len ; ++i) { + *dst = _pixel(fill, sqrt(det)); + ++dst; + det += detDelta; + detDelta += detDelta2; + } +} + + +void fillFetchLinear(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t offset, uint32_t len) +{ + if (fill->linear.len < FLT_EPSILON) return; + + //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 (fabsf(inc) < FLT_EPSILON) { + auto color = _fixedPixel(fill, static_cast(t * FIXPT_SIZE)); + rasterRGBA32(dst, color, offset, len); + return; + } + + dst += offset; + + auto vMax = static_cast(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(t * FIXPT_SIZE); + auto inc2 = static_cast(inc * FIXPT_SIZE); + for (uint32_t j = 0; j < len; ++j) { + *dst = _fixedPixel(fill, t2); + ++dst; + t2 += inc2; + } + //we have to fallback to float math + } else { + while (dst < dst + len) { + *dst = _pixel(fill, t / GRADIENT_STOP_SIZE); + ++dst; + t += inc; + } + } +} + + +bool fillGenColorTable(SwFill* fill, const Fill* fdata, const Matrix* transform, SwSurface* surface, bool ctable) +{ + if (!fill) return false; + + fill->spread = fdata->spread(); + + if (ctable) { + if (!_updateColorTable(fill, fdata, surface)) return false; + } + + if (fdata->id() == FILL_ID_LINEAR) { + return _prepareLinear(fill, static_cast(fdata), transform); + } else if (fdata->id() == FILL_ID_RADIAL) { + return _prepareRadial(fill, static_cast(fdata), transform); + } + + //LOG: What type of gradient?! + + return false; +} + + +void fillReset(SwFill* fill) +{ + if (fill->ctable) { + free(fill->ctable); + fill->ctable = nullptr; + } + fill->translucent = false; +} + + +void fillFree(SwFill* fill) +{ + if (!fill) return; + + if (fill->ctable) free(fill->ctable); + + free(fill); +} \ No newline at end of file diff --git a/src/lib/sw_engine/tvgSwMath.cpp b/src/lib/sw_engine/tvgSwMath.cpp new file mode 100644 index 00000000..56ce40fc --- /dev/null +++ b/src/lib/sw_engine/tvgSwMath.cpp @@ -0,0 +1,417 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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 "tvgSwCommon.h" + + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +constexpr SwFixed CORDIC_FACTOR = 0xDBD95B16UL; //the Cordic shrink factor 0.858785336480436 * 2^32 + +//this table was generated for SW_FT_PI = 180L << 16, i.e. degrees +constexpr static auto ATAN_MAX = 23; +constexpr static SwFixed ATAN_TBL[] = { + 1740967L, 919879L, 466945L, 234379L, 117304L, 58666L, 29335L, + 14668L, 7334L, 3667L, 1833L, 917L, 458L, 229L, 115L, + 57L, 29L, 14L, 7L, 4L, 2L, 1L}; + +static inline SwCoord SATURATE(const SwCoord x) +{ + return (x >> (sizeof(SwCoord) * 8 - 1)); +} + + +static inline SwFixed PAD_ROUND(const SwFixed x, int32_t n) +{ + return (((x) + ((n)/2)) & ~((n)-1)); +} + + +static SwCoord _downscale(SwFixed x) +{ + //multiply a give value by the CORDIC shrink factor + auto s = abs(x); + int64_t t = (s * static_cast(CORDIC_FACTOR)) + 0x100000000UL; + s = static_cast(t >> 32); + if (x < 0) s = -s; + return s; +} + + +static int32_t _normalize(SwPoint& pt) +{ + /* the highest bit in overflow-safe vector components + MSB of 0.858785336480436 * sqrt(0.5) * 2^30 */ + constexpr auto SAFE_MSB = 29; + + auto v = pt; + + //High order bit(MSB) + //clz: count leading zero’s + auto shift = 31 - __builtin_clz(abs(v.x) | abs(v.y)); + + if (shift <= SAFE_MSB) { + shift = SAFE_MSB - shift; + pt.x = static_cast((unsigned long)v.x << shift); + pt.y = static_cast((unsigned long)v.y << shift); + } else { + shift -= SAFE_MSB; + pt.x = v.x >> shift; + pt.y = v.y >> shift; + shift = -shift; + } + return shift; +} + + +static void _polarize(SwPoint& pt) +{ + auto v = pt; + SwFixed theta; + + //Get the vector into [-PI/4, PI/4] sector + if (v.y > v.x) { + if (v.y > -v.x) { + auto tmp = v.y; + v.y = -v.x; + v.x = tmp; + theta = SW_ANGLE_PI2; + } else { + theta = v.y > 0 ? SW_ANGLE_PI : -SW_ANGLE_PI; + v.x = -v.x; + v.y = -v.y; + } + } else { + if (v.y < -v.x) { + theta = -SW_ANGLE_PI2; + auto tmp = -v.y; + v.y = v.x; + v.x = tmp; + } else { + theta = 0; + } + } + + auto atan = ATAN_TBL; + uint32_t i; + SwFixed j; + + //Pseudorotations. with right shifts + for (i = 1, j = 1; i < ATAN_MAX; j <<= 1, ++i) { + if (v.y > 0) { + auto tmp = v.x + ((v.y + j) >> i); + v.y = v.y - ((v.x + j) >> i); + v.x = tmp; + theta += *atan++; + } else { + auto tmp = v.x - ((v.y + j) >> i); + v.y = v.y + ((v.x + j) >> i); + v.x = tmp; + theta -= *atan++; + } + } + + //round theta + if (theta >= 0) theta = PAD_ROUND(theta, 32); + else theta = -PAD_ROUND(-theta, 32); + + pt.x = v.x; + pt.y = theta; +} + + +static void _rotate(SwPoint& pt, SwFixed theta) +{ + SwFixed x = pt.x; + SwFixed y = pt.y; + + //Rotate inside [-PI/4, PI/4] sector + while (theta < -SW_ANGLE_PI4) { + auto tmp = y; + y = -x; + x = tmp; + theta += SW_ANGLE_PI2; + } + + while (theta > SW_ANGLE_PI4) { + auto tmp = -y; + y = x; + x = tmp; + theta -= SW_ANGLE_PI2; + } + + auto atan = ATAN_TBL; + uint32_t i; + SwFixed j; + + for (i = 1, j = 1; i < ATAN_MAX; j <<= 1, ++i) { + if (theta < 0) { + auto tmp = x + ((y + j) >> i); + y = y - ((x + j) >> i); + x = tmp; + theta += *atan++; + } else { + auto tmp = x - ((y + j) >> i); + y = y + ((x + j) >> i); + x = tmp; + theta -= *atan++; + } + } + + pt.x = static_cast(x); + pt.y = static_cast(y); +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +SwFixed mathMean(SwFixed angle1, SwFixed angle2) +{ + return angle1 + mathDiff(angle1, angle2) / 2; +} + + +bool mathSmallCubic(SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, SwFixed& angleOut) +{ + auto d1 = base[2] - base[3]; + auto d2 = base[1] - base[2]; + auto d3 = base[0] - base[1]; + + if (d1.small()) { + if (d2.small()) { + if (d3.small()) { + //basically a point. + //do nothing to retain original direction + } else { + angleIn = angleMid = angleOut = mathAtan(d3); + } + } else { + if (d3.small()) { + angleIn = angleMid = angleOut = mathAtan(d2); + } else { + angleIn = angleMid = mathAtan(d2); + angleOut = mathAtan(d3); + } + } + } else { + if (d2.small()) { + if (d3.small()) { + angleIn = angleMid = angleOut = mathAtan(d1); + } else { + angleIn = mathAtan(d1); + angleOut = mathAtan(d3); + angleMid = mathMean(angleIn, angleOut); + } + } else { + if (d3.small()) { + angleIn = mathAtan(d1); + angleMid = angleOut = mathAtan(d2); + } else { + angleIn = mathAtan(d1); + angleMid = mathAtan(d2); + angleOut = mathAtan(d3); + } + } + } + + auto theta1 = abs(mathDiff(angleIn, angleMid)); + auto theta2 = abs(mathDiff(angleMid, angleOut)); + + if ((theta1 < (SW_ANGLE_PI / 8)) && (theta2 < (SW_ANGLE_PI / 8))) return true; + return false; +} + + +int64_t mathMultiply(int64_t a, int64_t b) +{ + int32_t s = 1; + + //move sign + if (a < 0) { + a = -a; + s = -s; + } + if (b < 0) { + b = -b; + s = -s; + } + int64_t c = (a * b + 0x8000L ) >> 16; + return (s > 0) ? c : -c; +} + + +int64_t mathDivide(int64_t a, int64_t b) +{ + int32_t s = 1; + + //move sign + if (a < 0) { + a = -a; + s = -s; + } + if (b < 0) { + b = -b; + s = -s; + } + int64_t q = b > 0 ? ((a << 16) + (b >> 1)) / b : 0x7FFFFFFFL; + return (s < 0 ? -q : q); +} + + +int64_t mathMulDiv(int64_t a, int64_t b, int64_t c) +{ + int32_t s = 1; + + //move sign + if (a < 0) { + a = -a; + s = -s; + } + if (b < 0) { + b = -b; + s = -s; + } + if (c < 0) { + c = -c; + s = -s; + } + int64_t d = c > 0 ? (a * b + (c >> 1)) / c : 0x7FFFFFFFL; + + return (s > 0 ? d : -d); +} + + +void mathRotate(SwPoint& pt, SwFixed angle) +{ + if (angle == 0 || (pt.x == 0 && pt.y == 0)) return; + + auto v = pt; + auto shift = _normalize(v); + + auto theta = angle; + _rotate(v, theta); + + v.x = _downscale(v.x); + v.y = _downscale(v.y); + + if (shift > 0) { + auto half = static_cast(1L << (shift - 1)); + pt.x = (v.x + half + SATURATE(v.x)) >> shift; + pt.y = (v.y + half + SATURATE(v.y)) >> shift; + } else { + shift = -shift; + pt.x = static_cast((unsigned long)v.x << shift); + pt.y = static_cast((unsigned long)v.y << shift); + } +} + +SwFixed mathTan(SwFixed angle) +{ + SwPoint v = {CORDIC_FACTOR >> 8, 0}; + _rotate(v, angle); + return mathDivide(v.y, v.x); +} + + +SwFixed mathAtan(const SwPoint& pt) +{ + if (pt.x == 0 && pt.y == 0) return 0; + + auto v = pt; + _normalize(v); + _polarize(v); + + return v.y; +} + + +SwFixed mathSin(SwFixed angle) +{ + return mathCos(SW_ANGLE_PI2 - angle); +} + + +SwFixed mathCos(SwFixed angle) +{ + SwPoint v = {CORDIC_FACTOR >> 8, 0}; + _rotate(v, angle); + return (v.x + 0x80L) >> 8; +} + + +SwFixed mathLength(SwPoint& pt) +{ + auto v = pt; + + //trivial case + if (v.x == 0) return abs(v.y); + if (v.y == 0) return abs(v.x); + + //general case + auto shift = _normalize(v); + _polarize(v); + v.x = _downscale(v.x); + + if (shift > 0) return (v.x + (static_cast(1) << (shift -1))) >> shift; + return static_cast((uint32_t)v.x << -shift); +} + + +void mathSplitCubic(SwPoint* base) +{ + SwCoord a, b, c, d; + + base[6].x = base[3].x; + c = base[1].x; + d = base[2].x; + base[1].x = a = (base[0].x + c) / 2; + base[5].x = b = (base[3].x + d) / 2; + c = (c + d) / 2; + base[2].x = a = (a + c) / 2; + base[4].x = b = (b + c) / 2; + base[3].x = (a + b) / 2; + + base[6].y = base[3].y; + c = base[1].y; + d = base[2].y; + base[1].y = a = (base[0].y + c) / 2; + base[5].y = b = (base[3].y + d) / 2; + c = (c + d) / 2; + base[2].y = a = (a + c) / 2; + base[4].y = b = (b + c) / 2; + base[3].y = (a + b) / 2; +} + + +SwFixed mathDiff(SwFixed angle1, SwFixed angle2) +{ + auto delta = angle2 - angle1; + + delta %= SW_ANGLE_2PI; + if (delta < 0) delta += SW_ANGLE_2PI; + if (delta > SW_ANGLE_PI) delta -= SW_ANGLE_2PI; + + return delta; +} \ No newline at end of file diff --git a/src/lib/sw_engine/tvgSwRaster.cpp b/src/lib/sw_engine/tvgSwRaster.cpp new file mode 100644 index 00000000..7b5b9868 --- /dev/null +++ b/src/lib/sw_engine/tvgSwRaster.cpp @@ -0,0 +1,369 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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 "tvgSwCommon.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +static uint32_t _colorAlpha(uint32_t c) +{ + return (c >> 24) & 0xff; +} + + +static uint32_t _abgrJoin(uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ + return (a << 24 | b << 16 | g << 8 | r); +} + + +static uint32_t _argbJoin(uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ + return (a << 24 | r << 16 | g << 8 | b); +} + + +static SwBBox _clipRegion(Surface* surface, SwBBox& in) +{ + auto bbox = in; + + if (bbox.min.x < 0) bbox.min.x = 0; + if (bbox.min.y < 0) bbox.min.y = 0; + if (bbox.max.x > static_cast(surface->w)) bbox.max.x = surface->w; + if (bbox.max.y > static_cast(surface->h)) bbox.max.y = surface->h; + + return bbox; +} + +static bool _rasterTranslucentRect(SwSurface* surface, const SwBBox& region, uint32_t color) +{ + auto buffer = surface->buffer + (region.min.y * surface->stride) + region.min.x; + auto h = static_cast(region.max.y - region.min.y); + auto w = static_cast(region.max.x - region.min.x); + auto ialpha = 255 - surface->comp.alpha(color); + + for (uint32_t y = 0; y < h; ++y) { + auto dst = &buffer[y * surface->stride]; + for (uint32_t x = 0; x < w; ++x) { + dst[x] = color + ALPHA_BLEND(dst[x], ialpha); + } + } + return true; +} + + +static bool _rasterSolidRect(SwSurface* surface, const SwBBox& region, uint32_t color) +{ + auto buffer = surface->buffer + (region.min.y * surface->stride); + auto w = static_cast(region.max.x - region.min.x); + auto h = static_cast(region.max.y - region.min.y); + + for (uint32_t y = 0; y < h; ++y) { + rasterRGBA32(buffer + y * surface->stride, color, region.min.x, w); + } + return true; +} + + +static bool _rasterTranslucentRle(SwSurface* surface, SwRleData* rle, uint32_t color) +{ + if (!rle) return false; + + auto span = rle->spans; + uint32_t src; + + for (uint32_t i = 0; i < rle->size; ++i) { + auto dst = &surface->buffer[span->y * surface->stride + span->x]; + if (span->coverage < 255) src = ALPHA_BLEND(color, span->coverage); + else src = color; + auto ialpha = 255 - surface->comp.alpha(src); + for (uint32_t i = 0; i < span->len; ++i) { + dst[i] = src + ALPHA_BLEND(dst[i], ialpha); + } + ++span; + } + return true; +} + + +static bool _rasterSolidRle(SwSurface* surface, SwRleData* rle, uint32_t color) +{ + if (!rle) return false; + + auto span = rle->spans; + + for (uint32_t i = 0; i < rle->size; ++i) { + if (span->coverage == 255) { + rasterRGBA32(surface->buffer + span->y * surface->stride, color, span->x, span->len); + } else { + auto dst = &surface->buffer[span->y * surface->stride + span->x]; + auto src = ALPHA_BLEND(color, span->coverage); + auto ialpha = 255 - span->coverage; + for (uint32_t i = 0; i < span->len; ++i) { + dst[i] = src + ALPHA_BLEND(dst[i], ialpha); + } + } + ++span; + } + return true; +} + + +static bool _rasterLinearGradientRect(SwSurface* surface, const SwBBox& region, const SwFill* fill) +{ + if (!fill) return false; + + auto buffer = surface->buffer + (region.min.y * surface->stride) + region.min.x; + auto h = static_cast(region.max.y - region.min.y); + auto w = static_cast(region.max.x - region.min.x); + + //Translucent Gradient + if (fill->translucent) { + + auto tmpBuf = static_cast(alloca(surface->w * sizeof(uint32_t))); + if (!tmpBuf) return false; + + for (uint32_t y = 0; y < h; ++y) { + auto dst = &buffer[y * surface->stride]; + fillFetchLinear(fill, tmpBuf, region.min.y + y, region.min.x, 0, w); + for (uint32_t x = 0; x < w; ++x) { + dst[x] = tmpBuf[x] + ALPHA_BLEND(dst[x], 255 - surface->comp.alpha(tmpBuf[x])); + } + } + //Opaque Gradient + } else { + for (uint32_t y = 0; y < h; ++y) { + fillFetchLinear(fill, buffer + y * surface->stride, region.min.y + y, region.min.x, 0, w); + } + } + return true; +} + + +static bool _rasterRadialGradientRect(SwSurface* surface, const SwBBox& region, const SwFill* fill) +{ + if (!fill) return false; + + auto buffer = surface->buffer + (region.min.y * surface->stride) + region.min.x; + auto h = static_cast(region.max.y - region.min.y); + auto w = static_cast(region.max.x - region.min.x); + + //Translucent Gradient + if (fill->translucent) { + + auto tmpBuf = static_cast(alloca(surface->w * sizeof(uint32_t))); + if (!tmpBuf) return false; + + for (uint32_t y = 0; y < h; ++y) { + auto dst = &buffer[y * surface->stride]; + fillFetchRadial(fill, tmpBuf, region.min.y + y, region.min.x, w); + for (uint32_t x = 0; x < w; ++x) { + dst[x] = tmpBuf[x] + ALPHA_BLEND(dst[x], 255 - surface->comp.alpha(tmpBuf[x])); + } + } + //Opaque Gradient + } else { + for (uint32_t y = 0; y < h; ++y) { + auto dst = &buffer[y * surface->stride]; + fillFetchRadial(fill, dst, region.min.y + y, region.min.x, w); + } + } + return true; +} + + +static bool _rasterLinearGradientRle(SwSurface* surface, SwRleData* rle, const SwFill* fill) +{ + if (!rle || !fill) return false; + + auto buf = static_cast(alloca(surface->w * sizeof(uint32_t))); + if (!buf) return false; + + auto span = rle->spans; + + //Translucent Gradient + if (fill->translucent) { + for (uint32_t i = 0; i < rle->size; ++i) { + auto dst = &surface->buffer[span->y * surface->stride + span->x]; + fillFetchLinear(fill, buf, span->y, span->x, 0, span->len); + if (span->coverage == 255) { + for (uint32_t i = 0; i < span->len; ++i) { + dst[i] = buf[i] + ALPHA_BLEND(dst[i], 255 - surface->comp.alpha(buf[i])); + } + } else { + for (uint32_t i = 0; i < span->len; ++i) { + auto tmp = ALPHA_BLEND(buf[i], span->coverage); + dst[i] = tmp + ALPHA_BLEND(dst[i], 255 - surface->comp.alpha(tmp)); + } + } + ++span; + } + //Opaque Gradient + } else { + for (uint32_t i = 0; i < rle->size; ++i) { + if (span->coverage == 255) { + fillFetchLinear(fill, surface->buffer + span->y * surface->stride, span->y, span->x, span->x, span->len); + } else { + auto dst = &surface->buffer[span->y * surface->stride + span->x]; + fillFetchLinear(fill, buf, span->y, span->x, 0, span->len); + auto ialpha = 255 - span->coverage; + for (uint32_t i = 0; i < span->len; ++i) { + dst[i] = ALPHA_BLEND(buf[i], span->coverage) + ALPHA_BLEND(dst[i], ialpha); + } + } + ++span; + } + } + return true; +} + + +static bool _rasterRadialGradientRle(SwSurface* surface, SwRleData* rle, const SwFill* fill) +{ + if (!rle || !fill) return false; + + auto buf = static_cast(alloca(surface->w * sizeof(uint32_t))); + if (!buf) return false; + + auto span = rle->spans; + + //Translucent Gradient + if (fill->translucent) { + for (uint32_t i = 0; i < rle->size; ++i) { + auto dst = &surface->buffer[span->y * surface->stride + span->x]; + fillFetchRadial(fill, buf, span->y, span->x, span->len); + if (span->coverage == 255) { + for (uint32_t i = 0; i < span->len; ++i) { + dst[i] = buf[i] + ALPHA_BLEND(dst[i], 255 - surface->comp.alpha(buf[i])); + } + } else { + for (uint32_t i = 0; i < span->len; ++i) { + auto tmp = ALPHA_BLEND(buf[i], span->coverage); + dst[i] = tmp + ALPHA_BLEND(dst[i], 255 - surface->comp.alpha(tmp)); + } + } + ++span; + } + //Opaque Gradient + } else { + for (uint32_t i = 0; i < rle->size; ++i) { + auto dst = &surface->buffer[span->y * surface->stride + span->x]; + if (span->coverage == 255) { + fillFetchRadial(fill, dst, span->y, span->x, span->len); + } else { + fillFetchRadial(fill, buf, span->y, span->x, span->len); + auto ialpha = 255 - span->coverage; + for (uint32_t i = 0; i < span->len; ++i) { + dst[i] = ALPHA_BLEND(buf[i], span->coverage) + ALPHA_BLEND(dst[i], ialpha); + } + } + ++span; + } + } + return true; +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +bool rasterCompositor(SwSurface* surface) +{ + if (surface->cs == SwCanvas::ABGR8888) { + surface->comp.alpha = _colorAlpha; + surface->comp.join = _abgrJoin; + } else if (surface->cs == SwCanvas::ARGB8888) { + surface->comp.alpha = _colorAlpha; + surface->comp.join = _argbJoin; + } else { + //What Color Space ??? + return false; + } + + return true; +} + + +bool rasterGradientShape(SwSurface* surface, SwShape* shape, unsigned id) +{ + //Fast Track + if (shape->rect) { + auto region = _clipRegion(surface, shape->bbox); + if (id == FILL_ID_LINEAR) return _rasterLinearGradientRect(surface, region, shape->fill); + return _rasterRadialGradientRect(surface, region, shape->fill); + } else { + if (id == FILL_ID_LINEAR) return _rasterLinearGradientRle(surface, shape->rle, shape->fill); + return _rasterRadialGradientRle(surface, shape->rle, shape->fill); + } + return false; +} + + +bool rasterSolidShape(SwSurface* surface, SwShape* shape, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ + r = ALPHA_MULTIPLY(r, a); + g = ALPHA_MULTIPLY(g, a); + b = ALPHA_MULTIPLY(b, a); + + auto color = surface->comp.join(r, g, b, a); + + //Fast Track + if (shape->rect) { + auto region = _clipRegion(surface, shape->bbox); + if (a == 255) return _rasterSolidRect(surface, region, color); + return _rasterTranslucentRect(surface, region, color); + } else{ + if (a == 255) return _rasterSolidRle(surface, shape->rle, color); + return _rasterTranslucentRle(surface, shape->rle, color); + } + return false; +} + + +bool rasterStroke(SwSurface* surface, SwShape* shape, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ + r = ALPHA_MULTIPLY(r, a); + g = ALPHA_MULTIPLY(g, a); + b = ALPHA_MULTIPLY(b, a); + + auto color = surface->comp.join(r, g, b, a); + + if (a == 255) return _rasterSolidRle(surface, shape->strokeRle, color); + return _rasterTranslucentRle(surface, shape->strokeRle, color); +} + + +bool rasterClear(SwSurface* surface) +{ + if (!surface || !surface->buffer || surface->stride <= 0 || surface->w <= 0 || surface->h <= 0) return false; + + if (surface->w == surface->stride) { + rasterRGBA32(surface->buffer, 0x00000000, 0, surface->w * surface->h); + } else { + for (uint32_t i = 0; i < surface->h; i++) { + rasterRGBA32(surface->buffer + surface->stride * i, 0x00000000, 0, surface->w); + } + } + return true; +} \ No newline at end of file diff --git a/src/lib/sw_engine/tvgSwRenderer.cpp b/src/lib/sw_engine/tvgSwRenderer.cpp new file mode 100644 index 00000000..1a1e0bf7 --- /dev/null +++ b/src/lib/sw_engine/tvgSwRenderer.cpp @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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 "tvgSwCommon.h" +#include "tvgSwRenderer.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ +static RenderInitializer renderInit; + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +SwRenderer::~SwRenderer() +{ + if (surface) delete(surface); +} + + +bool SwRenderer::target(uint32_t* buffer, uint32_t stride, uint32_t w, uint32_t h, uint32_t cs) +{ + if (!buffer || stride == 0 || w == 0 || h == 0) return false; + + if (!surface) { + surface = new SwSurface; + if (!surface) return false; + } + + surface->buffer = buffer; + surface->stride = stride; + surface->w = w; + surface->h = h; + surface->cs = cs; + + return rasterCompositor(surface); +} + + +bool SwRenderer::preRender() +{ + return rasterClear(surface); +} + + +bool SwRenderer::render(const Shape& sdata, TVG_UNUSED void *data) +{ + auto shape = static_cast(data); + if (!shape) return false; + + uint8_t r, g, b, a; + + if (auto fill = sdata.fill()) { + rasterGradientShape(surface, shape, fill->id()); + } else{ + sdata.fill(&r, &g, &b, &a); + if (a > 0) rasterSolidShape(surface, shape, r, g, b, a); + } + sdata.strokeColor(&r, &g, &b, &a); + if (a > 0) rasterStroke(surface, shape, r, g, b, a); + + return true; +} + + +bool SwRenderer::dispose(TVG_UNUSED const Shape& sdata, void *data) +{ + auto shape = static_cast(data); + if (!shape) return false; + shapeFree(shape); + return true; +} + + +void* SwRenderer::prepare(const Shape& sdata, void* data, const RenderTransform* transform, RenderUpdateFlag flags) +{ + //prepare shape data + auto shape = static_cast(data); + if (!shape) { + shape = static_cast(calloc(1, sizeof(SwShape))); + if (!shape) return nullptr; + } + + if (flags == RenderUpdateFlag::None) return shape; + + SwSize clip = {static_cast(surface->w), static_cast(surface->h)}; + + //Valid Stroking? + uint8_t strokeAlpha = 0; + auto strokeWidth = sdata.strokeWidth(); + if (strokeWidth > FLT_EPSILON) { + sdata.strokeColor(nullptr, nullptr, nullptr, &strokeAlpha); + } + + const Matrix* matrix = (transform ? &transform->m : nullptr); + + //Shape + if (flags & (RenderUpdateFlag::Path | RenderUpdateFlag::Transform)) { + shapeReset(shape); + uint8_t alpha = 0; + sdata.fill(nullptr, nullptr, nullptr, &alpha); + bool renderShape = (alpha > 0 || sdata.fill()); + if (renderShape || strokeAlpha) { + if (!shapePrepare(shape, &sdata, clip, matrix)) return shape; + if (renderShape) { + auto antiAlias = (strokeAlpha > 0 && strokeWidth >= 2) ? false : true; + if (!shapeGenRle(shape, &sdata, clip, antiAlias)) return shape; + } + } + //Fill + if (flags & (RenderUpdateFlag::Gradient | RenderUpdateFlag::Transform)) { + auto fill = sdata.fill(); + if (fill) { + auto ctable = (flags & RenderUpdateFlag::Gradient) ? true : false; + if (ctable) shapeResetFill(shape); + if (!shapeGenFillColors(shape, fill, matrix, surface, ctable)) return shape; + } else { + shapeDelFill(shape); + } + } + //Stroke + if (flags & (RenderUpdateFlag::Stroke | RenderUpdateFlag::Transform)) { + if (strokeAlpha > 0) { + shapeResetStroke(shape, &sdata, matrix); + if (!shapeGenStrokeRle(shape, &sdata, matrix, clip)) return shape; + } else { + shapeDelStroke(shape); + } + } + shapeDelOutline(shape); + } + + return shape; +} + + +int SwRenderer::init() +{ + return RenderInitializer::init(renderInit, new SwRenderer); +} + + +int SwRenderer::term() +{ + return RenderInitializer::term(renderInit); +} + + +uint32_t SwRenderer::unref() +{ + return RenderInitializer::unref(renderInit); +} + + +uint32_t SwRenderer::ref() +{ + return RenderInitializer::ref(renderInit); +} + + +SwRenderer* SwRenderer::inst() +{ + //We know renderer type, avoid dynamic_cast for performance. + return static_cast(RenderInitializer::inst(renderInit)); +} \ No newline at end of file diff --git a/src/lib/sw_engine/tvgSwRenderer.h b/src/lib/sw_engine/tvgSwRenderer.h new file mode 100644 index 00000000..2185f344 --- /dev/null +++ b/src/lib/sw_engine/tvgSwRenderer.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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_SW_RENDERER_H_ +#define _TVG_SW_RENDERER_H_ + +struct SwSurface; + +namespace tvg +{ + +class SwRenderer : public RenderMethod +{ +public: + void* prepare(const Shape& shape, void* data, const RenderTransform* transform, RenderUpdateFlag flags) override; + bool dispose(const Shape& shape, void *data) override; + bool preRender() override; + bool render(const Shape& shape, void *data) override; + bool target(uint32_t* buffer, uint32_t stride, uint32_t w, uint32_t h, uint32_t cs); + uint32_t ref() override; + uint32_t unref() override; + + static SwRenderer* inst(); + static int init(); + static int term(); + +private: + SwSurface* surface = nullptr; + + SwRenderer(){}; + ~SwRenderer(); +}; + +} + +#endif /* _TVG_SW_RENDERER_H_ */ diff --git a/src/lib/sw_engine/tvgSwRle.cpp b/src/lib/sw_engine/tvgSwRle.cpp new file mode 100644 index 00000000..37a860f5 --- /dev/null +++ b/src/lib/sw_engine/tvgSwRle.cpp @@ -0,0 +1,754 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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 +#include +#include +#include "tvgSwCommon.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +constexpr auto MAX_SPANS = 256; +constexpr auto PIXEL_BITS = 8; //must be at least 6 bits! +constexpr auto ONE_PIXEL = (1L << PIXEL_BITS); + +using Area = long; + +struct Band +{ + SwCoord min, max; +}; + +struct Cell +{ + SwCoord x; + SwCoord cover; + Area area; + Cell *next; +}; + +struct RleWorker +{ + SwRleData* rle; + + SwPoint cellPos; + SwPoint cellMin; + SwPoint cellMax; + SwCoord cellXCnt; + SwCoord cellYCnt; + + Area area; + SwCoord cover; + + Cell* cells; + ptrdiff_t maxCells; + ptrdiff_t cellsCnt; + + SwPoint pos; + + SwPoint bezStack[32 * 3 + 1]; + int levStack[32]; + + SwOutline* outline; + + SwSpan spans[MAX_SPANS]; + int spansCnt; + int ySpan; + + int bandSize; + int bandShoot; + + jmp_buf jmpBuf; + + void* buffer; + long bufferSize; + + Cell** yCells; + SwCoord yCnt; + + SwSize clip; + + bool invalid; + bool antiAlias; +}; + + +static inline SwPoint UPSCALE(const SwPoint& pt) +{ + return {pt.x << (PIXEL_BITS - 6), pt.y << (PIXEL_BITS - 6)}; +} + + +static inline SwPoint DOWNSCALE(const SwPoint& pt) +{ + return {pt.x >> (PIXEL_BITS - 6), pt.y >> (PIXEL_BITS - 6)}; +} + + +static inline SwPoint TRUNC(const SwPoint& pt) +{ + return {pt.x >> PIXEL_BITS, pt.y >> PIXEL_BITS}; +} + + +static inline SwCoord TRUNC(const SwCoord x) +{ + return x >> PIXEL_BITS; +} + + +static inline SwPoint SUBPIXELS(const SwPoint& pt) +{ + return {pt.x << PIXEL_BITS, pt.y << PIXEL_BITS}; +} + + +static inline SwCoord SUBPIXELS(const SwCoord x) +{ + return (x << PIXEL_BITS); +} + +/* + * Approximate sqrt(x*x+y*y) using the `alpha max plus beta min' + * algorithm. We use alpha = 1, beta = 3/8, giving us results with a + * largest error less than 7% compared to the exact value. + */ +static inline SwCoord HYPOT(SwPoint pt) +{ + if (pt.x < 0) pt.x = -pt.x; + if (pt.y < 0) pt.y = -pt.y; + return ((pt.x > pt.y) ? (pt.x + (3 * pt.y >> 3)) : (pt.y + (3 * pt.x >> 3))); +} + +static void _genSpan(SwRleData* rle, SwSpan* spans, uint32_t count) +{ + auto newSize = rle->size + count; + + /* allocate enough memory for new spans */ + /* alloc is required to prevent free and reallocation */ + /* when the rle needs to be regenerated because of attribute change. */ + if (rle->alloc < newSize) { + rle->alloc = (newSize * 2); + rle->spans = static_cast(realloc(rle->spans, rle->alloc * sizeof(SwSpan))); + } + + //copy the new spans to the allocated memory + SwSpan* lastSpan = rle->spans + rle->size; + memcpy(lastSpan, spans, count * sizeof(SwSpan)); + + rle->size = newSize; +} + + +static void _horizLine(RleWorker& rw, SwCoord x, SwCoord y, SwCoord area, SwCoord acount) +{ + x += rw.cellMin.x; + y += rw.cellMin.y; + + //Clip Y range + if (y < 0) return; + if (y >= rw.clip.h) return; + + /* compute the coverage line's coverage, depending on the outline fill rule */ + /* the coverage percentage is area/(PIXEL_BITS*PIXEL_BITS*2) */ + auto coverage = static_cast(area >> (PIXEL_BITS * 2 + 1 - 8)); //range 0 - 256 + + if (coverage < 0) coverage = -coverage; + + if (rw.outline->fillMode == SW_OUTLINE_FILL_EVEN_ODD) { + coverage &= 511; + if (coverage > 256) coverage = 512 - coverage; + else if (coverage == 256) coverage = 255; + } else { + //normal non-zero winding rule + if (coverage >= 256) coverage = 255; + } + + //span has ushort coordinates. check limit overflow + if (x >= SHRT_MAX) { + //LOG: x coordinate overflow! + x = SHRT_MAX; + } + if (y >= SHRT_MAX) { + //LOG: y coordinate overflow! + y = SHRT_MAX; + } + + if (coverage > 0) { + if (!rw.antiAlias) coverage = 255; + auto count = rw.spansCnt; + auto span = rw.spans + count - 1; + + //see whether we can add this span to the current list + if ((count > 0) && (rw.ySpan == y) && + (span->x + span->len == x) && (span->coverage == coverage)) { + + //Clip x range + SwCoord xOver = 0; + if (x + acount >= rw.clip.w) xOver -= (x + acount - rw.clip.w); + if (x < 0) xOver += x; + + //span->len += (acount + xOver) - 1; + span->len += (acount + xOver); + return; + } + + if (count >= MAX_SPANS) { + _genSpan(rw.rle, rw.spans, count); + rw.spansCnt = 0; + rw.ySpan = 0; + span = rw.spans; + } else { + ++span; + } + + //Clip x range + SwCoord xOver = 0; + if (x + acount >= rw.clip.w) xOver -= (x + acount - rw.clip.w); + if (x < 0) { + xOver += x; + x = 0; + } + + //Nothing to draw + if (acount + xOver <= 0) return; + + //add a span to the current list + span->x = x; + span->y = y; + span->len = (acount + xOver); + span->coverage = coverage; + ++rw.spansCnt; + rw.ySpan = y; + } +} + + +static void _sweep(RleWorker& rw) +{ + if (rw.cellsCnt == 0) return; + + rw.spansCnt = 0; + rw.ySpan = 0; + + for (int y = 0; y < rw.yCnt; ++y) { + auto cover = 0; + auto x = 0; + auto cell = rw.yCells[y]; + + while (cell) { + + if (cell->x > x && cover != 0) _horizLine(rw, x, y, cover * (ONE_PIXEL * 2), cell->x - x); + cover += cell->cover; + auto area = cover * (ONE_PIXEL * 2) - cell->area; + if (area != 0 && cell->x >= 0) _horizLine(rw, cell->x, y, area, 1); + + x = cell->x + 1; + cell = cell->next; + } + + if (cover != 0) _horizLine(rw, x, y, cover * (ONE_PIXEL * 2), rw.cellXCnt - x); + } + + if (rw.spansCnt > 0) _genSpan(rw.rle, rw.spans, rw.spansCnt); +} + + +static Cell* _findCell(RleWorker& rw) +{ + auto x = rw.cellPos.x; + if (x > rw.cellXCnt) x = rw.cellXCnt; + + auto pcell = &rw.yCells[rw.cellPos.y]; + + while(true) { + Cell* cell = *pcell; + if (!cell || cell->x > x) break; + if (cell->x == x) return cell; + pcell = &cell->next; + } + + if (rw.cellsCnt >= rw.maxCells) longjmp(rw.jmpBuf, 1); + + auto cell = rw.cells + rw.cellsCnt++; + cell->x = x; + cell->area = 0; + cell->cover = 0; + cell->next = *pcell; + *pcell = cell; + + return cell; +} + + +static void _recordCell(RleWorker& rw) +{ + if (rw.area | rw.cover) { + auto cell = _findCell(rw); + cell->area += rw.area; + cell->cover += rw.cover; + } +} + + +static void _setCell(RleWorker& rw, SwPoint pos) +{ + /* Move the cell pointer to a new position. We set the `invalid' */ + /* flag to indicate that the cell isn't part of those we're interested */ + /* in during the render phase. This means that: */ + /* */ + /* . the new vertical position must be within min_ey..max_ey-1. */ + /* . the new horizontal position must be strictly less than max_ex */ + /* */ + /* Note that if a cell is to the left of the clipping region, it is */ + /* actually set to the (min_ex-1) horizontal position. */ + + /* All cells that are on the left of the clipping region go to the + min_ex - 1 horizontal position. */ + pos.y -= rw.cellMin.y; + + if (pos.x > rw.cellMax.x) pos.x = rw.cellMax.x; + pos.x -= rw.cellMin.x; + if (pos.x < 0) pos.x = -1; + + //Are we moving to a different cell? + if (pos != rw.cellPos) { + //Record the current one if it is valid + if (!rw.invalid) _recordCell(rw); + } + + rw.area = 0; + rw.cover = 0; + rw.cellPos = pos; + rw.invalid = ((unsigned)pos.y >= (unsigned)rw.cellYCnt || pos.x >= rw.cellXCnt); +} + + +static void _startCell(RleWorker& rw, SwPoint pos) +{ + if (pos.x > rw.cellMax.x) pos.x = rw.cellMax.x; + if (pos.x < rw.cellMin.x) pos.x = rw.cellMin.x; + //if (pos.x < rw.cellMin.x) pos.x = (rw.cellMin.x - 1); + + rw.area = 0; + rw.cover = 0; + rw.cellPos = pos - rw.cellMin; + rw.invalid = false; + + _setCell(rw, pos); +} + + +static void _moveTo(RleWorker& rw, const SwPoint& to) +{ + //record current cell, if any */ + if (!rw.invalid) _recordCell(rw); + + //start to a new position + _startCell(rw, TRUNC(to)); + + rw.pos = to; +} + + +static void _lineTo(RleWorker& rw, const SwPoint& to) +{ +#define SW_UDIV(a, b) \ + static_cast(((unsigned long)(a) * (unsigned long)(b)) >> \ + (sizeof(long) * CHAR_BIT - PIXEL_BITS)) + + auto e1 = TRUNC(rw.pos); + auto e2 = TRUNC(to); + + //vertical clipping + if ((e1.y >= rw.cellMax.y && e2.y >= rw.cellMax.y) || + (e1.y < rw.cellMin.y && e2.y < rw.cellMin.y)) { + rw.pos = to; + return; + } + + auto diff = to - rw.pos; + auto f1 = rw.pos - SUBPIXELS(e1); + SwPoint f2; + + //inside one cell + if (e1 == e2) { + ; + //any horizontal line + } else if (diff.y == 0) { + e1.x = e2.x; + _setCell(rw, e1); + } else if (diff.x == 0) { + //vertical line up + if (diff.y > 0) { + do { + f2.y = ONE_PIXEL; + rw.cover += (f2.y - f1.y); + rw.area += (f2.y - f1.y) * f1.x * 2; + f1.y = 0; + ++e1.y; + _setCell(rw, e1); + } while(e1.y != e2.y); + //vertical line down + } else { + do { + f2.y = 0; + rw.cover += (f2.y - f1.y); + rw.area += (f2.y - f1.y) * f1.x * 2; + f1.y = ONE_PIXEL; + --e1.y; + _setCell(rw, e1); + } while(e1.y != e2.y); + } + //any other line + } else { + Area prod = diff.x * f1.y - diff.y * f1.x; + + /* These macros speed up repetitive divisions by replacing them + with multiplications and right shifts. */ + auto dx_r = static_cast(ULONG_MAX >> PIXEL_BITS) / (diff.x); + auto dy_r = static_cast(ULONG_MAX >> PIXEL_BITS) / (diff.y); + + /* The fundamental value `prod' determines which side and the */ + /* exact coordinate where the line exits current cell. It is */ + /* also easily updated when moving from one cell to the next. */ + do { + auto px = diff.x * ONE_PIXEL; + auto py = diff.y * ONE_PIXEL; + + //left + if (prod <= 0 && prod - px > 0) { + f2 = {0, SW_UDIV(-prod, -dx_r)}; + prod -= py; + rw.cover += (f2.y - f1.y); + rw.area += (f2.y - f1.y) * (f1.x + f2.x); + f1 = {ONE_PIXEL, f2.y}; + --e1.x; + //up + } else if (prod - px <= 0 && prod - px + py > 0) { + prod -= px; + f2 = {SW_UDIV(-prod, dy_r), ONE_PIXEL}; + rw.cover += (f2.y - f1.y); + rw.area += (f2.y - f1.y) * (f1.x + f2.x); + f1 = {f2.x, 0}; + ++e1.y; + //right + } else if (prod - px + py <= 0 && prod + py >= 0) { + prod += py; + f2 = {ONE_PIXEL, SW_UDIV(prod, dx_r)}; + rw.cover += (f2.y - f1.y); + rw.area += (f2.y - f1.y) * (f1.x + f2.x); + f1 = {0, f2.y}; + ++e1.x; + //down + } else { + f2 = {SW_UDIV(prod, -dy_r), 0}; + prod += px; + rw.cover += (f2.y - f1.y); + rw.area += (f2.y - f1.y) * (f1.x + f2.x); + f1 = {f2.x, ONE_PIXEL}; + --e1.y; + } + + _setCell(rw, e1); + + } while(e1 != e2); + } + + f2 = {to.x - SUBPIXELS(e2.x), to.y - SUBPIXELS(e2.y)}; + rw.cover += (f2.y - f1.y); + rw.area += (f2.y - f1.y) * (f1.x + f2.x); + rw.pos = to; +} + + +static void _cubicTo(RleWorker& rw, const SwPoint& ctrl1, const SwPoint& ctrl2, const SwPoint& to) +{ + auto arc = rw.bezStack; + arc[0] = to; + arc[1] = ctrl2; + arc[2] = ctrl1; + arc[3] = rw.pos; + + //Short-cut the arc that crosses the current band + auto min = arc[0].y; + auto max = arc[0].y; + + SwCoord y; + for (auto i = 1; i < 4; ++i) { + y = arc[i].y; + if (y < min) min = y; + if (y > max) max = y; + } + + if (TRUNC(min) >= rw.cellMax.y || TRUNC(max) < rw.cellMin.y) goto draw; + + /* Decide whether to split or draw. See `Rapid Termination */ + /* Evaluation for Recursive Subdivision of Bezier Curves' by Thomas */ + /* F. Hain, at */ + /* http://www.cis.southalabama.edu/~hain/general/Publications/Bezier/Camera-ready%20CISST02%202.pdf */ + while (true) { + { + //diff is the P0 - P3 chord vector + auto diff = arc[3] - arc[0]; + auto L = HYPOT(diff); + + //avoid possible arithmetic overflow below by splitting + if (L > SHRT_MAX) goto split; + + //max deviation may be as much as (s/L) * 3/4 (if Hain's v = 1) + auto sLimit = L * (ONE_PIXEL / 6); + + auto diff1 = arc[1] - arc[0]; + auto s = diff.y * diff1.x - diff.x * diff1.y; + if (s < 0) s = -s; + if (s > sLimit) goto split; + + //s is L * the perpendicular distance from P2 to the line P0 - P3 + auto diff2 = arc[2] - arc[0]; + s = diff.y * diff2.x - diff.x * diff2.y; + if (s < 0) s = -s; + if (s > sLimit) goto split; + + /* Split super curvy segments where the off points are so far + from the chord that the angles P0-P1-P3 or P0-P2-P3 become + acute as detected by appropriate dot products */ + if (diff1.x * (diff1.x - diff.x) + diff1.y * (diff1.y - diff.y) > 0 || + diff2.x * (diff2.x - diff.x) + diff2.y * (diff2.y - diff.y) > 0) + goto split; + + //no reason to split + goto draw; + } + split: + mathSplitCubic(arc); + arc += 3; + continue; + + draw: + _lineTo(rw, arc[0]); + if (arc == rw.bezStack) return; + arc -= 3; + } +} + + +static bool _decomposeOutline(RleWorker& rw) +{ + auto outline = rw.outline; + auto first = 0; //index of first point in contour + + for (uint32_t n = 0; n < outline->cntrsCnt; ++n) { + auto last = outline->cntrs[n]; + auto limit = outline->pts + last; + auto start = UPSCALE(outline->pts[first]); + auto pt = outline->pts + first; + auto types = outline->types + first; + + /* A contour cannot start with a cubic control point! */ + if (types[0] == SW_CURVE_TYPE_CUBIC) goto invalid_outline; + + _moveTo(rw, UPSCALE(outline->pts[first])); + + while (pt < limit) { + ++pt; + ++types; + + //emit a single line_to + if (types[0] == SW_CURVE_TYPE_POINT) { + _lineTo(rw, UPSCALE(*pt)); + //types cubic + } else { + if (pt + 1 > limit || types[1] != SW_CURVE_TYPE_CUBIC) + goto invalid_outline; + + pt += 2; + types += 2; + + if (pt <= limit) { + _cubicTo(rw, UPSCALE(pt[-2]), UPSCALE(pt[-1]), UPSCALE(pt[0])); + continue; + } + _cubicTo(rw, UPSCALE(pt[-2]), UPSCALE(pt[-1]), start); + goto close; + } + } + _lineTo(rw, start); + close: + first = last + 1; + } + + return true; + +invalid_outline: + //LOG: Invalid Outline! + return false; +} + + +static int _genRle(RleWorker& rw) +{ + if (setjmp(rw.jmpBuf) == 0) { + auto ret = _decomposeOutline(rw); + if (!rw.invalid) _recordCell(rw); + if (ret) return 0; //success + else return 1; //fail + } + return -1; //lack of cell memory +} + + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +SwRleData* rleRender(const SwOutline* outline, const SwBBox& bbox, const SwSize& clip, bool antiAlias) +{ + constexpr auto RENDER_POOL_SIZE = 16384L; + constexpr auto BAND_SIZE = 40; + + //TODO: We can preserve several static workers in advance + RleWorker rw; + Cell buffer[RENDER_POOL_SIZE / sizeof(Cell)]; + + //Init Cells + rw.buffer = buffer; + rw.bufferSize = sizeof(buffer); + rw.yCells = reinterpret_cast(buffer); + rw.cells = nullptr; + rw.maxCells = 0; + rw.cellsCnt = 0; + rw.area = 0; + rw.cover = 0; + rw.invalid = true; + rw.cellMin = bbox.min; + rw.cellMax = bbox.max; + rw.cellXCnt = rw.cellMax.x - rw.cellMin.x; + rw.cellYCnt = rw.cellMax.y - rw.cellMin.y; + rw.ySpan = 0; + rw.outline = const_cast(outline); + rw.bandSize = rw.bufferSize / (sizeof(Cell) * 8); //bandSize: 64 + rw.bandShoot = 0; + rw.clip = clip; + rw.antiAlias = antiAlias; + rw.rle = reinterpret_cast(calloc(1, sizeof(SwRleData))); + + //Generate RLE + Band bands[BAND_SIZE]; + Band* band; + + /* set up vertical bands */ + auto bandCnt = static_cast((rw.cellMax.y - rw.cellMin.y) / rw.bandSize); + if (bandCnt == 0) bandCnt = 1; + else if (bandCnt >= BAND_SIZE) bandCnt = (BAND_SIZE - 1); + + auto min = rw.cellMin.y; + auto yMax = rw.cellMax.y; + SwCoord max; + int ret; + + for (int n = 0; n < bandCnt; ++n, min = max) { + max = min + rw.bandSize; + if (n == bandCnt -1 || max > yMax) max = yMax; + + bands[0].min = min; + bands[0].max = max; + band = bands; + + while (band >= bands) { + rw.yCells = static_cast(rw.buffer); + rw.yCnt = band->max - band->min; + + int cellStart = sizeof(Cell*) * (int)rw.yCnt; + int cellMod = cellStart % sizeof(Cell); + + if (cellMod > 0) cellStart += sizeof(Cell) - cellMod; + + auto cellEnd = rw.bufferSize; + cellEnd -= cellEnd % sizeof(Cell); + + auto cellsMax = reinterpret_cast((char*)rw.buffer + cellEnd); + rw.cells = reinterpret_cast((char*)rw.buffer + cellStart); + + if (rw.cells >= cellsMax) goto reduce_bands; + + rw.maxCells = cellsMax - rw.cells; + if (rw.maxCells < 2) goto reduce_bands; + + for (int y = 0; y < rw.yCnt; ++y) + rw.yCells[y] = nullptr; + + rw.cellsCnt = 0; + rw.invalid = true; + rw.cellMin.y = band->min; + rw.cellMax.y = band->max; + rw.cellYCnt = band->max - band->min; + + ret = _genRle(rw); + if (ret == 0) { + _sweep(rw); + --band; + continue; + } else if (ret == 1) { + goto error; + } + + reduce_bands: + /* render pool overflow: we will reduce the render band by half */ + auto bottom = band->min; + auto top = band->max; + auto middle = bottom + ((top - bottom) >> 1); + + /* This is too complex for a single scanline; there must + be some problems */ + if (middle == bottom) goto error; + + if (bottom - top >= rw.bandSize) ++rw.bandShoot; + + band[1].min = bottom; + band[1].max = middle; + band[0].min = middle; + band[0].max = top; + ++band; + } + } + + if (rw.bandShoot > 8 && rw.bandSize > 16) + rw.bandSize = (rw.bandSize >> 1); + + return rw.rle; + +error: + free(rw.rle); + rw.rle = nullptr; + return nullptr; +} + + +void rleFree(SwRleData* rle) +{ + if (!rle) return; + if (rle->spans) free(rle->spans); + free(rle); +} \ No newline at end of file diff --git a/src/lib/sw_engine/tvgSwShape.cpp b/src/lib/sw_engine/tvgSwShape.cpp new file mode 100644 index 00000000..d0a8f96d --- /dev/null +++ b/src/lib/sw_engine/tvgSwShape.cpp @@ -0,0 +1,672 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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 "tvgSwCommon.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +struct Line +{ + Point pt1; + Point pt2; +}; + + +static SwPoint _transform(const Point* to, const Matrix* transform) +{ + if (!transform) return {TO_SWCOORD(to->x), TO_SWCOORD(to->y)}; + + auto tx = round(to->x * transform->e11 + to->y * transform->e12 + transform->e13); + auto ty = round(to->x * transform->e21 + to->y * transform->e22 + transform->e23); + + return {TO_SWCOORD(tx), TO_SWCOORD(ty)}; +} + + +static float _lineLength(const Point& pt1, const Point& pt2) +{ + /* approximate sqrt(x*x + y*y) using alpha max plus beta min algorithm. + With alpha = 1, beta = 3/8, giving results with the largest error less + than 7% compared to the exact value. */ + Point diff = {pt2.x - pt1.x, pt2.y - pt1.y}; + if (diff.x < 0) diff.x = -diff.x; + if (diff.y < 0) diff.y = -diff.y; + return (diff.x > diff.y) ? (diff.x + diff.y * 0.375f) : (diff.y + diff.x * 0.375f); +} + + +static void _lineSplitAt(const Line& cur, float at, Line& left, Line& right) +{ + auto len = _lineLength(cur.pt1, cur.pt2); + auto dx = ((cur.pt2.x - cur.pt1.x) / len) * at; + auto dy = ((cur.pt2.y - cur.pt1.y) / len) * at; + left.pt1 = cur.pt1; + left.pt2.x = left.pt1.x + dx; + left.pt2.y = left.pt1.y + dy; + right.pt1 = left.pt2; + right.pt2 = cur.pt2; +} + + +static void _growOutlineContour(SwOutline& outline, uint32_t n) +{ + if (outline.reservedCntrsCnt >= outline.cntrsCnt + n) return; + outline.reservedCntrsCnt = outline.cntrsCnt + n; + outline.cntrs = static_cast(realloc(outline.cntrs, outline.reservedCntrsCnt * sizeof(uint32_t))); +} + + +static void _growOutlinePoint(SwOutline& outline, uint32_t n) +{ + if (outline.reservedPtsCnt >= outline.ptsCnt + n) return; + outline.reservedPtsCnt = outline.ptsCnt + n; + outline.pts = static_cast(realloc(outline.pts, outline.reservedPtsCnt * sizeof(SwPoint))); + outline.types = static_cast(realloc(outline.types, outline.reservedPtsCnt * sizeof(uint8_t))); +} + + +static void _delOutline(SwOutline* outline) +{ + if (!outline) return; + + if (outline->cntrs) free(outline->cntrs); + if (outline->pts) free(outline->pts); + if (outline->types) free(outline->types); + free(outline); +} + + +static void _outlineEnd(SwOutline& outline) +{ + _growOutlineContour(outline, 1); + if (outline.ptsCnt > 0) { + outline.cntrs[outline.cntrsCnt] = outline.ptsCnt - 1; + ++outline.cntrsCnt; + } +} + + +static void _outlineMoveTo(SwOutline& outline, const Point* to, const Matrix* transform) +{ + _growOutlinePoint(outline, 1); + + outline.pts[outline.ptsCnt] = _transform(to, transform); + + outline.types[outline.ptsCnt] = SW_CURVE_TYPE_POINT; + + if (outline.ptsCnt > 0) { + _growOutlineContour(outline, 1); + outline.cntrs[outline.cntrsCnt] = outline.ptsCnt - 1; + ++outline.cntrsCnt; + } + + ++outline.ptsCnt; +} + + +static void _outlineLineTo(SwOutline& outline, const Point* to, const Matrix* transform) +{ + _growOutlinePoint(outline, 1); + + outline.pts[outline.ptsCnt] = _transform(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) +{ + _growOutlinePoint(outline, 3); + + outline.pts[outline.ptsCnt] = _transform(ctrl1, transform); + outline.types[outline.ptsCnt] = SW_CURVE_TYPE_CUBIC; + ++outline.ptsCnt; + + outline.pts[outline.ptsCnt] = _transform(ctrl2, transform); + outline.types[outline.ptsCnt] = SW_CURVE_TYPE_CUBIC; + ++outline.ptsCnt; + + outline.pts[outline.ptsCnt] = _transform(to, transform); + outline.types[outline.ptsCnt] = SW_CURVE_TYPE_POINT; + ++outline.ptsCnt; +} + + +static void _outlineClose(SwOutline& outline) +{ + uint32_t i = 0; + + if (outline.cntrsCnt > 0) { + i = outline.cntrs[outline.cntrsCnt - 1] + 1; + } else { + i = 0; //First Path + } + + //Make sure there is at least one point in the current path + if (outline.ptsCnt == i) { + outline.opened = true; + return; + } + + //Close the path + _growOutlinePoint(outline, 1); + + outline.pts[outline.ptsCnt] = outline.pts[i]; + outline.types[outline.ptsCnt] = SW_CURVE_TYPE_POINT; + ++outline.ptsCnt; + + outline.opened = false; +} + + +static void _initBBox(SwBBox& bbox) +{ + bbox.min.x = bbox.min.y = 0; + bbox.max.x = bbox.max.y = 0; +} + + +static bool _updateBBox(SwOutline* outline, SwBBox& bbox) +{ + if (!outline) return false; + + auto pt = outline->pts; + + if (outline->ptsCnt <= 0) { + _initBBox(bbox); + return false; + } + + auto xMin = pt->x; + auto xMax = pt->x; + auto yMin = pt->y; + auto yMax = pt->y; + + ++pt; + + for(uint32_t i = 1; i < outline->ptsCnt; ++i, ++pt) { + if (xMin > pt->x) xMin = pt->x; + if (xMax < pt->x) xMax = pt->x; + if (yMin > pt->y) yMin = pt->y; + if (yMax < pt->y) yMax = pt->y; + } + bbox.min.x = xMin >> 6; + bbox.max.x = (xMax + 63) >> 6; + bbox.min.y = yMin >> 6; + bbox.max.y = (yMax + 63) >> 6; + + if (xMax - xMin < 1 && yMax - yMin < 1) return false; + + return true; +} + + +static bool _checkValid(const SwOutline* outline, const SwBBox& bbox, const SwSize& clip) +{ + if (outline->ptsCnt == 0 || outline->cntrsCnt <= 0) return false; + + //Check boundary + if (bbox.min.x >= clip.w || bbox.min.y >= clip.h || bbox.max.x <= 0 || bbox.max.y <= 0) return false; + + return true; +} + + +static void _dashLineTo(SwDashStroke& dash, const Point* to, const Matrix* transform) +{ + _growOutlinePoint(*dash.outline, dash.outline->ptsCnt >> 1); + _growOutlineContour(*dash.outline, dash.outline->cntrsCnt >> 1); + + Line cur = {dash.ptCur, *to}; + auto len = _lineLength(cur.pt1, cur.pt2); + + if (len < dash.curLen) { + dash.curLen -= len; + if (!dash.curOpGap) { + _outlineMoveTo(*dash.outline, &dash.ptCur, transform); + _outlineLineTo(*dash.outline, to, transform); + } + } else { + while (len > dash.curLen) { + len -= dash.curLen; + Line left, right; + _lineSplitAt(cur, dash.curLen, left, right);; + dash.curIdx = (dash.curIdx + 1) % dash.cnt; + if (!dash.curOpGap) { + _outlineMoveTo(*dash.outline, &left.pt1, transform); + _outlineLineTo(*dash.outline, &left.pt2, transform); + } + dash.curLen = dash.pattern[dash.curIdx]; + dash.curOpGap = !dash.curOpGap; + cur = right; + dash.ptCur = cur.pt1; + } + //leftovers + dash.curLen -= len; + if (!dash.curOpGap) { + _outlineMoveTo(*dash.outline, &cur.pt1, transform); + _outlineLineTo(*dash.outline, &cur.pt2, transform); + } + if (dash.curLen < 1) { + //move to next dash + dash.curIdx = (dash.curIdx + 1) % dash.cnt; + dash.curLen = dash.pattern[dash.curIdx]; + dash.curOpGap = !dash.curOpGap; + } + } + dash.ptCur = *to; +} + + +static void _dashCubicTo(SwDashStroke& dash, const Point* ctrl1, const Point* ctrl2, const Point* to, const Matrix* transform) +{ + _growOutlinePoint(*dash.outline, dash.outline->ptsCnt >> 1); + _growOutlineContour(*dash.outline, dash.outline->cntrsCnt >> 1); + + Bezier cur = { dash.ptCur, *ctrl1, *ctrl2, *to}; + auto len = bezLength(cur); + + if (len < dash.curLen) { + dash.curLen -= len; + if (!dash.curOpGap) { + _outlineMoveTo(*dash.outline, &dash.ptCur, transform); + _outlineCubicTo(*dash.outline, ctrl1, ctrl2, to, transform); + } + } else { + while (len > dash.curLen) { + Bezier left, right; + len -= dash.curLen; + bezSplitAt(cur, dash.curLen, left, right); + dash.curIdx = (dash.curIdx + 1) % dash.cnt; + if (!dash.curOpGap) { + _outlineMoveTo(*dash.outline, &left.start, transform); + _outlineCubicTo(*dash.outline, &left.ctrl1, &left.ctrl2, &left.end, transform); + } + dash.curLen = dash.pattern[dash.curIdx]; + dash.curOpGap = !dash.curOpGap; + cur = right; + dash.ptCur = right.start; + } + //leftovers + dash.curLen -= len; + if (!dash.curOpGap) { + _outlineMoveTo(*dash.outline, &cur.start, transform); + _outlineCubicTo(*dash.outline, &cur.ctrl1, &cur.ctrl2, &cur.end, transform); + } + if (dash.curLen < 1) { + //move to next dash + dash.curIdx = (dash.curIdx + 1) % dash.cnt; + dash.curLen = dash.pattern[dash.curIdx]; + dash.curOpGap = !dash.curOpGap; + } + } + dash.ptCur = *to; +} + + +SwOutline* _genDashOutline(const Shape* sdata, const Matrix* transform) +{ + const PathCommand* cmds = nullptr; + auto cmdCnt = sdata->pathCommands(&cmds); + + const Point* pts = nullptr; + auto ptsCnt = sdata->pathCoords(&pts); + + //No actual shape data + if (cmdCnt == 0 || ptsCnt == 0) return nullptr; + + SwDashStroke dash; + dash.curIdx = 0; + dash.curLen = 0; + dash.ptStart = {0, 0}; + dash.ptCur = {0, 0}; + dash.curOpGap = false; + + const float* pattern; + dash.cnt = sdata->strokeDash(&pattern); + + //Is it safe to mutual exclusive? + dash.pattern = const_cast(pattern); + dash.outline = static_cast(calloc(1, sizeof(SwOutline))); + dash.outline->opened = true; + + //smart reservation + auto outlinePtsCnt = 0; + auto outlineCntrsCnt = 0; + + for (uint32_t i = 0; i < cmdCnt; ++i) { + switch(*(cmds + i)) { + case PathCommand::Close: { + ++outlinePtsCnt; + break; + } + case PathCommand::MoveTo: { + ++outlineCntrsCnt; + ++outlinePtsCnt; + break; + } + case PathCommand::LineTo: { + ++outlinePtsCnt; + break; + } + case PathCommand::CubicTo: { + outlinePtsCnt += 3; + break; + } + } + } + + ++outlinePtsCnt; //for close + ++outlineCntrsCnt; //for end + + //Reserve Approximitely 20x... + _growOutlinePoint(*dash.outline, outlinePtsCnt * 20); + _growOutlineContour(*dash.outline, outlineCntrsCnt * 20); + + while (cmdCnt-- > 0) { + switch(*cmds) { + case PathCommand::Close: { + _dashLineTo(dash, &dash.ptStart, transform); + break; + } + case PathCommand::MoveTo: { + //reset the dash + dash.curIdx = 0; + dash.curLen = *dash.pattern; + dash.curOpGap = false; + dash.ptStart = dash.ptCur = *pts; + ++pts; + break; + } + case PathCommand::LineTo: { + _dashLineTo(dash, pts, transform); + ++pts; + break; + } + case PathCommand::CubicTo: { + _dashCubicTo(dash, pts, pts + 1, pts + 2, transform); + pts += 3; + break; + } + } + ++cmds; + } + + _outlineEnd(*dash.outline); + + return dash.outline; +} + + +bool _fastTrack(const SwOutline* outline) +{ + //Fast Track: Othogonal rectangle? + if (outline->ptsCnt != 5) return false; + + auto pt1 = outline->pts + 0; + auto pt2 = outline->pts + 1; + auto pt3 = outline->pts + 2; + auto pt4 = outline->pts + 3; + + auto min1 = pt1->y < pt3->y ? pt1 : pt3; + auto min2 = pt2->y < pt4->y ? pt2 : pt4; + if (min1->y != min2->y) return false; + + SwCoord len1 = pow(pt1->x - pt3->x, 2) + pow(pt1->y - pt3->y, 2); + SwCoord len2 = pow(pt2->x - pt4->x, 2) + pow(pt2->y - pt4->y, 2); + if (len1 == len2) return true; + + return false; +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +bool shapePrepare(SwShape* shape, const Shape* sdata, const SwSize& clip, const Matrix* transform) +{ + if (!shapeGenOutline(shape, sdata, transform)) return false; + + if (!_updateBBox(shape->outline, shape->bbox)) return false; + + if (!_checkValid(shape->outline, shape->bbox, clip)) return false; + + return true; +} + + +bool shapeGenRle(SwShape* shape, TVG_UNUSED const Shape* sdata, const SwSize& clip, bool antiAlias) +{ + //FIXME: Should we draw it? + //Case: Stroke Line + //if (shape.outline->opened) return true; + + //Case A: Fast Track Rectangle Drawing + if ((shape->rect = _fastTrack(shape->outline))) return true; + //Case B: Normale Shape RLE Drawing + if ((shape->rle = rleRender(shape->outline, shape->bbox, clip, antiAlias))) return true; + + return false; +} + + +void shapeDelOutline(SwShape* shape) +{ + auto outline = shape->outline; + _delOutline(outline); + shape->outline = nullptr; +} + + +void shapeReset(SwShape* shape) +{ + shapeDelOutline(shape); + rleFree(shape->rle); + shape->rle = nullptr; + shape->rect = false; + _initBBox(shape->bbox); +} + + +bool shapeGenOutline(SwShape* shape, const Shape* sdata, const Matrix* transform) +{ + const PathCommand* cmds = nullptr; + auto cmdCnt = sdata->pathCommands(&cmds); + + const Point* pts = nullptr; + auto ptsCnt = sdata->pathCoords(&pts); + + //No actual shape data + if (cmdCnt == 0 || ptsCnt == 0) return false; + + //smart reservation + auto outlinePtsCnt = 0; + auto outlineCntrsCnt = 0; + + for (uint32_t i = 0; i < cmdCnt; ++i) { + switch(*(cmds + i)) { + case PathCommand::Close: { + ++outlinePtsCnt; + break; + } + case PathCommand::MoveTo: { + ++outlineCntrsCnt; + ++outlinePtsCnt; + break; + } + case PathCommand::LineTo: { + ++outlinePtsCnt; + break; + } + case PathCommand::CubicTo: { + outlinePtsCnt += 3; + break; + } + } + } + + ++outlinePtsCnt; //for close + ++outlineCntrsCnt; //for end + + auto outline = shape->outline; + if (!outline) outline = static_cast(calloc(1, sizeof(SwOutline))); + outline->opened = true; + + _growOutlinePoint(*outline, outlinePtsCnt); + _growOutlineContour(*outline, outlineCntrsCnt); + + auto closed = false; + + //Generate Outlines + while (cmdCnt-- > 0) { + switch(*cmds) { + case PathCommand::Close: { + _outlineClose(*outline); + closed = true; + break; + } + case PathCommand::MoveTo: { + _outlineMoveTo(*outline, pts, transform); + ++pts; + break; + } + case PathCommand::LineTo: { + _outlineLineTo(*outline, pts, transform); + ++pts; + break; + } + case PathCommand::CubicTo: { + _outlineCubicTo(*outline, pts, pts + 1, pts + 2, transform); + pts += 3; + break; + } + } + ++cmds; + } + + _outlineEnd(*outline); + + if (closed) outline->opened = false; + + //FIXME: + //outline->flags = SwOutline::FillRule::Winding; + + shape->outline = outline; + + return true; +} + + +void shapeFree(SwShape* shape) +{ + shapeDelOutline(shape); + rleFree(shape->rle); + shapeDelFill(shape); + + if (shape->stroke) { + rleFree(shape->strokeRle); + strokeFree(shape->stroke); + } +} + + +void shapeDelStroke(SwShape* shape) +{ + if (!shape->stroke) return; + rleFree(shape->strokeRle); + shape->strokeRle = nullptr; + strokeFree(shape->stroke); + shape->stroke = nullptr; +} + + +void shapeResetStroke(SwShape* shape, const Shape* sdata, const Matrix* transform) +{ + if (!shape->stroke) shape->stroke = static_cast(calloc(1, sizeof(SwStroke))); + auto stroke = shape->stroke; + if (!stroke) return; + + strokeReset(stroke, sdata, transform); + + rleFree(shape->strokeRle); + shape->strokeRle = nullptr; +} + + +bool shapeGenStrokeRle(SwShape* shape, const Shape* sdata, const Matrix* transform, const SwSize& clip) +{ + SwOutline* shapeOutline = nullptr; + + //Dash Style Stroke + if (sdata->strokeDash(nullptr) > 0) { + shapeOutline = _genDashOutline(sdata, transform); + if (!shapeOutline) return false; + //Normal Style stroke + } else { + if (!shape->outline) { + if (!shapeGenOutline(shape, sdata, transform)) return false; + } + shapeOutline = shape->outline; + } + + if (!strokeParseOutline(shape->stroke, *shapeOutline)) return false; + + auto strokeOutline = strokeExportOutline(shape->stroke); + if (!strokeOutline) return false; + + SwBBox bbox; + _updateBBox(strokeOutline, bbox); + + if (!_checkValid(strokeOutline, bbox, clip)) return false; + + shape->strokeRle = rleRender(strokeOutline, bbox, clip, true); + + _delOutline(strokeOutline); + + return true; +} + + +bool shapeGenFillColors(SwShape* shape, const Fill* fill, const Matrix* transform, SwSurface* surface, bool ctable) +{ + return fillGenColorTable(shape->fill, fill, transform, surface, ctable); +} + + +void shapeResetFill(SwShape* shape) +{ + if (!shape->fill) { + shape->fill = static_cast(calloc(1, sizeof(SwFill))); + if (!shape->fill) return; + } + fillReset(shape->fill); +} + + +void shapeDelFill(SwShape* shape) +{ + if (!shape->fill) return; + fillFree(shape->fill); + shape->fill = nullptr; +} \ No newline at end of file diff --git a/src/lib/sw_engine/tvgSwStroke.cpp b/src/lib/sw_engine/tvgSwStroke.cpp new file mode 100644 index 00000000..08754f84 --- /dev/null +++ b/src/lib/sw_engine/tvgSwStroke.cpp @@ -0,0 +1,924 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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 "tvgSwCommon.h" + + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ +static constexpr auto SW_STROKE_TAG_POINT = 1; +static constexpr auto SW_STROKE_TAG_CUBIC = 2; +static constexpr auto SW_STROKE_TAG_BEGIN = 4; +static constexpr auto SW_STROKE_TAG_END = 8; + +static inline SwFixed SIDE_TO_ROTATE(const int32_t s) +{ + return (SW_ANGLE_PI2 - static_cast(s) * SW_ANGLE_PI); +} + + +static inline void SCALE(SwStroke& stroke, SwPoint& pt) +{ + pt.x *= stroke.sx; + pt.y *= stroke.sy; +} + + +static void _growBorder(SwStrokeBorder* border, uint32_t newPts) +{ + auto maxOld = border->maxPts; + auto maxNew = border->ptsCnt + newPts; + + if (maxNew <= maxOld) return; + + auto maxCur = maxOld; + + while (maxCur < maxNew) + maxCur += (maxCur >> 1) + 16; + + border->pts = static_cast(realloc(border->pts, maxCur * sizeof(SwPoint))); + border->tags = static_cast(realloc(border->tags, maxCur * sizeof(uint8_t))); + border->maxPts = maxCur; +} + + +static void _borderClose(SwStrokeBorder* border, bool reverse) +{ + auto start = border->start; + auto count = border->ptsCnt; + + //Don't record empty paths! + if (count <= start + 1U) { + border->ptsCnt = start; + } else { + /* Copy the last point to the start of this sub-path, + since it contains the adjusted starting coordinates */ + border->ptsCnt = --count; + border->pts[start] = border->pts[count]; + + if (reverse) { + //reverse the points + auto pt1 = border->pts + start + 1; + auto pt2 = border->pts + count - 1; + + while (pt1 < pt2) { + auto tmp = *pt1; + *pt1 = *pt2; + *pt2 = tmp; + ++pt1; + --pt2; + } + + //reverse the tags + auto tag1 = border->tags + start + 1; + auto tag2 = border->tags + count - 1; + + while (tag1 < tag2) { + auto tmp = *tag1; + *tag1 = *tag2; + *tag2 = tmp; + ++tag1; + --tag2; + } + } + + border->tags[start] |= SW_STROKE_TAG_BEGIN; + border->tags[count - 1] |= SW_STROKE_TAG_END; + } + + border->start = -1; + border->movable = false; +} + + +static void _borderCubicTo(SwStrokeBorder* border, SwPoint& ctrl1, SwPoint& ctrl2, SwPoint& to) +{ + _growBorder(border, 3); + + auto pt = border->pts + border->ptsCnt; + auto tag = border->tags + border->ptsCnt; + + pt[0] = ctrl1; + pt[1] = ctrl2; + pt[2] = to; + + tag[0] = SW_STROKE_TAG_CUBIC; + tag[1] = SW_STROKE_TAG_CUBIC; + tag[2] = SW_STROKE_TAG_POINT; + + border->ptsCnt += 3; + + border->movable = false; +} + + +static void _borderArcTo(SwStrokeBorder* border, SwPoint& center, SwFixed radius, SwFixed angleStart, SwFixed angleDiff, SwStroke& stroke) +{ + constexpr SwFixed ARC_CUBIC_ANGLE = SW_ANGLE_PI / 2; + SwPoint a = {static_cast(radius), 0}; + mathRotate(a, angleStart); + SCALE(stroke, a); + a += center; + + auto total = angleDiff; + auto angle = angleStart; + auto rotate = (angleDiff >= 0) ? SW_ANGLE_PI2 : -SW_ANGLE_PI2; + + while (total != 0) { + auto step = total; + if (step > ARC_CUBIC_ANGLE) step = ARC_CUBIC_ANGLE; + else if (step < -ARC_CUBIC_ANGLE) step = -ARC_CUBIC_ANGLE; + + auto next = angle + step; + auto theta = step; + if (theta < 0) theta = -theta; + + theta >>= 1; + + //compute end point + SwPoint b = {static_cast(radius), 0}; + mathRotate(b, next); + SCALE(stroke, b); + b += center; + + //compute first and second control points + auto length = mathMulDiv(radius, mathSin(theta) * 4, (0x10000L + mathCos(theta)) * 3); + + SwPoint a2 = {static_cast(length), 0}; + mathRotate(a2, angle + rotate); + SCALE(stroke, a2); + a2 += a; + + SwPoint b2 = {static_cast(length), 0}; + mathRotate(b2, next - rotate); + SCALE(stroke, b2); + b2 += b; + + //add cubic arc + _borderCubicTo(border, a2, b2, b); + + //process the rest of the arc? + a = b; + total -= step; + angle = next; + } +} + + +static void _borderLineTo(SwStrokeBorder* border, SwPoint& to, bool movable) +{ + if (border->movable) { + //move last point + border->pts[border->ptsCnt - 1] = to; + } else { + + //don't add zero-length line_to + if (border->ptsCnt > 0 && (border->pts[border->ptsCnt - 1] - to).small()) return; + + _growBorder(border, 1); + border->pts[border->ptsCnt] = to; + border->tags[border->ptsCnt] = SW_STROKE_TAG_POINT; + border->ptsCnt += 1; + } + + border->movable = movable; +} + + +static void _borderMoveTo(SwStrokeBorder* border, SwPoint& to) +{ + //close current open path if any? + if (border->start >= 0) _borderClose(border, false); + + border->start = border->ptsCnt; + border->movable = false; + + _borderLineTo(border, to, false); +} + + +static void _arcTo(SwStroke& stroke, int32_t side) +{ + auto border = stroke.borders + side; + auto rotate = SIDE_TO_ROTATE(side); + auto total = mathDiff(stroke.angleIn, stroke.angleOut); + if (total == SW_ANGLE_PI) total = -rotate * 2; + + _borderArcTo(border, stroke.center, stroke.width, stroke.angleIn + rotate, total, stroke); + border->movable = false; +} + + +static void _outside(SwStroke& stroke, int32_t side, SwFixed lineLength) +{ + constexpr SwFixed MITER_LIMIT = 4 * (1 << 16); + + auto border = stroke.borders + side; + + if (stroke.join == StrokeJoin::Round) { + _arcTo(stroke, side); + } else { + //this is a mitered (pointed) or beveled (truncated) corner + auto rotate = SIDE_TO_ROTATE(side); + auto bevel = (stroke.join == StrokeJoin::Bevel) ? true : false; + SwFixed phi = 0; + SwFixed thcos = 0; + + if (!bevel) { + auto theta = mathDiff(stroke.angleIn, stroke.angleOut); + if (theta == SW_ANGLE_PI) { + theta = rotate; + phi = stroke.angleIn; + } else { + theta /= 2; + phi = stroke.angleIn + theta + rotate; + } + + thcos = mathCos(theta); + auto sigma = mathMultiply(MITER_LIMIT, thcos); + + //is miter limit exceeded? + if (sigma < 0x10000L) bevel = true; + } + + //this is a bevel (broken angle) + if (bevel) { + SwPoint delta = {static_cast(stroke.width), 0}; + mathRotate(delta, stroke.angleOut + rotate); + SCALE(stroke, delta); + delta += stroke.center; + border->movable = false; + _borderLineTo(border, delta, false); + //this is a miter (intersection) + } else { + auto length = mathDivide(stroke.width, thcos); + SwPoint delta = {static_cast(length), 0}; + mathRotate(delta, phi); + SCALE(stroke, delta); + delta += stroke.center; + _borderLineTo(border, delta, false); + + /* Now add and end point + Only needed if not lineto (lineLength is zero for curves) */ + if (lineLength == 0) { + delta = {static_cast(stroke.width), 0}; + mathRotate(delta, stroke.angleOut + rotate); + SCALE(stroke, delta); + delta += stroke.center; + _borderLineTo(border, delta, false); + } + } + } +} + + +static void _inside(SwStroke& stroke, int32_t side, SwFixed lineLength) +{ + auto border = stroke.borders + side; + auto theta = mathDiff(stroke.angleIn, stroke.angleOut) / 2; + SwPoint delta; + bool intersect = false; + + /* Only intersect borders if between two line_to's and both + lines are long enough (line length is zero fur curves). */ + if (border->movable && lineLength > 0) { + //compute minimum required length of lines + SwFixed minLength = abs(mathMultiply(stroke.width, mathTan(theta))); + if (stroke.lineLength >= minLength && lineLength >= minLength) intersect = true; + } + + auto rotate = SIDE_TO_ROTATE(side); + + if (!intersect) { + delta = {static_cast(stroke.width), 0}; + mathRotate(delta, stroke.angleOut + rotate); + SCALE(stroke, delta); + delta += stroke.center; + border->movable = false; + } else { + //compute median angle + auto phi = stroke.angleIn + theta; + auto thcos = mathCos(theta); + delta = {static_cast(mathDivide(stroke.width, thcos)), 0}; + mathRotate(delta, phi + rotate); + SCALE(stroke, delta); + delta += stroke.center; + } + + _borderLineTo(border, delta, false); +} + + +void _processCorner(SwStroke& stroke, SwFixed lineLength) +{ + auto turn = mathDiff(stroke.angleIn, stroke.angleOut); + + //no specific corner processing is required if the turn is 0 + if (turn == 0) return; + + //when we turn to the right, the inside side is 0 + int32_t inside = 0; + + //otherwise, the inside is 1 + if (turn < 0) inside = 1; + + //process the inside + _inside(stroke, inside, lineLength); + + //process the outside + _outside(stroke, 1 - inside, lineLength); +} + + +void _firstSubPath(SwStroke& stroke, SwFixed startAngle, SwFixed lineLength) +{ + SwPoint delta = {static_cast(stroke.width), 0}; + mathRotate(delta, startAngle + SW_ANGLE_PI2); + SCALE(stroke, delta); + + auto pt = stroke.center + delta; + auto border = stroke.borders; + _borderMoveTo(border, pt); + + pt = stroke.center - delta; + ++border; + _borderMoveTo(border, pt); + + /* Save angle, position and line length for last join + lineLength is zero for curves */ + stroke.subPathAngle = startAngle; + stroke.firstPt = false; + stroke.subPathLineLength = lineLength; +} + + +static void _lineTo(SwStroke& stroke, const SwPoint& to) +{ + auto delta = to - stroke.center; + + //a zero-length lineto is a no-op; avoid creating a spurious corner + if (delta.zero()) return; + + //compute length of line + auto lineLength = mathLength(delta); + auto angle = mathAtan(delta); + + delta = {static_cast(stroke.width), 0}; + mathRotate(delta, angle + SW_ANGLE_PI2); + SCALE(stroke, delta); + + //process corner if necessary + if (stroke.firstPt) { + /* This is the first segment of a subpath. We need to add a point to each border + at their respective starting point locations. */ + _firstSubPath(stroke, angle, lineLength); + } else { + //process the current corner + stroke.angleOut = angle; + _processCorner(stroke, lineLength); + } + + //now add a line segment to both the inside and outside paths + auto border = stroke.borders; + auto side = 1; + + while (side >= 0) { + auto pt = to + delta; + + //the ends of lineto borders are movable + _borderLineTo(border, pt, true); + + delta.x = -delta.x; + delta.y = -delta.y; + + --side; + ++border; + } + + stroke.angleIn = angle; + stroke.center = to; + stroke.lineLength = lineLength; +} + + +static void _cubicTo(SwStroke& stroke, const SwPoint& ctrl1, const SwPoint& ctrl2, const SwPoint& to) +{ + /* if all control points are coincident, this is a no-op; + avoid creating a spurious corner */ + if ((stroke.center - ctrl1).small() && (ctrl1 - ctrl2).small() && (ctrl2 - to).small()) { + stroke.center = to; + return; + } + + SwPoint bezStack[37]; //TODO: static? + auto limit = bezStack + 32; + auto arc = bezStack; + auto firstArc = true; + arc[0] = to; + arc[1] = ctrl2; + arc[2] = ctrl1; + arc[3] = stroke.center; + + while (arc >= bezStack) { + SwFixed angleIn, angleOut, angleMid; + + //initialize with current direction + angleIn = angleOut = angleMid = stroke.angleIn; + + if (arc < limit && !mathSmallCubic(arc, angleIn, angleMid, angleOut)) { + if (stroke.firstPt) stroke.angleIn = angleIn; + mathSplitCubic(arc); + arc += 3; + continue; + } + + if (firstArc) { + firstArc = false; + //process corner if necessary + if (stroke.firstPt) { + _firstSubPath(stroke, angleIn, 0); + } else { + stroke.angleOut = angleIn; + _processCorner(stroke, 0); + } + } else if (abs(mathDiff(stroke.angleIn, angleIn)) > (SW_ANGLE_PI / 8) / 4) { + //if the deviation from one arc to the next is too great add a round corner + stroke.center = arc[3]; + stroke.angleOut = angleIn; + stroke.join = StrokeJoin::Round; + + _processCorner(stroke, 0); + + //reinstate line join style + stroke.join = stroke.joinSaved; + } + + //the arc's angle is small enough; we can add it directly to each border + auto theta1 = mathDiff(angleIn, angleMid) / 2; + auto theta2 = mathDiff(angleMid, angleOut) / 2; + auto phi1 = mathMean(angleIn, angleMid); + auto phi2 = mathMean(angleMid, angleOut); + auto length1 = mathDivide(stroke.width, mathCos(theta1)); + auto length2 = mathDivide(stroke.width, mathCos(theta2)); + SwFixed alpha0 = 0; + + //compute direction of original arc + if (stroke.handleWideStrokes) { + alpha0 = mathAtan(arc[0] - arc[3]); + } + + auto border = stroke.borders; + int32_t side = 0; + + while (side <= 1) + { + auto rotate = SIDE_TO_ROTATE(side); + + //compute control points + SwPoint _ctrl1 = {static_cast(length1), 0}; + mathRotate(_ctrl1, phi1 + rotate); + SCALE(stroke, _ctrl1); + _ctrl1 += arc[2]; + + SwPoint _ctrl2 = {static_cast(length2), 0}; + mathRotate(_ctrl2, phi2 + rotate); + SCALE(stroke, _ctrl2); + _ctrl2 += arc[1]; + + //compute end point + SwPoint _end = {static_cast(stroke.width), 0}; + mathRotate(_end, angleOut + rotate); + SCALE(stroke, _end); + _end += arc[0]; + + if (stroke.handleWideStrokes) { + + /* determine whether the border radius is greater than the radius of + curvature of the original arc */ + auto _start = border->pts[border->ptsCnt - 1]; + auto alpha1 = mathAtan(_end - _start); + + //is the direction of the border arc opposite to that of the original arc? + if (abs(mathDiff(alpha0, alpha1)) > SW_ANGLE_PI / 2) { + + //use the sine rule to find the intersection point + auto beta = mathAtan(arc[3] - _start); + auto gamma = mathAtan(arc[0] - _end); + auto bvec = _end - _start; + auto blen = mathLength(bvec); + auto sinA = abs(mathSin(alpha1 - gamma)); + auto sinB = abs(mathSin(beta - gamma)); + auto alen = mathMulDiv(blen, sinA, sinB); + + SwPoint delta = {static_cast(alen), 0}; + mathRotate(delta, beta); + delta += _start; + + //circumnavigate the negative sector backwards + border->movable = false; + _borderLineTo(border, delta, false); + _borderLineTo(border, _end, false); + _borderCubicTo(border, _ctrl2, _ctrl1, _start); + + //and then move to the endpoint + _borderLineTo(border, _end, false); + + ++side; + ++border; + continue; + } + + //else fall through + } + _borderCubicTo(border, _ctrl1, _ctrl2, _end); + ++side; + ++border; + } + arc -= 3; + stroke.angleIn = angleOut; + } + stroke.center = to; +} + + +static void _addCap(SwStroke& stroke, SwFixed angle, int32_t side) +{ + if (stroke.cap == StrokeCap::Square) { + auto rotate = SIDE_TO_ROTATE(side); + auto border = stroke.borders + side; + + SwPoint delta = {static_cast(stroke.width), 0}; + mathRotate(delta, angle); + SCALE(stroke, delta); + + SwPoint delta2 = {static_cast(stroke.width), 0}; + mathRotate(delta2, angle + rotate); + SCALE(stroke, delta2); + delta += stroke.center + delta2; + + _borderLineTo(border, delta, false); + + delta = {static_cast(stroke.width), 0}; + mathRotate(delta, angle); + SCALE(stroke, delta); + + delta2 = {static_cast(stroke.width), 0}; + mathRotate(delta2, angle - rotate); + SCALE(stroke, delta2); + delta += delta2 + stroke.center; + + _borderLineTo(border, delta, false); + + } else if (stroke.cap == StrokeCap::Round) { + + stroke.angleIn = angle; + stroke.angleOut = angle + SW_ANGLE_PI; + _arcTo(stroke, side); + return; + + } else { //Butt + auto rotate = SIDE_TO_ROTATE(side); + auto border = stroke.borders + side; + + SwPoint delta = {static_cast(stroke.width), 0}; + mathRotate(delta, angle + rotate); + SCALE(stroke, delta); + delta += stroke.center; + + _borderLineTo(border, delta, false); + + delta = {static_cast(stroke.width), 0}; + mathRotate(delta, angle - rotate); + SCALE(stroke, delta); + delta += stroke.center; + + _borderLineTo(border, delta, false); + } +} + + +static void _addReverseLeft(SwStroke& stroke, bool opened) +{ + auto right = stroke.borders + 0; + auto left = stroke.borders + 1; + auto newPts = left->ptsCnt - left->start; + + if (newPts <= 0) return; + + _growBorder(right, newPts); + + auto dstPt = right->pts + right->ptsCnt; + auto dstTag = right->tags + right->ptsCnt; + auto srcPt = left->pts + left->ptsCnt - 1; + auto srcTag = left->tags + left->ptsCnt - 1; + + while (srcPt >= left->pts + left->start) { + *dstPt = *srcPt; + *dstTag = *srcTag; + + if (opened) { + dstTag[0] &= ~(SW_STROKE_TAG_BEGIN | SW_STROKE_TAG_END); + } else { + //switch begin/end tags if necessary + auto ttag = dstTag[0] & (SW_STROKE_TAG_BEGIN | 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); + } + + --srcPt; + --srcTag; + ++dstPt; + ++dstTag; + } + + left->ptsCnt = left->start; + right->ptsCnt += newPts; + right->movable = false; + left->movable = false; +} + + +static void _beginSubPath(SwStroke& stroke, SwPoint& to, bool opened) +{ + /* We cannot process the first point because there is not enough + information regarding its corner/cap. Later, it will be processed + in the _endSubPath() */ + + stroke.firstPt = true; + stroke.center = to; + stroke.openSubPath = opened; + + /* Determine if we need to check whether the border radius is greater + than the radius of curvature of a curve, to handle this case specially. + This is only required if bevel joins or butt caps may be created because + round & miter joins and round & square caps cover the nagative sector + created with wide strokes. */ + if ((stroke.join != StrokeJoin::Round) || (stroke.openSubPath && stroke.cap == StrokeCap::Butt)) + stroke.handleWideStrokes = true; + else + stroke.handleWideStrokes = false; + + stroke.ptStartSubPath = to; + stroke.angleIn = 0; +} + + +static void _endSubPath(SwStroke& stroke) +{ + if (stroke.openSubPath) { + auto right = stroke.borders; + + /* all right, this is an opened path, we need to add a cap between + right & left, add the reverse of left, then add a final cap + between left & right */ + _addCap(stroke, stroke.angleIn, 0); + + //add reversed points from 'left' to 'right' + _addReverseLeft(stroke, true); + + //now add the final cap + stroke.center = stroke.ptStartSubPath; + _addCap(stroke, stroke.subPathAngle + SW_ANGLE_PI, 0); + + /* now end the right subpath accordingly. The left one is rewind + and deosn't need further processing */ + _borderClose(right, false); + } else { + + //close the path if needed + if (stroke.center != stroke.ptStartSubPath) + _lineTo(stroke, stroke.ptStartSubPath); + + //process the corner + stroke.angleOut = stroke.subPathAngle; + auto turn = mathDiff(stroke.angleIn, stroke.angleOut); + + //No specific corner processing is required if the turn is 0 + if (turn != 0) { + + //when we turn to the right, the inside is 0 + int32_t inside = 0; + + //otherwise, the inside is 1 + if (turn < 0) inside = 1; + + _inside(stroke, inside, stroke.subPathLineLength); //inside + _outside(stroke, 1 - inside, stroke.subPathLineLength); //outside + } + + _borderClose(stroke.borders + 0, false); + _borderClose(stroke.borders + 1, true); + } +} + + +static void _getCounts(SwStrokeBorder* border, uint32_t& ptsCnt, uint32_t& cntrsCnt) +{ + auto count = border->ptsCnt; + auto tags = border->tags; + uint32_t _ptsCnt = 0; + uint32_t _cntrsCnt = 0; + bool inCntr = false; + + while (count > 0) { + + if (tags[0] & SW_STROKE_TAG_BEGIN) { + if (inCntr) goto fail; + inCntr = true; + } else if (!inCntr) goto fail; + + if (tags[0] & SW_STROKE_TAG_END) { + inCntr = false; + ++_cntrsCnt; + } + --count; + ++_ptsCnt; + ++tags; + } + + if (inCntr) goto fail; + border->valid = true; + ptsCnt = _ptsCnt; + cntrsCnt = _cntrsCnt; + + return; + +fail: + ptsCnt = 0; + cntrsCnt = 0; +} + + +static void _exportBorderOutline(SwStroke& stroke, SwOutline* outline, uint32_t side) +{ + auto border = stroke.borders + side; + + if (!border->valid) return; + + memcpy(outline->pts + outline->ptsCnt, border->pts, border->ptsCnt * sizeof(SwPoint)); + + auto cnt = border->ptsCnt; + auto src = border->tags; + auto tags = outline->types + outline->ptsCnt; + auto cntrs = outline->cntrs + outline->cntrsCnt; + uint16_t idx = outline->ptsCnt; + + while (cnt > 0) { + + if (*src & SW_STROKE_TAG_POINT) *tags = SW_CURVE_TYPE_POINT; + else if (*src & SW_STROKE_TAG_CUBIC) *tags = SW_CURVE_TYPE_CUBIC; + else { + //LOG: What type of stroke outline?? + } + + if (*src & SW_STROKE_TAG_END) { + *cntrs = idx; + ++cntrs; + ++outline->cntrsCnt; + } + ++src; + ++tags; + ++idx; + --cnt; + } + outline->ptsCnt = outline->ptsCnt + border->ptsCnt; +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +void strokeFree(SwStroke* stroke) +{ + if (!stroke) return; + + //free borders + if (stroke->borders[0].pts) free(stroke->borders[0].pts); + if (stroke->borders[0].tags) free(stroke->borders[0].tags); + if (stroke->borders[1].pts) free(stroke->borders[1].pts); + if (stroke->borders[1].tags) free(stroke->borders[1].tags); + + free(stroke); +} + + +void strokeReset(SwStroke* stroke, const Shape* sdata, const Matrix* transform) +{ + if (transform) { + stroke->sx = sqrt(pow(transform->e11, 2) + pow(transform->e21, 2)); + stroke->sy = sqrt(pow(transform->e12, 2) + pow(transform->e22, 2)); + } else { + stroke->sx = stroke->sy = 1.0f; + } + + stroke->width = TO_SWCOORD(sdata->strokeWidth() * 0.5); + stroke->cap = sdata->strokeCap(); + + //Save line join: it can be temporarily changed when stroking curves... + stroke->joinSaved = stroke->join = sdata->strokeJoin(); + + stroke->borders[0].ptsCnt = 0; + stroke->borders[0].start = -1; + stroke->borders[0].valid = false; + + stroke->borders[1].ptsCnt = 0; + stroke->borders[1].start = -1; + stroke->borders[1].valid = false; +} + + +bool strokeParseOutline(SwStroke* stroke, const SwOutline& outline) +{ + uint32_t first = 0; + + for (uint32_t i = 0; i < outline.cntrsCnt; ++i) { + auto last = outline.cntrs[i]; //index of last point in contour + auto limit = outline.pts + last; + + //Skip empty points + if (last <= first) { + first = last + 1; + continue; + } + + auto start = outline.pts[first]; + auto pt = outline.pts + first; + auto types = outline.types + first; + auto type = types[0]; + + //A contour cannot start with a cubic control point + if (type == SW_CURVE_TYPE_CUBIC) return false; + + _beginSubPath(*stroke, start, outline.opened); + + while (pt < limit) { + ++pt; + ++types; + + //emit a signel line_to + if (types[0] == SW_CURVE_TYPE_POINT) { + _lineTo(*stroke, *pt); + //types cubic + } else { + if (pt + 1 > limit || types[1] != SW_CURVE_TYPE_CUBIC) return false; + + pt += 2; + types += 2; + + if (pt <= limit) { + _cubicTo(*stroke, pt[-2], pt[-1], pt[0]); + continue; + } + _cubicTo(*stroke, pt[-2], pt[-1], start); + goto close; + } + } + + close: + if (!stroke->firstPt) _endSubPath(*stroke); + first = last + 1; + } + return true; +} + + +SwOutline* strokeExportOutline(SwStroke* stroke) +{ + uint32_t count1, count2, count3, count4; + + _getCounts(stroke->borders + 0, count1, count2); + _getCounts(stroke->borders + 1, count3, count4); + + auto ptsCnt = count1 + count3; + auto cntrsCnt = count2 + count4; + + auto outline = static_cast(calloc(1, sizeof(SwOutline))); + outline->pts = static_cast(malloc(sizeof(SwPoint) * ptsCnt)); + outline->types = static_cast(malloc(sizeof(uint8_t) * ptsCnt)); + outline->cntrs = static_cast(malloc(sizeof(uint32_t) * cntrsCnt)); + + _exportBorderOutline(*stroke, outline, 0); //left + _exportBorderOutline(*stroke, outline, 1); //right + + return outline; +} \ No newline at end of file diff --git a/src/lib/tvgBezier.cpp b/src/lib/tvgBezier.cpp new file mode 100644 index 00000000..db4d58be --- /dev/null +++ b/src/lib/tvgBezier.cpp @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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 "tvgCommon.h" + + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +static float _lineLength(const Point& pt1, const Point& pt2) +{ + /* approximate sqrt(x*x + y*y) using alpha max plus beta min algorithm. + With alpha = 1, beta = 3/8, giving results with the largest error less + than 7% compared to the exact value. */ + Point diff = {pt2.x - pt1.x, pt2.y - pt1.y}; + if (diff.x < 0) diff.x = -diff.x; + if (diff.y < 0) diff.y = -diff.y; + return (diff.x > diff.y) ? (diff.x + diff.y * 0.375f) : (diff.y + diff.x * 0.375f); +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +namespace tvg +{ + +void bezSplit(const Bezier&cur, Bezier& left, Bezier& right) +{ + auto c = (cur.ctrl1.x + cur.ctrl2.x) * 0.5f; + left.ctrl1.x = (cur.start.x + cur.ctrl1.x) * 0.5f; + right.ctrl2.x = (cur.ctrl2.x + cur.end.x) * 0.5f; + left.start.x = cur.start.x; + right.end.x = cur.end.x; + left.ctrl2.x = (left.ctrl1.x + c) * 0.5f; + right.ctrl1.x = (right.ctrl2.x + c) * 0.5f; + left.end.x = right.start.x = (left.ctrl2.x + right.ctrl1.x) * 0.5f; + + c = (cur.ctrl1.y + cur.ctrl2.y) * 0.5f; + left.ctrl1.y = (cur.start.y + cur.ctrl1.y) * 0.5f; + right.ctrl2.y = (cur.ctrl2.y + cur.end.y) * 0.5f; + left.start.y = cur.start.y; + right.end.y = cur.end.y; + left.ctrl2.y = (left.ctrl1.y + c) * 0.5f; + right.ctrl1.y = (right.ctrl2.y + c) * 0.5f; + left.end.y = right.start.y = (left.ctrl2.y + right.ctrl1.y) * 0.5f; +} + + +float bezLength(const Bezier& cur) +{ + Bezier left, right; + auto len = _lineLength(cur.start, cur.ctrl1) + _lineLength(cur.ctrl1, cur.ctrl2) + _lineLength(cur.ctrl2, cur.end); + auto chord = _lineLength(cur.start, cur.end); + + if (fabs(len - chord) > FLT_EPSILON) { + bezSplit(cur, left, right); + return bezLength(left) + bezLength(right); + } + return len; +} + + +void bezSplitLeft(Bezier& cur, float at, Bezier& left) +{ + left.start = cur.start; + + left.ctrl1.x = cur.start.x + at * (cur.ctrl1.x - cur.start.x); + left.ctrl1.y = cur.start.y + at * (cur.ctrl1.y - cur.start.y); + + left.ctrl2.x = cur.ctrl1.x + at * (cur.ctrl2.x - cur.ctrl1.x); // temporary holding spot + left.ctrl2.y = cur.ctrl1.y + at * (cur.ctrl2.y - cur.ctrl1.y); // temporary holding spot + + cur.ctrl2.x = cur.ctrl2.x + at * (cur.end.x - cur.ctrl2.x); + cur.ctrl2.y = cur.ctrl2.y + at * (cur.end.y - cur.ctrl2.y); + + cur.ctrl1.x = left.ctrl2.x + at * (cur.ctrl2.x - left.ctrl2.x); + cur.ctrl1.y = left.ctrl2.y + at * (cur.ctrl2.y - left.ctrl2.y); + + left.ctrl2.x = left.ctrl1.x + at * (left.ctrl2.x - left.ctrl1.x); + left.ctrl2.y = left.ctrl1.y + at * (left.ctrl2.y - left.ctrl1.y); + + left.end.x = cur.start.x = left.ctrl2.x + at * (cur.ctrl1.x - left.ctrl2.x); + left.end.y = cur.start.y = left.ctrl2.y + at * (cur.ctrl1.y - left.ctrl2.y); +} + + +float bezAt(const Bezier& bz, float at) +{ + auto len = bezLength(bz); + auto biggest = 1.0f; + + if (at >= len) return 1.0f; + + at *= 0.5f; + + while (true) { + auto right = bz; + Bezier left; + bezSplitLeft(right, at, left); + auto len2 = bezLength(left); + + if (fabs(len2 - len) < FLT_EPSILON) break; + + if (len2 < len) { + at += (biggest - at) * 0.5f; + } else { + biggest = at; + at -= (at * 0.5f); + } + } + return at; +} + + +void bezSplitAt(const Bezier& cur, float at, Bezier& left, Bezier& right) +{ + right = cur; + auto t = bezAt(right, at); + bezSplitLeft(right, t, left); +} + +} \ No newline at end of file diff --git a/src/lib/tvgBezier.h b/src/lib/tvgBezier.h new file mode 100644 index 00000000..df21719d --- /dev/null +++ b/src/lib/tvgBezier.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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_BEZIER_H_ +#define _TVG_BEZIER_H_ + +namespace tvg +{ + +struct Bezier +{ + Point start; + Point ctrl1; + Point ctrl2; + Point end; +}; + +void bezSplit(const Bezier&cur, Bezier& left, Bezier& right); +float bezLength(const Bezier& cur); +void bezSplitLeft(Bezier& cur, float at, Bezier& left); +float bezAt(const Bezier& bz, float at); +void bezSplitAt(const Bezier& cur, float at, Bezier& left, Bezier& right); + +} + +#endif //_TVG_BEZIER_H_ \ No newline at end of file diff --git a/src/lib/tvgCanvas.cpp b/src/lib/tvgCanvas.cpp new file mode 100644 index 00000000..957940ca --- /dev/null +++ b/src/lib/tvgCanvas.cpp @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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 "tvgCommon.h" +#include "tvgCanvasImpl.h" + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +Canvas::Canvas(RenderMethod *pRenderer):pImpl(make_unique(pRenderer)) +{ +} + + +Canvas::~Canvas() +{ +} + + +Result Canvas::reserve(uint32_t n) noexcept +{ + IMPL->paints.reserve(n); + return Result::Success; +} + + +Result Canvas::push(unique_ptr paint) noexcept +{ + return IMPL->push(move(paint)); +} + + +Result Canvas::clear() noexcept +{ + return IMPL->clear(); +} + + +Result Canvas::draw() noexcept +{ + return IMPL->draw(); +} + + +Result Canvas::update(Paint* paint) noexcept +{ + return IMPL->update(paint); +} + + +Result Canvas::sync() noexcept +{ + if (IMPL->renderer->flush()) return Result::Success; + + return Result::InsufficientCondition; +} \ No newline at end of file diff --git a/src/lib/tvgCanvasImpl.h b/src/lib/tvgCanvasImpl.h new file mode 100644 index 00000000..aeab1f54 --- /dev/null +++ b/src/lib/tvgCanvasImpl.h @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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_CANVAS_IMPL_H_ +#define _TVG_CANVAS_IMPL_H_ + +#include "tvgCommon.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +struct Canvas::Impl +{ + vector paints; + RenderMethod* renderer; + + Impl(RenderMethod* pRenderer):renderer(pRenderer) + { + renderer->ref(); + } + + ~Impl() + { + clear(); + renderer->unref(); + } + + Result push(unique_ptr paint) + { + auto p = paint.release(); + if (!p) return Result::MemoryCorruption; + paints.push_back(p); + + return update(p); + } + + Result clear() + { + if (!renderer) return Result::InsufficientCondition; + + //Clear render target before drawing + if (!renderer->clear()) return Result::InsufficientCondition; + + for (auto paint : paints) { + paint->IMPL->dispose(*renderer); + delete(paint); + } + paints.clear(); + + return Result::Success; + } + + Result update(Paint* paint) + { + if (!renderer) return Result::InsufficientCondition; + + //Update single paint node + if (paint) { + if (!paint->IMPL->update(*renderer, nullptr, RenderUpdateFlag::None)) { + return Result::InsufficientCondition; + } + //Update retained all paint nodes + } else { + for(auto paint: paints) { + if (!paint->IMPL->update(*renderer, nullptr, RenderUpdateFlag::None)) { + return Result::InsufficientCondition; + } + } + } + return Result::Success; + } + + Result draw() + { + if (!renderer) return Result::InsufficientCondition; + + if (!renderer->preRender()) return Result::InsufficientCondition; + + for(auto paint: paints) { + if(!paint->IMPL->render(*renderer)) return Result::InsufficientCondition; + } + + if (!renderer->postRender()) return Result::InsufficientCondition; + + return Result::Success; + } +}; + +#endif /* _TVG_CANVAS_IMPL_H_ */ \ No newline at end of file diff --git a/src/lib/tvgCommon.h b/src/lib/tvgCommon.h new file mode 100644 index 00000000..c07a4cab --- /dev/null +++ b/src/lib/tvgCommon.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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_COMMON_H_ +#define _TVG_COMMON_H_ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "thorvg.h" + +using namespace std; +using namespace tvg; + +#define IMPL pImpl.get() + +#define FILL_ID_LINEAR 0 +#define FILL_ID_RADIAL 1 + +#define TVG_UNUSED __attribute__ ((__unused__)) + +#include "tvgBezier.h" +#include "tvgLoader.h" +#include "tvgLoaderMgr.h" +#include "tvgRender.h" +#include "tvgPaint.h" +#include "tvgTaskScheduler.h" + +#endif //_TVG_COMMON_H_ diff --git a/src/lib/tvgFill.cpp b/src/lib/tvgFill.cpp new file mode 100644 index 00000000..cc1a13bf --- /dev/null +++ b/src/lib/tvgFill.cpp @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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 "tvgCommon.h" + + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +struct Fill::Impl +{ + ColorStop* colorStops = nullptr; + uint32_t cnt = 0; + FillSpread spread; + + ~Impl() + { + if (colorStops) free(colorStops); + } +}; + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +Fill::Fill():pImpl(make_unique()) +{ +} + + +Fill::~Fill() +{ +} + + +Result Fill::colorStops(const ColorStop* colorStops, uint32_t cnt) noexcept +{ + auto impl = pImpl.get(); + + if (cnt == 0) { + if (impl->colorStops) { + free(impl->colorStops); + impl->colorStops = nullptr; + impl->cnt = cnt; + } + return Result::Success; + } + + if (impl->cnt != cnt) { + impl->colorStops = static_cast(realloc(impl->colorStops, cnt * sizeof(ColorStop))); + } + + impl->cnt = cnt; + memcpy(impl->colorStops, colorStops, cnt * sizeof(ColorStop)); + + return Result::Success; +} + + +uint32_t Fill::colorStops(const ColorStop** colorStops) const noexcept +{ + if (colorStops) *colorStops = IMPL->colorStops; + + return IMPL->cnt; +} + + +Result Fill::spread(FillSpread s) noexcept +{ + IMPL->spread = s; + + return Result::Success; +} + + +FillSpread Fill::spread() const noexcept +{ + return IMPL->spread; +} \ No newline at end of file diff --git a/src/lib/tvgGlCanvas.cpp b/src/lib/tvgGlCanvas.cpp new file mode 100644 index 00000000..7f267a44 --- /dev/null +++ b/src/lib/tvgGlCanvas.cpp @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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 "tvgCommon.h" +#include "tvgCanvasImpl.h" + +#ifdef THORVG_GL_RASTER_SUPPORT + #include "tvgGlRenderer.h" +#else + class GlRenderer : public RenderMethod + { + //Non Supported. Dummy Class */ + }; +#endif + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +struct GlCanvas::Impl +{ + Impl() {} +}; + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +#ifdef THORVG_GL_RASTER_SUPPORT +GlCanvas::GlCanvas() : Canvas(GlRenderer::inst()), pImpl(make_unique()) +#else +GlCanvas::GlCanvas() : Canvas(nullptr), pImpl(make_unique()) +#endif +{ +} + + + +GlCanvas::~GlCanvas() +{ +} + + +Result GlCanvas::target(uint32_t* buffer, uint32_t stride, uint32_t w, uint32_t h) noexcept +{ +#ifdef THORVG_GL_RASTER_SUPPORT + //We know renderer type, avoid dynamic_cast for performance. + auto renderer = static_cast(Canvas::pImpl.get()->renderer); + if (!renderer) return Result::MemoryCorruption; + + if (!renderer->target(buffer, stride, w, h, 0)) return Result::Unknown; + + return Result::Success; +#endif + return Result::NonSupport; +} + + +unique_ptr GlCanvas::gen() noexcept +{ +#ifdef THORVG_GL_RASTER_SUPPORT + return unique_ptr(new GlCanvas); +#endif + return nullptr; +} \ No newline at end of file diff --git a/src/lib/tvgInitializer.cpp b/src/lib/tvgInitializer.cpp new file mode 100644 index 00000000..7dcc8b91 --- /dev/null +++ b/src/lib/tvgInitializer.cpp @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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 "tvgCommon.h" +#include "tvgLoaderMgr.h" + +#ifdef THORVG_SW_RASTER_SUPPORT + #include "tvgSwRenderer.h" +#endif + +#ifdef THORVG_GL_RASTER_SUPPORT + #include "tvgGlRenderer.h" +#endif + + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ +static bool initialized = false; + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +Result Initializer::init(CanvasEngine engine, uint32_t threads) noexcept +{ + if (initialized) return Result::InsufficientCondition; + + auto nonSupport = true; + + if (static_cast(engine) & static_cast(CanvasEngine::Sw)) { + #ifdef THORVG_SW_RASTER_SUPPORT + if (!SwRenderer::init()) return Result::InsufficientCondition; + nonSupport = false; + #endif + } else if (static_cast(engine) & static_cast(CanvasEngine::Gl)) { + #ifdef THORVG_GL_RASTER_SUPPORT + if (!GlRenderer::init()) return Result::InsufficientCondition; + nonSupport = false; + #endif + } else { + return Result::InvalidArguments; + } + + if (nonSupport) return Result::NonSupport; + + if (!LoaderMgr::init()) return Result::Unknown; + + TaskScheduler::init(threads); + + initialized = true; + + return Result::Success; +} + + +Result Initializer::term(CanvasEngine engine) noexcept +{ + if (!initialized) return Result::InsufficientCondition; + + auto nonSupport = true; + + if (static_cast(engine) & static_cast(CanvasEngine::Sw)) { + #ifdef THORVG_SW_RASTER_SUPPORT + if (!SwRenderer::term()) return Result::InsufficientCondition; + nonSupport = false; + #endif + } else if (static_cast(engine) & static_cast(CanvasEngine::Gl)) { + #ifdef THORVG_GL_RASTER_SUPPORT + if (!GlRenderer::term()) return Result::InsufficientCondition; + nonSupport = false; + #endif + } else { + return Result::InvalidArguments; + } + + if (nonSupport) return Result::NonSupport; + + TaskScheduler::term(); + + if (!LoaderMgr::term()) return Result::Unknown; + + initialized = false; + + return Result::Success; +} \ No newline at end of file diff --git a/src/lib/tvgLinearGradient.cpp b/src/lib/tvgLinearGradient.cpp new file mode 100644 index 00000000..a76239e3 --- /dev/null +++ b/src/lib/tvgLinearGradient.cpp @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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 "tvgCommon.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +struct LinearGradient::Impl +{ + float x1, y1, x2, y2; +}; + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +LinearGradient::LinearGradient():pImpl(make_unique()) +{ + _id = FILL_ID_LINEAR; +} + + +LinearGradient::~LinearGradient() +{ +} + + +Result LinearGradient::linear(float x1, float y1, float x2, float y2) noexcept +{ + if (fabsf(x2 - x1) < FLT_EPSILON && fabsf(y2 - y1) < FLT_EPSILON) + return Result::InvalidArguments; + + IMPL->x1 = x1; + IMPL->y1 = y1; + IMPL->x2 = x2; + IMPL->y2 = y2; + + return Result::Success; +} + + +Result LinearGradient::linear(float* x1, float* y1, float* x2, float* y2) const noexcept +{ + if (x1) *x1 = IMPL->x1; + if (x2) *x2 = IMPL->x2; + if (y1) *y1 = IMPL->y1; + if (y2) *y2 = IMPL->y2; + + return Result::Success; +} + + +unique_ptr LinearGradient::gen() noexcept +{ + return unique_ptr(new LinearGradient); +} \ No newline at end of file diff --git a/src/lib/tvgLoader.h b/src/lib/tvgLoader.h new file mode 100644 index 00000000..9c7279eb --- /dev/null +++ b/src/lib/tvgLoader.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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_LOADER_H_ +#define _TVG_LOADER_H_ + +namespace tvg +{ + +class Loader +{ +public: + //default view box, if any. + float vx = 0; + float vy = 0; + float vw = 0; + float vh = 0; + + virtual ~Loader() {} + + virtual bool open(const char* path) = 0; + virtual bool open(const char* data, uint32_t size) = 0; + virtual bool read() = 0; + virtual bool close() = 0; + virtual unique_ptr data() = 0; +}; + +} + +#endif //_TVG_LOADER_H_ \ No newline at end of file diff --git a/src/lib/tvgLoaderMgr.cpp b/src/lib/tvgLoaderMgr.cpp new file mode 100644 index 00000000..6ec32132 --- /dev/null +++ b/src/lib/tvgLoaderMgr.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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 "tvgCommon.h" + +#ifdef THORVG_SVG_LOADER_SUPPORT + #include "tvgSvgLoader.h" +#endif + +static int initCnt = 0; + +bool LoaderMgr::init() +{ + if (initCnt > 0) return true; + ++initCnt; + + //TODO: + + return true; +} + +bool LoaderMgr::term() +{ + --initCnt; + if (initCnt > 0) return true; + + //TODO: + + return true; +} + +unique_ptr LoaderMgr::loader() +{ +#ifdef THORVG_SVG_LOADER_SUPPORT + return unique_ptr(new SvgLoader); +#endif + return nullptr; +} \ No newline at end of file diff --git a/src/lib/tvgLoaderMgr.h b/src/lib/tvgLoaderMgr.h new file mode 100644 index 00000000..95a06767 --- /dev/null +++ b/src/lib/tvgLoaderMgr.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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_LOADER_MGR_H_ +#define _TVG_LOADER_MGR_H_ + +struct LoaderMgr +{ + static bool init(); + static bool term(); + static unique_ptr loader(); +}; + +#endif //_TVG_LOADER_MGR_H_ \ No newline at end of file diff --git a/src/lib/tvgPaint.cpp b/src/lib/tvgPaint.cpp new file mode 100644 index 00000000..3105e279 --- /dev/null +++ b/src/lib/tvgPaint.cpp @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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 "tvgCommon.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +Paint :: Paint() : pImpl(make_unique()) +{ +} + + +Paint :: ~Paint() +{ +} + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +Result Paint::rotate(float degree) noexcept +{ + if (IMPL->rotate(degree)) return Result::Success; + return Result::FailedAllocation; +} + + +Result Paint::scale(float factor) noexcept +{ + if (IMPL->scale(factor)) return Result::Success; + return Result::FailedAllocation; +} + + +Result Paint::translate(float x, float y) noexcept +{ + if (IMPL->translate(x, y)) return Result::Success; + return Result::FailedAllocation; +} + + +Result Paint::transform(const Matrix& m) noexcept +{ + if (IMPL->transform(m)) return Result::Success; + return Result::FailedAllocation; +} + + +Result Paint::bounds(float* x, float* y, float* w, float* h) const noexcept +{ + if (IMPL->bounds(x, y, w, h)) return Result::Success; + return Result::InsufficientCondition; +} \ No newline at end of file diff --git a/src/lib/tvgPaint.h b/src/lib/tvgPaint.h new file mode 100644 index 00000000..f331fefd --- /dev/null +++ b/src/lib/tvgPaint.h @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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_PAINT_H_ +#define _TVG_PAINT_H_ + +namespace tvg +{ + struct StrategyMethod + { + virtual ~StrategyMethod(){} + + virtual bool dispose(RenderMethod& renderer) = 0; + virtual bool update(RenderMethod& renderer, const RenderTransform* transform, RenderUpdateFlag pFlag) = 0; + virtual bool render(RenderMethod& renderer) = 0; + virtual bool bounds(float* x, float* y, float* w, float* h) const = 0; + }; + + struct Paint::Impl + { + StrategyMethod* smethod = nullptr; + RenderTransform *rTransform = nullptr; + uint32_t flag = RenderUpdateFlag::None; + + ~Impl() { + if (smethod) delete(smethod); + if (rTransform) delete(rTransform); + } + + void method(StrategyMethod* method) + { + smethod = method; + } + + bool rotate(float degree) + { + if (rTransform) { + if (fabsf(degree - rTransform->degree) <= FLT_EPSILON) return true; + } else { + if (fabsf(degree) <= FLT_EPSILON) return true; + rTransform = new RenderTransform(); + if (!rTransform) return false; + } + rTransform->degree = degree; + if (!rTransform->overriding) flag |= RenderUpdateFlag::Transform; + + return true; + } + + bool scale(float factor) + { + if (rTransform) { + if (fabsf(factor - rTransform->scale) <= FLT_EPSILON) return true; + } else { + if (fabsf(factor) <= FLT_EPSILON) return true; + rTransform = new RenderTransform(); + if (!rTransform) return false; + } + rTransform->scale = factor; + if (!rTransform->overriding) flag |= RenderUpdateFlag::Transform; + + return true; + } + + bool translate(float x, float y) + { + if (rTransform) { + if (fabsf(x - rTransform->x) <= FLT_EPSILON && fabsf(y - rTransform->y) <= FLT_EPSILON) return true; + } else { + if (fabsf(x) <= FLT_EPSILON && fabsf(y) <= FLT_EPSILON) return true; + rTransform = new RenderTransform(); + if (!rTransform) return false; + } + rTransform->x = x; + rTransform->y = y; + if (!rTransform->overriding) flag |= RenderUpdateFlag::Transform; + + return true; + } + + bool transform(const Matrix& m) + { + if (!rTransform) { + rTransform = new RenderTransform(); + if (!rTransform) return false; + } + rTransform->override(m); + flag |= RenderUpdateFlag::Transform; + + return true; + } + + bool bounds(float* x, float* y, float* w, float* h) const + { + return smethod->bounds(x, y, w, h); + } + + bool dispose(RenderMethod& renderer) + { + return smethod->dispose(renderer); + } + + bool update(RenderMethod& renderer, const RenderTransform* pTransform, uint32_t pFlag) + { + if (flag & RenderUpdateFlag::Transform) { + if (!rTransform) return false; + if (!rTransform->update()) { + delete(rTransform); + rTransform = nullptr; + } + } + + auto newFlag = static_cast(pFlag | flag); + flag = RenderUpdateFlag::None; + + if (rTransform && pTransform) { + RenderTransform outTransform(pTransform, rTransform); + return smethod->update(renderer, &outTransform, newFlag); + } else { + auto outTransform = pTransform ? pTransform : rTransform; + return smethod->update(renderer, outTransform, newFlag); + } + } + + bool render(RenderMethod& renderer) + { + return smethod->render(renderer); + } + }; + + + template + struct PaintMethod : StrategyMethod + { + T* inst = nullptr; + + PaintMethod(T* _inst) : inst(_inst) {} + ~PaintMethod(){} + + bool bounds(float* x, float* y, float* w, float* h) const override + { + return inst->bounds(x, y, w, h); + } + + bool dispose(RenderMethod& renderer) + { + return inst->dispose(renderer); + } + + bool update(RenderMethod& renderer, const RenderTransform* transform, RenderUpdateFlag flag) + { + return inst->update(renderer, transform, flag); + } + + bool render(RenderMethod& renderer) + { + return inst->render(renderer); + } + }; +} + +#endif //_TVG_PAINT_H_ \ No newline at end of file diff --git a/src/lib/tvgPicture.cpp b/src/lib/tvgPicture.cpp new file mode 100644 index 00000000..7c20a372 --- /dev/null +++ b/src/lib/tvgPicture.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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 "tvgPictureImpl.h" + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +Picture::Picture() : pImpl(make_unique()) +{ + Paint::IMPL->method(new PaintMethod(IMPL)); +} + + +Picture::~Picture() +{ +} + + +unique_ptr Picture::gen() noexcept +{ + return unique_ptr(new Picture); +} + + +Result Picture::load(const std::string& path) noexcept +{ + if (path.empty()) return Result::InvalidArguments; + + return IMPL->load(path); +} + + +Result Picture::load(const char* data, uint32_t size) noexcept +{ + if (!data || size <= 0) return Result::InvalidArguments; + + return IMPL->load(data, size); +} + + +Result Picture::viewbox(float* x, float* y, float* w, float* h) const noexcept +{ + if (IMPL->viewbox(x, y, w, h)) return Result::Success; + return Result::InsufficientCondition; +} \ No newline at end of file diff --git a/src/lib/tvgPictureImpl.h b/src/lib/tvgPictureImpl.h new file mode 100644 index 00000000..d430de49 --- /dev/null +++ b/src/lib/tvgPictureImpl.h @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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_PICTURE_IMPL_H_ +#define _TVG_PICTURE_IMPL_H_ + +#include "tvgCommon.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +struct Picture::Impl +{ + unique_ptr loader = nullptr; + Paint* paint = nullptr; + + bool dispose(RenderMethod& renderer) + { + if (!paint) return false; + + paint->IMPL->dispose(renderer); + delete(paint); + + return true; + } + + bool update(RenderMethod &renderer, const RenderTransform* transform, RenderUpdateFlag flag) + { + if (loader) { + auto scene = loader->data(); + if (scene) { + this->paint = scene.release(); + if (!this->paint) return false; + loader->close(); + } + } + + if (!paint) return false; + + return paint->IMPL->update(renderer, transform, flag); + } + + bool render(RenderMethod &renderer) + { + if (!paint) return false; + return paint->IMPL->render(renderer); + } + + bool viewbox(float* x, float* y, float* w, float* h) + { + if (!loader) return false; + if (x) *x = loader->vx; + if (y) *y = loader->vy; + if (w) *w = loader->vw; + if (h) *h = loader->vh; + return true; + } + + bool bounds(float* x, float* y, float* w, float* h) + { + if (!paint) return false; + return paint->IMPL->bounds(x, y, w, h); + } + + Result load(const string& path) + { + if (loader) loader->close(); + loader = LoaderMgr::loader(); + if (!loader || !loader->open(path.c_str())) { + //LOG: Non supported format + return Result::NonSupport; + } + if (!loader->read()) return Result::Unknown; + return Result::Success; + } + + Result load(const char* data, uint32_t size) + { + if (loader) loader->close(); + loader = LoaderMgr::loader(); + if (!loader || !loader->open(data, size)) { + //LOG: Non supported load data + return Result::NonSupport; + } + if (!loader->read()) return Result::Unknown; + return Result::Success; + } +}; + +#endif //_TVG_PICTURE_IMPL_H_ \ No newline at end of file diff --git a/src/lib/tvgRadialGradient.cpp b/src/lib/tvgRadialGradient.cpp new file mode 100644 index 00000000..92e934ee --- /dev/null +++ b/src/lib/tvgRadialGradient.cpp @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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 "tvgCommon.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +struct RadialGradient::Impl +{ + float cx, cy, radius; +}; + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +RadialGradient::RadialGradient():pImpl(make_unique()) +{ + _id = FILL_ID_RADIAL; +} + + +RadialGradient::~RadialGradient() +{ +} + + +Result RadialGradient::radial(float cx, float cy, float radius) noexcept +{ + if (radius < FLT_EPSILON) return Result::InvalidArguments; + + IMPL->cx = cx; + IMPL->cy = cy; + IMPL->radius = radius; + + return Result::Success; +} + + +Result RadialGradient::radial(float* cx, float* cy, float* radius) const noexcept +{ + if (cx) *cx = IMPL->cx; + if (cy) *cy = IMPL->cy; + if (radius) *radius = IMPL->radius; + + return Result::Success; +} + + +unique_ptr RadialGradient::gen() noexcept +{ + return unique_ptr(new RadialGradient); +} \ No newline at end of file diff --git a/src/lib/tvgRender.cpp b/src/lib/tvgRender.cpp new file mode 100644 index 00000000..10a23e8c --- /dev/null +++ b/src/lib/tvgRender.cpp @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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 "tvgCommon.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +void RenderTransform::override(const Matrix& m) +{ + this->m = m; + + if (m.e11 == 0.0f && m.e12 == 0.0f && m.e13 == 0.0f && + m.e21 == 0.0f && m.e22 == 0.0f && m.e23 == 0.0f && + m.e31 == 0.0f && m.e32 == 0.0f && m.e33 == 0.0f) { + overriding = false; + } else overriding = true; +} + + +bool RenderTransform::update() +{ + constexpr auto PI = 3.141592f; + + if (overriding) return true; + + //Init Status + if (fabsf(x) <= FLT_EPSILON && fabsf(y) <= FLT_EPSILON && + fabsf(degree) <= FLT_EPSILON && fabsf(scale - 1) <= FLT_EPSILON) { + return false; + } + + //identity + m.e11 = 1.0f; + m.e12 = 0.0f; + m.e13 = 0.0f; + m.e21 = 0.0f; + m.e22 = 1.0f; + m.e23 = 0.0f; + m.e31 = 0.0f; + m.e32 = 0.0f; + m.e33 = 1.0f; + + //scale + m.e11 *= scale; + m.e22 *= scale; + + //rotation + if (fabsf(degree) > FLT_EPSILON) { + auto radian = degree / 180.0f * PI; + auto cosVal = cosf(radian); + auto sinVal = sinf(radian); + + auto t11 = m.e11 * cosVal + m.e12 * sinVal; + auto t12 = m.e11 * -sinVal + m.e12 * cosVal; + auto t21 = m.e21 * cosVal + m.e22 * sinVal; + auto t22 = m.e21 * -sinVal + m.e22 * cosVal; + auto t13 = m.e13 * cosVal + m.e23 * sinVal; + auto t23 = m.e13 * -sinVal + m.e23 * cosVal; + + m.e11 = t11; + m.e12 = t12; + m.e21 = t21; + m.e22 = t22; + m.e13 = t13; + m.e23 = t23; + } + + m.e13 += x; + m.e23 += y; + + return true; +} + + +RenderTransform::RenderTransform() +{ +} + + +RenderTransform::RenderTransform(const RenderTransform* lhs, const RenderTransform* rhs) +{ + m.e11 = lhs->m.e11 * rhs->m.e11 + lhs->m.e12 * rhs->m.e21 + lhs->m.e13 * rhs->m.e31; + m.e12 = lhs->m.e11 * rhs->m.e12 + lhs->m.e12 * rhs->m.e22 + lhs->m.e13 * rhs->m.e32; + m.e13 = lhs->m.e11 * rhs->m.e13 + lhs->m.e12 * rhs->m.e23 + lhs->m.e13 * rhs->m.e33; + + m.e21 = lhs->m.e21 * rhs->m.e11 + lhs->m.e22 * rhs->m.e21 + lhs->m.e23 * rhs->m.e31; + m.e22 = lhs->m.e21 * rhs->m.e12 + lhs->m.e22 * rhs->m.e22 + lhs->m.e23 * rhs->m.e32; + m.e23 = lhs->m.e21 * rhs->m.e13 + lhs->m.e22 * rhs->m.e23 + lhs->m.e23 * rhs->m.e33; + + m.e31 = lhs->m.e31 * rhs->m.e11 + lhs->m.e32 * rhs->m.e21 + lhs->m.e33 * rhs->m.e31; + m.e32 = lhs->m.e31 * rhs->m.e12 + lhs->m.e32 * rhs->m.e22 + lhs->m.e33 * rhs->m.e32; + m.e33 = lhs->m.e31 * rhs->m.e13 + lhs->m.e32 * rhs->m.e23 + lhs->m.e33 * rhs->m.e33; +} \ No newline at end of file diff --git a/src/lib/tvgRender.h b/src/lib/tvgRender.h new file mode 100644 index 00000000..c379d0e4 --- /dev/null +++ b/src/lib/tvgRender.h @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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_RENDER_H_ +#define _TVG_RENDER_H_ + +namespace tvg +{ + +struct Surface +{ + //TODO: Union for multiple types + uint32_t* buffer; + uint32_t stride; + uint32_t w, h; + uint32_t cs; +}; + +enum RenderUpdateFlag {None = 0, Path = 1, Color = 2, Gradient = 4, Stroke = 8, Transform = 16, All = 32}; + +struct RenderTransform +{ + Matrix m; //3x3 Matrix Elements + float x = 0.0f; + float y = 0.0f; + float degree = 0.0f; //rotation degree + float scale = 1.0f; //scale factor + bool overriding = false; //user transform? + + bool update(); + void override(const Matrix& m); + + RenderTransform(); + RenderTransform(const RenderTransform* lhs, const RenderTransform* rhs); +}; + + +class RenderMethod +{ +public: + virtual ~RenderMethod() {} + virtual void* prepare(TVG_UNUSED const Shape& shape, TVG_UNUSED void* data, TVG_UNUSED const RenderTransform* transform, TVG_UNUSED RenderUpdateFlag flags) { return nullptr; } + virtual bool dispose(TVG_UNUSED const Shape& shape, TVG_UNUSED void *data) { return true; } + virtual bool preRender() { return true; } + virtual bool render(TVG_UNUSED const Shape& shape, TVG_UNUSED void *data) { return true; } + virtual bool postRender() { return true; } + virtual bool clear() { return true; } + virtual bool flush() { return true; } + virtual uint32_t ref() { return 0; } + virtual uint32_t unref() { return 0; } +}; + +struct RenderInitializer +{ + RenderMethod* pInst = nullptr; + uint32_t refCnt = 0; + bool initialized = false; + + static bool init(RenderInitializer& renderInit, RenderMethod* engine) + { + if (renderInit.pInst || renderInit.refCnt > 0) return false; + renderInit.pInst = engine; + renderInit.refCnt = 0; + renderInit.initialized = true; + return true; + } + + static bool term(RenderInitializer& renderInit) + { + if (!renderInit.pInst || !renderInit.initialized) return false; + + renderInit.initialized = false; + + //Still it's refered.... + if (renderInit.refCnt > 0) return true; + delete(renderInit.pInst); + renderInit.pInst = nullptr; + + return true; + } + + static uint32_t unref(RenderInitializer& renderInit) + { + --renderInit.refCnt; + + //engine has been requested to termination + if (!renderInit.initialized && renderInit.refCnt == 0) { + if (renderInit.pInst) { + delete(renderInit.pInst); + renderInit.pInst = nullptr; + } + } + return renderInit.refCnt; + } + + static RenderMethod* inst(RenderInitializer& renderInit) + { + return renderInit.pInst; + } + + static uint32_t ref(RenderInitializer& renderInit) + { + return ++renderInit.refCnt; + } + +}; + +} + +#endif //_TVG_RENDER_H_ \ No newline at end of file diff --git a/src/lib/tvgScene.cpp b/src/lib/tvgScene.cpp new file mode 100644 index 00000000..df7362fb --- /dev/null +++ b/src/lib/tvgScene.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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 "tvgSceneImpl.h" + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +Scene::Scene() : pImpl(make_unique()) +{ + Paint::IMPL->method(new PaintMethod(IMPL)); +} + + +Scene::~Scene() +{ +} + + +unique_ptr Scene::gen() noexcept +{ + return unique_ptr(new Scene); +} + + +Result Scene::push(unique_ptr paint) noexcept +{ + auto p = paint.release(); + if (!p) return Result::MemoryCorruption; + IMPL->paints.push_back(p); + + return Result::Success; +} + + +Result Scene::reserve(uint32_t size) noexcept +{ + IMPL->paints.reserve(size); + + return Result::Success; +} \ No newline at end of file diff --git a/src/lib/tvgSceneImpl.h b/src/lib/tvgSceneImpl.h new file mode 100644 index 00000000..68939c3b --- /dev/null +++ b/src/lib/tvgSceneImpl.h @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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_SCENE_IMPL_H_ +#define _TVG_SCENE_IMPL_H_ + +#include "tvgCommon.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +struct Scene::Impl +{ + vector paints; + + bool dispose(RenderMethod& renderer) + { + for (auto paint : paints) { + paint->IMPL->dispose(renderer); + delete(paint); + } + paints.clear(); + + return true; + } + + bool update(RenderMethod &renderer, const RenderTransform* transform, RenderUpdateFlag flag) + { + for(auto paint: paints) { + if (!paint->IMPL->update(renderer, transform, static_cast(flag))) return false; + } + return true; + } + + bool render(RenderMethod &renderer) + { + for(auto paint: paints) { + if(!paint->IMPL->render(renderer)) return false; + } + return true; + } + + bool bounds(float* px, float* py, float* pw, float* ph) + { + auto x = FLT_MAX; + auto y = FLT_MAX; + auto w = 0.0f; + auto h = 0.0f; + + for(auto paint: paints) { + auto x2 = FLT_MAX; + auto y2 = FLT_MAX; + auto w2 = 0.0f; + auto h2 = 0.0f; + + if (paint->IMPL->bounds(&x2, &y2, &w2, &h2)) return false; + + //Merge regions + if (x2 < x) x = x2; + if (x + w < x2 + w2) w = (x2 + w2) - x; + if (y2 < y) y = x2; + if (y + h < y2 + h2) h = (y2 + h2) - y; + } + + if (px) *px = x; + if (py) *py = y; + if (pw) *pw = w; + if (ph) *ph = h; + + return true; + } +}; + +#endif //_TVG_SCENE_IMPL_H_ \ No newline at end of file diff --git a/src/lib/tvgShape.cpp b/src/lib/tvgShape.cpp new file mode 100644 index 00000000..8db945d7 --- /dev/null +++ b/src/lib/tvgShape.cpp @@ -0,0 +1,405 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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 + +#include "tvgShapeImpl.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ +constexpr auto PATH_KAPPA = 0.552284f; + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +Shape :: Shape() : pImpl(make_unique(this)) +{ + Paint::IMPL->method(new PaintMethod(IMPL)); +} + + +Shape :: ~Shape() +{ +} + + +unique_ptr Shape::gen() noexcept +{ + return unique_ptr(new Shape); +} + + +Result Shape::reset() noexcept +{ + IMPL->path->reset(); + + IMPL->flag |= RenderUpdateFlag::Path; + + return Result::Success; +} + + +uint32_t Shape::pathCommands(const PathCommand** cmds) const noexcept +{ + if (!cmds) return 0; + + *cmds = IMPL->path->cmds; + + return IMPL->path->cmdCnt; +} + + +uint32_t Shape::pathCoords(const Point** pts) const noexcept +{ + if (!pts) return 0; + + *pts = IMPL->path->pts; + + return IMPL->path->ptsCnt; +} + + +Result Shape::appendPath(const PathCommand *cmds, uint32_t cmdCnt, const Point* pts, uint32_t ptsCnt) noexcept +{ + if (cmdCnt == 0 || ptsCnt == 0 || !pts || !ptsCnt) return Result::InvalidArguments; + + IMPL->path->grow(cmdCnt, ptsCnt); + IMPL->path->append(cmds, cmdCnt, pts, ptsCnt); + + IMPL->flag |= RenderUpdateFlag::Path; + + return Result::Success; +} + + +Result Shape::moveTo(float x, float y) noexcept +{ + IMPL->path->moveTo(x, y); + + IMPL->flag |= RenderUpdateFlag::Path; + + return Result::Success; +} + + +Result Shape::lineTo(float x, float y) noexcept +{ + IMPL->path->lineTo(x, y); + + IMPL->flag |= RenderUpdateFlag::Path; + + return Result::Success; +} + + +Result Shape::cubicTo(float cx1, float cy1, float cx2, float cy2, float x, float y) noexcept +{ + IMPL->path->cubicTo(cx1, cy1, cx2, cy2, x, y); + + IMPL->flag |= RenderUpdateFlag::Path; + + return Result::Success; +} + + +Result Shape::close() noexcept +{ + IMPL->path->close(); + + IMPL->flag |= RenderUpdateFlag::Path; + + return Result::Success; +} + + +Result Shape::appendCircle(float cx, float cy, float rx, float ry) noexcept +{ + auto impl = pImpl.get(); + + auto rxKappa = rx * PATH_KAPPA; + auto ryKappa = ry * PATH_KAPPA; + + impl->path->grow(6, 13); + impl->path->moveTo(cx, cy - ry); + impl->path->cubicTo(cx + rxKappa, cy - ry, cx + rx, cy - ryKappa, cx + rx, cy); + impl->path->cubicTo(cx + rx, cy + ryKappa, cx + rxKappa, cy + ry, cx, cy + ry); + impl->path->cubicTo(cx - rxKappa, cy + ry, cx - rx, cy + ryKappa, cx - rx, cy); + impl->path->cubicTo(cx - rx, cy - ryKappa, cx - rxKappa, cy - ry, cx, cy - ry); + impl->path->close(); + + impl->flag |= RenderUpdateFlag::Path; + + return Result::Success; +} + +Result Shape::appendArc(float cx, float cy, float radius, float startAngle, float sweep, bool pie) noexcept +{ + const float M_PI_HALF = M_PI * 0.5f; + + //just circle + if (sweep >= 360) return appendCircle(cx, cy, radius, radius); + + auto impl = pImpl.get(); + + startAngle = (startAngle * M_PI) / 180; + sweep = sweep * M_PI / 180; + + auto nCurves = ceil(abs(sweep / M_PI_HALF)); + auto fract = fmod(sweep, M_PI_HALF); + fract = (fract < std::numeric_limits::epsilon()) ? M_PI_HALF : fract; + + //Start from here + Point start = {radius * cos(startAngle), radius * sin(startAngle)}; + + if (pie) { + impl->path->moveTo(cx, cy); + impl->path->lineTo(start.x + cx, start.y + cy); + }else { + impl->path->moveTo(start.x + cx, start.y + cy); + } + + for (int i = 0; i < nCurves; ++i) { + + auto endAngle = startAngle + ((i != nCurves - 1) ? M_PI_HALF : fract); + Point end = {radius * cos(endAngle), radius * sin(endAngle)}; + + //variables needed to calculate bezier control points + + //get bezier control points using article: + //(http://itc.ktu.lt/index.php/ITC/article/view/11812/6479) + auto ax = start.x; + auto ay = start.y; + auto bx = end.x; + auto by = end.y; + auto q1 = ax * ax + ay * ay; + auto q2 = ax * bx + ay * by + q1; + auto k2 = static_cast (4.0/3.0) * ((sqrt(2 * q1 * q2) - q2) / (ax * by - ay * bx)); + + start = end; //Next start point is the current end point + + end.x += cx; + end.y += cy; + + Point ctrl1 = {ax - k2 * ay + cx, ay + k2 * ax + cy}; + Point ctrl2 = {bx + k2 * by + cx, by - k2 * bx + cy}; + + impl->path->cubicTo(ctrl1.x, ctrl1.y, ctrl2.x, ctrl2.y, end.x, end.y); + + startAngle = endAngle; + } + + if (pie) { + impl->path->moveTo(cx, cy); + impl->path->close(); + } + + IMPL->flag |= RenderUpdateFlag::Path; + + return Result::Success; +} + + +Result Shape::appendRect(float x, float y, float w, float h, float rx, float ry) noexcept +{ + auto impl = pImpl.get(); + + auto halfW = w * 0.5f; + auto halfH = h * 0.5f; + + //clamping cornerRadius by minimum size + if (rx > halfW) rx = halfW; + if (ry > halfH) ry = halfH; + + //rectangle + if (rx == 0 && ry == 0) { + impl->path->grow(5, 4); + impl->path->moveTo(x, y); + impl->path->lineTo(x + w, y); + impl->path->lineTo(x + w, y + h); + impl->path->lineTo(x, y + h); + impl->path->close(); + //circle + } else if (fabsf(rx - halfW) < FLT_EPSILON && fabsf(ry - halfH) < FLT_EPSILON) { + return appendCircle(x + (w * 0.5f), y + (h * 0.5f), rx, ry); + } else { + auto hrx = rx * 0.5f; + auto hry = ry * 0.5f; + impl->path->grow(10, 17); + impl->path->moveTo(x + rx, y); + impl->path->lineTo(x + w - rx, y); + impl->path->cubicTo(x + w - rx + hrx, y, x + w, y + ry - hry, x + w, y + ry); + impl->path->lineTo(x + w, y + h - ry); + impl->path->cubicTo(x + w, y + h - ry + hry, x + w - rx + hrx, y + h, x + w - rx, y + h); + impl->path->lineTo(x + rx, y + h); + impl->path->cubicTo(x + rx - hrx, y + h, x, y + h - ry + hry, x, y + h - ry); + impl->path->lineTo(x, y + ry); + impl->path->cubicTo(x, y + ry - hry, x + rx - hrx, y, x + rx, y); + impl->path->close(); + } + + impl->flag |= RenderUpdateFlag::Path; + + return Result::Success; +} + + +Result Shape::fill(uint8_t r, uint8_t g, uint8_t b, uint8_t a) noexcept +{ + auto impl = pImpl.get(); + + impl->color[0] = r; + impl->color[1] = g; + impl->color[2] = b; + impl->color[3] = a; + impl->flag |= RenderUpdateFlag::Color; + + if (impl->fill) { + delete(impl->fill); + impl->fill = nullptr; + impl->flag |= RenderUpdateFlag::Gradient; + } + + return Result::Success; +} + + +Result Shape::fill(unique_ptr f) noexcept +{ + auto impl = pImpl.get(); + + auto p = f.release(); + if (!p) return Result::MemoryCorruption; + + if (impl->fill) delete(impl->fill); + impl->fill = p; + impl->flag |= RenderUpdateFlag::Gradient; + + return Result::Success; +} + + +Result Shape::fill(uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a) const noexcept +{ + auto impl = pImpl.get(); + + if (r) *r = impl->color[0]; + if (g) *g = impl->color[1]; + if (b) *b = impl->color[2]; + if (a) *a = impl->color[3]; + + return Result::Success; +} + +const Fill* Shape::fill() const noexcept +{ + return IMPL->fill; +} + + +Result Shape::stroke(float width) noexcept +{ + if (!IMPL->strokeWidth(width)) return Result::FailedAllocation; + + return Result::Success; +} + + +float Shape::strokeWidth() const noexcept +{ + if (!IMPL->stroke) return 0; + return IMPL->stroke->width; +} + + +Result Shape::stroke(uint8_t r, uint8_t g, uint8_t b, uint8_t a) noexcept +{ + if (!IMPL->strokeColor(r, g, b, a)) return Result::FailedAllocation; + + return Result::Success; +} + + +Result Shape::strokeColor(uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a) const noexcept +{ + auto impl = pImpl.get(); + + if (!impl->stroke) return Result::InsufficientCondition; + + if (r) *r = impl->stroke->color[0]; + if (g) *g = impl->stroke->color[1]; + if (b) *b = impl->stroke->color[2]; + if (a) *a = impl->stroke->color[3]; + + return Result::Success; +} + + +Result Shape::stroke(const float* dashPattern, uint32_t cnt) noexcept +{ + if (cnt < 2 || !dashPattern) return Result::InvalidArguments; + + if (!IMPL->strokeDash(dashPattern, cnt)) return Result::FailedAllocation; + + return Result::Success; +} + + +uint32_t Shape::strokeDash(const float** dashPattern) const noexcept +{ + if (!IMPL->stroke) return 0; + + if (dashPattern) *dashPattern = IMPL->stroke->dashPattern; + return IMPL->stroke->dashCnt; +} + + +Result Shape::stroke(StrokeCap cap) noexcept +{ + if (!IMPL->strokeCap(cap)) return Result::FailedAllocation; + + return Result::Success; +} + + +Result Shape::stroke(StrokeJoin join) noexcept +{ + if (!IMPL->strokeJoin(join)) return Result::FailedAllocation; + + return Result::Success; +} + + +StrokeCap Shape::strokeCap() const noexcept +{ + if (!IMPL->stroke) return StrokeCap::Square; + + return IMPL->stroke->cap; +} + + +StrokeJoin Shape::strokeJoin() const noexcept +{ + if (!IMPL->stroke) return StrokeJoin::Bevel; + + return IMPL->stroke->join; +} \ No newline at end of file diff --git a/src/lib/tvgShapeImpl.h b/src/lib/tvgShapeImpl.h new file mode 100644 index 00000000..612bfe34 --- /dev/null +++ b/src/lib/tvgShapeImpl.h @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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_SHAPE_IMPL_H_ +#define _TVG_SHAPE_IMPL_H_ + +#include "tvgCommon.h" +#include "tvgShapePath.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +struct ShapeStroke +{ + float width = 0; + uint8_t color[4] = {0, 0, 0, 0}; + float* dashPattern = nullptr; + uint32_t dashCnt = 0; + StrokeCap cap = StrokeCap::Square; + StrokeJoin join = StrokeJoin::Bevel; + + ~ShapeStroke() + { + if (dashPattern) free(dashPattern); + } +}; + + +struct Shape::Impl +{ + ShapePath *path = nullptr; + Fill *fill = nullptr; + ShapeStroke *stroke = nullptr; + uint8_t color[4] = {0, 0, 0, 0}; //r, g, b, a + void *edata = nullptr; //engine data + Shape *shape = nullptr; + uint32_t flag = RenderUpdateFlag::None; + + Impl(Shape* s) : path(new ShapePath), shape(s) + { + } + + ~Impl() + { + if (path) delete(path); + if (fill) delete(fill); + if (stroke) delete(stroke); + } + + bool dispose(RenderMethod& renderer) + { + return renderer.dispose(*shape, edata); + } + + bool render(RenderMethod& renderer) + { + return renderer.render(*shape, edata); + } + + bool update(RenderMethod& renderer, const RenderTransform* transform, RenderUpdateFlag pFlag) + { + edata = renderer.prepare(*shape, edata, transform, static_cast(pFlag | flag)); + + flag = RenderUpdateFlag::None; + + if (edata) return true; + return false; + } + + bool bounds(float* x, float* y, float* w, float* h) + { + if (!path) return false; + return path->bounds(x, y, w, h); + } + + bool strokeWidth(float width) + { + //TODO: Size Exception? + + if (!stroke) stroke = new ShapeStroke(); + if (!stroke) return false; + + stroke->width = width; + flag |= RenderUpdateFlag::Stroke; + + return true; + } + + bool strokeCap(StrokeCap cap) + { + if (!stroke) stroke = new ShapeStroke(); + if (!stroke) return false; + + stroke->cap = cap; + flag |= RenderUpdateFlag::Stroke; + + return true; + } + + bool strokeJoin(StrokeJoin join) + { + if (!stroke) stroke = new ShapeStroke(); + if (!stroke) return false; + + stroke->join = join; + flag |= RenderUpdateFlag::Stroke; + + return true; + } + + bool strokeColor(uint8_t r, uint8_t g, uint8_t b, uint8_t a) + { + if (!stroke) stroke = new ShapeStroke(); + if (!stroke) return false; + + stroke->color[0] = r; + stroke->color[1] = g; + stroke->color[2] = b; + stroke->color[3] = a; + + flag |= RenderUpdateFlag::Stroke; + + return true; + } + + bool strokeDash(const float* pattern, uint32_t cnt) + { + if (!stroke) stroke = new ShapeStroke(); + if (!stroke) return false; + + if (stroke->dashCnt != cnt) { + if (stroke->dashPattern) free(stroke->dashPattern); + stroke->dashPattern = nullptr; + } + + if (!stroke->dashPattern) stroke->dashPattern = static_cast(malloc(sizeof(float) * cnt)); + + for (uint32_t i = 0; i < cnt; ++i) + stroke->dashPattern[i] = pattern[i]; + + stroke->dashCnt = cnt; + flag |= RenderUpdateFlag::Stroke; + + return true; + } +}; + +#endif //_TVG_SHAPE_IMPL_H_ \ No newline at end of file diff --git a/src/lib/tvgShapePath.h b/src/lib/tvgShapePath.h new file mode 100644 index 00000000..f3e0ae59 --- /dev/null +++ b/src/lib/tvgShapePath.h @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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_SHAPE_PATH_H_ +#define _TVG_SHAPE_PATH_H_ + +#include "tvgCommon.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +struct ShapePath +{ + PathCommand* cmds = nullptr; + uint32_t cmdCnt = 0; + uint32_t reservedCmdCnt = 0; + + Point *pts = nullptr; + uint32_t ptsCnt = 0; + uint32_t reservedPtsCnt = 0; + + + ~ShapePath() + { + if (cmds) free(cmds); + if (pts) free(pts); + } + + void reserveCmd(uint32_t cmdCnt) + { + if (cmdCnt <= reservedCmdCnt) return; + reservedCmdCnt = cmdCnt; + cmds = static_cast(realloc(cmds, sizeof(PathCommand) * reservedCmdCnt)); + } + + void reservePts(uint32_t ptsCnt) + { + if (ptsCnt <= reservedPtsCnt) return; + reservedPtsCnt = ptsCnt; + pts = static_cast(realloc(pts, sizeof(Point) * reservedPtsCnt)); + } + + void grow(uint32_t cmdCnt, uint32_t ptsCnt) + { + reserveCmd(this->cmdCnt + cmdCnt); + reservePts(this->ptsCnt + ptsCnt); + } + + void reset() + { + cmdCnt = 0; + ptsCnt = 0; + } + + void append(const PathCommand* cmds, uint32_t cmdCnt, const Point* pts, uint32_t ptsCnt) + { + memcpy(this->cmds + this->cmdCnt, cmds, sizeof(PathCommand) * cmdCnt); + memcpy(this->pts + this->ptsCnt, pts, sizeof(Point) * ptsCnt); + this->cmdCnt += cmdCnt; + this->ptsCnt += ptsCnt; + } + + void moveTo(float x, float y) + { + if (cmdCnt + 1 > reservedCmdCnt) reserveCmd((cmdCnt + 1) * 2); + if (ptsCnt + 2 > reservedPtsCnt) reservePts((ptsCnt + 2) * 2); + + cmds[cmdCnt++] = PathCommand::MoveTo; + pts[ptsCnt++] = {x, y}; + } + + void lineTo(float x, float y) + { + if (cmdCnt + 1 > reservedCmdCnt) reserveCmd((cmdCnt + 1) * 2); + if (ptsCnt + 2 > reservedPtsCnt) reservePts((ptsCnt + 2) * 2); + + cmds[cmdCnt++] = PathCommand::LineTo; + pts[ptsCnt++] = {x, y}; + } + + void cubicTo(float cx1, float cy1, float cx2, float cy2, float x, float y) + { + if (cmdCnt + 1 > reservedCmdCnt) reserveCmd((cmdCnt + 1) * 2); + if (ptsCnt + 3 > reservedPtsCnt) reservePts((ptsCnt + 3) * 2); + + cmds[cmdCnt++] = PathCommand::CubicTo; + pts[ptsCnt++] = {cx1, cy1}; + pts[ptsCnt++] = {cx2, cy2}; + pts[ptsCnt++] = {x, y}; + } + + void close() + { + if (cmdCnt > 0 && cmds[cmdCnt - 1] == PathCommand::Close) return; + + if (cmdCnt + 1 > reservedCmdCnt) reserveCmd((cmdCnt + 1) * 2); + cmds[cmdCnt++] = PathCommand::Close; + } + + bool bounds(float* x, float* y, float* w, float* h) + { + if (ptsCnt == 0) return false; + + Point min = { pts[0].x, pts[0].y }; + Point max = { pts[0].x, pts[0].y }; + + for(uint32_t i = 1; i < ptsCnt; ++i) { + if (pts[i].x < min.x) min.x = pts[i].x; + if (pts[i].y < min.y) min.y = pts[i].y; + if (pts[i].x > max.x) max.x = pts[i].x; + if (pts[i].y > max.y) max.y = pts[i].y; + } + + if (x) *x = min.x; + if (y) *y = min.y; + if (w) *w = max.x - min.x; + if (h) *h = max.y - min.y; + + return true; + } +}; + +#endif //_TVG_SHAPE_PATH_H_ diff --git a/src/lib/tvgSwCanvas.cpp b/src/lib/tvgSwCanvas.cpp new file mode 100644 index 00000000..b56beb46 --- /dev/null +++ b/src/lib/tvgSwCanvas.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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 "tvgCommon.h" +#include "tvgCanvasImpl.h" + +#ifdef THORVG_SW_RASTER_SUPPORT + #include "tvgSwRenderer.h" +#else + class SwRenderer : public RenderMethod + { + //Non Supported. Dummy Class */ + }; +#endif + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +struct SwCanvas::Impl +{ + Impl() {} +}; + + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +#ifdef THORVG_SW_RASTER_SUPPORT +SwCanvas::SwCanvas() : Canvas(SwRenderer::inst()), pImpl(make_unique()) +#else +SwCanvas::SwCanvas() : Canvas(nullptr), pImpl(make_unique()) +#endif +{ +} + + +SwCanvas::~SwCanvas() +{ +} + + +Result SwCanvas::target(uint32_t* buffer, uint32_t stride, uint32_t w, uint32_t h, Colorspace cs) noexcept +{ +#ifdef THORVG_SW_RASTER_SUPPORT + //We know renderer type, avoid dynamic_cast for performance. + auto renderer = static_cast(Canvas::pImpl.get()->renderer); + if (!renderer) return Result::MemoryCorruption; + + if (!renderer->target(buffer, stride, w, h, cs)) return Result::InvalidArguments; + + return Result::Success; +#endif + return Result::NonSupport; +} + + +unique_ptr SwCanvas::gen() noexcept +{ +#ifdef THORVG_SW_RASTER_SUPPORT + return unique_ptr(new SwCanvas); +#endif + return nullptr; +} \ No newline at end of file diff --git a/src/lib/tvgTaskScheduler.cpp b/src/lib/tvgTaskScheduler.cpp new file mode 100644 index 00000000..31b1e849 --- /dev/null +++ b/src/lib/tvgTaskScheduler.cpp @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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 +#include +#include "tvgCommon.h" + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +namespace tvg { + +struct TaskQueue { + deque> taskDeque; + mutex mtx; + condition_variable ready; + bool done = false; + + bool tryPop(shared_ptr &task) + { + unique_lock lock{mtx, try_to_lock}; + if (!lock || taskDeque.empty()) return false; + task = move(taskDeque.front()); + taskDeque.pop_front(); + + return true; + } + + bool tryPush(shared_ptr &&task) + { + { + unique_lock lock{mtx, try_to_lock}; + if (!lock) return false; + taskDeque.push_back(move(task)); + } + + ready.notify_one(); + + return true; + } + + void complete() + { + { + unique_lock lock{mtx}; + done = true; + } + ready.notify_all(); + } + + bool pop(shared_ptr &task) + { + unique_lock lock{mtx}; + + while (taskDeque.empty() && !done) { + ready.wait(lock); + } + + if (taskDeque.empty()) return false; + + task = move(taskDeque.front()); + taskDeque.pop_front(); + + return true; + } + + void push(shared_ptr &&task) + { + { + unique_lock lock{mtx}; + taskDeque.push_back(move(task)); + } + + ready.notify_one(); + } + +}; + + +class TaskSchedulerImpl +{ +public: + unsigned threadCnt; + vector threads; + vector taskQueues{threadCnt}; + atomic idx{0}; + + TaskSchedulerImpl() + { + for (unsigned i = 0; i < threadCnt; ++i) { + threads.emplace_back([&, i] { run(i); }); + } + } + + ~TaskSchedulerImpl() + { + for (auto& queue : taskQueues) queue.complete(); + for (auto& thread : threads) thread.join(); + } + + void run(unsigned i) + { + shared_ptr task; + + //Thread Loop + while (true) { + auto success = false; + + for (unsigned i = 0; i < threadCnt * 2; ++i) { + if (taskQueues[(i + i) % threadCnt].tryPop(task)) { + success = true; + break; + } + } + + if (!success && !taskQueues[i].pop(task)) break; + + (*task)(); + } + } + + void request(shared_ptr task) + { + //Async + if (threadCnt > 0) { + task->prepare(); + auto i = idx++; + for (unsigned n = 0; n < threadCnt; ++n) { + if (taskQueues[(i + n) % threadCnt].tryPush(move(task))) return; + } + + taskQueues[i % threadCnt].push(move(task)); + + //Sync + } else { + task->run(); + } + } +}; + +} + +static TaskSchedulerImpl* inst = nullptr; + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +void TaskScheduler::init(unsigned threads) +{ + if (inst) return; + inst = new TaskSchedulerImpl; + inst->threadCnt = threads; +} + + +void TaskScheduler::term() +{ + if (!inst) return; + delete(inst); + inst = nullptr; +} + + +void TaskScheduler::request(shared_ptr task) +{ + if (inst) { + inst->request(move(task)); + } +} \ No newline at end of file diff --git a/src/lib/tvgTaskScheduler.h b/src/lib/tvgTaskScheduler.h new file mode 100644 index 00000000..4380a9ca --- /dev/null +++ b/src/lib/tvgTaskScheduler.h @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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_TASK_SCHEDULER_H_ +#define _TVG_TASK_SCHEDULER_H_ + +#include "tvgCommon.h" + +namespace tvg +{ + +struct Task +{ +private: + std::promise sender; + std::future receiver; + +public: + virtual ~Task() = default; + + void get() + { + if (receiver.valid()) receiver.get(); + } + +protected: + virtual void run() = 0; + +private: + void operator()() + { + run(); + sender.set_value(); + } + + void prepare() + { + sender = std::promise(); + receiver = sender.get_future(); + } + + friend class TaskSchedulerImpl; +}; + +struct TaskScheduler +{ + static void init(unsigned threads); + static void term(); + static void request(shared_ptr task); +}; + +} + +#endif //_TVG_TASK_SCHEDULER_H_ \ No newline at end of file diff --git a/src/loaders/meson.build b/src/loaders/meson.build new file mode 100644 index 00000000..bc67b53e --- /dev/null +++ b/src/loaders/meson.build @@ -0,0 +1,11 @@ +subloader_dep = [] + +if get_option('loaders').contains('svg') == true + subdir('svg') + message('Enable SVG Loader') +endif + +loader_dep = declare_dependency( + dependencies: subloader_dep, + include_directories : include_directories('.'), +) diff --git a/src/loaders/svg/meson.build b/src/loaders/svg/meson.build new file mode 100644 index 00000000..d62762cf --- /dev/null +++ b/src/loaders/svg/meson.build @@ -0,0 +1,16 @@ +source_file = [ + 'tvgSimpleXmlParser.h', + 'tvgSvgLoader.h', + 'tvgSvgLoaderCommon.h', + 'tvgSvgPath.h', + 'tvgSvgSceneBuilder.h', + 'tvgSimpleXmlParser.cpp', + 'tvgSvgLoader.cpp', + 'tvgSvgPath.cpp', + 'tvgSvgSceneBuilder.cpp', +] + +subloader_dep += [declare_dependency( + include_directories : include_directories('.'), + sources : source_file +)] diff --git a/src/loaders/svg/tvgSimpleXmlParser.cpp b/src/loaders/svg/tvgSimpleXmlParser.cpp new file mode 100644 index 00000000..76200aeb --- /dev/null +++ b/src/loaders/svg/tvgSimpleXmlParser.cpp @@ -0,0 +1,367 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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 "tvgSimpleXmlParser.h" + +static const char* _simpleXmlFindWhiteSpace(const char* itr, const char* itrEnd) +{ + for (; itr < itrEnd; itr++) { + if (isspace((unsigned char)*itr)) break; + } + return itr; +} + + +static const char* _simpleXmlSkipWhiteSpace(const char* itr, const char* itrEnd) +{ + for (; itr < itrEnd; itr++) { + if (!isspace((unsigned char)*itr)) break; + } + return itr; +} + + +static const char* _simpleXmlUnskipWhiteSpace(const char* itr, const char* itrStart) +{ + for (itr--; itr > itrStart; itr--) { + if (!isspace((unsigned char)*itr)) break; + } + return itr + 1; +} + + +static const char* _simpleXmlFindStartTag(const char* itr, const char* itrEnd) +{ + return (const char*)memchr(itr, '<', itrEnd - itr); +} + + +static const char* _simpleXmlFindEndTag(const char* itr, const char* itrEnd) +{ + bool insideQuote = false; + for (; itr < itrEnd; itr++) { + if (*itr == '"') insideQuote = !insideQuote; + if (!insideQuote) { + if ((*itr == '>') || (*itr == '<')) + return itr; + } + } + return nullptr; +} + + +static const char* _simpleXmlFindEndCommentTag(const char* itr, const char* itrEnd) +{ + for (; itr < itrEnd; itr++) { + if ((*itr == '-') && ((itr + 1 < itrEnd) && (*(itr + 1) == '-')) && ((itr + 2 < itrEnd) && (*(itr + 2) == '>'))) return itr + 2; + } + return nullptr; +} + + +static const char* _simpleXmlFindEndCdataTag(const char* itr, const char* itrEnd) +{ + for (; itr < itrEnd; itr++) { + if ((*itr == ']') && ((itr + 1 < itrEnd) && (*(itr + 1) == ']')) && ((itr + 2 < itrEnd) && (*(itr + 2) == '>'))) return itr + 2; + } + return nullptr; +} + + +static const char* _simpleXmlFindDoctypeChildEndTag(const char* itr, const char* itrEnd) +{ + for (; itr < itrEnd; itr++) { + if (*itr == '>') return itr; + } + return nullptr; +} + + +bool simpleXmlParseAttributes(const char* buf, unsigned bufLength, simpleXMLAttributeCb func, const void* data) +{ + const char *itr = buf, *itrEnd = buf + bufLength; + char* tmpBuf = (char*)alloca(bufLength + 1); + + if (!buf) return false; + if (!func) return false; + + while (itr < itrEnd) { + const char* p = _simpleXmlSkipWhiteSpace(itr, itrEnd); + const char *key, *keyEnd, *value, *valueEnd; + char* tval; + + if (p == itrEnd) return true; + + key = p; + for (keyEnd = key; keyEnd < itrEnd; keyEnd++) { + if ((*keyEnd == '=') || (isspace((unsigned char)*keyEnd))) break; + } + if (keyEnd == itrEnd) return false; + if (keyEnd == key) continue; + + if (*keyEnd == '=') value = keyEnd + 1; + else { + value = (const char*)memchr(keyEnd, '=', itrEnd - keyEnd); + if (!value) return false; + value++; + } + for (; value < itrEnd; value++) { + if (!isspace((unsigned char)*value)) break; + } + if (value == itrEnd) return false; + + if ((*value == '"') || (*value == '\'')) { + valueEnd = (const char*)memchr(value + 1, *value, itrEnd - value); + if (!valueEnd) return false; + value++; + } else { + valueEnd = _simpleXmlFindWhiteSpace(value, itrEnd); + } + + memcpy(tmpBuf, key, keyEnd - key); + tmpBuf[keyEnd - key] = '\0'; + + tval = tmpBuf + (keyEnd - key) + 1; + memcpy(tval, value, valueEnd - value); + tval[valueEnd - value] = '\0'; + + if (!func((void*)data, tmpBuf, tval)) return false; + + itr = valueEnd + 1; + } + return true; +} + + +bool simpleXmlParse(const char* buf, unsigned bufLength, bool strip, simpleXMLCb func, const void* data) +{ + const char *itr = buf, *itrEnd = buf + bufLength; + + if (!buf) return false; + if (!func) return false; + +#define CB(type, start, end) \ + do { \ + size_t _sz = end - start; \ + bool _ret; \ + _ret = func((void*)data, type, start, _sz); \ + if (!_ret) \ + return false; \ + } while (0) + + while (itr < itrEnd) { + if (itr[0] == '<') { + if (itr + 1 >= itrEnd) { + CB(SimpleXMLType::Error, itr, itrEnd); + return false; + } else { + SimpleXMLType type; + size_t toff; + const char* p; + + if (itr[1] == '/') { + type = SimpleXMLType::Close; + toff = 1; + } else if (itr[1] == '?') { + type = SimpleXMLType::Processing; + toff = 1; + } else if (itr[1] == '!') { + if ((itr + sizeof("") - 1 < itrEnd) && (!memcmp(itr + 2, "DOCTYPE", sizeof("DOCTYPE") - 1)) && ((itr[2 + sizeof("DOCTYPE") - 1] == '>') || (isspace((unsigned char)itr[2 + sizeof("DOCTYPE") - 1])))) { + type = SimpleXMLType::Doctype; + toff = sizeof("!DOCTYPE") - 1; + } else if ((itr + sizeof("") - 1 < itrEnd) && (!memcmp(itr + 2, "--", sizeof("--") - 1))) { + type = SimpleXMLType::Comment; + toff = sizeof("!--") - 1; + } else if ((itr + sizeof("") - 1 < itrEnd) && (!memcmp(itr + 2, "[CDATA[", sizeof("[CDATA[") - 1))) { + type = SimpleXMLType::CData; + toff = sizeof("![CDATA[") - 1; + } else if (itr + sizeof("") - 1 < itrEnd) { + type = SimpleXMLType::DoctypeChild; + toff = sizeof("!") - 1; + } else { + type = SimpleXMLType::Open; + toff = 0; + } + } else { + type = SimpleXMLType::Open; + toff = 0; + } + + if (type == SimpleXMLType::CData) p = _simpleXmlFindEndCdataTag(itr + 1 + toff, itrEnd); + else if (type == SimpleXMLType::DoctypeChild) p = _simpleXmlFindDoctypeChildEndTag(itr + 1 + toff, itrEnd); + else if (type == SimpleXMLType::Comment) p = _simpleXmlFindEndCommentTag(itr + 1 + toff, itrEnd); + else p = _simpleXmlFindEndTag(itr + 1 + toff, itrEnd); + + if ((p) && (*p == '<')) { + type = SimpleXMLType::Error; + toff = 0; + } + + if (p) { + const char *start, *end; + + start = itr + 1 + toff; + end = p; + + switch (type) { + case SimpleXMLType::Open: { + if (p[-1] == '/') { + type = SimpleXMLType::OpenEmpty; + end--; + } + break; + } + case SimpleXMLType::CData: { + if (!memcmp(p - 2, "]]", 2)) end -= 2; + break; + } + case SimpleXMLType::Processing: { + if (p[-1] == '?') end--; + break; + } + case SimpleXMLType::Comment: { + if (!memcmp(p - 2, "--", 2)) end -= 2; + break; + } + case SimpleXMLType::OpenEmpty: + case SimpleXMLType::Close: + case SimpleXMLType::Data: + case SimpleXMLType::Error: + case SimpleXMLType::Doctype: + case SimpleXMLType::DoctypeChild: + case SimpleXMLType::Ignored: { + break; + } + } + + if ((strip) && (type != SimpleXMLType::Error) && (type != SimpleXMLType::CData)) { + start = _simpleXmlSkipWhiteSpace(start, end); + end = _simpleXmlUnskipWhiteSpace(end, start + 1); + } + + CB(type, start, end); + + if (type != SimpleXMLType::Error) itr = p + 1; + else itr = p; + } else { + CB(SimpleXMLType::Error, itr, itrEnd); + return false; + } + } + } else { + const char *p, *end; + + if (strip) { + p = _simpleXmlSkipWhiteSpace(itr, itrEnd); + if (p) { + CB(SimpleXMLType::Ignored, itr, p); + itr = p; + } + } + + p = _simpleXmlFindStartTag(itr, itrEnd); + if (!p) p = itrEnd; + + end = p; + if (strip) end = _simpleXmlUnskipWhiteSpace(end, itr); + + if (itr != end) CB(SimpleXMLType::Data, itr, end); + + if ((strip) && (end < p)) CB(SimpleXMLType::Ignored, end, p); + + itr = p; + } + } + +#undef CB + + return true; +} + + +bool simpleXmlParseW3CAttribute(const char* buf, simpleXMLAttributeCb func, const void* data) +{ + const char* end; + char* key; + char* val; + char* next; + + if (!buf) return false; + + end = buf + strlen(buf); + key = (char*)alloca(end - buf + 1); + val = (char*)alloca(end - buf + 1); + + if (buf == end) return true; + + do { + char* sep = (char*)strchr(buf, ':'); + next = (char*)strchr(buf, ';'); + + key[0] = '\0'; + val[0] = '\0'; + + if (next == nullptr && sep != nullptr) { + memcpy(key, buf, sep - buf); + key[sep - buf] = '\0'; + + memcpy(val, sep + 1, end - sep - 1); + val[end - sep - 1] = '\0'; + } else if (sep < next && sep != nullptr) { + memcpy(key, buf, sep - buf); + key[sep - buf] = '\0'; + + memcpy(val, sep + 1, next - sep - 1); + val[next - sep - 1] = '\0'; + } else if (next) { + memcpy(key, buf, next - buf); + key[next - buf] = '\0'; + } + + if (key[0]) { + if (!func((void*)data, key, val)) return false; + } + + buf = next + 1; + } while (next != nullptr); + + return true; +} + + +const char* simpleXmlFindAttributesTag(const char* buf, unsigned bufLength) +{ + const char *itr = buf, *itrEnd = buf + bufLength; + + for (; itr < itrEnd; itr++) { + if (!isspace((unsigned char)*itr)) { + //User skip tagname and already gave it the attributes. + if (*itr == '=') return buf; + } else { + itr = _simpleXmlSkipWhiteSpace(itr + 1, itrEnd); + if (itr == itrEnd) return nullptr; + return itr; + } + } + + return nullptr; +} \ No newline at end of file diff --git a/src/loaders/svg/tvgSimpleXmlParser.h b/src/loaders/svg/tvgSimpleXmlParser.h new file mode 100644 index 00000000..a9f999db --- /dev/null +++ b/src/loaders/svg/tvgSimpleXmlParser.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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_SIMPLE_XML_PARSER_H_ +#define _TVG_SIMPLE_XML_PARSER_H_ + +#include +#include +#include + +enum class SimpleXMLType +{ + Open = 0, //!< \ + OpenEmpty, //!< \ + Close, //!< \ + Data, //!< tag text data + CData, //!< \ + Error, //!< error contents + Processing, //!< \ \ + Doctype, //!< \ + Ignored, //!< whatever is ignored by parser, like whitespace + DoctypeChild //!< \ +#include +#include "tvgSvgLoader.h" + + +/************************************************************************/ +/* Internal Class Implementation */ +/************************************************************************/ + +typedef SvgNode* (*FactoryMethod)(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength); +typedef SvgStyleGradient* (*GradientFactoryMethod)(SvgLoaderData* loader, const char* buf, unsigned bufLength); +static void _freeNode(SvgNode* node); + + +static char* _skipSpace(const char* str, const char* end) +{ + while (((end != nullptr && str < end) || (end == nullptr && *str != '\0')) && isspace(*str)) + ++str; + return (char*)str; +} + + +static string* _copyId(const char* str) +{ + if (str == nullptr) return nullptr; + + return new string(str); +} + + +static const char* _skipComma(const char* content) +{ + content = _skipSpace(content, nullptr); + if (*content == ',') return content + 1; + return content; +} + + +static bool _parseNumber(const char** content, float* number) +{ + char* end = nullptr; + + *number = strtof(*content, &end); + //If the start of string is not number + if ((*content) == end) return false; + //Skip comma if any + *content = _skipComma(end); + return true; +} + +/** + * According to https://www.w3.org/TR/SVG/coords.html#Units + * + * TODO + * Since this documentation is not obvious, more clean recalculation with dpi + * is required, but for now default w3 constants would be used + */ +static float _toFloat(SvgParser* svgParse, const char* str, SvgParserLengthType type) +{ + float parsedValue = strtof(str, nullptr); + + if (strstr(str, "cm")) parsedValue = parsedValue * 35.43307; + else if (strstr(str, "mm")) parsedValue = parsedValue * 3.543307; + else if (strstr(str, "pt")) parsedValue = parsedValue * 1.25; + else if (strstr(str, "pc")) parsedValue = parsedValue * 15; + else if (strstr(str, "in")) parsedValue = parsedValue * 90; + else if (strstr(str, "%")) { + if (type == SvgParserLengthType::Vertical) parsedValue = (parsedValue / 100.0) * svgParse->global.h; + else if (type == SvgParserLengthType::Horizontal) parsedValue = (parsedValue / 100.0) * svgParse->global.w; + else //if other then it's radius + { + float max = svgParse->global.w; + if (max < svgParse->global.h) + max = svgParse->global.h; + parsedValue = (parsedValue / 100.0) * max; + } + } + + //TODO: Implement 'em', 'ex' attributes + + return parsedValue; +} + + +static float _gradientToFloat(SvgParser* svgParse, const char* str, SvgParserLengthType type) +{ + char* end = nullptr; + + float parsedValue = strtof(str, &end); + float max = 1; + + /** + * That is according to Units in here + * + * https://www.w3.org/TR/2015/WD-SVG2-20150915/coords.html + */ + if (type == SvgParserLengthType::Vertical) max = svgParse->global.h; + else if (type == SvgParserLengthType::Horizontal) max = svgParse->global.w; + else if (type == SvgParserLengthType::Other) max = sqrt(pow(svgParse->global.h, 2) + pow(svgParse->global.w, 2)) / sqrt(2.0); + + if (strstr(str, "%")) parsedValue = parsedValue / 100.0; + else if (strstr(str, "cm")) parsedValue = parsedValue * 35.43307; + else if (strstr(str, "mm")) parsedValue = parsedValue * 3.543307; + else if (strstr(str, "pt")) parsedValue = parsedValue * 1.25; + else if (strstr(str, "pc")) parsedValue = parsedValue * 15; + else if (strstr(str, "in")) parsedValue = parsedValue * 90; + //TODO: Implement 'em', 'ex' attributes + + //Transform into global percentage + parsedValue = parsedValue / max; + + return parsedValue; +} + + +static float _toOffset(const char* str) +{ + char* end = nullptr; + + float parsedValue = strtof(str, &end); + + if (strstr(str, "%")) parsedValue = parsedValue / 100.0; + + return parsedValue; +} + + +static int _toOpacity(const char* str) +{ + char* end = nullptr; + int a = 0; + float opacity = strtof(str, &end); + + if (end && (*end == '\0')) a = lrint(opacity * 255); + return a; +} + + +#define _PARSE_TAG(Type, Name, Name1, Tags_Array, Default) \ + static Type _to##Name1(const char* str) \ + { \ + unsigned int i; \ + \ + for (i = 0; i < sizeof(Tags_Array) / sizeof(Tags_Array[0]); i++) { \ + if (!strcmp(str, Tags_Array[i].tag)) return Tags_Array[i].Name; \ + } \ + return Default; \ + } + + +/* parse the line cap used during stroking a path. + * Value: butt | round | square | inherit + * Initial: butt + * https://www.w3.org/TR/SVG/painting.html + */ +static constexpr struct +{ + StrokeCap lineCap; + const char* tag; +} lineCapTags[] = { + { StrokeCap::Butt, "butt" }, + { StrokeCap::Round, "round" }, + { StrokeCap::Square, "square" } +}; + + +_PARSE_TAG(StrokeCap, lineCap, LineCap, lineCapTags, StrokeCap::Butt) + + +/* parse the line join used during stroking a path. + * Value: miter | round | bevel | inherit + * Initial: miter + * https://www.w3.org/TR/SVG/painting.html + */ +static constexpr struct +{ + StrokeJoin lineJoin; + const char* tag; +} lineJoinTags[] = { + { StrokeJoin::Miter, "miter" }, + { StrokeJoin::Round, "round" }, + { StrokeJoin::Bevel, "bevel" } +}; + + +_PARSE_TAG(StrokeJoin, lineJoin, LineJoin, lineJoinTags, StrokeJoin::Miter) + + +/* parse the fill rule used during filling a path. + * Value: nonzero | evenodd | inherit + * Initial: nonzero + * https://www.w3.org/TR/SVG/painting.html + */ +static constexpr struct +{ + SvgFillRule fillRule; + const char* tag; +} fillRuleTags[] = { + { SvgFillRule::OddEven, "evenodd" } +}; + + +_PARSE_TAG(SvgFillRule, fillRule, FillRule, fillRuleTags, SvgFillRule::Winding) + + +static string* _idFromUrl(const char* url) +{ + char tmp[50]; + int i = 0; + + url = _skipSpace(url, nullptr); + if ((*url) == '(') { + ++url; + url = _skipSpace(url, nullptr); + } + + if ((*url) == '#') ++url; + + while ((*url) != ')') { + tmp[i++] = *url; + ++url; + } + tmp[i] = '\0'; + + return new string(tmp); +} + + +static unsigned char _parserColor(const char* value, char** end) +{ + float r; + + r = strtof(value + 4, end); + *end = _skipSpace(*end, nullptr); + if (**end == '%') r = 255 * r / 100; + *end = _skipSpace(*end, nullptr); + + if (r < 0 || r > 255) { + *end = nullptr; + return 0; + } + + return lrint(r); +} + + +static constexpr struct +{ + const char* name; + unsigned int value; +} colors[] = { + { "aliceblue", 0xfff0f8ff }, + { "antiquewhite", 0xfffaebd7 }, + { "aqua", 0xff00ffff }, + { "aquamarine", 0xff7fffd4 }, + { "azure", 0xfff0ffff }, + { "beige", 0xfff5f5dc }, + { "bisque", 0xffffe4c4 }, + { "black", 0xff000000 }, + { "blanchedalmond", 0xffffebcd }, + { "blue", 0xff0000ff }, + { "blueviolet", 0xff8a2be2 }, + { "brown", 0xffa52a2a }, + { "burlywood", 0xffdeb887 }, + { "cadetblue", 0xff5f9ea0 }, + { "chartreuse", 0xff7fff00 }, + { "chocolate", 0xffd2691e }, + { "coral", 0xffff7f50 }, + { "cornflowerblue", 0xff6495ed }, + { "cornsilk", 0xfffff8dc }, + { "crimson", 0xffdc143c }, + { "cyan", 0xff00ffff }, + { "darkblue", 0xff00008b }, + { "darkcyan", 0xff008b8b }, + { "darkgoldenrod", 0xffb8860b }, + { "darkgray", 0xffa9a9a9 }, + { "darkgrey", 0xffa9a9a9 }, + { "darkgreen", 0xff006400 }, + { "darkkhaki", 0xffbdb76b }, + { "darkmagenta", 0xff8b008b }, + { "darkolivegreen", 0xff556b2f }, + { "darkorange", 0xffff8c00 }, + { "darkorchid", 0xff9932cc }, + { "darkred", 0xff8b0000 }, + { "darksalmon", 0xffe9967a }, + { "darkseagreen", 0xff8fbc8f }, + { "darkslateblue", 0xff483d8b }, + { "darkslategray", 0xff2f4f4f }, + { "darkslategrey", 0xff2f4f4f }, + { "darkturquoise", 0xff00ced1 }, + { "darkviolet", 0xff9400d3 }, + { "deeppink", 0xffff1493 }, + { "deepskyblue", 0xff00bfff }, + { "dimgray", 0xff696969 }, + { "dimgrey", 0xff696969 }, + { "dodgerblue", 0xff1e90ff }, + { "firebrick", 0xffb22222 }, + { "floralwhite", 0xfffffaf0 }, + { "forestgreen", 0xff228b22 }, + { "fuchsia", 0xffff00ff }, + { "gainsboro", 0xffdcdcdc }, + { "ghostwhite", 0xfff8f8ff }, + { "gold", 0xffffd700 }, + { "goldenrod", 0xffdaa520 }, + { "gray", 0xff808080 }, + { "grey", 0xff808080 }, + { "green", 0xff008000 }, + { "greenyellow", 0xffadff2f }, + { "honeydew", 0xfff0fff0 }, + { "hotpink", 0xffff69b4 }, + { "indianred", 0xffcd5c5c }, + { "indigo", 0xff4b0082 }, + { "ivory", 0xfffffff0 }, + { "khaki", 0xfff0e68c }, + { "lavender", 0xffe6e6fa }, + { "lavenderblush", 0xfffff0f5 }, + { "lawngreen", 0xff7cfc00 }, + { "lemonchiffon", 0xfffffacd }, + { "lightblue", 0xffadd8e6 }, + { "lightcoral", 0xfff08080 }, + { "lightcyan", 0xffe0ffff }, + { "lightgoldenrodyellow", 0xfffafad2 }, + { "lightgray", 0xffd3d3d3 }, + { "lightgrey", 0xffd3d3d3 }, + { "lightgreen", 0xff90ee90 }, + { "lightpink", 0xffffb6c1 }, + { "lightsalmon", 0xffffa07a }, + { "lightseagreen", 0xff20b2aa }, + { "lightskyblue", 0xff87cefa }, + { "lightslategray", 0xff778899 }, + { "lightslategrey", 0xff778899 }, + { "lightsteelblue", 0xffb0c4de }, + { "lightyellow", 0xffffffe0 }, + { "lime", 0xff00ff00 }, + { "limegreen", 0xff32cd32 }, + { "linen", 0xfffaf0e6 }, + { "magenta", 0xffff00ff }, + { "maroon", 0xff800000 }, + { "mediumaquamarine", 0xff66cdaa }, + { "mediumblue", 0xff0000cd }, + { "mediumorchid", 0xffba55d3 }, + { "mediumpurple", 0xff9370d8 }, + { "mediumseagreen", 0xff3cb371 }, + { "mediumslateblue", 0xff7b68ee }, + { "mediumspringgreen", 0xff00fa9a }, + { "mediumturquoise", 0xff48d1cc }, + { "mediumvioletred", 0xffc71585 }, + { "midnightblue", 0xff191970 }, + { "mintcream", 0xfff5fffa }, + { "mistyrose", 0xffffe4e1 }, + { "moccasin", 0xffffe4b5 }, + { "navajowhite", 0xffffdead }, + { "navy", 0xff000080 }, + { "oldlace", 0xfffdf5e6 }, + { "olive", 0xff808000 }, + { "olivedrab", 0xff6b8e23 }, + { "orange", 0xffffa500 }, + { "orangered", 0xffff4500 }, + { "orchid", 0xffda70d6 }, + { "palegoldenrod", 0xffeee8aa }, + { "palegreen", 0xff98fb98 }, + { "paleturquoise", 0xffafeeee }, + { "palevioletred", 0xffd87093 }, + { "papayawhip", 0xffffefd5 }, + { "peachpuff", 0xffffdab9 }, + { "peru", 0xffcd853f }, + { "pink", 0xffffc0cb }, + { "plum", 0xffdda0dd }, + { "powderblue", 0xffb0e0e6 }, + { "purple", 0xff800080 }, + { "red", 0xffff0000 }, + { "rosybrown", 0xffbc8f8f }, + { "royalblue", 0xff4169e1 }, + { "saddlebrown", 0xff8b4513 }, + { "salmon", 0xfffa8072 }, + { "sandybrown", 0xfff4a460 }, + { "seagreen", 0xff2e8b57 }, + { "seashell", 0xfffff5ee }, + { "sienna", 0xffa0522d }, + { "silver", 0xffc0c0c0 }, + { "skyblue", 0xff87ceeb }, + { "slateblue", 0xff6a5acd }, + { "slategray", 0xff708090 }, + { "slategrey", 0xff708090 }, + { "snow", 0xfffffafa }, + { "springgreen", 0xff00ff7f }, + { "steelblue", 0xff4682b4 }, + { "tan", 0xffd2b48c }, + { "teal", 0xff008080 }, + { "thistle", 0xffd8bfd8 }, + { "tomato", 0xffff6347 }, + { "turquoise", 0xff40e0d0 }, + { "violet", 0xffee82ee }, + { "wheat", 0xfff5deb3 }, + { "white", 0xffffffff }, + { "whitesmoke", 0xfff5f5f5 }, + { "yellow", 0xffffff00 }, + { "yellowgreen", 0xff9acd32 } +}; + + +static void _toColor(const char* str, uint8_t* r, uint8_t* g, uint8_t* b, string** ref) +{ + unsigned int i, len = strlen(str); + char *red, *green, *blue; + unsigned char tr, tg, tb; + + if (len == 4 && str[0] == '#') { + //Case for "#456" should be interprete as "#445566" + if (isxdigit(str[1]) && isxdigit(str[2]) && isxdigit(str[3])) { + char tmp[3] = { '\0', '\0', '\0' }; + tmp[0] = str[1]; + tmp[1] = str[1]; + *r = strtol(tmp, nullptr, 16); + tmp[0] = str[2]; + tmp[1] = str[2]; + *g = strtol(tmp, nullptr, 16); + tmp[0] = str[3]; + tmp[1] = str[3]; + *b = strtol(tmp, nullptr, 16); + } + } else if (len == 7 && str[0] == '#') { + if (isxdigit(str[1]) && isxdigit(str[2]) && isxdigit(str[3]) && isxdigit(str[4]) && isxdigit(str[5]) && isxdigit(str[6])) { + char tmp[3] = { '\0', '\0', '\0' }; + tmp[0] = str[1]; + tmp[1] = str[2]; + *r = strtol(tmp, nullptr, 16); + tmp[0] = str[3]; + tmp[1] = str[4]; + *g = strtol(tmp, nullptr, 16); + tmp[0] = str[5]; + tmp[1] = str[6]; + *b = strtol(tmp, nullptr, 16); + } + } else if (len >= 10 && (str[0] == 'r' || str[0] == 'R') && (str[1] == 'g' || str[1] == 'G') && (str[2] == 'b' || str[2] == 'B') && str[3] == '(' && str[len - 1] == ')') { + tr = _parserColor(str + 4, &red); + if (red && *red == ',') { + tg = _parserColor(red + 1, &green); + if (green && *green == ',') { + tb = _parserColor(green + 1, &blue); + if (blue && blue[0] == ')' && blue[1] == '\0') { + *r = tr; + *g = tg; + *b = tb; + } + } + } + } else if (len >= 3 && !strncmp(str, "url", 3)) { + *ref = _idFromUrl((const char*)(str + 3)); + } else { + //Handle named color + for (i = 0; i < (sizeof(colors) / sizeof(colors[0])); i++) { + if (!strcasecmp(colors[i].name, str)) { + *r = (((uint8_t*)(&(colors[i].value)))[2]); + *g = (((uint8_t*)(&(colors[i].value)))[1]); + *b = (((uint8_t*)(&(colors[i].value)))[0]); + } + } + } +} + + +static char* _parseNumbersArray(char* str, float* points, int* ptCount) +{ + int count = 0; + char* end = nullptr; + + str = _skipSpace(str, nullptr); + while (isdigit(*str) || *str == '-' || *str == '+' || *str == '.') { + points[count++] = strtof(str, &end); + str = end; + str = _skipSpace(str, nullptr); + if (*str == ',') ++str; + //Eat the rest of space + str = _skipSpace(str, nullptr); + } + *ptCount = count; + return str; +} + + +enum class MatrixState { + Unknown, + Matrix, + Translate, + Rotate, + Scale, + SkewX, + SkewY +}; + + +#define MATRIX_DEF(Name, Value) \ + { \ +#Name, sizeof(#Name), Value \ + } + + +static constexpr struct +{ + const char* tag; + int sz; + MatrixState state; +} matrixTags[] = { + MATRIX_DEF(matrix, MatrixState::Matrix), + MATRIX_DEF(translate, MatrixState::Translate), + MATRIX_DEF(rotate, MatrixState::Rotate), + MATRIX_DEF(scale, MatrixState::Scale), + MATRIX_DEF(skewX, MatrixState::SkewX), + MATRIX_DEF(skewY, MatrixState::SkewY) +}; + + +static void _matrixCompose(const Matrix* m1, + const Matrix* m2, + Matrix* dst) +{ + float a11, a12, a13, a21, a22, a23, a31, a32, a33; + + a11 = (m1->e11 * m2->e11) + (m1->e12 * m2->e21) + (m1->e13 * m2->e31); + a12 = (m1->e11 * m2->e12) + (m1->e12 * m2->e22) + (m1->e13 * m2->e32); + a13 = (m1->e11 * m2->e13) + (m1->e12 * m2->e23) + (m1->e13 * m2->e33); + + a21 = (m1->e21 * m2->e11) + (m1->e22 * m2->e21) + (m1->e23 * m2->e31); + a22 = (m1->e21 * m2->e12) + (m1->e22 * m2->e22) + (m1->e23 * m2->e32); + a23 = (m1->e21 * m2->e13) + (m1->e22 * m2->e23) + (m1->e23 * m2->e33); + + a31 = (m1->e31 * m2->e11) + (m1->e32 * m2->e21) + (m1->e33 * m2->e31); + a32 = (m1->e31 * m2->e12) + (m1->e32 * m2->e22) + (m1->e33 * m2->e32); + a33 = (m1->e31 * m2->e13) + (m1->e32 * m2->e23) + (m1->e33 * m2->e33); + + dst->e11 = a11; + dst->e12 = a12; + dst->e13 = a13; + dst->e21 = a21; + dst->e22 = a22; + dst->e23 = a23; + dst->e31 = a31; + dst->e32 = a32; + dst->e33 = a33; +} + + +/* parse transform attribute + * https://www.w3.org/TR/SVG/coords.html#TransformAttribute + */ +static Matrix* _parseTransformationMatrix(const char* value) +{ + unsigned int i; + float points[8]; + int ptCount = 0; + float sx, sy; + MatrixState state = MatrixState::Unknown; + Matrix* matrix = (Matrix*)calloc(1, sizeof(Matrix)); + char* str = (char*)value; + char* end = str + strlen(str); + + *matrix = { 1, 0, 0, 0, 1, 0, 0, 0, 1 }; + while (str < end) { + if (isspace(*str) || (*str == ',')) { + ++str; + continue; + } + for (i = 0; i < sizeof(matrixTags) / sizeof(matrixTags[0]); i++) { + if (!strncmp(matrixTags[i].tag, str, matrixTags[i].sz - 1)) { + state = matrixTags[i].state; + str += (matrixTags[i].sz - 1); + } + } + if (state == MatrixState::Unknown) goto error; + + str = _skipSpace(str, end); + if (*str != '(') goto error; + ++str; + str = _parseNumbersArray(str, points, &ptCount); + if (*str != ')') goto error; + ++str; + + if (state == MatrixState::Matrix) { + Matrix tmp; + + if (ptCount != 6) goto error; + + tmp = { points[0], points[2], points[4], points[1], points[3], points[5], 0, 0, 1 }; + _matrixCompose(matrix, &tmp, matrix); + } else if (state == MatrixState::Translate) { + Matrix tmp; + + if (ptCount == 1) { + tmp = { 1, 0, points[0], 0, 1, 0, 0, 0, 1 }; + _matrixCompose(matrix, &tmp, matrix); + } else if (ptCount == 2) { + tmp = { 1, 0, points[0], 0, 1, points[1], 0, 0, 1 }; + _matrixCompose(matrix, &tmp, matrix); + } else goto error; + } else if (state == MatrixState::Rotate) { + Matrix tmp; + float c, s; + //Transform to signed. + points[0] = fmod(points[0], 360); + if (points[0] < 0) points[0] += 360; + + c = cosf(points[0] * (M_PI / 180.0)); + s = sinf(points[0] * (M_PI / 180.0)); + if (ptCount == 1) { + tmp = { c, -s, 0, s, c, 0, 0, 0, 1 }; + _matrixCompose(matrix, &tmp, matrix); + } else if (ptCount == 3) { + tmp = { 1, 0, points[1], 0, 1, points[2], 0, 0, 1 }; + _matrixCompose(matrix, &tmp, matrix); + + tmp = { c, -s, 0, s, c, 0, 0, 0, 1 }; + _matrixCompose(matrix, &tmp, matrix); + + tmp = { 1, 0, -points[1], 0, 1, -points[2], 0, 0, 1 }; + _matrixCompose(matrix, &tmp, matrix); + } else { + goto error; + } + } else if (state == MatrixState::Scale) { + Matrix tmp; + if (ptCount < 1 || ptCount > 2) goto error; + + sx = points[0]; + sy = sx; + if (ptCount == 2) sy = points[1]; + tmp = { sx, 0, 0, 0, sy, 0, 0, 0, 1 }; + _matrixCompose(matrix, &tmp, matrix); + } + } +error: + return matrix; +} + + +#define LENGTH_DEF(Name, Value) \ + { \ +#Name, sizeof(#Name), Value \ + } + + +static constexpr struct +{ + const char* tag; + int sz; + SvgLengthType type; +} lengthTags[] = { + LENGTH_DEF(%, SvgLengthType::Percent), + LENGTH_DEF(px, SvgLengthType::Px), + LENGTH_DEF(pc, SvgLengthType::Pc), + LENGTH_DEF(pt, SvgLengthType::Pt), + LENGTH_DEF(mm, SvgLengthType::Mm), + LENGTH_DEF(cm, SvgLengthType::Cm), + LENGTH_DEF(in, SvgLengthType::In) +}; + + +static float _parseLength(const char* str, SvgLengthType* type) +{ + unsigned int i; + float value; + int sz = strlen(str); + + *type = SvgLengthType::Px; + for (i = 0; i < sizeof(lengthTags) / sizeof(lengthTags[0]); i++) { + if (lengthTags[i].sz - 1 == sz && !strncmp(lengthTags[i].tag, str, sz)) *type = lengthTags[i].type; + } + value = strtof(str, nullptr); + return value; +} + + +static bool _parseStyleAttr(void* data, const char* key, const char* value); + + +static bool _attrParseSvgNode(void* data, const char* key, const char* value) +{ + SvgLoaderData* loader = (SvgLoaderData*)data; + SvgNode* node = loader->svgParse->node; + SvgDocNode* doc = &(node->node.doc); + SvgLengthType type; + + //@TODO handle lenght unit. + if (!strcmp(key, "width")) { + doc->w = _parseLength(value, &type); + } else if (!strcmp(key, "height")) { + doc->h = _parseLength(value, &type); + } else if (!strcmp(key, "viewBox")) { + if (_parseNumber(&value, &doc->vx)) { + if (_parseNumber(&value, &doc->vy)) { + if (_parseNumber(&value, &doc->vw)) { + _parseNumber(&value, &doc->vh); + loader->svgParse->global.h = doc->vh; + } + loader->svgParse->global.w = doc->vw; + } + loader->svgParse->global.y = doc->vy; + } + loader->svgParse->global.x = doc->vx; + } else if (!strcmp(key, "preserveAspectRatio")) { + if (!strcmp(value, "none")) doc->preserveAspect = false; + } else if (!strcmp(key, "style")) { + return simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader); + } else { + return _parseStyleAttr(loader, key, value); + } + return true; +} + + +//https://www.w3.org/TR/SVGTiny12/painting.html#SpecifyingPaint +static void _handlePaintAttr(SvgPaint* paint, const char* value) +{ + if (!strcmp(value, "none")) { + //No paint property + paint->none = true; + return; + } + paint->none = false; + if (!strcmp(value, "currentColor")) { + paint->curColor = true; + return; + } + _toColor(value, &paint->r, &paint->g, &paint->b, &paint->url); +} + + +static void _handleColorAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value) +{ + SvgStyleProperty* style = node->style; + _toColor(value, &style->r, &style->g, &style->b, nullptr); +} + + +static void _handleFillAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value) +{ + SvgStyleProperty* style = node->style; + style->fill.flags = (SvgFillFlags)((int)style->fill.flags | (int)SvgFillFlags::Paint); + _handlePaintAttr(&style->fill.paint, value); +} + + +static void _handleStrokeAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value) +{ + SvgStyleProperty* style = node->style; + style->stroke.flags = (SvgStrokeFlags)((int)style->stroke.flags | (int)SvgStrokeFlags::Paint); + _handlePaintAttr(&style->stroke.paint, value); +} + + +static void _handleStrokeOpacityAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value) +{ + node->style->stroke.flags = (SvgStrokeFlags)((int)node->style->stroke.flags | (int)SvgStrokeFlags::Opacity); + node->style->stroke.opacity = _toOpacity(value); +} + + +static void _handleStrokeWidthAttr(SvgLoaderData* loader, SvgNode* node, const char* value) +{ + node->style->stroke.flags = (SvgStrokeFlags)((int)node->style->stroke.flags | (int)SvgStrokeFlags::Width); + node->style->stroke.width = _toFloat(loader->svgParse, value, SvgParserLengthType::Horizontal); +} + + +static void _handleStrokeLineCapAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value) +{ + node->style->stroke.flags = (SvgStrokeFlags)((int)node->style->stroke.flags | (int)SvgStrokeFlags::Cap); + node->style->stroke.cap = _toLineCap(value); +} + + +static void _handleStrokeLineJoinAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value) +{ + node->style->stroke.flags = (SvgStrokeFlags)((int)node->style->stroke.flags | (int)SvgStrokeFlags::Join); + node->style->stroke.join = _toLineJoin(value); +} + + +static void _handleFillRuleAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value) +{ + node->style->fill.flags = (SvgFillFlags)((int)node->style->fill.flags | (int)SvgFillFlags::FillRule); + node->style->fill.fillRule = _toFillRule(value); +} + + +static void _handleOpacityAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value) +{ + node->style->opacity = _toOpacity(value); +} + + +static void _handleFillOpacityAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value) +{ + node->style->fill.flags = (SvgFillFlags)((int)node->style->fill.flags | (int)SvgFillFlags::Opacity); + node->style->fill.opacity = _toOpacity(value); +} + + +static void _handleTransformAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value) +{ + node->transform = _parseTransformationMatrix(value); +} + + +static void _handleDisplayAttr(TVG_UNUSED SvgLoaderData* loader, SvgNode* node, const char* value) +{ + //TODO : The display attribute can have various values as well as "none". + // The default is "inline" which means visible and "none" means invisible. + // Depending on the type of node, additional functionality may be required. + // refer to https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/display + if (!strcmp(value, "none")) node->display = false; + else node->display = true; +} + + +typedef void (*styleMethod)(SvgLoaderData* loader, SvgNode* node, const char* value); + + +#define STYLE_DEF(Name, Name1) \ + { \ +#Name, sizeof(#Name), _handle##Name1##Attr \ + } + + +static constexpr struct +{ + const char* tag; + int sz; + styleMethod tagHandler; +} styleTags[] = { + STYLE_DEF(color, Color), + STYLE_DEF(fill, Fill), + STYLE_DEF(fill-rule, FillRule), + STYLE_DEF(fill-opacity, FillOpacity), + STYLE_DEF(opacity, Opacity), + STYLE_DEF(stroke, Stroke), + STYLE_DEF(stroke-width, StrokeWidth), + STYLE_DEF(stroke-linejoin, StrokeLineJoin), + STYLE_DEF(stroke-linecap, StrokeLineCap), + STYLE_DEF(stroke-opacity, StrokeOpacity), + STYLE_DEF(transform, Transform), + STYLE_DEF(display, Display) +}; + + +static bool _parseStyleAttr(void* data, const char* key, const char* value) +{ + SvgLoaderData* loader = (SvgLoaderData*)data; + SvgNode* node = loader->svgParse->node; + unsigned int i; + int sz; + if (!key || !value) return false; + + //Trim the white space + key = _skipSpace(key, nullptr); + + value = _skipSpace(value, nullptr); + + sz = strlen(key); + for (i = 0; i < sizeof(styleTags) / sizeof(styleTags[0]); i++) { + if (styleTags[i].sz - 1 == sz && !strncmp(styleTags[i].tag, key, sz)) { + styleTags[i].tagHandler(loader, node, value); + return true; + } + } + + return true; +} + +/* parse g node + * https://www.w3.org/TR/SVG/struct.html#Groups + */ +static bool _attrParseGNode(void* data, const char* key, const char* value) +{ + SvgLoaderData* loader = (SvgLoaderData*)data; + SvgNode* node = loader->svgParse->node; + + if (!strcmp(key, "style")) { + return simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader); + } else if (!strcmp(key, "transform")) { + node->transform = _parseTransformationMatrix(value); + } else if (!strcmp(key, "id")) { + node->id = _copyId(value); + } else { + return _parseStyleAttr(loader, key, value); + } + return true; +} + + +static SvgNode* _createNode(SvgNode* parent, SvgNodeType type) +{ + SvgNode* node = (SvgNode*)calloc(1, sizeof(SvgNode)); + + //Default fill property + node->style = (SvgStyleProperty*)calloc(1, sizeof(SvgStyleProperty)); + + //Update the default value of stroke and fill + //https://www.w3.org/TR/SVGTiny12/painting.html#SpecifyingPaint + node->style->fill.paint.none = false; + //Default fill opacity is 1 + node->style->fill.opacity = 255; + node->style->opacity = 255; + + //Default fill rule is nonzero + node->style->fill.fillRule = SvgFillRule::Winding; + + //Default stroke is none + node->style->stroke.paint.none = true; + //Default stroke opacity is 1 + node->style->stroke.opacity = 255; + //Default stroke width is 1 + node->style->stroke.width = 1; + //Default line cap is butt + node->style->stroke.cap = StrokeCap::Butt; + //Default line join is miter + node->style->stroke.join = StrokeJoin::Miter; + node->style->stroke.scale = 1.0; + + //Default display is true("inline"). + node->display = true; + + node->parent = parent; + node->type = type; + + if (parent) parent->child.push_back(node); + return node; +} + + +static SvgNode* _createDefsNode(TVG_UNUSED SvgLoaderData* loader, TVG_UNUSED SvgNode* parent, const char* buf, unsigned bufLength) +{ + SvgNode* node = _createNode(nullptr, SvgNodeType::Defs); + simpleXmlParseAttributes(buf, bufLength, nullptr, node); + return node; +} + + +static SvgNode* _createGNode(TVG_UNUSED SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength) +{ + loader->svgParse->node = _createNode(parent, SvgNodeType::G); + + simpleXmlParseAttributes(buf, bufLength, _attrParseGNode, loader); + return loader->svgParse->node; +} + + +static SvgNode* _createSvgNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength) +{ + loader->svgParse->node = _createNode(parent, SvgNodeType::Doc); + SvgDocNode* doc = &(loader->svgParse->node->node.doc); + + doc->preserveAspect = true; + simpleXmlParseAttributes(buf, bufLength, _attrParseSvgNode, loader); + + return loader->svgParse->node; +} + + +static bool _attrParsePathNode(void* data, const char* key, const char* value) +{ + SvgLoaderData* loader = (SvgLoaderData*)data; + SvgNode* node = loader->svgParse->node; + SvgPathNode* path = &(node->node.path); + + if (!strcmp(key, "d")) { + //Temporary: need to copy + path->path = _copyId(value); + } else if (!strcmp(key, "style")) { + return simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader); + } else if (!strcmp(key, "id")) { + node->id = _copyId(value); + } else { + return _parseStyleAttr(loader, key, value); + } + return true; +} + + +static SvgNode* _createPathNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength) +{ + loader->svgParse->node = _createNode(parent, SvgNodeType::Path); + + simpleXmlParseAttributes(buf, bufLength, _attrParsePathNode, loader); + + return loader->svgParse->node; +} + + +static constexpr struct +{ + const char* tag; + SvgParserLengthType type; + int sz; + size_t offset; +} circleTags[] = { + {"cx", SvgParserLengthType::Horizontal, sizeof("cx"), offsetof(SvgCircleNode, cx)}, + {"cy", SvgParserLengthType::Vertical, sizeof("cy"), offsetof(SvgCircleNode, cy)}, + {"r", SvgParserLengthType::Other, sizeof("r"), offsetof(SvgCircleNode, r)} +}; + + +/* parse the attributes for a circle element. + * https://www.w3.org/TR/SVG/shapes.html#CircleElement + */ +static bool _attrParseCircleNode(void* data, const char* key, const char* value) +{ + SvgLoaderData* loader = (SvgLoaderData*)data; + SvgNode* node = loader->svgParse->node; + SvgCircleNode* circle = &(node->node.circle); + unsigned int i; + unsigned char* array; + int sz = strlen(key); + + array = (unsigned char*)circle; + for (i = 0; i < sizeof(circleTags) / sizeof(circleTags[0]); i++) { + if (circleTags[i].sz - 1 == sz && !strncmp(circleTags[i].tag, key, sz)) { + *((float*)(array + circleTags[i].offset)) = _toFloat(loader->svgParse, value, circleTags[i].type); + return true; + } + } + + if (!strcmp(key, "style")) { + return simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader); + } else if (!strcmp(key, "id")) { + node->id = _copyId(value); + } else { + return _parseStyleAttr(loader, key, value); + } + return true; +} + + +static SvgNode* _createCircleNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength) +{ + loader->svgParse->node = _createNode(parent, SvgNodeType::Circle); + + simpleXmlParseAttributes(buf, bufLength, _attrParseCircleNode, loader); + return loader->svgParse->node; +} + + +static constexpr struct +{ + const char* tag; + SvgParserLengthType type; + int sz; + size_t offset; +} ellipseTags[] = { + {"cx", SvgParserLengthType::Horizontal, sizeof("cx"), offsetof(SvgEllipseNode, cx)}, + {"cy", SvgParserLengthType::Vertical, sizeof("cy"), offsetof(SvgEllipseNode, cy)}, + {"rx", SvgParserLengthType::Horizontal, sizeof("rx"), offsetof(SvgEllipseNode, rx)}, + {"ry", SvgParserLengthType::Vertical, sizeof("ry"), offsetof(SvgEllipseNode, ry)} +}; + + +/* parse the attributes for an ellipse element. + * https://www.w3.org/TR/SVG/shapes.html#EllipseElement + */ +static bool _attrParseEllipseNode(void* data, const char* key, const char* value) +{ + SvgLoaderData* loader = (SvgLoaderData*)data; + SvgNode* node = loader->svgParse->node; + SvgEllipseNode* ellipse = &(node->node.ellipse); + unsigned int i; + unsigned char* array; + int sz = strlen(key); + + array = (unsigned char*)ellipse; + for (i = 0; i < sizeof(ellipseTags) / sizeof(ellipseTags[0]); i++) { + if (ellipseTags[i].sz - 1 == sz && !strncmp(ellipseTags[i].tag, key, sz)) { + *((float*)(array + ellipseTags[i].offset)) = _toFloat(loader->svgParse, value, ellipseTags[i].type); + return true; + } + } + + if (!strcmp(key, "id")) { + node->id = _copyId(value); + } else if (!strcmp(key, "style")) { + return simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader); + } else { + return _parseStyleAttr(loader, key, value); + } + return true; +} + + +static SvgNode* _createEllipseNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength) +{ + loader->svgParse->node = _createNode(parent, SvgNodeType::Ellipse); + + simpleXmlParseAttributes(buf, bufLength, _attrParseEllipseNode, loader); + return loader->svgParse->node; +} + + +static bool _attrParsePolygonPoints(const char* str, float** points, int* ptCount) +{ + float tmp[50]; + int tmpCount = 0; + int count = 0; + float num; + float *pointArray = nullptr, *tmpArray; + + while (_parseNumber(&str, &num)) { + tmp[tmpCount++] = num; + if (tmpCount == 50) { + tmpArray = (float*)realloc(pointArray, (count + tmpCount) * sizeof(float)); + if (!tmpArray) goto error_alloc; + pointArray = tmpArray; + memcpy(&pointArray[count], tmp, tmpCount * sizeof(float)); + count += tmpCount; + tmpCount = 0; + } + } + + if (tmpCount > 0) { + tmpArray = (float*)realloc(pointArray, (count + tmpCount) * sizeof(float)); + if (!tmpArray) goto error_alloc; + pointArray = tmpArray; + memcpy(&pointArray[count], tmp, tmpCount * sizeof(float)); + count += tmpCount; + } + *ptCount = count; + *points = pointArray; + return true; + +error_alloc: + //LOG: allocation for point array failed. out of memory + return false; +} + + +/* parse the attributes for a polygon element. + * https://www.w3.org/TR/SVG/shapes.html#PolylineElement + */ +static bool _attrParsePolygonNode(void* data, const char* key, const char* value) +{ + SvgLoaderData* loader = (SvgLoaderData*)data; + SvgNode* node = loader->svgParse->node; + SvgPolygonNode* polygon = nullptr; + + if (node->type == SvgNodeType::Polygon) polygon = &(node->node.polygon); + else polygon = &(node->node.polyline); + + if (!strcmp(key, "points")) { + return _attrParsePolygonPoints(value, &polygon->points, &polygon->pointsCount); + } else if (!strcmp(key, "style")) { + return simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader); + } else if (!strcmp(key, "id")) { + node->id = _copyId(value); + } else { + return _parseStyleAttr(loader, key, value); + } + return true; +} + + +static SvgNode* _createPolygonNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength) +{ + loader->svgParse->node = _createNode(parent, SvgNodeType::Polygon); + + simpleXmlParseAttributes(buf, bufLength, _attrParsePolygonNode, loader); + return loader->svgParse->node; +} + + +static SvgNode* _createPolylineNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength) +{ + loader->svgParse->node = _createNode(parent, SvgNodeType::Polyline); + + simpleXmlParseAttributes(buf, bufLength, _attrParsePolygonNode, loader); + return loader->svgParse->node; +} + +static constexpr struct +{ + const char* tag; + SvgParserLengthType type; + int sz; + size_t offset; +} rectTags[] = { + {"x", SvgParserLengthType::Horizontal, sizeof("x"), offsetof(SvgRectNode, x)}, + {"y", SvgParserLengthType::Vertical, sizeof("y"), offsetof(SvgRectNode, y)}, + {"width", SvgParserLengthType::Horizontal, sizeof("width"), offsetof(SvgRectNode, w)}, + {"height", SvgParserLengthType::Vertical, sizeof("height"), offsetof(SvgRectNode, h)}, + {"rx", SvgParserLengthType::Horizontal, sizeof("rx"), offsetof(SvgRectNode, rx)}, + {"ry", SvgParserLengthType::Vertical, sizeof("ry"), offsetof(SvgRectNode, ry)} +}; + + +/* parse the attributes for a rect element. + * https://www.w3.org/TR/SVG/shapes.html#RectElement + */ +static bool _attrParseRectNode(void* data, const char* key, const char* value) +{ + SvgLoaderData* loader = (SvgLoaderData*)data; + SvgNode* node = loader->svgParse->node; + SvgRectNode* rect = &(node->node.rect); + unsigned int i; + unsigned char* array; + bool ret = true; + int sz = strlen(key); + + array = (unsigned char*)rect; + for (i = 0; i < sizeof(rectTags) / sizeof(rectTags[0]); i++) { + if (rectTags[i].sz - 1 == sz && !strncmp(rectTags[i].tag, key, sz)) { + *((float*)(array + rectTags[i].offset)) = _toFloat(loader->svgParse, value, rectTags[i].type); + return ret; + } + } + + + if (!strcmp(key, "id")) { + node->id = _copyId(value); + } else if (!strcmp(key, "style")) { + ret = simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader); + } else { + ret = _parseStyleAttr(loader, key, value); + } + + if (!(rect->rx - 0 <= FLT_EPSILON) && (rect->ry - 0 <= FLT_EPSILON)) rect->ry = rect->rx; + if (!(rect->ry - 0 <= FLT_EPSILON) && (rect->rx - 0 <= FLT_EPSILON)) rect->rx = rect->ry; + + return ret; +} + + +static SvgNode* _createRectNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength) +{ + loader->svgParse->node = _createNode(parent, SvgNodeType::Rect); + + simpleXmlParseAttributes(buf, bufLength, _attrParseRectNode, loader); + return loader->svgParse->node; +} + + +static constexpr struct +{ + const char* tag; + SvgParserLengthType type; + int sz; + size_t offset; +} lineTags[] = { + {"x1", SvgParserLengthType::Horizontal, sizeof("x1"), offsetof(SvgLineNode, x1)}, + {"y1", SvgParserLengthType::Vertical, sizeof("y1"), offsetof(SvgLineNode, y1)}, + {"x2", SvgParserLengthType::Horizontal, sizeof("x2"), offsetof(SvgLineNode, x2)}, + {"y2", SvgParserLengthType::Vertical, sizeof("y2"), offsetof(SvgLineNode, y2)} +}; + + +/* parse the attributes for a rect element. + * https://www.w3.org/TR/SVG/shapes.html#LineElement + */ +static bool _attrParseLineNode(void* data, const char* key, const char* value) +{ + SvgLoaderData* loader = (SvgLoaderData*)data; + SvgNode* node = loader->svgParse->node; + SvgLineNode* line = &(node->node.line); + unsigned int i; + unsigned char* array; + int sz = strlen(key); + + array = (unsigned char*)line; + for (i = 0; i < sizeof(lineTags) / sizeof(lineTags[0]); i++) { + if (lineTags[i].sz - 1 == sz && !strncmp(lineTags[i].tag, key, sz)) { + *((float*)(array + lineTags[i].offset)) = _toFloat(loader->svgParse, value, lineTags[i].type); + return true; + } + } + + if (!strcmp(key, "id")) { + node->id = _copyId(value); + } else if (!strcmp(key, "style")) { + return simpleXmlParseW3CAttribute(value, _parseStyleAttr, loader); + } else { + return _parseStyleAttr(loader, key, value); + } + return true; +} + + +static SvgNode* _createLineNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength) +{ + loader->svgParse->node = _createNode(parent, SvgNodeType::Line); + + simpleXmlParseAttributes(buf, bufLength, _attrParseLineNode, loader); + return loader->svgParse->node; +} + + +static string* _idFromHref(const char* href) +{ + href = _skipSpace(href, nullptr); + if ((*href) == '#') href++; + return new string(href); +} + + +static SvgNode* _getDefsNode(SvgNode* node) +{ + if (!node) return nullptr; + + while (node->parent != nullptr) { + node = node->parent; + } + + if (node->type == SvgNodeType::Doc) return node->node.doc.defs; + + return nullptr; +} + + +static SvgNode* _findChildById(SvgNode* node, const char* id) +{ + + if (!node) return nullptr; + + for (vector::iterator itrChild = node->child.begin(); itrChild != node->child.end(); itrChild++) { + if (((*itrChild)->id != nullptr) && !strcmp((*itrChild)->id->c_str(), id)) return *itrChild; + } + return nullptr; +} + + +static void _cloneGradStops(vector *dst, vector src) +{ + for (auto colorStop : src) { + auto stop = static_cast(malloc(sizeof(Fill::ColorStop))); + *stop = *colorStop; + dst->push_back(stop); + } +} + + +static SvgStyleGradient* _cloneGradient(SvgStyleGradient* from) +{ + SvgStyleGradient* grad; + + if (!from) return nullptr; + + grad = (SvgStyleGradient*)calloc(1, sizeof(SvgStyleGradient)); + grad->type = from->type; + grad->id = from->id ? _copyId(from->id->c_str()) : nullptr; + grad->ref = from->ref ? _copyId(from->ref->c_str()) : nullptr; + grad->spread = from->spread; + grad->usePercentage = from->usePercentage; + grad->userSpace = from->userSpace; + if (from->transform) { + grad->transform = (Matrix*)calloc(1, sizeof(Matrix)); + memcpy(grad->transform, from->transform, sizeof(Matrix)); + } + if (grad->type == SvgGradientType::Linear) { + grad->linear = (SvgLinearGradient*)calloc(1, sizeof(SvgLinearGradient)); + memcpy(grad->linear, from->linear, sizeof(SvgLinearGradient)); + } else if (grad->type == SvgGradientType::Radial) { + grad->radial = (SvgRadialGradient*)calloc(1, sizeof(SvgRadialGradient)); + memcpy(grad->radial, from->radial, sizeof(SvgRadialGradient)); + } + + _cloneGradStops(&(grad->stops), from->stops); + return grad; +} + + +static void _copyAttr(SvgNode* to, SvgNode* from) +{ + //Copy matrix attribute + if (from->transform) { + to->transform = (Matrix*)calloc(1, sizeof(Matrix)); + memcpy(to->transform, from->transform, sizeof(Matrix)); + } + //Copy style attribute; + memcpy(to->style, from->style, sizeof(SvgStyleProperty)); + + //Copy node attribute + switch (from->type) { + case SvgNodeType::Circle: { + to->node.circle.cx = from->node.circle.cx; + to->node.circle.cy = from->node.circle.cy; + to->node.circle.r = from->node.circle.r; + break; + } + case SvgNodeType::Ellipse: { + to->node.ellipse.cx = from->node.ellipse.cx; + to->node.ellipse.cy = from->node.ellipse.cy; + to->node.ellipse.rx = from->node.ellipse.rx; + to->node.ellipse.ry = from->node.ellipse.ry; + break; + } + case SvgNodeType::Rect: { + to->node.rect.x = from->node.rect.x; + to->node.rect.y = from->node.rect.y; + to->node.rect.w = from->node.rect.w; + to->node.rect.h = from->node.rect.h; + to->node.rect.rx = from->node.rect.rx; + to->node.rect.ry = from->node.rect.ry; + break; + } + case SvgNodeType::Line: { + to->node.line.x1 = from->node.line.x1; + to->node.line.y1 = from->node.line.y1; + to->node.line.x2 = from->node.line.x2; + to->node.line.y2 = from->node.line.y2; + break; + } + case SvgNodeType::Path: { + to->node.path.path = new string(from->node.path.path->c_str()); + break; + } + case SvgNodeType::Polygon: { + to->node.polygon.pointsCount = from->node.polygon.pointsCount; + to->node.polygon.points = (float*)calloc(to->node.polygon.pointsCount, sizeof(float)); + break; + } + case SvgNodeType::Polyline: { + to->node.polyline.pointsCount = from->node.polyline.pointsCount; + to->node.polyline.points = (float*)calloc(to->node.polyline.pointsCount, sizeof(float)); + break; + } + default: { + break; + } + } +} + + +static void _cloneNode(SvgNode* from, SvgNode* parent) +{ + SvgNode* newNode; + if (!from) return; + + newNode = _createNode(parent, from->type); + _copyAttr(newNode, from); + + for (vector::iterator itrChild = from->child.begin(); itrChild != from->child.end(); itrChild++) { + _cloneNode(*itrChild, newNode); + } + + _freeNode(newNode); +} + + +static bool _attrParseUseNode(void* data, const char* key, const char* value) +{ + SvgLoaderData* loader = (SvgLoaderData*)data; + SvgNode *defs, *nodeFrom, *node = loader->svgParse->node; + string* id; + + if (!strcmp(key, "xlink:href")) { + id = _idFromHref(value); + defs = _getDefsNode(node); + nodeFrom = _findChildById(defs, id->c_str()); + _cloneNode(nodeFrom, node); + delete id; + } else { + _attrParseGNode(data, key, value); + } + return true; +} + + +static SvgNode* _createUseNode(SvgLoaderData* loader, SvgNode* parent, const char* buf, unsigned bufLength) +{ + loader->svgParse->node = _createNode(parent, SvgNodeType::G); + + simpleXmlParseAttributes(buf, bufLength, _attrParseUseNode, loader); + return loader->svgParse->node; +} + + +#define TAG_DEF(Name, Name1) \ + { \ +#Name, sizeof(#Name), _create##Name1##Node \ + } + + +//TODO: Implement 'text' primitive +static constexpr struct +{ + const char* tag; + int sz; + FactoryMethod tagHandler; +} graphicsTags[] = { + TAG_DEF(use, Use), + TAG_DEF(circle, Circle), + TAG_DEF(ellipse, Ellipse), + TAG_DEF(path, Path), + TAG_DEF(polygon, Polygon), + TAG_DEF(rect, Rect), + TAG_DEF(polyline, Polyline), + TAG_DEF(line, Line), +}; + + +static constexpr struct +{ + const char* tag; + int sz; + FactoryMethod tagHandler; +} groupTags[] = { + TAG_DEF(defs, Defs), + TAG_DEF(g, G), + TAG_DEF(svg, Svg), +}; + + +#define FIND_FACTORY(Short_Name, Tags_Array) \ + static FactoryMethod \ + _find##Short_Name##Factory(const char* name) \ + { \ + unsigned int i; \ + int sz = strlen(name); \ + \ + for (i = 0; i < sizeof(Tags_Array) / sizeof(Tags_Array[0]); i++) { \ + if (Tags_Array[i].sz - 1 == sz && !strncmp(Tags_Array[i].tag, name, sz)) { \ + return Tags_Array[i].tagHandler; \ + } \ + } \ + return nullptr; \ + } + +FIND_FACTORY(Group, groupTags) +FIND_FACTORY(Graphics, graphicsTags) + + +FillSpread _parseSpreadValue(const char* value) +{ + auto spread = FillSpread::Pad; + + if (!strcmp(value, "reflect")) { + spread = FillSpread::Reflect; + } else if (!strcmp(value, "repeat")) { + spread = FillSpread::Repeat; + } + + return spread; +} + + +static void _handleRadialCxAttr(SvgLoaderData* loader, SvgRadialGradient* radial, const char* value) +{ + radial->cx = _gradientToFloat(loader->svgParse, value, SvgParserLengthType::Horizontal); + if (!loader->svgParse->gradient.parsedFx) radial->fx = radial->cx; +} + + +static void _handleRadialCyAttr(SvgLoaderData* loader, SvgRadialGradient* radial, const char* value) +{ + radial->cy = _gradientToFloat(loader->svgParse, value, SvgParserLengthType::Vertical); + if (!loader->svgParse->gradient.parsedFy) radial->fy = radial->cy; +} + + +static void _handleRadialFxAttr(SvgLoaderData* loader, SvgRadialGradient* radial, const char* value) +{ + radial->fx = _gradientToFloat(loader->svgParse, value, SvgParserLengthType::Horizontal); + loader->svgParse->gradient.parsedFx = true; +} + + +static void _handleRadialFyAttr(SvgLoaderData* loader, SvgRadialGradient* radial, const char* value) +{ + radial->fy = _gradientToFloat(loader->svgParse, value, SvgParserLengthType::Vertical); + loader->svgParse->gradient.parsedFy = true; +} + + +static void _handleRadialRAttr(SvgLoaderData* loader, SvgRadialGradient* radial, const char* value) +{ + radial->r = _gradientToFloat(loader->svgParse, value, SvgParserLengthType::Other); +} + + +static void _recalcRadialCxAttr(SvgLoaderData* loader, SvgRadialGradient* radial, bool userSpace) +{ + if (!userSpace) radial->cx = radial->cx * loader->svgParse->global.w; +} + + +static void _recalcRadialCyAttr(SvgLoaderData* loader, SvgRadialGradient* radial, bool userSpace) +{ + if (!userSpace) radial->cy = radial->cy * loader->svgParse->global.h; +} + + +static void _recalcRadialFxAttr(SvgLoaderData* loader, SvgRadialGradient* radial, bool userSpace) +{ + if (!userSpace) radial->fx = radial->fx * loader->svgParse->global.w; +} + + +static void _recalcRadialFyAttr(SvgLoaderData* loader, SvgRadialGradient* radial, bool userSpace) +{ + if (!userSpace) radial->fy = radial->fy * loader->svgParse->global.h; +} + + +static void _recalcRadialRAttr(SvgLoaderData* loader, SvgRadialGradient* radial, bool userSpace) +{ + if (!userSpace) radial->r = radial->r * (sqrt(pow(loader->svgParse->global.h, 2) + pow(loader->svgParse->global.w, 2)) / sqrt(2.0)); +} + + +typedef void (*radialMethod)(SvgLoaderData* loader, SvgRadialGradient* radial, const char* value); +typedef void (*radialMethodRecalc)(SvgLoaderData* loader, SvgRadialGradient* radial, bool userSpace); + + +#define RADIAL_DEF(Name, Name1) \ + { \ +#Name, sizeof(#Name), _handleRadial##Name1##Attr, _recalcRadial##Name1##Attr \ + } + + +static constexpr struct +{ + const char* tag; + int sz; + radialMethod tagHandler; + radialMethodRecalc tagRecalc; +} radialTags[] = { + RADIAL_DEF(cx, Cx), + RADIAL_DEF(cy, Cy), + RADIAL_DEF(fx, Fx), + RADIAL_DEF(fy, Fy), + RADIAL_DEF(r, R) +}; + + +static bool _attrParseRadialGradientNode(void* data, const char* key, const char* value) +{ + SvgLoaderData* loader = (SvgLoaderData*)data; + SvgStyleGradient* grad = loader->svgParse->styleGrad; + SvgRadialGradient* radial = grad->radial; + unsigned int i; + int sz = strlen(key); + + for (i = 0; i < sizeof(radialTags) / sizeof(radialTags[0]); i++) { + if (radialTags[i].sz - 1 == sz && !strncmp(radialTags[i].tag, key, sz)) { + radialTags[i].tagHandler(loader, radial, value); + return true; + } + } + + if (!strcmp(key, "id")) { + grad->id = _copyId(value); + } else if (!strcmp(key, "spreadMethod")) { + grad->spread = _parseSpreadValue(value); + } else if (!strcmp(key, "xlink:href")) { + grad->ref = _idFromHref(value); + } else if (!strcmp(key, "gradientUnits") && !strcmp(value, "userSpaceOnUse")) { + grad->userSpace = true; + } + + return true; +} + + +static SvgStyleGradient* _createRadialGradient(SvgLoaderData* loader, const char* buf, unsigned bufLength) +{ + unsigned int i = 0; + SvgStyleGradient* grad = (SvgStyleGradient*)calloc(1, sizeof(SvgStyleGradient)); + loader->svgParse->styleGrad = grad; + + grad->type = SvgGradientType::Radial; + grad->userSpace = false; + grad->radial = (SvgRadialGradient*)calloc(1, sizeof(SvgRadialGradient)); + /** + * Default values of gradient + */ + grad->radial->cx = 0.5; + grad->radial->cy = 0.5; + grad->radial->fx = 0.5; + grad->radial->fy = 0.5; + grad->radial->r = 0.5; + + loader->svgParse->gradient.parsedFx = false; + loader->svgParse->gradient.parsedFy = false; + simpleXmlParseAttributes(buf, bufLength, + _attrParseRadialGradientNode, loader); + + for (i = 0; i < sizeof(radialTags) / sizeof(radialTags[0]); i++) { + radialTags[i].tagRecalc(loader, grad->radial, grad->userSpace); + } + + grad->usePercentage = true; + + return loader->svgParse->styleGrad; +} + + +static bool _attrParseStops(void* data, const char* key, const char* value) +{ + SvgLoaderData* loader = (SvgLoaderData*)data; + auto stop = loader->svgParse->gradStop; + + if (!strcmp(key, "offset")) { + stop->offset = _toOffset(value); + } else if (!strcmp(key, "stop-opacity")) { + stop->a = _toOpacity(value); + } else if (!strcmp(key, "stop-color")) { + _toColor(value, &stop->r, &stop->g, &stop->b, nullptr); + } else if (!strcmp(key, "style")) { + simpleXmlParseW3CAttribute(value, + _attrParseStops, data); + } + + return true; +} + + +static void _handleLinearX1Attr(SvgLoaderData* loader, SvgLinearGradient* linear, const char* value) +{ + linear->x1 = _gradientToFloat(loader->svgParse, value, SvgParserLengthType::Horizontal); +} + + +static void _handleLinearY1Attr(SvgLoaderData* loader, SvgLinearGradient* linear, const char* value) +{ + linear->y1 = _gradientToFloat(loader->svgParse, value, SvgParserLengthType::Vertical); +} + + +static void _handleLinearX2Attr(SvgLoaderData* loader, SvgLinearGradient* linear, const char* value) +{ + linear->x2 = _gradientToFloat(loader->svgParse, value, SvgParserLengthType::Horizontal); +} + + +static void _handleLinearY2Attr(SvgLoaderData* loader, SvgLinearGradient* linear, const char* value) +{ + linear->y2 = _gradientToFloat(loader->svgParse, value, SvgParserLengthType::Vertical); +} + + +static void _recalcLinearX1Attr(SvgLoaderData* loader, SvgLinearGradient* linear, bool userSpace) +{ + if (!userSpace) linear->x1 = linear->x1 * loader->svgParse->global.w; +} + + +static void _recalcLinearY1Attr(SvgLoaderData* loader, SvgLinearGradient* linear, bool userSpace) +{ + if (!userSpace) linear->y1 = linear->y1 * loader->svgParse->global.h; +} + + +static void _recalcLinearX2Attr(SvgLoaderData* loader, SvgLinearGradient* linear, bool userSpace) +{ + if (!userSpace) linear->x2 = linear->x2 * loader->svgParse->global.w; +} + + +static void _recalcLinearY2Attr(SvgLoaderData* loader, SvgLinearGradient* linear, bool userSpace) +{ + if (!userSpace) linear->y2 = linear->y2 * loader->svgParse->global.h; +} + + +typedef void (*Linear_Method)(SvgLoaderData* loader, SvgLinearGradient* linear, const char* value); +typedef void (*Linear_Method_Recalc)(SvgLoaderData* loader, SvgLinearGradient* linear, bool userSpace); + + +#define LINEAR_DEF(Name, Name1) \ + { \ +#Name, sizeof(#Name), _handleLinear##Name1##Attr, _recalcLinear##Name1##Attr \ + } + + +static constexpr struct +{ + const char* tag; + int sz; + Linear_Method tagHandler; + Linear_Method_Recalc tagRecalc; +} linear_tags[] = { + LINEAR_DEF(x1, X1), + LINEAR_DEF(y1, Y1), + LINEAR_DEF(x2, X2), + LINEAR_DEF(y2, Y2) +}; + + +static bool _attrParseLinearGradientNode(void* data, const char* key, const char* value) +{ + SvgLoaderData* loader = (SvgLoaderData*)data; + SvgStyleGradient* grad = loader->svgParse->styleGrad; + SvgLinearGradient* linear = grad->linear; + unsigned int i; + int sz = strlen(key); + + for (i = 0; i < sizeof(linear_tags) / sizeof(linear_tags[0]); i++) { + if (linear_tags[i].sz - 1 == sz && !strncmp(linear_tags[i].tag, key, sz)) { + linear_tags[i].tagHandler(loader, linear, value); + return true; + } + } + + if (!strcmp(key, "id")) { + grad->id = _copyId(value); + } else if (!strcmp(key, "spreadMethod")) { + grad->spread = _parseSpreadValue(value); + } else if (!strcmp(key, "xlink:href")) { + grad->ref = _idFromHref(value); + } else if (!strcmp(key, "gradientUnits") && !strcmp(value, "userSpaceOnUse")) { + grad->userSpace = true; + } else if (!strcmp(key, "gradientTransform")) { + grad->transform = _parseTransformationMatrix(value); + } + + return true; +} + + +static SvgStyleGradient* _createLinearGradient(SvgLoaderData* loader, const char* buf, unsigned bufLength) +{ + SvgStyleGradient* grad = (SvgStyleGradient*)calloc(1, sizeof(SvgStyleGradient)); + loader->svgParse->styleGrad = grad; + unsigned int i; + + grad->type = SvgGradientType::Linear; + grad->userSpace = false; + grad->linear = (SvgLinearGradient*)calloc(1, sizeof(SvgLinearGradient)); + /** + * Default value of x2 is 100% + */ + grad->linear->x2 = 1; + simpleXmlParseAttributes(buf, bufLength, _attrParseLinearGradientNode, loader); + + for (i = 0; i < sizeof(linear_tags) / sizeof(linear_tags[0]); i++) { + linear_tags[i].tagRecalc(loader, grad->linear, grad->userSpace); + } + + grad->usePercentage = true; + + return loader->svgParse->styleGrad; +} + + +#define GRADIENT_DEF(Name, Name1) \ + { \ +#Name, sizeof(#Name), _create##Name1 \ + } + + +/** + * For all Gradients lengths would be calculated into percentages related to + * canvas width and height. + * + * if user then recalculate actual pixels into percentages + */ +static constexpr struct +{ + const char* tag; + int sz; + GradientFactoryMethod tagHandler; +} gradientTags[] = { + GRADIENT_DEF(linearGradient, LinearGradient), + GRADIENT_DEF(radialGradient, RadialGradient) +}; + + +static GradientFactoryMethod _findGradientFactory(const char* name) +{ + unsigned int i; + int sz = strlen(name); + + for (i = 0; i < sizeof(gradientTags) / sizeof(gradientTags[0]); i++) { + if (gradientTags[i].sz - 1 == sz && !strncmp(gradientTags[i].tag, name, sz)) { + return gradientTags[i].tagHandler; + } + } + return nullptr; +} + + +#define POP_TAG(Tag) \ + { \ +#Tag, sizeof(#Tag) \ + } + + +static constexpr struct +{ + const char* tag; + size_t sz; +} popArray[] = { + POP_TAG(g), + POP_TAG(svg), + POP_TAG(defs) +}; + + +static void _svgLoaderParerXmlClose(SvgLoaderData* loader, const char* content) +{ + unsigned int i; + + content = _skipSpace(content, nullptr); + + for (i = 0; i < sizeof(popArray) / sizeof(popArray[0]); i++) { + if (!strncmp(content, popArray[i].tag, popArray[i].sz - 1)) { + loader->stack.pop_back(); + break; + } + } + + loader->level--; +} + + +static void _svgLoaderParserXmlOpen(SvgLoaderData* loader, const char* content, unsigned int length) +{ + const char* attrs = nullptr; + int attrsLength = 0; + int sz = length; + char tagName[20] = ""; + FactoryMethod method; + GradientFactoryMethod gradientMethod; + SvgNode *node = nullptr, *parent = nullptr; + loader->level++; + attrs = simpleXmlFindAttributesTag(content, length); + + if (!attrs) { + //Parse the empty tag + attrs = content; + while ((attrs != nullptr) && *attrs != '>') attrs++; + } + + if (attrs) { + //Find out the tag name starting from content till sz length + sz = attrs - content; + attrsLength = length - sz; + while ((sz > 0) && (isspace(content[sz - 1]))) sz--; + strncpy(tagName, content, sz); + tagName[sz] = '\0'; + } + + if ((method = _findGroupFactory(tagName))) { + //Group + if (!loader->doc) { + if (strcmp(tagName, "svg")) return; //Not a valid svg document + node = method(loader, nullptr, attrs, attrsLength); + loader->doc = node; + } else { + if (!strcmp(tagName, "svg")) return; //Already loadded (SvgNodeType::Doc) tag + if (loader->stack.size() > 0) parent = loader->stack.at(loader->stack.size() - 1); + node = method(loader, parent, attrs, attrsLength); + } + loader->stack.push_back(node); + + if (node->type == SvgNodeType::Defs) { + loader->doc->node.doc.defs = node; + loader->def = node; + } + } else if ((method = _findGraphicsFactory(tagName))) { + parent = loader->stack.at(loader->stack.size() - 1); + node = method(loader, parent, attrs, attrsLength); + } else if ((gradientMethod = _findGradientFactory(tagName))) { + SvgStyleGradient* gradient; + gradient = gradientMethod(loader, attrs, attrsLength); + //FIXME: The current parsing structure does not distinguish end tags. + // There is no way to know if the currently parsed gradient is in defs. + // If a gradient is declared outside of defs after defs is set, it is included in the gradients of defs. + // But finally, the loader has a gradient style list regardless of defs. + // This is only to support this when multiple gradients are declared, even if no defs are declared. + // refer to: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/defs + if (loader->doc->node.doc.defs) { + loader->def->node.defs.gradients.push_back(gradient); + } else { + loader->gradients.push_back(gradient); + } + loader->latestGradient = gradient; + } else if (!strcmp(tagName, "stop")) { + auto stop = static_cast(calloc(1, sizeof(Fill::ColorStop))); + loader->svgParse->gradStop = stop; + /* default value for opacity */ + stop->a = 255; + simpleXmlParseAttributes(attrs, attrsLength, _attrParseStops, loader); + if (loader->latestGradient) { + loader->latestGradient->stops.push_back(stop); + } + } +} + + +static bool _svgLoaderParser(void* data, SimpleXMLType type, const char* content, unsigned int length) +{ + SvgLoaderData* loader = (SvgLoaderData*)data; + + switch (type) { + case SimpleXMLType::Open: { + _svgLoaderParserXmlOpen(loader, content, length); + break; + } + case SimpleXMLType::OpenEmpty: { + _svgLoaderParserXmlOpen(loader, content, length); + break; + } + case SimpleXMLType::Close: { + _svgLoaderParerXmlClose(loader, content); + break; + } + case SimpleXMLType::Data: + case SimpleXMLType::CData: + case SimpleXMLType::DoctypeChild: { + break; + } + case SimpleXMLType::Ignored: + case SimpleXMLType::Comment: + case SimpleXMLType::Doctype: { + break; + } + default: { + break; + } + } + + return true; +} + + +static void _styleInherit(SvgStyleProperty* child, SvgStyleProperty* parent) +{ + if (parent == nullptr) return; + //Inherit the property of parent if not present in child. + //Fill + if (!((int)child->fill.flags & (int)SvgFillFlags::Paint)) { + child->fill.paint.r = parent->fill.paint.r; + child->fill.paint.g = parent->fill.paint.g; + child->fill.paint.b = parent->fill.paint.b; + child->fill.paint.none = parent->fill.paint.none; + child->fill.paint.curColor = parent->fill.paint.curColor; + if (parent->fill.paint.url) child->fill.paint.url = _copyId(parent->fill.paint.url->c_str()); + } + if (!((int)child->fill.flags & (int)SvgFillFlags::Opacity)) { + child->fill.opacity = parent->fill.opacity; + } + if (!((int)child->fill.flags & (int)SvgFillFlags::FillRule)) { + child->fill.fillRule = parent->fill.fillRule; + } + //Stroke + if (!((int)child->stroke.flags & (int)SvgStrokeFlags::Paint)) { + child->stroke.paint.r = parent->stroke.paint.r; + child->stroke.paint.g = parent->stroke.paint.g; + child->stroke.paint.b = parent->stroke.paint.b; + child->stroke.paint.none = parent->stroke.paint.none; + child->stroke.paint.curColor = parent->stroke.paint.curColor; + child->stroke.paint.url = parent->stroke.paint.url ? _copyId(parent->stroke.paint.url->c_str()) : nullptr; + } + if (!((int)child->stroke.flags & (int)SvgStrokeFlags::Opacity)) { + child->stroke.opacity = parent->stroke.opacity; + } + if (!((int)child->stroke.flags & (int)SvgStrokeFlags::Width)) { + child->stroke.width = parent->stroke.width; + } + if (!((int)child->stroke.flags & (int)SvgStrokeFlags::Cap)) { + child->stroke.cap = parent->stroke.cap; + } + if (!((int)child->stroke.flags & (int)SvgStrokeFlags::Join)) { + child->stroke.join = parent->stroke.join; + } +} + + +static void _updateStyle(SvgNode* node, SvgStyleProperty* parentStyle) +{ + _styleInherit(node->style, parentStyle); + + for (vector::iterator itrChild = node->child.begin(); itrChild != node->child.end(); itrChild++) { + _updateStyle(*itrChild, node->style); + } +} + + +static SvgStyleGradient* _gradientDup(vector gradList, string* id) +{ + SvgStyleGradient* result = nullptr; + + for (vector::iterator itrGrad = gradList.begin(); itrGrad != gradList.end(); itrGrad++) { + if (!((*itrGrad)->id->compare(*id))) { + result = _cloneGradient(*itrGrad); + break; + } + } + + if (result && result->ref) { + for (vector::iterator itrGrad = gradList.begin(); itrGrad != gradList.end(); itrGrad++) { + if (!((*itrGrad)->id->compare(*result->ref))) { + if (!result->stops.empty()) { + _cloneGradStops(&(result->stops), (*itrGrad)->stops); + } + //TODO: Properly inherit other property + break; + } + } + } + + return result; +} + + +static void _updateGradient(SvgNode* node, vector gradList) +{ + if (!node->child.empty()) { + for (vector::iterator itrChild = node->child.begin(); itrChild != node->child.end(); itrChild++) { + _updateGradient(*itrChild, gradList); + } + } else { + if (node->style->fill.paint.url) { + node->style->fill.paint.gradient = _gradientDup(gradList, node->style->fill.paint.url); + } else if (node->style->stroke.paint.url) { + //node->style->stroke.paint.gradient = _gradientDup(gradList, node->style->stroke.paint.url); + } + } +} + +static void _freeGradientStyle(SvgStyleGradient* grad) +{ + if (!grad) return; + + delete grad->id; + delete grad->ref; + free(grad->radial); + free(grad->linear); + if (grad->transform) free(grad->transform); + + for (auto colorStop : grad->stops) free(colorStop); + + free(grad); +} + +static void _freeNodeStyle(SvgStyleProperty* style) +{ + if (!style) return; + + _freeGradientStyle(style->fill.paint.gradient); + delete style->fill.paint.url; + _freeGradientStyle(style->stroke.paint.gradient); + delete style->stroke.paint.url; + free(style); +} + +static void _freeNode(SvgNode* node) +{ + if (!node) return; + + for(vector::iterator itrChild = node->child.begin(); itrChild != node->child.end(); itrChild++) { + _freeNode(*itrChild); + } + node->child.clear(); + + delete node->id; + free(node->transform); + _freeNodeStyle(node->style); + switch (node->type) { + case SvgNodeType::Path: { + delete node->node.path.path; + break; + } + case SvgNodeType::Polygon: { + free(node->node.polygon.points); + break; + } + case SvgNodeType::Polyline: { + free(node->node.polyline.points); + break; + } + case SvgNodeType::Doc: { + _freeNode(node->node.doc.defs); + break; + } + case SvgNodeType::Defs: { + for(vector::iterator itrGrad = node->node.defs.gradients.begin(); itrGrad != node->node.defs.gradients.end(); itrGrad++) { + _freeGradientStyle(*itrGrad); + } + break; + } + default: { + break; + } + } + if (!node->child.empty()) node->child.clear(); + free(node); +} + + +static bool _svgLoaderParserForValidCheckXmlOpen(SvgLoaderData* loader, const char* content, unsigned int length) +{ + const char* attrs = nullptr; + int sz = length; + char tagName[20] = ""; + FactoryMethod method; + SvgNode *node = nullptr; + int attrsLength = 0; + loader->level++; + attrs = simpleXmlFindAttributesTag(content, length); + + if (!attrs) { + //Parse the empty tag + attrs = content; + while ((attrs != nullptr) && *attrs != '>') attrs++; + } + + if (attrs) { + sz = attrs - content; + attrsLength = length - sz; + while ((sz > 0) && (isspace(content[sz - 1]))) sz--; + strncpy(tagName, content, sz); + tagName[sz] = '\0'; + } + + if ((method = _findGroupFactory(tagName))) { + if (!loader->doc) { + if (strcmp(tagName, "svg")) return true; //Not a valid svg document + node = method(loader, nullptr, attrs, attrsLength); + loader->doc = node; + loader->stack.push_back(node); + return false; + } + } + return true; +} + + +static bool _svgLoaderParserForValidCheck(void* data, SimpleXMLType type, const char* content, unsigned int length) +{ + SvgLoaderData* loader = (SvgLoaderData*)data; + bool res = true;; + + switch (type) { + case SimpleXMLType::Open: + case SimpleXMLType::OpenEmpty: { + //If 'res' is false, it means tag is found. + res = _svgLoaderParserForValidCheckXmlOpen(loader, content, length); + break; + } + default: { + break; + } + } + + return res; +} + +/************************************************************************/ +/* External Class Implementation */ +/************************************************************************/ + +SvgLoader::SvgLoader() +{ +} + + +SvgLoader::~SvgLoader() +{ + close(); +} + + +bool SvgLoader::header() +{ + //For valid check, only tag is parsed first. + //If the tag is found, the loaded file is valid and stores viewbox information. + //After that, the remaining content data is parsed in order with async. + loaderData.svgParse = (SvgParser*)malloc(sizeof(SvgParser)); + if (!loaderData.svgParse) return false; + + simpleXmlParse(content, size, true, _svgLoaderParserForValidCheck, &(loaderData)); + + if (loaderData.doc && loaderData.doc->type == SvgNodeType::Doc) { + //Return the brief resource info such as viewbox: + this->vx = loaderData.doc->node.doc.vx; + this->vy = loaderData.doc->node.doc.vy; + this->vw = loaderData.doc->node.doc.vw; + this->vh = loaderData.doc->node.doc.vh; + } else { + //LOG: No SVG File. There is no + return false; + } + + return true; +} + + +bool SvgLoader::open(const char* data, uint32_t size) +{ + this->content = data; + this->size = size; + + return header(); +} + + +bool SvgLoader::open(const char* path) +{ + ifstream f; + f.open(path); + + if (!f.is_open()) + { + //LOG: Failed to open file + return false; + } else { + getline(f, filePath, '\0'); + f.close(); + + if (filePath.empty()) return false; + + this->content = filePath.c_str(); + this->size = filePath.size(); + } + + return header(); +} + + +bool SvgLoader::read() +{ + if (!content || size == 0) return false; + + loaderData = {vector(), + nullptr, + nullptr, + vector(), + nullptr, + nullptr, + 0, + false}; + + loaderData.svgParse = (SvgParser*)malloc(sizeof(SvgParser)); + + if (!simpleXmlParse(content, size, true, _svgLoaderParser, &loaderData)) return false; + + if (loaderData.doc) { + _updateStyle(loaderData.doc, nullptr); + auto defs = loaderData.doc->node.doc.defs; + if (defs) _updateGradient(loaderData.doc, defs->node.defs.gradients); + else { + if (!loaderData.gradients.empty()) { + vector gradientList; + std::copy(loaderData.gradients.begin(), loaderData.gradients.end(), gradientList.begin()); + _updateGradient(loaderData.doc, gradientList); + gradientList.clear(); + } + } + } + + root = builder.build(loaderData.doc); + + return true; +} + + +bool SvgLoader::close() +{ + if (loaderData.svgParse) { + free(loaderData.svgParse); + loaderData.svgParse = nullptr; + } + _freeNode(loaderData.doc); + loaderData.doc = nullptr; + + return true; +} + + +unique_ptr SvgLoader::data() +{ + return move(root); +} \ No newline at end of file diff --git a/src/loaders/svg/tvgSvgLoader.h b/src/loaders/svg/tvgSvgLoader.h new file mode 100644 index 00000000..e85582bf --- /dev/null +++ b/src/loaders/svg/tvgSvgLoader.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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_SVG_LOADER_H_ +#define _TVG_SVG_LOADER_H_ + +#include "tvgSvgLoaderCommon.h" +#include "tvgSvgSceneBuilder.h" + + +class SvgLoader : public Loader +{ +private: + string filePath; + const char* content = nullptr; + uint32_t size = 0; + + SvgLoaderData loaderData; + SvgSceneBuilder builder; + unique_ptr root; + +public: + SvgLoader(); + ~SvgLoader(); + + bool open(const char* path) override; + bool open(const char* data, uint32_t size) override; + bool header(); + bool read() override; + bool close() override; + unique_ptr data() override; +}; + + +#endif //_TVG_SVG_LOADER_H_ diff --git a/src/loaders/svg/tvgSvgLoaderCommon.h b/src/loaders/svg/tvgSvgLoaderCommon.h new file mode 100644 index 00000000..5bf83334 --- /dev/null +++ b/src/loaders/svg/tvgSvgLoaderCommon.h @@ -0,0 +1,333 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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_SVG_LOADER_COMMON_H_ +#define _TVG_SVG_LOADER_COMMON_H_ + +#include "tvgCommon.h" +#include "tvgSimpleXmlParser.h" + +enum class SvgNodeType +{ + Doc, + G, + Defs, + //Switch, //Not support + Animation, + Arc, + Circle, + Ellipse, + Image, + Line, + Path, + Polygon, + Polyline, + Rect, + Text, + TextArea, + Tspan, + Use, + Video, + //Custome_command, //Not support + Unknown +}; + +enum class SvgLengthType +{ + Percent, + Px, + Pc, + Pt, + Mm, + Cm, + In, +}; + +enum class SvgFillFlags +{ + Paint = 0x1, + Opacity = 0x2, + Gradient = 0x4, + FillRule = 0x8 +}; + +enum class SvgStrokeFlags +{ + Paint = 0x1, + Opacity = 0x2, + Gradient = 0x4, + Scale = 0x8, + Width = 0x10, + Cap = 0x20, + Join = 0x40, + Dash = 0x80, +}; + +enum class SvgGradientType +{ + Linear, + Radial +}; + +enum class SvgStyleType +{ + Quality, + Fill, + ViewportFill, + Font, + Stroke, + SolidColor, + Gradient, + Transform, + Opacity, + CompOp +}; + +enum class SvgFillRule +{ + Winding = 0, + OddEven = 1 +}; + +//Length type to recalculate %, pt, pc, mm, cm etc +enum class SvgParserLengthType +{ + Vertical, + Horizontal, + //In case of, for example, radius of radial gradient + Other +}; + +typedef struct _SvgNode SvgNode; +typedef struct _SvgStyleGradient SvgStyleGradient; + +struct SvgDocNode +{ + float w; + float h; + float vx; + float vy; + float vw; + float vh; + SvgNode* defs; + bool preserveAspect; +}; + +struct SvgGNode +{ +}; + +struct SvgDefsNode +{ + vector gradients; +}; + +struct SvgArcNode +{ +}; + +struct SvgEllipseNode +{ + float cx; + float cy; + float rx; + float ry; +}; + +struct SvgCircleNode +{ + float cx; + float cy; + float r; +}; + +struct SvgRectNode +{ + float x; + float y; + float w; + float h; + float rx; + float ry; +}; + +struct SvgLineNode +{ + float x1; + float y1; + float x2; + float y2; +}; + +struct SvgPathNode +{ + string* path; +}; + +struct SvgPolygonNode +{ + int pointsCount; + float* points; +}; + +struct SvgLinearGradient +{ + float x1; + float y1; + float x2; + float y2; +}; + +struct SvgRadialGradient +{ + float cx; + float cy; + float fx; + float fy; + float r; +}; + +struct SvgGradientStop +{ + float offset; + uint8_t r; + uint8_t g; + uint8_t b; + uint8_t a; +}; + +struct SvgPaint +{ + SvgStyleGradient* gradient; + string *url; + uint8_t r; + uint8_t g; + uint8_t b; + bool none; + bool curColor; +}; + +struct SvgDash +{ + float length; + float gap; +}; + +struct _SvgStyleGradient +{ + SvgGradientType type; + string *id; + string *ref; + FillSpread spread; + SvgRadialGradient* radial; + SvgLinearGradient* linear; + Matrix* transform; + vector stops; + bool userSpace; + bool usePercentage; +}; + +struct SvgStyleFill +{ + SvgFillFlags flags; + SvgPaint paint; + int opacity; + SvgFillRule fillRule; +}; + +struct SvgStyleStroke +{ + SvgStrokeFlags flags; + SvgPaint paint; + int opacity; + float scale; + float width; + float centered; + StrokeCap cap; + StrokeJoin join; + SvgDash* dash; + int dashCount; +}; + +struct SvgStyleProperty +{ + SvgStyleFill fill; + SvgStyleStroke stroke; + int opacity; + uint8_t r; + uint8_t g; + uint8_t b; +}; + +struct _SvgNode +{ + SvgNodeType type; + SvgNode* parent; + vector child; + string *id; + SvgStyleProperty *style; + Matrix* transform; + union { + SvgGNode g; + SvgDocNode doc; + SvgDefsNode defs; + SvgArcNode arc; + SvgCircleNode circle; + SvgEllipseNode ellipse; + SvgPolygonNode polygon; + SvgPolygonNode polyline; + SvgRectNode rect; + SvgPathNode path; + SvgLineNode line; + } node; + bool display; +}; + +struct SvgParser +{ + SvgNode* node; + SvgStyleGradient* styleGrad; + Fill::ColorStop* gradStop; + struct + { + int x, y; + uint32_t w, h; + } global; + struct + { + bool parsedFx; + bool parsedFy; + } gradient; +}; + +struct SvgLoaderData +{ + vector stack; + SvgNode* doc = nullptr; + SvgNode* def = nullptr; + vector gradients; + SvgStyleGradient* latestGradient = nullptr; //For stops + SvgParser* svgParse = nullptr; + int level = 0; + bool result = false; +}; + +#endif diff --git a/src/loaders/svg/tvgSvgPath.cpp b/src/loaders/svg/tvgSvgPath.cpp new file mode 100644 index 00000000..4efc009b --- /dev/null +++ b/src/loaders/svg/tvgSvgPath.cpp @@ -0,0 +1,522 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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 "tvgSvgPath.h" + + +static char* _skipComma(const char* content) +{ + while (*content && isspace(*content)) { + content++; + } + if (*content == ',') return (char*)content + 1; + return (char*)content; +} + + +static bool _parseNumber(char** content, float* number) +{ + char* end = NULL; + *number = strtof(*content, &end); + //If the start of string is not number + if ((*content) == end) return false; + //Skip comma if any + *content = _skipComma(end); + return true; +} + + +static bool _parseLong(char** content, int* number) +{ + char* end = NULL; + *number = strtol(*content, &end, 10) ? 1 : 0; + //If the start of string is not number + if ((*content) == end) return false; + *content = _skipComma(end); + return true; +} + +void _pathAppendArcTo(vector* cmds, vector* pts, Point* cur, Point* curCtl, float x, float y, float rx, float ry, float angle, bool largeArc, bool sweep) +{ + float cxp, cyp, cx, cy; + float sx, sy; + float cosPhi, sinPhi; + float dx2, dy2; + float x1p, y1p; + float x1p2, y1p2; + float rx2, ry2; + float lambda; + float c; + float at; + float theta1, deltaTheta; + float nat; + float delta, bcp; + float cosPhiRx, cosPhiRy; + float sinPhiRx, sinPhiRy; + float cosTheta1, sinTheta1; + int segments, i; + + //Some helpful stuff is available here: + //http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes + sx = cur->x; + sy = cur->y; + + //If start and end points are identical, then no arc is drawn + if ((fabs(x - sx) < (1.0f / 256.0f)) && (fabs(y - sy) < (1.0f / 256.0f))) return; + + //Correction of out-of-range radii, see F6.6.1 (step 2) + rx = fabs(rx); + ry = fabs(ry); + if ((rx < 0.5f) || (ry < 0.5f)) { + Point p = {x, y}; + cmds->push_back(PathCommand::LineTo); + pts->push_back(p); + *cur = p; + return; + } + + angle = angle * M_PI / 180.0f; + cosPhi = cosf(angle); + sinPhi = sinf(angle); + dx2 = (sx - x) / 2.0f; + dy2 = (sy - y) / 2.0f; + x1p = cosPhi * dx2 + sinPhi * dy2; + y1p = cosPhi * dy2 - sinPhi * dx2; + x1p2 = x1p * x1p; + y1p2 = y1p * y1p; + rx2 = rx * rx; + ry2 = ry * ry; + lambda = (x1p2 / rx2) + (y1p2 / ry2); + + //Correction of out-of-range radii, see F6.6.2 (step 4) + if (lambda > 1.0f) { + //See F6.6.3 + float lambdaRoot = sqrt(lambda); + + rx *= lambdaRoot; + ry *= lambdaRoot; + //Update rx2 and ry2 + rx2 = rx * rx; + ry2 = ry * ry; + } + + c = (rx2 * ry2) - (rx2 * y1p2) - (ry2 * x1p2); + + //Check if there is no possible solution + //(i.e. we can't do a square root of a negative value) + if (c < 0.0f) { + //Scale uniformly until we have a single solution + //(see F6.2) i.e. when c == 0.0 + float scale = sqrt(1.0f - c / (rx2 * ry2)); + rx *= scale; + ry *= scale; + //Update rx2 and ry2 + rx2 = rx * rx; + ry2 = ry * ry; + + //Step 2 (F6.5.2) - simplified since c == 0.0 + cxp = 0.0f; + cyp = 0.0f; + //Step 3 (F6.5.3 first part) - simplified since cxp and cyp == 0.0 + cx = 0.0f; + cy = 0.0f; + } else { + //Complete c calculation + c = sqrt(c / ((rx2 * y1p2) + (ry2 * x1p2))); + //Inverse sign if Fa == Fs + if (largeArc == sweep) c = -c; + + //Step 2 (F6.5.2) + cxp = c * (rx * y1p / ry); + cyp = c * (-ry * x1p / rx); + + //Step 3 (F6.5.3 first part) + cx = cosPhi * cxp - sinPhi * cyp; + cy = sinPhi * cxp + cosPhi * cyp; + } + + //Step 3 (F6.5.3 second part) we now have the center point of the ellipse + cx += (sx + x) / 2.0f; + cy += (sy + y) / 2.0f; + + //Sstep 4 (F6.5.4) + //We dont' use arccos (as per w3c doc), see + //http://www.euclideanspace.com/maths/algebra/vectors/angleBetween/index.htm + //Note: atan2 (0.0, 1.0) == 0.0 + at = atan2(((y1p - cyp) / ry), ((x1p - cxp) / rx)); + theta1 = (at < 0.0f) ? 2.0f * M_PI + at : at; + + nat = atan2(((-y1p - cyp) / ry), ((-x1p - cxp) / rx)); + deltaTheta = (nat < at) ? 2.0f * M_PI - at + nat : nat - at; + + if (sweep) { + //Ensure delta theta < 0 or else add 360 degrees + if (deltaTheta < 0.0f) deltaTheta += 2.0f * M_PI; + } else { + //Ensure delta theta > 0 or else substract 360 degrees + if (deltaTheta > 0.0f) deltaTheta -= 2.0f * M_PI; + } + + //Add several cubic bezier to approximate the arc + //(smaller than 90 degrees) + //We add one extra segment because we want something + //Smaller than 90deg (i.e. not 90 itself) + segments = (int)(fabs(deltaTheta / M_PI_2)) + 1.0f; + delta = deltaTheta / segments; + + //http://www.stillhq.com/ctpfaq/2001/comp.text.pdf-faq-2001-04.txt (section 2.13) + bcp = 4.0f / 3.0f * (1.0f - cos(delta / 2.0f)) / sin(delta / 2.0f); + + cosPhiRx = cosPhi * rx; + cosPhiRy = cosPhi * ry; + sinPhiRx = sinPhi * rx; + sinPhiRy = sinPhi * ry; + + cosTheta1 = cos(theta1); + sinTheta1 = sin(theta1); + + for (i = 0; i < segments; ++i) { + //End angle (for this segment) = current + delta + float c1x, c1y, ex, ey, c2x, c2y; + float theta2 = theta1 + delta; + float cosTheta2 = cos(theta2); + float sinTheta2 = sin(theta2); + static Point p[3]; + + //First control point (based on start point sx,sy) + c1x = sx - bcp * (cosPhiRx * sinTheta1 + sinPhiRy * cosTheta1); + c1y = sy + bcp * (cosPhiRy * cosTheta1 - sinPhiRx * sinTheta1); + + //End point (for this segment) + ex = cx + (cosPhiRx * cosTheta2 - sinPhiRy * sinTheta2); + ey = cy + (sinPhiRx * cosTheta2 + cosPhiRy * sinTheta2); + + //Second control point (based on end point ex,ey) + c2x = ex + bcp * (cosPhiRx * sinTheta2 + sinPhiRy * cosTheta2); + c2y = ey + bcp * (sinPhiRx * sinTheta2 - cosPhiRy * cosTheta2); + cmds->push_back(PathCommand::CubicTo); + p[0] = {c1x, c1y}; + p[1] = {c2x, c2y}; + p[2] = {ex, ey}; + pts->push_back(p[0]); + pts->push_back(p[1]); + pts->push_back(p[2]); + *curCtl = p[1]; + *cur = p[2]; + + //Next start point is the current end point (same for angle) + sx = ex; + sy = ey; + theta1 = theta2; + //Avoid recomputations + cosTheta1 = cosTheta2; + sinTheta1 = sinTheta2; + } +} + +static int _numberCount(char cmd) +{ + int count = 0; + switch (cmd) { + case 'M': + case 'm': + case 'L': + case 'l': + case 'T': + case 't': { + count = 2; + break; + } + case 'C': + case 'c': + case 'E': + case 'e': { + count = 6; + break; + } + case 'H': + case 'h': + case 'V': + case 'v': { + count = 1; + break; + } + case 'S': + case 's': + case 'Q': + case 'q': { + count = 4; + break; + } + case 'A': + case 'a': { + count = 7; + break; + } + default: + break; + } + return count; +} + + +static void _processCommand(vector* cmds, vector* pts, char cmd, float* arr, int count, Point* cur, Point* curCtl, bool *isQuadratic) +{ + int i; + switch (cmd) { + case 'm': + case 'l': + case 'c': + case 's': + case 'q': + case 't': { + for (i = 0; i < count - 1; i += 2) { + arr[i] = arr[i] + cur->x; + arr[i + 1] = arr[i + 1] + cur->y; + } + break; + } + case 'h': { + arr[0] = arr[0] + cur->x; + break; + } + case 'v': { + arr[0] = arr[0] + cur->y; + break; + } + case 'a': { + arr[5] = arr[5] + cur->x; + arr[6] = arr[6] + cur->y; + break; + } + default: { + break; + } + } + + switch (cmd) { + case 'm': + case 'M': { + Point p = {arr[0], arr[1]}; + cmds->push_back(PathCommand::MoveTo); + pts->push_back(p); + *cur = {arr[0] ,arr[1]}; + break; + } + case 'l': + case 'L': { + Point p = {arr[0], arr[1]}; + cmds->push_back(PathCommand::LineTo); + pts->push_back(p); + *cur = {arr[0] ,arr[1]}; + break; + } + case 'c': + case 'C': { + Point p[3]; + cmds->push_back(PathCommand::CubicTo); + p[0] = {arr[0], arr[1]}; + p[1] = {arr[2], arr[3]}; + p[2] = {arr[4], arr[5]}; + pts->push_back(p[0]); + pts->push_back(p[1]); + pts->push_back(p[2]); + *curCtl = p[1]; + *cur = p[2]; + *isQuadratic = false; + break; + } + case 's': + case 'S': { + Point p[3], ctrl; + if ((cmds->size() > 1) && (cmds->at(cmds->size() - 1) == PathCommand::CubicTo) && + !(*isQuadratic)) { + ctrl.x = 2 * cur->x - curCtl->x; + ctrl.y = 2 * cur->y - curCtl->y; + } else { + ctrl = *cur; + } + cmds->push_back(PathCommand::CubicTo); + p[0] = ctrl; + p[1] = {arr[0], arr[1]}; + p[2] = {arr[2], arr[3]}; + pts->push_back(p[0]); + pts->push_back(p[1]); + pts->push_back(p[2]); + *curCtl = p[1]; + *cur = p[2]; + *isQuadratic = false; + break; + } + case 'q': + case 'Q': { + Point p[3]; + float ctrl_x0 = (cur->x + 2 * arr[0]) * (1.0 / 3.0); + float ctrl_y0 = (cur->y + 2 * arr[1]) * (1.0 / 3.0); + float ctrl_x1 = (arr[2] + 2 * arr[0]) * (1.0 / 3.0); + float ctrl_y1 = (arr[3] + 2 * arr[1]) * (1.0 / 3.0); + cmds->push_back(PathCommand::CubicTo); + p[0] = {ctrl_x0, ctrl_y0}; + p[1] = {ctrl_x1, ctrl_y1}; + p[2] = {arr[2], arr[3]}; + pts->push_back(p[0]); + pts->push_back(p[1]); + pts->push_back(p[2]); + *curCtl = {arr[0], arr[1]}; + *cur = p[2]; + *isQuadratic = true; + break; + } + case 't': + case 'T': { + Point p[3], ctrl; + if ((cmds->size() > 1) && (cmds->at(cmds->size() - 1) == PathCommand::CubicTo) && + *isQuadratic) { + ctrl.x = 2 * cur->x - curCtl->x; + ctrl.y = 2 * cur->y - curCtl->y; + } else { + ctrl = *cur; + } + float ctrl_x0 = (cur->x + 2 * ctrl.x) * (1.0 / 3.0); + float ctrl_y0 = (cur->y + 2 * ctrl.y) * (1.0 / 3.0); + float ctrl_x1 = (arr[0] + 2 * ctrl.x) * (1.0 / 3.0); + float ctrl_y1 = (arr[1] + 2 * ctrl.y) * (1.0 / 3.0); + cmds->push_back(PathCommand::CubicTo); + p[0] = {ctrl_x0, ctrl_y0}; + p[1] = {ctrl_x1, ctrl_y1}; + p[2] = {arr[0], arr[1]}; + pts->push_back(p[0]); + pts->push_back(p[1]); + pts->push_back(p[2]); + *curCtl = {ctrl.x, ctrl.y}; + *cur = p[2]; + *isQuadratic = true; + break; + } + case 'h': + case 'H': { + Point p = {arr[0], cur->y}; + cmds->push_back(PathCommand::LineTo); + pts->push_back(p); + cur->x = arr[0]; + break; + } + case 'v': + case 'V': { + Point p = {cur->x, arr[0]}; + cmds->push_back(PathCommand::LineTo); + pts->push_back(p); + cur->y = arr[0]; + break; + } + case 'z': + case 'Z': { + cmds->push_back(PathCommand::Close); + break; + } + case 'a': + case 'A': { + _pathAppendArcTo(cmds, pts, cur, curCtl, arr[5], arr[6], arr[0], arr[1], arr[2], arr[3], arr[4]); + *cur = {arr[5] ,arr[6]}; + break; + } + default: { + break; + } + } +} + + +static char* _nextCommand(char* path, char* cmd, float* arr, int* count) +{ + int i = 0, large, sweep; + + path = _skipComma(path); + if (isalpha(*path)) { + *cmd = *path; + path++; + *count = _numberCount(*cmd); + } else { + if (*cmd == 'm') *cmd = 'l'; + else if (*cmd == 'M') *cmd = 'L'; + } + if (*count == 7) { + //Special case for arc command + if (_parseNumber(&path, &arr[0])) { + if (_parseNumber(&path, &arr[1])) { + if (_parseNumber(&path, &arr[2])) { + if (_parseLong(&path, &large)) { + if (_parseLong(&path, &sweep)) { + if (_parseNumber(&path, &arr[5])) { + if (_parseNumber(&path, &arr[6])) { + arr[3] = large; + arr[4] = sweep; + return path; + } + } + } + } + } + } + } + *count = 0; + return NULL; + } + for (i = 0; i < *count; i++) { + if (!_parseNumber(&path, &arr[i])) { + *count = 0; + return NULL; + } + path = _skipComma(path); + } + return path; +} + + +tuple, vector> svgPathToTvgPath(const char* svgPath) +{ + vector cmds; + vector pts; + + float numberArray[7]; + int numberCount = 0; + Point cur = { 0, 0 }; + Point curCtl = { 0, 0 }; + char cmd = 0; + bool isQuadratic = false; + char* path = (char*)svgPath; + char* curLocale; + + curLocale = setlocale(LC_NUMERIC, NULL); + if (curLocale) curLocale = strdup(curLocale); + setlocale(LC_NUMERIC, "POSIX"); + + while ((path[0] != '\0')) { + path = _nextCommand(path, &cmd, numberArray, &numberCount); + if (!path) break; + _processCommand(&cmds, &pts, cmd, numberArray, numberCount, &cur, &curCtl, &isQuadratic); + } + + setlocale(LC_NUMERIC, curLocale); + if (curLocale) free(curLocale); + + return make_tuple(cmds, pts); +} \ No newline at end of file diff --git a/src/loaders/svg/tvgSvgPath.h b/src/loaders/svg/tvgSvgPath.h new file mode 100644 index 00000000..9d853d19 --- /dev/null +++ b/src/loaders/svg/tvgSvgPath.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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_SVG_PATH_H_ +#define _TVG_SVG_PATH_H_ + +#include "tvgCommon.h" + +tuple, vector> svgPathToTvgPath(const char* svg_path_data); + +#endif //_TVG_SVG_PATH_H_ diff --git a/src/loaders/svg/tvgSvgSceneBuilder.cpp b/src/loaders/svg/tvgSvgSceneBuilder.cpp new file mode 100644 index 00000000..5496ae87 --- /dev/null +++ b/src/loaders/svg/tvgSvgSceneBuilder.cpp @@ -0,0 +1,372 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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 "tvgSvgSceneBuilder.h" + +unique_ptr _applyLinearGradientProperty(SvgStyleGradient* g, Shape* vg, float rx, float ry, float rw, float rh) +{ + Fill::ColorStop* stops; + int stopCount = 0; + float fillOpacity = 255.0f; + float gx, gy, gw, gh; + + auto fillGrad = LinearGradient::gen(); + + if (g->usePercentage) { + g->linear->x1 = g->linear->x1 * rw + rx; + g->linear->y1 = g->linear->y1 * rh + ry; + g->linear->x2 = g->linear->x2 * rw + rx; + g->linear->y2 = g->linear->y2 * rh + ry; + } + + //In case of objectBoundingBox it need proper scaling + if (!g->userSpace) { + float scaleX = 1.0, scaleReversedX = 1.0; + float scaleY = 1.0, scaleReversedY = 1.0; + + //Check the smallest size, find the scale value + if (rh > rw) { + scaleY = ((float)rw) / rh; + scaleReversedY = ((float)rh) / rw; + } else { + scaleX = ((float)rh) / rw; + scaleReversedX = ((float)rw) / rh; + } + + vg->bounds(&gx, &gy, &gw, &gh); + + float cy = ((float)gh) * 0.5 + gy; + float cy_scaled = (((float)gh) * 0.5) * scaleReversedY; + float cx = ((float)gw) * 0.5 + gx; + float cx_scaled = (((float)gw) * 0.5) * scaleReversedX; + + //= T(gx, gy) x S(scaleX, scaleY) x T(cx_scaled - cx, cy_scaled - cy) x (radial->x, radial->y) + g->linear->x1 = g->linear->x1 * scaleX + scaleX * (cx_scaled - cx) + gx; + g->linear->y1 = g->linear->y1 * scaleY + scaleY * (cy_scaled - cy) + gy; + g->linear->x2 = g->linear->x2 * scaleX + scaleX * (cx_scaled - cx) + gx; + g->linear->y2 = g->linear->y2 * scaleY + scaleY * (cy_scaled - cy) + gy; + } + + if (g->transform) { + float cy = ((float) rh) * 0.5 + ry; + float cx = ((float) rw) * 0.5 + rx; + + //Calc start point + //= T(x - cx, y - cy) x g->transform x T(cx, cy) + g->linear->x1 = cx * (g->transform->e11 + g->transform->e31 * (g->linear->x1 - cx)) + + cx * (g->transform->e12 + g->transform->e32 * (g->linear->x1 - cx)) + + cx * (g->transform->e13 + g->transform->e33 * (g->linear->x1 - cx)); + + g->linear->y1 = cy * (g->transform->e21 + g->transform->e31 * (g->linear->y1 - cy)) + + cy * (g->transform->e22 + g->transform->e32 * (g->linear->y1 - cy)) + + cy * (g->transform->e23 + g->transform->e33 * (g->linear->y1 - cy)); + + //Calc end point + g->linear->x2 = cx * (g->transform->e11 + g->transform->e31 * (g->linear->x2 - cx)) + + cx * (g->transform->e12 + g->transform->e32 * (g->linear->x2 - cx)) + + cx * (g->transform->e13 + g->transform->e33 * (g->linear->x2 - cx)); + + g->linear->y2 = cy * (g->transform->e21 + g->transform->e31 * (g->linear->y2 - cy)) + + cy * (g->transform->e22 + g->transform->e32 * (g->linear->y2 - cy)) + + cy * (g->transform->e23 + g->transform->e33 * (g->linear->y2 - cy)); + } + + fillGrad->linear(g->linear->x1, g->linear->y1, g->linear->x2, g->linear->y2); + fillGrad->spread(g->spread); + + //Update the stops + stopCount = g->stops.size(); + if (stopCount > 0) { + float opacity; + float fopacity = fillOpacity / 255.0f; //fill opacity if any exists. + int i = 0; + stops = (Fill::ColorStop*)calloc(stopCount, sizeof(Fill::ColorStop)); + for (auto colorStop : g->stops) { + //Use premultiplied color + opacity = ((float)colorStop->a / 255.0f) * fopacity; + stops[i].r = colorStop->r * opacity; + stops[i].g = colorStop->g * opacity; + stops[i].b = colorStop->b * opacity; + stops[i].a = colorStop->a * fopacity; + stops[i].offset = colorStop->offset; + i++; + } + fillGrad->colorStops(stops, stopCount); + free(stops); + } + return fillGrad; +} + + +unique_ptr _applyRadialGradientProperty(SvgStyleGradient* g, Shape* vg, float rx, float ry, float rw, float rh) +{ + Fill::ColorStop *stops; + int stopCount = 0; + float gx, gy, gw, gh; + int radius; + float fillOpacity = 255.0f; + + auto fillGrad = RadialGradient::gen(); + + radius = sqrt(pow(rw, 2) + pow(rh, 2)) / sqrt(2.0); + if (!g->userSpace) { + //That is according to Units in here + //https://www.w3.org/TR/2015/WD-SVG2-20150915/coords.html + int min = (rh > rw) ? rw : rh; + radius = sqrt(pow(min, 2) + pow(min, 2)) / sqrt(2.0); + } + + if (g->usePercentage) { + g->radial->cx = g->radial->cx * rw + rx; + g->radial->cy = g->radial->cy * rh + ry; + g->radial->r = g->radial->r * radius; + g->radial->fx = g->radial->fx * rw + rx; + g->radial->fy = g->radial->fy * rh + ry; + } + + //In case of objectBoundingBox it need proper scaling + if (!g->userSpace) { + float scaleX = 1.0, scaleReversedX = 1.0; + float scaleY = 1.0, scaleReversedY = 1.0; + + //Check the smallest size, find the scale value + if (rh > rw) { + scaleY = ((float)rw) / rh; + scaleReversedY = ((float)rh) / rw; + } else { + scaleX = ((float)rh) / rw; + scaleReversedX = ((float)rw) / rh; + } + + vg->bounds(&gx, &gy, &gw, &gh); + + float cy = ((float)gh) * 0.5 + gy; + float cy_scaled = (((float)gh) * 0.5) * scaleReversedY; + float cx = ((float)gw) * 0.5 + gx; + float cx_scaled = (((float)gw) * 0.5) * scaleReversedX; + + //= T(gx, gy) x S(scaleX, scaleY) x T(cx_scaled - cx, cy_scaled - cy) x (radial->x, radial->y) + g->radial->cx = g->radial->cx * scaleX + scaleX * (cx_scaled - cx) + gx; + g->radial->cy = g->radial->cy * scaleY + scaleY * (cy_scaled - cy) + gy; + } + + //TODO: Radial gradient transformation is not yet supported. + //if (g->transform) {} + + //TODO: Tvg is not support to focal + //if (g->radial->fx != 0 && g->radial->fy != 0) { + // fillGrad->radial(g->radial->fx, g->radial->fy, g->radial->r); + //} + fillGrad->radial(g->radial->cx, g->radial->cy, g->radial->r); + fillGrad->spread(g->spread); + + //Update the stops + stopCount = g->stops.size(); + if (stopCount > 0) { + float opacity; + float fopacity = fillOpacity / 255.0f; //fill opacity if any exists. + int i = 0; + stops = (Fill::ColorStop*)calloc(stopCount, sizeof(Fill::ColorStop)); + for (auto colorStop : g->stops) { + //Use premultiplied color + opacity = ((float)colorStop->a / 255.0f) * fopacity; + stops[i].r = colorStop->r * opacity; + stops[i].g = colorStop->g * opacity; + stops[i].b = colorStop->b * opacity; + stops[i].a = colorStop->a * fopacity; + stops[i].offset = colorStop->offset; + i++; + } + fillGrad->colorStops(stops, stopCount); + free(stops); + } + return fillGrad; +} + + +void _applyProperty(SvgNode* node, Shape* vg, float vx, float vy, float vw, float vh) +{ + SvgStyleProperty* style = node->style; + + if (node->transform) vg->transform(*node->transform); + if (node->type == SvgNodeType::Doc || !node->display) return; + + //If fill property is nullptr then do nothing + if (style->fill.paint.none) { + //Do nothing + } else if (style->fill.paint.gradient) { + if (!style->fill.paint.gradient->userSpace) vg->bounds(&vx, &vy, &vw, &vh); + + if (style->fill.paint.gradient->type == SvgGradientType::Linear) { + auto linear = _applyLinearGradientProperty(style->fill.paint.gradient, vg, vx, vy, vw, vh); + vg->fill(move(linear)); + } else if (style->fill.paint.gradient->type == SvgGradientType::Radial) { + auto radial = _applyRadialGradientProperty(style->fill.paint.gradient, vg, vx, vy, vw, vh); + vg->fill(move(radial)); + } + } else if (style->fill.paint.curColor) { + //Apply the current style color + float fa = ((float)style->fill.opacity / 255.0); + vg->fill(((float)style->r) * fa, ((float)style->g) * fa, ((float)style->b) * fa, style->fill.opacity); + } else { + //Apply the fill color + float fa = ((float)style->fill.opacity / 255.0); + vg->fill(((float)style->fill.paint.r) * fa, ((float)style->fill.paint.g) * fa, ((float)style->fill.paint.b) * fa, style->fill.opacity); + } + + //Apply node opacity + if (style->opacity < 255) { + uint8_t r, g, b, a; + vg->fill(&r, &g, &b, &a); + float fa = ((float)style->opacity / 255.0); + vg->fill(((float)r) * fa, ((float)g) * fa, ((float)b) * fa, ((float)a) * fa); + } + + if (node->type == SvgNodeType::G) return; + + //Apply the stroke style property + vg->stroke(style->stroke.width); + vg->stroke(style->stroke.cap); + vg->stroke(style->stroke.join); + + + //If stroke property is nullptr then do nothing + if (style->stroke.paint.none) { + //Do nothing + } else if (style->stroke.paint.gradient) { + //TODO: Support gradient style + } else if (style->stroke.paint.url) { + //TODO: Apply the color pointed by url + } else if (style->stroke.paint.curColor) { + //Apply the current style color + vg->stroke(style->r, style->g, style->b, style->stroke.opacity); + + } else { + //Apply the stroke color + vg->stroke(style->stroke.paint.r, style->stroke.paint.g, style->stroke.paint.b, style->stroke.opacity); + } + + //Apply node opacity to stroke color + if (style->opacity < 255) { + uint8_t r, g, b, a; + vg->strokeColor(&r, &g, &b, &a); + float fa = ((float)style->opacity / 255.0); + vg->stroke(((float)r) * fa, ((float)g) * fa, ((float)b) * fa, ((float)a) * fa); + } +} + + +unique_ptr _shapeBuildHelper(SvgNode* node, float vx, float vy, float vw, float vh) +{ + auto shape = Shape::gen(); + switch (node->type) { + case SvgNodeType::Path: { + if (node->node.path.path) { + auto pathResult = svgPathToTvgPath(node->node.path.path->c_str()); + shape->appendPath(get<0>(pathResult).data(), get<0>(pathResult).size(), get<1>(pathResult).data(), get<1>(pathResult).size()); + } + break; + } + case SvgNodeType::Ellipse: { + shape->appendCircle(node->node.ellipse.cx, node->node.ellipse.cy, node->node.ellipse.rx, node->node.ellipse.ry); + break; + } + case SvgNodeType::Polygon: { + if (node->node.polygon.pointsCount < 2) break; + shape->moveTo(node->node.polygon.points[0], node->node.polygon.points[1]); + for (int i = 2; i < node->node.polygon.pointsCount; i += 2) { + shape->lineTo(node->node.polygon.points[i], node->node.polygon.points[i + 1]); + } + shape->close(); + break; + } + case SvgNodeType::Polyline: { + if (node->node.polygon.pointsCount < 2) break; + shape->moveTo(node->node.polygon.points[0], node->node.polygon.points[1]); + for (int i = 2; i < node->node.polygon.pointsCount; i += 2) { + shape->lineTo(node->node.polygon.points[i], node->node.polygon.points[i + 1]); + } + break; + } + case SvgNodeType::Circle: { + shape->appendCircle(node->node.circle.cx, node->node.circle.cy, node->node.circle.r, node->node.circle.r); + break; + } + case SvgNodeType::Rect: { + shape->appendRect(node->node.rect.x, node->node.rect.y, node->node.rect.w, node->node.rect.h, node->node.rect.rx, node->node.rect.ry); + break; + } + case SvgNodeType::Line: { + shape->moveTo(node->node.line.x1, node->node.line.y1); + shape->lineTo(node->node.line.x2, node->node.line.y2); + break; + } + default: { + break; + } + } + _applyProperty(node, shape.get(), vx, vy, vw, vh); + return shape; +} + + +unique_ptr _sceneBuildHelper(SvgNode* node, float vx, float vy, float vw, float vh, int parentOpacity) +{ + if (node->type == SvgNodeType::Doc || node->type == SvgNodeType::G) { + auto scene = Scene::gen(); + if (node->transform) scene->transform(*node->transform); + node->style->opacity = (node->style->opacity * parentOpacity) / 255.0f; + if (node->display) { + for (auto child : node->child) { + if (child->type == SvgNodeType::Doc || child->type == SvgNodeType::G) scene->push(_sceneBuildHelper(child, vx, vy, vw, vh, node->style->opacity)); + else { + child->style->opacity = (child->style->opacity * node->style->opacity) / 255.0f; + scene->push(_shapeBuildHelper(child, vx, vy, vw, vh)); + } + } + } + return scene; + } + return nullptr; +} + + +SvgSceneBuilder::SvgSceneBuilder() +{ +} + + +SvgSceneBuilder::~SvgSceneBuilder() +{ +} + + +unique_ptr SvgSceneBuilder::build(SvgNode* node) +{ + if (!node || (node->type != SvgNodeType::Doc)) return nullptr; + + viewBox.x = node->node.doc.vx; + viewBox.y = node->node.doc.vy; + viewBox.w = node->node.doc.vw; + viewBox.h = node->node.doc.vh; + preserveAspect = node->node.doc.preserveAspect; + return _sceneBuildHelper(node, viewBox.x, viewBox.y, viewBox.w, viewBox.h, 255); +} \ No newline at end of file diff --git a/src/loaders/svg/tvgSvgSceneBuilder.h b/src/loaders/svg/tvgSvgSceneBuilder.h new file mode 100644 index 00000000..0f93d961 --- /dev/null +++ b/src/loaders/svg/tvgSvgSceneBuilder.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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_SVG_SCENE_BUILDER_H_ +#define _TVG_SVG_SCENE_BUILDER_H_ + +#include "tvgSvgLoaderCommon.h" +#include "tvgSvgPath.h" + +class SvgSceneBuilder +{ +private: + struct { + int x, y; + uint32_t w, h; + } viewBox = {0, 0, 0, 0}; + bool preserveAspect = false; + +public: + SvgSceneBuilder(); + ~SvgSceneBuilder(); + + unique_ptr build(SvgNode* node); +}; + +#endif //_TVG_SVG_SCENE_BUILDER_H_ diff --git a/src/meson.build b/src/meson.build new file mode 100644 index 00000000..40d85cea --- /dev/null +++ b/src/meson.build @@ -0,0 +1,42 @@ +compiler_flags = ['-DTVG_BUILD'] + +cc = meson.get_compiler('cpp') +if (cc.get_id() != 'msvc') + if get_option('vectors').contains('avx') + compiler_flags += ['-mavx'] + message('Enable Advanced Vector Extension') + endif +endif + + +subdir('lib') +subdir('loaders') +subdir('bindings') + +thread_dep = meson.get_compiler('cpp').find_library('pthread') +thorvg_lib_dep = [common_dep, loader_dep, binding_dep, thread_dep] + +thorvg_lib = library( + 'thorvg', + include_directories : headers, + version : meson.project_version(), + dependencies : thorvg_lib_dep, + install : true, + cpp_args : compiler_flags, + gnu_symbol_visibility : 'hidden', +) + +thorvg_dep = declare_dependency( + include_directories: headers, + link_with : thorvg_lib +) + +pkg_mod = import('pkgconfig') + +pkg_mod.generate( + libraries : thorvg_lib, + version : meson.project_version(), + name : 'libthorvg', + filebase : 'thorvg', + description : 'A Thor library for rendering vector graphics' +) diff --git a/test/makefile b/test/makefile new file mode 100644 index 00000000..f48278d4 --- /dev/null +++ b/test/makefile @@ -0,0 +1,23 @@ +all: + gcc -o testShape testShape.cpp -g -lstdc++ `pkg-config --cflags --libs elementary thorvg` + gcc -o testMultiShapes testMultiShapes.cpp -g -lstdc++ `pkg-config --cflags --libs elementary thorvg` + gcc -o testBoundary testBoundary.cpp -g -lstdc++ `pkg-config --cflags --libs elementary thorvg` + gcc -o testPath testPath.cpp -g -lstdc++ `pkg-config --cflags --libs elementary thorvg` + gcc -o testPathCopy testPathCopy.cpp -g -lstdc++ `pkg-config --cflags --libs elementary thorvg` + gcc -o testBlending testBlending.cpp -g -lstdc++ `pkg-config --cflags --libs elementary thorvg` + gcc -o testUpdate testUpdate.cpp -g -lstdc++ `pkg-config --cflags --libs elementary thorvg` + gcc -o testDirectUpdate testDirectUpdate.cpp -g -lstdc++ `pkg-config --cflags --libs elementary thorvg` + gcc -o testScene testScene.cpp -g -lstdc++ `pkg-config --cflags --libs elementary thorvg` + gcc -o testTransform testTransform.cpp -g -lstdc++ `pkg-config --cflags --libs elementary thorvg` + gcc -o testCustomTransform testCustomTransform.cpp -g -lstdc++ `pkg-config --cflags --libs elementary thorvg` + gcc -o testSceneTransform testSceneTransform.cpp -g -lstdc++ `pkg-config --cflags --libs elementary thorvg` + gcc -o testStroke testStroke.cpp -g -lstdc++ `pkg-config --cflags --libs elementary thorvg` + gcc -o testStrokeLine testStrokeLine.cpp -g -lstdc++ `pkg-config --cflags --libs elementary thorvg` + gcc -o testLinearGradient testLinearGradient.cpp -g -lstdc++ `pkg-config --cflags --libs elementary thorvg` + gcc -o testRadialGradient testRadialGradient.cpp -g -lstdc++ `pkg-config --cflags --libs elementary thorvg` + gcc -o testGradientTransform testGradientTransform.cpp -g -lstdc++ `pkg-config --cflags --libs elementary thorvg` + gcc -o testSvg testSvg.cpp -g -lstdc++ `pkg-config --cflags --libs elementary thorvg` + gcc -o testSvg2 testSvg2.cpp -g -lstdc++ `pkg-config --cflags --libs elementary thorvg` + gcc -o testAsync testAsync.cpp -g -lstdc++ `pkg-config --cflags --libs elementary thorvg` + gcc -o testArc testArc.cpp -g -lstdc++ `pkg-config --cflags --libs elementary thorvg` + gcc -o testCapi testCapi.c -g `pkg-config --cflags --libs elementary thorvg` diff --git a/test/svgs/batman1.svg b/test/svgs/batman1.svg new file mode 100644 index 00000000..7a9d2291 --- /dev/null +++ b/test/svgs/batman1.svg @@ -0,0 +1,3 @@ + + + diff --git a/test/svgs/bojo.svg b/test/svgs/bojo.svg new file mode 100644 index 00000000..fe62615d --- /dev/null +++ b/test/svgs/bojo.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + diff --git a/test/svgs/bzrfeed.svg b/test/svgs/bzrfeed.svg new file mode 100644 index 00000000..e5d210f2 --- /dev/null +++ b/test/svgs/bzrfeed.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/svgs/cartman.svg b/test/svgs/cartman.svg new file mode 100644 index 00000000..d4b2740c --- /dev/null +++ b/test/svgs/cartman.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/test/svgs/dst.svg b/test/svgs/dst.svg new file mode 100644 index 00000000..bce75d84 --- /dev/null +++ b/test/svgs/dst.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/test/svgs/duke.svg b/test/svgs/duke.svg new file mode 100644 index 00000000..64cdcf8e --- /dev/null +++ b/test/svgs/duke.svg @@ -0,0 +1,25 @@ + + + + + diff --git a/test/svgs/eee.svg b/test/svgs/eee.svg new file mode 100644 index 00000000..c23875ee --- /dev/null +++ b/test/svgs/eee.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/test/svgs/favorite_on.svg b/test/svgs/favorite_on.svg new file mode 100644 index 00000000..c34b3209 --- /dev/null +++ b/test/svgs/favorite_on.svg @@ -0,0 +1,11 @@ + + + + + + diff --git a/test/svgs/google.svg b/test/svgs/google.svg new file mode 100644 index 00000000..3e51a42e --- /dev/null +++ b/test/svgs/google.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/test/svgs/ibm.svg b/test/svgs/ibm.svg new file mode 100644 index 00000000..c87ad116 --- /dev/null +++ b/test/svgs/ibm.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/svgs/lineargrad1.svg b/test/svgs/lineargrad1.svg new file mode 100644 index 00000000..343d161f --- /dev/null +++ b/test/svgs/lineargrad1.svg @@ -0,0 +1,20 @@ + + + + + + + + + diff --git a/test/svgs/radialgrad1.svg b/test/svgs/radialgrad1.svg new file mode 100644 index 00000000..5cb6da8b --- /dev/null +++ b/test/svgs/radialgrad1.svg @@ -0,0 +1,19 @@ + + + + + + + + + diff --git a/test/svgs/scion.svg b/test/svgs/scion.svg new file mode 100644 index 00000000..24fa109f --- /dev/null +++ b/test/svgs/scion.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/test/svgs/tiger.svg b/test/svgs/tiger.svg new file mode 100644 index 00000000..a59da8d3 --- /dev/null +++ b/test/svgs/tiger.svg @@ -0,0 +1,829 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/svgs/yadis.svg b/test/svgs/yadis.svg new file mode 100644 index 00000000..7bcbad8b --- /dev/null +++ b/test/svgs/yadis.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/svgs/yinyang.svg b/test/svgs/yinyang.svg new file mode 100644 index 00000000..3c64a6c7 --- /dev/null +++ b/test/svgs/yinyang.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/test/testArc.cpp b/test/testArc.cpp new file mode 100644 index 00000000..9c307d44 --- /dev/null +++ b/test/testArc.cpp @@ -0,0 +1,175 @@ +#include "testCommon.h" + +/************************************************************************/ +/* Drawing Commands */ +/************************************************************************/ + +void tvgDrawCmds(tvg::Canvas* canvas) +{ + if (!canvas) return; + + //Arc Line + auto shape1 = tvg::Shape::gen(); + shape1->appendArc(150, 150, 80, 10, 180, false); + shape1->stroke(255, 255, 255, 255); + shape1->stroke(2); + if (canvas->push(move(shape1)) != tvg::Result::Success) return; + + auto shape2 = tvg::Shape::gen(); + shape2->appendArc(400, 150, 80, 0, 300, false); + shape2->stroke(255, 255, 255, 255); + shape2->stroke(2); + if (canvas->push(move(shape2)) != tvg::Result::Success) return; + + auto shape3 = tvg::Shape::gen(); + shape3->appendArc(600, 150, 80, 300, 60, false); + shape3->stroke(255, 255, 255, 255); + shape3->stroke(2); + if (canvas->push(move(shape3)) != tvg::Result::Success) return; + + //Pie Line + auto shape4 = tvg::Shape::gen(); + shape4->appendArc(150, 400, 80, 10, 180, true); + shape4->stroke(255, 255, 255, 255); + shape4->stroke(2); + if (canvas->push(move(shape4)) != tvg::Result::Success) return; + + auto shape5 = tvg::Shape::gen(); + shape5->appendArc(400, 400, 80, 0, 300, true); + shape5->stroke(255, 255, 255, 255); + shape5->stroke(2); + if (canvas->push(move(shape5)) != tvg::Result::Success) return; + + auto shape6 = tvg::Shape::gen(); + shape6->appendArc(600, 400, 80, 300, 60, true); + shape6->stroke(255, 255, 255, 255); + shape6->stroke(2); + if (canvas->push(move(shape6)) != tvg::Result::Success) return; + + //Pie Fill + auto shape7 = tvg::Shape::gen(); + shape7->appendArc(150, 650, 80, 10, 180, true); + shape7->fill(255, 255, 255, 255); + shape7->stroke(255, 0, 0, 255); + shape7->stroke(2); + if (canvas->push(move(shape7)) != tvg::Result::Success) return; + + auto shape8 = tvg::Shape::gen(); + shape8->appendArc(400, 650, 80, 0, 300, true); + shape8->fill(255, 255, 255, 255); + shape8->stroke(255, 0, 0, 255); + shape8->stroke(2); + if (canvas->push(move(shape8)) != tvg::Result::Success) return; + + auto shape9 = tvg::Shape::gen(); + shape9->appendArc(600, 650, 80, 300, 60, true); + shape9->fill(255, 255, 255, 255); + shape9->stroke(255, 0, 0, 255); + shape9->stroke(2); + if (canvas->push(move(shape9)) != tvg::Result::Success) return; +} + +/************************************************************************/ +/* Sw Engine Test Code */ +/************************************************************************/ + +static unique_ptr swCanvas; + +void tvgSwTest(uint32_t* buffer) +{ + //Create a Canvas + swCanvas = tvg::SwCanvas::gen(); + swCanvas->target(buffer, WIDTH, WIDTH, HEIGHT, tvg::SwCanvas::ARGB8888); + + /* Push the shape into the Canvas drawing list + When this shape is into the canvas list, the shape could update & prepare + internal data asynchronously for coming rendering. + Canvas keeps this shape node unless user call canvas->clear() */ + tvgDrawCmds(swCanvas.get()); +} + +void drawSwView(void* data, Eo* obj) +{ + if (swCanvas->draw() == tvg::Result::Success) { + swCanvas->sync(); + } +} + + +/************************************************************************/ +/* GL Engine Test Code */ +/************************************************************************/ + +static unique_ptr glCanvas; + +void initGLview(Evas_Object *obj) +{ + static constexpr auto BPP = 4; + + //Create a Canvas + glCanvas = tvg::GlCanvas::gen(); + glCanvas->target(nullptr, WIDTH * BPP, WIDTH, HEIGHT); + + /* Push the shape into the Canvas drawing list + When this shape is into the canvas list, the shape could update & prepare + internal data asynchronously for coming rendering. + Canvas keeps this shape node unless user call canvas->clear() */ + tvgDrawCmds(glCanvas.get()); +} + +void drawGLview(Evas_Object *obj) +{ + auto gl = elm_glview_gl_api_get(obj); + gl->glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + gl->glClear(GL_COLOR_BUFFER_BIT); + + if (glCanvas->draw() == tvg::Result::Success) { + glCanvas->sync(); + } +} + + +/************************************************************************/ +/* Main Code */ +/************************************************************************/ + +int main(int argc, char **argv) +{ + tvg::CanvasEngine tvgEngine = tvg::CanvasEngine::Sw; + + if (argc > 1) { + if (!strcmp(argv[1], "gl")) tvgEngine = tvg::CanvasEngine::Gl; + } + + //Initialize ThorVG Engine + if (tvgEngine == tvg::CanvasEngine::Sw) { + cout << "tvg engine: software" << endl; + } else { + cout << "tvg engine: opengl" << endl; + } + + //Threads Count + auto threads = std::thread::hardware_concurrency(); + + //Initialize ThorVG Engine + if (tvg::Initializer::init(tvgEngine, threads) == tvg::Result::Success) { + + elm_init(argc, argv); + + if (tvgEngine == tvg::CanvasEngine::Sw) { + createSwView(); + } else { + createGlView(); + } + + elm_run(); + elm_shutdown(); + + //Terminate ThorVG Engine + tvg::Initializer::term(tvgEngine); + + } else { + cout << "engine is not supported" << endl; + } + return 0; +} diff --git a/test/testAsync.cpp b/test/testAsync.cpp new file mode 100644 index 00000000..25e57ba6 --- /dev/null +++ b/test/testAsync.cpp @@ -0,0 +1,181 @@ +#include "testCommon.h" + +/************************************************************************/ +/* Drawing Commands */ +/************************************************************************/ +#define COUNT 50 + +static double t1, t2, t3, t4; +static unsigned cnt = 0; + +bool tvgUpdateCmds(tvg::Canvas* canvas) +{ + if (!canvas) return false; + + auto t = ecore_time_get(); + + //Explicitly clear all retained paint nodes. + if (canvas->clear() != tvg::Result::Success) { + //Logically wrong! Probably, you missed to call sync() before. + return false; + } + + t1 = t; + t2 = ecore_time_get(); + + for (int i = 0; i < COUNT; i++) { + auto shape = tvg::Shape::gen(); + + float x = rand() % (WIDTH/2); + float y = rand() % (HEIGHT/2); + float w = 1 + rand() % (int)(WIDTH * 1.3 / 2); + float h = 1 + rand() % (int)(HEIGHT * 1.3 / 2); + + shape->appendRect(x, y, w, h, 0, 0); + + //LinearGradient + auto fill = tvg::LinearGradient::gen(); + fill->linear(x, y, x + w, y + h); + + //Gradient Color Stops + tvg::Fill::ColorStop colorStops[3]; + colorStops[0] = {0, uint8_t(rand() % 255), uint8_t(rand() % 255), uint8_t(rand() % 255), 255}; + colorStops[1] = {1, uint8_t(rand() % 255), uint8_t(rand() % 255), uint8_t(rand() % 255), 255}; + colorStops[2] = {2, uint8_t(rand() % 255), uint8_t(rand() % 255), uint8_t(rand() % 255), 255}; + + fill->colorStops(colorStops, 3); + shape->fill(move(fill)); + + if (canvas->push(move(shape)) != tvg::Result::Success) { + //Did you call clear()? Make it sure if canvas is on rendering + break; + } + } + + t3 = ecore_time_get(); + + return true; +} + + +/************************************************************************/ +/* Sw Engine Test Code */ +/************************************************************************/ + +static unique_ptr swCanvas; + +void tvgSwTest(uint32_t* buffer) +{ + //Create a Canvas + swCanvas = tvg::SwCanvas::gen(); + swCanvas->target(buffer, WIDTH, WIDTH, HEIGHT, tvg::SwCanvas::ARGB8888); +} + +Eina_Bool animSwCb(void* data) +{ + if (!tvgUpdateCmds(swCanvas.get())) return ECORE_CALLBACK_RENEW; + + //Drawing task can be performed asynchronously. + if (swCanvas->draw() != tvg::Result::Success) return false; + + //Update Efl Canvas + Eo* img = (Eo*) data; + evas_object_image_pixels_dirty_set(img, EINA_TRUE); + evas_object_image_data_update_add(img, 0, 0, WIDTH, HEIGHT); + + return ECORE_CALLBACK_RENEW; +} + +void drawSwView(void* data, Eo* obj) +{ + //Make it guarantee finishing drawing task. + swCanvas->sync(); + + t4 = ecore_time_get(); + + printf("[%5d]: total[%fms] = clear[%fms], update[%fms], render[%fms]\n", ++cnt, t4 - t1, t2 - t1, t3 - t2, t4 - t3); +} + + +/************************************************************************/ +/* GL Engine Test Code */ +/************************************************************************/ + +static unique_ptr glCanvas; + +void initGLview(Evas_Object *obj) +{ + static constexpr auto BPP = 4; + + //Create a Canvas + glCanvas = tvg::GlCanvas::gen(); + glCanvas->target(nullptr, WIDTH * BPP, WIDTH, HEIGHT); +} + +void drawGLview(Evas_Object *obj) +{ + auto gl = elm_glview_gl_api_get(obj); + gl->glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + gl->glClear(GL_COLOR_BUFFER_BIT); + + glCanvas->sync(); +} + +Eina_Bool animGlCb(void* data) +{ + if (!tvgUpdateCmds(glCanvas.get())) return ECORE_CALLBACK_RENEW; + + //Drawing task can be performed asynchronously. + glCanvas->draw(); + + return ECORE_CALLBACK_RENEW; +} + + +/************************************************************************/ +/* Main Code */ +/************************************************************************/ + +int main(int argc, char **argv) +{ + tvg::CanvasEngine tvgEngine = tvg::CanvasEngine::Sw; + + if (argc > 1) { + if (!strcmp(argv[1], "gl")) tvgEngine = tvg::CanvasEngine::Gl; + } + + //Initialize ThorVG Engine + if (tvgEngine == tvg::CanvasEngine::Sw) { + cout << "tvg engine: software" << endl; + } else { + cout << "tvg engine: opengl" << endl; + } + + //Threads Count + auto threads = std::thread::hardware_concurrency(); + + //Initialize ThorVG Engine + if (tvg::Initializer::init(tvgEngine, threads) == tvg::Result::Success) { + + elm_init(argc, argv); + + if (tvgEngine == tvg::CanvasEngine::Sw) { + auto view = createSwView(); + evas_object_image_pixels_get_callback_set(view, drawSwView, nullptr); + ecore_animator_add(animSwCb, view); + } else { + auto view = createGlView(); + ecore_animator_add(animGlCb, view); + } + + elm_run(); + elm_shutdown(); + + //Terminate ThorVG Engine + tvg::Initializer::term(tvgEngine); + + } else { + cout << "engine is not supported" << endl; + } + return 0; +} diff --git a/test/testBlending.cpp b/test/testBlending.cpp new file mode 100644 index 00000000..68623dc3 --- /dev/null +++ b/test/testBlending.cpp @@ -0,0 +1,158 @@ +#include "testCommon.h" + +/************************************************************************/ +/* Drawing Commands */ +/************************************************************************/ + +void tvgDrawCmds(tvg::Canvas* canvas) +{ + if (!canvas) return; + + canvas->reserve(5); + + //Prepare Round Rectangle + auto shape1 = tvg::Shape::gen(); + shape1->appendRect(0, 0, 400, 400, 50, 50); //x, y, w, h, rx, ry + shape1->fill(0, 255, 0, 255); //r, g, b, a + if (canvas->push(move(shape1)) != tvg::Result::Success) return; + + //Prepare Circle + auto shape2 = tvg::Shape::gen(); + shape2->appendCircle(400, 400, 200, 200); //cx, cy, radiusW, radiusH + shape2->fill(255, 255, 0, 170); //r, g, b, a + if (canvas->push(move(shape2)) != tvg::Result::Success) return; + + //Prepare Ellipse + auto shape3 = tvg::Shape::gen(); + shape3->appendCircle(400, 400, 250, 100); //cx, cy, radiusW, radiusH + shape3->fill(255, 255, 255, 100); //r, g, b, a + if (canvas->push(move(shape3)) != tvg::Result::Success) return; + + //Prepare Star + auto shape4 = tvg::Shape::gen(); + shape4->moveTo(199, 234); + shape4->lineTo(253, 343); + shape4->lineTo(374, 360); + shape4->lineTo(287, 444); + shape4->lineTo(307, 565); + shape4->lineTo(199, 509); + shape4->lineTo(97, 565); + shape4->lineTo(112, 445); + shape4->lineTo(26, 361); + shape4->lineTo(146, 343); + shape4->close(); + shape4->fill(255, 0, 200, 200); + if (canvas->push(move(shape4)) != tvg::Result::Success) return; + + //Prepare Opaque Ellipse + auto shape5 = tvg::Shape::gen(); + shape5->appendCircle(600, 650, 200, 150); + shape5->fill(0, 0, 255, 255); + if (canvas->push(move(shape5)) != tvg::Result::Success) return; +} + + +/************************************************************************/ +/* Sw Engine Test Code */ +/************************************************************************/ + +static unique_ptr swCanvas; + +void tvgSwTest(uint32_t* buffer) +{ + //Create a Canvas + swCanvas = tvg::SwCanvas::gen(); + swCanvas->target(buffer, WIDTH, WIDTH, HEIGHT, tvg::SwCanvas::ARGB8888); + + /* Push the shape into the Canvas drawing list + When this shape is into the canvas list, the shape could update & prepare + internal data asynchronously for coming rendering. + Canvas keeps this shape node unless user call canvas->clear() */ + tvgDrawCmds(swCanvas.get()); +} + +void drawSwView(void* data, Eo* obj) +{ + if (swCanvas->draw() == tvg::Result::Success) { + swCanvas->sync(); + } +} + + +/************************************************************************/ +/* GL Engine Test Code */ +/************************************************************************/ + +static unique_ptr glCanvas; + +void initGLview(Evas_Object *obj) +{ + static constexpr auto BPP = 4; + + //Create a Canvas + glCanvas = tvg::GlCanvas::gen(); + glCanvas->target(nullptr, WIDTH * BPP, WIDTH, HEIGHT); + + /* Push the shape into the Canvas drawing list + When this shape is into the canvas list, the shape could update & prepare + internal data asynchronously for coming rendering. + Canvas keeps this shape node unless user call canvas->clear() */ + tvgDrawCmds(glCanvas.get()); +} + +void drawGLview(Evas_Object *obj) +{ + auto gl = elm_glview_gl_api_get(obj); + gl->glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + gl->glClear(GL_COLOR_BUFFER_BIT); + + if (glCanvas->draw() == tvg::Result::Success) { + glCanvas->sync(); + } +} + + +/************************************************************************/ +/* Main Code */ +/************************************************************************/ + +int main(int argc, char **argv) +{ + tvg::CanvasEngine tvgEngine = tvg::CanvasEngine::Sw; + + if (argc > 1) { + if (!strcmp(argv[1], "gl")) tvgEngine = tvg::CanvasEngine::Gl; + } + + //Initialize ThorVG Engine + if (tvgEngine == tvg::CanvasEngine::Sw) { + cout << "tvg engine: software" << endl; + } else { + cout << "tvg engine: opengl" << endl; + } + + //Threads Count + auto threads = std::thread::hardware_concurrency(); + + //Initialize ThorVG Engine + if (tvg::Initializer::init(tvgEngine, threads) == tvg::Result::Success) { + + elm_init(argc, argv); + + if (tvgEngine == tvg::CanvasEngine::Sw) { + createSwView(); + } else { + createGlView(); + } + + elm_run(); + elm_shutdown(); + + //Terminate ThorVG Engine + tvg::Initializer::term(tvgEngine); + + } else { + cout << "engine is not supported" << endl; + } + return 0; +} diff --git a/test/testBoundary.cpp b/test/testBoundary.cpp new file mode 100644 index 00000000..e2ed1c08 --- /dev/null +++ b/test/testBoundary.cpp @@ -0,0 +1,147 @@ +#include "testCommon.h" + +/************************************************************************/ +/* Drawing Commands */ +/************************************************************************/ + +void tvgDrawCmds(tvg::Canvas* canvas) +{ + if (!canvas) return; + + canvas->reserve(5); //reserve 5 shape nodes (optional) + + //Prepare Shape1 + auto shape1 = tvg::Shape::gen(); + shape1->appendRect(-100, -100, 1000, 1000, 50, 50); + shape1->fill(255, 255, 255, 255); + if (canvas->push(move(shape1)) != tvg::Result::Success) return; + + //Prepare Shape2 + auto shape2 = tvg::Shape::gen(); + shape2->appendRect(-100, -100, 250, 250, 50, 50); + shape2->fill(0, 0, 255, 255); + if (canvas->push(move(shape2)) != tvg::Result::Success) return; + + //Prepare Shape3 + auto shape3 = tvg::Shape::gen(); + shape3->appendRect(500, 500, 550, 550, 0, 0); + shape3->fill(0, 255, 255, 255); + if (canvas->push(move(shape3)) != tvg::Result::Success) return; + + //Prepare Shape4 + auto shape4 = tvg::Shape::gen(); + shape4->appendCircle(800, 100, 200, 200); + shape4->fill(255, 255, 0, 255); + if (canvas->push(move(shape4)) != tvg::Result::Success) return; + + //Prepare Shape5 + auto shape5 = tvg::Shape::gen(); + shape5->appendCircle(200, 650, 250, 200); + shape5->fill(0, 0, 0, 255); + if (canvas->push(move(shape5)) != tvg::Result::Success) return; +} + +/************************************************************************/ +/* Sw Engine Test Code */ +/************************************************************************/ + +static unique_ptr swCanvas; + +void tvgSwTest(uint32_t* buffer) +{ + //Create a Canvas + swCanvas = tvg::SwCanvas::gen(); + swCanvas->target(buffer, WIDTH, WIDTH, HEIGHT, tvg::SwCanvas::ARGB8888); + + /* Push the shape into the Canvas drawing list + When this shape is into the canvas list, the shape could update & prepare + internal data asynchronously for coming rendering. + Canvas keeps this shape node unless user call canvas->clear() */ + tvgDrawCmds(swCanvas.get()); +} + +void drawSwView(void* data, Eo* obj) +{ + if (swCanvas->draw() == tvg::Result::Success) { + swCanvas->sync(); + } +} + + +/************************************************************************/ +/* GL Engine Test Code */ +/************************************************************************/ + +static unique_ptr glCanvas; + +void initGLview(Evas_Object *obj) +{ + static constexpr auto BPP = 4; + + //Create a Canvas + glCanvas = tvg::GlCanvas::gen(); + glCanvas->target(nullptr, WIDTH * BPP, WIDTH, HEIGHT); + + /* Push the shape into the Canvas drawing list + When this shape is into the canvas list, the shape could update & prepare + internal data asynchronously for coming rendering. + Canvas keeps this shape node unless user call canvas->clear() */ + tvgDrawCmds(glCanvas.get()); +} + +void drawGLview(Evas_Object *obj) +{ + auto gl = elm_glview_gl_api_get(obj); + gl->glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + gl->glClear(GL_COLOR_BUFFER_BIT); + + if (glCanvas->draw() == tvg::Result::Success) { + glCanvas->sync(); + } +} + + +/************************************************************************/ +/* Main Code */ +/************************************************************************/ + +int main(int argc, char **argv) +{ + tvg::CanvasEngine tvgEngine = tvg::CanvasEngine::Sw; + + if (argc > 1) { + if (!strcmp(argv[1], "gl")) tvgEngine = tvg::CanvasEngine::Gl; + } + + //Initialize ThorVG Engine + if (tvgEngine == tvg::CanvasEngine::Sw) { + cout << "tvg engine: software" << endl; + } else { + cout << "tvg engine: opengl" << endl; + } + + //Threads Count + auto threads = std::thread::hardware_concurrency(); + + //Initialize ThorVG Engine + if (tvg::Initializer::init(tvgEngine, threads) == tvg::Result::Success) { + + elm_init(argc, argv); + + if (tvgEngine == tvg::CanvasEngine::Sw) { + createSwView(); + } else { + createGlView(); + } + + elm_run(); + elm_shutdown(); + + //Terminate ThorVG Engine + tvg::Initializer::term(tvgEngine); + + } else { + cout << "engine is not supported" << endl; + } + return 0; +} diff --git a/test/testCapi.c b/test/testCapi.c new file mode 100644 index 00000000..1b0018d5 --- /dev/null +++ b/test/testCapi.c @@ -0,0 +1,153 @@ +#include +#include + +#define WIDTH 800 +#define HEIGHT 800 + + +/************************************************************************/ +/* Capi Test Code */ +/************************************************************************/ + +static uint32_t buffer[WIDTH * HEIGHT]; + +void testCapi() +{ + tvg_engine_init(TVG_ENGINE_SW | TVG_ENGINE_GL, 0); + + Tvg_Canvas* canvas = tvg_swcanvas_create(); + tvg_swcanvas_set_target(canvas, buffer, WIDTH, WIDTH, HEIGHT, TVG_COLORSPACE_ARGB8888); + + Tvg_Paint* shape = tvg_shape_new(); + tvg_shape_append_rect(shape, 0, 0, 200, 200, 0, 0); + tvg_shape_append_circle(shape, 200, 200, 100, 100); + tvg_shape_append_rect(shape, 100, 100, 300, 300, 100, 100); + Tvg_Gradient* grad = tvg_linear_gradient_new(); + tvg_linear_gradient_set(grad, 0, 0, 300, 300); + Tvg_Color_Stop color_stops[4] = + { + {.offset=0.0, .r=0, .g=0, .b=0, .a=255}, + {.offset=0.25, .r=255, .g=0, .b=0, .a=255}, + {.offset=0.5, .r=0, .g=255, .b=0, .a=255}, + {.offset=1.0, .r=0, .g=0, .b=255, .a=255} + }; + + + Tvg_Paint *shape1 = tvg_shape_new(); + tvg_shape_append_rect(shape1, 500, 500, 100, 100, 30, 30); + Tvg_Gradient* grad1 = tvg_radial_gradient_new(); + tvg_radial_gradient_set(grad1, 550, 550, 50); + Tvg_Color_Stop color_stops1[3] = + { + {.offset=0.0, .r=0, .g=0, .b=0, .a=255}, + {.offset=0.6, .r=255, .g=0, .b=0, .a=255}, + {.offset=1.0, .r=0, .g=255, .b=255, .a=255} + }; + + Tvg_Paint *shape2 = tvg_shape_new(); + tvg_shape_append_rect(shape2, 400, 0, 800, 400, 20, 20); + Tvg_Gradient* grad2 = tvg_linear_gradient_new(); + tvg_linear_gradient_set(grad2, 400, 0, 450, 50); + Tvg_Color_Stop color_stops2[2] = + { + {.offset=0.0, .r=0, .g=0, .b=0, .a=255}, + {.offset=1, .r=255, .g=0, .b=0, .a=255}, + }; + + tvg_gradient_spread(grad2, TVG_STROKE_FILL_REPEAT); + + Tvg_Paint* shape3 = tvg_shape_new(); + tvg_shape_append_rect(shape3, 0, 400, 400, 800, 20, 20); + Tvg_Gradient* grad3 = tvg_linear_gradient_new(); + tvg_linear_gradient_set(grad3, 0, 400, 50, 450); + Tvg_Color_Stop color_stops3[2] = + { + {.offset=0.0, .r=0, .g=0, .b=0, .a=255}, + {.offset=1, .r=0, .g=255, .b=0, .a=255}, + }; + + tvg_gradient_spread(grad3, TVG_STROKE_FILL_REFLECT); + + tvg_gradient_color_stops(grad, color_stops, 4); + tvg_gradient_color_stops(grad1, color_stops1, 3); + tvg_gradient_color_stops(grad2, color_stops2, 2); + tvg_gradient_color_stops(grad3, color_stops3, 2); + tvg_shape_linear_gradient_set(shape, grad); + tvg_shape_radial_gradient_set(shape1, grad1); + tvg_shape_linear_gradient_set(shape2, grad2); + tvg_shape_linear_gradient_set(shape3, grad3); + + tvg_canvas_push(canvas, shape); + tvg_canvas_push(canvas, shape1); + tvg_canvas_push(canvas, shape2); + tvg_canvas_push(canvas, shape3); + + Tvg_Paint* shape4 = tvg_shape_new(); + tvg_shape_append_rect(shape4, 700, 700, 100, 100, 20, 20); + Tvg_Gradient* grad4 = tvg_linear_gradient_new(); + tvg_linear_gradient_set(grad4, 700, 700, 800, 800); + Tvg_Color_Stop color_stops4[2] = + { + {.offset=0.0, .r=0, .g=0, .b=0, .a=255}, + {.offset=1, .r=0, .g=255, .b=0, .a=255}, + }; + tvg_gradient_color_stops(grad4, color_stops4, 2); + tvg_shape_linear_gradient_set(shape4, grad4); + + Tvg_Gradient* grad5 = tvg_linear_gradient_new(); + tvg_linear_gradient_set(grad5, 700, 700, 800, 800); + Tvg_Color_Stop color_stops5[2] = + { + {.offset=0.0, .r=0, .g=0, .b=255, .a=255}, + {.offset=1, .r=0, .g=255, .b=255, .a=255}, + }; + tvg_gradient_color_stops(grad5, color_stops5, 2); + tvg_shape_linear_gradient_set(shape4, grad5); + + tvg_canvas_push(canvas, shape4); + + tvg_canvas_draw(canvas); + tvg_canvas_sync(canvas); + + tvg_canvas_destroy(canvas); + + tvg_engine_term(TVG_ENGINE_SW | TVG_ENGINE_GL); +} + + +/************************************************************************/ +/* Main Code */ +/************************************************************************/ + +void win_del(void *data, Evas_Object *o, void *ev) +{ + elm_exit(); +} + + +int main(int argc, char **argv) +{ + elm_init(argc, argv); + + Eo* win = elm_win_util_standard_add(NULL, "ThorVG Test"); + evas_object_smart_callback_add(win, "delete,request", win_del, 0); + + Eo* view = evas_object_image_filled_add(evas_object_evas_get(win)); + evas_object_image_size_set(view, WIDTH, HEIGHT); + evas_object_image_data_set(view, buffer); + evas_object_image_pixels_dirty_set(view, EINA_TRUE); + evas_object_image_data_update_add(view, 0, 0, WIDTH, HEIGHT); + evas_object_size_hint_weight_set(view, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + evas_object_show(view); + + elm_win_resize_object_add(win, view); + evas_object_geometry_set(win, 0, 0, WIDTH, HEIGHT); + evas_object_show(win); + + testCapi(); + + elm_run(); + elm_shutdown(); + + return 0; +} diff --git a/test/testCommon.h b/test/testCommon.h new file mode 100644 index 00000000..a5f222ef --- /dev/null +++ b/test/testCommon.h @@ -0,0 +1,72 @@ +#include +#include +#include +#include + +using namespace std; + +#define WIDTH 800 +#define HEIGHT 800 + +/************************************************************************/ +/* Common Infrastructure Code */ +/************************************************************************/ + +void tvgSwTest(uint32_t* buffer); +void drawSwView(void* data, Eo* obj); + +void win_del(void *data, Evas_Object *o, void *ev) +{ + elm_exit(); +} + +static Eo* createSwView() +{ + static uint32_t buffer[WIDTH * HEIGHT]; + + Eo* win = elm_win_util_standard_add(NULL, "ThorVG Test"); + evas_object_smart_callback_add(win, "delete,request", win_del, 0); + + Eo* view = evas_object_image_filled_add(evas_object_evas_get(win)); + evas_object_image_size_set(view, WIDTH, HEIGHT); + evas_object_image_data_set(view, buffer); + evas_object_image_pixels_get_callback_set(view, drawSwView, nullptr); + evas_object_image_pixels_dirty_set(view, EINA_TRUE); + evas_object_image_data_update_add(view, 0, 0, WIDTH, HEIGHT); + evas_object_size_hint_weight_set(view, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + evas_object_show(view); + + elm_win_resize_object_add(win, view); + evas_object_geometry_set(win, 0, 0, WIDTH, HEIGHT); + evas_object_show(win); + + tvgSwTest(buffer); + + return view; +} + +void initGLview(Evas_Object *obj); +void drawGLview(Evas_Object *obj); + +static Eo* createGlView() +{ + elm_config_accel_preference_set("gl"); + + Eo* win = elm_win_util_standard_add(NULL, "ThorVG Test"); + evas_object_smart_callback_add(win, "delete,request", win_del, 0); + + Eo* view = elm_glview_add(win); + evas_object_size_hint_weight_set(view, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + elm_glview_mode_set(view, ELM_GLVIEW_ALPHA); + elm_glview_resize_policy_set(view, ELM_GLVIEW_RESIZE_POLICY_RECREATE); + elm_glview_render_policy_set(view, ELM_GLVIEW_RENDER_POLICY_ON_DEMAND); + elm_glview_init_func_set(view, initGLview); + elm_glview_render_func_set(view, drawGLview); + evas_object_show(view); + + elm_win_resize_object_add(win, view); + evas_object_geometry_set(win, 0, 0, WIDTH, HEIGHT); + evas_object_show(win); + + return view; +} diff --git a/test/testCustomTransform.cpp b/test/testCustomTransform.cpp new file mode 100644 index 00000000..50033f9d --- /dev/null +++ b/test/testCustomTransform.cpp @@ -0,0 +1,212 @@ +#include "testCommon.h" + +/************************************************************************/ +/* Drawing Commands */ +/************************************************************************/ +tvg::Shape* pShape = nullptr; + +void tvgDrawCmds(tvg::Canvas* canvas) +{ + if (!canvas) return; + + //Shape1 + auto shape = tvg::Shape::gen(); + + /* Acquire shape pointer to access it again. + instead, you should consider not to interrupt this pointer life-cycle. */ + pShape = shape.get(); + + shape->moveTo(0, -114.5); + shape->lineTo(54, -5.5); + shape->lineTo(175, 11.5); + shape->lineTo(88, 95.5); + shape->lineTo(108, 216.5); + shape->lineTo(0, 160.5); + shape->lineTo(-102, 216.5); + shape->lineTo(-87, 96.5); + shape->lineTo(-173, 12.5); + shape->lineTo(-53, -5.5); + shape->close(); + shape->fill(0, 0, 255, 255); + shape->stroke(3); + shape->stroke(255, 255, 255, 255); + if (canvas->push(move(shape)) != tvg::Result::Success) return; +} + +void tvgUpdateCmds(tvg::Canvas* canvas, float progress) +{ + if (!canvas) return; + + /* Update shape directly. + You can update only necessary properties of this shape, + while retaining other properties. */ + + //Transform Matrix + tvg::Matrix m = {1, 0, 0, 0, 1, 0, 0, 0, 1}; + + //scale x + m.e11 = 1 - (progress * 0.5f); + + //scale y + m.e22 = 1 + (progress * 2.0f); + + //rotation + constexpr auto PI = 3.141592f; + auto degree = 45.0f; + auto radian = degree / 180.0f * PI; + auto cosVal = cosf(radian); + auto sinVal = sinf(radian); + + auto t11 = m.e11 * cosVal + m.e12 * sinVal; + auto t12 = m.e11 * -sinVal + m.e12 * cosVal; + auto t21 = m.e21 * cosVal + m.e22 * sinVal; + auto t22 = m.e21 * -sinVal + m.e22 * cosVal; + auto t13 = m.e31 * cosVal + m.e32 * sinVal; + auto t23 = m.e31 * -sinVal + m.e32 * cosVal; + + m.e11 = t11; + m.e12 = t12; + m.e21 = t21; + m.e22 = t22; + m.e13 = t13; + m.e23 = t23; + + //translate + m.e13 = progress * 300.0f + 300.0f; + m.e23 = progress * -100.0f + 300.0f; + + pShape->transform(m); + + //Update shape for drawing (this may work asynchronously) + canvas->update(pShape); +} + + +/************************************************************************/ +/* Sw Engine Test Code */ +/************************************************************************/ + +static unique_ptr swCanvas; + +void tvgSwTest(uint32_t* buffer) +{ + //Create a Canvas + swCanvas = tvg::SwCanvas::gen(); + swCanvas->target(buffer, WIDTH, WIDTH, HEIGHT, tvg::SwCanvas::ARGB8888); + + /* Push the shape into the Canvas drawing list + When this shape is into the canvas list, the shape could update & prepare + internal data asynchronously for coming rendering. + Canvas keeps this shape node unless user call canvas->clear() */ + tvgDrawCmds(swCanvas.get()); +} + +void transitSwCb(Elm_Transit_Effect *effect, Elm_Transit* transit, double progress) +{ + tvgUpdateCmds(swCanvas.get(), progress); + + //Update Efl Canvas + Eo* img = (Eo*) effect; + evas_object_image_data_update_add(img, 0, 0, WIDTH, HEIGHT); + evas_object_image_pixels_dirty_set(img, EINA_TRUE); +} + +void drawSwView(void* data, Eo* obj) +{ + if (swCanvas->draw() == tvg::Result::Success) { + swCanvas->sync(); + } +} + + +/************************************************************************/ +/* GL Engine Test Code */ +/************************************************************************/ + +static unique_ptr glCanvas; + +void initGLview(Evas_Object *obj) +{ + static constexpr auto BPP = 4; + + //Create a Canvas + glCanvas = tvg::GlCanvas::gen(); + glCanvas->target(nullptr, WIDTH * BPP, WIDTH, HEIGHT); + + /* Push the shape into the Canvas drawing list + When this shape is into the canvas list, the shape could update & prepare + internal data asynchronously for coming rendering. + Canvas keeps this shape node unless user call canvas->clear() */ + tvgDrawCmds(glCanvas.get()); +} + +void drawGLview(Evas_Object *obj) +{ + auto gl = elm_glview_gl_api_get(obj); + gl->glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + gl->glClear(GL_COLOR_BUFFER_BIT); + + if (glCanvas->draw() == tvg::Result::Success) { + glCanvas->sync(); + } +} + +void transitGlCb(Elm_Transit_Effect *effect, Elm_Transit* transit, double progress) +{ + tvgUpdateCmds(glCanvas.get(), progress); +} + + +/************************************************************************/ +/* Main Code */ +/************************************************************************/ + +int main(int argc, char **argv) +{ + tvg::CanvasEngine tvgEngine = tvg::CanvasEngine::Sw; + + if (argc > 1) { + if (!strcmp(argv[1], "gl")) tvgEngine = tvg::CanvasEngine::Gl; + } + + //Initialize ThorVG Engine + if (tvgEngine == tvg::CanvasEngine::Sw) { + cout << "tvg engine: software" << endl; + } else { + cout << "tvg engine: opengl" << endl; + } + + //Threads Count + auto threads = std::thread::hardware_concurrency(); + + //Initialize ThorVG Engine + if (tvg::Initializer::init(tvgEngine, threads) == tvg::Result::Success) { + + elm_init(argc, argv); + + Elm_Transit *transit = elm_transit_add(); + + if (tvgEngine == tvg::CanvasEngine::Sw) { + auto view = createSwView(); + elm_transit_effect_add(transit, transitSwCb, view, nullptr); + } else { + auto view = createGlView(); + elm_transit_effect_add(transit, transitGlCb, view, nullptr); + } + + elm_transit_duration_set(transit, 2); + elm_transit_repeat_times_set(transit, -1); + elm_transit_auto_reverse_set(transit, EINA_TRUE); + elm_transit_go(transit); + + elm_run(); + elm_shutdown(); + + //Terminate ThorVG Engine + tvg::Initializer::term(tvgEngine); + + } else { + cout << "engine is not supported" << endl; + } + return 0; +} diff --git a/test/testDirectUpdate.cpp b/test/testDirectUpdate.cpp new file mode 100644 index 00000000..69105c32 --- /dev/null +++ b/test/testDirectUpdate.cpp @@ -0,0 +1,175 @@ +#include "testCommon.h" + +/************************************************************************/ +/* Drawing Commands */ +/************************************************************************/ +tvg::Shape* pShape = nullptr; + +void tvgDrawCmds(tvg::Canvas* canvas) +{ + if (!canvas) return; + + //Shape + auto shape = tvg::Shape::gen(); + + /* Acquire shape pointer to access it again. + instead, you should consider not to interrupt this pointer life-cycle. */ + pShape = shape.get(); + + shape->appendRect(-100, -100, 200, 200, 0, 0); + + //fill property will be retained + shape->fill(127, 255, 255, 255); + shape->stroke(0, 0, 255, 255); + shape->stroke(1); + + if (canvas->push(move(shape)) != tvg::Result::Success) return; +} + +void tvgUpdateCmds(tvg::Canvas* canvas, float progress) +{ + if (!canvas) return; + + /* Update shape directly. + You can update only necessary properties of this shape, + while retaining other properties. */ + + //Reset Shape + if (pShape->reset() == tvg::Result::Success) { + pShape->appendRect(-100 + (800 * progress), -100 + (800 * progress), 200, 200, (100 * progress), (100 * progress)); + pShape->stroke(30 * progress); + + //Update shape for drawing (this may work asynchronously) + canvas->update(pShape); + } +} + + +/************************************************************************/ +/* Sw Engine Test Code */ +/************************************************************************/ + +static unique_ptr swCanvas; + +void tvgSwTest(uint32_t* buffer) +{ + //Create a Canvas + swCanvas = tvg::SwCanvas::gen(); + swCanvas->target(buffer, WIDTH, WIDTH, HEIGHT, tvg::SwCanvas::ARGB8888); + + /* Push the shape into the Canvas drawing list + When this shape is into the canvas list, the shape could update & prepare + internal data asynchronously for coming rendering. + Canvas keeps this shape node unless user call canvas->clear() */ + tvgDrawCmds(swCanvas.get()); +} + +void transitSwCb(Elm_Transit_Effect *effect, Elm_Transit* transit, double progress) +{ + tvgUpdateCmds(swCanvas.get(), progress); + + //Update Efl Canvas + Eo* img = (Eo*) effect; + evas_object_image_data_update_add(img, 0, 0, WIDTH, HEIGHT); + evas_object_image_pixels_dirty_set(img, EINA_TRUE); +} + +void drawSwView(void* data, Eo* obj) +{ + if (swCanvas->draw() == tvg::Result::Success) { + swCanvas->sync(); + } +} + + +/************************************************************************/ +/* GL Engine Test Code */ +/************************************************************************/ + +static unique_ptr glCanvas; + +void initGLview(Evas_Object *obj) +{ + static constexpr auto BPP = 4; + + //Create a Canvas + glCanvas = tvg::GlCanvas::gen(); + glCanvas->target(nullptr, WIDTH * BPP, WIDTH, HEIGHT); + + /* Push the shape into the Canvas drawing list + When this shape is into the canvas list, the shape could update & prepare + internal data asynchronously for coming rendering. + Canvas keeps this shape node unless user call canvas->clear() */ + tvgDrawCmds(glCanvas.get()); +} + +void drawGLview(Evas_Object *obj) +{ + auto gl = elm_glview_gl_api_get(obj); + gl->glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + gl->glClear(GL_COLOR_BUFFER_BIT); + + if (glCanvas->draw() == tvg::Result::Success) { + glCanvas->sync(); + } +} + +void transitGlCb(Elm_Transit_Effect *effect, Elm_Transit* transit, double progress) +{ + tvgUpdateCmds(glCanvas.get(), progress); +} + + +/************************************************************************/ +/* Main Code */ +/************************************************************************/ + +int main(int argc, char **argv) +{ + tvg::CanvasEngine tvgEngine = tvg::CanvasEngine::Sw; + + if (argc > 1) { + if (!strcmp(argv[1], "gl")) tvgEngine = tvg::CanvasEngine::Gl; + } + + //Initialize ThorVG Engine + if (tvgEngine == tvg::CanvasEngine::Sw) { + cout << "tvg engine: software" << endl; + } else { + cout << "tvg engine: opengl" << endl; + } + + //Threads Count + auto threads = std::thread::hardware_concurrency(); + + //Initialize ThorVG Engine + if (tvg::Initializer::init(tvgEngine, threads) == tvg::Result::Success) { + + elm_init(argc, argv); + + Elm_Transit *transit = elm_transit_add(); + + if (tvgEngine == tvg::CanvasEngine::Sw) { + auto view = createSwView(); + elm_transit_effect_add(transit, transitSwCb, view, nullptr); + } else { + auto view = createGlView(); + elm_transit_effect_add(transit, transitGlCb, view, nullptr); + } + + elm_transit_duration_set(transit, 2); + elm_transit_repeat_times_set(transit, -1); + elm_transit_auto_reverse_set(transit, EINA_TRUE); + elm_transit_go(transit); + + elm_run(); + elm_shutdown(); + + //Terminate ThorVG Engine + tvg::Initializer::term(tvgEngine); + + } else { + cout << "engine is not supported" << endl; + } + return 0; +} diff --git a/test/testGradientTransform.cpp b/test/testGradientTransform.cpp new file mode 100644 index 00000000..45431e3f --- /dev/null +++ b/test/testGradientTransform.cpp @@ -0,0 +1,240 @@ +#include "testCommon.h" + +/************************************************************************/ +/* Drawing Commands */ +/************************************************************************/ +tvg::Shape* pShape = nullptr; +tvg::Shape* pShape2 = nullptr; +tvg::Shape* pShape3 = nullptr; + +void tvgDrawCmds(tvg::Canvas* canvas) +{ + if (!canvas) return; + + //Shape1 + auto shape = tvg::Shape::gen(); + + /* Acquire shape pointer to access it again. + instead, you should consider not to interrupt this pointer life-cycle. */ + pShape = shape.get(); + + shape->appendRect(-285, -300, 200, 200, 0, 0); + shape->appendRect(-185, -200, 300, 300, 100, 100); + shape->appendCircle(115, 100, 100, 100); + shape->appendCircle(115, 200, 170, 100); + + //LinearGradient + auto fill = tvg::LinearGradient::gen(); + fill->linear(-285, -300, 285, 300); + + //Gradient Color Stops + tvg::Fill::ColorStop colorStops[3]; + colorStops[0] = {0, 255, 0, 0, 255}; + colorStops[1] = {0.5, 255, 255, 0, 255}; + colorStops[2] = {1, 255, 255, 255, 255}; + + fill->colorStops(colorStops, 3); + shape->fill(move(fill)); + shape->translate(385, 400); + if (canvas->push(move(shape)) != tvg::Result::Success) return; + + //Shape2 + auto shape2 = tvg::Shape::gen(); + pShape2 = shape2.get(); + shape2->appendRect(-50, -50, 100, 100, 0, 0); + shape2->translate(400, 400); + + //LinearGradient + auto fill2 = tvg::LinearGradient::gen(); + fill2->linear(-50, -50, 50, 50); + + //Gradient Color Stops + tvg::Fill::ColorStop colorStops2[2]; + colorStops2[0] = {0, 0, 0, 0, 255}; + colorStops2[1] = {1, 255, 255, 255, 255}; + + fill2->colorStops(colorStops2, 2); + shape2->fill(move(fill2)); + if (canvas->push(move(shape2)) != tvg::Result::Success) return; + + //Shape3 + auto shape3 = tvg::Shape::gen(); + pShape3 = shape3.get(); + + /* Look, how shape3's origin is different with shape2 + The center of the shape is the anchor point for transformation. */ + shape3->appendRect(100, 100, 150, 100, 20, 20); + + //RadialGradient + auto fill3 = tvg::RadialGradient::gen(); + fill3->radial(175, 150, 75); + + //Gradient Color Stops + tvg::Fill::ColorStop colorStops3[4]; + colorStops3[0] = {0, 0, 127, 0, 127}; + colorStops3[1] = {0.25, 0, 170, 170, 170}; + colorStops3[2] = {0.5, 200, 0, 200, 200}; + colorStops3[3] = {1, 255, 255, 255, 255}; + + fill3->colorStops(colorStops3, 4); + + shape3->fill(move(fill3)); + shape3->translate(400, 400); + if (canvas->push(move(shape3)) != tvg::Result::Success) return; +} + +void tvgUpdateCmds(tvg::Canvas* canvas, float progress) +{ + if (!canvas) return; + + /* Update shape directly. + You can update only necessary properties of this shape, + while retaining other properties. */ + + //Update Shape1 + pShape->scale(1 - 0.75 * progress); + pShape->rotate(360 * progress); + + //Update shape for drawing (this may work asynchronously) + if (canvas->update(pShape) != tvg::Result::Success) return; + + //Update Shape2 + pShape2->rotate(360 * progress); + pShape2->translate(400 + progress * 300, 400); + if (canvas->update(pShape2) != tvg::Result::Success) return; + + //Update Shape3 + pShape3->rotate(-360 * progress); + pShape3->scale(0.5 + progress); + if (canvas->update(pShape3) != tvg::Result::Success) return; +} + + +/************************************************************************/ +/* Sw Engine Test Code */ +/************************************************************************/ + +static unique_ptr swCanvas; + +void tvgSwTest(uint32_t* buffer) +{ + //Create a Canvas + swCanvas = tvg::SwCanvas::gen(); + swCanvas->target(buffer, WIDTH, WIDTH, HEIGHT, tvg::SwCanvas::ARGB8888); + + /* Push the shape into the Canvas drawing list + When this shape is into the canvas list, the shape could update & prepare + internal data asynchronously for coming rendering. + Canvas keeps this shape node unless user call canvas->clear() */ + tvgDrawCmds(swCanvas.get()); +} + +void transitSwCb(Elm_Transit_Effect *effect, Elm_Transit* transit, double progress) +{ + tvgUpdateCmds(swCanvas.get(), progress); + + //Update Efl Canvas + Eo* img = (Eo*) effect; + evas_object_image_data_update_add(img, 0, 0, WIDTH, HEIGHT); + evas_object_image_pixels_dirty_set(img, EINA_TRUE); +} + +void drawSwView(void* data, Eo* obj) +{ + if (swCanvas->draw() == tvg::Result::Success) { + swCanvas->sync(); + } +} + + +/************************************************************************/ +/* GL Engine Test Code */ +/************************************************************************/ + +static unique_ptr glCanvas; + +void initGLview(Evas_Object *obj) +{ + static constexpr auto BPP = 4; + + //Create a Canvas + glCanvas = tvg::GlCanvas::gen(); + glCanvas->target(nullptr, WIDTH * BPP, WIDTH, HEIGHT); + + /* Push the shape into the Canvas drawing list + When this shape is into the canvas list, the shape could update & prepare + internal data asynchronously for coming rendering. + Canvas keeps this shape node unless user call canvas->clear() */ + tvgDrawCmds(glCanvas.get()); +} + +void drawGLview(Evas_Object *obj) +{ + auto gl = elm_glview_gl_api_get(obj); + gl->glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + gl->glClear(GL_COLOR_BUFFER_BIT); + + if (glCanvas->draw() == tvg::Result::Success) { + glCanvas->sync(); + } +} + +void transitGlCb(Elm_Transit_Effect *effect, Elm_Transit* transit, double progress) +{ + tvgUpdateCmds(glCanvas.get(), progress); +} + + +/************************************************************************/ +/* Main Code */ +/************************************************************************/ + +int main(int argc, char **argv) +{ + tvg::CanvasEngine tvgEngine = tvg::CanvasEngine::Sw; + + if (argc > 1) { + if (!strcmp(argv[1], "gl")) tvgEngine = tvg::CanvasEngine::Gl; + } + + //Initialize ThorVG Engine + if (tvgEngine == tvg::CanvasEngine::Sw) { + cout << "tvg engine: software" << endl; + } else { + cout << "tvg engine: opengl" << endl; + } + + //Threads Count + auto threads = std::thread::hardware_concurrency(); + + //Initialize ThorVG Engine + if (tvg::Initializer::init(tvgEngine, threads) == tvg::Result::Success) { + + elm_init(argc, argv); + + Elm_Transit *transit = elm_transit_add(); + + if (tvgEngine == tvg::CanvasEngine::Sw) { + auto view = createSwView(); + elm_transit_effect_add(transit, transitSwCb, view, nullptr); + } else { + auto view = createGlView(); + elm_transit_effect_add(transit, transitGlCb, view, nullptr); + } + + elm_transit_duration_set(transit, 2); + elm_transit_repeat_times_set(transit, -1); + elm_transit_auto_reverse_set(transit, EINA_TRUE); + elm_transit_go(transit); + + elm_run(); + elm_shutdown(); + + //Terminate ThorVG Engine + tvg::Initializer::term(tvgEngine); + + } else { + cout << "engine is not supported" << endl; + } + return 0; +} diff --git a/test/testLinearGradient.cpp b/test/testLinearGradient.cpp new file mode 100644 index 00000000..d1ed5fa7 --- /dev/null +++ b/test/testLinearGradient.cpp @@ -0,0 +1,176 @@ +#include "testCommon.h" + +/************************************************************************/ +/* Drawing Commands */ +/************************************************************************/ + +void tvgDrawCmds(tvg::Canvas* canvas) +{ + if (!canvas) return; + + canvas->reserve(3); //reserve 3 shape nodes (optional) + + //Prepare Round Rectangle + auto shape1 = tvg::Shape::gen(); + shape1->appendRect(0, 0, 400, 400, 0, 0); //x, y, w, h, rx, ry + + //LinearGradient + auto fill = tvg::LinearGradient::gen(); + fill->linear(0, 0, 400, 400); + + //Gradient Color Stops + tvg::Fill::ColorStop colorStops[2]; + colorStops[0] = {0, 0, 0, 0, 255}; + colorStops[1] = {1, 255, 255, 255, 255}; + + fill->colorStops(colorStops, 2); + + shape1->fill(move(fill)); + if (canvas->push(move(shape1)) != tvg::Result::Success) return; + + //Prepare Circle + auto shape2 = tvg::Shape::gen(); + shape2->appendCircle(400, 400, 200, 200); //cx, cy, radiusW, radiusH + + //LinearGradient + auto fill2 = tvg::LinearGradient::gen(); + fill2->linear(400, 200, 400, 600); + + //Gradient Color Stops + tvg::Fill::ColorStop colorStops2[3]; + colorStops2[0] = {0, 255, 0, 0, 255}; + colorStops2[1] = {0.5, 255, 255, 0, 255}; + colorStops2[2] = {1, 255, 255, 255, 255}; + + fill2->colorStops(colorStops2, 3); + + shape2->fill(move(fill2)); + if (canvas->push(move(shape2)) != tvg::Result::Success) return; + + + //Prepare Ellipse + auto shape3 = tvg::Shape::gen(); + shape3->appendCircle(600, 600, 150, 100); //cx, cy, radiusW, radiusH + + //LinearGradient + auto fill3 = tvg::LinearGradient::gen(); + fill3->linear(450, 600, 750, 600); + + //Gradient Color Stops + tvg::Fill::ColorStop colorStops3[4]; + colorStops3[0] = {0, 0, 127, 0, 127}; + colorStops3[1] = {0.25, 0, 170, 170, 170}; + colorStops3[2] = {0.5, 200, 0, 200, 200}; + colorStops3[3] = {1, 255, 255, 255, 255}; + + fill3->colorStops(colorStops3, 4); + + shape3->fill(move(fill3)); + if (canvas->push(move(shape3)) != tvg::Result::Success) return; +} + + +/************************************************************************/ +/* Sw Engine Test Code */ +/************************************************************************/ + +static unique_ptr swCanvas; + +void tvgSwTest(uint32_t* buffer) +{ + //Create a Canvas + swCanvas = tvg::SwCanvas::gen(); + swCanvas->target(buffer, WIDTH, WIDTH, HEIGHT, tvg::SwCanvas::ARGB8888); + + /* Push the shape into the Canvas drawing list + When this shape is into the canvas list, the shape could update & prepare + internal data asynchronously for coming rendering. + Canvas keeps this shape node unless user call canvas->clear() */ + tvgDrawCmds(swCanvas.get()); +} + +void drawSwView(void* data, Eo* obj) +{ + if (swCanvas->draw() == tvg::Result::Success) { + swCanvas->sync(); + } +} + + +/************************************************************************/ +/* GL Engine Test Code */ +/************************************************************************/ + +static unique_ptr glCanvas; + +void initGLview(Evas_Object *obj) +{ + static constexpr auto BPP = 4; + + //Create a Canvas + glCanvas = tvg::GlCanvas::gen(); + glCanvas->target(nullptr, WIDTH * BPP, WIDTH, HEIGHT); + + /* Push the shape into the Canvas drawing list + When this shape is into the canvas list, the shape could update & prepare + internal data asynchronously for coming rendering. + Canvas keeps this shape node unless user call canvas->clear() */ + tvgDrawCmds(glCanvas.get()); +} + +void drawGLview(Evas_Object *obj) +{ + auto gl = elm_glview_gl_api_get(obj); + gl->glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + gl->glClear(GL_COLOR_BUFFER_BIT); + + if (glCanvas->draw() == tvg::Result::Success) { + glCanvas->sync(); + } +} + + +/************************************************************************/ +/* Main Code */ +/************************************************************************/ + +int main(int argc, char **argv) +{ + tvg::CanvasEngine tvgEngine = tvg::CanvasEngine::Sw; + + if (argc > 1) { + if (!strcmp(argv[1], "gl")) tvgEngine = tvg::CanvasEngine::Gl; + } + + //Initialize ThorVG Engine + if (tvgEngine == tvg::CanvasEngine::Sw) { + cout << "tvg engine: software" << endl; + } else { + cout << "tvg engine: opengl" << endl; + } + + //Threads Count + auto threads = std::thread::hardware_concurrency(); + + //Initialize ThorVG Engine + if (tvg::Initializer::init(tvgEngine, threads) == tvg::Result::Success) { + + elm_init(argc, argv); + + if (tvgEngine == tvg::CanvasEngine::Sw) { + createSwView(); + } else { + createGlView(); + } + + elm_run(); + elm_shutdown(); + + //Terminate ThorVG Engine + tvg::Initializer::term(tvgEngine); + + } else { + cout << "engine is not supported" << endl; + } + return 0; +} diff --git a/test/testMultiShapes.cpp b/test/testMultiShapes.cpp new file mode 100644 index 00000000..3db41b9d --- /dev/null +++ b/test/testMultiShapes.cpp @@ -0,0 +1,136 @@ +#include "testCommon.h" + +/************************************************************************/ +/* Drawing Commands */ +/************************************************************************/ + +void tvgDrawCmds(tvg::Canvas* canvas) +{ + if (!canvas) return; + + canvas->reserve(3); //reserve 3 shape nodes (optional) + + //Prepare Round Rectangle + auto shape1 = tvg::Shape::gen(); + shape1->appendRect(0, 0, 400, 400, 50, 50); //x, y, w, h, rx, ry + shape1->fill(0, 255, 0, 255); //r, g, b, a + if (canvas->push(move(shape1)) != tvg::Result::Success) return; + + //Prepare Circle + auto shape2 = tvg::Shape::gen(); + shape2->appendCircle(400, 400, 200, 200); //cx, cy, radiusW, radiusH + shape2->fill(255, 255, 0, 255); //r, g, b, a + if (canvas->push(move(shape2)) != tvg::Result::Success) return; + + //Prepare Ellipse + auto shape3 = tvg::Shape::gen(); + shape3->appendCircle(600, 600, 150, 100); //cx, cy, radiusW, radiusH + shape3->fill(0, 255, 255, 255); //r, g, b, a + if (canvas->push(move(shape3)) != tvg::Result::Success) return; +} + + +/************************************************************************/ +/* Sw Engine Test Code */ +/************************************************************************/ + +static unique_ptr swCanvas; + +void tvgSwTest(uint32_t* buffer) +{ + //Create a Canvas + swCanvas = tvg::SwCanvas::gen(); + swCanvas->target(buffer, WIDTH, WIDTH, HEIGHT, tvg::SwCanvas::ARGB8888); + + /* Push the shape into the Canvas drawing list + When this shape is into the canvas list, the shape could update & prepare + internal data asynchronously for coming rendering. + Canvas keeps this shape node unless user call canvas->clear() */ + tvgDrawCmds(swCanvas.get()); +} + +void drawSwView(void* data, Eo* obj) +{ + if (swCanvas->draw() == tvg::Result::Success) { + swCanvas->sync(); + } +} + + +/************************************************************************/ +/* GL Engine Test Code */ +/************************************************************************/ + +static unique_ptr glCanvas; + +void initGLview(Evas_Object *obj) +{ + static constexpr auto BPP = 4; + + //Create a Canvas + glCanvas = tvg::GlCanvas::gen(); + glCanvas->target(nullptr, WIDTH * BPP, WIDTH, HEIGHT); + + /* Push the shape into the Canvas drawing list + When this shape is into the canvas list, the shape could update & prepare + internal data asynchronously for coming rendering. + Canvas keeps this shape node unless user call canvas->clear() */ + tvgDrawCmds(glCanvas.get()); +} + +void drawGLview(Evas_Object *obj) +{ + auto gl = elm_glview_gl_api_get(obj); + gl->glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + gl->glClear(GL_COLOR_BUFFER_BIT); + + if (glCanvas->draw() == tvg::Result::Success) { + glCanvas->sync(); + } +} + + +/************************************************************************/ +/* Main Code */ +/************************************************************************/ + +int main(int argc, char **argv) +{ + tvg::CanvasEngine tvgEngine = tvg::CanvasEngine::Sw; + + if (argc > 1) { + if (!strcmp(argv[1], "gl")) tvgEngine = tvg::CanvasEngine::Gl; + } + + //Initialize ThorVG Engine + if (tvgEngine == tvg::CanvasEngine::Sw) { + cout << "tvg engine: software" << endl; + } else { + cout << "tvg engine: opengl" << endl; + } + + //Threads Count + auto threads = std::thread::hardware_concurrency(); + + //Initialize ThorVG Engine + if (tvg::Initializer::init(tvgEngine, threads) == tvg::Result::Success) { + + elm_init(argc, argv); + + if (tvgEngine == tvg::CanvasEngine::Sw) { + createSwView(); + } else { + createGlView(); + } + + elm_run(); + elm_shutdown(); + + //Terminate ThorVG Engine + tvg::Initializer::term(tvgEngine); + + } else { + cout << "engine is not supported" << endl; + } + return 0; +} diff --git a/test/testPath.cpp b/test/testPath.cpp new file mode 100644 index 00000000..a265d03c --- /dev/null +++ b/test/testPath.cpp @@ -0,0 +1,153 @@ +#include "testCommon.h" + +/************************************************************************/ +/* Drawing Commands */ +/************************************************************************/ + +void tvgDrawCmds(tvg::Canvas* canvas) +{ + if (!canvas) return; + + //Star + auto shape1 = tvg::Shape::gen(); + + //Appends Paths + shape1->moveTo(199, 34); + shape1->lineTo(253, 143); + shape1->lineTo(374, 160); + shape1->lineTo(287, 244); + shape1->lineTo(307, 365); + shape1->lineTo(199, 309); + shape1->lineTo(97, 365); + shape1->lineTo(112, 245); + shape1->lineTo(26, 161); + shape1->lineTo(146, 143); + shape1->close(); + shape1->fill(0, 0, 255, 255); + if (canvas->push(move(shape1)) != tvg::Result::Success) return; + + + //Circle + auto shape2 = tvg::Shape::gen(); + + auto cx = 550.0f; + auto cy = 550.0f; + auto radius = 125.0f; + auto halfRadius = radius * 0.552284f; + + //Append Paths + shape2->moveTo(cx, cy - radius); + shape2->cubicTo(cx + halfRadius, cy - radius, cx + radius, cy - halfRadius, cx + radius, cy); + shape2->cubicTo(cx + radius, cy + halfRadius, cx + halfRadius, cy + radius, cx, cy+ radius); + shape2->cubicTo(cx - halfRadius, cy + radius, cx - radius, cy + halfRadius, cx - radius, cy); + shape2->cubicTo(cx - radius, cy - halfRadius, cx - halfRadius, cy - radius, cx, cy - radius); + shape2->close(); + shape2->fill(255, 0, 0, 255); + if (canvas->push(move(shape2)) != tvg::Result::Success) return; + +} + +/************************************************************************/ +/* Sw Engine Test Code */ +/************************************************************************/ + +static unique_ptr swCanvas; + +void tvgSwTest(uint32_t* buffer) +{ + //Create a Canvas + swCanvas = tvg::SwCanvas::gen(); + swCanvas->target(buffer, WIDTH, WIDTH, HEIGHT, tvg::SwCanvas::ARGB8888); + + /* Push the shape into the Canvas drawing list + When this shape is into the canvas list, the shape could update & prepare + internal data asynchronously for coming rendering. + Canvas keeps this shape node unless user call canvas->clear() */ + tvgDrawCmds(swCanvas.get()); +} + +void drawSwView(void* data, Eo* obj) +{ + if (swCanvas->draw() == tvg::Result::Success) { + swCanvas->sync(); + } +} + + +/************************************************************************/ +/* GL Engine Test Code */ +/************************************************************************/ + +static unique_ptr glCanvas; + +void initGLview(Evas_Object *obj) +{ + static constexpr auto BPP = 4; + + //Create a Canvas + glCanvas = tvg::GlCanvas::gen(); + glCanvas->target(nullptr, WIDTH * BPP, WIDTH, HEIGHT); + + /* Push the shape into the Canvas drawing list + When this shape is into the canvas list, the shape could update & prepare + internal data asynchronously for coming rendering. + Canvas keeps this shape node unless user call canvas->clear() */ + tvgDrawCmds(glCanvas.get()); +} + +void drawGLview(Evas_Object *obj) +{ + auto gl = elm_glview_gl_api_get(obj); + gl->glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + gl->glClear(GL_COLOR_BUFFER_BIT); + + if (glCanvas->draw() == tvg::Result::Success) { + glCanvas->sync(); + } +} + + +/************************************************************************/ +/* Main Code */ +/************************************************************************/ + +int main(int argc, char **argv) +{ + tvg::CanvasEngine tvgEngine = tvg::CanvasEngine::Sw; + + if (argc > 1) { + if (!strcmp(argv[1], "gl")) tvgEngine = tvg::CanvasEngine::Gl; + } + + //Initialize ThorVG Engine + if (tvgEngine == tvg::CanvasEngine::Sw) { + cout << "tvg engine: software" << endl; + } else { + cout << "tvg engine: opengl" << endl; + } + + //Threads Count + auto threads = std::thread::hardware_concurrency(); + + //Initialize ThorVG Engine + if (tvg::Initializer::init(tvgEngine, threads) == tvg::Result::Success) { + + elm_init(argc, argv); + + if (tvgEngine == tvg::CanvasEngine::Sw) { + createSwView(); + } else { + createGlView(); + } + + elm_run(); + elm_shutdown(); + + //Terminate ThorVG Engine + tvg::Initializer::term(tvgEngine); + + } else { + cout << "engine is not supported" << endl; + } + return 0; +} diff --git a/test/testPathCopy.cpp b/test/testPathCopy.cpp new file mode 100644 index 00000000..b5d97cfc --- /dev/null +++ b/test/testPathCopy.cpp @@ -0,0 +1,190 @@ +#include "testCommon.h" + +/************************************************************************/ +/* Drawing Commands */ +/************************************************************************/ + +void tvgDrawCmds(tvg::Canvas* canvas) +{ + if (!canvas) return; + + /* Star */ + + //Prepare Path Commands + tvg::PathCommand cmds[11]; + cmds[0] = tvg::PathCommand::MoveTo; + cmds[1] = tvg::PathCommand::LineTo; + cmds[2] = tvg::PathCommand::LineTo; + cmds[3] = tvg::PathCommand::LineTo; + cmds[4] = tvg::PathCommand::LineTo; + cmds[5] = tvg::PathCommand::LineTo; + cmds[6] = tvg::PathCommand::LineTo; + cmds[7] = tvg::PathCommand::LineTo; + cmds[8] = tvg::PathCommand::LineTo; + cmds[9] = tvg::PathCommand::LineTo; + cmds[10] = tvg::PathCommand::Close; + + //Prepare Path Points + tvg::Point pts[10]; + pts[0] = {199, 34}; //MoveTo + pts[1] = {253, 143}; //LineTo + pts[2] = {374, 160}; //LineTo + pts[3] = {287, 244}; //LineTo + pts[4] = {307, 365}; //LineTo + pts[5] = {199, 309}; //LineTo + pts[6] = {97, 365}; //LineTo + pts[7] = {112, 245}; //LineTo + pts[8] = {26, 161}; //LineTo + pts[9] = {146, 143}; //LineTo + + auto shape1 = tvg::Shape::gen(); + shape1->appendPath(cmds, 11, pts, 10); //copy path data + shape1->fill(0, 255, 0, 255); + if (canvas->push(move(shape1)) != tvg::Result::Success) return; + + /* Circle */ + auto cx = 550.0f; + auto cy = 550.0f; + auto radius = 125.0f; + auto halfRadius = radius * 0.552284f; + + //Prepare Path Commands + tvg::PathCommand cmds2[6]; + cmds2[0] = tvg::PathCommand::MoveTo; + cmds2[1] = tvg::PathCommand::CubicTo; + cmds2[2] = tvg::PathCommand::CubicTo; + cmds2[3] = tvg::PathCommand::CubicTo; + cmds2[4] = tvg::PathCommand::CubicTo; + cmds2[5] = tvg::PathCommand::Close; + + //Prepare Path Points + tvg::Point pts2[13]; + pts2[0] = {cx, cy - radius}; //MoveTo + //CubicTo 1 + pts2[1] = {cx + halfRadius, cy - radius}; //Ctrl1 + pts2[2] = {cx + radius, cy - halfRadius}; //Ctrl2 + pts2[3] = {cx + radius, cy}; //To + //CubicTo 2 + pts2[4] = {cx + radius, cy + halfRadius}; //Ctrl1 + pts2[5] = {cx + halfRadius, cy + radius}; //Ctrl2 + pts2[6] = {cx, cy+ radius}; //To + //CubicTo 3 + pts2[7] = {cx - halfRadius, cy + radius}; //Ctrl1 + pts2[8] = {cx - radius, cy + halfRadius}; //Ctrl2 + pts2[9] = {cx - radius, cy}; //To + //CubicTo 4 + pts2[10] = {cx - radius, cy - halfRadius}; //Ctrl1 + pts2[11] = {cx - halfRadius, cy - radius}; //Ctrl2 + pts2[12] = {cx, cy - radius}; //To + + auto shape2 = tvg::Shape::gen(); + shape2->appendPath(cmds2, 6, pts2, 13); //copy path data + shape2->fill(255, 255, 0, 255); + if (canvas->push(move(shape2)) != tvg::Result::Success) return; + +} + +/************************************************************************/ +/* Sw Engine Test Code */ +/************************************************************************/ + +static unique_ptr swCanvas; + +void tvgSwTest(uint32_t* buffer) +{ + //Create a Canvas + swCanvas = tvg::SwCanvas::gen(); + swCanvas->target(buffer, WIDTH, WIDTH, HEIGHT, tvg::SwCanvas::ARGB8888); + + /* Push the shape into the Canvas drawing list + When this shape is into the canvas list, the shape could update & prepare + internal data asynchronously for coming rendering. + Canvas keeps this shape node unless user call canvas->clear() */ + tvgDrawCmds(swCanvas.get()); +} + +void drawSwView(void* data, Eo* obj) +{ + if (swCanvas->draw() == tvg::Result::Success) { + swCanvas->sync(); + } +} + + +/************************************************************************/ +/* GL Engine Test Code */ +/************************************************************************/ + +static unique_ptr glCanvas; + +void initGLview(Evas_Object *obj) +{ + static constexpr auto BPP = 4; + + //Create a Canvas + glCanvas = tvg::GlCanvas::gen(); + glCanvas->target(nullptr, WIDTH * BPP, WIDTH, HEIGHT); + + /* Push the shape into the Canvas drawing list + When this shape is into the canvas list, the shape could update & prepare + internal data asynchronously for coming rendering. + Canvas keeps this shape node unless user call canvas->clear() */ + tvgDrawCmds(glCanvas.get()); +} + +void drawGLview(Evas_Object *obj) +{ + auto gl = elm_glview_gl_api_get(obj); + gl->glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + gl->glClear(GL_COLOR_BUFFER_BIT); + + if (glCanvas->draw() == tvg::Result::Success) { + glCanvas->sync(); + } +} + + +/************************************************************************/ +/* Main Code */ +/************************************************************************/ + +int main(int argc, char **argv) +{ + tvg::CanvasEngine tvgEngine = tvg::CanvasEngine::Sw; + + if (argc > 1) { + if (!strcmp(argv[1], "gl")) tvgEngine = tvg::CanvasEngine::Gl; + } + + //Initialize ThorVG Engine + if (tvgEngine == tvg::CanvasEngine::Sw) { + cout << "tvg engine: software" << endl; + } else { + cout << "tvg engine: opengl" << endl; + } + + //Threads Count + auto threads = std::thread::hardware_concurrency(); + + //Initialize ThorVG Engine + if (tvg::Initializer::init(tvgEngine, threads) == tvg::Result::Success) { + + elm_init(argc, argv); + + if (tvgEngine == tvg::CanvasEngine::Sw) { + createSwView(); + } else { + createGlView(); + } + + elm_run(); + elm_shutdown(); + + //Terminate ThorVG Engine + tvg::Initializer::term(tvgEngine); + + } else { + cout << "engine is not supported" << endl; + } + return 0; +} diff --git a/test/testRadialGradient.cpp b/test/testRadialGradient.cpp new file mode 100644 index 00000000..a64e47b5 --- /dev/null +++ b/test/testRadialGradient.cpp @@ -0,0 +1,176 @@ +#include "testCommon.h" + +/************************************************************************/ +/* Drawing Commands */ +/************************************************************************/ + +void tvgDrawCmds(tvg::Canvas* canvas) +{ + if (!canvas) return; + + canvas->reserve(3); //reserve 3 shape nodes (optional) + + //Prepare Round Rectangle + auto shape1 = tvg::Shape::gen(); + shape1->appendRect(0, 0, 400, 400, 0, 0); //x, y, w, h, rx, ry + + //RadialGradient + auto fill = tvg::RadialGradient::gen(); + fill->radial(200, 200, 200); + + //Gradient Color Stops + tvg::Fill::ColorStop colorStops[2]; + colorStops[0] = {0, 255, 255, 255, 255}; + colorStops[1] = {1, 0, 0, 0, 255}; + + fill->colorStops(colorStops, 2); + + shape1->fill(move(fill)); + if (canvas->push(move(shape1)) != tvg::Result::Success) return; + + //Prepare Circle + auto shape2 = tvg::Shape::gen(); + shape2->appendCircle(400, 400, 200, 200); //cx, cy, radiusW, radiusH + + //RadialGradient + auto fill2 = tvg::RadialGradient::gen(); + fill2->radial(400, 400, 200); + + //Gradient Color Stops + tvg::Fill::ColorStop colorStops2[3]; + colorStops2[0] = {0, 255, 0, 0, 255}; + colorStops2[1] = {0.5, 255, 255, 0, 255}; + colorStops2[2] = {1, 255, 255, 255, 255}; + + fill2->colorStops(colorStops2, 3); + + shape2->fill(move(fill2)); + if (canvas->push(move(shape2)) != tvg::Result::Success) return; + + + //Prepare Ellipse + auto shape3 = tvg::Shape::gen(); + shape3->appendCircle(600, 600, 150, 100); //cx, cy, radiusW, radiusH + + //RadialGradient + auto fill3 = tvg::RadialGradient::gen(); + fill3->radial(600, 600, 150); + + //Gradient Color Stops + tvg::Fill::ColorStop colorStops3[4]; + colorStops3[0] = {0, 0, 127, 0, 127}; + colorStops3[1] = {0.25, 0, 170, 170, 170}; + colorStops3[2] = {0.5, 200, 0, 200, 200}; + colorStops3[3] = {1, 255, 255, 255, 255}; + + fill3->colorStops(colorStops3, 4); + + shape3->fill(move(fill3)); + if (canvas->push(move(shape3)) != tvg::Result::Success) return; +} + + +/************************************************************************/ +/* Sw Engine Test Code */ +/************************************************************************/ + +static unique_ptr swCanvas; + +void tvgSwTest(uint32_t* buffer) +{ + //Create a Canvas + swCanvas = tvg::SwCanvas::gen(); + swCanvas->target(buffer, WIDTH, WIDTH, HEIGHT, tvg::SwCanvas::ARGB8888); + + /* Push the shape into the Canvas drawing list + When this shape is into the canvas list, the shape could update & prepare + internal data asynchronously for coming rendering. + Canvas keeps this shape node unless user call canvas->clear() */ + tvgDrawCmds(swCanvas.get()); +} + +void drawSwView(void* data, Eo* obj) +{ + if (swCanvas->draw() == tvg::Result::Success) { + swCanvas->sync(); + } +} + + +/************************************************************************/ +/* GL Engine Test Code */ +/************************************************************************/ + +static unique_ptr glCanvas; + +void initGLview(Evas_Object *obj) +{ + static constexpr auto BPP = 4; + + //Create a Canvas + glCanvas = tvg::GlCanvas::gen(); + glCanvas->target(nullptr, WIDTH * BPP, WIDTH, HEIGHT); + + /* Push the shape into the Canvas drawing list + When this shape is into the canvas list, the shape could update & prepare + internal data asynchronously for coming rendering. + Canvas keeps this shape node unless user call canvas->clear() */ + tvgDrawCmds(glCanvas.get()); +} + +void drawGLview(Evas_Object *obj) +{ + auto gl = elm_glview_gl_api_get(obj); + gl->glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + gl->glClear(GL_COLOR_BUFFER_BIT); + + if (glCanvas->draw() == tvg::Result::Success) { + glCanvas->sync(); + } +} + + +/************************************************************************/ +/* Main Code */ +/************************************************************************/ + +int main(int argc, char **argv) +{ + tvg::CanvasEngine tvgEngine = tvg::CanvasEngine::Sw; + + if (argc > 1) { + if (!strcmp(argv[1], "gl")) tvgEngine = tvg::CanvasEngine::Gl; + } + + //Initialize ThorVG Engine + if (tvgEngine == tvg::CanvasEngine::Sw) { + cout << "tvg engine: software" << endl; + } else { + cout << "tvg engine: opengl" << endl; + } + + //Threads Count + auto threads = std::thread::hardware_concurrency(); + + //Initialize ThorVG Engine + if (tvg::Initializer::init(tvgEngine, threads) == tvg::Result::Success) { + + elm_init(argc, argv); + + if (tvgEngine == tvg::CanvasEngine::Sw) { + createSwView(); + } else { + createGlView(); + } + + elm_run(); + elm_shutdown(); + + //Terminate ThorVG Engine + tvg::Initializer::term(tvgEngine); + + } else { + cout << "engine is not supported" << endl; + } + return 0; +} diff --git a/test/testScene.cpp b/test/testScene.cpp new file mode 100644 index 00000000..8c464282 --- /dev/null +++ b/test/testScene.cpp @@ -0,0 +1,184 @@ +#include "testCommon.h" + +/************************************************************************/ +/* Drawing Commands */ +/************************************************************************/ + +void tvgDrawCmds(tvg::Canvas* canvas) +{ + if (!canvas) return; + + //Create a Scene + auto scene = tvg::Scene::gen(); + scene->reserve(3); //reserve 3 shape nodes (optional) + + //Prepare Round Rectangle + auto shape1 = tvg::Shape::gen(); + shape1->appendRect(0, 0, 400, 400, 50, 50); //x, y, w, h, rx, ry + shape1->fill(0, 255, 0, 255); //r, g, b, a + scene->push(move(shape1)); + + //Prepare Circle + auto shape2 = tvg::Shape::gen(); + shape2->appendCircle(400, 400, 200, 200); //cx, cy, radiusW, radiusH + shape2->fill(255, 255, 0, 255); //r, g, b, a + scene->push(move(shape2)); + + //Prepare Ellipse + auto shape3 = tvg::Shape::gen(); + shape3->appendCircle(600, 600, 150, 100); //cx, cy, radiusW, radiusH + shape3->fill(0, 255, 255, 255); //r, g, b, a + scene->push(move(shape3)); + + //Create another Scene + auto scene2 = tvg::Scene::gen(); + scene2->reserve(2); //reserve 2 shape nodes (optional) + + //Star + auto shape4 = tvg::Shape::gen(); + + //Appends Paths + shape4->moveTo(199, 34); + shape4->lineTo(253, 143); + shape4->lineTo(374, 160); + shape4->lineTo(287, 244); + shape4->lineTo(307, 365); + shape4->lineTo(199, 309); + shape4->lineTo(97, 365); + shape4->lineTo(112, 245); + shape4->lineTo(26, 161); + shape4->lineTo(146, 143); + shape4->close(); + shape4->fill(0, 0, 255, 255); + scene2->push(move(shape4)); + + //Circle + auto shape5 = tvg::Shape::gen(); + + auto cx = 550.0f; + auto cy = 550.0f; + auto radius = 125.0f; + auto halfRadius = radius * 0.552284f; + + //Append Paths + shape5->moveTo(cx, cy - radius); + shape5->cubicTo(cx + halfRadius, cy - radius, cx + radius, cy - halfRadius, cx + radius, cy); + shape5->cubicTo(cx + radius, cy + halfRadius, cx + halfRadius, cy + radius, cx, cy+ radius); + shape5->cubicTo(cx - halfRadius, cy + radius, cx - radius, cy + halfRadius, cx - radius, cy); + shape5->cubicTo(cx - radius, cy - halfRadius, cx - halfRadius, cy - radius, cx, cy - radius); + shape5->fill(255, 0, 0, 255); + scene2->push(move(shape5)); + + //Push scene2 onto the scene + scene->push(move(scene2)); + + //Draw the Scene onto the Canvas + canvas->push(move(scene)); +} + + +/************************************************************************/ +/* Sw Engine Test Code */ +/************************************************************************/ + +static unique_ptr swCanvas; + +void tvgSwTest(uint32_t* buffer) +{ + //Create a Canvas + swCanvas = tvg::SwCanvas::gen(); + swCanvas->target(buffer, WIDTH, WIDTH, HEIGHT, tvg::SwCanvas::ARGB8888); + + /* Push the shape into the Canvas drawing list + When this shape is into the canvas list, the shape could update & prepare + internal data asynchronously for coming rendering. + Canvas keeps this shape node unless user call canvas->clear() */ + tvgDrawCmds(swCanvas.get()); +} + +void drawSwView(void* data, Eo* obj) +{ + if (swCanvas->draw() == tvg::Result::Success) { + swCanvas->sync(); + } +} + + +/************************************************************************/ +/* GL Engine Test Code */ +/************************************************************************/ + +static unique_ptr glCanvas; + +void initGLview(Evas_Object *obj) +{ + static constexpr auto BPP = 4; + + //Create a Canvas + glCanvas = tvg::GlCanvas::gen(); + glCanvas->target(nullptr, WIDTH * BPP, WIDTH, HEIGHT); + + /* Push the shape into the Canvas drawing list + When this shape is into the canvas list, the shape could update & prepare + internal data asynchronously for coming rendering. + Canvas keeps this shape node unless user call canvas->clear() */ + tvgDrawCmds(glCanvas.get()); +} + +void drawGLview(Evas_Object *obj) +{ + auto gl = elm_glview_gl_api_get(obj); + gl->glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + gl->glClear(GL_COLOR_BUFFER_BIT); + + if (glCanvas->draw() == tvg::Result::Success) { + glCanvas->sync(); + } +} + + +/************************************************************************/ +/* Main Code */ +/************************************************************************/ + +int main(int argc, char **argv) +{ + tvg::CanvasEngine tvgEngine = tvg::CanvasEngine::Sw; + + if (argc > 1) { + if (!strcmp(argv[1], "gl")) tvgEngine = tvg::CanvasEngine::Gl; + } + + //Initialize ThorVG Engine + if (tvgEngine == tvg::CanvasEngine::Sw) { + cout << "tvg engine: software" << endl; + } else { + cout << "tvg engine: opengl" << endl; + } + + //Threads Count + auto threads = std::thread::hardware_concurrency(); + + //Initialize ThorVG Engine + if (tvg::Initializer::init(tvgEngine, threads) == tvg::Result::Success) { + + + elm_init(argc, argv); + + if (tvgEngine == tvg::CanvasEngine::Sw) { + createSwView(); + } else { + createGlView(); + } + + elm_run(); + elm_shutdown(); + + //Terminate ThorVG Engine + tvg::Initializer::term(tvgEngine); + + } else { + cout << "engine is not supported" << endl; + } + return 0; +} diff --git a/test/testSceneTransform.cpp b/test/testSceneTransform.cpp new file mode 100644 index 00000000..ef6c1578 --- /dev/null +++ b/test/testSceneTransform.cpp @@ -0,0 +1,236 @@ +#include "testCommon.h" + +/************************************************************************/ +/* Drawing Commands */ +/************************************************************************/ +tvg::Scene* pScene1 = nullptr; +tvg::Scene* pScene2 = nullptr; + +void tvgDrawCmds(tvg::Canvas* canvas) +{ + if (!canvas) return; + + //Create a Scene1 + auto scene = tvg::Scene::gen(); + pScene1 = scene.get(); + scene->reserve(3); //reserve 3 shape nodes (optional) + + //Prepare Round Rectangle (Scene1) + auto shape1 = tvg::Shape::gen(); + shape1->appendRect(-235, -250, 400, 400, 50, 50); //x, y, w, h, rx, ry + shape1->fill(0, 255, 0, 255); //r, g, b, a + shape1->stroke(5); //width + shape1->stroke(255, 255, 255, 255); //r, g, b, a + scene->push(move(shape1)); + + //Prepare Circle (Scene1) + auto shape2 = tvg::Shape::gen(); + shape2->appendCircle(-165, -150, 200, 200); //cx, cy, radiusW, radiusH + shape2->fill(255, 255, 0, 255); //r, g, b, a + scene->push(move(shape2)); + + //Prepare Ellipse (Scene1) + auto shape3 = tvg::Shape::gen(); + shape3->appendCircle(265, 250, 150, 100); //cx, cy, radiusW, radiusH + shape3->fill(0, 255, 255, 255); //r, g, b, a + scene->push(move(shape3)); + + scene->translate(350, 350); + scene->scale(0.5); + + //Create Scene2 + auto scene2 = tvg::Scene::gen(); + pScene2 = scene2.get(); + scene2->reserve(2); //reserve 2 shape nodes (optional) + + //Star (Scene2) + auto shape4 = tvg::Shape::gen(); + + //Appends Paths + shape4->moveTo(0, -114.5); + shape4->lineTo(54, -5.5); + shape4->lineTo(175, 11.5); + shape4->lineTo(88, 95.5); + shape4->lineTo(108, 216.5); + shape4->lineTo(0, 160.5); + shape4->lineTo(-102, 216.5); + shape4->lineTo(-87, 96.5); + shape4->lineTo(-173, 12.5); + shape4->lineTo(-53, -5.5); + shape4->close(); + shape4->fill(0, 0, 255, 127); + shape4->stroke(3); //width + shape4->stroke(0, 0, 255, 255); //r, g, b, a + scene2->push(move(shape4)); + + //Circle (Scene2) + auto shape5 = tvg::Shape::gen(); + + auto cx = -150.0f; + auto cy = -150.0f; + auto radius = 100.0f; + auto halfRadius = radius * 0.552284f; + + //Append Paths + shape5->moveTo(cx, cy - radius); + shape5->cubicTo(cx + halfRadius, cy - radius, cx + radius, cy - halfRadius, cx + radius, cy); + shape5->cubicTo(cx + radius, cy + halfRadius, cx + halfRadius, cy + radius, cx, cy+ radius); + shape5->cubicTo(cx - halfRadius, cy + radius, cx - radius, cy + halfRadius, cx - radius, cy); + shape5->cubicTo(cx - radius, cy - halfRadius, cx - halfRadius, cy - radius, cx, cy - radius); + shape5->close(); + shape5->fill(255, 0, 0, 127); + scene2->push(move(shape5)); + + scene2->translate(500, 350); + + //Push scene2 onto the scene + scene->push(move(scene2)); + + //Draw the Scene onto the Canvas + canvas->push(move(scene)); +} + +void tvgUpdateCmds(tvg::Canvas* canvas, float progress) +{ + if (!canvas) return; + + /* Update scene directly. + You can update only necessary properties of this scene, + while retaining other properties. */ + + pScene1->rotate(360 * progress); + pScene2->rotate(360 * progress); + + //Update shape for drawing (this may work asynchronously) + canvas->update(pScene1); +} + + +/************************************************************************/ +/* Sw Engine Test Code */ +/************************************************************************/ + +static unique_ptr swCanvas; + +void tvgSwTest(uint32_t* buffer) +{ + //Create a Canvas + swCanvas = tvg::SwCanvas::gen(); + swCanvas->target(buffer, WIDTH, WIDTH, HEIGHT, tvg::SwCanvas::ARGB8888); + + /* Push the shape into the Canvas drawing list + When this shape is into the canvas list, the shape could update & prepare + internal data asynchronously for coming rendering. + Canvas keeps this shape node unless user call canvas->clear() */ + tvgDrawCmds(swCanvas.get()); +} + +void transitSwCb(Elm_Transit_Effect *effect, Elm_Transit* transit, double progress) +{ + tvgUpdateCmds(swCanvas.get(), progress); + + //Update Efl Canvas + Eo* img = (Eo*) effect; + evas_object_image_data_update_add(img, 0, 0, WIDTH, HEIGHT); + evas_object_image_pixels_dirty_set(img, EINA_TRUE); +} + +void drawSwView(void* data, Eo* obj) +{ + if (swCanvas->draw() == tvg::Result::Success) { + swCanvas->sync(); + } +} + + +/************************************************************************/ +/* GL Engine Test Code */ +/************************************************************************/ + +static unique_ptr glCanvas; + +void initGLview(Evas_Object *obj) +{ + static constexpr auto BPP = 4; + + //Create a Canvas + glCanvas = tvg::GlCanvas::gen(); + glCanvas->target(nullptr, WIDTH * BPP, WIDTH, HEIGHT); + + /* Push the shape into the Canvas drawing list + When this shape is into the canvas list, the shape could update & prepare + internal data asynchronously for coming rendering. + Canvas keeps this shape node unless user call canvas->clear() */ + tvgDrawCmds(glCanvas.get()); +} + +void drawGLview(Evas_Object *obj) +{ + auto gl = elm_glview_gl_api_get(obj); + gl->glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + gl->glClear(GL_COLOR_BUFFER_BIT); + + if (glCanvas->draw() == tvg::Result::Success) { + glCanvas->sync(); + } +} + +void transitGlCb(Elm_Transit_Effect *effect, Elm_Transit* transit, double progress) +{ + tvgUpdateCmds(glCanvas.get(), progress); +} + + +/************************************************************************/ +/* Main Code */ +/************************************************************************/ + +int main(int argc, char **argv) +{ + tvg::CanvasEngine tvgEngine = tvg::CanvasEngine::Sw; + + if (argc > 1) { + if (!strcmp(argv[1], "gl")) tvgEngine = tvg::CanvasEngine::Gl; + } + + //Initialize ThorVG Engine + if (tvgEngine == tvg::CanvasEngine::Sw) { + cout << "tvg engine: software" << endl; + } else { + cout << "tvg engine: opengl" << endl; + } + + //Threads Count + auto threads = std::thread::hardware_concurrency(); + + //Initialize ThorVG Engine + if (tvg::Initializer::init(tvgEngine, threads) == tvg::Result::Success) { + + elm_init(argc, argv); + + Elm_Transit *transit = elm_transit_add(); + + if (tvgEngine == tvg::CanvasEngine::Sw) { + auto view = createSwView(); + elm_transit_effect_add(transit, transitSwCb, view, nullptr); + } else { + auto view = createGlView(); + elm_transit_effect_add(transit, transitGlCb, view, nullptr); + } + + elm_transit_duration_set(transit, 2); + elm_transit_repeat_times_set(transit, -1); + elm_transit_auto_reverse_set(transit, EINA_TRUE); + elm_transit_go(transit); + + elm_run(); + elm_shutdown(); + + //Terminate ThorVG Engine + tvg::Initializer::term(tvgEngine); + + } else { + cout << "engine is not supported" << endl; + } + return 0; +} diff --git a/test/testShape.cpp b/test/testShape.cpp new file mode 100644 index 00000000..56acfd5c --- /dev/null +++ b/test/testShape.cpp @@ -0,0 +1,126 @@ +#include "testCommon.h" + +/************************************************************************/ +/* Drawing Commands */ +/************************************************************************/ + +void tvgDrawCmds(tvg::Canvas* canvas) +{ + if (!canvas) return; + + //Prepare a Shape (Rectangle + Rectangle + Circle + Circle) + auto shape1 = tvg::Shape::gen(); + shape1->appendRect(0, 0, 200, 200, 0, 0); //x, y, w, h, rx, ry + shape1->appendRect(100, 100, 300, 300, 100, 100); //x, y, w, h, rx, ry + shape1->appendCircle(400, 400, 100, 100); //cx, cy, radiusW, radiusH + shape1->appendCircle(400, 500, 170, 100); //cx, cy, radiusW, radiusH + shape1->fill(255, 255, 0, 255); //r, g, b, a + + canvas->push(move(shape1)); +} + + +/************************************************************************/ +/* Sw Engine Test Code */ +/************************************************************************/ + +static unique_ptr swCanvas; + +void tvgSwTest(uint32_t* buffer) +{ + //Create a Canvas + swCanvas = tvg::SwCanvas::gen(); + swCanvas->target(buffer, WIDTH, WIDTH, HEIGHT, tvg::SwCanvas::ARGB8888); + + /* Push the shape into the Canvas drawing list + When this shape is into the canvas list, the shape could update & prepare + internal data asynchronously for coming rendering. + Canvas keeps this shape node unless user call canvas->clear() */ + tvgDrawCmds(swCanvas.get()); +} + +void drawSwView(void* data, Eo* obj) +{ + if (swCanvas->draw() == tvg::Result::Success) { + swCanvas->sync(); + } +} + + +/************************************************************************/ +/* GL Engine Test Code */ +/************************************************************************/ + +static unique_ptr glCanvas; + +void initGLview(Evas_Object *obj) +{ + static constexpr auto BPP = 4; + + //Create a Canvas + glCanvas = tvg::GlCanvas::gen(); + glCanvas->target(nullptr, WIDTH * BPP, WIDTH, HEIGHT); + + /* Push the shape into the Canvas drawing list + When this shape is into the canvas list, the shape could update & prepare + internal data asynchronously for coming rendering. + Canvas keeps this shape node unless user call canvas->clear() */ + tvgDrawCmds(glCanvas.get()); +} + +void drawGLview(Evas_Object *obj) +{ + auto gl = elm_glview_gl_api_get(obj); + gl->glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + gl->glClear(GL_COLOR_BUFFER_BIT); + + if (glCanvas->draw() == tvg::Result::Success) { + glCanvas->sync(); + } +} + + +/************************************************************************/ +/* Main Code */ +/************************************************************************/ + +int main(int argc, char **argv) +{ + tvg::CanvasEngine tvgEngine = tvg::CanvasEngine::Sw; + + if (argc > 1) { + if (!strcmp(argv[1], "gl")) tvgEngine = tvg::CanvasEngine::Gl; + } + + //Initialize ThorVG Engine + if (tvgEngine == tvg::CanvasEngine::Sw) { + cout << "tvg engine: software" << endl; + } else { + cout << "tvg engine: opengl" << endl; + } + + //Threads Count + auto threads = std::thread::hardware_concurrency(); + + //Initialize ThorVG Engine + if (tvg::Initializer::init(tvgEngine, threads) == tvg::Result::Success) { + + elm_init(argc, argv); + + if (tvgEngine == tvg::CanvasEngine::Sw) { + createSwView(); + } else { + createGlView(); + } + + elm_run(); + elm_shutdown(); + + //Terminate ThorVG Engine + tvg::Initializer::term(tvgEngine); + + } else { + cout << "engine is not supported" << endl; + } + return 0; +} diff --git a/test/testStroke.cpp b/test/testStroke.cpp new file mode 100644 index 00000000..5ac22659 --- /dev/null +++ b/test/testStroke.cpp @@ -0,0 +1,173 @@ +#include "testCommon.h" + +/************************************************************************/ +/* Drawing Commands */ +/************************************************************************/ + +void tvgDrawCmds(tvg::Canvas* canvas) +{ + if (!canvas) return; + + //Shape 1 + auto shape1 = tvg::Shape::gen(); + shape1->appendRect(50, 50, 200, 200, 0, 0); + shape1->fill(50, 50, 50, 255); + shape1->stroke(255, 255, 255, 255); //color: r, g, b, a + shape1->stroke(tvg::StrokeJoin::Bevel); //default is Bevel + shape1->stroke(10); //width: 10px + + if (canvas->push(move(shape1)) != tvg::Result::Success) return; + + //Shape 2 + auto shape2 = tvg::Shape::gen(); + shape2->appendRect(300, 50, 200, 200, 0, 0); + shape2->fill(50, 50, 50, 255); + shape2->stroke(255, 255, 255, 255); + shape2->stroke(tvg::StrokeJoin::Round); + shape2->stroke(10); + + if (canvas->push(move(shape2)) != tvg::Result::Success) return; + + //Shape 3 + auto shape3 = tvg::Shape::gen(); + shape3->appendRect(550, 50, 200, 200, 0, 0); + shape3->fill(50, 50, 50, 255); + shape3->stroke(255, 255, 255, 255); + shape3->stroke(tvg::StrokeJoin::Miter); + shape3->stroke(10); + + if (canvas->push(move(shape3)) != tvg::Result::Success) return; + + //Shape 4 + auto shape4 = tvg::Shape::gen(); + shape4->appendCircle(150, 450, 100, 100); + shape4->fill(50, 50, 50, 255); + shape4->stroke(255, 255, 255, 255); + shape4->stroke(1); + + if (canvas->push(move(shape4)) != tvg::Result::Success) return; + + //Shape 5 + auto shape5 = tvg::Shape::gen(); + shape5->appendCircle(400, 450, 100, 100); + shape5->fill(50, 50, 50, 255); + shape5->stroke(255, 255, 255, 255); + shape5->stroke(2); + + if (canvas->push(move(shape5)) != tvg::Result::Success) return; + + //Shape 6 + auto shape6 = tvg::Shape::gen(); + shape6->appendCircle(650, 450, 100, 100); + shape6->fill(50, 50, 50, 255); + shape6->stroke(255, 255, 255, 255); + shape6->stroke(4); + + if (canvas->push(move(shape6)) != tvg::Result::Success) return; +} + + +/************************************************************************/ +/* Sw Engine Test Code */ +/************************************************************************/ + +static unique_ptr swCanvas; + +void tvgSwTest(uint32_t* buffer) +{ + //Create a Canvas + swCanvas = tvg::SwCanvas::gen(); + swCanvas->target(buffer, WIDTH, WIDTH, HEIGHT, tvg::SwCanvas::ARGB8888); + + /* Push the shape into the Canvas drawing list + When this shape is into the canvas list, the shape could update & prepare + internal data asynchronously for coming rendering. + Canvas keeps this shape node unless user call canvas->clear() */ + tvgDrawCmds(swCanvas.get()); +} + +void drawSwView(void* data, Eo* obj) +{ + if (swCanvas->draw() == tvg::Result::Success) { + swCanvas->sync(); + } +} + + +/************************************************************************/ +/* GL Engine Test Code */ +/************************************************************************/ + +static unique_ptr glCanvas; + +void initGLview(Evas_Object *obj) +{ + static constexpr auto BPP = 4; + + //Create a Canvas + glCanvas = tvg::GlCanvas::gen(); + glCanvas->target(nullptr, WIDTH * BPP, WIDTH, HEIGHT); + + /* Push the shape into the Canvas drawing list + When this shape is into the canvas list, the shape could update & prepare + internal data asynchronously for coming rendering. + Canvas keeps this shape node unless user call canvas->clear() */ + tvgDrawCmds(glCanvas.get()); +} + +void drawGLview(Evas_Object *obj) +{ + auto gl = elm_glview_gl_api_get(obj); + gl->glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + gl->glClear(GL_COLOR_BUFFER_BIT); + + if (glCanvas->draw() == tvg::Result::Success) { + glCanvas->sync(); + } +} + + +/************************************************************************/ +/* Main Code */ +/************************************************************************/ + +int main(int argc, char **argv) +{ + tvg::CanvasEngine tvgEngine = tvg::CanvasEngine::Sw; + + if (argc > 1) { + if (!strcmp(argv[1], "gl")) tvgEngine = tvg::CanvasEngine::Gl; + } + + //Initialize ThorVG Engine + if (tvgEngine == tvg::CanvasEngine::Sw) { + cout << "tvg engine: software" << endl; + } else { + cout << "tvg engine: opengl" << endl; + } + + //Threads Count + auto threads = std::thread::hardware_concurrency(); + + //Initialize ThorVG Engine + if (tvg::Initializer::init(tvgEngine, threads) == tvg::Result::Success) { + + elm_init(argc, argv); + + if (tvgEngine == tvg::CanvasEngine::Sw) { + createSwView(); + } else { + createGlView(); + } + + elm_run(); + elm_shutdown(); + + //Terminate ThorVG Engine + tvg::Initializer::term(tvgEngine); + + } else { + cout << "engine is not supported" << endl; + } + return 0; +} diff --git a/test/testStrokeLine.cpp b/test/testStrokeLine.cpp new file mode 100644 index 00000000..8cc16d0f --- /dev/null +++ b/test/testStrokeLine.cpp @@ -0,0 +1,211 @@ +#include "testCommon.h" + +/************************************************************************/ +/* Drawing Commands */ +/************************************************************************/ + +void tvgDrawCmds(tvg::Canvas* canvas) +{ + if (!canvas) return; + + //Test for Stroke Width + for (int i = 0; i < 10; ++i) { + auto shape = tvg::Shape::gen(); + shape->moveTo(50, 50 + (25 * i)); + shape->lineTo(750, 50 + (25 * i)); + shape->stroke(255, 255, 255, 255); //color: r, g, b, a + shape->stroke(i + 1); //stroke width + shape->stroke(tvg::StrokeCap::Round); //default is Square + if (canvas->push(move(shape)) != tvg::Result::Success) return; + } + + //Test for StrokeJoin & StrokeCap + auto shape1 = tvg::Shape::gen(); + shape1->moveTo(20, 350); + shape1->lineTo(250, 350); + shape1->lineTo(220, 500); + shape1->lineTo(70, 470); + shape1->lineTo(70, 330); + shape1->stroke(255, 0, 0, 255); + shape1->stroke(10); + shape1->stroke(tvg::StrokeJoin::Round); + shape1->stroke(tvg::StrokeCap::Round); + if (canvas->push(move(shape1)) != tvg::Result::Success) return; + + auto shape2 = tvg::Shape::gen(); + shape2->moveTo(270, 350); + shape2->lineTo(500, 350); + shape2->lineTo(470, 500); + shape2->lineTo(320, 470); + shape2->lineTo(320, 330); + shape2->stroke(255, 255, 0, 255); + shape2->stroke(10); + shape2->stroke(tvg::StrokeJoin::Bevel); + shape2->stroke(tvg::StrokeCap::Square); + if (canvas->push(move(shape2)) != tvg::Result::Success) return; + + auto shape3 = tvg::Shape::gen(); + shape3->moveTo(520, 350); + shape3->lineTo(750, 350); + shape3->lineTo(720, 500); + shape3->lineTo(570, 470); + shape3->lineTo(570, 330); + shape3->stroke(0, 255, 0, 255); + shape3->stroke(10); + shape3->stroke(tvg::StrokeJoin::Miter); + shape3->stroke(tvg::StrokeCap::Butt); + if (canvas->push(move(shape3)) != tvg::Result::Success) return; + + //Test for Stroke Dash + auto shape4 = tvg::Shape::gen(); + shape4->moveTo(20, 600); + shape4->lineTo(250, 600); + shape4->lineTo(220, 750); + shape4->lineTo(70, 720); + shape4->lineTo(70, 580); + shape4->stroke(255, 0, 0, 255); + shape4->stroke(5); + shape4->stroke(tvg::StrokeJoin::Round); + shape4->stroke(tvg::StrokeCap::Round); + + float dashPattern1[2] = {10, 10}; + shape4->stroke(dashPattern1, 2); + if (canvas->push(move(shape4)) != tvg::Result::Success) return; + + auto shape5 = tvg::Shape::gen(); + shape5->moveTo(270, 600); + shape5->lineTo(500, 600); + shape5->lineTo(470, 750); + shape5->lineTo(320, 720); + shape5->lineTo(320, 580); + shape5->stroke(255, 255, 0, 255); + shape5->stroke(5); + shape5->stroke(tvg::StrokeJoin::Bevel); + shape5->stroke(tvg::StrokeCap::Butt); + + float dashPattern2[4] = {10, 10}; + shape5->stroke(dashPattern2, 4); + if (canvas->push(move(shape5)) != tvg::Result::Success) return; + + auto shape6 = tvg::Shape::gen(); + shape6->moveTo(520, 600); + shape6->lineTo(750, 600); + shape6->lineTo(720, 750); + shape6->lineTo(570, 720); + shape6->lineTo(570, 580); + shape6->stroke(255, 255, 255, 255); + shape6->stroke(5); + shape6->stroke(tvg::StrokeJoin::Miter); + shape6->stroke(tvg::StrokeCap::Square); + + float dashPattern3[2] = {10, 10}; + shape6->stroke(dashPattern3, 2); + if (canvas->push(move(shape6)) != tvg::Result::Success) return; +} + + +/************************************************************************/ +/* Sw Engine Test Code */ +/************************************************************************/ + +static unique_ptr swCanvas; + +void tvgSwTest(uint32_t* buffer) +{ + //Create a Canvas + swCanvas = tvg::SwCanvas::gen(); + swCanvas->target(buffer, WIDTH, WIDTH, HEIGHT, tvg::SwCanvas::ARGB8888); + + /* Push the shape into the Canvas drawing list + When this shape is into the canvas list, the shape could update & prepare + internal data asynchronously for coming rendering. + Canvas keeps this shape node unless user call canvas->clear() */ + tvgDrawCmds(swCanvas.get()); +} + +void drawSwView(void* data, Eo* obj) +{ + if (swCanvas->draw() == tvg::Result::Success) { + swCanvas->sync(); + } +} + + +/************************************************************************/ +/* GL Engine Test Code */ +/************************************************************************/ + +static unique_ptr glCanvas; + +void initGLview(Evas_Object *obj) +{ + static constexpr auto BPP = 4; + + //Create a Canvas + glCanvas = tvg::GlCanvas::gen(); + glCanvas->target(nullptr, WIDTH * BPP, WIDTH, HEIGHT); + + /* Push the shape into the Canvas drawing list + When this shape is into the canvas list, the shape could update & prepare + internal data asynchronously for coming rendering. + Canvas keeps this shape node unless user call canvas->clear() */ + tvgDrawCmds(glCanvas.get()); +} + +void drawGLview(Evas_Object *obj) +{ + auto gl = elm_glview_gl_api_get(obj); + gl->glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + gl->glClear(GL_COLOR_BUFFER_BIT); + + if (glCanvas->draw() == tvg::Result::Success) { + glCanvas->sync(); + } +} + + +/************************************************************************/ +/* Main Code */ +/************************************************************************/ + +int main(int argc, char **argv) +{ + tvg::CanvasEngine tvgEngine = tvg::CanvasEngine::Sw; + + if (argc > 1) { + if (!strcmp(argv[1], "gl")) tvgEngine = tvg::CanvasEngine::Gl; + } + + //Initialize ThorVG Engine + if (tvgEngine == tvg::CanvasEngine::Sw) { + cout << "tvg engine: software" << endl; + } else { + cout << "tvg engine: opengl" << endl; + } + + //Threads Count + auto threads = std::thread::hardware_concurrency(); + + //Initialize ThorVG Engine + if (tvg::Initializer::init(tvgEngine, threads) == tvg::Result::Success) { + + + elm_init(argc, argv); + + if (tvgEngine == tvg::CanvasEngine::Sw) { + createSwView(); + } else { + createGlView(); + } + + elm_run(); + elm_shutdown(); + + //Terminate ThorVG Engine + tvg::Initializer::term(tvgEngine); + + } else { + cout << "engine is not supported" << endl; + } + return 0; +} diff --git a/test/testSvg.cpp b/test/testSvg.cpp new file mode 100644 index 00000000..ad720271 --- /dev/null +++ b/test/testSvg.cpp @@ -0,0 +1,168 @@ +#include "testCommon.h" + +/************************************************************************/ +/* Drawing Commands */ +/************************************************************************/ + +#define NUM_PER_LINE 4 +#define SIZE 200 + +static int count = 0; + +void svgDirCallback(const char* name, const char* path, void* data) +{ + tvg::Canvas* canvas = static_cast(data); + + auto picture = tvg::Picture::gen(); + + char buf[PATH_MAX]; + sprintf(buf,"%s/%s", path, name); + + if (picture->load(buf) != tvg::Result::Success) return; + + float x, y, w, h; + picture->viewbox(&x, &y, &w, &h); + + float rate = (SIZE/(w > h ? w : h)); + picture->scale(rate); + + x *= rate; + y *= rate; + w *= rate; + h *= rate; + + //Center Align ? + if (w > h) { + y -= (SIZE - h) * 0.5f; + } else { + x -= (SIZE - w) * 0.5f; + } + + picture->translate((count % NUM_PER_LINE) * SIZE - x, SIZE * (count / NUM_PER_LINE) - y); + + canvas->push(move(picture)); + + cout << "SVG: " << buf << endl; + + count++; +} + +void tvgDrawCmds(tvg::Canvas* canvas) +{ + if (!canvas) return; + + //Background + auto shape = tvg::Shape::gen(); + shape->appendRect(0, 0, WIDTH, HEIGHT, 0, 0); //x, y, w, h, rx, ry + shape->fill(255, 255, 255, 255); //r, g, b, a + + if (canvas->push(move(shape)) != tvg::Result::Success) return; + + eina_file_dir_list("./svgs", EINA_TRUE, svgDirCallback, canvas); +} + + +/************************************************************************/ +/* Sw Engine Test Code */ +/************************************************************************/ + +static unique_ptr swCanvas; + +void tvgSwTest(uint32_t* buffer) +{ + //Create a Canvas + swCanvas = tvg::SwCanvas::gen(); + swCanvas->target(buffer, WIDTH, WIDTH, HEIGHT, tvg::SwCanvas::ARGB8888); + + /* Push the shape into the Canvas drawing list + When this shape is into the canvas list, the shape could update & prepare + internal data asynchronously for coming rendering. + Canvas keeps this shape node unless user call canvas->clear() */ + tvgDrawCmds(swCanvas.get()); +} + +void drawSwView(void* data, Eo* obj) +{ + if (swCanvas->draw() == tvg::Result::Success) { + swCanvas->sync(); + } +} + + +/************************************************************************/ +/* GL Engine Test Code */ +/************************************************************************/ + +static unique_ptr glCanvas; + +void initGLview(Evas_Object *obj) +{ + static constexpr auto BPP = 4; + + //Create a Canvas + glCanvas = tvg::GlCanvas::gen(); + glCanvas->target(nullptr, WIDTH * BPP, WIDTH, HEIGHT); + + /* Push the shape into the Canvas drawing list + When this shape is into the canvas list, the shape could update & prepare + internal data asynchronously for coming rendering. + Canvas keeps this shape node unless user call canvas->clear() */ + tvgDrawCmds(glCanvas.get()); +} + +void drawGLview(Evas_Object *obj) +{ + auto gl = elm_glview_gl_api_get(obj); + gl->glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + gl->glClear(GL_COLOR_BUFFER_BIT); + + if (glCanvas->draw() == tvg::Result::Success) { + glCanvas->sync(); + } +} + + +/************************************************************************/ +/* Main Code */ +/************************************************************************/ + +int main(int argc, char **argv) +{ + tvg::CanvasEngine tvgEngine = tvg::CanvasEngine::Sw; + + if (argc > 1) { + if (!strcmp(argv[1], "gl")) tvgEngine = tvg::CanvasEngine::Gl; + } + + //Initialize ThorVG Engine + if (tvgEngine == tvg::CanvasEngine::Sw) { + cout << "tvg engine: software" << endl; + } else { + cout << "tvg engine: opengl" << endl; + } + + //Threads Count + auto threads = std::thread::hardware_concurrency(); + + //Initialize ThorVG Engine + if (tvg::Initializer::init(tvgEngine, threads) == tvg::Result::Success) { + + elm_init(argc, argv); + + if (tvgEngine == tvg::CanvasEngine::Sw) { + createSwView(); + } else { + createGlView(); + } + + elm_run(); + elm_shutdown(); + + //Terminate ThorVG Engine + tvg::Initializer::term(tvg::CanvasEngine::Sw); + + } else { + cout << "engine is not supported" << endl; + } + return 0; +} diff --git a/test/testSvg2.cpp b/test/testSvg2.cpp new file mode 100644 index 00000000..c859b042 --- /dev/null +++ b/test/testSvg2.cpp @@ -0,0 +1,151 @@ +#include "testCommon.h" + +/************************************************************************/ +/* Drawing Commands */ +/************************************************************************/ + +static const char* svg = ""; + + +void tvgDrawCmds(tvg::Canvas* canvas) +{ + if (!canvas) return; + + //Background + auto shape = tvg::Shape::gen(); + shape->appendRect(0, 0, WIDTH, HEIGHT, 0, 0); //x, y, w, h, rx, ry + shape->fill(255, 255, 255, 255); //r, g, b, a + + if (canvas->push(move(shape)) != tvg::Result::Success) return; + + auto picture = tvg::Picture::gen(); + if (picture->load(svg, strlen(svg)) != tvg::Result::Success) return; + + float x, y, w, h; + picture->viewbox(&x, &y, &w, &h); + + float rate = (WIDTH/(w > h ? w : h)); + picture->scale(rate); + + x *= rate; + y *= rate; + w *= rate; + h *= rate; + + //Center Align ? + if (w > h) { + y -= (WIDTH - h) * 0.5f; + } else { + x -= (WIDTH - w) * 0.5f; + } + + picture->translate(-x, -y); + + canvas->push(move(picture)); +} + + +/************************************************************************/ +/* Sw Engine Test Code */ +/************************************************************************/ + +static unique_ptr swCanvas; + +void tvgSwTest(uint32_t* buffer) +{ + //Create a Canvas + swCanvas = tvg::SwCanvas::gen(); + swCanvas->target(buffer, WIDTH, WIDTH, HEIGHT, tvg::SwCanvas::ARGB8888); + + /* Push the shape into the Canvas drawing list + When this shape is into the canvas list, the shape could update & prepare + internal data asynchronously for coming rendering. + Canvas keeps this shape node unless user call canvas->clear() */ + tvgDrawCmds(swCanvas.get()); +} + +void drawSwView(void* data, Eo* obj) +{ + if (swCanvas->draw() == tvg::Result::Success) { + swCanvas->sync(); + } +} + + +/************************************************************************/ +/* GL Engine Test Code */ +/************************************************************************/ + +static unique_ptr glCanvas; + +void initGLview(Evas_Object *obj) +{ + static constexpr auto BPP = 4; + + //Create a Canvas + glCanvas = tvg::GlCanvas::gen(); + glCanvas->target(nullptr, WIDTH * BPP, WIDTH, HEIGHT); + + /* Push the shape into the Canvas drawing list + When this shape is into the canvas list, the shape could update & prepare + internal data asynchronously for coming rendering. + Canvas keeps this shape node unless user call canvas->clear() */ + tvgDrawCmds(glCanvas.get()); +} + +void drawGLview(Evas_Object *obj) +{ + auto gl = elm_glview_gl_api_get(obj); + gl->glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + gl->glClear(GL_COLOR_BUFFER_BIT); + + if (glCanvas->draw() == tvg::Result::Success) { + glCanvas->sync(); + } +} + + +/************************************************************************/ +/* Main Code */ +/************************************************************************/ + +int main(int argc, char **argv) +{ + tvg::CanvasEngine tvgEngine = tvg::CanvasEngine::Sw; + + if (argc > 1) { + if (!strcmp(argv[1], "gl")) tvgEngine = tvg::CanvasEngine::Gl; + } + + //Initialize ThorVG Engine + if (tvgEngine == tvg::CanvasEngine::Sw) { + cout << "tvg engine: software" << endl; + } else { + cout << "tvg engine: opengl" << endl; + } + + //Threads Count + auto threads = std::thread::hardware_concurrency(); + + //Initialize ThorVG Engine + if (tvg::Initializer::init(tvgEngine, threads) == tvg::Result::Success) { + + elm_init(argc, argv); + + if (tvgEngine == tvg::CanvasEngine::Sw) { + createSwView(); + } else { + createGlView(); + } + + elm_run(); + elm_shutdown(); + + //Terminate ThorVG Engine + tvg::Initializer::term(tvg::CanvasEngine::Sw); + + } else { + cout << "engine is not supported" << endl; + } + return 0; +} diff --git a/test/testTransform.cpp b/test/testTransform.cpp new file mode 100644 index 00000000..6cd9be67 --- /dev/null +++ b/test/testTransform.cpp @@ -0,0 +1,203 @@ +#include "testCommon.h" + +/************************************************************************/ +/* Drawing Commands */ +/************************************************************************/ +tvg::Shape* pShape = nullptr; +tvg::Shape* pShape2 = nullptr; +tvg::Shape* pShape3 = nullptr; + +void tvgDrawCmds(tvg::Canvas* canvas) +{ + if (!canvas) return; + + //Shape1 + auto shape = tvg::Shape::gen(); + + /* Acquire shape pointer to access it again. + instead, you should consider not to interrupt this pointer life-cycle. */ + pShape = shape.get(); + + shape->appendRect(-285, -300, 200, 200, 0, 0); + shape->appendRect(-185, -200, 300, 300, 100, 100); + shape->appendCircle(115, 100, 100, 100); + shape->appendCircle(115, 200, 170, 100); + shape->fill(255, 255, 255, 255); + shape->translate(385, 400); + if (canvas->push(move(shape)) != tvg::Result::Success) return; + + //Shape2 + auto shape2 = tvg::Shape::gen(); + pShape2 = shape2.get(); + shape2->appendRect(-50, -50, 100, 100, 0, 0); + shape2->fill(0, 255, 255, 255); + shape2->translate(400, 400); + if (canvas->push(move(shape2)) != tvg::Result::Success) return; + + //Shape3 + auto shape3 = tvg::Shape::gen(); + pShape3 = shape3.get(); + + /* Look, how shape3's origin is different with shape2 + The center of the shape is the anchor point for transformation. */ + shape3->appendRect(100, 100, 150, 50, 20, 20); + shape3->fill(255, 0, 255, 255); + shape3->translate(400, 400); + if (canvas->push(move(shape3)) != tvg::Result::Success) return; +} + +void tvgUpdateCmds(tvg::Canvas* canvas, float progress) +{ + if (!canvas) return; + + /* Update shape directly. + You can update only necessary properties of this shape, + while retaining other properties. */ + + //Update Shape1 + pShape->scale(1 - 0.75 * progress); + pShape->rotate(360 * progress); + + //Update shape for drawing (this may work asynchronously) + if (canvas->update(pShape) != tvg::Result::Success) return; + + //Update Shape2 + pShape2->rotate(360 * progress); + pShape2->translate(400 + progress * 300, 400); + if (canvas->update(pShape2) != tvg::Result::Success) return; + + //Update Shape3 + pShape3->rotate(-360 * progress); + pShape3->scale(0.5 + progress); + if (canvas->update(pShape3) != tvg::Result::Success) return; +} + + +/************************************************************************/ +/* Sw Engine Test Code */ +/************************************************************************/ + +static unique_ptr swCanvas; + +void tvgSwTest(uint32_t* buffer) +{ + //Create a Canvas + swCanvas = tvg::SwCanvas::gen(); + swCanvas->target(buffer, WIDTH, WIDTH, HEIGHT, tvg::SwCanvas::ARGB8888); + + /* Push the shape into the Canvas drawing list + When this shape is into the canvas list, the shape could update & prepare + internal data asynchronously for coming rendering. + Canvas keeps this shape node unless user call canvas->clear() */ + tvgDrawCmds(swCanvas.get()); +} + +void transitSwCb(Elm_Transit_Effect *effect, Elm_Transit* transit, double progress) +{ + tvgUpdateCmds(swCanvas.get(), progress); + + //Update Efl Canvas + Eo* img = (Eo*) effect; + evas_object_image_data_update_add(img, 0, 0, WIDTH, HEIGHT); + evas_object_image_pixels_dirty_set(img, EINA_TRUE); +} + +void drawSwView(void* data, Eo* obj) +{ + if (swCanvas->draw() == tvg::Result::Success) { + swCanvas->sync(); + } +} + + +/************************************************************************/ +/* GL Engine Test Code */ +/************************************************************************/ + +static unique_ptr glCanvas; + +void initGLview(Evas_Object *obj) +{ + static constexpr auto BPP = 4; + + //Create a Canvas + glCanvas = tvg::GlCanvas::gen(); + glCanvas->target(nullptr, WIDTH * BPP, WIDTH, HEIGHT); + + /* Push the shape into the Canvas drawing list + When this shape is into the canvas list, the shape could update & prepare + internal data asynchronously for coming rendering. + Canvas keeps this shape node unless user call canvas->clear() */ + tvgDrawCmds(glCanvas.get()); +} + +void drawGLview(Evas_Object *obj) +{ + auto gl = elm_glview_gl_api_get(obj); + gl->glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + gl->glClear(GL_COLOR_BUFFER_BIT); + + if (glCanvas->draw() == tvg::Result::Success) { + glCanvas->sync(); + } +} + +void transitGlCb(Elm_Transit_Effect *effect, Elm_Transit* transit, double progress) +{ + tvgUpdateCmds(glCanvas.get(), progress); +} + + +/************************************************************************/ +/* Main Code */ +/************************************************************************/ + +int main(int argc, char **argv) +{ + tvg::CanvasEngine tvgEngine = tvg::CanvasEngine::Sw; + + if (argc > 1) { + if (!strcmp(argv[1], "gl")) tvgEngine = tvg::CanvasEngine::Gl; + } + + //Initialize ThorVG Engine + if (tvgEngine == tvg::CanvasEngine::Sw) { + cout << "tvg engine: software" << endl; + } else { + cout << "tvg engine: opengl" << endl; + } + + //Threads Count + auto threads = std::thread::hardware_concurrency(); + + //Initialize ThorVG Engine + if (tvg::Initializer::init(tvgEngine, threads) == tvg::Result::Success) { + + elm_init(argc, argv); + + Elm_Transit *transit = elm_transit_add(); + + if (tvgEngine == tvg::CanvasEngine::Sw) { + auto view = createSwView(); + elm_transit_effect_add(transit, transitSwCb, view, nullptr); + } else { + auto view = createGlView(); + elm_transit_effect_add(transit, transitGlCb, view, nullptr); + } + + elm_transit_duration_set(transit, 2); + elm_transit_repeat_times_set(transit, -1); + elm_transit_auto_reverse_set(transit, EINA_TRUE); + elm_transit_go(transit); + + elm_run(); + elm_shutdown(); + + //Terminate ThorVG Engine + tvg::Initializer::term(tvgEngine); + + } else { + cout << "engine is not supported" << endl; + } + return 0; +} diff --git a/test/testUpdate.cpp b/test/testUpdate.cpp new file mode 100644 index 00000000..6df8533b --- /dev/null +++ b/test/testUpdate.cpp @@ -0,0 +1,164 @@ +#include "testCommon.h" + +/************************************************************************/ +/* Drawing Commands */ +/************************************************************************/ + +void tvgDrawCmds(tvg::Canvas* canvas) +{ + if (!canvas) return; + + //Shape + auto shape = tvg::Shape::gen(); + shape->appendRect(-100, -100, 200, 200, 0, 0); + shape->fill(255, 255, 255, 255); + canvas->push(move(shape)); +} + +void tvgUpdateCmds(tvg::Canvas* canvas, float progress) +{ + if (!canvas) return; + + //Explicitly clear all retained paint nodes. + if (canvas->clear() != tvg::Result::Success) return; + + //Shape + auto shape = tvg::Shape::gen(); + shape->appendRect(-100, -100, 200, 200, (100 * progress), (100 * progress)); + shape->fill(rand()%255, rand()%255, rand()%255, 255); + shape->translate(800 * progress, 800 * progress); + shape->scale(1 - 0.75 * progress); + shape->rotate(360 * progress); + + canvas->push(move(shape)); +} + + +/************************************************************************/ +/* Sw Engine Test Code */ +/************************************************************************/ + +static unique_ptr swCanvas; + +void tvgSwTest(uint32_t* buffer) +{ + //Create a Canvas + swCanvas = tvg::SwCanvas::gen(); + swCanvas->target(buffer, WIDTH, WIDTH, HEIGHT, tvg::SwCanvas::ARGB8888); + + /* Push the shape into the Canvas drawing list + When this shape is into the canvas list, the shape could update & prepare + internal data asynchronously for coming rendering. + Canvas keeps this shape node unless user call canvas->clear() */ + tvgDrawCmds(swCanvas.get()); +} + +void transitSwCb(Elm_Transit_Effect *effect, Elm_Transit* transit, double progress) +{ + tvgUpdateCmds(swCanvas.get(), progress); + + //Update Efl Canvas + Eo* img = (Eo*) effect; + evas_object_image_data_update_add(img, 0, 0, WIDTH, HEIGHT); + evas_object_image_pixels_dirty_set(img, EINA_TRUE); +} + +void drawSwView(void* data, Eo* obj) +{ + if (swCanvas->draw() == tvg::Result::Success) { + swCanvas->sync(); + } +} + + +/************************************************************************/ +/* GL Engine Test Code */ +/************************************************************************/ + +static unique_ptr glCanvas; + +void initGLview(Evas_Object *obj) +{ + static constexpr auto BPP = 4; + + //Create a Canvas + glCanvas = tvg::GlCanvas::gen(); + glCanvas->target(nullptr, WIDTH * BPP, WIDTH, HEIGHT); + + /* Push the shape into the Canvas drawing list + When this shape is into the canvas list, the shape could update & prepare + internal data asynchronously for coming rendering. + Canvas keeps this shape node unless user call canvas->clear() */ + tvgDrawCmds(glCanvas.get()); +} + +void drawGLview(Evas_Object *obj) +{ + auto gl = elm_glview_gl_api_get(obj); + gl->glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + gl->glClear(GL_COLOR_BUFFER_BIT); + + if (glCanvas->draw() == tvg::Result::Success) { + glCanvas->sync(); + } +} + +void transitGlCb(Elm_Transit_Effect *effect, Elm_Transit* transit, double progress) +{ + tvgUpdateCmds(glCanvas.get(), progress); +} + + +/************************************************************************/ +/* Main Code */ +/************************************************************************/ + +int main(int argc, char **argv) +{ + tvg::CanvasEngine tvgEngine = tvg::CanvasEngine::Sw; + + if (argc > 1) { + if (!strcmp(argv[1], "gl")) tvgEngine = tvg::CanvasEngine::Gl; + } + + //Initialize ThorVG Engine + if (tvgEngine == tvg::CanvasEngine::Sw) { + cout << "tvg engine: software" << endl; + } else { + cout << "tvg engine: opengl" << endl; + } + + //Threads Count + auto threads = std::thread::hardware_concurrency(); + + //Initialize ThorVG Engine + if (tvg::Initializer::init(tvgEngine, threads) == tvg::Result::Success) { + + elm_init(argc, argv); + + Elm_Transit *transit = elm_transit_add(); + + if (tvgEngine == tvg::CanvasEngine::Sw) { + auto view = createSwView(); + elm_transit_effect_add(transit, transitSwCb, view, nullptr); + } else { + auto view = createGlView(); + elm_transit_effect_add(transit, transitGlCb, view, nullptr); + } + + elm_transit_duration_set(transit, 2); + elm_transit_repeat_times_set(transit, -1); + elm_transit_auto_reverse_set(transit, EINA_TRUE); + elm_transit_go(transit); + + elm_run(); + elm_shutdown(); + + //Terminate ThorVG Engine + tvg::Initializer::term(tvgEngine); + + } else { + cout << "engine is not supported" << endl; + } + return 0; +} diff --git a/thorvg.manifest b/thorvg.manifest new file mode 100644 index 00000000..97e8c313 --- /dev/null +++ b/thorvg.manifest @@ -0,0 +1,5 @@ + + + + +