acid-drop- Hacking the planet from a LilyGo T-Deck using custom firmware |
git clone git://git.acid.vegas/acid-drop.git |
Log | Files | Refs | Archive | README | LICENSE |
lv_draw_sw_gradient.c (10895B)
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 #if defined(LV_ARCH_64) 25 #define ALIGN(X) (((X) + 7) & ~7) 26 #else 27 #define ALIGN(X) (((X) + 3) & ~3) 28 #endif 29 30 #if LV_GRAD_CACHE_DEF_SIZE != 0 && LV_GRAD_CACHE_DEF_SIZE < 256 31 #error "LV_GRAD_CACHE_DEF_SIZE is too small" 32 #endif 33 34 /********************** 35 * STATIC PROTOTYPES 36 **********************/ 37 static lv_grad_t * next_in_cache(lv_grad_t * item); 38 39 typedef lv_res_t (*op_cache_t)(lv_grad_t * c, void * ctx); 40 static lv_res_t iterate_cache(op_cache_t func, void * ctx, lv_grad_t ** out); 41 static size_t get_cache_item_size(lv_grad_t * c); 42 static lv_grad_t * allocate_item(const lv_grad_dsc_t * g, lv_coord_t w, lv_coord_t h); 43 static lv_res_t find_oldest_item_life(lv_grad_t * c, void * ctx); 44 static lv_res_t kill_oldest_item(lv_grad_t * c, void * ctx); 45 static lv_res_t find_item(lv_grad_t * c, void * ctx); 46 static void free_item(lv_grad_t * c); 47 static uint32_t compute_key(const lv_grad_dsc_t * g, lv_coord_t w, lv_coord_t h); 48 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 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 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 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 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 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 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 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 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 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 /********************** 232 * FUNCTIONS 233 **********************/ 234 void lv_gradient_free_cache(void) 235 { 236 lv_mem_free(LV_GC_ROOT(_lv_grad_cache_mem)); 237 LV_GC_ROOT(_lv_grad_cache_mem) = grad_cache_end = NULL; 238 grad_cache_size = 0; 239 } 240 241 void lv_gradient_set_cache_size(size_t max_bytes) 242 { 243 lv_mem_free(LV_GC_ROOT(_lv_grad_cache_mem)); 244 grad_cache_end = LV_GC_ROOT(_lv_grad_cache_mem) = lv_mem_alloc(max_bytes); 245 LV_ASSERT_MALLOC(LV_GC_ROOT(_lv_grad_cache_mem)); 246 lv_memset_00(LV_GC_ROOT(_lv_grad_cache_mem), max_bytes); 247 grad_cache_size = max_bytes; 248 } 249 250 lv_grad_t * lv_gradient_get(const lv_grad_dsc_t * g, lv_coord_t w, lv_coord_t h) 251 { 252 /* No gradient, no cache */ 253 if(g->dir == LV_GRAD_DIR_NONE) return NULL; 254 255 /* Step 0: Check if the cache exist (else create it) */ 256 static bool inited = false; 257 if(!inited) { 258 lv_gradient_set_cache_size(LV_GRAD_CACHE_DEF_SIZE); 259 inited = true; 260 } 261 262 /* Step 1: Search cache for the given key */ 263 lv_coord_t size = g->dir == LV_GRAD_DIR_HOR ? w : h; 264 uint32_t key = compute_key(g, size, w); 265 lv_grad_t * item = NULL; 266 if(iterate_cache(&find_item, &key, &item) == LV_RES_OK) { 267 item->life++; /* Don't forget to bump the counter */ 268 return item; 269 } 270 271 /* Step 2: Need to allocate an item for it */ 272 item = allocate_item(g, w, h); 273 if(item == NULL) { 274 LV_LOG_WARN("Faild to allcoate item for teh gradient"); 275 return item; 276 } 277 278 /* Step 3: Fill it with the gradient, as expected */ 279 #if _DITHER_GRADIENT 280 for(lv_coord_t i = 0; i < item->size; i++) { 281 item->hmap[i] = lv_gradient_calculate(g, item->size, i); 282 } 283 #if LV_DITHER_ERROR_DIFFUSION == 1 284 lv_memset_00(item->error_acc, w * sizeof(lv_scolor24_t)); 285 #endif 286 #else 287 for(lv_coord_t i = 0; i < item->size; i++) { 288 item->map[i] = lv_gradient_calculate(g, item->size, i); 289 } 290 #endif 291 292 return item; 293 } 294 295 LV_ATTRIBUTE_FAST_MEM lv_grad_color_t lv_gradient_calculate(const lv_grad_dsc_t * dsc, lv_coord_t range, 296 lv_coord_t frac) 297 { 298 lv_grad_color_t tmp; 299 lv_color32_t one, two; 300 /*Clip out-of-bounds first*/ 301 int32_t min = (dsc->stops[0].frac * range) >> 8; 302 if(frac <= min) { 303 GRAD_CONV(tmp, dsc->stops[0].color); 304 return tmp; 305 } 306 307 int32_t max = (dsc->stops[dsc->stops_count - 1].frac * range) >> 8; 308 if(frac >= max) { 309 GRAD_CONV(tmp, dsc->stops[dsc->stops_count - 1].color); 310 return tmp; 311 } 312 313 /*Find the 2 closest stop now*/ 314 int32_t d = 0; 315 for(uint8_t i = 1; i < dsc->stops_count; i++) { 316 int32_t cur = (dsc->stops[i].frac * range) >> 8; 317 if(frac <= cur) { 318 one.full = lv_color_to32(dsc->stops[i - 1].color); 319 two.full = lv_color_to32(dsc->stops[i].color); 320 min = (dsc->stops[i - 1].frac * range) >> 8; 321 max = (dsc->stops[i].frac * range) >> 8; 322 d = max - min; 323 break; 324 } 325 } 326 327 LV_ASSERT(d != 0); 328 329 /*Then interpolate*/ 330 frac -= min; 331 lv_opa_t mix = (frac * 255) / d; 332 lv_opa_t imix = 255 - mix; 333 334 lv_grad_color_t r = GRAD_CM(LV_UDIV255(two.ch.red * mix + one.ch.red * imix), 335 LV_UDIV255(two.ch.green * mix + one.ch.green * imix), 336 LV_UDIV255(two.ch.blue * mix + one.ch.blue * imix)); 337 return r; 338 } 339 340 void lv_gradient_cleanup(lv_grad_t * grad) 341 { 342 if(grad->not_cached) { 343 lv_mem_free(grad); 344 } 345 }