From 85507c4af48bb5510c1711ca81c3477f2c086d9c Mon Sep 17 00:00:00 2001 From: Gabor Kiss-Vamosi Date: Fri, 23 Aug 2019 10:53:38 +0200 Subject: [PATCH] add 2d blending --- src/lv_core/lv_style.c | 2 +- src/lv_draw/lv_blend.c | 137 +++++++++++++++++++++++++++-- src/lv_draw/lv_blend.h | 5 ++ src/lv_draw/lv_draw_rect.c | 176 +++++++++++++++++++++---------------- src/lv_draw/lv_mask.c | 2 +- src/lv_draw/lv_mask.h | 2 +- 6 files changed, 240 insertions(+), 84 deletions(-) diff --git a/src/lv_core/lv_style.c b/src/lv_core/lv_style.c index c3a8585a4..f5241fb77 100644 --- a/src/lv_core/lv_style.c +++ b/src/lv_core/lv_style.c @@ -170,7 +170,7 @@ void lv_style_init(void) lv_style_btn_rel.body.padding.bottom = LV_DPI / 6; lv_style_btn_rel.body.padding.inner = LV_DPI / 10; lv_style_btn_rel.body.border.color = lv_color_make(0x0b, 0x19, 0x28); - lv_style_btn_rel.body.border.width = 20;//LV_DPI / 50 >= 1 ? LV_DPI / 50 : 1; + lv_style_btn_rel.body.border.width = LV_DPI / 50 >= 1 ? LV_DPI / 50 : 1; lv_style_btn_rel.body.border.opa = LV_OPA_70; lv_style_btn_rel.body.shadow.color = LV_COLOR_GRAY; lv_style_btn_rel.body.shadow.width = 0; diff --git a/src/lv_draw/lv_blend.c b/src/lv_draw/lv_blend.c index 6d4ff1dbf..405fdca91 100644 --- a/src/lv_draw/lv_blend.c +++ b/src/lv_draw/lv_blend.c @@ -32,9 +32,136 @@ * GLOBAL FUNCTIONS **********************/ + +/** + * + * @param disp_area + * @param clip_area already truncated to disp_arae + * @param fill_area + * @param disp_buf + * @param cf + * @param color + * @param mask + * @param mask_res + * @param opa + * @param mode + */ +void lv_blend_fill(const lv_area_t * disp_area, const lv_area_t * clip_area, const lv_area_t * fill_area, + lv_color_t * disp_buf, lv_img_cf_t cf, lv_color_t color, + lv_opa_t * mask, lv_mask_res_t mask_res, lv_opa_t opa, lv_blend_mode_t mode) +{ + + /*Do not draw transparent things*/ + if(opa < LV_OPA_MIN) return; + if(mask_res == LV_MASK_RES_FULL_TRANSP) return; + + /* Get clipped fill area which is the real draw area. + * It is always the same or inside `fill_area` */ + lv_area_t draw_area; + bool is_common; + is_common = lv_area_intersect(&draw_area, clip_area, fill_area); + if(!is_common) return; + + /* Now `draw_area` has absolute coordinates. + * Make it relative to `disp_area` to simplify draw to `disp_buf`*/ + draw_area.x1 -= disp_area->x1; + draw_area.y1 -= disp_area->y1; + draw_area.x2 -= disp_area->x1; + draw_area.y2 -= disp_area->y1; + + /*Get the width of the `disp_area` it will be used to go to the next line*/ + lv_coord_t disp_w = lv_area_get_width(disp_area); + + /*Create a temp. disp_buf which always point to current line to draw*/ + lv_color_t * disp_buf_tmp = disp_buf + disp_w * draw_area.y1; + + lv_coord_t x; + lv_coord_t y; + + /*Simple fill (maybe with opacity), no masking*/ + if(mask_res == LV_MASK_RES_FULL_COVER) { + if(opa > LV_OPA_MAX) { + for(y = draw_area.y1; y <= draw_area.y2; y++) { + for(x = draw_area.x1; x <= draw_area.x2; x++) { + disp_buf_tmp[x].full = color.full; + } + disp_buf_tmp += disp_w; + } + } + else { + for(y = draw_area.y1; y <= draw_area.y2; y++) { + for(x = draw_area.x1; x <= draw_area.x2; x++) { + disp_buf_tmp[x] = lv_color_mix(color, disp_buf[x], opa); + } + disp_buf_tmp += disp_w; + } + } + } + /*Masked*/ + else { + /*Get the width of the `fill_area` it will be used to go to the next line of the mask*/ + lv_coord_t fill_w = lv_area_get_width(fill_area); + + /* The mask is relative to the original `fill_area`. + * If some lines and columns are clipped move on the mask accordingly.*/ + lv_opa_t * mask_tmp = mask + fill_w * (draw_area.y1 - fill_area->y1 + disp_area->y1); + mask_tmp -= draw_area.x1; + + /*Buffer the result color to avoid recalculating the same color*/ + lv_color_t last_dest_color; + lv_color_t last_res_color; + lv_opa_t last_mask = LV_OPA_TRANSP; + last_dest_color.full = disp_buf_tmp[0].full; + last_res_color.full = disp_buf_tmp[0].full; + + /*Only the mask matters*/ + if(opa > LV_OPA_MAX) { + for(y = draw_area.y1; y <= draw_area.y2; y++) { + for(x = draw_area.x1; x <= draw_area.x2; x++) { + if(mask_tmp[x] != last_mask || last_dest_color.full != disp_buf_tmp[x].full) { + if(mask_tmp[x] > LV_OPA_MAX) last_res_color = color; + else if(mask_tmp[x] < LV_OPA_MIN) last_res_color = disp_buf_tmp[x]; + else last_res_color = lv_color_mix(color, disp_buf_tmp[x], mask_tmp[x]); + last_mask = mask_tmp[x]; + last_dest_color.full = disp_buf_tmp[x].full; + } + disp_buf_tmp[x] = last_res_color; + } + disp_buf_tmp += disp_w; + mask_tmp += fill_w; + } + } + /*Handle opa and mask values too*/ + else { + for(y = draw_area.y1; y <= draw_area.y2; y++) { + for(x = draw_area.x1; x <= draw_area.x2; x++) { + if(mask[x] != last_mask || last_dest_color.full != disp_buf_tmp[x].full) { + lv_opa_t opa_tmp = (uint16_t)((uint16_t)mask_tmp[x] * opa) >> 8; + + if(opa_tmp > LV_OPA_MAX) last_res_color = color; + else if(opa_tmp < LV_OPA_MIN) last_res_color = disp_buf_tmp[x]; + else last_res_color = lv_color_mix(color, disp_buf_tmp[x],opa_tmp); + last_mask = mask[x]; + last_dest_color.full = disp_buf_tmp[x].full; + } + disp_buf_tmp[x] = last_res_color; + } + disp_buf_tmp += disp_w; + mask_tmp += fill_w; + } + } + } +} + + + + + + + void lv_blend_color(lv_color_t * dest_buf, lv_img_cf_t dest_cf, lv_coord_t len, - lv_color_t color, lv_img_cf_t src_cf, - lv_opa_t * mask, lv_mask_res_t mask_res, lv_opa_t opa, lv_blend_mode_t mode) + lv_color_t color, lv_img_cf_t src_cf, + lv_opa_t * mask, lv_mask_res_t mask_res, lv_opa_t opa, lv_blend_mode_t mode) { if(opa < LV_OPA_MIN) return; if(mask_res == LV_MASK_RES_FULL_TRANSP) return; @@ -65,7 +192,7 @@ void lv_blend_color(lv_color_t * dest_buf, lv_img_cf_t dest_cf, lv_coord_t len, last_res_color.full = dest_buf[0].full; for(i = 0; i < len; i++) { -// if(mask[i] == 0) continue; + // if(mask[i] == 0) continue; if(mask[i] != last_mask || last_dest_color.full != dest_buf[i].full) { if(mask[i] > LV_OPA_MAX) last_res_color = color; @@ -83,7 +210,7 @@ void lv_blend_color(lv_color_t * dest_buf, lv_img_cf_t dest_cf, lv_coord_t len, last_dest_color.full = dest_buf[0].full; last_res_color.full = dest_buf[0].full; for(i = 0; i < len; i++) { -// if(mask[i] == 0) continue; + // if(mask[i] == 0) continue; if(mask[i] != last_mask || last_dest_color.full != dest_buf[i].full) { lv_opa_t tmp = (uint16_t)((uint16_t)mask[i] * opa) >> 8; @@ -94,7 +221,7 @@ void lv_blend_color(lv_color_t * dest_buf, lv_img_cf_t dest_cf, lv_coord_t len, last_dest_color.full = dest_buf[i].full; } dest_buf[i] = last_res_color; -// + // } } } diff --git a/src/lv_draw/lv_blend.h b/src/lv_draw/lv_blend.h index a745ec6d7..b9204a1ce 100644 --- a/src/lv_draw/lv_blend.h +++ b/src/lv_draw/lv_blend.h @@ -34,6 +34,11 @@ typedef enum { * GLOBAL PROTOTYPES **********************/ +void lv_blend_fill(const lv_area_t * disp_area, const lv_area_t * clip_area, const lv_area_t * fill_area, + lv_color_t * disp_buf, lv_img_cf_t cf, lv_color_t color, + lv_opa_t * mask, lv_mask_res_t mask_res, lv_opa_t opa, lv_blend_mode_t mode); + + void lv_blend_color(lv_color_t * dest_buf, lv_img_cf_t dest_cf, lv_coord_t len, lv_color_t color, lv_img_cf_t src_cf, lv_opa_t * mask, lv_mask_res_t mask_res, lv_opa_t opa, lv_blend_mode_t mode); diff --git a/src/lv_draw/lv_draw_rect.c b/src/lv_draw/lv_draw_rect.c index f134081db..8b3bcb890 100644 --- a/src/lv_draw/lv_draw_rect.c +++ b/src/lv_draw/lv_draw_rect.c @@ -65,81 +65,105 @@ static void draw_bg(const lv_area_t * coords, const lv_area_t * clip, const lv_s if(opa > LV_OPA_MAX) opa = LV_OPA_COVER; - lv_area_t draw_a; - bool union_ok; - - /* Get the union of `coords` and `clip`*/ - /* `clip` is already truncated to the `vdb` size - * in 'lv_refr_area' function */ - union_ok = lv_area_intersect(&draw_a, coords, clip); - - /*If there are common part of `clip` and `vdb` then draw*/ - if(union_ok == false) return; - lv_disp_t * disp = lv_refr_get_disp_refreshing(); lv_disp_buf_t * vdb = lv_disp_get_buf(disp); - /*Store the coordinates of the `draw_a` relative to the VDB */ - lv_area_t draw_rel_a; - draw_rel_a.x1 = draw_a.x1 - vdb->area.x1; - draw_rel_a.y1 = draw_a.y1 - vdb->area.y1; - draw_rel_a.x2 = draw_a.x2 - vdb->area.x1; - draw_rel_a.y2 = draw_a.y2 - vdb->area.y1; - lv_coord_t vdb_width = lv_area_get_width(&vdb->area); - lv_coord_t draw_a_width = lv_area_get_width(&draw_rel_a); + /* Get clipped fill area which is the real draw area. + * It is always the same or inside `fill_area` */ + lv_area_t draw_area; + bool is_common; + is_common = lv_area_intersect(&draw_area, coords, clip); + if(is_common == false) return; - /*Move the vdb_buf_tmp to the first row*/ - lv_color_t * vdb_buf_tmp = vdb->buf_act; - vdb_buf_tmp += vdb_width * draw_rel_a.y1; + const lv_area_t * disp_area = &vdb->area; + lv_color_t * disp_buf = vdb->buf_act; + /* Now `draw_area` has absolute coordinates. + * Make it relative to `disp_area` to simplify draw to `disp_buf`*/ + draw_area.x1 -= disp_area->x1; + draw_area.y1 -= disp_area->y1; + draw_area.x2 -= disp_area->x1; + draw_area.y2 -= disp_area->y1; + + lv_coord_t draw_area_w = lv_area_get_width(&draw_area); + + /*Get the width of the `disp_area` it will be used to go to the next line*/ + lv_coord_t disp_w = lv_area_get_width(disp_area); + + /*Create a temp. disp_buf which always point to current line to draw*/ + lv_color_t * disp_buf_tmp = disp_buf + disp_w * draw_area.y1; + + /*Create a mask if there is a radius*/ lv_opa_t mask_buf[LV_HOR_RES_MAX]; - lv_coord_t short_side = LV_MATH_MIN(lv_area_get_width(coords), lv_area_get_height(coords)); - lv_coord_t rout = style->body.radius; - if(rout > short_side >> 1) rout = short_side >> 1; - - /*Create the outer mask*/ - lv_mask_param_t mask_rout_param; - int16_t mask_rout_id = LV_MASK_ID_INV; uint8_t other_mask_cnt = lv_mask_get_cnt(); + int16_t mask_rout_id = LV_MASK_ID_INV; - if(rout != 0) { - lv_mask_radius_init(&mask_rout_param, coords, rout, false); - mask_rout_id = lv_mask_add(lv_mask_radius, &mask_rout_param, NULL); + /*Get the real radius*/ + lv_coord_t rout = style->body.radius; + if(rout == LV_RADIUS_CIRCLE) { + lv_coord_t short_side = LV_MATH_MIN(lv_area_get_width(coords), lv_area_get_height(coords)); + if(rout > short_side >> 1) rout = short_side >> 1; } - /*Draw the background line by line*/ - lv_coord_t h; - lv_mask_res_t mask_res = LV_MASK_RES_FULL_COVER; - lv_color_t grad_color = style->body.main_color; + /*Most simple case: just a plain rectangle*/ + if(other_mask_cnt == 0 && rout == 0 && style->body.main_color.full == style->body.grad_color.full) { + lv_blend_fill(&vdb->area, clip, coords, + vdb->buf_act, LV_IMG_CF_TRUE_COLOR, style->body.main_color, + NULL, LV_MASK_RES_FULL_COVER, style->body.opa, LV_BLIT_MODE_NORMAL); + } + /*More complex case: there is a radius, gradient or mask.*/ + else { - /*Fill the first row with 'color'*/ - if(opa >= LV_OPA_MIN) { - for(h = draw_rel_a.y1; h <= draw_rel_a.y2; h++) { - lv_coord_t y = h + vdb->area.y1; - if(y > coords->y1 + rout + 1 && - y < coords->y2 - rout - 1) { - mask_res = LV_MASK_RES_FULL_COVER; - if(other_mask_cnt != 0) { - memset(mask_buf, LV_OPA_COVER, draw_a_width); - mask_res = lv_mask_apply(mask_buf, vdb->area.x1 + draw_rel_a.x1, vdb->area.y1 + h, draw_a_width); + lv_mask_param_t mask_rout_param; + if(rout > 0) { + lv_mask_radius_init(&mask_rout_param, coords, rout, false); + mask_rout_id = lv_mask_add(lv_mask_radius, &mask_rout_param, NULL); + } + + /*Draw the background line by line*/ + lv_coord_t h; + lv_mask_res_t mask_res = LV_MASK_RES_FULL_COVER; + lv_color_t grad_color = style->body.main_color; + + /*Fill the first row with 'color'*/ + if(opa >= LV_OPA_MIN) { + for(h = draw_area.y1; h <= draw_area.y2; h++) { + lv_coord_t y = h + vdb->area.y1; + + /*In not corner areas apply the mask only if required*/ + if(y > coords->y1 + rout + 1 && + y < coords->y2 - rout - 1) { + mask_res = LV_MASK_RES_FULL_COVER; + if(other_mask_cnt != 0) { + memset(mask_buf, LV_OPA_COVER, draw_area_w); + mask_res = lv_mask_apply(mask_buf, vdb->area.x1 + draw_area.x1, vdb->area.y1 + h, draw_area_w); + } } - } else { - memset(mask_buf, LV_OPA_COVER, draw_a_width); - mask_res = lv_mask_apply(mask_buf, vdb->area.x1 + draw_rel_a.x1, vdb->area.y1 + h, draw_a_width); + /*In corner areas apply the mask anyway*/ + else { + memset(mask_buf, LV_OPA_COVER, draw_area_w); + mask_res = lv_mask_apply(mask_buf, vdb->area.x1 + draw_area.x1, vdb->area.y1 + h, draw_area_w); + } + + /*Get the current line color*/ + if(style->body.main_color.full != style->body.grad_color.full) { + lv_opa_t mix = (uint32_t)((uint32_t) (y - coords->y1) * 255) / lv_area_get_height(coords); + grad_color = lv_color_mix(style->body.grad_color, style->body.main_color, mix); + } + + /*Create a area for this line to fill*/ + lv_area_t fill_area; + fill_area.x1 = coords->x1; + fill_area.x2 = coords->x2; + fill_area.y1 = disp_area->y1 + h; + fill_area.y2 = fill_area.y1; + + lv_blend_fill(disp_area, clip, &fill_area, + disp_buf, LV_IMG_CF_TRUE_COLOR, grad_color, + mask_buf, mask_res, style->body.opa, LV_BLIT_MODE_NORMAL); } - - if(style->body.main_color.full != style->body.grad_color.full) { - lv_opa_t mix = (uint32_t)((uint32_t) (y - coords->y1) * 255) / lv_area_get_height(coords); - grad_color = lv_color_mix(style->body.grad_color, style->body.main_color, mix); - } - - lv_blend_color(&vdb_buf_tmp[draw_rel_a.x1], LV_IMG_CF_TRUE_COLOR, draw_a_width, - grad_color, LV_IMG_CF_TRUE_COLOR, - mask_buf, mask_res, style->body.opa, LV_BLIT_MODE_NORMAL); - - vdb_buf_tmp += vdb_width; } } @@ -147,8 +171,6 @@ static void draw_bg(const lv_area_t * coords, const lv_area_t * clip, const lv_s lv_coord_t border_width = style->body.border.width; if(border_width) { /*Move the vdb_buf_tmp to the first row*/ - vdb_buf_tmp = vdb->buf_act; - vdb_buf_tmp += vdb_width * draw_rel_a.y1; lv_mask_param_t mask_rsmall_param; lv_coord_t rin = rout - border_width; @@ -162,19 +184,21 @@ static void draw_bg(const lv_area_t * coords, const lv_area_t * clip, const lv_s lv_mask_radius_init(&mask_rsmall_param, &area_small, style->body.radius - border_width, true); int16_t mask_rsmall_id = lv_mask_add(lv_mask_radius, &mask_rsmall_param, NULL); - lv_coord_t len_left = (coords->x1 + border_width) - (vdb->area.x1 + draw_rel_a.x1); - if(draw_rel_a.x1 + len_left > draw_rel_a.x2) { - len_left = draw_rel_a.x2 - draw_rel_a.x1 + 1; + lv_coord_t len_left = (coords->x1 + border_width) - (vdb->area.x1 + draw_area.x1); + if(draw_area.x1 + len_left > draw_area.x2) { + len_left = draw_area.x2 - draw_area.x1 + 1; } - lv_coord_t first_right = coords->x2 - (vdb->area.x1 + draw_rel_a.x1 + border_width - 1); + lv_coord_t first_right = coords->x2 - (vdb->area.x1 + draw_area.x1 + border_width - 1); if(first_right < 0) first_right = 0; - lv_coord_t len_right = draw_a_width - first_right; + lv_coord_t len_right = draw_area_w - first_right; lv_coord_t corner_size = LV_MATH_MAX(rout, border_width) + 1; /*Fill the first row with 'color'*/ - for(h = draw_rel_a.y1; h <= draw_rel_a.y2; h++) { + lv_coord_t h; + lv_mask_res_t mask_res; + for(h = draw_area.y1; h <= draw_area.y2; h++) { /* Do not blend the large empty area in the middle. * Truncate the mask to use only the very edges*/ lv_coord_t y = h + vdb->area.y1; @@ -182,32 +206,32 @@ static void draw_bg(const lv_area_t * coords, const lv_area_t * clip, const lv_s y < coords->y2 - corner_size) { mask_res = LV_MASK_RES_FULL_COVER; if(other_mask_cnt != 0) { - memset(mask_buf, LV_OPA_COVER, draw_a_width); - mask_res = lv_mask_apply(mask_buf, vdb->area.x1 + draw_rel_a.x1, vdb->area.y1 + h, draw_a_width); + memset(mask_buf, LV_OPA_COVER, draw_area_w); + mask_res = lv_mask_apply(mask_buf, vdb->area.x1 + draw_area.x1, vdb->area.y1 + h, draw_area_w); } /* It's sure that we don't need more then border_width pixels on the left.*/ if(len_left > 0) { - lv_blend_color(&vdb_buf_tmp[draw_rel_a.x1], LV_IMG_CF_TRUE_COLOR, len_left, + lv_blend_color(&disp_buf_tmp[draw_area.x1], LV_IMG_CF_TRUE_COLOR, len_left, style->body.border.color, LV_IMG_CF_TRUE_COLOR, mask_buf, mask_res, style->body.border.opa, LV_BLIT_MODE_NORMAL); } /* Similarly we don't need more then border_width pixels on the right.*/ if(len_right > 0) { - lv_blend_color(&vdb_buf_tmp[draw_rel_a.x1 + first_right], LV_IMG_CF_TRUE_COLOR, len_right, + lv_blend_color(&disp_buf_tmp[draw_area.x1 + first_right], LV_IMG_CF_TRUE_COLOR, len_right, style->body.border.color, LV_IMG_CF_TRUE_COLOR, mask_buf + first_right, mask_res, style->body.border.opa, LV_BLIT_MODE_NORMAL); } } else { - memset(mask_buf, LV_OPA_COVER, draw_a_width); - mask_res = lv_mask_apply(mask_buf, vdb->area.x1 + draw_rel_a.x1, vdb->area.y1 + h, draw_a_width); + memset(mask_buf, LV_OPA_COVER, draw_area_w); + mask_res = lv_mask_apply(mask_buf, vdb->area.x1 + draw_area.x1, vdb->area.y1 + h, draw_area_w); - lv_blend_color(&vdb_buf_tmp[draw_rel_a.x1], LV_IMG_CF_TRUE_COLOR, draw_a_width, + lv_blend_color(&disp_buf_tmp[draw_area.x1], LV_IMG_CF_TRUE_COLOR, draw_area_w, style->body.border.color, LV_IMG_CF_TRUE_COLOR, mask_buf, mask_res, style->body.border.opa, LV_BLIT_MODE_NORMAL); } - vdb_buf_tmp += vdb_width; + disp_buf_tmp += disp_w; } lv_mask_remove_id(mask_rsmall_id); diff --git a/src/lv_draw/lv_mask.c b/src/lv_draw/lv_mask.c index 7c67ad8b7..12f1dea00 100644 --- a/src/lv_draw/lv_mask.c +++ b/src/lv_draw/lv_mask.c @@ -345,7 +345,7 @@ lv_mask_res_t lv_mask_angle(lv_opa_t * mask_buf, lv_coord_t abs_x, lv_coord_t ab else return LV_MASK_RES_CHANGED; } -void lv_mask_radius_init(lv_mask_param_t * param, lv_area_t * rect, lv_coord_t radius, bool inv) +void lv_mask_radius_init(lv_mask_param_t * param, const lv_area_t * rect, lv_coord_t radius, bool inv) { lv_mask_radius_param_t * p = ¶m->radius; diff --git a/src/lv_draw/lv_mask.h b/src/lv_draw/lv_mask.h index 728f5b6d7..b1de2a55d 100644 --- a/src/lv_draw/lv_mask.h +++ b/src/lv_draw/lv_mask.h @@ -112,7 +112,7 @@ void lv_mask_line_angle_init(lv_mask_param_t * param, lv_coord_t p1x, lv_coord_t lv_mask_res_t lv_mask_line(lv_opa_t * mask_buf, lv_coord_t abs_x, lv_coord_t abs_y, lv_coord_t len, lv_mask_param_t * param); -void lv_mask_radius_init(lv_mask_param_t * param, lv_area_t * rect, lv_coord_t radius, bool inv); +void lv_mask_radius_init(lv_mask_param_t * param, const lv_area_t * rect, lv_coord_t radius, bool inv); lv_mask_res_t lv_mask_radius(lv_opa_t * mask_buf, lv_coord_t abs_x, lv_coord_t abs_y, lv_coord_t len, lv_mask_param_t * param); void lv_mask_angle_init(lv_mask_param_t * param, lv_coord_t origio_x, lv_coord_t origo_y, lv_coord_t start_angle, lv_coord_t end_angle);