Files
lvgl/src/draw/sw/lv_draw_sw_fill.c
2024-12-03 12:58:50 +01:00

348 lines
12 KiB
C

/**
* @file lv_draw_sw_fill.c
*
*/
/*********************
* INCLUDES
*********************/
#include "../../misc/lv_area_private.h"
#include "lv_draw_sw_mask_private.h"
#include "../lv_draw_private.h"
#include "lv_draw_sw.h"
#if LV_USE_DRAW_SW
#include "blend/lv_draw_sw_blend_private.h"
#include "lv_draw_sw_gradient_private.h"
#include "../../misc/lv_math.h"
#include "../../misc/lv_text_ap.h"
#include "../../core/lv_refr.h"
#include "../../misc/lv_assert.h"
#include "../../stdlib/lv_string.h"
#include "../lv_draw_mask.h"
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
/**********************
* STATIC VARIABLES
**********************/
/**********************
* MACROS
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
void lv_draw_sw_fill(lv_draw_unit_t * draw_unit, lv_draw_fill_dsc_t * dsc, const lv_area_t * coords)
{
if(dsc->opa <= LV_OPA_MIN) return;
lv_area_t bg_coords;
lv_area_copy(&bg_coords, coords);
lv_area_t clipped_coords;
if(!lv_area_intersect(&clipped_coords, &bg_coords, draw_unit->clip_area)) return;
lv_grad_dir_t grad_dir = dsc->grad.dir;
lv_color_t bg_color = grad_dir == LV_GRAD_DIR_NONE ? dsc->color : dsc->grad.stops[0].color;
lv_draw_sw_blend_dsc_t blend_dsc = {0};
blend_dsc.color = bg_color;
/*Most simple case: just a plain rectangle*/
if(dsc->radius == 0 && (grad_dir == LV_GRAD_DIR_NONE)) {
blend_dsc.blend_area = &bg_coords;
blend_dsc.opa = dsc->opa;
lv_draw_sw_blend(draw_unit, &blend_dsc);
return;
}
/*Complex case: there is gradient, mask, or radius*/
#if LV_DRAW_SW_COMPLEX == 0
LV_LOG_WARN("Can't draw complex rectangle because LV_DRAW_SW_COMPLEX = 0");
#else
lv_opa_t opa = dsc->opa >= LV_OPA_MAX ? LV_OPA_COVER : dsc->opa;
/*Get the real radius. Can't be larger than the half of the shortest side */
int32_t coords_bg_w = lv_area_get_width(&bg_coords);
int32_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 a radius*/
int32_t clipped_w = lv_area_get_width(&clipped_coords);
lv_opa_t * mask_buf = NULL;
lv_draw_sw_mask_radius_param_t mask_rout_param;
void * mask_list[2] = {NULL, NULL};
if(rout > 0) {
mask_buf = lv_malloc(clipped_w);
lv_draw_sw_mask_radius_init(&mask_rout_param, &bg_coords, rout, false);
mask_list[0] = &mask_rout_param;
}
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->grad, coords_bg_w, coords_bg_h);
lv_opa_t * grad_opa_map = NULL;
bool transp = false;
if(grad && grad_dir >= LV_GRAD_DIR_HOR) {
blend_dsc.src_area = &blend_area;
blend_dsc.src_buf = grad->color_map + clipped_coords.x1 - bg_coords.x1;
uint32_t s;
for(s = 0; s < dsc->grad.stops_count; s++) {
if(dsc->grad.stops[s].opa != LV_OPA_COVER) {
transp = true;
break;
}
}
if(grad_dir == LV_GRAD_DIR_HOR) {
if(transp) grad_opa_map = grad->opa_map + clipped_coords.x1 - bg_coords.x1;
}
blend_dsc.src_color_format = LV_COLOR_FORMAT_RGB888;
}
#if LV_USE_DRAW_SW_COMPLEX_GRADIENTS
/*Prepare complex gradient*/
if(grad_dir >= LV_GRAD_DIR_LINEAR) {
LV_ASSERT_NULL(grad);
switch(grad_dir) {
case LV_GRAD_DIR_LINEAR:
lv_gradient_linear_setup(&dsc->grad, coords);
break;
case LV_GRAD_DIR_RADIAL:
lv_gradient_radial_setup(&dsc->grad, coords);
break;
case LV_GRAD_DIR_CONICAL:
lv_gradient_conical_setup(&dsc->grad, coords);
break;
default:
LV_LOG_WARN("Gradient type is not supported");
return;
}
blend_dsc.src_area = &blend_area;
/* For complex gradients we reuse the color map buffer for the pixel data */
blend_dsc.src_buf = grad->color_map;
grad_opa_map = grad->opa_map;
}
#endif
/* Draw the top of the rectangle line by line and mirror it to the bottom. */
for(h = 0; h < rout; h++) {
int32_t top_y = bg_coords.y1 + h;
int32_t bottom_y = bg_coords.y2 - h;
if(top_y < clipped_coords.y1 && bottom_y > clipped_coords.y2) continue; /*This line is clipped now*/
bool preblend = false;
/* 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_sw_mask_apply(mask_list, mask_buf, blend_area.x1, top_y, clipped_w);
if(blend_dsc.mask_res == LV_DRAW_SW_MASK_RES_FULL_COVER) blend_dsc.mask_res = LV_DRAW_SW_MASK_RES_CHANGED;
bool hor_grad_processed = false;
if(top_y >= clipped_coords.y1) {
blend_area.y1 = top_y;
blend_area.y2 = top_y;
switch(grad_dir) {
case LV_GRAD_DIR_VER:
LV_ASSERT_NULL(grad);
blend_dsc.color = grad->color_map[top_y - bg_coords.y1];
blend_dsc.opa = grad->opa_map[top_y - bg_coords.y1];
break;
case LV_GRAD_DIR_HOR:
hor_grad_processed = true;
preblend = grad_opa_map != NULL;
break;
#if LV_USE_DRAW_SW_COMPLEX_GRADIENTS
case LV_GRAD_DIR_LINEAR:
lv_gradient_linear_get_line(&dsc->grad, clipped_coords.x1 - bg_coords.x1, top_y - bg_coords.y1, coords_bg_w, grad);
preblend = true;
break;
case LV_GRAD_DIR_RADIAL:
lv_gradient_radial_get_line(&dsc->grad, clipped_coords.x1 - bg_coords.x1, top_y - bg_coords.y1, coords_bg_w, grad);
preblend = true;
break;
case LV_GRAD_DIR_CONICAL:
lv_gradient_conical_get_line(&dsc->grad, clipped_coords.x1 - bg_coords.x1, top_y - bg_coords.y1, coords_bg_w, grad);
preblend = true;
break;
#endif
default:
break;
}
/* pre-blend the mask */
if(preblend) {
int32_t i;
for(i = 0; i < clipped_w; i++) {
if(grad_opa_map[i] < LV_OPA_MAX) mask_buf[i] = (mask_buf[i] * grad_opa_map[i]) >> 8;
}
blend_dsc.mask_res = LV_DRAW_SW_MASK_RES_CHANGED;
}
lv_draw_sw_blend(draw_unit, &blend_dsc);
}
if(bottom_y <= clipped_coords.y2) {
blend_area.y1 = bottom_y;
blend_area.y2 = bottom_y;
switch(grad_dir) {
case LV_GRAD_DIR_VER:
LV_ASSERT_NULL(grad);
blend_dsc.color = grad->color_map[bottom_y - bg_coords.y1];
blend_dsc.opa = grad->opa_map[bottom_y - bg_coords.y1];
break;
case LV_GRAD_DIR_HOR:
preblend = !hor_grad_processed && (grad_opa_map != NULL);
break;
#if LV_USE_DRAW_SW_COMPLEX_GRADIENTS
case LV_GRAD_DIR_LINEAR:
lv_gradient_linear_get_line(&dsc->grad, clipped_coords.x1 - bg_coords.x1, bottom_y - bg_coords.y1, coords_bg_w, grad);
preblend = true;
break;
case LV_GRAD_DIR_RADIAL:
lv_gradient_radial_get_line(&dsc->grad, clipped_coords.x1 - bg_coords.x1, bottom_y - bg_coords.y1, coords_bg_w, grad);
preblend = true;
break;
case LV_GRAD_DIR_CONICAL:
lv_gradient_conical_get_line(&dsc->grad, clipped_coords.x1 - bg_coords.x1, bottom_y - bg_coords.y1, coords_bg_w, grad);
preblend = true;
break;
#endif
default:
break;
}
/* pre-blend the mask */
if(preblend) {
int32_t i;
if(grad_dir >= LV_GRAD_DIR_LINEAR) {
/*Need to generate the mask again, because we have mixed in the upper part of the gradient*/
lv_memset(mask_buf, opa, clipped_w);
blend_dsc.mask_res = lv_draw_sw_mask_apply(mask_list, mask_buf, blend_area.x1, top_y, clipped_w);
if(blend_dsc.mask_res == LV_DRAW_SW_MASK_RES_FULL_COVER) blend_dsc.mask_res = LV_DRAW_SW_MASK_RES_CHANGED;
}
for(i = 0; i < clipped_w; i++) {
if(grad_opa_map[i] < LV_OPA_MAX) mask_buf[i] = (mask_buf[i] * grad_opa_map[i]) >> 8;
}
blend_dsc.mask_res = LV_DRAW_SW_MASK_RES_CHANGED;
}
lv_draw_sw_blend(draw_unit, &blend_dsc);
}
}
/* Draw the center of the rectangle.*/
/*If no gradient, the center is a simple rectangle*/
if(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_unit, &blend_dsc);
}
/*With gradient draw line by line*/
else {
blend_dsc.opa = opa;
switch(grad_dir) {
case LV_GRAD_DIR_VER:
blend_dsc.mask_res = LV_DRAW_SW_MASK_RES_FULL_COVER;
break;
case LV_GRAD_DIR_HOR:
blend_dsc.mask_res = LV_DRAW_SW_MASK_RES_CHANGED;
blend_dsc.mask_buf = grad_opa_map;
break;
case LV_GRAD_DIR_LINEAR:
case LV_GRAD_DIR_RADIAL:
case LV_GRAD_DIR_CONICAL:
blend_dsc.mask_res = transp ? LV_DRAW_SW_MASK_RES_CHANGED : LV_DRAW_SW_MASK_RES_FULL_COVER;
blend_dsc.mask_buf = grad_opa_map;
break;
default:
break;
}
int32_t h_start = LV_MAX(bg_coords.y1 + rout, clipped_coords.y1);
int32_t h_end = LV_MIN(bg_coords.y2 - rout, clipped_coords.y2);
for(h = h_start; h <= h_end; h++) {
blend_area.y1 = h;
blend_area.y2 = h;
switch(grad_dir) {
case LV_GRAD_DIR_VER:
LV_ASSERT_NULL(grad);
blend_dsc.color = grad->color_map[h - bg_coords.y1];
if(opa >= LV_OPA_MAX) blend_dsc.opa = grad->opa_map[h - bg_coords.y1];
else blend_dsc.opa = LV_OPA_MIX2(grad->opa_map[h - bg_coords.y1], opa);
break;
#if LV_USE_DRAW_SW_COMPLEX_GRADIENTS
case LV_GRAD_DIR_LINEAR:
lv_gradient_linear_get_line(&dsc->grad, clipped_coords.x1 - bg_coords.x1, h - bg_coords.y1, coords_bg_w, grad);
break;
case LV_GRAD_DIR_RADIAL:
lv_gradient_radial_get_line(&dsc->grad, clipped_coords.x1 - bg_coords.x1, h - bg_coords.y1, coords_bg_w, grad);
break;
case LV_GRAD_DIR_CONICAL:
lv_gradient_conical_get_line(&dsc->grad, clipped_coords.x1 - bg_coords.x1, h - bg_coords.y1, coords_bg_w, grad);
break;
#endif
default:
break;
}
lv_draw_sw_blend(draw_unit, &blend_dsc);
}
}
if(mask_buf) {
lv_free(mask_buf);
lv_draw_sw_mask_free_param(&mask_rout_param);
}
if(grad) {
lv_gradient_cleanup(grad);
}
#if LV_USE_DRAW_SW_COMPLEX_GRADIENTS
if(grad_dir >= LV_GRAD_DIR_LINEAR) {
switch(grad_dir) {
case LV_GRAD_DIR_LINEAR:
lv_gradient_linear_cleanup(&dsc->grad);
break;
case LV_GRAD_DIR_RADIAL:
lv_gradient_radial_cleanup(&dsc->grad);
break;
case LV_GRAD_DIR_CONICAL:
lv_gradient_conical_cleanup(&dsc->grad);
break;
default:
break;
}
}
#endif
#endif
}
#endif /*LV_USE_DRAW_SW*/