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