From d2d886aae572a29818de322002a6393a9145ebda Mon Sep 17 00:00:00 2001 From: Mariotaku Date: Thu, 8 Dec 2022 18:36:03 +0900 Subject: [PATCH] fix(draw): SDL2 gradient support #3848 (#3856) --- src/draw/sdl/lv_draw_sdl_rect.c | 281 +++++++++++++++++++++-- src/draw/sdl/lv_draw_sdl_rect.h | 5 + src/draw/sdl/lv_draw_sdl_texture_cache.h | 1 + 3 files changed, 274 insertions(+), 13 deletions(-) diff --git a/src/draw/sdl/lv_draw_sdl_rect.c b/src/draw/sdl/lv_draw_sdl_rect.c index a303ac764..6418a0af6 100644 --- a/src/draw/sdl/lv_draw_sdl_rect.c +++ b/src/draw/sdl/lv_draw_sdl_rect.c @@ -27,6 +27,8 @@ * DEFINES *********************/ +#define FRAG_SPACING 3 + /********************** * TYPEDEFS **********************/ @@ -37,6 +39,23 @@ typedef struct { lv_coord_t size; } lv_draw_rect_bg_key_t; +typedef struct { + lv_sdl_cache_key_magic_t magic; + lv_gradient_stop_t stops[LV_GRADIENT_MAX_STOPS]; + uint8_t stops_count; + lv_grad_dir_t dir; +} lv_draw_rect_grad_strip_key_t; + +typedef struct { + lv_sdl_cache_key_magic_t magic; + lv_gradient_stop_t stops[LV_GRADIENT_MAX_STOPS]; + uint8_t stops_count; + lv_grad_dir_t dir; + lv_coord_t w; + lv_coord_t h; + lv_coord_t radius; +} lv_draw_rect_grad_frag_key_t; + typedef struct { lv_sdl_cache_key_magic_t magic; lv_coord_t radius; @@ -57,6 +76,12 @@ typedef struct { static void draw_bg_color(lv_draw_sdl_ctx_t * ctx, const lv_area_t * coords, const lv_area_t * draw_area, const lv_draw_rect_dsc_t * dsc); +static void draw_bg_grad_simple(lv_draw_sdl_ctx_t * ctx, const lv_area_t * coords, const lv_area_t * draw_area, + const lv_grad_dsc_t * grad, bool blend_mod); + +static void draw_bg_grad_radius(lv_draw_sdl_ctx_t * ctx, const lv_area_t * coords, const lv_area_t * draw_area, + const lv_draw_rect_dsc_t * dsc); + static void draw_bg_img(lv_draw_sdl_ctx_t * ctx, const lv_area_t * coords, const lv_area_t * draw_area, const lv_draw_rect_dsc_t * dsc); @@ -81,6 +106,11 @@ static void frag_render_center(SDL_Renderer * renderer, SDL_Texture * frag, lv_c static lv_draw_rect_bg_key_t rect_bg_key_create(lv_coord_t radius, lv_coord_t size); +static lv_draw_rect_grad_frag_key_t rect_grad_frag_key_create(const lv_grad_dsc_t * grad, lv_coord_t w, lv_coord_t h, + lv_coord_t radius); + +static lv_draw_rect_grad_strip_key_t rect_grad_strip_key_create(const lv_grad_dsc_t * grad); + static lv_draw_rect_shadow_key_t rect_shadow_key_create(lv_coord_t radius, lv_coord_t size, lv_coord_t blur); static lv_draw_rect_border_key_t rect_border_key_create(lv_coord_t rout, lv_coord_t rin, const lv_area_t * outer_area, @@ -148,16 +178,93 @@ void lv_draw_sdl_draw_rect(lv_draw_ctx_t * draw_ctx, const lv_draw_rect_dsc_t * SDL_Texture * lv_draw_sdl_rect_bg_frag_obtain(lv_draw_sdl_ctx_t * ctx, lv_coord_t radius) { lv_draw_rect_bg_key_t key = rect_bg_key_create(radius, radius); - lv_area_t coords = {0, 0, radius * 2 - 1, radius * 2 - 1}; - lv_area_t coords_frag = {0, 0, radius - 1, radius - 1}; SDL_Texture * texture = lv_draw_sdl_texture_cache_get(ctx, &key, sizeof(key), NULL); if(texture == NULL) { + lv_area_t coords = {0, 0, radius * 2 - 1, radius * 2 - 1}; + lv_area_t coords_frag = {0, 0, radius - 1, radius - 1}; lv_draw_mask_radius_param_t mask_rout_param; lv_draw_mask_radius_init(&mask_rout_param, &coords, radius, false); int16_t mask_id = lv_draw_mask_add(&mask_rout_param, NULL); texture = lv_draw_sdl_mask_dump_texture(ctx->renderer, &coords_frag, &mask_id, 1); + SDL_assert(texture != NULL); lv_draw_mask_remove_id(mask_id); - SDL_assert(texture); + lv_draw_sdl_texture_cache_put(ctx, &key, sizeof(key), texture); + } + return texture; +} + +SDL_Texture * lv_draw_sdl_rect_grad_frag_obtain(lv_draw_sdl_ctx_t * ctx, const lv_grad_dsc_t * grad, lv_coord_t w, + lv_coord_t h, lv_coord_t radius) +{ + lv_draw_rect_grad_frag_key_t key = rect_grad_frag_key_create(grad, w, h, radius); + SDL_Texture * texture = lv_draw_sdl_texture_cache_get(ctx, &key, sizeof(key), NULL); + if(texture == NULL) { + lv_area_t coords = {0, 0, radius * 2 + FRAG_SPACING - 1, radius * 2 + FRAG_SPACING - 1}; + texture = SDL_CreateTexture(ctx->renderer, LV_DRAW_SDL_TEXTURE_FORMAT, SDL_TEXTUREACCESS_TARGET, + lv_area_get_width(&coords), lv_area_get_height(&coords)); + SDL_assert(texture != NULL); + + lv_draw_mask_radius_param_t mask_rout_param; + lv_draw_mask_radius_init(&mask_rout_param, &coords, radius, false); + int16_t mask_id = lv_draw_mask_add(&mask_rout_param, NULL); + SDL_Texture * mask = lv_draw_sdl_mask_dump_texture(ctx->renderer, &coords, &mask_id, 1); + SDL_assert(mask != NULL); + SDL_SetTextureBlendMode(mask, SDL_BLENDMODE_NONE); + lv_draw_mask_remove_id(mask_id); + + SDL_Texture * target_backup = SDL_GetRenderTarget(ctx->renderer); + SDL_SetRenderTarget(ctx->renderer, texture); + SDL_RenderCopy(ctx->renderer, mask, NULL, NULL); + SDL_DestroyTexture(mask); + + lv_area_t blend_coords = {.x1 = 0, .y1 = 0, .x2 = w - 1, .y2 = h - 1}; + lv_area_t draw_area = {.x1 = 0, .y1 = 0, .x2 = radius - 1, .y2 = radius - 1}; + /* Align to top left */ + lv_area_align(&coords, &draw_area, LV_ALIGN_TOP_LEFT, 0, 0); + lv_area_align(&coords, &blend_coords, LV_ALIGN_TOP_LEFT, 0, 0); + draw_bg_grad_simple(ctx, &blend_coords, &draw_area, grad, true); + + /* Align to top right */ + lv_area_align(&coords, &draw_area, LV_ALIGN_TOP_RIGHT, 0, 0); + lv_area_align(&coords, &blend_coords, LV_ALIGN_TOP_RIGHT, 0, 0); + draw_bg_grad_simple(ctx, &blend_coords, &draw_area, grad, true); + + /* Align to bottom right */ + lv_area_align(&coords, &draw_area, LV_ALIGN_BOTTOM_RIGHT, 0, 0); + lv_area_align(&coords, &blend_coords, LV_ALIGN_BOTTOM_RIGHT, 0, 0); + draw_bg_grad_simple(ctx, &blend_coords, &draw_area, grad, true); + + /* Align to bottom left */ + lv_area_align(&coords, &draw_area, LV_ALIGN_BOTTOM_LEFT, 0, 0); + lv_area_align(&coords, &blend_coords, LV_ALIGN_BOTTOM_LEFT, 0, 0); + draw_bg_grad_simple(ctx, &blend_coords, &draw_area, grad, true); + + SDL_SetRenderTarget(ctx->renderer, target_backup); + lv_draw_sdl_texture_cache_put(ctx, &key, sizeof(key), texture); + } + return texture; +} + +SDL_Texture * lv_draw_sdl_rect_grad_strip_obtain(lv_draw_sdl_ctx_t * ctx, const lv_grad_dsc_t * grad) +{ + lv_draw_rect_grad_strip_key_t key = rect_grad_strip_key_create(grad); + SDL_Texture * texture = lv_draw_sdl_texture_cache_get(ctx, &key, sizeof(key), NULL); + if(texture == NULL) { + Uint32 amask = 0xFF000000; + Uint32 rmask = 0x00FF0000; + Uint32 gmask = 0x0000FF00; + Uint32 bmask = 0x000000FF; + lv_color_t pixels[256]; + for(int i = 0; i < 256; i++) { + pixels[i] = lv_gradient_calculate(grad, 256, i); + } + int width = grad->dir == LV_GRAD_DIR_VER ? 1 : 256; + int height = grad->dir == LV_GRAD_DIR_VER ? 256 : 1; + SDL_Surface * surface = SDL_CreateRGBSurfaceFrom(pixels, width, height, LV_COLOR_DEPTH, width * LV_COLOR_DEPTH / 8, + rmask, gmask, bmask, amask); + texture = SDL_CreateTextureFromSurface(ctx->renderer, surface); + SDL_assert(texture != NULL); + SDL_FreeSurface(surface); lv_draw_sdl_texture_cache_put(ctx, &key, sizeof(key), texture); } return texture; @@ -193,7 +300,7 @@ void lv_draw_sdl_rect_bg_frag_draw_corners(lv_draw_sdl_ctx_t * ctx, SDL_Texture if(full) { lv_coord_t sx = (lv_coord_t)(dst_area.x1 - corner_area.x1), sy = (lv_coord_t)(dst_area.y1 - corner_area.y1); - SDL_Rect src_rect = {frag_size + 3 + sx, sy, dw, dh}; + SDL_Rect src_rect = {frag_size + FRAG_SPACING + sx, sy, dw, dh}; SDL_RenderCopy(ctx->renderer, frag, &src_rect, &dst_rect); } else { @@ -212,7 +319,7 @@ void lv_draw_sdl_rect_bg_frag_draw_corners(lv_draw_sdl_ctx_t * ctx, SDL_Texture if(full) { lv_coord_t sx = (lv_coord_t)(dst_area.x1 - corner_area.x1), sy = (lv_coord_t)(dst_area.y1 - corner_area.y1); - SDL_Rect src_rect = {frag_size + 3 + sx, frag_size + 3 + sy, dw, dh}; + SDL_Rect src_rect = {frag_size + FRAG_SPACING + sx, frag_size + FRAG_SPACING + sy, dw, dh}; SDL_RenderCopy(ctx->renderer, frag, &src_rect, &dst_rect); } else { @@ -220,7 +327,7 @@ void lv_draw_sdl_rect_bg_frag_draw_corners(lv_draw_sdl_ctx_t * ctx, SDL_Texture SDL_RenderCopyEx(ctx->renderer, frag, &src_rect, &dst_rect, 0, NULL, SDL_FLIP_HORIZONTAL | SDL_FLIP_VERTICAL); } } - /* Lower left, right edge should not be clip */ + /* Lower left, right edge should not be clipped */ corner_area.x1 = coords->x1; corner_area.x2 = coords->x1 + frag_size - 1; if(_lv_area_intersect(&dst_area, &corner_area, clip)) { @@ -231,7 +338,7 @@ void lv_draw_sdl_rect_bg_frag_draw_corners(lv_draw_sdl_ctx_t * ctx, SDL_Texture if(full) { lv_coord_t sx = (lv_coord_t)(dst_area.x1 - corner_area.x1), sy = (lv_coord_t)(dst_area.y1 - corner_area.y1); - SDL_Rect src_rect = {sx, frag_size + 3 + sy, dw, dh}; + SDL_Rect src_rect = {sx, frag_size + FRAG_SPACING + sy, dw, dh}; SDL_RenderCopy(ctx->renderer, frag, &src_rect, &dst_rect); } else { @@ -252,9 +359,23 @@ static void draw_bg_color(lv_draw_sdl_ctx_t * ctx, const lv_area_t * coords, con if(dsc->bg_opa == 0) { return; } - SDL_Color bg_color; - lv_color_to_sdl_color(&dsc->bg_color, &bg_color); lv_coord_t radius = dsc->radius; + SDL_Color bg_color; + if(dsc->bg_grad.dir == LV_GRAD_DIR_NONE) { + lv_color_to_sdl_color(&dsc->bg_color, &bg_color); + } + else if(dsc->bg_grad.stops_count == 1) { + lv_color_to_sdl_color(&dsc->bg_grad.stops[0].color, &bg_color); + } + else { + if(radius <= 0) { + draw_bg_grad_simple(ctx, coords, draw_area, &dsc->bg_grad, false); + } + else { + draw_bg_grad_radius(ctx, coords, draw_area, dsc); + } + return; + } if(radius <= 0) { SDL_Rect rect; lv_area_to_sdl_rect(draw_area, &rect); @@ -277,9 +398,111 @@ static void draw_bg_color(lv_draw_sdl_ctx_t * ctx, const lv_area_t * coords, con frag_render_center(ctx->renderer, texture, real_radius, coords, draw_area, false); } +static void draw_bg_grad_simple(lv_draw_sdl_ctx_t * ctx, const lv_area_t * coords, const lv_area_t * draw_area, + const lv_grad_dsc_t * grad, bool blend_mod) +{ + SDL_Rect dstrect; + lv_area_to_sdl_rect(draw_area, &dstrect); + SDL_Rect srcrect; + if(grad->dir == LV_GRAD_DIR_VER) { + lv_coord_t coords_h = lv_area_get_height(coords); + srcrect.x = 0; + srcrect.y = (draw_area->y1 - coords->y1) * 255 / coords_h; + srcrect.w = 1; + srcrect.h = dstrect.h * 256 / coords_h; + + if(srcrect.y < 0 || srcrect.y > 255) { + return; + } + } + else { + lv_coord_t coords_w = lv_area_get_width(coords); + srcrect.x = (draw_area->x1 - coords->x1) * 255 / coords_w; + srcrect.y = 0; + srcrect.w = dstrect.w * 256 / coords_w; + srcrect.h = 1; + + if(srcrect.x < 0 || srcrect.x > 255) { + return; + } + } + + SDL_Texture * grad_texture = lv_draw_sdl_rect_grad_strip_obtain(ctx, grad); + if(blend_mod) { + SDL_SetTextureBlendMode(grad_texture, SDL_BLENDMODE_MOD); + } + else { + SDL_SetTextureBlendMode(grad_texture, SDL_BLENDMODE_BLEND); + } + + SDL_RenderCopy(ctx->renderer, grad_texture, &srcrect, &dstrect); +} + +static void draw_bg_grad_radius(lv_draw_sdl_ctx_t * ctx, const lv_area_t * coords, const lv_area_t * draw_area, + const lv_draw_rect_dsc_t * dsc) +{ + lv_coord_t radius = dsc->radius; + /*A small texture with a quarter of the rect is enough*/ + lv_coord_t bg_w = lv_area_get_width(coords), bg_h = lv_area_get_height(coords); + lv_coord_t real_radius = LV_MIN3(bg_w / 2, bg_h / 2, radius); + SDL_Texture * grad_texture = lv_draw_sdl_rect_grad_frag_obtain(ctx, &dsc->bg_grad, bg_w, bg_h, radius); + SDL_SetTextureBlendMode(grad_texture, SDL_BLENDMODE_BLEND); + + lv_draw_sdl_rect_bg_frag_draw_corners(ctx, grad_texture, real_radius, coords, draw_area, true); + lv_area_t part_coords; + lv_area_t part_area; + if(bg_w > radius * 2) { + /*Draw left, middle, right*/ + part_coords.x1 = 0; + part_coords.x2 = radius - 1; + part_coords.y1 = radius; + part_coords.y2 = bg_h - radius - 1; + + lv_area_align(coords, &part_coords, LV_ALIGN_LEFT_MID, 0, 0); + _lv_area_intersect(&part_area, &part_coords, draw_area); + draw_bg_grad_simple(ctx, coords, &part_area, &dsc->bg_grad, false); + + lv_area_align(coords, &part_coords, LV_ALIGN_RIGHT_MID, 0, 0); + _lv_area_intersect(&part_area, &part_coords, draw_area); + draw_bg_grad_simple(ctx, coords, &part_area, &dsc->bg_grad, false); + + part_coords.x1 = radius; + part_coords.x2 = bg_w - radius - 1; + part_coords.y1 = 0; + part_coords.y2 = bg_h - 1; + lv_area_align(coords, &part_coords, LV_ALIGN_CENTER, 0, 0); + _lv_area_intersect(&part_area, &part_coords, draw_area); + draw_bg_grad_simple(ctx, coords, &part_area, &dsc->bg_grad, false); + } + else if(bg_h > radius * 2) { + /*Draw top, middle, bottom*/ + part_coords.x1 = radius; + part_coords.x2 = bg_w - radius - 1; + part_coords.y1 = 0; + part_coords.y2 = radius - 1; + + lv_area_align(coords, &part_coords, LV_ALIGN_TOP_MID, 0, 0); + _lv_area_intersect(&part_area, &part_coords, draw_area); + draw_bg_grad_simple(ctx, coords, &part_area, &dsc->bg_grad, false); + + lv_area_align(coords, &part_coords, LV_ALIGN_BOTTOM_MID, 0, 0); + _lv_area_intersect(&part_area, &part_coords, draw_area); + draw_bg_grad_simple(ctx, coords, &part_area, &dsc->bg_grad, false); + + part_coords.x1 = 0; + part_coords.x2 = bg_w - 1; + part_coords.y1 = radius; + part_coords.y2 = bg_h - radius - 1; + lv_area_align(coords, &part_coords, LV_ALIGN_CENTER, 0, 0); + _lv_area_intersect(&part_area, &part_coords, draw_area); + draw_bg_grad_simple(ctx, coords, &part_area, &dsc->bg_grad, false); + } +} + static void draw_bg_img(lv_draw_sdl_ctx_t * ctx, const lv_area_t * coords, const lv_area_t * draw_area, const lv_draw_rect_dsc_t * dsc) { + LV_UNUSED(draw_area); if(SKIP_IMAGE(dsc)) return; lv_img_src_t src_type = lv_img_src_get_type(dsc->bg_img_src); @@ -521,8 +744,8 @@ static void draw_border_generic(lv_draw_sdl_ctx_t * ctx, const lv_area_t * outer lv_coord_t frag_size = LV_MAX(radius, max_side); SDL_Texture * texture = lv_draw_sdl_texture_cache_get(ctx, &key, sizeof(key), NULL); if(texture == NULL) { - /* Create a mask texture with size of (frag_size * 2 + 3) */ - const lv_area_t frag_area = {0, 0, frag_size * 2 + 2, frag_size * 2 + 2}; + /* Create a mask texture with size of (frag_size * 2 + FRAG_SPACING) */ + const lv_area_t frag_area = {0, 0, frag_size * 2 + FRAG_SPACING - 1, frag_size * 2 + FRAG_SPACING - 1}; /*Create mask for the outer area*/ int16_t mask_ids[2] = {LV_MASK_ID_INV, LV_MASK_ID_INV}; @@ -595,7 +818,7 @@ static void frag_render_borders(SDL_Renderer * renderer, SDL_Texture * frag, lv_ lv_coord_t dh = lv_area_get_height(&dst_area); if(full) { lv_coord_t sy = (lv_coord_t)(dst_area.y1 - border_area.y1); - SDL_Rect src_rect = {frag_size + 1, frag_size + 3 + sy, 1, dh}; + SDL_Rect src_rect = {frag_size + 1, frag_size + FRAG_SPACING + sy, 1, dh}; SDL_RenderCopy(renderer, frag, &src_rect, &dst_rect); } else { @@ -634,7 +857,7 @@ static void frag_render_borders(SDL_Renderer * renderer, SDL_Texture * frag, lv_ lv_coord_t dw = lv_area_get_width(&dst_area); if(full) { lv_coord_t sx = (lv_coord_t)(dst_area.x1 - border_area.x1); - SDL_Rect src_rect = {frag_size + 3 + sx, frag_size + 1, dw, 1}; + SDL_Rect src_rect = {frag_size + FRAG_SPACING + sx, frag_size + 1, dw, 1}; SDL_RenderCopy(renderer, frag, &src_rect, &dst_rect); } else { @@ -682,6 +905,38 @@ static lv_draw_rect_bg_key_t rect_bg_key_create(lv_coord_t radius, lv_coord_t si return key; } +static lv_draw_rect_grad_frag_key_t rect_grad_frag_key_create(const lv_grad_dsc_t * grad, lv_coord_t w, lv_coord_t h, + lv_coord_t radius) +{ + lv_draw_rect_grad_frag_key_t key; + SDL_memset(&key, 0, sizeof(key)); + key.magic = LV_GPU_CACHE_KEY_MAGIC_RECT_GRAD; + key.stops_count = grad->stops_count; + key.dir = grad->dir; + for(uint8_t i = 0; i < grad->stops_count; i++) { + key.stops[i].frac = grad->stops[i].frac; + key.stops[i].color = grad->stops[i].color; + } + key.w = w; + key.h = h; + key.radius = radius; + return key; +} + +static lv_draw_rect_grad_strip_key_t rect_grad_strip_key_create(const lv_grad_dsc_t * grad) +{ + lv_draw_rect_grad_strip_key_t key; + SDL_memset(&key, 0, sizeof(key)); + key.magic = LV_GPU_CACHE_KEY_MAGIC_RECT_GRAD; + key.stops_count = grad->stops_count; + key.dir = grad->dir; + for(uint8_t i = 0; i < grad->stops_count; i++) { + key.stops[i].frac = grad->stops[i].frac; + key.stops[i].color = grad->stops[i].color; + } + return key; +} + static lv_draw_rect_shadow_key_t rect_shadow_key_create(lv_coord_t radius, lv_coord_t size, lv_coord_t blur) { lv_draw_rect_shadow_key_t key; diff --git a/src/draw/sdl/lv_draw_sdl_rect.h b/src/draw/sdl/lv_draw_sdl_rect.h index 1e9be3445..3472af31b 100644 --- a/src/draw/sdl/lv_draw_sdl_rect.h +++ b/src/draw/sdl/lv_draw_sdl_rect.h @@ -59,6 +59,11 @@ typedef struct lv_draw_sdl_rect_header_t { SDL_Texture * lv_draw_sdl_rect_bg_frag_obtain(lv_draw_sdl_ctx_t * ctx, lv_coord_t radius); +SDL_Texture * lv_draw_sdl_rect_grad_frag_obtain(lv_draw_sdl_ctx_t * ctx, const lv_grad_dsc_t * grad, lv_coord_t w, + lv_coord_t h, lv_coord_t radius); + +SDL_Texture * lv_draw_sdl_rect_grad_strip_obtain(lv_draw_sdl_ctx_t * ctx, const lv_grad_dsc_t * grad); + void lv_draw_sdl_rect_bg_frag_draw_corners(lv_draw_sdl_ctx_t * ctx, SDL_Texture * frag, lv_coord_t frag_size, const lv_area_t * coords, const lv_area_t * clip, bool full); diff --git a/src/draw/sdl/lv_draw_sdl_texture_cache.h b/src/draw/sdl/lv_draw_sdl_texture_cache.h index dc8b578e6..1bbf17cd8 100644 --- a/src/draw/sdl/lv_draw_sdl_texture_cache.h +++ b/src/draw/sdl/lv_draw_sdl_texture_cache.h @@ -50,6 +50,7 @@ typedef enum { LV_GPU_CACHE_KEY_MAGIC_RECT_BG = 0x31, LV_GPU_CACHE_KEY_MAGIC_RECT_SHADOW = 0x32, LV_GPU_CACHE_KEY_MAGIC_RECT_BORDER = 0x33, + LV_GPU_CACHE_KEY_MAGIC_RECT_GRAD = 0x34, LV_GPU_CACHE_KEY_MAGIC_FONT_GLYPH = 0x41, LV_GPU_CACHE_KEY_MAGIC_MASK = 0x51, } lv_sdl_cache_key_magic_t;