From 68f28d1fda1645602a9926b23a4f97ba8f5c7282 Mon Sep 17 00:00:00 2001 From: Gabor Kiss-Vamosi Date: Thu, 20 Jun 2019 18:43:03 +0200 Subject: [PATCH] add lv_img_cache --- lv_conf_template.h | 22 ++++- src/lv_conf_checker.h | 23 ++++- src/lv_core/lv_obj.c | 1 + src/lv_draw/lv_draw_img.c | 36 +++---- src/lv_draw/lv_img_cache.c | 186 +++++++++++++++++++++++++++++++++++++ src/lv_draw/lv_img_cache.h | 82 ++++++++++++++++ 6 files changed, 324 insertions(+), 26 deletions(-) create mode 100644 src/lv_draw/lv_img_cache.c create mode 100644 src/lv_draw/lv_img_cache.h diff --git a/lv_conf_template.h b/lv_conf_template.h index ed0c86412..b933f3b5b 100644 --- a/lv_conf_template.h +++ b/lv_conf_template.h @@ -148,18 +148,34 @@ typedef void * lv_group_user_data_t; typedef void * lv_fs_drv_user_data_t; #endif +/*1: Add a `user_data` to drivers and objects*/ +#define LV_USE_USER_DATA 1 + +/*======================== + * Image decoder and cache + *========================*/ + /* 1: Enable indexed (palette) images */ #define LV_IMG_CF_INDEXED 1 /* 1: Enable alpha indexed images */ #define LV_IMG_CF_ALPHA 1 +/* Default image cache size. Image caching keeps the images opened. + * If only built-in images are used there is no real advantage of caching. + * With complex image decoders (e.g. PNG or JPG) caching can save the continuous open/decode of images. + * However the opened images might consume additional RAM. + * LV_IMG_CACHE_DEF_SIZE must be >= 1 */ +#define LV_IMG_CACHE_DEF_SIZE 1 + +/* If an image wasn't used for this time consider it unused. + * It's more provable that "unused" images will be replaced by other in the cache + * The unit is [ms]*/ +#define LV_IMG_CACHE_DEF_LIFE_TIME 10000 + /*Declare the type of the user data of image decoder (can be e.g. `void *`, `int`, `struct`)*/ typedef void * lv_img_decoder_user_data_t; -/*1: Add a `user_data` to drivers and objects*/ -#define LV_USE_USER_DATA 1 - /*===================== * Compiler settings *====================*/ diff --git a/src/lv_conf_checker.h b/src/lv_conf_checker.h index d37f31389..5e9d1bdb1 100644 --- a/src/lv_conf_checker.h +++ b/src/lv_conf_checker.h @@ -2,7 +2,6 @@ * GENERATED FILE, DO NOT EDIT IT! * @file lv_conf_checker.h * Make sure all the defines of lv_conf.h have a default value - * \internal **/ #ifndef LV_CONF_CHECKER_H @@ -212,6 +211,22 @@ #define LV_IMG_CF_ALPHA 1 #endif +/* Default image cache size. Image caching keeps the images opened. + * If only built-in images are used there is no real advantage of caching. + * With complex image decoders (e.g. PNG or JPG) caching can save the continuous open/decode of images. + * However the opened images might consume additional RAM. + * LV_IMG_CACHE_DEF_SIZE must be >= 1 */ +#ifndef LV_IMG_CACHE_DEF_SIZE +#define LV_IMG_CACHE_DEF_SIZE 1 +#endif + +/* If an image wasn't used for this time consider it unused. + * It's more provable that "unused" images will be replaced by other in the cache + * The unit is [ms]*/ +#ifndef LV_IMG_CACHE_DEF_LIFE_TIME +#define LV_IMG_CACHE_DEF_LIFE_TIME 10000 +#endif + /*Declare the type of the user data of image decoder (can be e.g. `void *`, `int`, `struct`)*/ /*1: Add a `user_data` to drivers and objects*/ @@ -570,6 +585,12 @@ #ifndef LV_USE_PAGE #define LV_USE_PAGE 1 #endif +#if LV_USE_PAGE != 0 +/*Focus default animation time [ms] (0: no animation)*/ +#ifndef LV_PAGE_DEF_ANIM_TIME +# define LV_PAGE_DEF_ANIM_TIME 400 +#endif +#endif /*Preload (dependencies: lv_arc, lv_anim)*/ #ifndef LV_USE_PRELOAD diff --git a/src/lv_core/lv_obj.c b/src/lv_core/lv_obj.c index 4185bd310..84e27aa30 100644 --- a/src/lv_core/lv_obj.c +++ b/src/lv_core/lv_obj.c @@ -109,6 +109,7 @@ void lv_init(void) lv_indev_init(); lv_img_decoder_init(); + lv_img_cache_set_size(LV_IMG_CACHE_DEF_SIZE); lv_initialized = true; LV_LOG_INFO("lv_init ready"); diff --git a/src/lv_draw/lv_draw_img.c b/src/lv_draw/lv_draw_img.c index e17e2c683..d03f8f9f6 100644 --- a/src/lv_draw/lv_draw_img.c +++ b/src/lv_draw/lv_draw_img.c @@ -7,6 +7,7 @@ * INCLUDES *********************/ #include "lv_draw_img.h" +#include "lv_img_cache.h" #include "../lv_misc/lv_log.h" /********************* @@ -445,29 +446,22 @@ static lv_res_t lv_img_draw_core(const lv_area_t * coords, const lv_area_t * mas lv_opa_t opa = opa_scale == LV_OPA_COVER ? style->image.opa : (uint16_t)((uint16_t)style->image.opa * opa_scale) >> 8; - lv_img_header_t header; - lv_res_t header_res; - header_res = lv_img_decoder_get_info(src, &header); - if(header_res != LV_RES_OK) { - LV_LOG_WARN("Image draw can't get image info"); - return LV_RES_INV; - } - bool chroma_keyed = lv_img_color_format_is_chroma_keyed(header.cf); - bool alpha_byte = lv_img_color_format_has_alpha(header.cf); + lv_img_cache_t * cdsc = lv_img_cache_open(src, style); + + if(cdsc == NULL) return LV_RES_INV; + + + bool chroma_keyed = lv_img_color_format_is_chroma_keyed(cdsc->dsc.header.cf); + bool alpha_byte = lv_img_color_format_has_alpha(cdsc->dsc.header.cf); + + - lv_img_decoder_dsc_t dsc; - const uint8_t * img_data = lv_img_decoder_open(&dsc, src, style); - if(img_data == LV_IMG_DECODER_OPEN_FAIL) { - LV_LOG_WARN("Image draw cannot open the image resource"); - lv_img_decoder_close(&dsc); - return LV_RES_INV; - } /* The decoder open could open the image and gave the entire uncompressed image. * Just draw it!*/ - if(img_data) { - lv_draw_map(coords, mask, img_data, opa, chroma_keyed, alpha_byte, style->image.color, style->image.intense); + if(cdsc->img_data) { + lv_draw_map(coords, mask, cdsc->img_data, opa, chroma_keyed, alpha_byte, style->image.color, style->image.intense); } /* The whole uncompressed image is not available. Try to read it line-by-line*/ else { @@ -486,9 +480,9 @@ static lv_res_t lv_img_draw_core(const lv_area_t * coords, const lv_area_t * mas lv_coord_t row; lv_res_t read_res; for(row = mask_com.y1; row <= mask_com.y2; row++) { - read_res = lv_img_decoder_read_line(&dsc, x, y, width, buf); + read_res = lv_img_decoder_read_line(&cdsc->dsc, x, y, width, buf); if(read_res != LV_RES_OK) { - lv_img_decoder_close(&dsc); + lv_img_decoder_close(&cdsc->dsc); LV_LOG_WARN("Image draw can't read the line"); return LV_RES_INV; } @@ -499,7 +493,5 @@ static lv_res_t lv_img_draw_core(const lv_area_t * coords, const lv_area_t * mas } } - lv_img_decoder_close(&dsc); - return LV_RES_OK; } diff --git a/src/lv_draw/lv_img_cache.c b/src/lv_draw/lv_img_cache.c new file mode 100644 index 000000000..b86149295 --- /dev/null +++ b/src/lv_draw/lv_img_cache.c @@ -0,0 +1,186 @@ +/** + * @file lv_img_cache.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_img_cache.h" +#include "../lv_hal/lv_hal_tick.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ + +/********************** + * STATIC VARIABLES + **********************/ +static lv_img_cache_t * img_cache; +static uint32_t life_time = LV_IMG_CACHE_DEF_LIFE_TIME; +static uint16_t slot_num; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/** + * Open an image using the image decoder interface and cache it. + * The image will be left open meaning if the image decoder open callback allocated memory then it will remain. + * The image is closed if a new image is opened and the new image takes its place in the cache. + * @param src source of the image. Path to file or pointer to an `lv_img_dsc_t` variable + * @param style style of the image + * @return pointer to the cache entry or NULL if can open the image + */ +lv_img_cache_t * lv_img_cache_open(const void * src, const lv_style_t * style) +{ + if(slot_num == 0) { + LV_LOG_WARN("lv_img_cache_open: the cache size is 0"); + return NULL; + } + + /*Check if the image is already cached*/ + lv_img_cache_t * cached_dsc = NULL; + lv_img_cache_t * cache_dsc_reuse = NULL; + uint16_t i; + for(i = 0; i < slot_num; i++) { + if(img_cache[i].dsc.src == src) { + cached_dsc = &img_cache[i]; + LV_LOG_TRACE("image draw: image found in the cache"); + break; + } + /*Meanwhile check for a reusable slot in the cache. It will be required if `src` is not cached*/ + else { + + /*If there is no idea for `cache_dsc_reuse` then let's use this*/ + if(cache_dsc_reuse == NULL) { + cache_dsc_reuse = &img_cache[i]; + } + + /*There won't be better option then an empty slot so keep it.*/ + if(cache_dsc_reuse->dsc.src == NULL) continue; + + /*If its an empty slot then its the best choice to use*/ + if(img_cache[i].dsc.src == NULL) { + cache_dsc_reuse = &img_cache[i]; + } + /*If this image wasn't used for while then reuse it*/ + else if(lv_tick_elaps(img_cache[i].timestamp) > life_time) { + cache_dsc_reuse = &img_cache[i]; + } + /* Drop the image which is the fastest to reopen */ + else if(img_cache[i].open_duration < cache_dsc_reuse->open_duration) { + cache_dsc_reuse = &img_cache[i]; + } + } + } + + /*The image is not cached then cache it now*/ + if(cached_dsc == NULL) { + /*Close the slot to reuse if it was opened (has a valid source)*/ + if(cache_dsc_reuse->dsc.src) { + lv_img_decoder_close(&cache_dsc_reuse->dsc); + LV_LOG_INFO("image draw: cache miss, close and reuse a slot"); + } else { + LV_LOG_INFO("image draw: cache miss, cached in empty slot"); + } + + /*Open the image and measure the time to open*/ + uint32_t t_start; + t_start = lv_tick_get(); + const uint8_t * img_data = lv_img_decoder_open(&cache_dsc_reuse->dsc, src, style); + if(img_data == LV_IMG_DECODER_OPEN_FAIL) { + LV_LOG_WARN("Image draw cannot open the image resource"); + lv_img_decoder_close(&cache_dsc_reuse->dsc); + memset(&cache_dsc_reuse->dsc, 0, sizeof(lv_img_decoder_dsc_t)); + memset(&cache_dsc_reuse, 0, sizeof(lv_img_cache_t)); + return NULL; + } + + cached_dsc = cache_dsc_reuse; + cached_dsc->img_data = img_data; + cached_dsc->open_duration = lv_tick_elaps(t_start); + } + + cached_dsc->timestamp = lv_tick_get(); + + return cached_dsc; +} + +/** + * Set the number of images to be cached. + * More cached images mean more opened image at same time which might mean more memory usage. + * E.g. if 20 PNG or JPG images are open in the RAM they consume memory while opened in the cache. + * @param new_slot_num number of image to cache + */ +void lv_img_cache_set_size(uint16_t new_slot_num) +{ + if(img_cache != NULL) { + /*Clean the cache before free it*/ + lv_img_cache_invalidate_src(NULL); + lv_mem_free(img_cache); + } + + /*Reallocate the cache*/ + img_cache = lv_mem_alloc(sizeof(lv_img_cache_t) * new_slot_num); + lv_mem_assert(img_cache); + if(img_cache == NULL) { + slot_num = 0; + return; + } + slot_num = new_slot_num; + + /*Clean the cache*/ + uint16_t i; + for(i = 0; i < slot_num; i++) { + memset(&img_cache[i].dsc, 0, sizeof(lv_img_decoder_dsc_t)); + memset(&img_cache[i], 0, sizeof(lv_img_cache_t)); + } +} + + +/** + * Set a life time for the cache entries. + * After this time a cached image is considered unused and it's more probable the it will be replaced by a new image. + * @param new_life_time the new life time in milliseconds + */ +void lv_img_cache_set_life_time(uint32_t new_life_time) +{ + life_time = new_life_time; +} + +/** + * Invalidate an image source in the cache. + * Useful if the image source is updated therefore it needs to be cached again. + * @param src an image source path to a file or pointer to an `lv_img_dsc_t` variable. + */ +void lv_img_cache_invalidate_src(const void * src) +{ + uint16_t i; + for(i = 0; i < slot_num; i++) { + if(img_cache[i].dsc.src == src || src == NULL) { + if(img_cache[i].dsc.src != NULL) { + lv_img_decoder_close(&img_cache[i].dsc); + } + + memset(&img_cache[i].dsc, 0, sizeof(lv_img_decoder_dsc_t)); + memset(&img_cache[i], 0, sizeof(lv_img_cache_t)); + } + } +} + +/********************** + * STATIC FUNCTIONS + **********************/ diff --git a/src/lv_draw/lv_img_cache.h b/src/lv_draw/lv_img_cache.h new file mode 100644 index 000000000..cece7c8c6 --- /dev/null +++ b/src/lv_draw/lv_img_cache.h @@ -0,0 +1,82 @@ +/** + * @file lv_img_cache.h + * + */ + +#ifndef LV_CAHCE_H +#define LV_CACHE_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "lv_img_decoder.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ +typedef struct +{ + lv_img_decoder_dsc_t dsc; + const uint8_t * img_data; + + /* How much time did it take to open the image? + * When out of cache close the images which are faster to reopen*/ + uint32_t open_duration; + + /*Time stamp when the image was last used*/ + uint32_t timestamp; +}lv_img_cache_t; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Open an image using the image decoder interface and cache it. + * The image will be left open meaning if the image decoder open callback allocated memory then it will remain. + * The image is closed if a new image is opened and the new image takes its place in the cache. + * @param src source of the image. Path to file or pointer to an `lv_img_dsc_t` variable + * @param style style of the image + * @return pointer to the cache entry or NULL if can open the image + */ +lv_img_cache_t * lv_img_cache_open(const void * src, const lv_style_t * style); + +/** + * Set the number of images to be cached. + * More cached images mean more opened image at same time which might mean more memory usage. + * E.g. if 20 PNG or JPG images are open in the RAM they consume memory while opened in the cache. + * @param new_slot_num number of image to cache + */ +void lv_img_cache_set_size(uint16_t new_slot_num); + +/** + * Set a life time for the cache entries. + * After this time a cached image is considered unused and it's more probable the it will be replaced by a new image. + * @param new_life_time the new life time in milliseconds + */ +void lv_img_cache_set_life_time(uint32_t new_life_time); + +/** + * Invalidate an image source in the cache. + * Useful if the image source is updated therefore it needs to be cached again. + * @param src an image source path to a file or pointer to an `lv_img_dsc_t` variable. + */ +void lv_img_cache_invalidate_src(const void * src); + +/********************** + * MACROS + **********************/ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /*LV_CACHE_H*/