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