diff --git a/Kconfig b/Kconfig index 8705894da..7fd6b5527 100644 --- a/Kconfig +++ b/Kconfig @@ -1011,6 +1011,9 @@ menu "LVGL configuration" config LV_USE_LODEPNG bool "PNG decoder library" + config LV_USE_LIBPNG + bool "PNG decoder(libpng) library" + config LV_USE_BMP bool "BMP decoder library" diff --git a/docs/libs/index.rst b/docs/libs/index.rst index eea3a9034..b92201e3a 100644 --- a/docs/libs/index.rst +++ b/docs/libs/index.rst @@ -11,6 +11,7 @@ tjpgd libjpeg_turbo lodepng + libpng gif freetype tiny_ttf diff --git a/docs/libs/libpng.rst b/docs/libs/libpng.rst new file mode 100644 index 000000000..b8bb0d0ec --- /dev/null +++ b/docs/libs/libpng.rst @@ -0,0 +1,42 @@ +============== +libpng decoder +============== + +libpng is the official PNG reference library. It supports almost all PNG features, is extensible, and has been extensively tested for over 28 years. +Detailed introduction: `libpng `__. + +Install +------- + +.. code:: bash + + sudo apt install libpng-dev + +Add libpng to your project +-------------------------- + +.. code:: cmake + + find_package(PNG REQUIRED) + include_directories(${PNG_INCLUDE_DIR}) + target_link_libraries(${PROJECT_NAME} PRIVATE ${PNG_LIBRARIES}) + +Usage +----- + +Enable :c:macro:`LV_USE_LIBPNG` in ``lv_conf.h``. + +See the examples below. +It should be noted that each image of this decoder needs to consume ``image width x image height x 4`` bytes of RAM, +and it needs to be combined with the ref:`image-caching` feature to ensure that the memory usage is within a reasonable range. + +Example +------- + +.. include:: ../examples/libs/libpng/index.rst + +API +--- + +:ref:`libpng` + diff --git a/examples/libs/libpng/index.rst b/examples/libs/libpng/index.rst new file mode 100644 index 000000000..4604fa7c9 --- /dev/null +++ b/examples/libs/libpng/index.rst @@ -0,0 +1,6 @@ +Open a PNG image from file +-------------------------- + +.. lv_example:: libs/libpng/lv_example_libpng_1 + :language: c + diff --git a/examples/libs/libpng/lv_example_libpng.h b/examples/libs/libpng/lv_example_libpng.h new file mode 100644 index 000000000..f41004ca0 --- /dev/null +++ b/examples/libs/libpng/lv_example_libpng.h @@ -0,0 +1,38 @@ +/** + * @file lv_example_libpng.h + * + */ + +#ifndef LV_EXAMPLE_LIBPNG_H +#define LV_EXAMPLE_LIBPNG_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ +void lv_example_libpng_1(void); + +/********************** + * MACROS + **********************/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_EXAMPLE_LIBPNG_H*/ diff --git a/examples/libs/libpng/lv_example_libpng_1.c b/examples/libs/libpng/lv_example_libpng_1.c new file mode 100644 index 000000000..cd1a3214b --- /dev/null +++ b/examples/libs/libpng/lv_example_libpng_1.c @@ -0,0 +1,31 @@ +#include "../../lv_examples.h" +#if LV_BUILD_EXAMPLES + +#if LV_USE_LIBPNG + +/** + * Open a PNG image from a file + */ +void lv_example_libpng_1(void) +{ + lv_obj_t * img; + + img = lv_image_create(lv_scr_act()); + /* Assuming a File system is attached to letter 'A' + * E.g. set LV_USE_FS_STDIO 'A' in lv_conf.h */ + lv_image_set_src(img, "A:lvgl/examples/libs/libpng/png_demo.png"); + lv_obj_center(img); +} + +#else + +void lv_example_libpng_1(void) +{ + lv_obj_t * label = lv_label_create(lv_scr_act()); + lv_label_set_text(label, "LibPNG is not installed"); + lv_obj_center(label); +} + +#endif + +#endif diff --git a/examples/libs/libpng/lv_example_libpng_1.py b/examples/libs/libpng/lv_example_libpng_1.py new file mode 100755 index 000000000..8103f2590 --- /dev/null +++ b/examples/libs/libpng/lv_example_libpng_1.py @@ -0,0 +1,11 @@ +#!/opt/bin/lv_micropython -i +import lvgl as lv +import display_driver +import fs_driver + +fs_drv = lv.fs_drv_t() +fs_driver.fs_register(fs_drv, 'S') + +image = lv.image(lv.scr_act()) +image.set_src("S:png_demo.png") +image.center() diff --git a/examples/libs/libpng/png_demo.png b/examples/libs/libpng/png_demo.png new file mode 100644 index 000000000..d8dbbb78e Binary files /dev/null and b/examples/libs/libpng/png_demo.png differ diff --git a/examples/libs/lv_example_libs.h b/examples/libs/lv_example_libs.h index 2a7b60c11..ae8dcb921 100644 --- a/examples/libs/lv_example_libs.h +++ b/examples/libs/lv_example_libs.h @@ -19,6 +19,7 @@ extern "C" { #include "freetype/lv_example_freetype.h" #include "gif/lv_example_gif.h" #include "lodepng/lv_example_lodepng.h" +#include "libpng/lv_example_libpng.h" #include "qrcode/lv_example_qrcode.h" #include "rlottie/lv_example_rlottie.h" #include "tjpgd/lv_example_tjpgd.h" diff --git a/lv_conf_template.h b/lv_conf_template.h index c513f7c76..83aaf2e84 100644 --- a/lv_conf_template.h +++ b/lv_conf_template.h @@ -579,6 +579,9 @@ /*LODEPNG decoder library*/ #define LV_USE_LODEPNG 0 +/*PNG decoder(libpng) library*/ +#define LV_USE_LIBPNG 0 + /*BMP decoder library*/ #define LV_USE_BMP 0 diff --git a/lvgl.h b/lvgl.h index 36321440b..3023b5dab 100644 --- a/lvgl.h +++ b/lvgl.h @@ -93,6 +93,7 @@ extern "C" { #include "src/libs/bmp/lv_bmp.h" #include "src/libs/fsdrv/lv_fsdrv.h" #include "src/libs/lodepng/lv_lodepng.h" +#include "src/libs/libpng/lv_libpng.h" #include "src/libs/gif/lv_gif.h" #include "src/libs/qrcode/lv_qrcode.h" #include "src/libs/tjpgd/lv_tjpgd.h" diff --git a/src/libs/libpng/lv_libpng.c b/src/libs/libpng/lv_libpng.c new file mode 100644 index 000000000..2627b505e --- /dev/null +++ b/src/libs/libpng/lv_libpng.c @@ -0,0 +1,304 @@ +/** + * @file lv_libpng.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "../../../lvgl.h" +#if LV_USE_LIBPNG + +#include "lv_libpng.h" +#include + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static lv_result_t decoder_info(lv_image_decoder_t * decoder, const void * src, lv_image_header_t * header); +static lv_result_t decoder_open(lv_image_decoder_t * decoder, lv_image_decoder_dsc_t * dsc); +static void decoder_close(lv_image_decoder_t * decoder, lv_image_decoder_dsc_t * dsc); +static const void * decode_png_file(const char * filename); +static lv_result_t try_cache(lv_image_decoder_dsc_t * dsc); + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/** + * Register the PNG decoder functions in LVGL + */ +void lv_libpng_init(void) +{ + 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_close_cb(dec, decoder_close); +} + +void lv_libpng_deinit(void) +{ + lv_image_decoder_t * dec = NULL; + while((dec = lv_image_decoder_get_next(dec)) != NULL) { + if(dec->info_cb == decoder_info) { + lv_image_decoder_delete(dec); + break; + } + } +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +/** + * Get info about a PNG image + * @param src can be file name or pointer to a C array + * @param header store the info here + * @return LV_RESULT_OK: no error; LV_RESULT_INVALID: can't get the info + */ +static lv_result_t decoder_info(lv_image_decoder_t * decoder, const void * src, lv_image_header_t * header) +{ + LV_UNUSED(decoder); /*Unused*/ + lv_image_src_t src_type = lv_image_src_get_type(src); /*Get the source type*/ + + /*If it's a PNG file...*/ + if(src_type == LV_IMAGE_SRC_FILE) { + const char * fn = src; + + lv_fs_file_t f; + lv_fs_res_t res = lv_fs_open(&f, fn, LV_FS_MODE_RD); + if(res != LV_FS_RES_OK) return LV_RESULT_INVALID; + + /* Read the width and height from the file. They have a constant location: + * [16..19]: width + * [20..23]: height + */ + uint8_t buf[24]; + uint32_t rn; + lv_fs_read(&f, buf, sizeof(buf), &rn); + lv_fs_close(&f); + + if(rn != sizeof(buf)) return LV_RESULT_INVALID; + + const uint8_t magic[] = {0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a}; + if(memcmp(buf, magic, sizeof(magic)) != 0) return LV_RESULT_INVALID; + + uint32_t * size = (uint32_t *)&buf[16]; + /*Save the data in the header*/ + header->always_zero = 0; + header->cf = LV_COLOR_FORMAT_ARGB8888; + /*The width and height are stored in Big endian format so convert them to little endian*/ + header->w = (lv_coord_t)((size[0] & 0xff000000) >> 24) + ((size[0] & 0x00ff0000) >> 8); + header->h = (lv_coord_t)((size[1] & 0xff000000) >> 24) + ((size[1] & 0x00ff0000) >> 8); + + return LV_RESULT_OK; + } + + return LV_RESULT_INVALID; /*If didn't succeeded earlier then it's an error*/ +} + + +/** + * Open a PNG image and return the decided image + * @param src can be file name or pointer to a C array + * @param style style of the image object (unused now but certain formats might use it) + * @return pointer to the decoded image or `LV_IMAGE_DECODER_OPEN_FAIL` if failed + */ +static lv_result_t decoder_open(lv_image_decoder_t * decoder, lv_image_decoder_dsc_t * dsc) +{ + LV_UNUSED(decoder); /*Unused*/ + + /*Check the cache first*/ + if(try_cache(dsc) == LV_RESULT_OK) return LV_RESULT_OK; + + /*If it's a PNG file...*/ + if(dsc->src_type == LV_IMAGE_SRC_FILE) { + const char * fn = dsc->src; + lv_cache_lock(); + lv_cache_entry_t * cache = lv_cache_add(dsc->header.w * dsc->header.h * sizeof(uint32_t)); + if(cache == NULL) { + lv_cache_unlock(); + return LV_RESULT_INVALID; + } + + uint32_t t = lv_tick_get(); + const void * decoded_img = decode_png_file(fn); + t = lv_tick_elaps(t); + cache->weight = t; + cache->data = decoded_img; + cache->free_data = 1; + if(dsc->src_type == LV_IMAGE_SRC_FILE) { + cache->src = lv_strdup(dsc->src); + cache->src_type = LV_CACHE_SRC_TYPE_STR; + cache->free_src = 1; + } + else { + cache->src_type = LV_CACHE_SRC_TYPE_PTR; + cache->src = dsc->src; + } + + dsc->img_data = lv_cache_get_data(cache); + dsc->user_data = cache; + + lv_cache_unlock(); + return LV_RESULT_OK; /*The image is fully decoded. Return with its pointer*/ + } + + return LV_RESULT_INVALID; /*If not returned earlier then it failed*/ +} + +/** + * Free the allocated resources + */ +static void decoder_close(lv_image_decoder_t * decoder, lv_image_decoder_dsc_t * dsc) +{ + LV_UNUSED(decoder); /*Unused*/ + + lv_cache_lock(); + lv_cache_release(dsc->user_data); + lv_cache_unlock(); +} + +static lv_result_t try_cache(lv_image_decoder_dsc_t * dsc) +{ + lv_cache_lock(); + if(dsc->src_type == LV_IMAGE_SRC_FILE) { + const char * fn = dsc->src; + + lv_cache_entry_t * cache = lv_cache_find(fn, LV_CACHE_SRC_TYPE_STR, 0, 0); + if(cache) { + dsc->img_data = lv_cache_get_data(cache); + dsc->user_data = cache; /*Save the cache to release it in decoder_close*/ + lv_cache_unlock(); + return LV_RESULT_OK; + } + } + + lv_cache_unlock(); + return LV_RESULT_INVALID; +} + +static uint8_t * alloc_file(const char * filename, uint32_t * size) +{ + uint8_t * data = NULL; + lv_fs_file_t f; + uint32_t data_size; + uint32_t rn; + lv_fs_res_t res; + + *size = 0; + + res = lv_fs_open(&f, filename, LV_FS_MODE_RD); + if(res != LV_FS_RES_OK) { + LV_LOG_WARN("can't open %s", filename); + return NULL; + } + + res = lv_fs_seek(&f, 0, LV_FS_SEEK_END); + if(res != LV_FS_RES_OK) { + goto failed; + } + + res = lv_fs_tell(&f, &data_size); + if(res != LV_FS_RES_OK) { + goto failed; + } + + res = lv_fs_seek(&f, 0, LV_FS_SEEK_SET); + if(res != LV_FS_RES_OK) { + goto failed; + } + + /*Read file to buffer*/ + data = lv_malloc(data_size); + if(data == NULL) { + LV_LOG_WARN("malloc failed for data"); + goto failed; + } + + res = lv_fs_read(&f, data, data_size, &rn); + + if(res == LV_FS_RES_OK && rn == data_size) { + *size = rn; + } + else { + LV_LOG_WARN("read file failed"); + lv_free(data); + data = NULL; + } + +failed: + lv_fs_close(&f); + + return data; +} + +static const void * decode_png_file(const char * filename) +{ + int ret; + + /*Prepare png_image*/ + png_image image; + lv_memzero(&image, sizeof(image)); + image.version = PNG_IMAGE_VERSION; + + uint32_t data_size; + uint8_t * data = alloc_file(filename, &data_size); + if(data == NULL) { + LV_LOG_WARN("can't load file: %s", filename); + return NULL; + } + + /*Ready to read file*/ + ret = png_image_begin_read_from_memory(&image, data, data_size); + if(!ret) { + LV_LOG_ERROR("png file: %s read failed: %d", filename, ret); + lv_free(data); + return NULL; + } + + /*Set color format*/ + image.format = PNG_FORMAT_BGRA; + + /*Alloc image buffer*/ + size_t image_size = PNG_IMAGE_SIZE(image); + void * image_data = lv_draw_buf_malloc(image_size, LV_COLOR_FORMAT_ARGB8888); + + if(image_data) { + /*Start decoding*/ + ret = png_image_finish_read(&image, NULL, image_data, 0, NULL); + if(!ret) { + LV_LOG_ERROR("png decode failed: %d", ret); + lv_draw_buf_free(image_data); + image_data = NULL; + } + } + else { + LV_LOG_ERROR("png alloc %zu failed", image_size); + } + + /*free decoder*/ + png_image_free(&image); + lv_free(data); + + return image_data; +} + +#endif /*LV_USE_LIBPNG*/ diff --git a/src/libs/libpng/lv_libpng.h b/src/libs/libpng/lv_libpng.h new file mode 100644 index 000000000..12794585d --- /dev/null +++ b/src/libs/libpng/lv_libpng.h @@ -0,0 +1,48 @@ +/** + * @file lv_libpng.h + * + */ + +#ifndef LV_LIBPNG_H +#define LV_LIBPNG_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../lv_conf_internal.h" +#if LV_USE_LIBPNG + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Register the PNG decoder functions in LVGL + */ +void lv_libpng_init(void); + +void lv_libpng_deinit(void); + +/********************** + * MACROS + **********************/ + +#endif /*LV_USE_LIBPNG*/ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /*LV_LIBPNG_H*/ diff --git a/src/lv_conf_internal.h b/src/lv_conf_internal.h index ba6efedd6..fd8b478d5 100644 --- a/src/lv_conf_internal.h +++ b/src/lv_conf_internal.h @@ -1943,6 +1943,15 @@ #endif #endif +/*PNG decoder(libpng) library*/ +#ifndef LV_USE_LIBPNG + #ifdef CONFIG_LV_USE_LIBPNG + #define LV_USE_LIBPNG CONFIG_LV_USE_LIBPNG + #else + #define LV_USE_LIBPNG 0 + #endif +#endif + /*BMP decoder library*/ #ifndef LV_USE_BMP #ifdef CONFIG_LV_USE_BMP diff --git a/src/lv_init.c b/src/lv_init.c index 7daa29b3b..1f6de44eb 100644 --- a/src/lv_init.c +++ b/src/lv_init.c @@ -19,6 +19,7 @@ #include "libs/tjpgd/lv_tjpgd.h" #include "libs/libjpeg_turbo/lv_libjpeg_turbo.h" #include "libs/lodepng/lv_lodepng.h" +#include "libs/libpng/lv_libpng.h" #include "draw/lv_draw.h" #include "misc/lv_cache.h" #include "misc/lv_cache_builtin.h" @@ -234,6 +235,10 @@ void lv_init(void) lv_lodepng_init(); #endif +#if LV_USE_LIBPNG + lv_libpng_init(); +#endif + #if LV_USE_TJPGD lv_tjpgd_init(); #endif diff --git a/tests/ref_imgs/libs/png_2.png b/tests/ref_imgs/libs/png_2.png new file mode 100644 index 000000000..6ad03d189 Binary files /dev/null and b/tests/ref_imgs/libs/png_2.png differ diff --git a/tests/src/lv_test_conf_full.h b/tests/src/lv_test_conf_full.h index 29485e8d4..eff31a1a3 100644 --- a/tests/src/lv_test_conf_full.h +++ b/tests/src/lv_test_conf_full.h @@ -62,6 +62,7 @@ #define LV_FS_MEMFS_LETTER 'M' #define LV_USE_LODEPNG 1 +#define LV_USE_LIBPNG 1 #define LV_USE_BMP 1 #define LV_USE_TJPGD 1 #define LV_USE_LIBJPEG_TURBO 1 diff --git a/tests/src/test_cases/libs/test_libpng.c b/tests/src/test_cases/libs/test_libpng.c new file mode 100644 index 000000000..b64fc2a44 --- /dev/null +++ b/tests/src/test_cases/libs/test_libpng.c @@ -0,0 +1,54 @@ +#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) +{ + /* Function run after every test */ +} + +static void create_images(void) +{ + lv_obj_clean(lv_scr_act()); + + lv_obj_t * img; + + img = lv_img_create(lv_scr_act()); + lv_img_set_src(img, "A:src/test_assets/test_img_lvgl_logo.png"); + lv_obj_center(img); +} + +void test_libpng_1(void) +{ + /* Temporarily remove lodepng decoder */ + lv_lodepng_deinit(); + + create_images(); + + TEST_ASSERT_EQUAL_SCREENSHOT("libs/png_2.png"); + + + uint32_t mem_before = lv_test_get_free_mem(); + for(uint32_t i = 0; i < 20; i++) { + create_images(); + + lv_obj_invalidate(lv_scr_act()); + lv_refr_now(NULL); + } + + TEST_ASSERT_EQUAL_SCREENSHOT("libs/png_2.png"); + + TEST_ASSERT_EQUAL(mem_before, lv_test_get_free_mem()); + + /* Re-add lodepng decoder */ + lv_lodepng_init(); +} + +#endif diff --git a/tests/src/test_cases/libs/test_lodepng.c b/tests/src/test_cases/libs/test_lodepng.c index 4fe88a845..d5c391e56 100644 --- a/tests/src/test_cases/libs/test_lodepng.c +++ b/tests/src/test_cases/libs/test_lodepng.c @@ -41,6 +41,9 @@ static void create_images(void) void test_lodepng_1(void) { + /* Temporarily remove libpng decoder */ + lv_libpng_deinit(); + create_images(); TEST_ASSERT_EQUAL_SCREENSHOT("libs/png_1.png"); @@ -58,6 +61,8 @@ void test_lodepng_1(void) TEST_ASSERT_EQUAL(mem_before, lv_test_get_free_mem()); + /* Re-add libpng decoder */ + lv_libpng_init(); } #endif