diff --git a/.gitignore b/.gitignore index b796b5e9..7f4d09bf 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ testGradientTransform testSvg testAsync testCapi +testArc diff --git a/inc/thorvg.h b/inc/thorvg.h index c738a0ee..50b03fd0 100644 --- a/inc/thorvg.h +++ b/inc/thorvg.h @@ -237,6 +237,7 @@ public: //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 x, float y, float w, float h, float startAngle, float sweep) noexcept; Result appendPath(const PathCommand* cmds, uint32_t cmdCnt, const Point* pts, uint32_t ptsCnt) noexcept; //Stroke diff --git a/inc/thorvg_capi.h b/inc/thorvg_capi.h index 9fa4d590..695b9948 100644 --- a/inc/thorvg_capi.h +++ b/inc/thorvg_capi.h @@ -133,6 +133,7 @@ TVG_EXPORT Tvg_Result tvg_shape_cubic_to(Tvg_Paint* paint, float cx1, float cy1, 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 x, float y, float w, float h, float startAngle, float sweep); 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); diff --git a/src/bindings/capi/tvgCapi.cpp b/src/bindings/capi/tvgCapi.cpp index 9ceb1135..8ddbc073 100644 --- a/src/bindings/capi/tvgCapi.cpp +++ b/src/bindings/capi/tvgCapi.cpp @@ -178,6 +178,10 @@ TVG_EXPORT Tvg_Result tvg_shape_append_rect(Tvg_Paint* paint, float x, float y, 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 x, float y, float w, float h, float startAngle, float sweep) +{ + return (Tvg_Result) reinterpret_cast(paint)->appendArc(x, y, w ,h, startAngle, sweep); +} TVG_EXPORT Tvg_Result tvg_shape_append_circle(Tvg_Paint* paint, float cx, float cy, float rx, float ry) { @@ -248,4 +252,4 @@ TVG_EXPORT Tvg_Result tvg_shape_transform(Tvg_Paint* paint, const Tvg_Matrix* m) #ifdef __cplusplus } -#endif \ No newline at end of file +#endif diff --git a/src/lib/tvgShape.cpp b/src/lib/tvgShape.cpp index 59718d8e..57c097a0 100644 --- a/src/lib/tvgShape.cpp +++ b/src/lib/tvgShape.cpp @@ -17,6 +17,8 @@ #ifndef _TVG_SHAPE_CPP_ #define _TVG_SHAPE_CPP_ +#include + #include "tvgShapeImpl.h" /************************************************************************/ @@ -148,6 +150,76 @@ Result Shape::appendCircle(float cx, float cy, float rx, float ry) noexcept return Result::Success; } +Result Shape::appendArc(float x, float y, float w, float h, float startAngle, float sweep) noexcept +{ + const float M_PI_HALF = M_PI / 2.0; + const float radius = w / 2; + + Point center = {x + radius, y + radius}; + + auto impl = pImpl.get(); + + startAngle = (startAngle * M_PI) / 180; + sweep = sweep * M_PI / 180; + + auto nCurves = ceil(sweep / M_PI_HALF); + auto fract = fmod(sweep, M_PI_HALF); + fract = (fract < std::numeric_limits::epsilon()) ? M_PI_HALF : fract; + + for (int i = 0; i < nCurves; ++i) { + //bezier parameters + Point start = {0, 0}; + Point end = {0, 0}; + Point bControl1 = {0, 0}; + Point bControl2 = {0, 0}; + + //variables needed to calculate bezier control points + auto ax = 0.0f, ay = 0.0f, bx = 0.0f, by = 0.0f, q1 = 0.0f, q2 = 0.0f, k2 = 0.0f; + auto endAngle = startAngle + ((i != nCurves - 1) ? M_PI_HALF : fract); + + //get bezier start and end point + start.x = radius * cos(startAngle); + start.y = radius * sin(startAngle); + end.x = radius * cos(endAngle); + end.y = radius * sin(endAngle); + + //get bezier control points using article: + //(http://itc.ktu.lt/index.php/ITC/article/view/11812/6479) + ax = start.x; + ay = start.y; + bx = end.x; + by = end.y; + + q1 = ax * ax + ay * ay; + q2 = ax * bx + ay * by + q1; + + k2 = static_cast (4.0/3.0) * ((sqrt(2 * q1 * q2) - q2) / (ax * by - ay * bx)); + + bControl1.x = ax - k2 * ay; + bControl1.y = ay + k2 * ax; + bControl2.x = bx + k2 * by; + bControl2.y = by - k2 * bx; + + //move points to proper arc center + start.x += center.x; + start.y += center.y; + end.x += center.x; + end.y += center.y; + bControl1.x += center.x; + bControl1.y += center.y; + bControl2.x += center.x; + bControl2.y += center.y; + + impl->path->moveTo(start.x, start.y); + impl->path->cubicTo(bControl1.x, bControl1.y, bControl2.x, bControl2.y, end.x, end.y); + + startAngle = endAngle; + } + + IMPL->flag |= RenderUpdateFlag::Path; + return Result::Success; +} + Result Shape::appendRect(float x, float y, float w, float h, float rx, float ry) noexcept { diff --git a/test/makefile b/test/makefile index e1a8afb0..29e6e779 100644 --- a/test/makefile +++ b/test/makefile @@ -18,4 +18,5 @@ all: 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 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/testArc.cpp b/test/testArc.cpp new file mode 100644 index 00000000..3878aff8 --- /dev/null +++ b/test/testArc.cpp @@ -0,0 +1,142 @@ +#include "testCommon.h" + +/************************************************************************/ +/* Drawing Commands */ +/************************************************************************/ + +void tvgDrawCmds(tvg::Canvas* canvas) +{ + if (!canvas) return; + + //draw reference rectangles + auto shape2 = tvg::Shape::gen(); + shape2->appendRect(0, 0, 200, 200, 0, 0); + shape2->stroke(255, 0, 0, 255); + shape2->stroke(2); + + auto shape3 = tvg::Shape::gen(); + shape3->moveTo(0, 100); + shape3->lineTo(200, 100); + shape3->stroke(255, 0, 0, 255); + shape3->stroke(2); + + auto shape4 = tvg::Shape::gen(); + shape4->moveTo(100, 0); + shape4->lineTo(100, 200); + shape4->stroke(255, 0, 0, 255); + shape4->stroke(2); + + //test arc + auto shape1 = tvg::Shape::gen(); + shape1->appendArc(0, 0, 200, 200, 10, 270); + shape1->stroke(255, 255, 255, 255); + shape1->stroke(3); + + //Appends Paths + if (canvas->push(move(shape2)) != tvg::Result::Success) return; + if (canvas->push(move(shape3)) != tvg::Result::Success) return; + if (canvas->push(move(shape4)) != tvg::Result::Success) return; + if (canvas->push(move(shape1)) != 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); + + /* 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; + } + + //Initialize ThorVG Engine + if (tvg::Initializer::init(tvgEngine) == 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; +}