diff --git a/docs/overview/image.rst b/docs/overview/image.rst index a774cce0a..ea262cc60 100644 --- a/docs/overview/image.rst +++ b/docs/overview/image.rst @@ -244,6 +244,7 @@ open/close the PNG files. It should look like this: lv_image_decoder_t * dec = lv_image_decoder_create(); lv_image_decoder_set_info_cb(dec, decoder_info); lv_image_decoder_set_open_cb(dec, decoder_open); + lv_image_decoder_set_get_area_cb(dec, decoder_get_area); lv_image_decoder_set_close_cb(dec, decoder_close); @@ -280,7 +281,7 @@ open/close the PNG files. It should look like this: /*Check whether the type `src` is known by the decoder*/ if(is_png(dsc->src) == false) return LV_RESULT_INVALID; - /*Decode and store the image. If `dsc->decoded` is `NULL`, the `read_line` function will be called to get the image data line-by-line*/ + /*Decode and store the image. If `dsc->decoded` is `NULL`, the `decoder_get_area` function will be called to get the image data line-by-line*/ dsc->decoded = my_png_decoder(dsc->src); /*Change the color format if decoded image format is different than original format. For PNG it's usually decoded to ARGB8888 format*/ @@ -296,13 +297,58 @@ open/close the PNG files. It should look like this: * Decode an area of image * @param decoder pointer to the decoder where this function belongs * @param dsc image decoder descriptor - * @param full_area full image area information - * @param decoded_area area information to decode (x1, y1, x2, y2) - * @return LV_RESULT_OK: no error; LV_RESULT_INVALID: can't decode image area + * @param full_area input parameter. the full area to decode after enough subsequent calls + * @param decoded_area input+output parameter. set the values to `LV_COORD_MIN` for the first call and to reset decoding. + * the decoded area is stored here after each call. + * @return LV_RESULT_OK: ok; LV_RESULT_INVALID: failed or there is nothing left to decode */ static lv_result_t decoder_get_area(lv_image_decoder_t * decoder, lv_image_decoder_dsc_t * dsc, const lv_area_t * full_area, lv_area_t * decoded_area) { + /** + * If `dsc->decoded` is always set in `decoder_open` then `decoder_get_area` does not need to be implemented. + * If `dsc->decoded` is only sometimes set or never set in `decoder_open` then `decoder_get_area` is used to + * incrementally decode the image by calling it repeatedly until it returns `LV_RESULT_INVALID`. + * In the example below the image is decoded line-by-line but the decoded area can have any shape and size + * depending on the requirements and capabilities of the image decoder. + */ + + my_decoder_data_t * my_decoder_data = dsc->user_data; + + /* if `decoded_area` has a field set to `LV_COORD_MIN` then reset decoding */ + if(decoded_area->y1 == LV_COORD_MIN) { + decoded_area->x1 = full_area->x1; + decoded_area->x2 = full_area->x2; + decoded_area->y1 = full_area->y1; + decoded_area->y2 = decoded_area->y1; /* decode line-by-line, starting with the first line */ + + /* create a draw buf the size of one line */ + bool reshape_success = NULL != lv_draw_buf_reshape(my_decoder_data->partial, + dsc->decoded.header.cf, + lv_area_get_width(full_area), + 1, + LV_STRIDE_AUTO); + if(!reshape_success) { + lv_draw_buf_destroy(my_decoder_data->partial); + my_decoder_data->partial = lv_draw_buf_create(lv_area_get_width(full_area), + 1, + dsc->decoded.header.cf, + LV_STRIDE_AUTO); + + my_png_decode_line_reset(full_area); + } + } + /* otherwise decoding is already in progress. decode the next line */ + else { + /* all lines have already been decoded. indicate completion by returning `LV_RESULT_INVALID` */ + if (decoded_area->y1 >= full_area->y2) return LV_RESULT_INVALID; + decoded_area->y1++; + decoded_area->y2++; + } + + my_png_decode_line(my_decoder_data->partial); + + return LV_RESULT_OK; } /** @@ -314,8 +360,12 @@ open/close the PNG files. It should look like this: static void decoder_close(lv_image_decoder_t * decoder, lv_image_decoder_dsc_t * dsc) { /*Free all allocated data*/ + my_png_cleanup(); - /*Call the built-in close function if the built-in open/read_line was used*/ + my_decoder_data_t * my_decoder_data = dsc->user_data; + lv_draw_buf_destroy(my_decoder_data->partial); + + /*Call the built-in close function if the built-in open/get_area was used*/ lv_bin_decoder_close(decoder, dsc); } diff --git a/examples/libs/barcode/lv_example_barcode_1.c b/examples/libs/barcode/lv_example_barcode_1.c index 4d03a6b44..eea012ce6 100644 --- a/examples/libs/barcode/lv_example_barcode_1.c +++ b/examples/libs/barcode/lv_example_barcode_1.c @@ -19,7 +19,6 @@ void lv_example_barcode_1(void) /*Add a border with bg_color*/ lv_obj_set_style_border_color(barcode, bg_color, 0); - lv_obj_set_style_border_width(barcode, 5, 0); /*Set data*/ lv_barcode_update(barcode, "https://lvgl.io"); diff --git a/src/draw/lv_image_decoder.h b/src/draw/lv_image_decoder.h index 3065b69ee..599671919 100644 --- a/src/draw/lv_image_decoder.h +++ b/src/draw/lv_image_decoder.h @@ -82,15 +82,14 @@ typedef lv_result_t (*lv_image_decoder_info_f_t)(lv_image_decoder_t * decoder, c typedef lv_result_t (*lv_image_decoder_open_f_t)(lv_image_decoder_t * decoder, lv_image_decoder_dsc_t * dsc); /** - * Decode `len` pixels starting from the given `x`, `y` coordinates and store them in `buf`. + * Decode `full_area` pixels incrementally by calling in a loop. Set `decoded_area` values to `LV_COORD_MIN` on first call. * Required only if the "open" function can't return with the whole decoded pixel array. * @param decoder pointer to the decoder the function associated with * @param dsc pointer to decoder descriptor - * @param x start x coordinate - * @param y start y coordinate - * @param len number of pixels to decode - * @param buf a buffer to store the decoded pixels - * @return LV_RESULT_OK: ok; LV_RESULT_INVALID: failed + * @param full_area input parameter. the full area to decode after enough subsequent calls + * @param decoded_area input+output parameter. set the values to `LV_COORD_MIN` for the first call and to reset decoding. + * the decoded area is stored here after each call. + * @return LV_RESULT_OK: ok; LV_RESULT_INVALID: failed or there is nothing left to decode */ typedef lv_result_t (*lv_image_decoder_get_area_cb_t)(lv_image_decoder_t * decoder, lv_image_decoder_dsc_t * dsc, @@ -217,12 +216,13 @@ lv_result_t lv_image_decoder_get_info(const void * src, lv_image_header_t * head */ lv_result_t lv_image_decoder_open(lv_image_decoder_dsc_t * dsc, const void * src, const lv_image_decoder_args_t * args); -/** - * Decode an area of the opened image +/*** + * Decode `full_area` pixels incrementally by calling in a loop. Set `decoded_area` to `LV_COORD_MIN` on first call. * @param dsc image decoder descriptor - * @param full_area start X coordinate (from left) - * @param decoded_area start Y coordinate (from top) - * @return LV_RESULT_OK: success; LV_RESULT_INVALID: an error occurred + * @param full_area input parameter. the full area to decode after enough subsequent calls + * @param decoded_area input+output parameter. set the values to `LV_COORD_MIN` for the first call and to reset decoding. + * the decoded area is stored here after each call. + * @return LV_RESULT_OK: success; LV_RESULT_INVALID: an error occurred or there is nothing left to decode */ lv_result_t lv_image_decoder_get_area(lv_image_decoder_dsc_t * dsc, const lv_area_t * full_area, lv_area_t * decoded_area); diff --git a/src/libs/bin_decoder/lv_bin_decoder.c b/src/libs/bin_decoder/lv_bin_decoder.c index 3df8c4846..2a339a22e 100644 --- a/src/libs/bin_decoder/lv_bin_decoder.c +++ b/src/libs/bin_decoder/lv_bin_decoder.c @@ -363,7 +363,7 @@ void lv_bin_decoder_close(lv_image_decoder_t * decoder, lv_image_decoder_dsc_t * free_decoder_data(dsc); - if(dsc->cache_entry) { + if(dsc->cache && dsc->cache_entry) { /*Decoded data is in cache, release it from cache's callback*/ lv_cache_release(dsc->cache, dsc->cache_entry, NULL); } @@ -407,23 +407,20 @@ lv_result_t lv_bin_decoder_get_area(lv_image_decoder_t * decoder, lv_image_decod /*We only support read line by line for now*/ if(decoded_area->y1 == LV_COORD_MIN) { /*Indexed image is converted to ARGB888*/ - uint32_t len = LV_COLOR_FORMAT_IS_INDEXED(cf) ? sizeof(lv_color32_t) * 8 : bpp; lv_color_format_t cf_decoded = LV_COLOR_FORMAT_IS_INDEXED(cf) ? LV_COLOR_FORMAT_ARGB8888 : cf; - len = (len * w_px) / 8; - decoded = decoder_data->decoded_partial; - if(decoded && decoded->header.w == w_px) { - /*Use existing one directly*/ - } - else { + decoded = lv_draw_buf_reshape(decoder_data->decoded_partial, cf_decoded, w_px, 1, LV_STRIDE_AUTO); + if(decoded == NULL) { + if(decoder_data->decoded_partial != NULL) { + lv_draw_buf_destroy(decoder_data->decoded_partial); + decoder_data->decoded_partial = NULL; + } decoded = lv_draw_buf_create(w_px, 1, cf_decoded, LV_STRIDE_AUTO); - if(decoded == NULL) - return LV_RESULT_INVALID; + if(decoded == NULL) return LV_RESULT_INVALID; + decoder_data->decoded_partial = decoded; /*Free on decoder close*/ } - *decoded_area = *full_area; decoded_area->y2 = decoded_area->y1; - decoder_data->decoded_partial = decoded; /*Free on decoder close*/ } else { decoded_area->y1++; diff --git a/src/libs/bmp/lv_bmp.c b/src/libs/bmp/lv_bmp.c index 3fbb02cb8..44786c6e0 100644 --- a/src/libs/bmp/lv_bmp.c +++ b/src/libs/bmp/lv_bmp.c @@ -200,7 +200,20 @@ static lv_result_t decoder_get_area(lv_image_decoder_t * decoder, lv_image_decod if(decoded_area->y1 == LV_COORD_MIN) { *decoded_area = *full_area; decoded_area->y2 = decoded_area->y1; - if(decoded == NULL) decoded = lv_draw_buf_create(lv_area_get_width(full_area), 1, dsc->header.cf, LV_STRIDE_AUTO); + int32_t w_px = lv_area_get_width(full_area); + lv_draw_buf_t * reshaped = lv_draw_buf_reshape(decoded, dsc->header.cf, w_px, 1, LV_STRIDE_AUTO); + if(reshaped == NULL) { + if(decoded != NULL) { + lv_draw_buf_destroy(decoded); + decoded = NULL; + dsc->decoded = NULL; + } + decoded = lv_draw_buf_create(w_px, 1, dsc->header.cf, LV_STRIDE_AUTO); + if(decoded == NULL) return LV_RESULT_INVALID; + } + else { + decoded = reshaped; + } dsc->decoded = decoded; } else { diff --git a/src/libs/tjpgd/lv_tjpgd.c b/src/libs/tjpgd/lv_tjpgd.c index ac12ce21b..e6f87f705 100644 --- a/src/libs/tjpgd/lv_tjpgd.c +++ b/src/libs/tjpgd/lv_tjpgd.c @@ -216,8 +216,6 @@ static lv_result_t decoder_get_area(lv_image_decoder_t * decoder, lv_image_decod JDEC * jd = dsc->user_data; lv_draw_buf_t * decoded = (void *)dsc->decoded; - if(decoded == NULL) decoded = lv_malloc_zeroed(sizeof(lv_draw_buf_t)); - dsc->decoded = decoded; uint32_t mx, my; mx = jd->msx * 8; @@ -231,6 +229,15 @@ static lv_result_t decoder_get_area(lv_image_decoder_t * decoder, lv_image_decod jd->dcv[2] = jd->dcv[1] = jd->dcv[0] = 0; /* Initialize DC values */ jd->rst = 0; jd->rsc = 0; + if(decoded == NULL) { + decoded = lv_malloc_zeroed(sizeof(lv_draw_buf_t)); + dsc->decoded = decoded; + } + else { + lv_fs_seek(jd->device, 0, LV_FS_SEEK_SET); + JRESULT rc = jd_prepare(jd, input_func, jd->pool_original, (size_t)TJPGD_WORKBUFF_SIZE, jd->device); + if(rc) return rc; + } decoded->data = jd->workbuf; decoded->header = dsc->header; decoded->header.stride = mx * 3; diff --git a/tests/ref_imgs/libs/bin_decoder_1.png b/tests/ref_imgs/libs/bin_decoder_1.png new file mode 100644 index 000000000..7236c16f8 Binary files /dev/null and b/tests/ref_imgs/libs/bin_decoder_1.png differ diff --git a/tests/ref_imgs/libs/bin_decoder_2.png b/tests/ref_imgs/libs/bin_decoder_2.png new file mode 100644 index 000000000..5602c52df Binary files /dev/null and b/tests/ref_imgs/libs/bin_decoder_2.png differ diff --git a/tests/ref_imgs/libs/bin_decoder_3.png b/tests/ref_imgs/libs/bin_decoder_3.png new file mode 100644 index 000000000..077b514d9 Binary files /dev/null and b/tests/ref_imgs/libs/bin_decoder_3.png differ diff --git a/tests/ref_imgs/libs/bin_decoder_4.png b/tests/ref_imgs/libs/bin_decoder_4.png new file mode 100644 index 000000000..8d8917446 Binary files /dev/null and b/tests/ref_imgs/libs/bin_decoder_4.png differ diff --git a/tests/ref_imgs/libs/bmp_1.png b/tests/ref_imgs/libs/bmp_1.png new file mode 100644 index 000000000..6ad03d189 Binary files /dev/null and b/tests/ref_imgs/libs/bmp_1.png differ diff --git a/tests/ref_imgs/libs/bmp_2.png b/tests/ref_imgs/libs/bmp_2.png new file mode 100644 index 000000000..12b099037 Binary files /dev/null and b/tests/ref_imgs/libs/bmp_2.png differ diff --git a/tests/ref_imgs/libs/jpg_3.png b/tests/ref_imgs/libs/jpg_3.png new file mode 100644 index 000000000..246752249 Binary files /dev/null and b/tests/ref_imgs/libs/jpg_3.png differ diff --git a/tests/src/test_assets/test_img_lvgl_logo.bmp b/tests/src/test_assets/test_img_lvgl_logo.bmp new file mode 100644 index 000000000..be93a5ed1 Binary files /dev/null and b/tests/src/test_assets/test_img_lvgl_logo.bmp differ diff --git a/tests/src/test_cases/libs/test_bin_decoder.c b/tests/src/test_cases/libs/test_bin_decoder.c new file mode 100644 index 000000000..55e2ee684 --- /dev/null +++ b/tests/src/test_cases/libs/test_bin_decoder.c @@ -0,0 +1,91 @@ +#if LV_BUILD_TEST +#include "../lvgl.h" + +#include "unity/unity.h" + +void setUp(void) +{ + /* Function run before every test */ +} + +void tearDown(void) +{ + lv_obj_clean(lv_screen_active()); +} + +static void create_image(const void * src) +{ + lv_obj_t * img = lv_image_create(lv_screen_active()); + lv_image_set_src(img, src); + lv_obj_center(img); +} + +static void bin_decoder(const void * src, const char * screenshot) +{ + create_image(src); + TEST_ASSERT_EQUAL_SCREENSHOT(screenshot); + lv_obj_clean(lv_screen_active()); + + size_t mem_before = lv_test_get_free_mem(); + for(uint32_t i = 0; i < 20; i++) { + lv_obj_clean(lv_screen_active()); + create_image(src); + + lv_obj_invalidate(lv_screen_active()); + lv_refr_now(NULL); + } + TEST_ASSERT_EQUAL_SCREENSHOT(screenshot); + lv_obj_clean(lv_screen_active()); + TEST_ASSERT_MEM_LEAK_LESS_THAN(mem_before, 0); +} + +static void create_image_tile(const void * src) +{ + lv_obj_t * img = lv_image_create(lv_screen_active()); + lv_image_set_src(img, src); + lv_obj_center(img); + lv_obj_set_size(img, 275, 175); + lv_image_set_inner_align(img, LV_IMAGE_ALIGN_TILE); +} + +void bin_decoder_tile(const void * src, const char * screenshot) +{ + create_image_tile(src); + TEST_ASSERT_EQUAL_SCREENSHOT(screenshot); + lv_obj_clean(lv_screen_active()); + + size_t mem_before = lv_test_get_free_mem(); + for(uint32_t i = 0; i < 20; i++) { + lv_obj_clean(lv_screen_active()); + create_image_tile(src); + + lv_obj_invalidate(lv_screen_active()); + lv_refr_now(NULL); + } + TEST_ASSERT_EQUAL_SCREENSHOT(screenshot); + lv_obj_clean(lv_screen_active()); + TEST_ASSERT_MEM_LEAK_LESS_THAN(mem_before, 0); +} + +void test_bin_decoder_i4(void) +{ + LV_IMAGE_DECLARE(test_image_cogwheel_i4); + bin_decoder(&test_image_cogwheel_i4, "libs/bin_decoder_1.png"); +} +void test_bin_decoder_i4_tile(void) +{ + LV_IMAGE_DECLARE(test_image_cogwheel_i4); + bin_decoder_tile(&test_image_cogwheel_i4, "libs/bin_decoder_2.png"); +} +void test_bin_decoder_argb8888(void) +{ + LV_IMAGE_DECLARE(test_image_cogwheel_argb8888); + bin_decoder(&test_image_cogwheel_argb8888, "libs/bin_decoder_3.png"); +} +void test_bin_decoder_argb8888_tile(void) +{ + LV_IMAGE_DECLARE(test_image_cogwheel_argb8888); + bin_decoder_tile(&test_image_cogwheel_argb8888, "libs/bin_decoder_4.png"); +} + +#endif diff --git a/tests/src/test_cases/libs/test_bmp.c b/tests/src/test_cases/libs/test_bmp.c new file mode 100644 index 000000000..9e96f5331 --- /dev/null +++ b/tests/src/test_cases/libs/test_bmp.c @@ -0,0 +1,71 @@ +#if LV_BUILD_TEST +#include "../lvgl.h" + +#include "unity/unity.h" +#include "lv_test_helpers.h" + +void setUp(void) +{ + /* Function run before every test */ +} + +void tearDown(void) +{ + lv_obj_clean(lv_screen_active()); +} + +static void create_image(void) +{ + lv_obj_t * img = lv_image_create(lv_screen_active()); + lv_image_set_src(img, "A:src/test_assets/test_img_lvgl_logo.bmp"); + lv_obj_center(img); +} + +void test_bmp(void) +{ + create_image(); + TEST_ASSERT_EQUAL_SCREENSHOT("libs/bmp_1.png"); + lv_obj_clean(lv_screen_active()); + + size_t mem_before = lv_test_get_free_mem(); + for(uint32_t i = 0; i < 20; i++) { + lv_obj_clean(lv_screen_active()); + create_image(); + + lv_obj_invalidate(lv_screen_active()); + lv_refr_now(NULL); + } + TEST_ASSERT_EQUAL_SCREENSHOT("libs/bmp_1.png"); + lv_obj_clean(lv_screen_active()); + TEST_ASSERT_MEM_LEAK_LESS_THAN(mem_before, 0); +} + +static void create_image_tile(void) +{ + lv_obj_t * img = lv_image_create(lv_screen_active()); + lv_image_set_src(img, "A:src/test_assets/test_img_lvgl_logo.bmp"); + lv_obj_center(img); + lv_obj_set_size(img, 300, 200); + lv_image_set_inner_align(img, LV_IMAGE_ALIGN_TILE); +} + +void test_bmp_align_tile(void) +{ + create_image_tile(); + TEST_ASSERT_EQUAL_SCREENSHOT("libs/bmp_2.png"); + lv_obj_clean(lv_screen_active()); + + size_t mem_before = lv_test_get_free_mem(); + for(uint32_t i = 0; i < 20; i++) { + lv_obj_clean(lv_screen_active()); + create_image_tile(); + + lv_obj_invalidate(lv_screen_active()); + lv_refr_now(NULL); + } + TEST_ASSERT_EQUAL_SCREENSHOT("libs/bmp_2.png"); + lv_obj_clean(lv_screen_active()); + TEST_ASSERT_MEM_LEAK_LESS_THAN(mem_before, 0); +} + +#endif diff --git a/tests/src/test_cases/libs/test_tjpgd.c b/tests/src/test_cases/libs/test_tjpgd.c index 5d6f8a0a3..628fb4ec5 100644 --- a/tests/src/test_cases/libs/test_tjpgd.c +++ b/tests/src/test_cases/libs/test_tjpgd.c @@ -11,7 +11,7 @@ void setUp(void) void tearDown(void) { - /* Function run after every test */ + lv_obj_clean(lv_screen_active()); } static void create_images(void) @@ -64,4 +64,39 @@ void test_tjpgd_1(void) lv_libjpeg_turbo_init(); } +static void create_image_2(void) +{ + LV_IMG_DECLARE(test_img_lvgl_logo_jpg); + lv_obj_t * img = lv_image_create(lv_screen_active()); + lv_image_set_src(img, &test_img_lvgl_logo_jpg); + lv_obj_center(img); + lv_obj_set_size(img, 300, 200); + lv_image_set_inner_align(img, LV_IMAGE_ALIGN_TILE); +} + +void test_jdpgd_align_tile(void) +{ + /* Temporarily remove libjpeg_turbo decoder */ + lv_libjpeg_turbo_deinit(); + + create_image_2(); + TEST_ASSERT_EQUAL_SCREENSHOT("libs/jpg_3.png"); + lv_obj_clean(lv_screen_active()); + + size_t mem_before = lv_test_get_free_mem(); + for(uint32_t i = 0; i < 20; i++) { + lv_obj_clean(lv_screen_active()); + create_image_2(); + + lv_obj_invalidate(lv_screen_active()); + lv_refr_now(NULL); + } + TEST_ASSERT_EQUAL_SCREENSHOT("libs/jpg_3.png"); + lv_obj_clean(lv_screen_active()); + TEST_ASSERT_MEM_LEAK_LESS_THAN(mem_before, 0); + + /* Re-add libjpeg_turbo decoder */ + lv_libjpeg_turbo_init(); +} + #endif