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