1 /**
2 * @file lv_draw_sw_dither.c
3 *
4 */
5
6 /*********************
7 * INCLUDES
8 *********************/
9 #include "lv_draw_sw_dither.h"
10 #include "lv_draw_sw_gradient.h"
11 #include "../../misc/lv_color.h"
12
13 /**********************
14 * STATIC FUNCTIONS
15 **********************/
16
17
18 #if _DITHER_GRADIENT
19
lv_dither_none(lv_grad_t * grad,lv_coord_t x,lv_coord_t y,lv_coord_t w)20 void LV_ATTRIBUTE_FAST_MEM lv_dither_none(lv_grad_t * grad, lv_coord_t x, lv_coord_t y, lv_coord_t w)
21 {
22 LV_UNUSED(x);
23 LV_UNUSED(y);
24 if(grad == NULL || grad->filled) return;
25 for(lv_coord_t i = 0; i < w; i++) {
26 grad->map[i] = lv_color_hex(grad->hmap[i].full);
27 }
28 grad->filled = 1;
29 }
30
31 static const uint8_t dither_ordered_threshold_matrix[8 * 8] = {
32 0, 48, 12, 60, 3, 51, 15, 63,
33 32, 16, 44, 28, 35, 19, 47, 31,
34 8, 56, 4, 52, 11, 59, 7, 55,
35 40, 24, 36, 20, 43, 27, 39, 23,
36 2, 50, 14, 62, 1, 49, 13, 61,
37 34, 18, 46, 30, 33, 17, 45, 29,
38 10, 58, 6, 54, 9, 57, 5, 53,
39 42, 26, 38, 22, 41, 25, 37, 21
40 }; /* Shift by 6 to normalize */
41
42
lv_dither_ordered_hor(lv_grad_t * grad,lv_coord_t x,lv_coord_t y,lv_coord_t w)43 void LV_ATTRIBUTE_FAST_MEM lv_dither_ordered_hor(lv_grad_t * grad, lv_coord_t x, lv_coord_t y, lv_coord_t w)
44 {
45 LV_UNUSED(x);
46 /* For vertical dithering, the error is spread on the next column (and not next line).
47 Since the renderer is scanline based, it's not obvious what could be used to perform the rendering efficiently.
48 The algorithm below is based on few assumptions:
49 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
50 2. Instead an ordered dithering algorithm shift the value a bit, but the influence only spread from the matrix size (used 8x8 here)
51 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.
52 Then we compute a complete row of ordered dither and store it in out. */
53
54 /*The apply the algorithm for this patch*/
55 for(lv_coord_t j = 0; j < w; j++) {
56 int8_t factor = dither_ordered_threshold_matrix[(y & 7) * 8 + ((j) & 7)] - 32;
57 lv_color32_t tmp = grad->hmap[LV_CLAMP(0, j - 4, grad->size)];
58 lv_color32_t t;
59 t.ch.red = LV_CLAMP(0, tmp.ch.red + factor, 255);
60 t.ch.green = LV_CLAMP(0, tmp.ch.green + factor, 255);
61 t.ch.blue = LV_CLAMP(0, tmp.ch.blue + factor, 255);
62
63 grad->map[j] = lv_color_hex(t.full);
64 }
65 }
66
lv_dither_ordered_ver(lv_grad_t * grad,lv_coord_t x,lv_coord_t y,lv_coord_t w)67 void LV_ATTRIBUTE_FAST_MEM lv_dither_ordered_ver(lv_grad_t * grad, lv_coord_t x, lv_coord_t y, lv_coord_t w)
68 {
69 /* For vertical dithering, the error is spread on the next column (and not next line).
70 Since the renderer is scanline based, it's not obvious what could be used to perform the rendering efficiently.
71 The algorithm below is based on few assumptions:
72 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
73 2. Instead an ordered dithering algorithm shift the value a bit, but the influence only spread from the matrix size (used 8x8 here)
74 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.
75 Then we compute a complete row of ordered dither and store it in out. */
76
77 /*Extract patch for working with, selected pseudo randomly*/
78 lv_color32_t tmp = grad->hmap[LV_CLAMP(0, y - 4, grad->size)];
79
80 /*The apply the algorithm for this patch*/
81 for(lv_coord_t j = 0; j < 8; j++) {
82 int8_t factor = dither_ordered_threshold_matrix[(y & 7) * 8 + ((j + x) & 7)] - 32;
83 lv_color32_t t;
84 t.ch.red = LV_CLAMP(0, tmp.ch.red + factor, 255);
85 t.ch.green = LV_CLAMP(0, tmp.ch.green + factor, 255);
86 t.ch.blue = LV_CLAMP(0, tmp.ch.blue + factor, 255);
87
88 grad->map[j] = lv_color_hex(t.full);
89 }
90 /*Finally fill the line*/
91 lv_coord_t j = 8;
92 for(; j < w - 8; j += 8) {
93 lv_memcpy(grad->map + j, grad->map, 8 * sizeof(*grad->map));
94 }
95 /* Prevent overwriting */
96 for(; j < w; j++) {
97 grad->map[j] = grad->map[j & 7];
98 }
99 }
100
101 #if LV_DITHER_ERROR_DIFFUSION == 1
lv_dither_err_diff_hor(lv_grad_t * grad,lv_coord_t xs,lv_coord_t y,lv_coord_t w)102 void LV_ATTRIBUTE_FAST_MEM lv_dither_err_diff_hor(lv_grad_t * grad, lv_coord_t xs, lv_coord_t y, lv_coord_t w)
103 {
104 LV_UNUSED(xs);
105 LV_UNUSED(y);
106 LV_UNUSED(w);
107
108 /* Implement Floyd Steinberg algorithm, see https://surma.dev/things/ditherpunk/
109 Coefs are: x 7
110 3 5 1
111 / 16
112 Can be implemented as: x (x<<3 - x)
113 (x<<2 - x) (x<<2+x) x
114 */
115 int coef[4] = {0, 0, 0, 0};
116 #define FS_COMPUTE_ERROR(e) { coef[0] = (e<<3) - e; coef[1] = (e<<2) - e; coef[2] = (e<<2) + e; coef[3] = e; }
117 #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);
118 #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); }
119 lv_scolor24_t next_px_err = {0}, next_l = {0}, error;
120 /*First last pixel are not dithered */
121 grad->map[0] = lv_color_hex(grad->hmap[0].full);
122 for(lv_coord_t x = 1; x < grad->size - 1; x++) {
123 lv_color32_t t = grad->hmap[x];
124 lv_color_t q;
125 /*Add error term*/
126 FS_COMPONENTS(t, +, next_px_err, next_l);
127 next_l = grad->error_acc[x + 1];
128 /*Quantify*/
129 q = lv_color_hex(t.full);
130 /*Then compute error*/
131 FS_QUANT_ERROR(error, t, q);
132 /*Dither the error*/
133 FS_COMPUTE_ERROR(error.r);
134 next_px_err.r = coef[0] >> 4;
135 grad->error_acc[x - 1].r += coef[1] >> 4;
136 grad->error_acc[x].r += coef[2] >> 4;
137 grad->error_acc[x + 1].r = coef[3] >> 4;
138
139 FS_COMPUTE_ERROR(error.g);
140 next_px_err.g = coef[0] >> 4;
141 grad->error_acc[x - 1].g += coef[1] >> 4;
142 grad->error_acc[x].g += coef[2] >> 4;
143 grad->error_acc[x + 1].g = coef[3] >> 4;
144
145 FS_COMPUTE_ERROR(error.b);
146 next_px_err.b = coef[0] >> 4;
147 grad->error_acc[x - 1].b += coef[1] >> 4;
148 grad->error_acc[x].b += coef[2] >> 4;
149 grad->error_acc[x + 1].b = coef[3] >> 4;
150
151 grad->map[x] = q;
152 }
153 grad->map[grad->size - 1] = lv_color_hex(grad->hmap[grad->size - 1].full);
154 }
155
lv_dither_err_diff_ver(lv_grad_t * grad,lv_coord_t xs,lv_coord_t y,lv_coord_t w)156 void LV_ATTRIBUTE_FAST_MEM lv_dither_err_diff_ver(lv_grad_t * grad, lv_coord_t xs, lv_coord_t y, lv_coord_t w)
157 {
158 /* Try to implement error diffusion on a vertical gradient and an horizontal map using those tricks:
159 Since the given hi-resolution gradient (in src) is vertical, the Floyd Steinberg algorithm pass need to be rotated,
160 so we'll get this instead (from top to bottom):
161
162 A B C
163 1 [ ][ ][ ]
164 2 [ ][ ][ ] Pixel A2 will spread its error on pixel A3 with coefficient 7,
165 3 [ ][ ][ ] Pixel A2 will spread its error on pixel B1 with coefficient 3, B2 with coef 5 and B3 with coef 1
166
167 When taking into account an arbitrary pixel P(i,j), its added error diffusion term is:
168 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]
169
170 This means that the error term depends on pixel W, NW, N and SW.
171 If we consider that we are generating the error diffused gradient map from top to bottom, we can remember the previous
172 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.
173 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).
174 Let's remove it and re-dispatch the error factor accordingly so they stays normalized:
175 e(i,j) ~= 1/16 * [ e(i-1,j) * 6 + e(i-1,j-1) * 1 + e(i,j-1) * 9]
176
177 That's the idea of this pseudo Floyd Steinberg dithering */
178 #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;
179 #define FS_COMPONENTS3(A, OP, B, b, C, c, D, d) \
180 A.ch.red = LV_CLAMP(0, A.ch.red OP ((B.r * b OP C.r * c OP D.r * d) >> 4), 255); \
181 A.ch.green = LV_CLAMP(0, A.ch.green OP ((B.r * b OP C.r * c OP D.r * d) >> 4), 255); \
182 A.ch.blue = LV_CLAMP(0, A.ch.blue OP ((B.r * b OP C.r * c OP D.r * d) >> 4), 255);
183
184 lv_scolor24_t next_px_err, prev_l = grad->error_acc[0];
185 /*Compute the error term for the current pixel (first pixel is never dithered)*/
186 if(xs == 0) {
187 grad->map[0] = lv_color_hex(grad->hmap[y].full);
188 FS_QUANT_ERROR(next_px_err, grad->hmap[y], grad->map[0]);
189 }
190 else {
191 lv_color_t tmp = lv_color_hex(grad->hmap[y].full);
192 lv_color32_t t = grad->hmap[y];
193 FS_QUANT_ERROR(next_px_err, grad->hmap[y], tmp);
194 FS_COMPONENTS3(t, +, next_px_err, 6, prev_l, 1, grad->error_acc[0], 9);
195 grad->map[0] = lv_color_hex(t.full);
196 }
197
198 for(lv_coord_t x = 1; x < w; x++) {
199 lv_color32_t t = grad->hmap[y];
200 lv_color_t q;
201 /*Add the current error term*/
202 FS_COMPONENTS3(t, +, next_px_err, 6, prev_l, 1, grad->error_acc[x], 9);
203 prev_l = grad->error_acc[x];
204 /*Quantize and compute error term*/
205 q = lv_color_hex(t.full);
206 FS_QUANT_ERROR(next_px_err, t, q);
207 /*Store error for next line computation*/
208 grad->error_acc[x] = next_px_err;
209 grad->map[x] = q;
210 }
211 }
212 #endif
213 #endif
214