diff --git a/src/draw/lv_draw_vector.c b/src/draw/lv_draw_vector.c index 478e2b39e1..90dd2a8d01 100644 --- a/src/draw/lv_draw_vector.c +++ b/src/draw/lv_draw_vector.c @@ -22,6 +22,7 @@ #include #include +#define EPSILON 1e-6f #define MATH_PI 3.14159265358979323846f #define MATH_HALF_PI 1.57079632679489661923f @@ -74,11 +75,6 @@ static void _copy_draw_dsc(lv_vector_draw_dsc_t * dst, const lv_vector_draw_dsc_ dst->stroke_dsc.join = src->stroke_dsc.join; dst->stroke_dsc.miter_limit = src->stroke_dsc.miter_limit; lv_array_copy(&(dst->stroke_dsc.dash_pattern), &(src->stroke_dsc.dash_pattern)); - dst->stroke_dsc.gradient.style = src->stroke_dsc.gradient.style; - dst->stroke_dsc.gradient.cx = src->stroke_dsc.gradient.cx; - dst->stroke_dsc.gradient.cy = src->stroke_dsc.gradient.cy; - dst->stroke_dsc.gradient.cr = src->stroke_dsc.gradient.cr; - dst->stroke_dsc.gradient.spread = src->fill_dsc.gradient.spread; lv_memcpy(&(dst->stroke_dsc.gradient), &(src->stroke_dsc.gradient), sizeof(lv_vector_gradient_t)); lv_memcpy(&(dst->stroke_dsc.matrix), &(src->stroke_dsc.matrix), sizeof(lv_matrix_t)); @@ -195,6 +191,162 @@ void lv_vector_path_cubic_to(lv_vector_path_t * path, const lv_fpoint_t * p1, co lv_array_push_back(&path->points, p3); } +static lv_fpoint_t _point_on_ellipse(float rx, float ry, float cos_r, float sin_r, + float cx, float cy, float theta, float alpha) +{ + float cos_theta = cosf(theta); + float sin_theta = sinf(theta); + + float x = rx * cos_theta; + float y = ry * sin_theta; + + float x_rot = cos_r * x - sin_r * y; + float y_rot = sin_r * x + cos_r * y; + + if(fabsf(alpha) > EPSILON) { + float dx = -rx * sin_theta; + float dy = ry * cos_theta; + float dx_rot = cos_r * dx - sin_r * dy; + float dy_rot = sin_r * dx + cos_r * dy; + + x_rot += alpha * dx_rot; + y_rot += alpha * dy_rot; + } + + return (lv_fpoint_t) { + x_rot + cx, y_rot + cy + }; +} + +void lv_vector_path_arc_to(lv_vector_path_t * path, float rx, float ry, float rotate_angle, bool large_arc, + bool clockwise, const lv_fpoint_t * p) +{ + LV_ASSERT_NULL(path); + LV_ASSERT_NULL(p); + + if(lv_array_is_empty(&path->ops)) { + /*first op must be move_to*/ + return; + } + + if(rx <= 0 || ry <= 0) { + /*no needed to draw*/ + return; + } + + /*https://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes*/ + + lv_fpoint_t * cpt = lv_array_back(&path->points); + + float x0 = cpt->x; + float y0 = cpt->y; + + /*1. dealing with degradation*/ + if(fabsf(x0 - p->x) < EPSILON && fabsf(y0 - p->y) < EPSILON) { + /*same point*/ + return; + } + + float rotate = MATH_RADIANS(rotate_angle); + float sin_r = sinf(rotate); + float cos_r = cosf(rotate); + + /*2. transform point*/ + float dx = (x0 - p->x) * 0.5f; + float dy = (y0 - p->y) * 0.5f; + + float x1 = cos_r * dx + sin_r * dy; + float y1 = -sin_r * dx + cos_r * dy; + + /*3. adjust radius*/ + float lambda_val = (x1 * x1) / (rx * rx) + (y1 * y1) / (ry * ry); + if(lambda_val > 1.0f) { + rx *= sqrtf(lambda_val); + ry *= sqrtf(lambda_val); + } + + /*4. calc center point*/ + float rx_sq = rx * rx; + float ry_sq = ry * ry; + float x1_sq = x1 * x1; + float y1_sq = y1 * y1; + + float num = rx_sq * ry_sq - rx_sq * y1_sq - ry_sq * x1_sq; + float denom = rx_sq * y1_sq + ry_sq * x1_sq; + + float radicand = (denom > EPSILON) ? num / denom : 0.0f; + if(radicand < 0.0f) radicand = 0.0f; + + float sign = (large_arc == clockwise) ? -1.0f : 1.0f; + float coef = sign * sqrtf(radicand); + + float cx_prime = (coef * rx * y1) / ry; + float cy_prime = -(coef * ry * x1) / rx; + + float cx = cos_r * cx_prime - sin_r * cy_prime + (x0 + p->x) * 0.5f; + float cy = sin_r * cx_prime + cos_r * cy_prime + (y0 + p->y) * 0.5f; + + float ux = (x1 - cx_prime) / rx; + float uy = (y1 - cy_prime) / ry; + + /*5. calculate the starting angle and ending angle*/ + float n_sq = ux * ux + uy * uy; + float theta1 = 0.0f; + if(n_sq > EPSILON) { + theta1 = atan2f(uy, ux); + } + + float vx = (-x1 - cx_prime) / rx; + float vy = (-y1 - cy_prime) / ry; + + float n = sqrtf(n_sq * (vx * vx + vy * vy)); + float delta = 0.0f; + if(n > EPSILON) { + float cos_delta = (ux * vx + uy * vy) / n; + if(cos_delta > 1.0f) cos_delta = 1.0f; + else if(cos_delta < -1.0f) cos_delta = -1.0f; + + delta = acosf(cos_delta); + if(ux * vy - uy * vx < 0.0f) delta = -delta; + } + + if(!clockwise && delta > 0.0f) { + delta -= 2.0f * MATH_PI; + } + else if(clockwise && delta < 0.0f) { + delta += 2.0f * MATH_PI; + } + + /*6. split arc into segments within 90 degrees*/ + float angle_left = fabsf(delta); + int seg_count = (int)ceilf(angle_left / MATH_HALF_PI); + if(seg_count == 0) seg_count = 1; + + float segment_angle = delta / (float)seg_count; + + float current_angle = theta1; + for(int i = 0; i < seg_count; i++) { + float next_angle = current_angle + segment_angle; + + float alpha_val; + if(fabsf(segment_angle) < 0.1f) { + alpha_val = segment_angle / 6.0f; + } + else { + float tan_half = tanf(segment_angle * 0.5f); + alpha_val = sinf(segment_angle) * (sqrtf(4.0f + 3.0f * tan_half * tan_half) - 1.0f) / 3.0f; + } + + lv_fpoint_t p1 = _point_on_ellipse(rx, ry, cos_r, sin_r, cx, cy, current_angle, alpha_val); + lv_fpoint_t p2 = _point_on_ellipse(rx, ry, cos_r, sin_r, cx, cy, next_angle, -alpha_val); + lv_fpoint_t p3 = _point_on_ellipse(rx, ry, cos_r, sin_r, cx, cy, next_angle, 0.0f); + + lv_vector_path_cubic_to(path, &p1, &p2, &p3); + + current_angle = next_angle; + } +} + void lv_vector_path_close(lv_vector_path_t * path) { if(lv_array_is_empty(&path->ops)) { diff --git a/src/draw/lv_draw_vector.h b/src/draw/lv_draw_vector.h index ccb03df73a..ae9a7d0649 100644 --- a/src/draw/lv_draw_vector.h +++ b/src/draw/lv_draw_vector.h @@ -172,6 +172,19 @@ void lv_vector_path_quad_to(lv_vector_path_t * path, const lv_fpoint_t * p1, con void lv_vector_path_cubic_to(lv_vector_path_t * path, const lv_fpoint_t * p1, const lv_fpoint_t * p2, const lv_fpoint_t * p3); +/** + * Add ellipse arc to the path from last point to the point + * @param path pointer to a path + * @param radius_x the x radius for ellipse arc + * @param radius_y the y radius for ellipse arc + * @param rotate_angle the rotate angle for arc + * @param large_arc true for large arc, otherwise small + * @param clockwise true for clockwise, otherwise anticlockwise + * @param p pointer to a `lv_fpoint_t` variable for end point + */ +void lv_vector_path_arc_to(lv_vector_path_t * path, float radius_x, float radius_y, float rotate_angle, bool large_arc, + bool clockwise, const lv_fpoint_t * p); + /** * Close the sub path * @param path pointer to a path diff --git a/src/libs/svg/lv_svg.h b/src/libs/svg/lv_svg.h index 44721b2ace..3ed25c3435 100644 --- a/src/libs/svg/lv_svg.h +++ b/src/libs/svg/lv_svg.h @@ -255,6 +255,7 @@ enum _lv_svg_path_cmd_t { LV_SVG_PATH_CMD_LINE_TO = 76, LV_SVG_PATH_CMD_CURVE_TO = 67, LV_SVG_PATH_CMD_QUAD_TO = 81, + LV_SVG_PATH_CMD_ARC_TO = 65, /*svg2 extension*/ LV_SVG_PATH_CMD_CLOSE = 90, }; diff --git a/src/libs/svg/lv_svg_parser.c b/src/libs/svg/lv_svg_parser.c index a3dec320a8..e44c74a9b0 100644 --- a/src/libs/svg/lv_svg_parser.c +++ b/src/libs/svg/lv_svg_parser.c @@ -893,6 +893,9 @@ static int _get_path_point_count(char cmd) case 'T': case 't': return 2; + case 'A': + case 'a': + return 4; default: return 0; } @@ -901,6 +904,7 @@ static int _get_path_point_count(char cmd) static bool _is_relative_cmd(char cmd) { switch(cmd) { + case 'a': case 'm': case 'l': case 'h': @@ -911,6 +915,7 @@ static bool _is_relative_cmd(char cmd) case 't': case 'z': return true; + case 'A': case 'M': case 'L': case 'H': @@ -927,7 +932,7 @@ static bool _is_relative_cmd(char cmd) static bool _is_path_cmd(char ch) { - return ch != 0 && strchr("MLHVCSQTZmlhvcsqtz", ch) != NULL; + return ch != 0 && strchr("AMLHVCSQTZamlhvcsqtz", ch) != NULL; } static void _process_path_value(lv_svg_node_t * node, lv_svg_attr_type_t type, const char * val_start, @@ -1178,6 +1183,43 @@ static void _process_path_value(lv_svg_node_t * node, lv_svg_attr_type_t type, c cur_point.y = point[1].y; } break; + case 'A': + case 'a': { + lv_svg_point_t * point = (lv_svg_point_t *)(&path_seg->data); + float rx = 0.0f; + ptr = _parse_number(ptr, val_end, &rx); + float ry = 0.0f; + ptr = _parse_number(ptr, val_end, &ry); + float rotate = 0.0f; + ptr = _parse_number(ptr, val_end, &rotate); + float large_arc = 0.0f; + ptr = _parse_number(ptr, val_end, &large_arc); + float sweep = 0.0f; + ptr = _parse_number(ptr, val_end, &sweep); + + float xval = 0.0f; + ptr = _parse_number(ptr, val_end, &xval); + float yval = 0.0f; + ptr = _parse_number(ptr, val_end, &yval); + if(relative) { + xval += cur_point.x; + yval += cur_point.y; + } + + path_seg->cmd = LV_SVG_PATH_CMD_ARC_TO; + point[0].x = rx; + point[0].y = ry; + point[1].x = rotate; + point[1].y = 0.0f; + point[2].x = large_arc; + point[2].y = sweep; + point[3].x = xval; + point[3].y = yval; + + cur_point.x = xval; + cur_point.y = yval; + } + break; case 'Z': case 'z': { path_seg->cmd = LV_SVG_PATH_CMD_CLOSE; diff --git a/src/libs/svg/lv_svg_render.c b/src/libs/svg/lv_svg_render.c index 345bbddd64..0206a3b366 100644 --- a/src/libs/svg/lv_svg_render.c +++ b/src/libs/svg/lv_svg_render.c @@ -503,6 +503,8 @@ static size_t _get_path_seg_size(uint32_t cmd) return sizeof(lv_svg_point_t) * 2 + sizeof(uint32_t); case LV_SVG_PATH_CMD_CURVE_TO: return sizeof(lv_svg_point_t) * 3 + sizeof(uint32_t); + case LV_SVG_PATH_CMD_ARC_TO: + return sizeof(lv_svg_point_t) * 4 + sizeof(uint32_t); default: return 0; } @@ -557,6 +559,17 @@ static void _set_path_attr(lv_svg_render_obj_t * obj, lv_vector_draw_dsc_t * dsc CALC_BOUNDS(pt[2], poly->bounds); } break; + case LV_SVG_PATH_CMD_ARC_TO: { + lv_fpoint_t pt[4] = { + {points[0].x, points[0].y}, + {points[1].x, points[1].y}, + {points[2].x, points[2].y}, + {points[3].x, points[3].y} + }; + lv_vector_path_arc_to(poly->path, pt[0].x, pt[0].y, pt[1].x, pt[2].x > 0, pt[2].y > 0, &pt[3]); + lv_vector_path_get_bounding(poly->path, &poly->bounds); + } + break; case LV_SVG_PATH_CMD_CLOSE: { lv_vector_path_close(poly->path); } diff --git a/tests/ref_imgs/draw/svg_draw_svg_shapes_12.lp32.png b/tests/ref_imgs/draw/svg_draw_svg_shapes_12.lp32.png new file mode 100644 index 0000000000..07d946fd94 Binary files /dev/null and b/tests/ref_imgs/draw/svg_draw_svg_shapes_12.lp32.png differ diff --git a/tests/ref_imgs/draw/svg_draw_svg_shapes_12.lp64.png b/tests/ref_imgs/draw/svg_draw_svg_shapes_12.lp64.png new file mode 100644 index 0000000000..89245160eb Binary files /dev/null and b/tests/ref_imgs/draw/svg_draw_svg_shapes_12.lp64.png differ diff --git a/tests/ref_imgs_vg_lite/draw/svg_draw_svg_shapes_12.lp32.png b/tests/ref_imgs_vg_lite/draw/svg_draw_svg_shapes_12.lp32.png new file mode 100644 index 0000000000..cb97b447d7 Binary files /dev/null and b/tests/ref_imgs_vg_lite/draw/svg_draw_svg_shapes_12.lp32.png differ diff --git a/tests/ref_imgs_vg_lite/draw/svg_draw_svg_shapes_12.lp64.png b/tests/ref_imgs_vg_lite/draw/svg_draw_svg_shapes_12.lp64.png new file mode 100644 index 0000000000..05324a697a Binary files /dev/null and b/tests/ref_imgs_vg_lite/draw/svg_draw_svg_shapes_12.lp64.png differ diff --git a/tests/src/test_cases/draw/test_draw_svg.c b/tests/src/test_cases/draw/test_draw_svg.c index d106df1814..acb775aba5 100644 --- a/tests/src/test_cases/draw/test_draw_svg.c +++ b/tests/src/test_cases/draw/test_draw_svg.c @@ -308,6 +308,25 @@ void test_draw_shapes(void) draw_svg(svg); draw_snapshot(SNAPSHOT_NAME(svg_shapes_11)); lv_svg_node_delete(svg); + + const char * svg_shapes_12 = \ + "" + "" + "" + "" + ""; + + svg = lv_svg_load_data(svg_shapes_12, lv_strlen(svg_shapes_12)); + TEST_ASSERT_NOT_EQUAL(NULL, svg); + draw_svg(svg); + draw_snapshot(SNAPSHOT_NAME(svg_shapes_12)); + lv_svg_node_delete(svg); } void test_draw_image(void)