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 }