common shape: support rounded rectangle. 07/231907/3
authorHermet Park <chuneon.park@samsung.com>
Sun, 26 Apr 2020 06:00:29 +0000 (15:00 +0900)
committerHermet Park <chuneon.park@samsung.com>
Sun, 26 Apr 2020 07:18:25 +0000 (16:18 +0900)
Also remove arcTo implementation since curveTo could covers it.

Change-Id: Icc63eca55e51622fc80b57672f308f25f2301f85

src/lib/tvgShapeNode.cpp
src/lib/tvgShapePath.h
test/testMultipleShapes.cpp
test/testShape.cpp
test/testStroke.cpp

index 7c4ab2e..6ee0b7a 100644 (file)
@@ -23,6 +23,7 @@
 /************************************************************************/
 /* Internal Class Implementation                                        */
 /************************************************************************/
+constexpr auto PATH_KAPPA = 0.552284f;
 
 struct ShapeFill
 {
@@ -139,8 +140,14 @@ 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);
+    auto halfKappa = radius * PATH_KAPPA;
+
+    impl->path->reserve(6, 13);
+    impl->path->moveTo(cx, cy - radius);
+    impl->path->cubicTo(cx + halfKappa, cy - radius, cx + radius, cy - halfKappa, cx + radius, cy);
+    impl->path->cubicTo(cx + radius, cy + halfKappa, cx + halfKappa, cy + radius, cx, cy + radius);
+    impl->path->cubicTo(cx - halfKappa, cy + radius, cx - radius, cy + halfKappa, cx - radius, cy);
+    impl->path->cubicTo(cx - radius, cy - halfKappa, cx - halfKappa, cy - radius, cx, cy - radius);
     impl->path->close();
 
     return 0;
@@ -168,7 +175,18 @@ int ShapeNode::appendRect(float x, float y, float w, float h, float cornerRadius
     } else if (w == h && cornerRadius * 2 == w) {
         return appendCircle(x + (w * 0.5f), y + (h * 0.5f), cornerRadius);
     } else {
-        //...
+        auto halfKappa = cornerRadius * 0.5;
+        impl->path->reserve(10, 17);
+        impl->path->moveTo(x + cornerRadius, y);
+        impl->path->lineTo(x + w - cornerRadius, y);
+        impl->path->cubicTo(x + w - cornerRadius + halfKappa, y, x + w, y + cornerRadius - halfKappa, x + w, y + cornerRadius);
+        impl->path->lineTo(x + w, y + h - cornerRadius);
+        impl->path->cubicTo(x + w, y + h - cornerRadius + halfKappa, x + w - cornerRadius + halfKappa, y + h, x + w - cornerRadius, y + h);
+        impl->path->lineTo(x + cornerRadius, y + h);
+        impl->path->cubicTo(x + cornerRadius - halfKappa, y + h, x, y + h - cornerRadius + halfKappa, x, y + h - cornerRadius);
+        impl->path->lineTo(x, y + cornerRadius);
+        impl->path->cubicTo(x, y + cornerRadius - halfKappa, x + cornerRadius - halfKappa, y, x + cornerRadius, y);
+        impl->path->close();
     }
 
     return 0;
index 8c35bbe..6fda0f0 100644 (file)
 /* 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;
@@ -120,144 +112,6 @@ struct ShapePath
     }
 
 
-    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<int>(floor(startAngle / 90));
-        auto endSegment = static_cast<int>(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);
@@ -289,101 +143,4 @@ struct ShapePath
     }
 };
 
-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<int>(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_
index 3826f8f..f1bdfe8 100644 (file)
@@ -19,7 +19,7 @@ void tvgtest()
 
     //Prepare Rectangle
     auto shape1 = tvg::ShapeNode::gen();
-    shape1->appendRect(0, 0, 400, 400, 0);       //x, y, w, h, corner_radius
+    shape1->appendRect(0, 0, 400, 400, 50);      //x, y, w, h, cornerRadius
     shape1->fill(0, 255, 0, 255);                //r, g, b, a
     canvas->push(move(shape1));
 
index e42cba8..7d0aa88 100644 (file)
@@ -18,7 +18,7 @@ void tvgtest()
 
     //Prepare a Shape (Rectangle)
     auto shape1 = tvg::ShapeNode::gen();
-    shape1->appendRect(0, 0, 400, 400, 0);      //x, y, w, h, corner_radius
+    shape1->appendRect(0, 0, 400, 400, 0);      //x, y, w, h, cornerRadius
     shape1->fill(255, 0, 0, 255);               //r, g, b, a
 
     /* Push the shape into the Canvas drawing list
index 6a51412..de84c32 100644 (file)
@@ -17,7 +17,7 @@ int main(int argc, char **argv)
 
     //Prepare a Shape
     auto shape1 = tvg::ShapeNode::gen();
-    shape1->rect(0, 0, 400, 400, 0.1);     //x, y, w, h, corner_radius
+    shape1->rect(0, 0, 400, 400, 0.1);     //x, y, w, h, cornerRadius
     shape1->fill(0, 255, 0, 255);
 
     //Stroke Style