diff --git a/docs/overview/style-props.md b/docs/overview/style-props.md
index 45b39c419..c999a741c 100644
--- a/docs/overview/style-props.md
+++ b/docs/overview/style-props.md
@@ -252,6 +252,24 @@ Set the point from which the background's gradient color should start. 0 means t
Ext. draw No
+### bg_gradient
+Set the gradient definition for the body. The pointed instance must exist while the object is alive. NULL to disable
+
+- Default `NULL`
+- Inherited No
+- Layout No
+- Ext. draw No
+
+
+### bg_dither_mode
+Set the dithering mode of the gradient of the background. The possible values are `LV_DITHER_NONE/ORDERED/ERR_DIFF`.
+
+- Default `LV_DITHER_NONE`
+- Inherited No
+- Layout No
+- Ext. draw No
+
+
### 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_...`
diff --git a/examples/styles/lv_example_style_2.c b/examples/styles/lv_example_style_2.c
index 356711506..1ceb369ed 100644
--- a/examples/styles/lv_example_style_2.c
+++ b/examples/styles/lv_example_style_2.c
@@ -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());
diff --git a/examples/widgets/canvas/lv_example_canvas_1.c b/examples/widgets/canvas/lv_example_canvas_1.c
index 1b75688a3..fb408d6c0 100644
--- a/examples/widgets/canvas/lv_example_canvas_1.c
+++ b/examples/widgets/canvas/lv_example_canvas_1.c
@@ -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();
diff --git a/examples/widgets/canvas/lv_example_canvas_1.py b/examples/widgets/canvas/lv_example_canvas_1.py
index 8b84ab0fc..5a3da3b23 100644
--- a/examples/widgets/canvas/lv_example_canvas_1.py
+++ b/examples/widgets/canvas/lv_example_canvas_1.py
@@ -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()
diff --git a/lv_conf_template.h b/lv_conf_template.h
index 923aeefd8..8f68d9db0 100644
--- a/lv_conf_template.h
+++ b/lv_conf_template.h
@@ -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.
diff --git a/scripts/style_api_gen.py b/scripts/style_api_gen.py
index b29083a63..ece478c33 100755
--- a/scripts/style_api_gen.py
+++ b/scripts/style_api_gen.py
@@ -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_...`"},
diff --git a/src/core/lv_obj_draw.c b/src/core/lv_obj_draw.c
index e619ae978..99c544e17 100644
--- a/src/core/lv_obj_draw.c
+++ b/src/core/lv_obj_draw.c
@@ -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);
}
}
}
diff --git a/src/core/lv_obj_style_gen.c b/src/core/lv_obj_style_gen.c
index e8a8f1399..acf7effb3 100644
--- a/src/core/lv_obj_style_gen.c
+++ b/src/core/lv_obj_style_gen.c
@@ -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 = {
diff --git a/src/core/lv_obj_style_gen.h b/src/core/lv_obj_style_gen.h
index fe7863dbd..479004079 100644
--- a/src/core/lv_obj_style_gen.h
+++ b/src/core/lv_obj_style_gen.h
@@ -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);
diff --git a/src/draw/lv_draw_rect.c b/src/draw/lv_draw_rect.c
index 8d5d06a9d..8cb428824 100644
--- a/src/draw/lv_draw_rect.c
+++ b/src/draw/lv_draw_rect.c
@@ -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;
diff --git a/src/draw/lv_draw_rect.h b/src/draw/lv_draw_rect.h
index 87522876d..55d9708df 100644
--- a/src/draw/lv_draw_rect.h
+++ b/src/draw/lv_draw_rect.h
@@ -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;
diff --git a/src/draw/sw/lv_draw_sw.mk b/src/draw/sw/lv_draw_sw.mk
index 07075b3a6..386d9ed81 100644
--- a/src/draw/sw/lv_draw_sw.mk
+++ b/src/draw/sw/lv_draw_sw.mk
@@ -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
diff --git a/src/draw/sw/lv_draw_sw_blend.c b/src/draw/sw/lv_draw_sw_blend.c
index d8cbc0735..3ef29cf40 100644
--- a/src/draw/sw/lv_draw_sw_blend.c
+++ b/src/draw/sw/lv_draw_sw_blend.c
@@ -128,7 +128,7 @@ LV_ATTRIBUTE_FAST_MEM void lv_draw_sw_blend_basic(lv_draw_ctx_t * draw_ctx, cons
lv_coord_t src_stride;
if(src_buf) {
src_stride = lv_area_get_width(dsc->blend_area);
- src_buf += src_stride * (blend_area.y1 - dsc->blend_area->y1) + (blend_area.x1 - dsc->blend_area->x1);
+ src_buf += src_stride * (blend_area.y1 - dsc->blend_area->y1) + (blend_area.x1 - dsc->blend_area->x1);
}
else {
src_stride = 0;
diff --git a/src/draw/sw/lv_draw_sw_blend.h b/src/draw/sw/lv_draw_sw_blend.h
index a1fde7de3..9a00e533a 100644
--- a/src/draw/sw/lv_draw_sw_blend.h
+++ b/src/draw/sw/lv_draw_sw_blend.h
@@ -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 */
diff --git a/src/draw/sw/lv_draw_sw_dither.c b/src/draw/sw/lv_draw_sw_dither.c
new file mode 100644
index 000000000..e9f73c549
--- /dev/null
+++ b/src/draw/sw/lv_draw_sw_dither.c
@@ -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
diff --git a/src/draw/sw/lv_draw_sw_dither.h b/src/draw/sw/lv_draw_sw_dither.h
new file mode 100644
index 000000000..2c2243d1d
--- /dev/null
+++ b/src/draw/sw/lv_draw_sw_dither.h
@@ -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
diff --git a/src/draw/sw/lv_draw_sw_gradient.c b/src/draw/sw/lv_draw_sw_gradient.c
new file mode 100644
index 000000000..05e251445
--- /dev/null
+++ b/src/draw/sw/lv_draw_sw_gradient.c
@@ -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;
+}
diff --git a/src/draw/sw/lv_draw_sw_gradient.h b/src/draw/sw/lv_draw_sw_gradient.h
new file mode 100644
index 000000000..617cf3ad3
--- /dev/null
+++ b/src/draw/sw/lv_draw_sw_gradient.h
@@ -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*/
diff --git a/src/draw/sw/lv_draw_sw_rect.c b/src/draw/sw/lv_draw_sw_rect.c
index 66b7ed773..45d46d5ad 100644
--- a/src/draw/sw/lv_draw_sw_rect.c
+++ b/src/draw/sw/lv_draw_sw_rect.c
@@ -11,14 +11,16 @@
#include "../../misc/lv_txt_ap.h"
#include "../../core/lv_refr.h"
#include "../../misc/lv_assert.h"
+#include "lv_draw_sw_dither.h"
/*********************
* DEFINES
*********************/
-#define SHADOW_UPSCALE_SHIFT 6
+#define SHADOW_UPSCALE_SHIFT 6
#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)
diff --git a/src/extra/widgets/chart/lv_chart.c b/src/extra/widgets/chart/lv_chart.c
index 2eb37f425..3bee20497 100644
--- a/src/extra/widgets/chart/lv_chart.c
+++ b/src/extra/widgets/chart/lv_chart.c
@@ -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*/
diff --git a/src/extra/widgets/led/lv_led.c b/src/extra/widgets/led/lv_led.c
index 5899ddac9..03e136272 100644
--- a/src/extra/widgets/led/lv_led.c
+++ b/src/extra/widgets/led/lv_led.c
@@ -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);
diff --git a/src/lv_conf_internal.h b/src/lv_conf_internal.h
index b4962d689..370658e75 100644
--- a/src/lv_conf_internal.h
+++ b/src/lv_conf_internal.h
@@ -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.
diff --git a/src/misc/lv_style.h b/src/misc/lv_style.h
index 6561a9471..2a81559d4 100644
--- a/src/misc/lv_style.h
+++ b/src/misc/lv_style.h
@@ -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,
diff --git a/src/misc/lv_style_gen.c b/src/misc/lv_style_gen.c
index f9adadb87..3602ba7e1 100644
--- a/src/misc/lv_style_gen.c
+++ b/src/misc/lv_style_gen.c
@@ -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 = {
diff --git a/src/misc/lv_style_gen.h b/src/misc/lv_style_gen.h
index e264fb9a4..5c4e98f4f 100644
--- a/src/misc/lv_style_gen.h
+++ b/src/misc/lv_style_gen.h
@@ -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 } \