feat(svg): add SVG2 special path command 'A' support. (#8574)
Arduino Lint / lint (push) Has been cancelled
Build Examples with C++ Compiler / build-examples (push) Has been cancelled
MicroPython CI / Build esp32 port (push) Has been cancelled
MicroPython CI / Build rp2 port (push) Has been cancelled
MicroPython CI / Build stm32 port (push) Has been cancelled
MicroPython CI / Build unix port (push) Has been cancelled
C/C++ CI / Build OPTIONS_16BIT - Ubuntu (push) Has been cancelled
C/C++ CI / Build OPTIONS_24BIT - Ubuntu (push) Has been cancelled
C/C++ CI / Build OPTIONS_FULL_32BIT - Ubuntu (push) Has been cancelled
C/C++ CI / Build OPTIONS_NORMAL_8BIT - Ubuntu (push) Has been cancelled
C/C++ CI / Build OPTIONS_SDL - Ubuntu (push) Has been cancelled
C/C++ CI / Build OPTIONS_VG_LITE - Ubuntu (push) Has been cancelled
C/C++ CI / Build OPTIONS_16BIT - cl - Windows (push) Has been cancelled
C/C++ CI / Build OPTIONS_16BIT - gcc - Windows (push) Has been cancelled
C/C++ CI / Build OPTIONS_24BIT - cl - Windows (push) Has been cancelled
C/C++ CI / Build OPTIONS_24BIT - gcc - Windows (push) Has been cancelled
C/C++ CI / Build OPTIONS_FULL_32BIT - cl - Windows (push) Has been cancelled
C/C++ CI / Build OPTIONS_FULL_32BIT - gcc - Windows (push) Has been cancelled
C/C++ CI / Build OPTIONS_VG_LITE - cl - Windows (push) Has been cancelled
C/C++ CI / Build OPTIONS_VG_LITE - gcc - Windows (push) Has been cancelled
C/C++ CI / Build ESP IDF ESP32S3 (push) Has been cancelled
C/C++ CI / Run tests with 32bit build (push) Has been cancelled
C/C++ CI / Run tests with 64bit build (push) Has been cancelled
BOM Check / bom-check (push) Has been cancelled
Verify that lv_conf_internal.h matches repository state / verify-conf-internal (push) Has been cancelled
Verify the widget property name / verify-property-name (push) Has been cancelled
Verify code formatting / verify-formatting (push) Has been cancelled
Compare file templates with file names / template-check (push) Has been cancelled
Build docs / build-and-deploy (push) Has been cancelled
Test API JSON generator / Test API JSON (push) Has been cancelled
Check Makefile / Build using Makefile (push) Has been cancelled
Check Makefile for UEFI / Build using Makefile for UEFI (push) Has been cancelled
Emulated Performance Test / ARM Emulated Benchmark - Script Check (scripts/perf/tests/benchmark_results_comment/test.sh) (push) Has been cancelled
Emulated Performance Test / ARM Emulated Benchmark - Script Check (scripts/perf/tests/filter_docker_logs/test.sh) (push) Has been cancelled
Emulated Performance Test / ARM Emulated Benchmark - Script Check (scripts/perf/tests/serialize_results/test.sh) (push) Has been cancelled
Emulated Performance Test / ARM Emulated Benchmark 32b - lv_conf_perf32b (push) Has been cancelled
Emulated Performance Test / ARM Emulated Benchmark 64b - lv_conf_perf64b (push) Has been cancelled
Emulated Performance Test / ARM Emulated Benchmark - Save PR Number (push) Has been cancelled
Hardware Performance Test / Hardware Performance Benchmark (push) Has been cancelled
Hardware Performance Test / HW Benchmark - Save PR Number (push) Has been cancelled
Performance Tests CI / Perf Tests OPTIONS_TEST_PERF_32B - Ubuntu (push) Has been cancelled
Performance Tests CI / Perf Tests OPTIONS_TEST_PERF_64B - Ubuntu (push) Has been cancelled
Port repo release update / run-release-branch-updater (push) Has been cancelled
Verify Font License / verify-font-license (push) Has been cancelled
Verify Kconfig / verify-kconfig (push) Has been cancelled
Hardware Build Test / Hardware Build Test (push) Has been cancelled

This commit is contained in:
Zhang Ji Peng
2025-07-22 15:40:34 +08:00
committed by GitHub
parent 56be82873f
commit 05adc7e965
10 changed files with 246 additions and 6 deletions
+157 -5
View File
@@ -22,6 +22,7 @@
#include <math.h>
#include <float.h>
#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)) {
+13
View File
@@ -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
+1
View File
@@ -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,
};
+43 -1
View File
@@ -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;
+13
View File
@@ -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);
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

+19
View File
@@ -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 width=600 height=200 viewBox=\"0 0 1200 400\" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">"
"<rect x=\"1\" y=\"1\" width=\"1198\" height=\"398\" fill=\"none\" stroke=\"blue\" stroke-width=\"1\"/>"
"<path d=\"M300,200 h-150 a150,150 0 1,0 150,-150 z\" fill=\"red\" stroke=\"blue\" stroke-width=\"5\"/>"
"<path d=\"M275,175 v-150 a150,150 0 0,0 -150,150 z\""
" fill=\"yellow\" stroke=\"blue\" stroke-width=\"5\"/>"
"<path d=\"M600,350 l 50,-25"
" a25,25 -30 0,1 50,-25 l 50,-25"
" a25,50 -30 0,1 50,-25 l 50,-25"
" a25,75 -30 0,1 50,-25 l 50,-25"
" a25,100 -30 0,1 50,-25 l 50,-25\""
" fill=\"none\" stroke=\"red\" stroke-width=\"5\"/></svg>";
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)