evas vg: Keep backward comaptibility for append_arc. 42/255442/3
authorMichal Szczecinski <m.szczecinsk@partner.samsung.com>
Thu, 18 Mar 2021 07:01:30 +0000 (08:01 +0100)
committerMichal Szczecinski <m.szczecinsk@partner.samsung.com>
Thu, 18 Mar 2021 10:39:16 +0000 (11:39 +0100)
In origin solution evas_vg_shape_append_arc() connects don't closed
arcs using line. To keep backward comaptibility solution from
efl_gfx_path was moved to tvg integration code.

Change-Id: Id868bd729e9b4a6ed26c5aea02a0a7de0d41edb1

src/lib/evas/canvas/efl_canvas_vg_shape.c
src/lib/evas/canvas/evas_tvg_path_helpers.h

index 95fdce7..69da7b1 100644 (file)
@@ -1452,18 +1452,35 @@ evas_vg_shape_append_arc(Evas_Vg_Shape *obj, double x, double y, double w, doubl
    efl_gfx_path_append_arc(obj, x, y, w, h, start_angle, sweep_length);
 #else
    Efl_Canvas_Vg_Shape_Data *sd = NULL;
+   const Tvg_Path_Command *cmds = NULL;
+   uint32_t cmds_count = 0;
+
+   int i = 0, point_count = 0;
+   Point pts[15] = { 0 };
+   //FIX_TVG: we can revise this with tvg appendArc() way.
+   Point curve_start = _curves_for_arc(x, y, w, h, start_angle, sweep_length, pts, &point_count);
 
    if (!obj) return;
    sd = _get_shape_data(obj);
    if (!sd || !sd->shape) return;
 
-   double radius = fmin(w / 2.0f, h / 2.0f);
-   double cx = x + (w / 2.0f);
-   double cy = y + (h / 2.0f);
+   tvg_shape_get_path_commands(sd->shape, &cmds, &cmds_count);
+   if (cmds_count && cmds[cmds_count - 1] != TVG_PATH_COMMAND_CLOSE)
+     {
+        tvg_shape_line_to(sd->shape, curve_start.x, curve_start.y);
+     }
+   else
+     {
+        tvg_shape_move_to(sd->shape, curve_start.x, curve_start.y);
+     }
 
-   tvg_shape_append_arc(sd->shape, cx, cy, radius, -start_angle, -sweep_length, 0);
-   _assign_current_point(sd, NULL, cx + radius*cos((start_angle + sweep_length) * M_PI / 180),
-                                   cy - radius*sin((start_angle + sweep_length) * M_PI / 180));
+   for (i = 0; i < point_count; i += 3)
+     {
+        tvg_shape_cubic_to(sd->shape, pts[i].x, pts[i].y,
+                           pts[i+1].x, pts[i+1].y,
+                           pts[i+2].x, pts[i+2].y);
+     }
+   _assign_current_point(sd, NULL, pts[i+2].x, pts[i+2].y);
 #endif
    efl_canvas_vg_node_change(obj);
 }
index f3586f1..087fa11 100644 (file)
@@ -6,8 +6,11 @@
  * which will be deprecated at some point
  */
 
+#define PATH_KAPPA 0.5522847498
+
 typedef struct _Efl_Tvg_Shape_Svg_Path  Efl_Tvg_Shape_Svg_Path;
 typedef struct _Arc_To_Init_Variables   Arc_To_Init_Variables;
+typedef struct _Point Point;
 
 struct _Efl_Tvg_Shape_Svg_Path {
    char             *svg_path_data;
@@ -29,6 +32,11 @@ struct _Arc_To_Init_Variables {
    int    segments;
 };
 
+struct _Point {
+   double x;
+   double y;
+};
+
 static char *
 _skipcomma(const char *content)
 {
@@ -607,4 +615,285 @@ _process_command(Efl_Tvg_Shape_Svg_Path *svg_path, char c, double *pts,
          break;
      }
 }
+
+
+inline static void
+_bezier_coefficients(double t, double *ap, double *bp, double *cp, double *dp)
+{
+   double a,b,c,d;
+   double m_t = 1.0 - t;
+
+   b = m_t * m_t;
+   c = t * t;
+   d = c * t;
+   a = b * m_t;
+   b *= 3.0 * t;
+   c *= 3.0 * m_t;
+   *ap = a;
+   *bp = b;
+   *cp = c;
+   *dp = d;
+}
+
+static double
+_efl_gfx_t_for_arc_angle(double angle)
+{
+   double radians, cos_angle, sin_angle, tc, ts, t;
+
+   if (angle < 0.00001) return 0;
+   if (EINA_FLT_EQ(angle, 90.0)) return 1;
+
+   radians = (angle/180) * M_PI;
+
+   cos_angle = cos(radians);
+   sin_angle = sin(radians);
+
+   // initial guess
+   tc = angle / 90;
+
+   // do some iterations of newton's method to approximate cos_angle
+   // finds the zero of the function b.pointAt(tc).x() - cos_angle
+   tc -= ((((2-3*PATH_KAPPA) * tc + 3*(PATH_KAPPA-1)) * tc) * tc + 1 - cos_angle) // 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 - cos_angle) // value
+   / (((6-9*PATH_KAPPA) * tc + 6*(PATH_KAPPA-1)) * tc); // derivative
+
+   // initial guess
+   ts = tc;
+   // do some iterations of newton's method to approximate sin_angle
+   // finds the zero of the function b.pointAt(tc).y() - sin_angle
+   ts -= ((((3*PATH_KAPPA-2) * ts -  6*PATH_KAPPA + 3) * ts + 3*PATH_KAPPA) * ts - sin_angle)
+   / (((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 - sin_angle)
+   / (((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
+   t = 0.5 * (tc + ts);
+   return t;
+}
+
+static void
+_find_ellipse_coords(double x, double y, double w, double h, double angle,
+                     double length, Point* start_point, Point *end_point)
+{
+   int i, quadrant;
+   double theta, t, a, b, c, d, px, py, cx, cy;
+   double w2 = w / 2;
+   double h2 = h / 2;
+   double angles[2] = { angle, angle + length };
+   Point *points[2];
+
+   if (EINA_FLT_EQ(w, 0.0) || EINA_FLT_EQ(h, 0.0))
+     {
+        if (start_point)
+          {
+            start_point->x = 0;
+            start_point->y = 0;
+          }
+        if (end_point)
+          {
+             end_point->x = 0;
+             end_point->y = 0;
+          }
+        return;
+     }
+
+   points[0] = start_point;
+   points[1] = end_point;
+
+   for (i = 0; i < 2; ++i)
+     {
+        if (!points[i])
+          continue;
+
+        theta = angles[i] - 360 * floor(angles[i] / 360);
+        t = theta / 90;
+        // truncate
+        quadrant = (int)t;
+        t -= quadrant;
+
+        t = _efl_gfx_t_for_arc_angle(90 * t);
+
+        // swap x and y?
+        if (quadrant & 1)
+          t = 1 - t;
+
+        _bezier_coefficients(t, &a, &b, &c, &d);
+        px = a + b + c*PATH_KAPPA;
+        py = d + c + b*PATH_KAPPA;
+
+        // left quadrants
+        if (quadrant == 1 || quadrant == 2)
+          px = -px;
+
+        // top quadrants
+        if (quadrant == 0 || quadrant == 1)
+          py = -py;
+        cx = x+w/2;
+        cy = y+h/2;
+        points[i]->x = cx + w2 * px;
+        points[i]->y = cy + h2 * py;
+     }
+}
+
+
+// The return value is the starting point of the arc
+static Point
+_curves_for_arc(double x, double y, double w, double h,
+                double start_angle, double sweep_length,
+                Point *curves, int *point_count)
+{
+   int start_segment, end_segment, delta, i, j, end, quadrant;
+   double start_t, end_t;
+   Eina_Bool split_at_start, split_at_end;
+   Eina_Bezier b, res;
+   Point start_point, end_point;
+   double w2 = w / 2;
+   double w2k = w2 * PATH_KAPPA;
+   double h2 = h / 2;
+   double h2k = h2 * PATH_KAPPA;
+
+   Point points[16] =
+     {
+        // start point
+          { x + w, y + h2 },
+
+          // 0 -> 270 degrees
+          { x + w, y + h2 + h2k },
+          { x + w2 + w2k, y + h },
+          { x + w2, y + h },
+
+          // 270 -> 180 degrees
+          { x + w2 - w2k, y + h },
+          { x, y + h2 + h2k },
+          { x, y + h2 },
+
+          // 180 -> 90 degrees
+          { x, y + h2 - h2k },
+          { x + w2 - w2k, y },
+          { x + w2, y },
+
+          // 90 -> 0 degrees
+          { x + w2 + w2k, y },
+          { x + w, y + h2 - h2k },
+          { x + w, y + h2 }
+     };
+
+   *point_count = 0;
+
+   if (sweep_length > 360) sweep_length = 360;
+   else if (sweep_length < -360) sweep_length = -360;
+
+   // Special case fast paths
+   if (EINA_FLT_EQ(start_angle, 0))
+     {
+        if (EINA_FLT_EQ(sweep_length, 360))
+          {
+             for (i = 11; i >= 0; --i)
+               curves[(*point_count)++] = points[i];
+             return points[12];
+          }
+        else if (EINA_FLT_EQ(sweep_length, -360))
+          {
+             for (i = 1; i <= 12; ++i)
+               curves[(*point_count)++] = points[i];
+             return points[0];
+          }
+     }
+
+   start_segment = (int)(floor(start_angle / 90));
+   end_segment = (int)(floor((start_angle + sweep_length) / 90));
+
+   start_t = (start_angle - start_segment * 90) / 90;
+   end_t = (start_angle + sweep_length - end_segment * 90) / 90;
+
+   delta = sweep_length > 0 ? 1 : -1;
+   if (delta < 0)
+     {
+        start_t = 1 - start_t;
+        end_t = 1 - end_t;
+     }
+
+   // avoid empty start segment
+   if (EINA_FLT_EQ(start_t, 1.0))
+     {
+        start_t = 0;
+        start_segment += delta;
+     }
+
+   // avoid empty end segment
+   if (EINA_FLT_EQ(end_t, 0.0))
+     {
+        end_t = 1;
+        end_segment -= delta;
+     }
+
+   start_t = _efl_gfx_t_for_arc_angle(start_t * 90);
+   end_t = _efl_gfx_t_for_arc_angle(end_t * 90);
+
+   split_at_start = !(fabs(start_t) <= 0.00001f);
+   split_at_end = !(fabs(end_t - 1.0) <= 0.00001f);
+
+   end = end_segment + delta;
+
+   // empty arc?
+   if (start_segment == end)
+     {
+        quadrant = 3 - ((start_segment % 4) + 4) % 4;
+        j = 3 * quadrant;
+        return delta > 0 ? points[j + 3] : points[j];
+     }
+
+   _find_ellipse_coords(x, y, w, h, start_angle, sweep_length,
+                        &start_point, &end_point);
+
+   for (i = start_segment; i != end; i += delta)
+     {
+        quadrant = 3 - ((i % 4) + 4) % 4;
+        j = 3 * quadrant;
+
+        if (delta > 0)
+          eina_bezier_values_set(&b, points[j + 3].x, points[j + 3].y,
+                                 points[j + 2].x, points[j + 2].y,
+                                 points[j + 1].x, points[j + 1].y,
+                                 points[j].x, points[j].y);
+        else
+          eina_bezier_values_set(&b, points[j].x, points[j].y,
+                                 points[j + 1].x, points[j + 1].y,
+                                 points[j + 2].x, points[j + 2].y,
+                                 points[j + 3].x, points[j + 3].y);
+
+        // empty arc?
+        if (start_segment == end_segment && (EINA_FLT_EQ(start_t, end_t)))
+            return start_point;
+
+        res = b;
+        if (i == start_segment)
+          {
+             if (i == end_segment && split_at_end)
+               eina_bezier_on_interval(&b, start_t, end_t, &res);
+             else if (split_at_start)
+               eina_bezier_on_interval(&b, start_t, 1, &res);
+          }
+        else if (i == end_segment && split_at_end)
+          {
+             eina_bezier_on_interval(&b, 0, end_t, &res);
+          }
+
+        // push control points
+        curves[(*point_count)].x = res.ctrl_start.x;
+        curves[(*point_count)++].y = res.ctrl_start.y;
+        curves[(*point_count)].x = res.ctrl_end.x;
+        curves[(*point_count)++].y = res.ctrl_end.y;
+        curves[(*point_count)].x = res.end.x;
+        curves[(*point_count)++].y = res.end.y;
+     }
+
+   curves[*(point_count)-1] = end_point;
+
+   return start_point;
+}
+
+
 #endif