feat(draw): add gradient dithering support (#2872)

* Add dithering to gradients

* Add support for 8x8 matrix for ordered dithering

* Fix CI errors

* Try error diffusion on vertical gradient too

* Vertical error diffusion dithering

* Add support for runtime based dithering mode selection (from none, ordered, error diffusion).

* Reduce the binary size of the code by sharing the dithering table when appropriate.

* Fix CI

* Fix CI

* Review corrections

* Fix union mapping

* Revert bg_color changes

* Fix for keeping bg_color in the API.

* Fix after review

* Add support for setting multiple stops per gradient in the style API

* Let's make an example

Co-authored-by: Gabor Kiss-Vamosi <kisvegabor@gmail.com>
This commit is contained in:
X-Ryl669
2022-01-11 12:38:30 +01:00
committed by GitHub
parent 89389d3c96
commit 6617385f8a
25 changed files with 980 additions and 82 deletions

View File

@@ -252,6 +252,24 @@ Set the point from which the background's gradient color should start. 0 means t
<li style='display:inline; margin-right: 20px; margin-left: 0px'><strong>Ext. draw</strong> No</li>
</ul>
### bg_gradient
Set the gradient definition for the body. The pointed instance must exist while the object is alive. NULL to disable
<ul>
<li style='display:inline; margin-right: 20px; margin-left: 0px'><strong>Default</strong> `NULL`</li>
<li style='display:inline; margin-right: 20px; margin-left: 0px'><strong>Inherited</strong> No</li>
<li style='display:inline; margin-right: 20px; margin-left: 0px'><strong>Layout</strong> No</li>
<li style='display:inline; margin-right: 20px; margin-left: 0px'><strong>Ext. draw</strong> No</li>
</ul>
### bg_dither_mode
Set the dithering mode of the gradient of the background. The possible values are `LV_DITHER_NONE/ORDERED/ERR_DIFF`.
<ul>
<li style='display:inline; margin-right: 20px; margin-left: 0px'><strong>Default</strong> `LV_DITHER_NONE`</li>
<li style='display:inline; margin-right: 20px; margin-left: 0px'><strong>Inherited</strong> No</li>
<li style='display:inline; margin-right: 20px; margin-left: 0px'><strong>Layout</strong> No</li>
<li style='display:inline; margin-right: 20px; margin-left: 0px'><strong>Ext. draw</strong> No</li>
</ul>
### bg_img_src
Set a background image. Can be a pointer to `lv_img_dsc_t`, a path to a file or an `LV_SYMBOL_...`
<ul>

View File

@@ -12,13 +12,17 @@ void lv_example_style_2(void)
/*Make a gradient*/
lv_style_set_bg_opa(&style, LV_OPA_COVER);
lv_style_set_bg_color(&style, lv_palette_lighten(LV_PALETTE_GREY, 1));
lv_style_set_bg_grad_color(&style, lv_palette_main(LV_PALETTE_BLUE));
lv_style_set_bg_grad_dir(&style, LV_GRAD_DIR_VER);
static lv_gradient_t grad;
grad.dir = LV_GRAD_DIR_VER;
grad.stops_count = 2;
grad.stops[0].color = lv_palette_lighten(LV_PALETTE_GREY, 1);
grad.stops[1].color = lv_palette_main(LV_PALETTE_BLUE);
/*Shift the gradient to the bottom*/
lv_style_set_bg_main_stop(&style, 128);
lv_style_set_bg_grad_stop(&style, 192);
grad.stops[0].frac = 128;
grad.stops[1].frac = 192;
lv_style_set_bg_gradient(&style, &grad);
/*Create an object with the new style*/
lv_obj_t * obj = lv_obj_create(lv_scr_act());

View File

@@ -11,9 +11,9 @@ void lv_example_canvas_1(void)
lv_draw_rect_dsc_init(&rect_dsc);
rect_dsc.radius = 10;
rect_dsc.bg_opa = LV_OPA_COVER;
rect_dsc.bg_grad_dir = LV_GRAD_DIR_HOR;
rect_dsc.bg_color = lv_palette_main(LV_PALETTE_RED);
rect_dsc.bg_grad_color = lv_palette_main(LV_PALETTE_BLUE);
rect_dsc.bg_grad.dir = LV_GRAD_DIR_HOR;
rect_dsc.bg_grad.stops[0].color = lv_palette_main(LV_PALETTE_RED);
rect_dsc.bg_grad.stops[1].color = lv_palette_main(LV_PALETTE_BLUE);
rect_dsc.border_width = 2;
rect_dsc.border_opa = LV_OPA_90;
rect_dsc.border_color = lv_color_white();

View File

@@ -6,9 +6,9 @@ rect_dsc = lv.draw_rect_dsc_t()
rect_dsc.init()
rect_dsc.radius = 10
rect_dsc.bg_opa = lv.OPA.COVER
rect_dsc.bg_grad_dir = lv.GRAD_DIR.HOR
rect_dsc.bg_color = lv.palette_main(lv.PALETTE.RED)
rect_dsc.bg_grad_color = lv.palette_main(lv.PALETTE.BLUE)
rect_dsc.bg_grad.dir = lv.GRAD_DIR.HOR
rect_dsc.bg_grad.stops[0].color = lv.palette_main(lv.PALETTE.RED)
rect_dsc.bg_grad.stops[1].color = lv.palette_main(lv.PALETTE.BLUE)
rect_dsc.border_width = 2
rect_dsc.border_opa = lv.OPA._90
rect_dsc.border_color = lv.color_white()

View File

@@ -119,6 +119,20 @@
* 0: to disable caching */
#define LV_CIRCLE_CACHE_SIZE 4
/*Allow dithering gradient (to achieve visual smooth color gradients on limited color depth display)
*LV_DITHER_GRADIENT implies allocating one or two more lines of the object's rendering surface
*The increase in memory consumption is (32 bits * object width) plus 24 bits * object width if using error diffusion */
#define LV_DITHER_GRADIENT 1
/*Add support for error diffusion dithering.
*Error diffusion dithering gets a much better visual result, but implies more CPU consumption and memory when drawing.
*The increase in memory consumption is (24 bits * object's width)*/
#define LV_DITHER_ERROR_DIFFUSION 1
/**Number of stops allowed per gradient. Increase this to allow more stops.
*This adds (sizeof(lv_color_t) + 1) bytes per additional stop*/
#define LV_GRADIENT_MAX_STOPS 2
#endif /*LV_DRAW_COMPLEX*/
/*Default image cache size. Image caching keeps the images opened.

View File

@@ -120,6 +120,14 @@ props = [
'style_type': 'num', 'var_type': 'lv_coord_t', 'default':255, 'inherited': 0, 'layout': 0, 'ext_draw': 0,
'dsc': "Set the point from which the background's gradient color should start. 0 means to top/left side, 255 the bottom/right side, 128 the center, and so on"},
{'name': 'BG_GRADIENT',
'style_type': 'ptr', 'var_type': 'const lv_gradient_t *', 'default':'`NULL`', 'inherited': 0, 'layout': 0, 'ext_draw': 0,
'dsc': "Set the gradient definition for the body. The pointed instance must exist while the object is alive. NULL to disable"},
{'name': 'BG_DITHER_MODE',
'style_type': 'num', 'var_type': 'lv_dither_mode_t', 'default':'`LV_DITHER_NONE`', 'inherited': 0, 'layout': 0, 'ext_draw': 0,
'dsc': "Set the dithering mode of the gradient of the background. The possible values are `LV_DITHER_NONE/ORDERED/ERR_DIFF`."},
{'name': 'BG_IMG_SRC',
'style_type': 'ptr', 'var_type': 'const void *', 'default':'`NULL`', 'inherited': 0, 'layout': 0, 'ext_draw': 1,
'dsc': "Set a background image. Can be a pointer to `lv_img_dsc_t`, a path to a file or an `LV_SYMBOL_...`"},

View File

@@ -58,11 +58,19 @@ void lv_obj_init_draw_rect_dsc(lv_obj_t * obj, uint32_t part, lv_draw_rect_dsc_t
draw_dsc->bg_opa = lv_obj_get_style_bg_opa(obj, part);
if(draw_dsc->bg_opa > LV_OPA_MIN) {
draw_dsc->bg_color = lv_obj_get_style_bg_color_filtered(obj, part);
draw_dsc->bg_grad_dir = lv_obj_get_style_bg_grad_dir(obj, part);
if(draw_dsc->bg_grad_dir != LV_GRAD_DIR_NONE) {
draw_dsc->bg_grad_color = lv_obj_get_style_bg_grad_color_filtered(obj, part);
draw_dsc->bg_main_color_stop = lv_obj_get_style_bg_main_stop(obj, part);
draw_dsc->bg_grad_color_stop = lv_obj_get_style_bg_grad_stop(obj, part);
const lv_gradient_t * grad = lv_obj_get_style_bg_gradient(obj, part);
if(grad && grad->dir != LV_GRAD_DIR_NONE) {
lv_memcpy(&draw_dsc->bg_grad, grad, sizeof(*grad));
}
else {
draw_dsc->bg_grad.dir = lv_obj_get_style_bg_grad_dir(obj, part);
if(draw_dsc->bg_grad.dir != LV_GRAD_DIR_NONE) {
draw_dsc->bg_grad.stops[0].color = lv_obj_get_style_bg_color_filtered(obj, part);
draw_dsc->bg_grad.stops[1].color = lv_obj_get_style_bg_grad_color_filtered(obj, part);
draw_dsc->bg_grad.stops[0].frac = lv_obj_get_style_bg_main_stop(obj, part);
draw_dsc->bg_grad.stops[1].frac = lv_obj_get_style_bg_grad_stop(obj, part);
}
draw_dsc->bg_grad.dither = lv_obj_get_style_bg_dither_mode(obj, part);
}
}
}

View File

@@ -232,6 +232,22 @@ void lv_obj_set_style_bg_grad_stop(struct _lv_obj_t * obj, lv_coord_t value, lv_
lv_obj_set_local_style_prop(obj, LV_STYLE_BG_GRAD_STOP, v, selector);
}
void lv_obj_set_style_bg_gradient(struct _lv_obj_t * obj, const lv_gradient_t * value, lv_style_selector_t selector)
{
lv_style_value_t v = {
.ptr = value
};
lv_obj_set_local_style_prop(obj, LV_STYLE_BG_GRADIENT, v, selector);
}
void lv_obj_set_style_bg_dither_mode(struct _lv_obj_t * obj, lv_dither_mode_t value, lv_style_selector_t selector)
{
lv_style_value_t v = {
.num = (int32_t)value
};
lv_obj_set_local_style_prop(obj, LV_STYLE_BG_DITHER_MODE, v, selector);
}
void lv_obj_set_style_bg_img_src(struct _lv_obj_t * obj, const void * value, lv_style_selector_t selector)
{
lv_style_value_t v = {

View File

@@ -172,6 +172,18 @@ static inline lv_coord_t lv_obj_get_style_bg_grad_stop(const struct _lv_obj_t *
return (lv_coord_t)v.num;
}
static inline const lv_gradient_t * lv_obj_get_style_bg_gradient(const struct _lv_obj_t * obj, uint32_t part)
{
lv_style_value_t v = lv_obj_get_style_prop(obj, part, LV_STYLE_BG_GRADIENT);
return (const lv_gradient_t *)v.ptr;
}
static inline lv_dither_mode_t lv_obj_get_style_bg_dither_mode(const struct _lv_obj_t * obj, uint32_t part)
{
lv_style_value_t v = lv_obj_get_style_prop(obj, part, LV_STYLE_BG_DITHER_MODE);
return (lv_dither_mode_t)v.num;
}
static inline const void * lv_obj_get_style_bg_img_src(const struct _lv_obj_t * obj, uint32_t part)
{
lv_style_value_t v = lv_obj_get_style_prop(obj, part, LV_STYLE_BG_IMG_SRC);
@@ -562,6 +574,8 @@ void lv_obj_set_style_bg_grad_color_filtered(struct _lv_obj_t * obj, lv_color_t
void lv_obj_set_style_bg_grad_dir(struct _lv_obj_t * obj, lv_grad_dir_t value, lv_style_selector_t selector);
void lv_obj_set_style_bg_main_stop(struct _lv_obj_t * obj, lv_coord_t value, lv_style_selector_t selector);
void lv_obj_set_style_bg_grad_stop(struct _lv_obj_t * obj, lv_coord_t value, lv_style_selector_t selector);
void lv_obj_set_style_bg_gradient(struct _lv_obj_t * obj, const lv_gradient_t * value, lv_style_selector_t selector);
void lv_obj_set_style_bg_dither_mode(struct _lv_obj_t * obj, lv_dither_mode_t value, lv_style_selector_t selector);
void lv_obj_set_style_bg_img_src(struct _lv_obj_t * obj, const void * value, lv_style_selector_t selector);
void lv_obj_set_style_bg_img_opa(struct _lv_obj_t * obj, lv_opa_t value, lv_style_selector_t selector);
void lv_obj_set_style_bg_img_recolor(struct _lv_obj_t * obj, lv_color_t value, lv_style_selector_t selector);

View File

@@ -38,10 +38,14 @@ LV_ATTRIBUTE_FAST_MEM void lv_draw_rect_dsc_init(lv_draw_rect_dsc_t * dsc)
{
lv_memset_00(dsc, sizeof(lv_draw_rect_dsc_t));
dsc->bg_color = lv_color_white();
dsc->bg_grad_color = lv_color_black();
#if __STDC_VERSION__ < 201112L
dsc->bg_grad.stops[0].color = lv_color_white();
#endif
dsc->bg_grad.stops[1].color = lv_color_black();
dsc->bg_grad.stops[1].frac = 0xFF;
dsc->bg_grad.stops_count = 2;
dsc->border_color = lv_color_black();
dsc->shadow_color = lv_color_black();
dsc->bg_grad_color_stop = 0xFF;
dsc->bg_img_symbol_font = LV_FONT_DEFAULT;
dsc->bg_opa = LV_OPA_COVER;
dsc->bg_img_opa = LV_OPA_COVER;

View File

@@ -17,6 +17,7 @@ extern "C" {
#include "../misc/lv_color.h"
#include "../misc/lv_area.h"
#include "../misc/lv_style.h"
#include "sw/lv_draw_sw_gradient.h"
/*********************
* DEFINES
@@ -33,12 +34,15 @@ typedef struct {
lv_blend_mode_t blend_mode;
/*Background*/
lv_color_t bg_color;
lv_color_t bg_grad_color;
uint8_t bg_main_color_stop;
uint8_t bg_grad_color_stop;
lv_opa_t bg_opa;
lv_grad_dir_t bg_grad_dir : 3;
#if __STDC_VERSION__ >= 201112L
union {
#endif
lv_color_t bg_color; /**< First element of a gradient is a color, so it maps well here*/
lv_gradient_t bg_grad;
#if __STDC_VERSION__ >= 201112L
};
#endif
/*Background img*/
const void * bg_img_src;

View File

@@ -6,6 +6,8 @@ CSRCS += lv_draw_sw_letter.c
CSRCS += lv_draw_sw_line.c
CSRCS += lv_draw_sw_rect.c
CSRCS += lv_draw_sw_polygon.c
CSRCS += lv_draw_sw_gradient.c
CSRCS += lv_draw_sw_dither.c
DEPPATH += --dep-path $(LVGL_DIR)/$(LVGL_DIR_NAME)/src/draw/sw
VPATH += :$(LVGL_DIR)/$(LVGL_DIR_NAME)/src/draw/sw

View File

@@ -28,8 +28,8 @@ extern "C" {
typedef struct {
const lv_area_t * blend_area; /**< The area with absolute coordinates to draw on `draw_ctx->buf`
* will be clipped to draw_`ctx->clip_area` */
const lv_color_t * src_buf; /**< Pointer to an image to blend. If set `fill_color is ignored`*/
* will be clipped to `draw_ctx->clip_area` */
const lv_color_t * src_buf; /**< Pointer to an image to blend. If set `fill_color` is ignored */
lv_color_t color; /**< Fill color*/
lv_opa_t * mask_buf; /**< NULL if ignored, or an alpha mask to apply on `blend_area`*/
lv_draw_mask_res_t mask_res; /**< The result of the previous mask operation */

View File

@@ -0,0 +1,213 @@
/**
* @file lv_draw_sw_dither.c
*
*/
/*********************
* INCLUDES
*********************/
#include "lv_draw_sw_dither.h"
#include "lv_draw_sw_gradient.h"
#include "../../misc/lv_color.h"
/**********************
* STATIC FUNCTIONS
**********************/
#if _DITHER_GRADIENT
LV_ATTRIBUTE_FAST_MEM void lv_dither_none(lv_gradient_cache_t * grad, lv_coord_t x, lv_coord_t y, lv_coord_t w)
{
LV_UNUSED(x);
LV_UNUSED(y);
if(grad == NULL || grad->filled) return;
for(lv_coord_t i = 0; i < w; i++) {
grad->map[i] = lv_color_hex(grad->hmap[i].full);
}
grad->filled = 1;
}
static const uint8_t dither_ordered_threshold_matrix[8 * 8] = {
0, 48, 12, 60, 3, 51, 15, 63,
32, 16, 44, 28, 35, 19, 47, 31,
8, 56, 4, 52, 11, 59, 7, 55,
40, 24, 36, 20, 43, 27, 39, 23,
2, 50, 14, 62, 1, 49, 13, 61,
34, 18, 46, 30, 33, 17, 45, 29,
10, 58, 6, 54, 9, 57, 5, 53,
42, 26, 38, 22, 41, 25, 37, 21
}; /* Shift by 6 to normalize */
LV_ATTRIBUTE_FAST_MEM void lv_dither_ordered_hor(lv_gradient_cache_t * grad, lv_coord_t x, lv_coord_t y, lv_coord_t w)
{
LV_UNUSED(x);
/* For vertical dithering, the error is spread on the next column (and not next line).
Since the renderer is scanline based, it's not obvious what could be used to perform the rendering efficiently.
The algorithm below is based on few assumptions:
1. An error diffusion algorithm (like Floyd Steinberg) here would be hard to implement since it means that a pixel on column n depends on the pixel on row n
2. Instead an ordered dithering algorithm shift the value a bit, but the influence only spread from the matrix size (used 8x8 here)
3. It means that a pixel i,j only depends on the value of a pixel i-7, j-7 to i,j and no other one.
Then we compute a complete row of ordered dither and store it in out. */
/*The apply the algorithm for this patch*/
for(lv_coord_t j = 0; j < w; j++) {
int8_t factor = dither_ordered_threshold_matrix[(y & 7) * 8 + ((j) & 7)] - 32;
lv_color32_t tmp = grad->hmap[LV_CLAMP(0, j - 4, grad->hmap_size)];
lv_color32_t t;
t.ch.red = LV_CLAMP(0, tmp.ch.red + factor, 255);
t.ch.green = LV_CLAMP(0, tmp.ch.green + factor, 255);
t.ch.blue = LV_CLAMP(0, tmp.ch.blue + factor, 255);
grad->map[j] = lv_color_hex(t.full);
}
}
LV_ATTRIBUTE_FAST_MEM void lv_dither_ordered_ver(lv_gradient_cache_t * grad, lv_coord_t x, lv_coord_t y, lv_coord_t w)
{
/* For vertical dithering, the error is spread on the next column (and not next line).
Since the renderer is scanline based, it's not obvious what could be used to perform the rendering efficiently.
The algorithm below is based on few assumptions:
1. An error diffusion algorithm (like Floyd Steinberg) here would be hard to implement since it means that a pixel on column n depends on the pixel on row n
2. Instead an ordered dithering algorithm shift the value a bit, but the influence only spread from the matrix size (used 8x8 here)
3. It means that a pixel i,j only depends on the value of a pixel i-7, j-7 to i,j and no other one.
Then we compute a complete row of ordered dither and store it in out. */
/*Extract patch for working with, selected pseudo randomly*/
lv_color32_t tmp = grad->hmap[LV_CLAMP(0, y - 4, grad->hmap_size)];
/*The apply the algorithm for this patch*/
for(lv_coord_t j = 0; j < 8; j++) {
int8_t factor = dither_ordered_threshold_matrix[(y & 7) * 8 + ((j + x) & 7)] - 32;
lv_color32_t t;
t.ch.red = LV_CLAMP(0, tmp.ch.red + factor, 255);
t.ch.green = LV_CLAMP(0, tmp.ch.green + factor, 255);
t.ch.blue = LV_CLAMP(0, tmp.ch.blue + factor, 255);
grad->map[j] = lv_color_hex(t.full);
}
/*Finally fill the line*/
lv_coord_t j = 8;
for(; j < w - 8; j += 8) {
lv_memcpy(grad->map + j, grad->map, 8 * sizeof(*grad->map));
}
/* Prevent overwriting */
for(; j < w; j++) {
grad->map[j] = grad->map[j & 7];
}
}
#if LV_DITHER_ERROR_DIFFUSION == 1
LV_ATTRIBUTE_FAST_MEM void lv_dither_err_diff_hor(lv_gradient_cache_t * grad, lv_coord_t xs, lv_coord_t y, lv_coord_t w)
{
LV_UNUSED(xs);
LV_UNUSED(y);
LV_UNUSED(w);
/* Implement Floyd Steinberg algorithm, see https://surma.dev/things/ditherpunk/
Coefs are: x 7
3 5 1
/ 16
Can be implemented as: x (x<<3 - x)
(x<<2 - x) (x<<2+x) x
*/
int coef[4] = {0, 0, 0, 0};
#define FS_COMPUTE_ERROR(e) { coef[0] = (e<<3) - e; coef[1] = (e<<2) - e; coef[2] = (e<<2) + e; coef[3] = e; }
#define FS_COMPONENTS(A, OP, B, C) A.ch.red = LV_CLAMP(0, A.ch.red OP B.r OP C.r, 255); A.ch.green = LV_CLAMP(0, A.ch.green OP B.g OP C.g, 255); A.ch.blue = LV_CLAMP(0, A.ch.blue OP B.b OP C.b, 255);
#define FS_QUANT_ERROR(e, t, q) { lv_color32_t u; u.full = lv_color_to32(q); e.r = (int8_t)(t.ch.red - u.ch.red); e.g = (int8_t)(t.ch.green - u.ch.green); e.b = (int8_t)(t.ch.blue - u.ch.blue); }
lv_scolor24_t next_px_err, next_l = grad->error_acc[1], error;
/*First last pixel are not dithered */
grad->map[0] = lv_color_hex(grad->hmap[0].full);
for(lv_coord_t x = 1; x < grad->hmap_size - 1; x++) {
lv_color32_t t = grad->hmap[x];
lv_color_t q;
/*Add error term*/
FS_COMPONENTS(t, +, next_px_err, next_l);
next_l = grad->error_acc[x + 1];
/*Quantify*/
q = lv_color_hex(t.full);
/*Then compute error*/
FS_QUANT_ERROR(error, t, q);
/*Dither the error*/
FS_COMPUTE_ERROR(error.r);
next_px_err.r = coef[0] >> 4;
grad->error_acc[x - 1].r += coef[1] >> 4;
grad->error_acc[x].r += coef[2] >> 4;
grad->error_acc[x + 1].r = coef[3] >> 4;
FS_COMPUTE_ERROR(error.g);
next_px_err.g = coef[0] >> 4;
grad->error_acc[x - 1].g += coef[1] >> 4;
grad->error_acc[x].g += coef[2] >> 4;
grad->error_acc[x + 1].g = coef[3] >> 4;
FS_COMPUTE_ERROR(error.b);
next_px_err.b = coef[0] >> 4;
grad->error_acc[x - 1].b += coef[1] >> 4;
grad->error_acc[x].b += coef[2] >> 4;
grad->error_acc[x + 1].b = coef[3] >> 4;
grad->map[x] = q;
}
grad->map[grad->hmap_size - 1] = lv_color_hex(grad->hmap[grad->hmap_size - 1].full);
}
LV_ATTRIBUTE_FAST_MEM void lv_dither_err_diff_ver(lv_gradient_cache_t * grad, lv_coord_t xs, lv_coord_t y, lv_coord_t w)
{
/* Try to implement error diffusion on a vertical gradient and an horizontal map using those tricks:
Since the given hi-resolution gradient (in src) is vertical, the Floyd Steinberg algorithm pass need to be rotated,
so we'll get this instead (from top to bottom):
A B C
1 [ ][ ][ ]
2 [ ][ ][ ] Pixel A2 will spread its error on pixel A3 with coefficient 7,
3 [ ][ ][ ] Pixel A2 will spread its error on pixel B1 with coefficient 3, B2 with coef 5 and B3 with coef 1
When taking into account an arbitrary pixel P(i,j), its added error diffusion term is:
e(i,j) = 1/16 * [ e(i-1,j) * 5 + e(i-1,j+1) * 3 + e(i-1,j-1) * 1 + e(i,j-1) * 7]
This means that the error term depends on pixel W, NW, N and SW.
If we consider that we are generating the error diffused gradient map from top to bottom, we can remember the previous
line (N, NW) in the term above. Also, we remember the (W) term too since we are computing the gradient map from left to right.
However, the SW term is painful for us, we can't support it (since to get it, we need its own SW term and so on).
Let's remove it and re-dispatch the error factor accordingly so they stays normalized:
e(i,j) ~= 1/16 * [ e(i-1,j) * 6 + e(i-1,j-1) * 1 + e(i,j-1) * 9]
That's the idea of this pseudo Floyd Steinberg dithering */
#define FS_APPLY(d, s, c) d.r = (int8_t)(s.r * c) >> 4; d.g = (int8_t)(s.g * c) >> 4; d.b = (int8_t)(s.b * c) >> 4;
#define FS_COMPONENTS3(A, OP, B, b, C, c, D, d) \
A.ch.red = LV_CLAMP(0, A.ch.red OP ((B.r * b OP C.r * c OP D.r * d) >> 4), 255); \
A.ch.green = LV_CLAMP(0, A.ch.green OP ((B.r * b OP C.r * c OP D.r * d) >> 4), 255); \
A.ch.blue = LV_CLAMP(0, A.ch.blue OP ((B.r * b OP C.r * c OP D.r * d) >> 4), 255);
lv_scolor24_t next_px_err, prev_l = grad->error_acc[0];
/*Compute the error term for the current pixel (first pixel is never dithered)*/
if(xs == 0) {
grad->map[0] = lv_color_hex(grad->hmap[y].full);
FS_QUANT_ERROR(next_px_err, grad->hmap[y], grad->map[0]);
}
else {
lv_color_t tmp = lv_color_hex(grad->hmap[y].full);
lv_color32_t t = grad->hmap[y];
FS_QUANT_ERROR(next_px_err, grad->hmap[y], tmp);
FS_COMPONENTS3(t, +, next_px_err, 6, prev_l, 1, grad->error_acc[0], 9);
grad->map[0] = lv_color_hex(t.full);
}
for(lv_coord_t x = 1; x < w; x++) {
lv_color32_t t = grad->hmap[y];
lv_color_t q;
/*Add the current error term*/
FS_COMPONENTS3(t, +, next_px_err, 6, prev_l, 1, grad->error_acc[x], 9);
prev_l = grad->error_acc[x];
/*Quantize and compute error term*/
q = lv_color_hex(t.full);
FS_QUANT_ERROR(next_px_err, t, q);
/*Store error for next line computation*/
grad->error_acc[x] = next_px_err;
grad->map[x] = q;
}
}
#endif
#endif

View File

@@ -0,0 +1,71 @@
/**
* @file lv_draw_sw_dither.h
*
*/
#ifndef LV_DRAW_SW_DITHER_H
#define LV_DRAW_SW_DITHER_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#include "../../core/lv_obj_pos.h"
/*********************
* DEFINES
*********************/
#if LV_COLOR_DEPTH < 32 && LV_DRAW_COMPLEX == 1 && LV_DITHER_GRADIENT == 1
#define _DITHER_GRADIENT 1
#else
#define _DITHER_GRADIENT 0
#endif
/**********************
* TYPEDEFS
**********************/
#if _DITHER_GRADIENT
/*A signed error color component*/
typedef struct {
int8_t r, g, b;
} lv_scolor24_t;
struct _lv_gradient_cache_t;
typedef void (*lv_dither_func_t)(struct _lv_gradient_cache_t * grad, lv_coord_t x, lv_coord_t y, lv_coord_t w);
#endif
/**********************
* PROTOTYPES
**********************/
#if LV_DRAW_COMPLEX
#if _DITHER_GRADIENT
LV_ATTRIBUTE_FAST_MEM void lv_dither_none(struct _lv_gradient_cache_t * grad, lv_coord_t x, lv_coord_t y, lv_coord_t w);
LV_ATTRIBUTE_FAST_MEM void lv_dither_ordered_hor(struct _lv_gradient_cache_t * grad, const lv_coord_t xs,
const lv_coord_t y, const lv_coord_t w);
LV_ATTRIBUTE_FAST_MEM void lv_dither_ordered_ver(struct _lv_gradient_cache_t * grad, const lv_coord_t xs,
const lv_coord_t y, const lv_coord_t w);
#if LV_DITHER_ERROR_DIFFUSION == 1
LV_ATTRIBUTE_FAST_MEM void lv_dither_err_diff_hor(struct _lv_gradient_cache_t * grad, const lv_coord_t xs,
const lv_coord_t y, const lv_coord_t w);
LV_ATTRIBUTE_FAST_MEM void lv_dither_err_diff_ver(struct _lv_gradient_cache_t * grad, const lv_coord_t xs,
const lv_coord_t y, const lv_coord_t w);
#endif /* LV_DITHER_ERROR_DIFFUSION */
#endif /* _DITHER_GRADIENT */
#endif
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif

View File

@@ -0,0 +1,316 @@
/**
* @file lv_draw_sw_gradient.c
*
*/
/*********************
* INCLUDES
*********************/
#include "lv_draw_sw_gradient.h"
/*********************
* DEFINES
*********************/
#if _DITHER_GRADIENT
#define GRAD_CM(r,g,b) LV_COLOR_MAKE32(r,g,b)
#define GRAD_CONV(t, x) t.full = lv_color_to32(x)
#else
#define GRAD_CM(r,g,b) LV_COLOR_MAKE(r,g,b)
#define GRAD_CONV(t, x) t = x
#endif
#define MAX_WIN_RES 1024 /**TODO: Find a way to get this information: max(horz_res, vert_res)*/
#if _DITHER_GRADIENT
#if LV_DITHER_ERROR_DIFFUSION == 1
#define LV_DEFAULT_GRAD_CACHE_SIZE sizeof(lv_gradient_cache_t) + MAX_WIN_RES * sizeof(lv_grad_color_t) + MAX_WIN_RES * sizeof(lv_color_t) + MAX_WIN_RES * sizeof(lv_scolor24_t)
#else
#define LV_DEFAULT_GRAD_CACHE_SIZE sizeof(lv_gradient_cache_t) + MAX_WIN_RES * sizeof(lv_grad_color_t) + MAX_WIN_RES * sizeof(lv_color_t)
#endif /* LV_DITHER_ERROR_DIFFUSION */
#else
#define LV_DEFAULT_GRAD_CACHE_SIZE sizeof(lv_gradient_cache_t) + MAX_WIN_RES * sizeof(lv_grad_color_t)
#endif /* _DITHER_GRADIENT */
/**********************
* STATIC PROTOTYPES
**********************/
static lv_gradient_cache_t * next_in_cache(lv_gradient_cache_t * first);
typedef lv_res_t (*op_cache_t)(lv_gradient_cache_t * c, void * ctx);
static lv_res_t iterate_cache(op_cache_t func, void * ctx, lv_gradient_cache_t ** out);
static size_t get_cache_item_size(lv_gradient_cache_t * c);
static lv_gradient_cache_t * allocate_item(const lv_gradient_t * g, lv_coord_t w, lv_coord_t h);
static lv_res_t find_oldest_item_life(lv_gradient_cache_t * c, void * ctx);
static lv_res_t kill_oldest_item(lv_gradient_cache_t * c, void * ctx);
static lv_res_t find_item(lv_gradient_cache_t * c, void * ctx);
static void free_item(lv_gradient_cache_t * c);
static uint32_t compute_key(const lv_gradient_t * g, lv_coord_t w, lv_coord_t h);
/**********************
* STATIC VARIABLE
**********************/
static uint8_t * grad_cache_mem = 0;
static size_t grad_cache_size = 0;
static uint8_t * grad_cache_end = 0;
/**********************
* STATIC FUNCTIONS
**********************/
union void_cast {
const void * ptr;
const uint32_t value;
};
static uint32_t compute_key(const lv_gradient_t * g, lv_coord_t size, lv_coord_t w)
{
union void_cast v;
v.ptr = g;
return (v.value ^ size ^ (w >> 1)); /*Yes, this is correct, it's like a hash that changes if the width changes*/
}
static size_t get_cache_item_size(lv_gradient_cache_t * c)
{
size_t s = sizeof(*c) + c->size * sizeof(lv_color_t)
#if _DITHER_GRADIENT
+ c->hmap_size * sizeof(lv_color32_t)
#if LV_DITHER_ERROR_DIFFUSION == 1
+ c->size * sizeof(lv_scolor24_t)
#endif
#endif
;
return s; /* TODO: Should we align this ? */
}
static lv_gradient_cache_t * next_in_cache(lv_gradient_cache_t * first)
{
if(first == NULL)
return (lv_gradient_cache_t *)grad_cache_mem;
if(first == NULL)
return NULL;
size_t s = get_cache_item_size(first);
/*Compute the size for this cache item*/
if((uint8_t *)first + s > grad_cache_end)
return NULL;
return (lv_gradient_cache_t *)((uint8_t *)first + s);
}
static lv_res_t iterate_cache(op_cache_t func, void * ctx, lv_gradient_cache_t ** out)
{
lv_gradient_cache_t * first = next_in_cache(NULL);
while(first != NULL) {
if((*func)(first, ctx) == LV_RES_OK) {
if(out != NULL) *out = first;
return LV_RES_OK;
}
first = next_in_cache(first);
}
return LV_RES_INV;
}
static lv_res_t find_oldest_item_life(lv_gradient_cache_t * c, void * ctx)
{
uint32_t * min_life = (uint32_t *)ctx;
if(c->life < *min_life) *min_life = c->life;
return LV_RES_INV;
}
static void free_item(lv_gradient_cache_t * c)
{
size_t size = get_cache_item_size(c);
size_t next_items_size = (size_t)(grad_cache_end - (uint8_t *)c) - size;
grad_cache_end -= size;
if(next_items_size) {
lv_memcpy(c, ((uint8_t *)c) + size, next_items_size);
/* Then need to fix all internal pointers too */
while((uint8_t *)c != grad_cache_end) {
c->map = (lv_color_t *)(((uint8_t *)c->map) - size);
#if _DITHER_GRADIENT
c->hmap = (lv_color32_t *)(((uint8_t *)c->hmap) - size);
#if LV_DITHER_ERROR_DIFFUSION == 1
c->error_acc = (lv_scolor24_t *)(((uint8_t *)c->error_acc) - size);
#endif
#endif
c = (lv_gradient_cache_t *)(((uint8_t *)c) + get_cache_item_size(c));
}
}
}
static lv_res_t kill_oldest_item(lv_gradient_cache_t * c, void * ctx)
{
uint32_t * min_life = (uint32_t *)ctx;
if(c->life == *min_life) {
/*Found, let's kill it*/
free_item(c);
return LV_RES_OK;
}
return LV_RES_INV;
}
static lv_res_t find_item(lv_gradient_cache_t * c, void * ctx)
{
uint32_t * k = (uint32_t *)ctx;
if(c->key == *k) return LV_RES_OK;
return LV_RES_INV;
}
static lv_gradient_cache_t * allocate_item(const lv_gradient_t * g, lv_coord_t w, lv_coord_t h)
{
lv_coord_t size = g->dir == LV_GRAD_DIR_HOR ? w : h;
size_t req_size = sizeof(lv_gradient_cache_t) + w * sizeof(lv_color_t)
#if _DITHER_GRADIENT
+ size * sizeof(lv_color32_t)
#if LV_DITHER_ERROR_DIFFUSION == 1
+ w * sizeof(lv_scolor24_t)
#endif
#endif
;
size_t act_size = (size_t)(grad_cache_end - grad_cache_mem);
if(req_size + act_size > grad_cache_size) {
/*Need to evict items from cache until we find enough space to allocate this one */
if(req_size > grad_cache_size) {
LV_LOG_WARN("Gradient cache too small, failed to allocate");
return NULL; /*No magic here, if the empty cache is still too small*/
}
while(act_size + req_size < grad_cache_size) {
uint32_t oldest_life = UINT32_MAX;
iterate_cache(&find_oldest_item_life, &oldest_life, NULL);
iterate_cache(&kill_oldest_item, &oldest_life, NULL);
act_size = (size_t)(grad_cache_end - grad_cache_mem);
}
/*Ok, now we have space to allocate*/
}
lv_gradient_cache_t * item = (lv_gradient_cache_t *)grad_cache_end;
item->key = compute_key(g, size, w);
item->life = 1;
item->filled = 0;
item->size = w;
item->map = (lv_color_t *)(grad_cache_end + sizeof(*item));
#if _DITHER_GRADIENT
item->hmap = (lv_color32_t *)(grad_cache_end + sizeof(*item) + w * sizeof(lv_color_t));
item->hmap_size = size;
#if LV_DITHER_ERROR_DIFFUSION == 1
item->error_acc = (lv_scolor24_t *)(grad_cache_end + sizeof(*item) + size * sizeof(lv_grad_color_t) + w * sizeof(
lv_color_t));
#endif
#endif
grad_cache_end += req_size;
return item;
}
/**********************
* FUNCTIONS
**********************/
void lv_grad_free_cache()
{
lv_mem_free(grad_cache_mem);
grad_cache_mem = grad_cache_end = NULL;
grad_cache_size = 0;
}
void lv_grad_set_cache_size(size_t max_bytes)
{
lv_mem_free(grad_cache_mem);
grad_cache_end = grad_cache_mem = lv_mem_alloc(max_bytes);
LV_ASSERT_MALLOC(grad_cache_mem);
grad_cache_size = max_bytes;
}
lv_gradient_cache_t * lv_grad_get_from_cache(const lv_gradient_t * g, lv_coord_t w, lv_coord_t h)
{
/* No gradient, no cache */
if(g->dir == LV_GRAD_DIR_NONE) return NULL;
/* Step 0: Check if the cache exist (else create it) */
if(!grad_cache_size) {
lv_grad_set_cache_size(LV_DEFAULT_GRAD_CACHE_SIZE);
}
/* Step 1: Search cache for the given key */
lv_coord_t size = g->dir == LV_GRAD_DIR_HOR ? w : h;
uint32_t key = compute_key(g, size, w);
lv_gradient_cache_t * item = NULL;
if(iterate_cache(&find_item, &key, &item) == LV_RES_OK) {
item->life++; /* Don't forget to bump the counter */
return item;
}
/* Step 2: Need to allocate an item for it */
item = allocate_item(g, w, h);
if(item == NULL) return item;
/* Step 3: Fill it with the gradient, as expected */
#if _DITHER_GRADIENT
for(lv_coord_t i = 0; i < item->hmap_size; i++) {
item->hmap[i] = lv_grad_get(g, item->hmap_size, i);
}
#if LV_DITHER_ERROR_DIFFUSION == 1
lv_memset_00(item->error_acc, w * sizeof(lv_scolor24_t));
#endif
#else
for(lv_coord_t i = 0; i < item->size; i++) {
item->map[i] = lv_grad_get(g, item->size, i);
}
#endif
return item;
}
void lv_grad_pop_from_cache(const lv_gradient_t * g, lv_coord_t w, lv_coord_t h)
{
lv_coord_t size = g->dir == LV_GRAD_DIR_HOR ? w : h;
uint32_t key = compute_key(g, size, w);
lv_gradient_cache_t * item = NULL;
if(iterate_cache(&find_item, &key, &item) == LV_RES_OK) {
free_item(item);
}
}
LV_ATTRIBUTE_FAST_MEM lv_grad_color_t lv_grad_get(const lv_gradient_t * dsc, lv_coord_t range, lv_coord_t frac)
{
lv_grad_color_t tmp;
lv_color32_t one, two;
/*Clip out-of-bounds first*/
int32_t min = (dsc->stops[0].frac * range) >> 8;
if(frac <= min) {
GRAD_CONV(tmp, dsc->stops[0].color);
return tmp;
}
int32_t max = (dsc->stops[dsc->stops_count - 1].frac * range) >> 8;
if(frac >= max) {
GRAD_CONV(tmp, dsc->stops[dsc->stops_count - 1].color);
return tmp;
}
/*Find the 2 closest stop now*/
int32_t d = 0;
for(uint8_t i = 1; i < dsc->stops_count; i++) {
int32_t cur = (dsc->stops[i].frac * range) >> 8;
if(frac <= cur) {
one.full = lv_color_to32(dsc->stops[i - 1].color);
two.full = lv_color_to32(dsc->stops[i].color);
min = (dsc->stops[i - 1].frac * range) >> 8;
max = (dsc->stops[i].frac * range) >> 8;
d = max - min;
break;
}
}
/*Then interpolate*/
frac -= min;
lv_opa_t mix = (frac * 255) / d;
lv_opa_t imix = 255 - mix;
lv_grad_color_t r = GRAD_CM(LV_UDIV255(two.ch.red * mix + one.ch.red * imix),
LV_UDIV255(two.ch.green * mix + one.ch.green * imix),
LV_UDIV255(two.ch.blue * mix + one.ch.blue * imix));
return r;
}

View File

@@ -0,0 +1,91 @@
/**
* @file lv_draw_sw_gradient.h
*
*/
#ifndef LV_DRAW_SW_GRADIENT_H
#define LV_DRAW_SW_GRADIENT_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#include "../../misc/lv_color.h"
#include "../../misc/lv_style.h"
#include "lv_draw_sw_dither.h"
/*********************
* DEFINES
*********************/
#if LV_GRADIENT_MAX_STOPS < 2
#error LVGL needs at least 2 stops for gradients. Please increase the LV_GRADIENT_MAX_STOPS
#endif
/**********************
* TYPEDEFS
**********************/
#if _DITHER_GRADIENT
typedef lv_color32_t lv_grad_color_t;
#else
typedef lv_color_t lv_grad_color_t;
#endif
/** To avoid recomputing gradient for each draw operation,
* it's possible to cache the computation in this structure instance.
* Whenever possible, this structure is reused instead of recomputing the gradient map */
typedef struct _lv_gradient_cache_t {
uint32_t key; /**< A discriminating key that's built from the drawing operation.
* If the key does not match, the cache item is not used */
uint32_t life : 31; /**< A life counter that's incremented on usage. Higher counter is
* less likely to be evicted from the cache */
uint32_t filled : 1; /**< Used to skip dithering in it if already done */
lv_color_t * map; /**< The computed gradient low bitdepth color map, points into the
* cache's buffer, no free needed */
lv_coord_t size; /**< The computed gradient color map size, in colors */
#if _DITHER_GRADIENT
lv_color32_t * hmap; /**< If dithering, we need to store the current, high bitdepth gradient
* map too, points to the cache's buffer, no free needed */
lv_coord_t hmap_size; /**< The array size in pixels */
#if LV_DITHER_ERROR_DIFFUSION == 1
lv_scolor24_t * error_acc; /**< Error diffusion dithering algorithm requires storing the last error
* drawn, points to the cache's buffer, no free needed */
#endif
#endif
} lv_gradient_cache_t;
/**********************
* PROTOTYPES
**********************/
/** Compute the color in the given gradient and fraction
* Gradient are specified in a virtual [0-255] range, so this function scales the virtual range to the given range
* @param dsc The gradient descriptor to use
* @param range The range to use in computation.
* @param frac The current part used in the range. frac is in [0; range]
*/
LV_ATTRIBUTE_FAST_MEM lv_grad_color_t lv_grad_get(const lv_gradient_t * dsc, lv_coord_t range, lv_coord_t frac);
/** Set the gradient cache size */
void lv_grad_set_cache_size(size_t max_bytes);
/** Get a gradient cache from the given parameters */
lv_gradient_cache_t * lv_grad_get_from_cache(const lv_gradient_t * gradient, lv_coord_t w, lv_coord_t h);
/** Evict item from the gradient cache (not used anymore).
* This bypass the life counter on the item to remove this item.
*/
void lv_grad_pop_from_cache(const lv_gradient_t * gradient, lv_coord_t w, lv_coord_t h);
/** Free the gradient cache */
void lv_grad_free_cache(void);
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /*LV_DRAW_GRADIENT_H*/

View File

@@ -11,6 +11,7 @@
#include "../../misc/lv_txt_ap.h"
#include "../../core/lv_refr.h"
#include "../../misc/lv_assert.h"
#include "lv_draw_sw_dither.h"
/*********************
* DEFINES
@@ -19,6 +20,7 @@
#define SHADOW_ENHANCE 1
#define SPLIT_LIMIT 50
/**********************
* TYPEDEFS
**********************/
@@ -46,9 +48,6 @@ void draw_border_generic(lv_draw_ctx_t * draw_ctx, const lv_area_t * outer_area,
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);
#if LV_DRAW_COMPLEX
LV_ATTRIBUTE_FAST_MEM static inline lv_color_t grad_get(const lv_draw_rect_dsc_t * dsc, lv_coord_t s, lv_coord_t i);
#endif
/**********************
* STATIC VARIABLES
@@ -105,8 +104,9 @@ static void draw_bg(lv_draw_ctx_t * draw_ctx, const lv_draw_rect_dsc_t * dsc, co
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;
if(dsc->bg_color.full == dsc->bg_grad_color.full) grad_dir = LV_GRAD_DIR_NONE;
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);
@@ -115,7 +115,7 @@ static void draw_bg(lv_draw_ctx_t * draw_ctx, const lv_draw_rect_dsc_t * dsc, co
lv_draw_sw_blend_dsc_t blend_dsc;
lv_memset_00(&blend_dsc, sizeof(lv_draw_sw_blend_dsc_t));
blend_dsc.blend_mode = dsc->blend_mode;
blend_dsc.color = dsc->bg_color;
blend_dsc.color = bg_color;
blend_dsc.blend_area = &bg_coords;
blend_dsc.opa = dsc->bg_opa;
@@ -155,25 +155,40 @@ static void draw_bg(lv_draw_ctx_t * draw_ctx, const lv_draw_rect_dsc_t * dsc, co
lv_draw_sw_blend_dsc_t blend_dsc;
lv_memset_00(&blend_dsc, sizeof(lv_draw_sw_blend_dsc_t));
blend_dsc.blend_mode = dsc->blend_mode;
blend_dsc.color = dsc->bg_color;
blend_dsc.color = dsc->bg_grad.stops[0].color;
blend_dsc.mask_buf = mask_buf;
blend_dsc.opa = LV_OPA_COVER;
blend_dsc.blend_area = &blend_area;
blend_dsc.mask_area = &blend_area;
/*In case of horizontal gradient pre-compute a line with a gradient*/
lv_color_t * grad_map = NULL;
lv_color_t * grad_map_ofs = NULL;
if(grad_dir == LV_GRAD_DIR_HOR) {
grad_map = lv_mem_buf_get(coords_bg_w * sizeof(lv_color_t));
int32_t i;
for(i = 0; i < coords_bg_w; i++) grad_map[i] = grad_get(dsc, coords_bg_w, i);
grad_map_ofs = grad_map;
if(dsc->bg_grad_dir == LV_GRAD_DIR_HOR) grad_map_ofs += clipped_coords.x1 - bg_coords.x1;
blend_dsc.src_buf = grad_map_ofs;
/*Get gradient if appropriate*/
lv_gradient_cache_t * grad = lv_grad_get_from_cache(&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;
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 ?*/
}
else
#if LV_DITHER_ERROR_DIFFUSION
if(dither_mode == LV_DITHER_ORDERED)
#endif
dither_func = grad_dir == LV_GRAD_DIR_HOR ? &lv_dither_ordered_hor : &lv_dither_ordered_ver;
#if LV_DITHER_ERROR_DIFFUSION
else if(dither_mode == LV_DITHER_ERR_DIFF)
dither_func = grad_dir == LV_GRAD_DIR_HOR ? &lv_dither_err_diff_hor : &lv_dither_err_diff_ver;
#endif
#endif
/*There is another mask too. Draw line by line. */
if(mask_any) {
for(h = clipped_coords.y1; h <= clipped_coords.y2; h++) {
@@ -186,8 +201,10 @@ static void draw_bg(lv_draw_ctx_t * draw_ctx, const lv_draw_rect_dsc_t * dsc, co
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(grad_dir == LV_GRAD_DIR_VER) blend_dsc.color = grad_get(dsc, coords_bg_h, h - bg_coords.y1);
#if _DITHER_GRADIENT
dither_func(grad, blend_area.x1, h - bg_coords.y1, coords_bg_w);
#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;
@@ -210,8 +227,10 @@ static void draw_bg(lv_draw_ctx_t * draw_ctx, const lv_draw_rect_dsc_t * dsc, co
blend_area.y1 = top_y;
blend_area.y2 = top_y;
if(grad_dir == LV_GRAD_DIR_VER) blend_dsc.color = grad_get(dsc, coords_bg_h, top_y - bg_coords.y1);
#if _DITHER_GRADIENT
dither_func(grad, blend_area.x1, top_y - bg_coords.y1, coords_bg_w);
#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);
}
@@ -219,8 +238,10 @@ static void draw_bg(lv_draw_ctx_t * draw_ctx, const lv_draw_rect_dsc_t * dsc, co
blend_area.y1 = bottom_y;
blend_area.y2 = bottom_y;
if(grad_dir == LV_GRAD_DIR_VER) blend_dsc.color = grad_get(dsc, coords_bg_h, bottom_y - bg_coords.y1);
#if _DITHER_GRADIENT
dither_func(grad, blend_area.x1, bottom_y - bg_coords.y1, coords_bg_w);
#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);
}
}
@@ -256,15 +277,16 @@ static void draw_bg(lv_draw_ctx_t * draw_ctx, const lv_draw_rect_dsc_t * dsc, co
blend_area.y1 = h;
blend_area.y2 = h;
if(grad_dir == LV_GRAD_DIR_VER) blend_dsc.color = grad_get(dsc, coords_bg_h, h - bg_coords.y1);
#if _DITHER_GRADIENT
dither_func(grad, blend_area.x1, h - bg_coords.y1, coords_bg_w);
#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(grad_map) lv_mem_buf_release(grad_map);
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);
@@ -366,23 +388,7 @@ static void draw_border(lv_draw_ctx_t * draw_ctx, const lv_draw_rect_dsc_t * dsc
}
#if LV_DRAW_COMPLEX
LV_ATTRIBUTE_FAST_MEM static inline lv_color_t grad_get(const lv_draw_rect_dsc_t * dsc, lv_coord_t s, lv_coord_t i)
{
int32_t min = (dsc->bg_main_color_stop * s) >> 8;
if(i <= min) return dsc->bg_color;
int32_t max = (dsc->bg_grad_color_stop * s) >> 8;
if(i >= max) return dsc->bg_grad_color;
int32_t d = dsc->bg_grad_color_stop - dsc->bg_main_color_stop;
d = (s * d) >> 8;
i -= min;
lv_opa_t mix = (i * 255) / d;
return lv_color_mix(dsc->bg_grad_color, dsc->bg_color, mix);
}
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)
{
@@ -432,6 +438,7 @@ LV_ATTRIBUTE_FAST_MEM static void draw_shadow(lv_draw_ctx_t * draw_ctx, const lv
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;
@@ -1061,7 +1068,6 @@ LV_ATTRIBUTE_FAST_MEM static void shadow_blur_corner(lv_coord_t size, lv_coord_t
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)

View File

@@ -1202,7 +1202,7 @@ static void draw_series_bar(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx)
lv_draw_rect_dsc_t col_dsc;
lv_draw_rect_dsc_init(&col_dsc);
lv_obj_init_draw_rect_dsc(obj, LV_PART_ITEMS, &col_dsc);
col_dsc.bg_grad_dir = LV_GRAD_DIR_NONE;
col_dsc.bg_grad.dir = LV_GRAD_DIR_NONE;
col_dsc.bg_opa = LV_OPA_COVER;
/*Make the cols longer with `radius` to clip the rounding from the bottom*/

View File

@@ -178,15 +178,17 @@ static void lv_led_event(const lv_obj_class_t * class_p, lv_event_t * e)
lv_obj_init_draw_rect_dsc(obj, LV_PART_MAIN, &rect_dsc);
/*Use the original colors brightness to modify color->led*/
rect_dsc.bg_color = lv_color_mix(led->color, lv_color_black(), lv_color_brightness(rect_dsc.bg_color));
rect_dsc.bg_grad_color = lv_color_mix(led->color, lv_color_black(), lv_color_brightness(rect_dsc.bg_grad_color));
rect_dsc.bg_grad.stops[0].color = lv_color_mix(led->color, lv_color_black(),
lv_color_brightness(rect_dsc.bg_grad.stops[0].color));
rect_dsc.bg_grad.stops[1].color = lv_color_mix(led->color, lv_color_black(),
lv_color_brightness(rect_dsc.bg_grad.stops[1].color));
rect_dsc.shadow_color = lv_color_mix(led->color, lv_color_black(), lv_color_brightness(rect_dsc.shadow_color));
rect_dsc.border_color = lv_color_mix(led->color, lv_color_black(), lv_color_brightness(rect_dsc.border_color));
rect_dsc.outline_color = lv_color_mix(led->color, lv_color_black(), lv_color_brightness(rect_dsc.outline_color));
/*Mix. the color with black proportionally with brightness*/
rect_dsc.bg_color = lv_color_mix(rect_dsc.bg_color, lv_color_black(), led->bright);
rect_dsc.bg_grad_color = lv_color_mix(rect_dsc.bg_grad_color, lv_color_black(), led->bright);
rect_dsc.bg_grad.stops[0].color = lv_color_mix(rect_dsc.bg_grad.stops[0].color, lv_color_black(), led->bright);
rect_dsc.bg_grad.stops[1].color = lv_color_mix(rect_dsc.bg_grad.stops[1].color, lv_color_black(), led->bright);
rect_dsc.border_color = lv_color_mix(rect_dsc.border_color, lv_color_black(), led->bright);
rect_dsc.shadow_color = lv_color_mix(rect_dsc.shadow_color, lv_color_black(), led->bright);
rect_dsc.outline_color = lv_color_mix(rect_dsc.outline_color, lv_color_black(), led->bright);

View File

@@ -294,6 +294,46 @@
#endif
#endif
/*Allow dithering gradient (to achieve visual smooth color gradients on limited color depth display)
*LV_DITHER_GRADIENT implies allocating one or two more lines of the object's rendering surface
*The increase in memory consumption is (32 bits * object width) plus 24 bits * object width if using error diffusion */
#ifndef LV_DITHER_GRADIENT
#ifdef _LV_KCONFIG_PRESENT
#ifdef CONFIG_LV_DITHER_GRADIENT
#define LV_DITHER_GRADIENT CONFIG_LV_DITHER_GRADIENT
#else
#define LV_DITHER_GRADIENT 0
#endif
#else
#define LV_DITHER_GRADIENT 1
#endif
#endif
/*Add support for error diffusion dithering.
*Error diffusion dithering gets a much better visual result, but implies more CPU consumption and memory when drawing.
*The increase in memory consumption is (24 bits * object's width)*/
#ifndef LV_DITHER_ERROR_DIFFUSION
#ifdef _LV_KCONFIG_PRESENT
#ifdef CONFIG_LV_DITHER_ERROR_DIFFUSION
#define LV_DITHER_ERROR_DIFFUSION CONFIG_LV_DITHER_ERROR_DIFFUSION
#else
#define LV_DITHER_ERROR_DIFFUSION 0
#endif
#else
#define LV_DITHER_ERROR_DIFFUSION 1
#endif
#endif
/**Number of stops allowed per gradient. Increase this to allow more stops.
*This adds (sizeof(lv_color_t) + 1) bytes per additional stop*/
#ifndef LV_GRADIENT_MAX_STOPS
#ifdef CONFIG_LV_GRADIENT_MAX_STOPS
#define LV_GRADIENT_MAX_STOPS CONFIG_LV_GRADIENT_MAX_STOPS
#else
#define LV_GRADIENT_MAX_STOPS 2
#endif
#endif
#endif /*LV_DRAW_COMPLEX*/
/*Default image cache size. Image caching keeps the images opened.

View File

@@ -51,6 +51,12 @@ LV_EXPORT_CONST_INT(LV_IMG_ZOOM_NONE);
#define LV_STYLE_CONST_INIT(var_name, prop_array) const lv_style_t var_name = { .v_p = { .const_props = prop_array }, .has_group = 0xFF, .is_const = 1 }
#endif
/** On simple system, don't waste resources on gradients */
#if !defined(LV_DRAW_COMPLEX) || !defined(LV_GRADIENT_MAX_STOPS)
#define LV_GRADIENT_MAX_STOPS 2
#endif
/**********************
* TYPEDEFS
**********************/
@@ -106,6 +112,36 @@ enum {
typedef uint8_t lv_grad_dir_t;
/**
* The dithering algorithm for the gradient
* Depends on LV_DITHER_GRADIENT
*/
enum {
LV_DITHER_NONE, /**< No dithering, colors are just quantized to the output resolution*/
LV_DITHER_ORDERED, /**< Ordered dithering. Faster to compute and use less memory but lower quality*/
LV_DITHER_ERR_DIFF, /**< Error diffusion mode. Slower to compute and use more memory but give highest dither quality*/
};
typedef uint8_t lv_dither_mode_t;
/** A gradient stop definition.
* This matches a color and a position in a virtual 0-255 scale.
*/
typedef struct {
lv_color_t color; /**< The stop color */
uint8_t frac; /**< The stop position in 1/255 unit */
} lv_gradient_stop_t;
/** A descriptor of a gradient. */
typedef struct {
lv_gradient_stop_t stops[LV_GRADIENT_MAX_STOPS]; /**< A gradient stop array */
uint8_t stops_count; /**< The number of used stops in the array */
lv_grad_dir_t dir : 3; /**< The gradient direction.
* Any of LV_GRAD_DIR_HOR, LV_GRAD_DIR_VER, LV_GRAD_DIR_NONE */
lv_dither_mode_t dither : 3; /**< Whether to dither the gradient or not.
* Any of LV_DITHER_NONE, LV_DITHER_ORDERED, LV_DITHER_ERR_DIFF */
} lv_gradient_t;
/**
* A common type to handle all the property types in the same way.
*/
@@ -155,13 +191,16 @@ typedef enum {
LV_STYLE_BG_GRAD_DIR = 35,
LV_STYLE_BG_MAIN_STOP = 36,
LV_STYLE_BG_GRAD_STOP = 37,
LV_STYLE_BG_GRADIENT = 38,
LV_STYLE_BG_DITHER_MODE = 39,
LV_STYLE_BG_IMG_SRC = 38 | LV_STYLE_PROP_EXT_DRAW,
LV_STYLE_BG_IMG_OPA = 39,
LV_STYLE_BG_IMG_RECOLOR = 40,
LV_STYLE_BG_IMG_RECOLOR_FILTERED = 40 | LV_STYLE_PROP_FILTER,
LV_STYLE_BG_IMG_RECOLOR_OPA = 41,
LV_STYLE_BG_IMG_TILED = 42,
LV_STYLE_BG_IMG_SRC = 40 | LV_STYLE_PROP_EXT_DRAW,
LV_STYLE_BG_IMG_OPA = 41,
LV_STYLE_BG_IMG_RECOLOR = 42,
LV_STYLE_BG_IMG_RECOLOR_FILTERED = 42 | LV_STYLE_PROP_FILTER,
LV_STYLE_BG_IMG_RECOLOR_OPA = 43,
LV_STYLE_BG_IMG_TILED = 44,
/*Group 3*/
LV_STYLE_BORDER_COLOR = 48,

View File

@@ -232,6 +232,22 @@ void lv_style_set_bg_grad_stop(lv_style_t * style, lv_coord_t value)
lv_style_set_prop(style, LV_STYLE_BG_GRAD_STOP, v);
}
void lv_style_set_bg_gradient(lv_style_t * style, const lv_gradient_t * value)
{
lv_style_value_t v = {
.ptr = value
};
lv_style_set_prop(style, LV_STYLE_BG_GRADIENT, v);
}
void lv_style_set_bg_dither_mode(lv_style_t * style, lv_dither_mode_t value)
{
lv_style_value_t v = {
.num = (int32_t)value
};
lv_style_set_prop(style, LV_STYLE_BG_DITHER_MODE, v);
}
void lv_style_set_bg_img_src(lv_style_t * style, const void * value)
{
lv_style_value_t v = {

View File

@@ -27,6 +27,8 @@ void lv_style_set_bg_grad_color_filtered(lv_style_t * style, lv_color_t value);
void lv_style_set_bg_grad_dir(lv_style_t * style, lv_grad_dir_t value);
void lv_style_set_bg_main_stop(lv_style_t * style, lv_coord_t value);
void lv_style_set_bg_grad_stop(lv_style_t * style, lv_coord_t value);
void lv_style_set_bg_gradient(lv_style_t * style, const lv_gradient_t * value);
void lv_style_set_bg_dither_mode(lv_style_t * style, lv_dither_mode_t value);
void lv_style_set_bg_img_src(lv_style_t * style, const void * value);
void lv_style_set_bg_img_opa(lv_style_t * style, lv_opa_t value);
void lv_style_set_bg_img_recolor(lv_style_t * style, lv_color_t value);
@@ -233,6 +235,16 @@ void lv_style_set_base_dir(lv_style_t * style, lv_base_dir_t value);
.prop = LV_STYLE_BG_GRAD_STOP, .value = { .num = (int32_t)val } \
}
#define LV_STYLE_CONST_BG_GRADIENT(val) \
{ \
.prop = LV_STYLE_BG_GRADIENT, .value = { .ptr = val } \
}
#define LV_STYLE_CONST_BG_DITHER_MODE(val) \
{ \
.prop = LV_STYLE_BG_DITHER_MODE, .value = { .num = (int32_t)val } \
}
#define LV_STYLE_CONST_BG_IMG_SRC(val) \
{ \
.prop = LV_STYLE_BG_IMG_SRC, .value = { .ptr = val } \