lvgl_cpp/lvgl/draw/sw/lv_draw_sw_rect.c

1420 lines
52 KiB
C

/**
* @file lv_draw_rect.c
*
*/
/*********************
* INCLUDES
*********************/
#include "lv_draw_sw.h"
#include "../../misc/lv_math.h"
#include "../../misc/lv_txt_ap.h"
#include "../../core/lv_refr.h"
#include "../../misc/lv_assert.h"
#include "lv_draw_sw_dither.h"
/*********************
* DEFINES
*********************/
#define SHADOW_UPSCALE_SHIFT 6
#define SHADOW_ENHANCE 1
#define SPLIT_LIMIT 50
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
static void draw_bg(lv_draw_ctx_t * draw_ctx, const lv_draw_rect_dsc_t * dsc, const lv_area_t * coords);
static void draw_bg_img(lv_draw_ctx_t * draw_ctx, const lv_draw_rect_dsc_t * dsc, const lv_area_t * coords);
static void draw_border(lv_draw_ctx_t * draw_ctx, const lv_draw_rect_dsc_t * dsc, const lv_area_t * coords);
static void draw_outline(lv_draw_ctx_t * draw_ctx, const lv_draw_rect_dsc_t * dsc, const lv_area_t * coords);
#if LV_DRAW_COMPLEX
LV_ATTRIBUTE_FAST_MEM static void draw_shadow(lv_draw_ctx_t * draw_ctx, const lv_draw_rect_dsc_t * dsc,
const lv_area_t * coords);
LV_ATTRIBUTE_FAST_MEM static void shadow_draw_corner_buf(const lv_area_t * coords, uint16_t * sh_buf, lv_coord_t s,
lv_coord_t r);
LV_ATTRIBUTE_FAST_MEM static void shadow_blur_corner(lv_coord_t size, lv_coord_t sw, uint16_t * sh_ups_buf);
#endif
void draw_border_generic(lv_draw_ctx_t * draw_ctx, const lv_area_t * outer_area, const lv_area_t * inner_area,
lv_coord_t rout, lv_coord_t rin, lv_color_t color, lv_opa_t opa, lv_blend_mode_t blend_mode);
static void draw_border_simple(lv_draw_ctx_t * draw_ctx, const lv_area_t * outer_area, const lv_area_t * inner_area,
lv_color_t color, lv_opa_t opa);
/**********************
* STATIC VARIABLES
**********************/
#if defined(LV_SHADOW_CACHE_SIZE) && LV_SHADOW_CACHE_SIZE > 0
static uint8_t sh_cache[LV_SHADOW_CACHE_SIZE * LV_SHADOW_CACHE_SIZE];
static int32_t sh_cache_size = -1;
static int32_t sh_cache_r = -1;
#endif
/**********************
* MACROS
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
void lv_draw_sw_rect(lv_draw_ctx_t * draw_ctx, const lv_draw_rect_dsc_t * dsc, const lv_area_t * coords)
{
#if LV_DRAW_COMPLEX
draw_shadow(draw_ctx, dsc, coords);
#endif
draw_bg(draw_ctx, dsc, coords);
draw_bg_img(draw_ctx, dsc, coords);
draw_border(draw_ctx, dsc, coords);
draw_outline(draw_ctx, dsc, coords);
LV_ASSERT_MEM_INTEGRITY();
}
void lv_draw_sw_bg(lv_draw_ctx_t * draw_ctx, const lv_draw_rect_dsc_t * dsc, const lv_area_t * coords)
{
#if LV_COLOR_SCREEN_TRANSP && LV_COLOR_DEPTH == 32
lv_memset_00(draw_ctx->buf, lv_area_get_size(draw_ctx->buf_area) * sizeof(lv_color_t));
#endif
draw_bg(draw_ctx, dsc, coords);
draw_bg_img(draw_ctx, dsc, coords);
}
/**********************
* STATIC FUNCTIONS
**********************/
static void draw_bg(lv_draw_ctx_t * draw_ctx, const lv_draw_rect_dsc_t * dsc, const lv_area_t * coords)
{
if(dsc->bg_opa <= LV_OPA_MIN) return;
lv_area_t bg_coords;
lv_area_copy(&bg_coords, coords);
/*If the border fully covers make the bg area 1px smaller to avoid artifacts on the corners*/
if(dsc->border_width > 1 && dsc->border_opa >= LV_OPA_MAX && dsc->radius != 0) {
bg_coords.x1 += (dsc->border_side & LV_BORDER_SIDE_LEFT) ? 1 : 0;
bg_coords.y1 += (dsc->border_side & LV_BORDER_SIDE_TOP) ? 1 : 0;
bg_coords.x2 -= (dsc->border_side & LV_BORDER_SIDE_RIGHT) ? 1 : 0;
bg_coords.y2 -= (dsc->border_side & LV_BORDER_SIDE_BOTTOM) ? 1 : 0;
}
lv_area_t clipped_coords;
if(!_lv_area_intersect(&clipped_coords, &bg_coords, draw_ctx->clip_area)) return;
lv_grad_dir_t grad_dir = dsc->bg_grad.dir;
lv_color_t bg_color = grad_dir == LV_GRAD_DIR_NONE ? dsc->bg_color : dsc->bg_grad.stops[0].color;
if(bg_color.full == dsc->bg_grad.stops[1].color.full) grad_dir = LV_GRAD_DIR_NONE;
bool mask_any = lv_draw_mask_is_any(&bg_coords);
lv_draw_sw_blend_dsc_t blend_dsc = {0};
blend_dsc.blend_mode = dsc->blend_mode;
blend_dsc.color = bg_color;
/*Most simple case: just a plain rectangle*/
if(!mask_any && dsc->radius == 0 && (grad_dir == LV_GRAD_DIR_NONE)) {
blend_dsc.blend_area = &bg_coords;
blend_dsc.opa = dsc->bg_opa;
lv_draw_sw_blend(draw_ctx, &blend_dsc);
return;
}
/*Complex case: there is gradient, mask, or radius*/
#if LV_DRAW_COMPLEX == 0
LV_LOG_WARN("Can't draw complex rectangle because LV_DRAW_COMPLEX = 0");
#else
lv_opa_t opa = dsc->bg_opa >= LV_OPA_MAX ? LV_OPA_COVER : dsc->bg_opa;
/*Get the real radius. Can't be larger than the half of the shortest side */
lv_coord_t coords_bg_w = lv_area_get_width(&bg_coords);
lv_coord_t coords_bg_h = lv_area_get_height(&bg_coords);
int32_t short_side = LV_MIN(coords_bg_w, coords_bg_h);
int32_t rout = LV_MIN(dsc->radius, short_side >> 1);
/*Add a radius mask if there is radius*/
int32_t clipped_w = lv_area_get_width(&clipped_coords);
int16_t mask_rout_id = LV_MASK_ID_INV;
lv_opa_t * mask_buf = NULL;
lv_draw_mask_radius_param_t mask_rout_param;
if(rout > 0 || mask_any) {
mask_buf = lv_mem_buf_get(clipped_w);
lv_draw_mask_radius_init(&mask_rout_param, &bg_coords, rout, false);
mask_rout_id = lv_draw_mask_add(&mask_rout_param, NULL);
}
int32_t h;
lv_area_t blend_area;
blend_area.x1 = clipped_coords.x1;
blend_area.x2 = clipped_coords.x2;
blend_dsc.mask_buf = mask_buf;
blend_dsc.blend_area = &blend_area;
blend_dsc.mask_area = &blend_area;
blend_dsc.opa = LV_OPA_COVER;
/*Get gradient if appropriate*/
lv_grad_t * grad = lv_gradient_get(&dsc->bg_grad, coords_bg_w, coords_bg_h);
if(grad && grad_dir == LV_GRAD_DIR_HOR) {
blend_dsc.src_buf = grad->map + clipped_coords.x1 - bg_coords.x1;
}
#if _DITHER_GRADIENT
lv_dither_mode_t dither_mode = dsc->bg_grad.dither;
lv_dither_func_t dither_func = &lv_dither_none;
lv_coord_t grad_size = coords_bg_w;
if(grad_dir == LV_GRAD_DIR_VER && dither_mode != LV_DITHER_NONE) {
/* When dithering, we are still using a map that's changing from line to line*/
blend_dsc.src_buf = grad->map;
}
if(grad && dither_mode == LV_DITHER_NONE) {
grad->filled = 0; /*Should we force refilling it each draw call ?*/
if(grad_dir == LV_GRAD_DIR_VER)
grad_size = coords_bg_h;
}
else
#if LV_DITHER_ERROR_DIFFUSION
if(dither_mode == LV_DITHER_ORDERED)
#endif
switch(grad_dir) {
case LV_GRAD_DIR_HOR:
dither_func = lv_dither_ordered_hor;
break;
case LV_GRAD_DIR_VER:
dither_func = lv_dither_ordered_ver;
break;
default:
dither_func = NULL;
}
#if LV_DITHER_ERROR_DIFFUSION
else if(dither_mode == LV_DITHER_ERR_DIFF)
switch(grad_dir) {
case LV_GRAD_DIR_HOR:
dither_func = lv_dither_err_diff_hor;
break;
case LV_GRAD_DIR_VER:
dither_func = lv_dither_err_diff_ver;
break;
default:
dither_func = NULL;
}
#endif
#endif
/*There is another mask too. Draw line by line. */
if(mask_any) {
for(h = clipped_coords.y1; h <= clipped_coords.y2; h++) {
blend_area.y1 = h;
blend_area.y2 = h;
/* Initialize the mask to opa instead of 0xFF and blend with LV_OPA_COVER.
* It saves calculating the final opa in lv_draw_sw_blend*/
lv_memset(mask_buf, opa, clipped_w);
blend_dsc.mask_res = lv_draw_mask_apply(mask_buf, clipped_coords.x1, h, clipped_w);
if(blend_dsc.mask_res == LV_DRAW_MASK_RES_FULL_COVER) blend_dsc.mask_res = LV_DRAW_MASK_RES_CHANGED;
#if _DITHER_GRADIENT
if(dither_func) dither_func(grad, blend_area.x1, h - bg_coords.y1, grad_size);
#endif
if(grad_dir == LV_GRAD_DIR_VER) blend_dsc.color = grad->map[h - bg_coords.y1];
lv_draw_sw_blend(draw_ctx, &blend_dsc);
}
goto bg_clean_up;
}
/* Draw the top of the rectangle line by line and mirror it to the bottom. */
for(h = 0; h < rout; h++) {
lv_coord_t top_y = bg_coords.y1 + h;
lv_coord_t bottom_y = bg_coords.y2 - h;
if(top_y < clipped_coords.y1 && bottom_y > clipped_coords.y2) continue; /*This line is clipped now*/
/* Initialize the mask to opa instead of 0xFF and blend with LV_OPA_COVER.
* It saves calculating the final opa in lv_draw_sw_blend*/
lv_memset(mask_buf, opa, clipped_w);
blend_dsc.mask_res = lv_draw_mask_apply(mask_buf, blend_area.x1, top_y, clipped_w);
if(blend_dsc.mask_res == LV_DRAW_MASK_RES_FULL_COVER) blend_dsc.mask_res = LV_DRAW_MASK_RES_CHANGED;
if(top_y >= clipped_coords.y1) {
blend_area.y1 = top_y;
blend_area.y2 = top_y;
#if _DITHER_GRADIENT
if(dither_func) dither_func(grad, blend_area.x1, top_y - bg_coords.y1, grad_size);
#endif
if(grad_dir == LV_GRAD_DIR_VER) blend_dsc.color = grad->map[top_y - bg_coords.y1];
lv_draw_sw_blend(draw_ctx, &blend_dsc);
}
if(bottom_y <= clipped_coords.y2) {
blend_area.y1 = bottom_y;
blend_area.y2 = bottom_y;
#if _DITHER_GRADIENT
if(dither_func) dither_func(grad, blend_area.x1, bottom_y - bg_coords.y1, grad_size);
#endif
if(grad_dir == LV_GRAD_DIR_VER) blend_dsc.color = grad->map[bottom_y - bg_coords.y1];
lv_draw_sw_blend(draw_ctx, &blend_dsc);
}
}
/* Draw the center of the rectangle.*/
/*If no other masks and no gradient, the center is a simple rectangle*/
lv_area_t center_coords;
center_coords.x1 = bg_coords.x1;
center_coords.x2 = bg_coords.x2;
center_coords.y1 = bg_coords.y1 + rout;
center_coords.y2 = bg_coords.y2 - rout;
bool mask_any_center = lv_draw_mask_is_any(&center_coords);
if(!mask_any_center && grad_dir == LV_GRAD_DIR_NONE) {
blend_area.y1 = bg_coords.y1 + rout;
blend_area.y2 = bg_coords.y2 - rout;
blend_dsc.opa = opa;
blend_dsc.mask_buf = NULL;
lv_draw_sw_blend(draw_ctx, &blend_dsc);
}
/*With gradient and/or mask draw line by line*/
else {
blend_dsc.opa = opa;
blend_dsc.mask_res = LV_DRAW_MASK_RES_FULL_COVER;
int32_t h_end = bg_coords.y2 - rout;
for(h = bg_coords.y1 + rout; h <= h_end; h++) {
/*If there is no other mask do not apply mask as in the center there is no radius to mask*/
if(mask_any_center) {
lv_memset(mask_buf, opa, clipped_w);
blend_dsc.mask_res = lv_draw_mask_apply(mask_buf, clipped_coords.x1, h, clipped_w);
}
blend_area.y1 = h;
blend_area.y2 = h;
#if _DITHER_GRADIENT
if(dither_func) dither_func(grad, blend_area.x1, h - bg_coords.y1, grad_size);
#endif
if(grad_dir == LV_GRAD_DIR_VER) blend_dsc.color = grad->map[h - bg_coords.y1];
lv_draw_sw_blend(draw_ctx, &blend_dsc);
}
}
bg_clean_up:
if(mask_buf) lv_mem_buf_release(mask_buf);
if(mask_rout_id != LV_MASK_ID_INV) {
lv_draw_mask_remove_id(mask_rout_id);
lv_draw_mask_free_param(&mask_rout_param);
}
if(grad) {
lv_gradient_cleanup(grad);
}
#endif
}
static void draw_bg_img(lv_draw_ctx_t * draw_ctx, const lv_draw_rect_dsc_t * dsc, const lv_area_t * coords)
{
if(dsc->bg_img_src == NULL) return;
if(dsc->bg_img_opa <= LV_OPA_MIN) return;
lv_img_src_t src_type = lv_img_src_get_type(dsc->bg_img_src);
if(src_type == LV_IMG_SRC_SYMBOL) {
lv_point_t size;
lv_txt_get_size(&size, dsc->bg_img_src, dsc->bg_img_symbol_font, 0, 0, LV_COORD_MAX, LV_TEXT_FLAG_NONE);
lv_area_t a;
a.x1 = coords->x1 + lv_area_get_width(coords) / 2 - size.x / 2;
a.x2 = a.x1 + size.x - 1;
a.y1 = coords->y1 + lv_area_get_height(coords) / 2 - size.y / 2;
a.y2 = a.y1 + size.y - 1;
lv_draw_label_dsc_t label_draw_dsc;
lv_draw_label_dsc_init(&label_draw_dsc);
label_draw_dsc.font = dsc->bg_img_symbol_font;
label_draw_dsc.color = dsc->bg_img_recolor;
label_draw_dsc.opa = dsc->bg_img_opa;
lv_draw_label(draw_ctx, &label_draw_dsc, &a, dsc->bg_img_src, NULL);
}
else {
lv_img_header_t header;
lv_res_t res = lv_img_decoder_get_info(dsc->bg_img_src, &header);
if(res != LV_RES_OK) {
LV_LOG_WARN("Couldn't read the background image");
return;
}
lv_draw_img_dsc_t img_dsc;
lv_draw_img_dsc_init(&img_dsc);
img_dsc.blend_mode = dsc->blend_mode;
img_dsc.recolor = dsc->bg_img_recolor;
img_dsc.recolor_opa = dsc->bg_img_recolor_opa;
img_dsc.opa = dsc->bg_img_opa;
/*Center align*/
if(dsc->bg_img_tiled == false) {
lv_area_t area;
area.x1 = coords->x1 + lv_area_get_width(coords) / 2 - header.w / 2;
area.y1 = coords->y1 + lv_area_get_height(coords) / 2 - header.h / 2;
area.x2 = area.x1 + header.w - 1;
area.y2 = area.y1 + header.h - 1;
lv_draw_img(draw_ctx, &img_dsc, &area, dsc->bg_img_src);
}
else {
lv_area_t area;
area.y1 = coords->y1;
area.y2 = area.y1 + header.h - 1;
for(; area.y1 <= coords->y2; area.y1 += header.h, area.y2 += header.h) {
area.x1 = coords->x1;
area.x2 = area.x1 + header.w - 1;
for(; area.x1 <= coords->x2; area.x1 += header.w, area.x2 += header.w) {
lv_draw_img(draw_ctx, &img_dsc, &area, dsc->bg_img_src);
}
}
}
}
}
static void draw_border(lv_draw_ctx_t * draw_ctx, const lv_draw_rect_dsc_t * dsc, const lv_area_t * coords)
{
if(dsc->border_opa <= LV_OPA_MIN) return;
if(dsc->border_width == 0) return;
if(dsc->border_side == LV_BORDER_SIDE_NONE) return;
if(dsc->border_post) return;
int32_t coords_w = lv_area_get_width(coords);
int32_t coords_h = lv_area_get_height(coords);
int32_t rout = dsc->radius;
int32_t short_side = LV_MIN(coords_w, coords_h);
if(rout > short_side >> 1) rout = short_side >> 1;
/*Get the inner area*/
lv_area_t area_inner;
lv_area_copy(&area_inner, coords);
area_inner.x1 += ((dsc->border_side & LV_BORDER_SIDE_LEFT) ? dsc->border_width : - (dsc->border_width + rout));
area_inner.x2 -= ((dsc->border_side & LV_BORDER_SIDE_RIGHT) ? dsc->border_width : - (dsc->border_width + rout));
area_inner.y1 += ((dsc->border_side & LV_BORDER_SIDE_TOP) ? dsc->border_width : - (dsc->border_width + rout));
area_inner.y2 -= ((dsc->border_side & LV_BORDER_SIDE_BOTTOM) ? dsc->border_width : - (dsc->border_width + rout));
lv_coord_t rin = rout - dsc->border_width;
if(rin < 0) rin = 0;
draw_border_generic(draw_ctx, coords, &area_inner, rout, rin, dsc->border_color, dsc->border_opa, dsc->blend_mode);
}
#if LV_DRAW_COMPLEX
LV_ATTRIBUTE_FAST_MEM static void draw_shadow(lv_draw_ctx_t * draw_ctx, const lv_draw_rect_dsc_t * dsc,
const lv_area_t * coords)
{
/*Check whether the shadow is visible*/
if(dsc->shadow_width == 0) return;
if(dsc->shadow_opa <= LV_OPA_MIN) return;
if(dsc->shadow_width == 1 && dsc->shadow_spread <= 0 &&
dsc->shadow_ofs_x == 0 && dsc->shadow_ofs_y == 0) {
return;
}
/*Calculate the rectangle which is blurred to get the shadow in `shadow_area`*/
lv_area_t core_area;
core_area.x1 = coords->x1 + dsc->shadow_ofs_x - dsc->shadow_spread;
core_area.x2 = coords->x2 + dsc->shadow_ofs_x + dsc->shadow_spread;
core_area.y1 = coords->y1 + dsc->shadow_ofs_y - dsc->shadow_spread;
core_area.y2 = coords->y2 + dsc->shadow_ofs_y + dsc->shadow_spread;
/*Calculate the bounding box of the shadow*/
lv_area_t shadow_area;
shadow_area.x1 = core_area.x1 - dsc->shadow_width / 2 - 1;
shadow_area.x2 = core_area.x2 + dsc->shadow_width / 2 + 1;
shadow_area.y1 = core_area.y1 - dsc->shadow_width / 2 - 1;
shadow_area.y2 = core_area.y2 + dsc->shadow_width / 2 + 1;
lv_opa_t opa = dsc->shadow_opa;
if(opa > LV_OPA_MAX) opa = LV_OPA_COVER;
/*Get clipped draw area which is the real draw area.
*It is always the same or inside `shadow_area`*/
lv_area_t draw_area;
if(!_lv_area_intersect(&draw_area, &shadow_area, draw_ctx->clip_area)) return;
/*Consider 1 px smaller bg to be sure the edge will be covered by the shadow*/
lv_area_t bg_area;
lv_area_copy(&bg_area, coords);
lv_area_increase(&bg_area, -1, -1);
/*Get the clamped radius*/
int32_t r_bg = dsc->radius;
lv_coord_t short_side = LV_MIN(lv_area_get_width(&bg_area), lv_area_get_height(&bg_area));
if(r_bg > short_side >> 1) r_bg = short_side >> 1;
/*Get the clamped radius*/
int32_t r_sh = dsc->radius;
short_side = LV_MIN(lv_area_get_width(&core_area), lv_area_get_height(&core_area));
if(r_sh > short_side >> 1) r_sh = short_side >> 1;
/*Get how many pixels are affected by the blur on the corners*/
int32_t corner_size = dsc->shadow_width + r_sh;
lv_opa_t * sh_buf;
#if LV_SHADOW_CACHE_SIZE
if(sh_cache_size == corner_size && sh_cache_r == r_sh) {
/*Use the cache if available*/
sh_buf = lv_mem_buf_get(corner_size * corner_size);
lv_memcpy(sh_buf, sh_cache, corner_size * corner_size);
}
else {
/*A larger buffer is required for calculation*/
sh_buf = lv_mem_buf_get(corner_size * corner_size * sizeof(uint16_t));
shadow_draw_corner_buf(&core_area, (uint16_t *)sh_buf, dsc->shadow_width, r_sh);
/*Cache the corner if it fits into the cache size*/
if((uint32_t)corner_size * corner_size < sizeof(sh_cache)) {
lv_memcpy(sh_cache, sh_buf, corner_size * corner_size);
sh_cache_size = corner_size;
sh_cache_r = r_sh;
}
}
#else
sh_buf = lv_mem_buf_get(corner_size * corner_size * sizeof(uint16_t));
shadow_draw_corner_buf(&core_area, (uint16_t *)sh_buf, dsc->shadow_width, r_sh);
#endif
/*Skip a lot of masking if the background will cover the shadow that would be masked out*/
bool mask_any = lv_draw_mask_is_any(&shadow_area);
bool simple = true;
if(mask_any || dsc->bg_opa < LV_OPA_COVER || dsc->blend_mode != LV_BLEND_MODE_NORMAL) simple = false;
/*Create a radius mask to clip remove shadow on the bg area*/
lv_draw_mask_radius_param_t mask_rout_param;
int16_t mask_rout_id = LV_MASK_ID_INV;
if(!simple) {
lv_draw_mask_radius_init(&mask_rout_param, &bg_area, r_bg, true);
mask_rout_id = lv_draw_mask_add(&mask_rout_param, NULL);
}
lv_opa_t * mask_buf = lv_mem_buf_get(lv_area_get_width(&shadow_area));
lv_area_t blend_area;
lv_area_t clip_area_sub;
lv_opa_t * sh_buf_tmp;
lv_coord_t y;
bool simple_sub;
lv_draw_sw_blend_dsc_t blend_dsc;
lv_memset_00(&blend_dsc, sizeof(blend_dsc));
blend_dsc.blend_area = &blend_area;
blend_dsc.mask_area = &blend_area;
blend_dsc.mask_buf = mask_buf;
blend_dsc.color = dsc->shadow_color;
blend_dsc.opa = dsc->shadow_opa;
blend_dsc.blend_mode = dsc->blend_mode;
lv_coord_t w_half = shadow_area.x1 + lv_area_get_width(&shadow_area) / 2;
lv_coord_t h_half = shadow_area.y1 + lv_area_get_height(&shadow_area) / 2;
/*Draw the corners if they are on the current clip area and not fully covered by the bg*/
/*Top right corner*/
blend_area.x2 = shadow_area.x2;
blend_area.x1 = shadow_area.x2 - corner_size + 1;
blend_area.y1 = shadow_area.y1;
blend_area.y2 = shadow_area.y1 + corner_size - 1;
/*Do not overdraw the other top corners*/
blend_area.x1 = LV_MAX(blend_area.x1, w_half);
blend_area.y2 = LV_MIN(blend_area.y2, h_half);
if(_lv_area_intersect(&clip_area_sub, &blend_area, draw_ctx->clip_area) &&
!_lv_area_is_in(&clip_area_sub, &bg_area, r_bg)) {
lv_coord_t w = lv_area_get_width(&clip_area_sub);
sh_buf_tmp = sh_buf;
sh_buf_tmp += (clip_area_sub.y1 - shadow_area.y1) * corner_size;
sh_buf_tmp += clip_area_sub.x1 - (shadow_area.x2 - corner_size + 1);
/*Do not mask if out of the bg*/
if(simple && _lv_area_is_out(&clip_area_sub, &bg_area, r_bg)) simple_sub = true;
else simple_sub = simple;
if(w > 0) {
blend_dsc.mask_buf = mask_buf;
blend_area.x1 = clip_area_sub.x1;
blend_area.x2 = clip_area_sub.x2;
blend_dsc.mask_res = LV_DRAW_MASK_RES_CHANGED; /*In simple mode it won't be overwritten*/
for(y = clip_area_sub.y1; y <= clip_area_sub.y2; y++) {
blend_area.y1 = y;
blend_area.y2 = y;
if(!simple_sub) {
lv_memcpy(mask_buf, sh_buf_tmp, corner_size);
blend_dsc.mask_res = lv_draw_mask_apply(mask_buf, clip_area_sub.x1, y, w);
if(blend_dsc.mask_res == LV_DRAW_MASK_RES_FULL_COVER) blend_dsc.mask_res = LV_DRAW_MASK_RES_CHANGED;
}
else {
blend_dsc.mask_buf = sh_buf_tmp;
}
lv_draw_sw_blend(draw_ctx, &blend_dsc);
sh_buf_tmp += corner_size;
}
}
}
/*Bottom right corner.
*Almost the same as top right just read the lines of `sh_buf` from then end*/
blend_area.x2 = shadow_area.x2;
blend_area.x1 = shadow_area.x2 - corner_size + 1;
blend_area.y1 = shadow_area.y2 - corner_size + 1;
blend_area.y2 = shadow_area.y2;
/*Do not overdraw the other corners*/
blend_area.x1 = LV_MAX(blend_area.x1, w_half);
blend_area.y1 = LV_MAX(blend_area.y1, h_half + 1);
if(_lv_area_intersect(&clip_area_sub, &blend_area, draw_ctx->clip_area) &&
!_lv_area_is_in(&clip_area_sub, &bg_area, r_bg)) {
lv_coord_t w = lv_area_get_width(&clip_area_sub);
sh_buf_tmp = sh_buf;
sh_buf_tmp += (blend_area.y2 - clip_area_sub.y2) * corner_size;
sh_buf_tmp += clip_area_sub.x1 - (shadow_area.x2 - corner_size + 1);
/*Do not mask if out of the bg*/
if(simple && _lv_area_is_out(&clip_area_sub, &bg_area, r_bg)) simple_sub = true;
else simple_sub = simple;
if(w > 0) {
blend_dsc.mask_buf = mask_buf;
blend_area.x1 = clip_area_sub.x1;
blend_area.x2 = clip_area_sub.x2;
blend_dsc.mask_res = LV_DRAW_MASK_RES_CHANGED; /*In simple mode it won't be overwritten*/
for(y = clip_area_sub.y2; y >= clip_area_sub.y1; y--) {
blend_area.y1 = y;
blend_area.y2 = y;
if(!simple_sub) {
lv_memcpy(mask_buf, sh_buf_tmp, corner_size);
blend_dsc.mask_res = lv_draw_mask_apply(mask_buf, clip_area_sub.x1, y, w);
if(blend_dsc.mask_res == LV_DRAW_MASK_RES_FULL_COVER) blend_dsc.mask_res = LV_DRAW_MASK_RES_CHANGED;
}
else {
blend_dsc.mask_buf = sh_buf_tmp;
}
lv_draw_sw_blend(draw_ctx, &blend_dsc);
sh_buf_tmp += corner_size;
}
}
}
/*Top side*/
blend_area.x1 = shadow_area.x1 + corner_size;
blend_area.x2 = shadow_area.x2 - corner_size;
blend_area.y1 = shadow_area.y1;
blend_area.y2 = shadow_area.y1 + corner_size - 1;
blend_area.y2 = LV_MIN(blend_area.y2, h_half);
if(_lv_area_intersect(&clip_area_sub, &blend_area, draw_ctx->clip_area) &&
!_lv_area_is_in(&clip_area_sub, &bg_area, r_bg)) {
lv_coord_t w = lv_area_get_width(&clip_area_sub);
sh_buf_tmp = sh_buf;
sh_buf_tmp += (clip_area_sub.y1 - blend_area.y1) * corner_size;
/*Do not mask if out of the bg*/
if(simple && _lv_area_is_out(&clip_area_sub, &bg_area, r_bg)) simple_sub = true;
else simple_sub = simple;
if(w > 0) {
if(!simple_sub) {
blend_dsc.mask_buf = mask_buf;
}
else {
blend_dsc.mask_buf = NULL;
}
blend_area.x1 = clip_area_sub.x1;
blend_area.x2 = clip_area_sub.x2;
for(y = clip_area_sub.y1; y <= clip_area_sub.y2; y++) {
blend_area.y1 = y;
blend_area.y2 = y;
if(!simple_sub) {
lv_memset(mask_buf, sh_buf_tmp[0], w);
blend_dsc.mask_res = lv_draw_mask_apply(mask_buf, clip_area_sub.x1, y, w);
if(blend_dsc.mask_res == LV_DRAW_MASK_RES_FULL_COVER) blend_dsc.mask_res = LV_DRAW_MASK_RES_CHANGED;
lv_draw_sw_blend(draw_ctx, &blend_dsc);
}
else {
blend_dsc.opa = opa == LV_OPA_COVER ? sh_buf_tmp[0] : (sh_buf_tmp[0] * dsc->shadow_opa) >> 8;
lv_draw_sw_blend(draw_ctx, &blend_dsc);
}
sh_buf_tmp += corner_size;
}
}
}
blend_dsc.opa = dsc->shadow_opa; /*Restore*/
/*Bottom side*/
blend_area.x1 = shadow_area.x1 + corner_size;
blend_area.x2 = shadow_area.x2 - corner_size;
blend_area.y1 = shadow_area.y2 - corner_size + 1;
blend_area.y2 = shadow_area.y2;
blend_area.y1 = LV_MAX(blend_area.y1, h_half + 1);
if(_lv_area_intersect(&clip_area_sub, &blend_area, draw_ctx->clip_area) &&
!_lv_area_is_in(&clip_area_sub, &bg_area, r_bg)) {
lv_coord_t w = lv_area_get_width(&clip_area_sub);
sh_buf_tmp = sh_buf;
sh_buf_tmp += (blend_area.y2 - clip_area_sub.y2) * corner_size;
if(w > 0) {
/*Do not mask if out of the bg*/
if(simple && _lv_area_is_out(&clip_area_sub, &bg_area, r_bg)) simple_sub = true;
else simple_sub = simple;
if(!simple_sub) {
blend_dsc.mask_buf = mask_buf;
}
else {
blend_dsc.mask_buf = NULL;
}
blend_area.x1 = clip_area_sub.x1;
blend_area.x2 = clip_area_sub.x2;
for(y = clip_area_sub.y2; y >= clip_area_sub.y1; y--) {
blend_area.y1 = y;
blend_area.y2 = y;
/*Do not mask if out of the bg*/
if(simple && _lv_area_is_out(&clip_area_sub, &bg_area, r_bg)) simple_sub = true;
else simple_sub = simple;
if(!simple_sub) {
lv_memset(mask_buf, sh_buf_tmp[0], w);
blend_dsc.mask_res = lv_draw_mask_apply(mask_buf, clip_area_sub.x1, y, w);
if(blend_dsc.mask_res == LV_DRAW_MASK_RES_FULL_COVER) blend_dsc.mask_res = LV_DRAW_MASK_RES_CHANGED;
lv_draw_sw_blend(draw_ctx, &blend_dsc);
}
else {
blend_dsc.opa = opa == LV_OPA_COVER ? sh_buf_tmp[0] : (sh_buf_tmp[0] * dsc->shadow_opa) >> 8;
lv_draw_sw_blend(draw_ctx, &blend_dsc);
}
sh_buf_tmp += corner_size;
}
}
}
blend_dsc.opa = dsc->shadow_opa; /*Restore*/
/*Right side*/
blend_area.x1 = shadow_area.x2 - corner_size + 1;
blend_area.x2 = shadow_area.x2;
blend_area.y1 = shadow_area.y1 + corner_size;
blend_area.y2 = shadow_area.y2 - corner_size;
/*Do not overdraw the other corners*/
blend_area.y1 = LV_MIN(blend_area.y1, h_half + 1);
blend_area.y2 = LV_MAX(blend_area.y2, h_half);
blend_area.x1 = LV_MAX(blend_area.x1, w_half);
if(_lv_area_intersect(&clip_area_sub, &blend_area, draw_ctx->clip_area) &&
!_lv_area_is_in(&clip_area_sub, &bg_area, r_bg)) {
lv_coord_t w = lv_area_get_width(&clip_area_sub);
sh_buf_tmp = sh_buf;
sh_buf_tmp += (corner_size - 1) * corner_size;
sh_buf_tmp += clip_area_sub.x1 - (shadow_area.x2 - corner_size + 1);
/*Do not mask if out of the bg*/
if(simple && _lv_area_is_out(&clip_area_sub, &bg_area, r_bg)) simple_sub = true;
else simple_sub = simple;
blend_dsc.mask_buf = simple_sub ? sh_buf_tmp : mask_buf;
if(w > 0) {
blend_area.x1 = clip_area_sub.x1;
blend_area.x2 = clip_area_sub.x2;
blend_dsc.mask_res = LV_DRAW_MASK_RES_CHANGED; /*In simple mode it won't be overwritten*/
for(y = clip_area_sub.y1; y <= clip_area_sub.y2; y++) {
blend_area.y1 = y;
blend_area.y2 = y;
if(!simple_sub) {
lv_memcpy(mask_buf, sh_buf_tmp, w);
blend_dsc.mask_res = lv_draw_mask_apply(mask_buf, clip_area_sub.x1, y, w);
if(blend_dsc.mask_res == LV_DRAW_MASK_RES_FULL_COVER) blend_dsc.mask_res = LV_DRAW_MASK_RES_CHANGED;
}
lv_draw_sw_blend(draw_ctx, &blend_dsc);
}
}
}
/*Mirror the shadow corner buffer horizontally*/
sh_buf_tmp = sh_buf ;
for(y = 0; y < corner_size; y++) {
int32_t x;
lv_opa_t * start = sh_buf_tmp;
lv_opa_t * end = sh_buf_tmp + corner_size - 1;
for(x = 0; x < corner_size / 2; x++) {
lv_opa_t tmp = *start;
*start = *end;
*end = tmp;
start++;
end--;
}
sh_buf_tmp += corner_size;
}
/*Left side*/
blend_area.x1 = shadow_area.x1;
blend_area.x2 = shadow_area.x1 + corner_size - 1;
blend_area.y1 = shadow_area.y1 + corner_size;
blend_area.y2 = shadow_area.y2 - corner_size;
/*Do not overdraw the other corners*/
blend_area.y1 = LV_MIN(blend_area.y1, h_half + 1);
blend_area.y2 = LV_MAX(blend_area.y2, h_half);
blend_area.x2 = LV_MIN(blend_area.x2, w_half - 1);
if(_lv_area_intersect(&clip_area_sub, &blend_area, draw_ctx->clip_area) &&
!_lv_area_is_in(&clip_area_sub, &bg_area, r_bg)) {
lv_coord_t w = lv_area_get_width(&clip_area_sub);
sh_buf_tmp = sh_buf;
sh_buf_tmp += (corner_size - 1) * corner_size;
sh_buf_tmp += clip_area_sub.x1 - blend_area.x1;
/*Do not mask if out of the bg*/
if(simple && _lv_area_is_out(&clip_area_sub, &bg_area, r_bg)) simple_sub = true;
else simple_sub = simple;
blend_dsc.mask_buf = simple_sub ? sh_buf_tmp : mask_buf;
if(w > 0) {
blend_area.x1 = clip_area_sub.x1;
blend_area.x2 = clip_area_sub.x2;
blend_dsc.mask_res = LV_DRAW_MASK_RES_CHANGED; /*In simple mode it won't be overwritten*/
for(y = clip_area_sub.y1; y <= clip_area_sub.y2; y++) {
blend_area.y1 = y;
blend_area.y2 = y;
if(!simple_sub) {
lv_memcpy(mask_buf, sh_buf_tmp, w);
blend_dsc.mask_res = lv_draw_mask_apply(mask_buf, clip_area_sub.x1, y, w);
if(blend_dsc.mask_res == LV_DRAW_MASK_RES_FULL_COVER) blend_dsc.mask_res = LV_DRAW_MASK_RES_CHANGED;
}
lv_draw_sw_blend(draw_ctx, &blend_dsc);
}
}
}
/*Top left corner*/
blend_area.x1 = shadow_area.x1;
blend_area.x2 = shadow_area.x1 + corner_size - 1;
blend_area.y1 = shadow_area.y1;
blend_area.y2 = shadow_area.y1 + corner_size - 1;
/*Do not overdraw the other corners*/
blend_area.x2 = LV_MIN(blend_area.x2, w_half - 1);
blend_area.y2 = LV_MIN(blend_area.y2, h_half);
if(_lv_area_intersect(&clip_area_sub, &blend_area, draw_ctx->clip_area) &&
!_lv_area_is_in(&clip_area_sub, &bg_area, r_bg)) {
lv_coord_t w = lv_area_get_width(&clip_area_sub);
sh_buf_tmp = sh_buf;
sh_buf_tmp += (clip_area_sub.y1 - blend_area.y1) * corner_size;
sh_buf_tmp += clip_area_sub.x1 - blend_area.x1;
/*Do not mask if out of the bg*/
if(simple && _lv_area_is_out(&clip_area_sub, &bg_area, r_bg)) simple_sub = true;
else simple_sub = simple;
blend_dsc.mask_buf = mask_buf;
if(w > 0) {
blend_area.x1 = clip_area_sub.x1;
blend_area.x2 = clip_area_sub.x2;
blend_dsc.mask_res = LV_DRAW_MASK_RES_CHANGED; /*In simple mode it won't be overwritten*/
for(y = clip_area_sub.y1; y <= clip_area_sub.y2; y++) {
blend_area.y1 = y;
blend_area.y2 = y;
if(!simple_sub) {
lv_memcpy(mask_buf, sh_buf_tmp, corner_size);
blend_dsc.mask_res = lv_draw_mask_apply(mask_buf, clip_area_sub.x1, y, w);
if(blend_dsc.mask_res == LV_DRAW_MASK_RES_FULL_COVER) blend_dsc.mask_res = LV_DRAW_MASK_RES_CHANGED;
}
else {
blend_dsc.mask_buf = sh_buf_tmp;
}
lv_draw_sw_blend(draw_ctx, &blend_dsc);
sh_buf_tmp += corner_size;
}
}
}
/*Bottom left corner.
*Almost the same as bottom right just read the lines of `sh_buf` from then end*/
blend_area.x1 = shadow_area.x1 ;
blend_area.x2 = shadow_area.x1 + corner_size - 1;
blend_area.y1 = shadow_area.y2 - corner_size + 1;
blend_area.y2 = shadow_area.y2;
/*Do not overdraw the other corners*/
blend_area.y1 = LV_MAX(blend_area.y1, h_half + 1);
blend_area.x2 = LV_MIN(blend_area.x2, w_half - 1);
if(_lv_area_intersect(&clip_area_sub, &blend_area, draw_ctx->clip_area) &&
!_lv_area_is_in(&clip_area_sub, &bg_area, r_bg)) {
lv_coord_t w = lv_area_get_width(&clip_area_sub);
sh_buf_tmp = sh_buf;
sh_buf_tmp += (blend_area.y2 - clip_area_sub.y2) * corner_size;
sh_buf_tmp += clip_area_sub.x1 - blend_area.x1;
/*Do not mask if out of the bg*/
if(simple && _lv_area_is_out(&clip_area_sub, &bg_area, r_bg)) simple_sub = true;
else simple_sub = simple;
blend_dsc.mask_buf = mask_buf;
if(w > 0) {
blend_area.x1 = clip_area_sub.x1;
blend_area.x2 = clip_area_sub.x2;
blend_dsc.mask_res = LV_DRAW_MASK_RES_CHANGED; /*In simple mode it won't be overwritten*/
for(y = clip_area_sub.y2; y >= clip_area_sub.y1; y--) {
blend_area.y1 = y;
blend_area.y2 = y;
if(!simple_sub) {
lv_memcpy(mask_buf, sh_buf_tmp, corner_size);
blend_dsc.mask_res = lv_draw_mask_apply(mask_buf, clip_area_sub.x1, y, w);
if(blend_dsc.mask_res == LV_DRAW_MASK_RES_FULL_COVER) blend_dsc.mask_res = LV_DRAW_MASK_RES_CHANGED;
}
else {
blend_dsc.mask_buf = sh_buf_tmp;
}
lv_draw_sw_blend(draw_ctx, &blend_dsc);
sh_buf_tmp += corner_size;
}
}
}
/*Draw the center rectangle.*/
blend_area.x1 = shadow_area.x1 + corner_size ;
blend_area.x2 = shadow_area.x2 - corner_size;
blend_area.y1 = shadow_area.y1 + corner_size;
blend_area.y2 = shadow_area.y2 - corner_size;
blend_dsc.mask_buf = mask_buf;
if(_lv_area_intersect(&clip_area_sub, &blend_area, draw_ctx->clip_area) &&
!_lv_area_is_in(&clip_area_sub, &bg_area, r_bg)) {
lv_coord_t w = lv_area_get_width(&clip_area_sub);
if(w > 0) {
blend_area.x1 = clip_area_sub.x1;
blend_area.x2 = clip_area_sub.x2;
for(y = clip_area_sub.y1; y <= clip_area_sub.y2; y++) {
blend_area.y1 = y;
blend_area.y2 = y;
lv_memset_ff(mask_buf, w);
blend_dsc.mask_res = lv_draw_mask_apply(mask_buf, clip_area_sub.x1, y, w);
lv_draw_sw_blend(draw_ctx, &blend_dsc);
}
}
}
if(!simple) {
lv_draw_mask_free_param(&mask_rout_param);
lv_draw_mask_remove_id(mask_rout_id);
}
lv_mem_buf_release(sh_buf);
lv_mem_buf_release(mask_buf);
}
/**
* Calculate a blurred corner
* @param coords Coordinates of the shadow
* @param sh_buf a buffer to store the result. Its size should be `(sw + r)^2 * 2`
* @param sw shadow width
* @param r radius
*/
LV_ATTRIBUTE_FAST_MEM static void shadow_draw_corner_buf(const lv_area_t * coords, uint16_t * sh_buf, lv_coord_t sw,
lv_coord_t r)
{
int32_t sw_ori = sw;
int32_t size = sw_ori + r;
lv_area_t sh_area;
lv_area_copy(&sh_area, coords);
sh_area.x2 = sw / 2 + r - 1 - ((sw & 1) ? 0 : 1);
sh_area.y1 = sw / 2 + 1;
sh_area.x1 = sh_area.x2 - lv_area_get_width(coords);
sh_area.y2 = sh_area.y1 + lv_area_get_height(coords);
lv_draw_mask_radius_param_t mask_param;
lv_draw_mask_radius_init(&mask_param, &sh_area, r, false);
#if SHADOW_ENHANCE
/*Set half shadow width width because blur will be repeated*/
if(sw_ori == 1) sw = 1;
else sw = sw_ori >> 1;
#endif
int32_t y;
lv_opa_t * mask_line = lv_mem_buf_get(size);
uint16_t * sh_ups_tmp_buf = (uint16_t *)sh_buf;
for(y = 0; y < size; y++) {
lv_memset_ff(mask_line, size);
lv_draw_mask_res_t mask_res = mask_param.dsc.cb(mask_line, 0, y, size, &mask_param);
if(mask_res == LV_DRAW_MASK_RES_TRANSP) {
lv_memset_00(sh_ups_tmp_buf, size * sizeof(sh_ups_tmp_buf[0]));
}
else {
int32_t i;
sh_ups_tmp_buf[0] = (mask_line[0] << SHADOW_UPSCALE_SHIFT) / sw;
for(i = 1; i < size; i++) {
if(mask_line[i] == mask_line[i - 1]) sh_ups_tmp_buf[i] = sh_ups_tmp_buf[i - 1];
else sh_ups_tmp_buf[i] = (mask_line[i] << SHADOW_UPSCALE_SHIFT) / sw;
}
}
sh_ups_tmp_buf += size;
}
lv_mem_buf_release(mask_line);
lv_draw_mask_free_param(&mask_param);
if(sw == 1) {
int32_t i;
lv_opa_t * res_buf = (lv_opa_t *)sh_buf;
for(i = 0; i < size * size; i++) {
res_buf[i] = (sh_buf[i] >> SHADOW_UPSCALE_SHIFT);
}
return;
}
shadow_blur_corner(size, sw, sh_buf);
#if SHADOW_ENHANCE == 0
/*The result is required in lv_opa_t not uint16_t*/
uint32_t x;
lv_opa_t * res_buf = (lv_opa_t *)sh_buf;
for(x = 0; x < size * size; x++) {
res_buf[x] = sh_buf[x];
}
#else
sw += sw_ori & 1;
if(sw > 1) {
uint32_t i;
uint32_t max_v_div = (LV_OPA_COVER << SHADOW_UPSCALE_SHIFT) / sw;
for(i = 0; i < (uint32_t)size * size; i++) {
if(sh_buf[i] == 0) continue;
else if(sh_buf[i] == LV_OPA_COVER) sh_buf[i] = max_v_div;
else sh_buf[i] = (sh_buf[i] << SHADOW_UPSCALE_SHIFT) / sw;
}
shadow_blur_corner(size, sw, sh_buf);
}
int32_t x;
lv_opa_t * res_buf = (lv_opa_t *)sh_buf;
for(x = 0; x < size * size; x++) {
res_buf[x] = sh_buf[x];
}
#endif
}
LV_ATTRIBUTE_FAST_MEM static void shadow_blur_corner(lv_coord_t size, lv_coord_t sw, uint16_t * sh_ups_buf)
{
int32_t s_left = sw >> 1;
int32_t s_right = (sw >> 1);
if((sw & 1) == 0) s_left--;
/*Horizontal blur*/
uint16_t * sh_ups_blur_buf = lv_mem_buf_get(size * sizeof(uint16_t));
int32_t x;
int32_t y;
uint16_t * sh_ups_tmp_buf = sh_ups_buf;
for(y = 0; y < size; y++) {
int32_t v = sh_ups_tmp_buf[size - 1] * sw;
for(x = size - 1; x >= 0; x--) {
sh_ups_blur_buf[x] = v;
/*Forget the right pixel*/
uint32_t right_val = 0;
if(x + s_right < size) right_val = sh_ups_tmp_buf[x + s_right];
v -= right_val;
/*Add the left pixel*/
uint32_t left_val;
if(x - s_left - 1 < 0) left_val = sh_ups_tmp_buf[0];
else left_val = sh_ups_tmp_buf[x - s_left - 1];
v += left_val;
}
lv_memcpy(sh_ups_tmp_buf, sh_ups_blur_buf, size * sizeof(uint16_t));
sh_ups_tmp_buf += size;
}
/*Vertical blur*/
uint32_t i;
uint32_t max_v = LV_OPA_COVER << SHADOW_UPSCALE_SHIFT;
uint32_t max_v_div = max_v / sw;
for(i = 0; i < (uint32_t)size * size; i++) {
if(sh_ups_buf[i] == 0) continue;
else if(sh_ups_buf[i] == max_v) sh_ups_buf[i] = max_v_div;
else sh_ups_buf[i] = sh_ups_buf[i] / sw;
}
for(x = 0; x < size; x++) {
sh_ups_tmp_buf = &sh_ups_buf[x];
int32_t v = sh_ups_tmp_buf[0] * sw;
for(y = 0; y < size ; y++, sh_ups_tmp_buf += size) {
sh_ups_blur_buf[y] = v < 0 ? 0 : (v >> SHADOW_UPSCALE_SHIFT);
/*Forget the top pixel*/
uint32_t top_val;
if(y - s_right <= 0) top_val = sh_ups_tmp_buf[0];
else top_val = sh_ups_buf[(y - s_right) * size + x];
v -= top_val;
/*Add the bottom pixel*/
uint32_t bottom_val;
if(y + s_left + 1 < size) bottom_val = sh_ups_buf[(y + s_left + 1) * size + x];
else bottom_val = sh_ups_buf[(size - 1) * size + x];
v += bottom_val;
}
/*Write back the result into `sh_ups_buf`*/
sh_ups_tmp_buf = &sh_ups_buf[x];
for(y = 0; y < size; y++, sh_ups_tmp_buf += size) {
(*sh_ups_tmp_buf) = sh_ups_blur_buf[y];
}
}
lv_mem_buf_release(sh_ups_blur_buf);
}
#endif
static void draw_outline(lv_draw_ctx_t * draw_ctx, const lv_draw_rect_dsc_t * dsc, const lv_area_t * coords)
{
if(dsc->outline_opa <= LV_OPA_MIN) return;
if(dsc->outline_width == 0) return;
lv_opa_t opa = dsc->outline_opa;
if(opa > LV_OPA_MAX) opa = LV_OPA_COVER;
/*Get the inner radius*/
lv_area_t area_inner;
lv_area_copy(&area_inner, coords);
/*Bring the outline closer to make sure there is no color bleeding with pad=0*/
lv_coord_t pad = dsc->outline_pad - 1;
area_inner.x1 -= pad;
area_inner.y1 -= pad;
area_inner.x2 += pad;
area_inner.y2 += pad;
lv_area_t area_outer;
lv_area_copy(&area_outer, &area_inner);
area_outer.x1 -= dsc->outline_width;
area_outer.x2 += dsc->outline_width;
area_outer.y1 -= dsc->outline_width;
area_outer.y2 += dsc->outline_width;
int32_t inner_w = lv_area_get_width(&area_inner);
int32_t inner_h = lv_area_get_height(&area_inner);
int32_t rin = dsc->radius;
int32_t short_side = LV_MIN(inner_w, inner_h);
if(rin > short_side >> 1) rin = short_side >> 1;
lv_coord_t rout = rin + dsc->outline_width;
draw_border_generic(draw_ctx, &area_outer, &area_inner, rout, rin, dsc->outline_color, dsc->outline_opa,
dsc->blend_mode);
}
void draw_border_generic(lv_draw_ctx_t * draw_ctx, const lv_area_t * outer_area, const lv_area_t * inner_area,
lv_coord_t rout, lv_coord_t rin, lv_color_t color, lv_opa_t opa, lv_blend_mode_t blend_mode)
{
opa = opa >= LV_OPA_COVER ? LV_OPA_COVER : opa;
bool mask_any = lv_draw_mask_is_any(outer_area);
if(!mask_any && rout == 0 && rin == 0) {
draw_border_simple(draw_ctx, outer_area, inner_area, color, opa);
return;
}
#if LV_DRAW_COMPLEX
/*Get clipped draw area which is the real draw area.
*It is always the same or inside `coords`*/
lv_area_t draw_area;
if(!_lv_area_intersect(&draw_area, outer_area, draw_ctx->clip_area)) return;
int32_t draw_area_w = lv_area_get_width(&draw_area);
lv_draw_sw_blend_dsc_t blend_dsc;
lv_memset_00(&blend_dsc, sizeof(blend_dsc));
blend_dsc.mask_buf = lv_mem_buf_get(draw_area_w);;
/*Create mask for the outer area*/
int16_t mask_rout_id = LV_MASK_ID_INV;
lv_draw_mask_radius_param_t mask_rout_param;
if(rout > 0) {
lv_draw_mask_radius_init(&mask_rout_param, outer_area, rout, false);
mask_rout_id = lv_draw_mask_add(&mask_rout_param, NULL);
}
/*Create mask for the inner mask*/
lv_draw_mask_radius_param_t mask_rin_param;
lv_draw_mask_radius_init(&mask_rin_param, inner_area, rin, true);
int16_t mask_rin_id = lv_draw_mask_add(&mask_rin_param, NULL);
int32_t h;
lv_area_t blend_area;
blend_dsc.blend_area = &blend_area;
blend_dsc.mask_area = &blend_area;
blend_dsc.color = color;
blend_dsc.opa = opa;
blend_dsc.blend_mode = blend_mode;
/*Calculate the x and y coordinates where the straight parts area*/
lv_area_t core_area;
core_area.x1 = LV_MAX(outer_area->x1 + rout, inner_area->x1);
core_area.x2 = LV_MIN(outer_area->x2 - rout, inner_area->x2);
core_area.y1 = LV_MAX(outer_area->y1 + rout, inner_area->y1);
core_area.y2 = LV_MIN(outer_area->y2 - rout, inner_area->y2);
lv_coord_t core_w = lv_area_get_width(&core_area);
bool top_side = outer_area->y1 <= inner_area->y1 ? true : false;
bool bottom_side = outer_area->y2 >= inner_area->y2 ? true : false;
/*If there is other masks, need to draw line by line*/
if(mask_any) {
blend_area.x1 = draw_area.x1;
blend_area.x2 = draw_area.x2;
for(h = draw_area.y1; h <= draw_area.y2; h++) {
if(!top_side && h < core_area.y1) continue;
if(!bottom_side && h > core_area.y2) break;
blend_area.y1 = h;
blend_area.y2 = h;
lv_memset_ff(blend_dsc.mask_buf, draw_area_w);
blend_dsc.mask_res = lv_draw_mask_apply(blend_dsc.mask_buf, draw_area.x1, h, draw_area_w);
lv_draw_sw_blend(draw_ctx, &blend_dsc);
}
lv_draw_mask_free_param(&mask_rin_param);
lv_draw_mask_remove_id(mask_rin_id);
if(mask_rout_id != LV_MASK_ID_INV) {
lv_draw_mask_free_param(&mask_rout_param);
lv_draw_mask_remove_id(mask_rout_id);
}
lv_mem_buf_release(blend_dsc.mask_buf);
return;
}
/*No masks*/
bool left_side = outer_area->x1 <= inner_area->x1 ? true : false;
bool right_side = outer_area->x2 >= inner_area->x2 ? true : false;
bool split_hor = true;
if(left_side && right_side && top_side && bottom_side &&
core_w < SPLIT_LIMIT) {
split_hor = false;
}
blend_dsc.mask_res = LV_DRAW_MASK_RES_FULL_COVER;
/*Draw the straight lines first if they are long enough*/
if(top_side && split_hor) {
blend_area.x1 = core_area.x1;
blend_area.x2 = core_area.x2;
blend_area.y1 = outer_area->y1;
blend_area.y2 = inner_area->y1 - 1;
lv_draw_sw_blend(draw_ctx, &blend_dsc);
}
if(bottom_side && split_hor) {
blend_area.x1 = core_area.x1;
blend_area.x2 = core_area.x2;
blend_area.y1 = inner_area->y2 + 1;
blend_area.y2 = outer_area->y2;
lv_draw_sw_blend(draw_ctx, &blend_dsc);
}
if(left_side) {
blend_area.x1 = outer_area->x1;
blend_area.x2 = inner_area->x1 - 1;
blend_area.y1 = core_area.y1;
blend_area.y2 = core_area.y2;
lv_draw_sw_blend(draw_ctx, &blend_dsc);
}
if(right_side) {
blend_area.x1 = inner_area->x2 + 1;
blend_area.x2 = outer_area->x2;
blend_area.y1 = core_area.y1;
blend_area.y2 = core_area.y2;
lv_draw_sw_blend(draw_ctx, &blend_dsc);
}
/*Draw the corners*/
lv_coord_t blend_w;
/*Left and right corner together is they close to eachother*/
if(!split_hor) {
/*Calculate the top corner and mirror it to the bottom*/
blend_area.x1 = draw_area.x1;
blend_area.x2 = draw_area.x2;
lv_coord_t max_h = LV_MAX(rout, outer_area->y1 - inner_area->y1);
for(h = 0; h < max_h; h++) {
lv_coord_t top_y = outer_area->y1 + h;
lv_coord_t bottom_y = outer_area->y2 - h;
if(top_y < draw_area.y1 && bottom_y > draw_area.y2) continue; /*This line is clipped now*/
lv_memset_ff(blend_dsc.mask_buf, draw_area_w);
blend_dsc.mask_res = lv_draw_mask_apply(blend_dsc.mask_buf, blend_area.x1, top_y, draw_area_w);
if(top_y >= draw_area.y1) {
blend_area.y1 = top_y;
blend_area.y2 = top_y;
lv_draw_sw_blend(draw_ctx, &blend_dsc);
}
if(bottom_y <= draw_area.y2) {
blend_area.y1 = bottom_y;
blend_area.y2 = bottom_y;
lv_draw_sw_blend(draw_ctx, &blend_dsc);
}
}
}
else {
/*Left corners*/
blend_area.x1 = draw_area.x1;
blend_area.x2 = LV_MIN(draw_area.x2, core_area.x1 - 1);
blend_w = lv_area_get_width(&blend_area);
if(blend_w > 0) {
if(left_side || top_side) {
for(h = draw_area.y1; h < core_area.y1; h++) {
blend_area.y1 = h;
blend_area.y2 = h;
lv_memset_ff(blend_dsc.mask_buf, blend_w);
blend_dsc.mask_res = lv_draw_mask_apply(blend_dsc.mask_buf, blend_area.x1, h, blend_w);
lv_draw_sw_blend(draw_ctx, &blend_dsc);
}
}
if(left_side || bottom_side) {
for(h = core_area.y2 + 1; h <= draw_area.y2; h++) {
blend_area.y1 = h;
blend_area.y2 = h;
lv_memset_ff(blend_dsc.mask_buf, blend_w);
blend_dsc.mask_res = lv_draw_mask_apply(blend_dsc.mask_buf, blend_area.x1, h, blend_w);
lv_draw_sw_blend(draw_ctx, &blend_dsc);
}
}
}
/*Right corners*/
blend_area.x1 = LV_MAX(draw_area.x1, core_area.x2 + 1);
blend_area.x2 = draw_area.x2;
blend_w = lv_area_get_width(&blend_area);
if(blend_w > 0) {
if(right_side || top_side) {
for(h = draw_area.y1; h < core_area.y1; h++) {
blend_area.y1 = h;
blend_area.y2 = h;
lv_memset_ff(blend_dsc.mask_buf, blend_w);
blend_dsc.mask_res = lv_draw_mask_apply(blend_dsc.mask_buf, blend_area.x1, h, blend_w);
lv_draw_sw_blend(draw_ctx, &blend_dsc);
}
}
if(right_side || bottom_side) {
for(h = core_area.y2 + 1; h <= draw_area.y2; h++) {
blend_area.y1 = h;
blend_area.y2 = h;
lv_memset_ff(blend_dsc.mask_buf, blend_w);
blend_dsc.mask_res = lv_draw_mask_apply(blend_dsc.mask_buf, blend_area.x1, h, blend_w);
lv_draw_sw_blend(draw_ctx, &blend_dsc);
}
}
}
}
lv_draw_mask_free_param(&mask_rin_param);
lv_draw_mask_remove_id(mask_rin_id);
lv_draw_mask_free_param(&mask_rout_param);
lv_draw_mask_remove_id(mask_rout_id);
lv_mem_buf_release(blend_dsc.mask_buf);
#else /*LV_DRAW_COMPLEX*/
LV_UNUSED(blend_mode);
#endif /*LV_DRAW_COMPLEX*/
}
static void draw_border_simple(lv_draw_ctx_t * draw_ctx, const lv_area_t * outer_area, const lv_area_t * inner_area,
lv_color_t color, lv_opa_t opa)
{
lv_area_t a;
lv_draw_sw_blend_dsc_t blend_dsc;
lv_memset_00(&blend_dsc, sizeof(lv_draw_sw_blend_dsc_t));
blend_dsc.blend_area = &a;
blend_dsc.color = color;
blend_dsc.opa = opa;
bool top_side = outer_area->y1 <= inner_area->y1 ? true : false;
bool bottom_side = outer_area->y2 >= inner_area->y2 ? true : false;
bool left_side = outer_area->x1 <= inner_area->x1 ? true : false;
bool right_side = outer_area->x2 >= inner_area->x2 ? true : false;
/*Top*/
a.x1 = outer_area->x1;
a.x2 = outer_area->x2;
a.y1 = outer_area->y1;
a.y2 = inner_area->y1 - 1;
if(top_side) {
lv_draw_sw_blend(draw_ctx, &blend_dsc);
}
/*Bottom*/
a.y1 = inner_area->y2 + 1;
a.y2 = outer_area->y2;
if(bottom_side) {
lv_draw_sw_blend(draw_ctx, &blend_dsc);
}
/*Left*/
a.x1 = outer_area->x1;
a.x2 = inner_area->x1 - 1;
a.y1 = (top_side) ? inner_area->y1 : outer_area->y1;
a.y2 = (bottom_side) ? inner_area->y2 : outer_area->y2;
if(left_side) {
lv_draw_sw_blend(draw_ctx, &blend_dsc);
}
/*Right*/
a.x1 = inner_area->x2 + 1;
a.x2 = outer_area->x2;
if(right_side) {
lv_draw_sw_blend(draw_ctx, &blend_dsc);
}
}