1 /**
2  * @file lv_img_cache.c
3  *
4  */
5 
6 /*********************
7  *      INCLUDES
8  *********************/
9 #include "../lv_misc/lv_debug.h"
10 #include "lv_img_cache.h"
11 #include "lv_img_decoder.h"
12 #include "lv_draw_img.h"
13 #include "../lv_hal/lv_hal_tick.h"
14 #include "../lv_misc/lv_gc.h"
15 
16 #if defined(LV_GC_INCLUDE)
17     #include LV_GC_INCLUDE
18 #endif /* LV_ENABLE_GC */
19 /*********************
20  *      DEFINES
21  *********************/
22 /*Decrement life with this value in every open*/
23 #define LV_IMG_CACHE_AGING 1
24 
25 /*Boost life by this factor (multiply time_to_open with this value)*/
26 #define LV_IMG_CACHE_LIFE_GAIN 1
27 
28 /*Don't let life to be greater than this limit because it would require a lot of time to
29  * "die" from very high values */
30 #define LV_IMG_CACHE_LIFE_LIMIT 1000
31 
32 #if LV_IMG_CACHE_DEF_SIZE < 1
33     #error "LV_IMG_CACHE_DEF_SIZE must be >= 1. See lv_conf.h"
34 #endif
35 
36 /**********************
37  *      TYPEDEFS
38  **********************/
39 
40 /**********************
41  *  STATIC PROTOTYPES
42  **********************/
43 
44 /**********************
45  *  STATIC VARIABLES
46  **********************/
47 static uint16_t entry_cnt;
48 
49 /**********************
50  *      MACROS
51  **********************/
52 
53 /**********************
54  *   GLOBAL FUNCTIONS
55  **********************/
56 
57 /**
58  * Open an image using the image decoder interface and cache it.
59  * The image will be left open meaning if the image decoder open callback allocated memory then it will remain.
60  * The image is closed if a new image is opened and the new image takes its place in the cache.
61  * @param src source of the image. Path to file or pointer to an `lv_img_dsc_t` variable
62  * @param color color The color of the image with `LV_IMG_CF_ALPHA_...`
63  * @return pointer to the cache entry or NULL if can open the image
64  */
_lv_img_cache_open(const void * src,lv_color_t color)65 lv_img_cache_entry_t * _lv_img_cache_open(const void * src, lv_color_t color)
66 {
67     if(entry_cnt == 0) {
68         LV_LOG_WARN("lv_img_cache_open: the cache size is 0");
69         return NULL;
70     }
71 
72     lv_img_cache_entry_t * cache = LV_GC_ROOT(_lv_img_cache_array);
73 
74     /*Decrement all lifes. Make the entries older*/
75     uint16_t i;
76     for(i = 0; i < entry_cnt; i++) {
77         if(cache[i].life > INT32_MIN + LV_IMG_CACHE_AGING) {
78             cache[i].life -= LV_IMG_CACHE_AGING;
79         }
80     }
81 
82     /*Is the image cached?*/
83     lv_img_cache_entry_t * cached_src = NULL;
84     for(i = 0; i < entry_cnt; i++) {
85         bool match = false;
86         lv_img_src_t src_type = lv_img_src_get_type(cache[i].dec_dsc.src);
87         if(src_type == LV_IMG_SRC_VARIABLE) {
88             if(cache[i].dec_dsc.src == src && cache[i].dec_dsc.color.full == color.full) match = true;
89         }
90         else if(src_type == LV_IMG_SRC_FILE) {
91             if(strcmp(cache[i].dec_dsc.src, src) == 0) match = true;
92         }
93 
94         if(match) {
95             /* If opened increment its life.
96              * Image difficult to open should live longer to keep avoid frequent their recaching.
97              * Therefore increase `life` with `time_to_open`*/
98             cached_src = &cache[i];
99             cached_src->life += cached_src->dec_dsc.time_to_open * LV_IMG_CACHE_LIFE_GAIN;
100             if(cached_src->life > LV_IMG_CACHE_LIFE_LIMIT) cached_src->life = LV_IMG_CACHE_LIFE_LIMIT;
101             LV_LOG_TRACE("image draw: image found in the cache");
102             break;
103         }
104     }
105 
106     /*The image is not cached then cache it now*/
107     if(cached_src == NULL) {
108         /*Find an entry to reuse. Select the entry with the least life*/
109         cached_src = &cache[0];
110         for(i = 1; i < entry_cnt; i++) {
111             if(cache[i].life < cached_src->life) {
112                 cached_src = &cache[i];
113             }
114         }
115 
116         /*Close the decoder to reuse if it was opened (has a valid source)*/
117         if(cached_src->dec_dsc.src) {
118             lv_img_decoder_close(&cached_src->dec_dsc);
119             LV_LOG_INFO("image draw: cache miss, close and reuse an entry");
120         }
121         else {
122             LV_LOG_INFO("image draw: cache miss, cached to an empty entry");
123         }
124 
125         /*Open the image and measure the time to open*/
126         uint32_t t_start;
127         t_start                          = lv_tick_get();
128         cached_src->dec_dsc.time_to_open = 0;
129         lv_res_t open_res                = lv_img_decoder_open(&cached_src->dec_dsc, src, color);
130         if(open_res == LV_RES_INV) {
131             LV_LOG_WARN("Image draw cannot open the image resource");
132             lv_img_decoder_close(&cached_src->dec_dsc);
133             _lv_memset_00(&cached_src->dec_dsc, sizeof(lv_img_decoder_dsc_t));
134             _lv_memset_00(cached_src, sizeof(lv_img_cache_entry_t));
135             cached_src->life = INT32_MIN; /*Make the empty entry very "weak" to force its use  */
136             return NULL;
137         }
138 
139         cached_src->life = 0;
140 
141         /*If `time_to_open` was not set in the open function set it here*/
142         if(cached_src->dec_dsc.time_to_open == 0) {
143             cached_src->dec_dsc.time_to_open = lv_tick_elaps(t_start);
144         }
145 
146         if(cached_src->dec_dsc.time_to_open == 0) cached_src->dec_dsc.time_to_open = 1;
147     }
148 
149     return cached_src;
150 }
151 
152 /**
153  * Set the number of images to be cached.
154  * More cached images mean more opened image at same time which might mean more memory usage.
155  * E.g. if 20 PNG or JPG images are open in the RAM they consume memory while opened in the cache.
156  * @param new_entry_cnt number of image to cache
157  */
lv_img_cache_set_size(uint16_t new_entry_cnt)158 void lv_img_cache_set_size(uint16_t new_entry_cnt)
159 {
160     if(LV_GC_ROOT(_lv_img_cache_array) != NULL) {
161         /*Clean the cache before free it*/
162         lv_img_cache_invalidate_src(NULL);
163         lv_mem_free(LV_GC_ROOT(_lv_img_cache_array));
164     }
165 
166     /*Reallocate the cache*/
167     LV_GC_ROOT(_lv_img_cache_array) = lv_mem_alloc(sizeof(lv_img_cache_entry_t) * new_entry_cnt);
168     LV_ASSERT_MEM(LV_GC_ROOT(_lv_img_cache_array));
169     if(LV_GC_ROOT(_lv_img_cache_array) == NULL) {
170         entry_cnt = 0;
171         return;
172     }
173     entry_cnt = new_entry_cnt;
174 
175     /*Clean the cache*/
176     uint16_t i;
177     for(i = 0; i < entry_cnt; i++) {
178         _lv_memset_00(&LV_GC_ROOT(_lv_img_cache_array)[i].dec_dsc, sizeof(lv_img_decoder_dsc_t));
179         _lv_memset_00(&LV_GC_ROOT(_lv_img_cache_array)[i], sizeof(lv_img_cache_entry_t));
180     }
181 }
182 
183 /**
184  * Invalidate an image source in the cache.
185  * Useful if the image source is updated therefore it needs to be cached again.
186  * @param src an image source path to a file or pointer to an `lv_img_dsc_t` variable.
187  */
lv_img_cache_invalidate_src(const void * src)188 void lv_img_cache_invalidate_src(const void * src)
189 {
190 
191     lv_img_cache_entry_t * cache = LV_GC_ROOT(_lv_img_cache_array);
192 
193     uint16_t i;
194     for(i = 0; i < entry_cnt; i++) {
195         if(cache[i].dec_dsc.src == src || src == NULL) {
196             if(cache[i].dec_dsc.src != NULL) {
197                 lv_img_decoder_close(&cache[i].dec_dsc);
198             }
199 
200             _lv_memset_00(&cache[i].dec_dsc, sizeof(lv_img_decoder_dsc_t));
201             _lv_memset_00(&cache[i], sizeof(lv_img_cache_entry_t));
202         }
203     }
204 }
205 
206 /**********************
207  *   STATIC FUNCTIONS
208  **********************/
209