1 /**
2  * @file lv_draw_sw_gradient.c
3  *
4  */
5 
6 /*********************
7  *      INCLUDES
8  *********************/
9 #include "lv_draw_sw_gradient.h"
10 #include "../../misc/lv_gc.h"
11 #include "../../misc/lv_types.h"
12 
13 /*********************
14  *      DEFINES
15  *********************/
16 #if _DITHER_GRADIENT
17     #define GRAD_CM(r,g,b) LV_COLOR_MAKE32(r,g,b)
18     #define GRAD_CONV(t, x) t.full = lv_color_to32(x)
19 #else
20     #define GRAD_CM(r,g,b) LV_COLOR_MAKE(r,g,b)
21     #define GRAD_CONV(t, x) t = x
22 #endif
23 
24 #undef ALIGN
25 #if defined(LV_ARCH_64)
26     #define ALIGN(X)    (((X) + 7) & ~7)
27 #else
28     #define ALIGN(X)    (((X) + 3) & ~3)
29 #endif
30 
31 #if LV_GRAD_CACHE_DEF_SIZE != 0 && LV_GRAD_CACHE_DEF_SIZE < 256
32     #error "LV_GRAD_CACHE_DEF_SIZE is too small"
33 #endif
34 
35 /**********************
36  *  STATIC PROTOTYPES
37  **********************/
38 static lv_grad_t * next_in_cache(lv_grad_t * item);
39 
40 typedef lv_res_t (*op_cache_t)(lv_grad_t * c, void * ctx);
41 static lv_res_t iterate_cache(op_cache_t func, void * ctx, lv_grad_t ** out);
42 static size_t get_cache_item_size(lv_grad_t * c);
43 static lv_grad_t * allocate_item(const lv_grad_dsc_t * g, lv_coord_t w, lv_coord_t h);
44 static lv_res_t find_oldest_item_life(lv_grad_t * c, void * ctx);
45 static lv_res_t kill_oldest_item(lv_grad_t * c, void * ctx);
46 static lv_res_t find_item(lv_grad_t * c, void * ctx);
47 static void free_item(lv_grad_t * c);
48 static  uint32_t compute_key(const lv_grad_dsc_t * g, lv_coord_t w, lv_coord_t h);
49 
50 
51 /**********************
52  *   STATIC VARIABLE
53  **********************/
54 static size_t    grad_cache_size = 0;
55 static uint8_t * grad_cache_end = 0;
56 
57 /**********************
58  *   STATIC FUNCTIONS
59  **********************/
60 union void_cast {
61     const void * ptr;
62     const uint32_t value;
63 };
64 
compute_key(const lv_grad_dsc_t * g,lv_coord_t size,lv_coord_t w)65 static uint32_t compute_key(const lv_grad_dsc_t * g, lv_coord_t size, lv_coord_t w)
66 {
67     union void_cast v;
68     v.ptr = g;
69     return (v.value ^ size ^ (w >> 1)); /*Yes, this is correct, it's like a hash that changes if the width changes*/
70 }
71 
get_cache_item_size(lv_grad_t * c)72 static size_t get_cache_item_size(lv_grad_t * c)
73 {
74     size_t s = ALIGN(sizeof(*c)) + ALIGN(c->alloc_size * sizeof(lv_color_t));
75 #if _DITHER_GRADIENT
76     s += ALIGN(c->size * sizeof(lv_color32_t));
77 #if LV_DITHER_ERROR_DIFFUSION == 1
78     s += ALIGN(c->w * sizeof(lv_scolor24_t));
79 #endif
80 #endif
81     return s;
82 }
83 
next_in_cache(lv_grad_t * item)84 static lv_grad_t * next_in_cache(lv_grad_t * item)
85 {
86     if(grad_cache_size == 0) return NULL;
87 
88     if(item == NULL)
89         return (lv_grad_t *)LV_GC_ROOT(_lv_grad_cache_mem);
90 
91     size_t s = get_cache_item_size(item);
92     /*Compute the size for this cache item*/
93     if((uint8_t *)item + s >= grad_cache_end) return NULL;
94     else return (lv_grad_t *)((uint8_t *)item + s);
95 }
96 
iterate_cache(op_cache_t func,void * ctx,lv_grad_t ** out)97 static lv_res_t iterate_cache(op_cache_t func, void * ctx, lv_grad_t ** out)
98 {
99     lv_grad_t * first = next_in_cache(NULL);
100     while(first != NULL && first->life) {
101         if((*func)(first, ctx) == LV_RES_OK) {
102             if(out != NULL) *out = first;
103             return LV_RES_OK;
104         }
105         first = next_in_cache(first);
106     }
107     return LV_RES_INV;
108 }
109 
find_oldest_item_life(lv_grad_t * c,void * ctx)110 static lv_res_t find_oldest_item_life(lv_grad_t * c, void * ctx)
111 {
112     uint32_t * min_life = (uint32_t *)ctx;
113     if(c->life < *min_life) *min_life = c->life;
114     return LV_RES_INV;
115 }
116 
free_item(lv_grad_t * c)117 static void free_item(lv_grad_t * c)
118 {
119     size_t size = get_cache_item_size(c);
120     size_t next_items_size = (size_t)(grad_cache_end - (uint8_t *)c) - size;
121     grad_cache_end -= size;
122     if(next_items_size) {
123         uint8_t * old = (uint8_t *)c;
124         lv_memcpy(c, ((uint8_t *)c) + size, next_items_size);
125         /* Then need to fix all internal pointers too */
126         while((uint8_t *)c != grad_cache_end) {
127             c->map = (lv_color_t *)(((uint8_t *)c->map) - size);
128 #if _DITHER_GRADIENT
129             c->hmap = (lv_color32_t *)(((uint8_t *)c->hmap) - size);
130 #if LV_DITHER_ERROR_DIFFUSION == 1
131             c->error_acc = (lv_scolor24_t *)(((uint8_t *)c->error_acc) - size);
132 #endif
133 #endif
134             c = (lv_grad_t *)(((uint8_t *)c) + get_cache_item_size(c));
135         }
136         lv_memset_00(old + next_items_size, size);
137     }
138 }
139 
kill_oldest_item(lv_grad_t * c,void * ctx)140 static lv_res_t kill_oldest_item(lv_grad_t * c, void * ctx)
141 {
142     uint32_t * min_life = (uint32_t *)ctx;
143     if(c->life == *min_life) {
144         /*Found, let's kill it*/
145         free_item(c);
146         return LV_RES_OK;
147     }
148     return LV_RES_INV;
149 }
150 
find_item(lv_grad_t * c,void * ctx)151 static lv_res_t find_item(lv_grad_t * c, void * ctx)
152 {
153     uint32_t * k = (uint32_t *)ctx;
154     if(c->key == *k) return LV_RES_OK;
155     return LV_RES_INV;
156 }
157 
allocate_item(const lv_grad_dsc_t * g,lv_coord_t w,lv_coord_t h)158 static lv_grad_t * allocate_item(const lv_grad_dsc_t * g, lv_coord_t w, lv_coord_t h)
159 {
160     lv_coord_t size = g->dir == LV_GRAD_DIR_HOR ? w : h;
161     lv_coord_t map_size = LV_MAX(w, h); /* The map is being used horizontally (width) unless
162                                            no dithering is selected where it's used vertically */
163 
164     size_t req_size = ALIGN(sizeof(lv_grad_t)) + ALIGN(map_size * sizeof(lv_color_t));
165 #if _DITHER_GRADIENT
166     req_size += ALIGN(size * sizeof(lv_color32_t));
167 #if LV_DITHER_ERROR_DIFFUSION == 1
168     req_size += ALIGN(w * sizeof(lv_scolor24_t));
169 #endif
170 #endif
171 
172     size_t act_size = (size_t)(grad_cache_end - LV_GC_ROOT(_lv_grad_cache_mem));
173     lv_grad_t * item = NULL;
174     if(req_size + act_size < grad_cache_size) {
175         item = (lv_grad_t *)grad_cache_end;
176         item->not_cached = 0;
177     }
178     else {
179         /*Need to evict items from cache until we find enough space to allocate this one */
180         if(req_size <= grad_cache_size) {
181             while(act_size + req_size > grad_cache_size) {
182                 uint32_t oldest_life = UINT32_MAX;
183                 iterate_cache(&find_oldest_item_life, &oldest_life, NULL);
184                 iterate_cache(&kill_oldest_item, &oldest_life, NULL);
185                 act_size = (size_t)(grad_cache_end - LV_GC_ROOT(_lv_grad_cache_mem));
186             }
187             item = (lv_grad_t *)grad_cache_end;
188             item->not_cached = 0;
189         }
190         else {
191             /*The cache is too small. Allocate the item manually and free it later.*/
192             item = lv_mem_alloc(req_size);
193             LV_ASSERT_MALLOC(item);
194             if(item == NULL) return NULL;
195             item->not_cached = 1;
196         }
197     }
198 
199     item->key = compute_key(g, size, w);
200     item->life = 1;
201     item->filled = 0;
202     item->alloc_size = map_size;
203     item->size = size;
204     if(item->not_cached) {
205         uint8_t * p = (uint8_t *)item;
206         item->map = (lv_color_t *)(p + ALIGN(sizeof(*item)));
207 #if _DITHER_GRADIENT
208         item->hmap = (lv_color32_t *)(p + ALIGN(sizeof(*item)) + ALIGN(map_size * sizeof(lv_color_t)));
209 #if LV_DITHER_ERROR_DIFFUSION == 1
210         item->error_acc = (lv_scolor24_t *)(p + ALIGN(sizeof(*item)) + ALIGN(size * sizeof(lv_grad_color_t)) +
211                                             ALIGN(map_size * sizeof(lv_color_t)));
212         item->w = w;
213 #endif
214 #endif
215     }
216     else {
217         item->map = (lv_color_t *)(grad_cache_end + ALIGN(sizeof(*item)));
218 #if _DITHER_GRADIENT
219         item->hmap = (lv_color32_t *)(grad_cache_end + ALIGN(sizeof(*item)) + ALIGN(map_size * sizeof(lv_color_t)));
220 #if LV_DITHER_ERROR_DIFFUSION == 1
221         item->error_acc = (lv_scolor24_t *)(grad_cache_end + ALIGN(sizeof(*item)) + ALIGN(size * sizeof(lv_grad_color_t)) +
222                                             ALIGN(map_size * sizeof(lv_color_t)));
223         item->w = w;
224 #endif
225 #endif
226         grad_cache_end += req_size;
227     }
228     return item;
229 }
230 
231 
232 /**********************
233  *     FUNCTIONS
234  **********************/
lv_gradient_free_cache(void)235 void lv_gradient_free_cache(void)
236 {
237     lv_mem_free(LV_GC_ROOT(_lv_grad_cache_mem));
238     LV_GC_ROOT(_lv_grad_cache_mem) = grad_cache_end = NULL;
239     grad_cache_size = 0;
240 }
241 
lv_gradient_set_cache_size(size_t max_bytes)242 void lv_gradient_set_cache_size(size_t max_bytes)
243 {
244     lv_mem_free(LV_GC_ROOT(_lv_grad_cache_mem));
245     grad_cache_end = LV_GC_ROOT(_lv_grad_cache_mem) = lv_mem_alloc(max_bytes);
246     LV_ASSERT_MALLOC(LV_GC_ROOT(_lv_grad_cache_mem));
247     lv_memset_00(LV_GC_ROOT(_lv_grad_cache_mem), max_bytes);
248     grad_cache_size = max_bytes;
249 }
250 
lv_gradient_get(const lv_grad_dsc_t * g,lv_coord_t w,lv_coord_t h)251 lv_grad_t * lv_gradient_get(const lv_grad_dsc_t * g, lv_coord_t w, lv_coord_t h)
252 {
253     /* No gradient, no cache */
254     if(g->dir == LV_GRAD_DIR_NONE) return NULL;
255 
256     /* Step 0: Check if the cache exist (else create it) */
257     static bool inited = false;
258     if(!inited) {
259         lv_gradient_set_cache_size(LV_GRAD_CACHE_DEF_SIZE);
260         inited = true;
261     }
262 
263     /* Step 1: Search cache for the given key */
264     lv_coord_t size = g->dir == LV_GRAD_DIR_HOR ? w : h;
265     uint32_t key = compute_key(g, size, w);
266     lv_grad_t * item = NULL;
267     if(iterate_cache(&find_item, &key, &item) == LV_RES_OK) {
268         item->life++; /* Don't forget to bump the counter */
269         return item;
270     }
271 
272     /* Step 2: Need to allocate an item for it */
273     item = allocate_item(g, w, h);
274     if(item == NULL) {
275         LV_LOG_WARN("Faild to allcoate item for teh gradient");
276         return item;
277     }
278 
279     /* Step 3: Fill it with the gradient, as expected */
280 #if _DITHER_GRADIENT
281     for(lv_coord_t i = 0; i < item->size; i++) {
282         item->hmap[i] = lv_gradient_calculate(g, item->size, i);
283     }
284 #if LV_DITHER_ERROR_DIFFUSION == 1
285     lv_memset_00(item->error_acc, w * sizeof(lv_scolor24_t));
286 #endif
287 #else
288     for(lv_coord_t i = 0; i < item->size; i++) {
289         item->map[i] = lv_gradient_calculate(g, item->size, i);
290     }
291 #endif
292 
293     return item;
294 }
295 
lv_gradient_calculate(const lv_grad_dsc_t * dsc,lv_coord_t range,lv_coord_t frac)296 LV_ATTRIBUTE_FAST_MEM lv_grad_color_t lv_gradient_calculate(const lv_grad_dsc_t * dsc, lv_coord_t range,
297                                                             lv_coord_t frac)
298 {
299     lv_grad_color_t tmp;
300     lv_color32_t one, two;
301     /*Clip out-of-bounds first*/
302     int32_t min = (dsc->stops[0].frac * range) >> 8;
303     if(frac <= min) {
304         GRAD_CONV(tmp, dsc->stops[0].color);
305         return tmp;
306     }
307 
308     int32_t max = (dsc->stops[dsc->stops_count - 1].frac * range) >> 8;
309     if(frac >= max) {
310         GRAD_CONV(tmp, dsc->stops[dsc->stops_count - 1].color);
311         return tmp;
312     }
313 
314     /*Find the 2 closest stop now*/
315     int32_t d = 0;
316     for(uint8_t i = 1; i < dsc->stops_count; i++) {
317         int32_t cur = (dsc->stops[i].frac * range) >> 8;
318         if(frac <= cur) {
319             one.full = lv_color_to32(dsc->stops[i - 1].color);
320             two.full = lv_color_to32(dsc->stops[i].color);
321             min = (dsc->stops[i - 1].frac * range) >> 8;
322             max = (dsc->stops[i].frac * range) >> 8;
323             d = max - min;
324             break;
325         }
326     }
327 
328     LV_ASSERT(d != 0);
329 
330     /*Then interpolate*/
331     frac -= min;
332     lv_opa_t mix = (frac * 255) / d;
333     lv_opa_t imix = 255 - mix;
334 
335     lv_grad_color_t r = GRAD_CM(LV_UDIV255(two.ch.red * mix   + one.ch.red * imix),
336                                 LV_UDIV255(two.ch.green * mix + one.ch.green * imix),
337                                 LV_UDIV255(two.ch.blue * mix  + one.ch.blue * imix));
338     return r;
339 }
340 
lv_gradient_cleanup(lv_grad_t * grad)341 void lv_gradient_cleanup(lv_grad_t * grad)
342 {
343     if(grad->not_cached) {
344         lv_mem_free(grad);
345     }
346 }
347