diff --git a/inc/tizenvg.h b/inc/tizenvg.h index 24f2d3f5..64c8ee47 100644 --- a/inc/tizenvg.h +++ b/inc/tizenvg.h @@ -92,7 +92,7 @@ public: int update(RenderMethod* engine) noexcept override; int clear() noexcept; - int appendRect(float x, float y, float w, float h, float radius) noexcept; + int appendRect(float x, float y, float w, float h, float cornerRadius) noexcept; int appendCircle(float cx, float cy, float radius) noexcept; int fill(size_t r, size_t g, size_t b, size_t a) noexcept; diff --git a/meson.build b/meson.build index 32dd23be..5692fbce 100644 --- a/meson.build +++ b/meson.build @@ -1,6 +1,6 @@ project('tizenvg', 'cpp', - default_options : ['buildtype=debugoptimized', 'werror=false', 'cpp_std=c++14'], + default_options : ['buildtype=debug', 'werror=false', 'cpp_std=c++14'], version : '0.1.0', license : 'Apache-2.0') diff --git a/src/lib/sw_engine/tvgSwRle.cpp b/src/lib/sw_engine/tvgSwRle.cpp index 49d6238a..4542567a 100644 --- a/src/lib/sw_engine/tvgSwRle.cpp +++ b/src/lib/sw_engine/tvgSwRle.cpp @@ -417,7 +417,7 @@ static void _lineTo(RleWorker& rw, const SwPoint& to) auto py = diff.y * ONE_PIXEL; //left - if (prod <= 0 && prod - px) { + if (prod <= 0 && prod - px > 0) { f2 = {0, SW_UDIV(-prod, -dx_r)}; prod -= py; rw.cover += (f2.y - f1.y); @@ -557,9 +557,7 @@ static void _cubicTo(RleWorker& rw, const SwPoint& ctrl1, const SwPoint& ctrl2, draw: _lineTo(rw, arc[0]); - if (arc == rw.bezStack) return; - arc -= 3; } } @@ -612,7 +610,7 @@ static bool _decomposeOutline(RleWorker& rw) } //Close the contour with a line segment? - //if (!lineTo(rw, outline->pts[first])); + //_lineTo(rw, UPSCALE(outline->pts[first])); close: first = last + 1; } @@ -680,7 +678,7 @@ SwRleData* rleRender(const SwShape& sdata) rw.rle = reinterpret_cast(calloc(1, sizeof(SwRleData))); assert(rw.rle); - //printf("bufferSize = %d, bbox(%f %f %f %f), exCnt(%f), eyCnt(%f), bandSize(%d)\n", rw.bufferSize, rw.exMin, rw.eyMin, rw.exMax, rw.eyMax, rw.exCnt, rw.eyCnt, rw.bandSize); + //printf("bufferSize = %d, bbox(%d %d %d %d), exCnt(%f), eyCnt(%f), bandSize(%d)\n", rw.bufferSize, rw.cellMin.x, rw.cellMin.y, rw.cellMax.x, rw.cellMax.y, rw.cellXCnt, rw.cellYCnt, rw.bandSize); //Generate RLE Band bands[BAND_SIZE]; diff --git a/src/lib/tvgCommon.h b/src/lib/tvgCommon.h index 1a4a0d0d..2653d3a1 100644 --- a/src/lib/tvgCommon.h +++ b/src/lib/tvgCommon.h @@ -21,6 +21,7 @@ #include #include #include +#include #include "tizenvg.h" #include "tvgRenderCommon.h" diff --git a/src/lib/tvgShapeNode.cpp b/src/lib/tvgShapeNode.cpp index 2c15b1bc..7c4ab2e5 100644 --- a/src/lib/tvgShapeNode.cpp +++ b/src/lib/tvgShapeNode.cpp @@ -136,21 +136,28 @@ int ShapeNode::pathCoords(const Point** pts) const noexcept int ShapeNode::appendCircle(float cx, float cy, float radius) noexcept { + auto impl = pImpl.get(); + assert(impl); + + impl->path->reserve(5, 13); //decide size experimentally (move + curve * 4) + impl->path->arcTo(cx - radius, cy - radius, 2 * radius, 2 * radius, 0, 360); + impl->path->close(); + return 0; } -int ShapeNode::appendRect(float x, float y, float w, float h, float radius) noexcept +int ShapeNode::appendRect(float x, float y, float w, float h, float cornerRadius) noexcept { auto impl = pImpl.get(); assert(impl); - //clamping radius by minimum size + //clamping cornerRadius by minimum size auto min = (w < h ? w : h) * 0.5f; - if (radius > min) radius = min; + if (cornerRadius > min) cornerRadius = min; //rectangle - if (radius == 0) { + if (cornerRadius == 0) { impl->path->reserve(5, 4); impl->path->moveTo(x, y); impl->path->lineTo(x + w, y); @@ -158,8 +165,8 @@ int ShapeNode::appendRect(float x, float y, float w, float h, float radius) noex impl->path->lineTo(x, y + h); impl->path->close(); //circle - } else if (w == h && radius * 2 == w) { - appendCircle(x + (w * 0.5f), y + (h * 0.5f), radius); + } else if (w == h && cornerRadius * 2 == w) { + return appendCircle(x + (w * 0.5f), y + (h * 0.5f), cornerRadius); } else { //... } diff --git a/src/lib/tvgShapePath.h b/src/lib/tvgShapePath.h index 0d11cdf4..8c35bbe9 100644 --- a/src/lib/tvgShapePath.h +++ b/src/lib/tvgShapePath.h @@ -23,6 +23,14 @@ /* Internal Class Implementation */ /************************************************************************/ +constexpr auto PATH_KAPPA = 0.552284f; + +struct ShapePath; + +static float _arcAngle(float angle); +static int _arcToCubic(ShapePath& path, const Point* pts, size_t ptsCnt); +static void _findEllipseCoords(float x, float y, float w, float h, float startAngle, float sweepAngle, Point& ptStart, Point& ptEnd); + struct ShapePath { PathCommand* cmds = nullptr; @@ -111,6 +119,145 @@ struct ShapePath return 0; } + + int arcTo(float x, float y, float w, float h, float startAngle, float sweepAngle) + { + if ((fabsf(w) < FLT_EPSILON) || (fabsf(h) < FLT_EPSILON)) return -1; + if (fabsf(sweepAngle) < FLT_EPSILON) return -1; + + if (sweepAngle > 360) sweepAngle = 360; + else if (sweepAngle < -360) sweepAngle = -360; + + auto half_w = w * 0.5f; + auto half_h = h * 0.5f; + auto half_w_kappa = half_w * PATH_KAPPA; + auto half_h_kappa = half_h * PATH_KAPPA; + + //Curves for arc + Point pts[13] { + //start point: 0 degree + {x + w, y + half_h}, + + //0 -> 90 degree + {x + w, y + half_h + half_h_kappa}, + {x + half_w + half_w_kappa, y + h}, + {x + half_w, y + h}, + + //90 -> 180 degree + {x + half_w - half_w_kappa, y + h}, + {x, y + half_h + half_h_kappa}, + {x, y + half_h}, + + //180 -> 270 degree + {x, y + half_h - half_h_kappa}, + {x + half_w - half_w_kappa, y}, + {x + half_w, y}, + + //270 -> 0 degree + {x + half_w + half_w_kappa, y}, + {x + w, y + half_h - half_h_kappa}, + {x + w, y + half_w} + }; + + auto ptsCnt = 1; //one is reserved for the start point + Point curves[13]; + + //perfect circle: special case fast paths + if (fabsf(startAngle) <= FLT_EPSILON) { + if (fabsf(sweepAngle - 360) <= FLT_EPSILON) { + for (int i = 11; i >= 0; --i) { + curves[ptsCnt++] = pts[i]; + } + curves[0] = pts[12]; + return _arcToCubic(*this, curves, ptsCnt); + } else if (fabsf(sweepAngle + 360) <= FLT_EPSILON) { + for (int i = 1; i <= 12; ++i) { + curves[ptsCnt++] = pts[i]; + } + curves[0] = pts[0]; + return _arcToCubic(*this, curves, ptsCnt); + } + } + + auto startSegment = static_cast(floor(startAngle / 90)); + auto endSegment = static_cast(floor((startAngle + sweepAngle) / 90)); + auto startDelta = (startAngle - (startSegment * 90)) / 90; + auto endDelta = ((startAngle + sweepAngle) - (endSegment * 90)) / 90; + auto delta = sweepAngle > 0 ? 1 : -1; + + if (delta < 0) { + startDelta = 1 - startDelta; + endDelta = 1 - endDelta; + } + + //avoid empty start segment + if (fabsf(startDelta - 1) < FLT_EPSILON) { + startDelta = 0; + startSegment += delta; + } + + //avoid empty end segment + if (fabsf(endDelta) < FLT_EPSILON) { + endDelta = 1; + endSegment -= delta; + } + + startDelta = _arcAngle(startDelta * 90); + endDelta = _arcAngle(endDelta * 90); + + auto splitAtStart = (fabsf(startDelta) >= FLT_EPSILON) ? true : false; + auto splitAtEnd = (fabsf(endDelta - 1.0f) >= FLT_EPSILON) ? true : false; + auto end = endSegment + delta; + + //empty arc? + if (startSegment == end) { + auto quadrant = 3 - ((startSegment % 4) + 4) % 4; + auto i = 3 * quadrant; + curves[0] = (delta > 0) ? pts[i + 3] : pts[i]; + return _arcToCubic(*this, curves, ptsCnt); + } + + Point ptStart, ptEnd; + _findEllipseCoords(x, y, w, h, startAngle, sweepAngle, ptStart, ptEnd); + + for (auto i = startSegment; i != end; i += delta) { + //auto quadrant = 3 - ((i % 4) + 4) % 4; + //auto j = 3 * quadrant; + + if (delta > 0) { + //TODO: bezier + } else { + //TODO: bezier + } + + //empty arc? + if (startSegment == endSegment && (fabsf(startDelta - endDelta) < FLT_EPSILON)) { + curves[0] = ptStart; + return _arcToCubic(*this, curves, ptsCnt); + } + + if (i == startSegment) { + if (i == endSegment && splitAtEnd) { + //TODO: bezier + } else if (splitAtStart) { + //TODO: bezier + } + } else if (i == endSegment && splitAtEnd) { + //TODO: bezier + } + + //push control points + //curves[ptsCnt++] = ctrlPt1; + //curves[ptsCnt++] = ctrlPt2; + //curves[ptsCnt++] = endPt; + cout << "ArcTo: Not Implemented!" << endl; + } + + curves[ptsCnt - 1] = ptEnd; + + return _arcToCubic(*this, curves, ptsCnt); + } + int close() { if (cmdCnt + 1 > reservedCmdCnt) reserveCmd((cmdCnt + 1) * 2); @@ -142,8 +289,101 @@ struct ShapePath } }; -/************************************************************************/ -/* External Class Implementation */ -/************************************************************************/ +static float _arcAngle(float angle) + { + if (angle < FLT_EPSILON) return 0; + if (fabsf(angle - 90) < FLT_EPSILON) return 1; + + auto radian = (angle / 180) * M_PI; + auto cosAngle = cos(radian); + auto sinAngle = sin(radian); + + //initial guess + auto tc = angle / 90; + + /* do some iterations of newton's method to approximate cosAngle + finds the zero of the function b.pointAt(tc).x() - cosAngle */ + tc -= ((((2 - 3 * PATH_KAPPA) * tc + 3 * (PATH_KAPPA - 1)) * tc) * tc + 1 - cosAngle) // value + / (((6 - 9 * PATH_KAPPA) * tc + 6 * (PATH_KAPPA - 1)) * tc); // derivative + tc -= ((((2 - 3 * PATH_KAPPA) * tc + 3 * (PATH_KAPPA - 1)) * tc) * tc + 1 - cosAngle) // value + / (((6 - 9 * PATH_KAPPA) * tc + 6 * (PATH_KAPPA - 1)) * tc); // derivative + + // initial guess + auto ts = tc; + + /* do some iterations of newton's method to approximate sin_angle + finds the zero of the function b.pointAt(tc).y() - sinAngle */ + ts -= ((((3 * PATH_KAPPA - 2) * ts - 6 * PATH_KAPPA + 3) * ts + 3 * PATH_KAPPA) * ts - sinAngle) + / (((9 * PATH_KAPPA - 6) * ts + 12 * PATH_KAPPA - 6) * ts + 3 * PATH_KAPPA); + ts -= ((((3 * PATH_KAPPA - 2) * ts - 6 * PATH_KAPPA + 3) * ts + 3 * PATH_KAPPA) * ts - sinAngle) + / (((9 * PATH_KAPPA - 6) * ts + 12 * PATH_KAPPA - 6) * ts + 3 * PATH_KAPPA); + + //use the average of the t that best approximates cos_angle and the t that best approximates sin_angle + return (0.5 * (tc + ts)); +} + + +static int _arcToCubic(ShapePath& path, const Point* pts, size_t ptsCnt) +{ + assert(pts); + + if (path.cmdCnt > 0 && path.cmds[path.cmdCnt] != PathCommand::Close) { + if (path.lineTo(pts[0].x, pts[0].y)) return -1; + } else { + if (path.moveTo(pts[0].x, pts[0].y)) return -1; + } + + for (size_t i = 1; i < ptsCnt; i += 3) { + if (path.cubicTo(pts[i].x, pts[i].y, pts[i+1].x, pts[i+1].y, pts[i+2].x, pts[i+2].y)) { + return -1; + } + } + + return 0; +} + + +static void _findEllipseCoords(float x, float y, float w, float h, float startAngle, float sweepAngle, Point& ptStart, Point& ptEnd) +{ + float angles[2] = {startAngle, startAngle + sweepAngle}; + float half_w = w * 0.5f; + float half_h = h * 0.5f; + float cx = x + half_w; + float cy = y + half_h; + Point* pts[2] = {&ptStart, &ptEnd}; + + for (auto i = 0; i < 2; ++i) { + auto theta = angles[i] - 360 * floor(angles[i] / 360); + auto t = theta / 90; + auto quadrant = static_cast(t); //truncate + t -= quadrant; + t = _arcAngle(90 * t); + + //swap x and y? + if (quadrant & 1) t = (1 - t); + + //bezier coefficients + auto m = 1 - t; + auto b = m * m; + auto c = t * t; + auto d = c * t; + auto a = b * m; + b *= 3 * t; + c *= 3 * m; + + auto px = a + b + c * PATH_KAPPA; + auto py = d + c + b * PATH_KAPPA; + + //left quadrants + if (quadrant == 1 || quadrant == 2) px = -px; + + //top quadrants + if (quadrant == 0 || quadrant == 1) py = -py; + + pts[i]->x = cx + half_w * px; + pts[i]->y = cy + half_h * py; + } +} + #endif //_TVG_SHAPE_PATH_CPP_ diff --git a/test/makefile b/test/makefile index 7b242a39..00b7d7ff 100644 --- a/test/makefile +++ b/test/makefile @@ -1,2 +1,3 @@ all: gcc -o testShape testShape.cpp -g -lstdc++ `pkg-config --cflags --libs elementary tizenvg` + gcc -o testMultipleShapes testMultipleShapes.cpp -g -lstdc++ `pkg-config --cflags --libs elementary tizenvg` diff --git a/test/testMultipleShapes.cpp b/test/testMultipleShapes.cpp index feeb9644..3826f8f7 100644 --- a/test/testMultipleShapes.cpp +++ b/test/testMultipleShapes.cpp @@ -1,4 +1,5 @@ #include +#include using namespace std; @@ -7,25 +8,25 @@ using namespace std; static uint32_t buffer[WIDTH * HEIGHT]; -int main(int argc, char **argv) +void tvgtest() { //Initialize TizenVG Engine tvg::Engine::init(); //Create a Canvas auto canvas = tvg::SwCanvas::gen(buffer, WIDTH, HEIGHT); - canvas->reserve(2); //reserve 2 shape nodes (optional) + //canvas->reserve(2); //reserve 2 shape nodes (optional) //Prepare Rectangle auto shape1 = tvg::ShapeNode::gen(); - shape1->rect(0, 0, 400, 400, 0.1); //x, y, w, h, corner_radius - shape1->fill(0, 255, 0, 255); //r, g, b, a + shape1->appendRect(0, 0, 400, 400, 0); //x, y, w, h, corner_radius + shape1->fill(0, 255, 0, 255); //r, g, b, a canvas->push(move(shape1)); //Prepare Circle auto shape2 = tvg::ShapeNode::gen(); - shape2->circle(400, 400, 200); //cx, cy, radius - shape2->fill(255, 255, 0, 255); //r, g, b, a + shape2->appendCircle(400, 400, 200); //cx, cy, radius + shape2->fill(255, 255, 0, 255); //r, g, b, a canvas->push(move(shape2)); //Draw the Shapes onto the Canvas @@ -35,3 +36,26 @@ int main(int argc, char **argv) //Terminate TizenVG Engine tvg::Engine::term(); } + +int main(int argc, char **argv) +{ + tvgtest(); + + //Show the result using EFL... + elm_init(argc, argv); + + Eo* win = elm_win_util_standard_add(NULL, "TizenVG Test"); + + Eo* img = evas_object_image_filled_add(evas_object_evas_get(win)); + evas_object_image_size_set(img, WIDTH, HEIGHT); + evas_object_image_data_set(img, buffer); + evas_object_size_hint_weight_set(img, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + evas_object_show(img); + + elm_win_resize_object_add(win, img); + evas_object_geometry_set(win, 0, 0, WIDTH, HEIGHT); + evas_object_show(win); + + elm_run(); + elm_shutdown(); +}