From ef20181bfc85ec938aebcbcb08f045618c756d99 Mon Sep 17 00:00:00 2001 From: Michal Szczecinski Date: Thu, 18 Mar 2021 08:01:30 +0100 Subject: [PATCH] evas vg: Keep backward comaptibility for append_arc. 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 | 29 ++- src/lib/evas/canvas/evas_tvg_path_helpers.h | 289 ++++++++++++++++++++++++++++ 2 files changed, 312 insertions(+), 6 deletions(-) diff --git a/src/lib/evas/canvas/efl_canvas_vg_shape.c b/src/lib/evas/canvas/efl_canvas_vg_shape.c index 95fdce7..69da7b1 100644 --- a/src/lib/evas/canvas/efl_canvas_vg_shape.c +++ b/src/lib/evas/canvas/efl_canvas_vg_shape.c @@ -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); } diff --git a/src/lib/evas/canvas/evas_tvg_path_helpers.h b/src/lib/evas/canvas/evas_tvg_path_helpers.h index f3586f1..087fa11 100644 --- a/src/lib/evas/canvas/evas_tvg_path_helpers.h +++ b/src/lib/evas/canvas/evas_tvg_path_helpers.h @@ -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 -- 2.7.4