diff --git a/docs/src/details/integration/chip_vendors/stm32/neochrom.rst b/docs/src/details/integration/chip_vendors/stm32/neochrom.rst index 9d63d1c77a..77c7c1fa52 100644 --- a/docs/src/details/integration/chip_vendors/stm32/neochrom.rst +++ b/docs/src/details/integration/chip_vendors/stm32/neochrom.rst @@ -75,6 +75,36 @@ provided implementations by setting :c:macro:`LV_USE_NEMA_HAL` to a value other :c:macro:`LV_NEMA_HAL_CUSTOM`. +Vector Graphics +*************** + +The NeoChrom VG driver in LVGL can render vector graphics Widgets using NeoChrom VG's +hardware support for vector graphics drawing. You can display SVG files in your application. +See the SVG examples for usage. + +To use vector graphics with NeoChrom, you should enable the following configs in ``lv_conf.h``. + +.. code-block:: + + LV_USE_NEMA_GFX 1 + LV_USE_NEMA_VG 1 + LV_USE_VECTOR_GRAPHIC 1 + LV_USE_MATRIX 1 + LV_USE_FLOAT 1 + +To use the SVG widget, additionally enable ``LV_USE_SVG``. + +If there is RAM available, SVG performance can be increased by enabling the image cache, +``LV_CACHE_DEF_SIZE``. +``LV_CACHE_DEF_SIZE`` is a cache size in bytes. If it is large enough for your SVGs, +it will cache decoded SVG data so it does not need to be parsed every refresh, significantly +reducing SVG redraw time. + +``LV_USE_DEMO_VECTOR_GRAPHIC`` is a demo you can enable which draws some vector graphics shapes. +Gradient and image fills are not supported yet, as well as dashed strokes. These are +missing from the demo when it is run with the NeoChrom driver. + + TSC Images ********** diff --git a/examples/assets/circle.svg b/examples/assets/circle.svg new file mode 100644 index 0000000000..abc6e157ef --- /dev/null +++ b/examples/assets/circle.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/examples/libs/svg/index.rst b/examples/libs/svg/index.rst index 8b361cb92a..2f0fecb824 100644 --- a/examples/libs/svg/index.rst +++ b/examples/libs/svg/index.rst @@ -1,6 +1,17 @@ Load and render SVG data ---------------------------------------- +------------------------ .. lv_example:: libs/svg/lv_example_svg_1 :language: c +Load and render SVG data from a file +------------------------------------ + +.. lv_example:: libs/svg/lv_example_svg_2 + :language: c + +Load and render SVG data in a draw event +---------------------------------------- + +.. lv_example:: libs/svg/lv_example_svg_3 + :language: c diff --git a/examples/libs/svg/lv_example_svg.h b/examples/libs/svg/lv_example_svg.h index 1af7d0552a..a37b44bc89 100644 --- a/examples/libs/svg/lv_example_svg.h +++ b/examples/libs/svg/lv_example_svg.h @@ -26,6 +26,8 @@ extern "C" { * GLOBAL PROTOTYPES **********************/ void lv_example_svg_1(void); +void lv_example_svg_2(void); +void lv_example_svg_3(void); /********************** * MACROS diff --git a/examples/libs/svg/lv_example_svg_1.c b/examples/libs/svg/lv_example_svg_1.c index c9dcf2e9a7..7af06b64cb 100644 --- a/examples/libs/svg/lv_example_svg_1.c +++ b/examples/libs/svg/lv_example_svg_1.c @@ -3,22 +3,22 @@ #if LV_USE_SVG && LV_USE_VECTOR_GRAPHIC /** - * Load an SVG data + * Load an SVG from data */ -static void event_cb(lv_event_t * e) -{ - static char svg_data[] = "" - ""; - - lv_layer_t * layer = lv_event_get_layer(e); - lv_svg_node_t * svg = lv_svg_load_data(svg_data, sizeof(svg_data) / sizeof(char)); - lv_draw_svg(layer, svg); - lv_svg_node_delete(svg); -} - void lv_example_svg_1(void) { - lv_obj_add_event_cb(lv_screen_active(), event_cb, LV_EVENT_DRAW_MAIN, NULL); + static const char svg_data[] = "" + ""; + + static lv_image_dsc_t svg_dsc; + svg_dsc.header.magic = LV_IMAGE_HEADER_MAGIC; + svg_dsc.header.w = 450; + svg_dsc.header.h = 150; + svg_dsc.data_size = sizeof(svg_data) - 1; + svg_dsc.data = (const uint8_t *) svg_data; + + lv_obj_t * svg = lv_image_create(lv_screen_active()); + lv_image_set_src(svg, &svg_dsc); } #else diff --git a/examples/libs/svg/lv_example_svg_2.c b/examples/libs/svg/lv_example_svg_2.c new file mode 100644 index 0000000000..b4e839512f --- /dev/null +++ b/examples/libs/svg/lv_example_svg_2.c @@ -0,0 +1,26 @@ +#include "../../lv_examples.h" +#if LV_BUILD_EXAMPLES +#if LV_USE_SVG && LV_USE_VECTOR_GRAPHIC + +/** + * Load an SVG from a file + */ +void lv_example_svg_2(void) +{ + lv_obj_t * svg = lv_image_create(lv_screen_active()); + lv_image_set_src(svg, "A:lvgl/examples/assets/circle.svg"); +} +#else + +void lv_example_svg_2(void) +{ + /*TODO + *fallback for online examples*/ + + lv_obj_t * label = lv_label_create(lv_screen_active()); + lv_label_set_text(label, "SVG is not enabled"); + lv_obj_center(label); +} + +#endif +#endif diff --git a/examples/libs/svg/lv_example_svg_3.c b/examples/libs/svg/lv_example_svg_3.c new file mode 100644 index 0000000000..04ab15c659 --- /dev/null +++ b/examples/libs/svg/lv_example_svg_3.c @@ -0,0 +1,36 @@ +#include "../../lv_examples.h" +#if LV_BUILD_EXAMPLES +#if LV_USE_SVG && LV_USE_VECTOR_GRAPHIC + +/** + * Draw SVG data in a draw event + */ +static void event_cb(lv_event_t * e) +{ + static const char svg_data[] = "" + ""; + + lv_layer_t * layer = lv_event_get_layer(e); + lv_svg_node_t * svg = lv_svg_load_data(svg_data, sizeof(svg_data) / sizeof(char)); + lv_draw_svg(layer, svg); + lv_svg_node_delete(svg); +} + +void lv_example_svg_3(void) +{ + lv_obj_add_event_cb(lv_screen_active(), event_cb, LV_EVENT_DRAW_MAIN, NULL); +} +#else + +void lv_example_svg_3(void) +{ + /*TODO + *fallback for online examples*/ + + lv_obj_t * label = lv_label_create(lv_screen_active()); + lv_label_set_text(label, "SVG is not enabled"); + lv_obj_center(label); +} + +#endif +#endif diff --git a/src/draw/lv_draw_vector.c b/src/draw/lv_draw_vector.c index 1d5a7e0486..089554dedb 100644 --- a/src/draw/lv_draw_vector.c +++ b/src/draw/lv_draw_vector.c @@ -12,8 +12,8 @@ #if LV_USE_VECTOR_GRAPHIC -#if !((LV_USE_DRAW_SW && LV_USE_THORVG) || LV_USE_DRAW_VG_LITE) - #error "LV_USE_VECTOR_GRAPHIC requires either (LV_USE_DRAW_SW and LV_USE_THORVG) or LV_USE_DRAW_VG_LITE" +#if !((LV_USE_DRAW_SW && LV_USE_THORVG) || LV_USE_DRAW_VG_LITE || (LV_USE_NEMA_GFX && LV_USE_NEMA_VG)) + #error "LV_USE_VECTOR_GRAPHIC requires (LV_USE_DRAW_SW and LV_USE_THORVG) or LV_USE_DRAW_VG_LITE or (LV_USE_NEMA_GFX and LV_USE_NEMA_VG)" #endif #include "../misc/lv_ll.h" diff --git a/src/draw/nema_gfx/lv_draw_nema_gfx.c b/src/draw/nema_gfx/lv_draw_nema_gfx.c index 44c914c35c..a56420f223 100644 --- a/src/draw/nema_gfx/lv_draw_nema_gfx.c +++ b/src/draw/nema_gfx/lv_draw_nema_gfx.c @@ -243,12 +243,18 @@ static int32_t nema_gfx_evaluate(lv_draw_unit_t * draw_unit, lv_draw_task_t * ta } break; } +#if LV_USE_VECTOR_GRAPHIC && LV_USE_NEMA_VG + case LV_DRAW_TASK_TYPE_VECTOR: { + if(task->preference_score > 80) { + task->preference_score = 80; + task->preferred_draw_unit_id = DRAW_UNIT_ID_NEMA_GFX; + } + return 1; + } +#endif case LV_DRAW_TASK_TYPE_BOX_SHADOW: case LV_DRAW_TASK_TYPE_MASK_RECTANGLE: case LV_DRAW_TASK_TYPE_MASK_BITMAP: -#if LV_USE_VECTOR_GRAPHIC - case LV_DRAW_TASK_TYPE_VECTOR: -#endif default: break; } @@ -328,6 +334,11 @@ static void nema_gfx_execute_drawing(lv_draw_nema_gfx_unit_t * u) case LV_DRAW_TASK_TYPE_BORDER: lv_draw_nema_gfx_border(t, t->draw_dsc, &t->area); break; +#if LV_USE_VECTOR_GRAPHIC && LV_USE_NEMA_VG + case LV_DRAW_TASK_TYPE_VECTOR: + lv_draw_nema_gfx_vector(t, t->draw_dsc, &t->area); + break; +#endif default: break; } diff --git a/src/draw/nema_gfx/lv_draw_nema_gfx.h b/src/draw/nema_gfx/lv_draw_nema_gfx.h index 8197fab28e..5717e5be45 100644 --- a/src/draw/nema_gfx/lv_draw_nema_gfx.h +++ b/src/draw/nema_gfx/lv_draw_nema_gfx.h @@ -110,6 +110,11 @@ void lv_draw_nema_gfx_border(lv_draw_task_t * t, const lv_draw_border_dsc_t * ds void lv_draw_nema_gfx_arc(lv_draw_task_t * t, const lv_draw_arc_dsc_t * dsc, const lv_area_t * coords); +#if LV_USE_VECTOR_GRAPHIC && LV_USE_NEMA_VG +void lv_draw_nema_gfx_vector(lv_draw_task_t * t, const lv_draw_vector_dsc_t * dsc, + const lv_area_t * coords); +#endif + /********************** * MACROS diff --git a/src/draw/nema_gfx/lv_draw_nema_gfx_vector.c b/src/draw/nema_gfx/lv_draw_nema_gfx_vector.c new file mode 100644 index 0000000000..923f514c30 --- /dev/null +++ b/src/draw/nema_gfx/lv_draw_nema_gfx_vector.c @@ -0,0 +1,161 @@ +/** + * @file lv_draw_nema_gfx_vector.c + * + */ + +/********************* + * INCLUDES + *********************/ + +#include "lv_draw_nema_gfx.h" +#if LV_USE_NEMA_GFX && LV_USE_VECTOR_GRAPHIC && LV_USE_NEMA_VG + +#include "lv_nema_gfx_path.h" +#include "../lv_draw_vector_private.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +typedef struct { + lv_draw_nema_gfx_unit_t * u; + float rel_translate_x; + float rel_translate_y; +} ctx_t; + +/********************** + * STATIC PROTOTYPES + **********************/ + +static void task_draw_cb(void * ctx, const lv_vector_path_t * path, const lv_vector_path_ctx_t * dsc); + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +void lv_draw_nema_gfx_vector(lv_draw_task_t * t, const lv_draw_vector_dsc_t * dsc, + const lv_area_t * coords) +{ + ctx_t c; + c.u = (lv_draw_nema_gfx_unit_t *) t->draw_unit; + + lv_layer_t * layer = t->target_layer; + lv_area_t rel_clip_area; + lv_area_copy(&rel_clip_area, &t->clip_area); + lv_area_move(&rel_clip_area, -layer->buf_area.x1, -layer->buf_area.y1); + + c.rel_translate_x = -layer->buf_area.x1; + c.rel_translate_y = -layer->buf_area.y1; + + nema_set_clip(rel_clip_area.x1, rel_clip_area.y1, lv_area_get_width(&rel_clip_area), + lv_area_get_height(&rel_clip_area)); + + lv_color_format_t dst_cf = layer->draw_buf->header.cf; + uint32_t dst_nema_cf = lv_nemagfx_cf_to_nema(dst_cf); + + /* the stride should be computed internally for NEMA_TSC images and images missing a stride value */ + int32_t stride = (dst_cf >= LV_COLOR_FORMAT_NEMA_TSC_START && dst_cf <= LV_COLOR_FORMAT_NEMA_TSC_END) ? + -1 : lv_area_get_width(&(layer->buf_area)) * lv_color_format_get_size(dst_cf); + + nema_bind_dst_tex((uintptr_t)NEMA_VIRT2PHYS(layer->draw_buf->data), lv_area_get_width(&(layer->buf_area)), + lv_area_get_height(&(layer->buf_area)), dst_nema_cf, stride); + + nema_vg_set_blend(NEMA_BL_SRC_OVER | NEMA_BLOP_SRC_PREMULT); + + lv_vector_for_each_destroy_tasks(dsc->task_list, task_draw_cb, &c); +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void task_draw_cb(void * ctx, const lv_vector_path_t * path, const lv_vector_path_ctx_t * dsc) +{ + ctx_t * c = ctx; + lv_draw_nema_gfx_unit_t * u = c->u; + + if(!path) return; + + nema_vg_path_clear(u->path); + nema_vg_paint_clear(u->paint); + + nema_vg_paint_set_type(u->paint, NEMA_VG_PAINT_COLOR); + + lv_matrix_t matrix = dsc->matrix; + matrix.m[0][2] += c->rel_translate_x; + matrix.m[1][2] += c->rel_translate_y; + nema_vg_path_set_matrix(c->u->path, (void *) &matrix); + + /* the path ops array needs to be translated to nema's opcodes */ + lv_vector_path_op_t * ops = lv_array_front(&path->ops); + uint32_t op_count = lv_array_size(&path->ops); + uint8_t * nema_ops = lv_malloc(op_count * sizeof(*nema_ops)); + LV_ASSERT_MALLOC(nema_ops); + for(uint32_t i = 0; i < op_count; i++) { + nema_ops[i] = ops[i] == LV_VECTOR_PATH_OP_MOVE_TO ? NEMA_VG_PRIM_MOVE : + ops[i] == LV_VECTOR_PATH_OP_LINE_TO ? NEMA_VG_PRIM_LINE : + ops[i] == LV_VECTOR_PATH_OP_QUAD_TO ? NEMA_VG_PRIM_BEZIER_QUAD : + ops[i] == LV_VECTOR_PATH_OP_CUBIC_TO ? NEMA_VG_PRIM_BEZIER_CUBIC : + /*LV_VECTOR_PATH_OP_CLOSE*/ NEMA_VG_PRIM_CLOSE; + } + + /* the path points array is in the right format for nema to use as-is */ + uint32_t point_count = lv_array_size(&path->points); + float * points = lv_array_front(&path->points); + + nema_vg_path_set_shape(u->path, op_count, nema_ops, point_count, points); + + nema_vg_set_quality(path->quality == LV_VECTOR_PATH_QUALITY_LOW ? NEMA_VG_QUALITY_FASTER : + path->quality == LV_VECTOR_PATH_QUALITY_MEDIUM ? NEMA_VG_QUALITY_BETTER : + /*LV_VECTOR_PATH_QUALITY_HIGH*/ NEMA_VG_QUALITY_MAXIMUM); + + if(dsc->fill_dsc.opa) { + nema_vg_set_fill_rule(dsc->fill_dsc.fill_rule == LV_VECTOR_FILL_NONZERO ? NEMA_VG_FILL_NON_ZERO : + /*LV_VECTOR_FILL_EVENODD*/ NEMA_VG_FILL_EVEN_ODD); + + uint32_t nema_dsc_color = nema_rgba(dsc->fill_dsc.color.red, dsc->fill_dsc.color.green, + dsc->fill_dsc.color.blue, dsc->fill_dsc.color.alpha); + nema_vg_paint_set_paint_color(u->paint, nema_dsc_color); + + nema_vg_draw_path(u->path, u->paint); + } + + if(dsc->stroke_dsc.opa) { + nema_vg_set_fill_rule(NEMA_VG_STROKE); + nema_vg_stroke_set_width(dsc->stroke_dsc.width); + uint8_t cap = dsc->stroke_dsc.cap == LV_VECTOR_STROKE_CAP_BUTT ? NEMA_VG_CAP_BUTT : + dsc->stroke_dsc.cap == LV_VECTOR_STROKE_CAP_SQUARE ? NEMA_VG_CAP_SQUARE : + /*LV_VECTOR_STROKE_CAP_ROUND*/ NEMA_VG_CAP_ROUND; + nema_vg_stroke_set_cap_style(cap, cap); + uint8_t join = dsc->stroke_dsc.join == LV_VECTOR_STROKE_JOIN_MITER ? NEMA_VG_JOIN_MITER : + dsc->stroke_dsc.join == LV_VECTOR_STROKE_JOIN_BEVEL ? NEMA_VG_JOIN_BEVEL : + /*LV_VECTOR_STROKE_JOIN_ROUND*/ NEMA_VG_JOIN_ROUND; + nema_vg_stroke_set_join_style(join); + nema_vg_stroke_set_miter_limit(dsc->stroke_dsc.miter_limit); + + uint32_t nema_dsc_color = nema_rgba(dsc->stroke_dsc.color.red, dsc->stroke_dsc.color.green, + dsc->stroke_dsc.color.blue, dsc->stroke_dsc.color.alpha); + nema_vg_paint_set_paint_color(u->paint, nema_dsc_color); + + nema_vg_draw_path(u->path, u->paint); + } + + nema_cl_submit(&c->u->cl); + nema_cl_wait(&u->cl); + + lv_free(nema_ops); +} + +#endif /*LV_USE_NEMA_GFX && LV_USE_VECTOR_GRAPHIC && LV_USE_NEMA_VG*/