1 /**
2 * @file lv_draw_blend.c
3 *
4 */
5
6 /*********************
7 * INCLUDES
8 *********************/
9 #include "lv_draw_blend.h"
10 #include "lv_img_decoder.h"
11 #include "../lv_misc/lv_math.h"
12 #include "../lv_hal/lv_hal_disp.h"
13 #include "../lv_core/lv_refr.h"
14
15 #include "../lv_gpu/lv_gpu_stm32_dma2d.h"
16
17 /*********************
18 * DEFINES
19 *********************/
20 #define GPU_SIZE_LIMIT 240
21
22 /**********************
23 * TYPEDEFS
24 **********************/
25
26 /**********************
27 * STATIC PROTOTYPES
28 **********************/
29
30 static void fill_set_px(const lv_area_t * disp_area, lv_color_t * disp_buf, const lv_area_t * draw_area,
31 lv_color_t color, lv_opa_t opa,
32 const lv_opa_t * mask, lv_draw_mask_res_t mask_res);
33
34 LV_ATTRIBUTE_FAST_MEM static void fill_normal(const lv_area_t * disp_area, lv_color_t * disp_buf,
35 const lv_area_t * draw_area,
36 lv_color_t color, lv_opa_t opa,
37 const lv_opa_t * mask, lv_draw_mask_res_t mask_res);
38
39 #if LV_USE_BLEND_MODES
40 static void fill_blended(const lv_area_t * disp_area, lv_color_t * disp_buf, const lv_area_t * draw_area,
41 lv_color_t color, lv_opa_t opa,
42 const lv_opa_t * mask, lv_draw_mask_res_t mask_res, lv_blend_mode_t mode);
43 #endif
44
45 static void map_set_px(const lv_area_t * disp_area, lv_color_t * disp_buf, const lv_area_t * draw_area,
46 const lv_area_t * map_area, const lv_color_t * map_buf, lv_opa_t opa,
47 const lv_opa_t * mask, lv_draw_mask_res_t mask_res);
48
49 LV_ATTRIBUTE_FAST_MEM static void map_normal(const lv_area_t * disp_area, lv_color_t * disp_buf,
50 const lv_area_t * draw_area,
51 const lv_area_t * map_area, const lv_color_t * map_buf, lv_opa_t opa,
52 const lv_opa_t * mask, lv_draw_mask_res_t mask_res);
53
54 #if LV_USE_BLEND_MODES
55 static void map_blended(const lv_area_t * disp_area, lv_color_t * disp_buf, const lv_area_t * draw_area,
56 const lv_area_t * map_area, const lv_color_t * map_buf, lv_opa_t opa,
57 const lv_opa_t * mask, lv_draw_mask_res_t mask_res, lv_blend_mode_t mode);
58
59 static inline lv_color_t color_blend_true_color_additive(lv_color_t fg, lv_color_t bg, lv_opa_t opa);
60 static inline lv_color_t color_blend_true_color_subtractive(lv_color_t fg, lv_color_t bg, lv_opa_t opa);
61 #endif
62
63 /**********************
64 * STATIC VARIABLES
65 **********************/
66
67 #if LV_USE_GPU || LV_USE_GPU_STM32_DMA2D
68 LV_ATTRIBUTE_DMA static lv_color_t blend_buf[LV_HOR_RES_MAX];
69 #endif
70
71 /**********************
72 * MACROS
73 **********************/
74
75
76 #define FILL_NORMAL_MASK_PX(out_x, color) \
77 if(*mask_tmp_x) { \
78 if(*mask_tmp_x == LV_OPA_COVER) disp_buf_first[out_x] = color; \
79 else disp_buf_first[out_x] = lv_color_mix(color, disp_buf_first[out_x], *mask_tmp_x); \
80 } \
81 mask_tmp_x++;
82
83
84 #define FILL_NORMAL_MASK_PX_SCR_TRANSP(out_x, color) \
85 if(*mask_tmp_x) { \
86 if(*mask_tmp_x == LV_OPA_COVER) disp_buf_first[out_x] = color; \
87 else if(disp->driver.screen_transp) lv_color_mix_with_alpha(disp_buf_first[out_x], disp_buf_first[out_x].ch.alpha, \
88 color, *mask_tmp_x, &disp_buf_first[out_x], &disp_buf_first[out_x].ch.alpha); \
89 else disp_buf_first[out_x] = lv_color_mix(color, disp_buf_first[out_x], *mask_tmp_x); \
90 } \
91 mask_tmp_x++;
92
93
94 #define MAP_NORMAL_MASK_PX(x) \
95 if(*mask_tmp_x) { \
96 if(*mask_tmp_x == LV_OPA_COVER) disp_buf_first[x] = map_buf_first[x]; \
97 else disp_buf_first[x] = lv_color_mix(map_buf_first[x], disp_buf_first[x], *mask_tmp_x); \
98 } \
99 mask_tmp_x++;
100
101 #define MAP_NORMAL_MASK_PX_SCR_TRANSP(x) \
102 if(*mask_tmp_x) { \
103 if(*mask_tmp_x == LV_OPA_COVER) disp_buf_first[x] = map_buf_first[x]; \
104 else if(disp->driver.screen_transp) lv_color_mix_with_alpha(disp_buf_first[x], disp_buf_first[x].ch.alpha, \
105 map_buf_first[x], *mask_tmp_x, &disp_buf_first[x], &disp_buf_first[x].ch.alpha); \
106 else disp_buf_first[x] = lv_color_mix(map_buf_first[x], disp_buf_first[x], *mask_tmp_x); \
107 } \
108 mask_tmp_x++;
109
110 /**********************
111 * GLOBAL FUNCTIONS
112 **********************/
113
114
115 /**
116 * Fill and area in the display buffer.
117 * @param clip_area clip the fill to this area (absolute coordinates)
118 * @param fill_area fill this area (absolute coordinates) (should be clipped)
119 * @param color fill color
120 * @param mask a mask to apply on the fill (uint8_t array with 0x00..0xff values).
121 * Relative to fill area but its width is truncated to clip area.
122 * @param mask_res LV_MASK_RES_COVER: the mask has only 0xff values (no mask),
123 * LV_MASK_RES_TRANSP: the mask has only 0x00 values (full transparent),
124 * LV_MASK_RES_CHANGED: the mask has mixed values
125 * @param opa overall opacity in 0x00..0xff range
126 * @param mode blend mode from `lv_blend_mode_t`
127 */
_lv_blend_fill(const lv_area_t * clip_area,const lv_area_t * fill_area,lv_color_t color,lv_opa_t * mask,lv_draw_mask_res_t mask_res,lv_opa_t opa,lv_blend_mode_t mode)128 LV_ATTRIBUTE_FAST_MEM void _lv_blend_fill(const lv_area_t * clip_area, const lv_area_t * fill_area,
129 lv_color_t color, lv_opa_t * mask, lv_draw_mask_res_t mask_res, lv_opa_t opa,
130 lv_blend_mode_t mode)
131 {
132 /*Do not draw transparent things*/
133 if(opa < LV_OPA_MIN) return;
134 if(mask_res == LV_DRAW_MASK_RES_TRANSP) return;
135
136 lv_disp_t * disp = _lv_refr_get_disp_refreshing();
137 lv_disp_buf_t * vdb = lv_disp_get_buf(disp);
138 const lv_area_t * disp_area = &vdb->area;
139 lv_color_t * disp_buf = vdb->buf_act;
140
141 if(disp->driver.gpu_wait_cb) disp->driver.gpu_wait_cb(&disp->driver);
142
143 /* Get clipped fill area which is the real draw area.
144 * It is always the same or inside `fill_area` */
145 lv_area_t draw_area;
146 bool is_common;
147 is_common = _lv_area_intersect(&draw_area, clip_area, fill_area);
148 if(!is_common) return;
149
150 /* Now `draw_area` has absolute coordinates.
151 * Make it relative to `disp_area` to simplify draw to `disp_buf`*/
152 draw_area.x1 -= disp_area->x1;
153 draw_area.y1 -= disp_area->y1;
154 draw_area.x2 -= disp_area->x1;
155 draw_area.y2 -= disp_area->y1;
156
157 /*Round the values in the mask if anti-aliasing is disabled*/
158 #if LV_ANTIALIAS
159 if(mask && disp->driver.antialiasing == 0)
160 #else
161 if(mask)
162 #endif
163 {
164 int32_t mask_w = lv_area_get_width(&draw_area);
165 int32_t i;
166 for(i = 0; i < mask_w; i++) mask[i] = mask[i] > 128 ? LV_OPA_COVER : LV_OPA_TRANSP;
167 }
168
169 if(disp->driver.set_px_cb) {
170 fill_set_px(disp_area, disp_buf, &draw_area, color, opa, mask, mask_res);
171 }
172 else if(mode == LV_BLEND_MODE_NORMAL) {
173 fill_normal(disp_area, disp_buf, &draw_area, color, opa, mask, mask_res);
174 }
175 #if LV_USE_BLEND_MODES
176 else {
177 fill_blended(disp_area, disp_buf, &draw_area, color, opa, mask, mask_res, mode);
178 }
179 #endif
180 }
181
182 /**
183 * Copy a map (image) to a display buffer.
184 * @param clip_area clip the map to this area (absolute coordinates)
185 * @param map_area area of the image (absolute coordinates)
186 * @param map_buf a pixels of the map (image)
187 * @param mask a mask to apply on every pixel (uint8_t array with 0x00..0xff values).
188 * Relative to map area but its width is truncated to clip area.
189 * @param mask_res LV_MASK_RES_COVER: the mask has only 0xff values (no mask),
190 * LV_MASK_RES_TRANSP: the mask has only 0x00 values (full transparent),
191 * LV_MASK_RES_CHANGED: the mask has mixed values
192 * @param opa overall opacity in 0x00..0xff range
193 * @param mode blend mode from `lv_blend_mode_t`
194 */
_lv_blend_map(const lv_area_t * clip_area,const lv_area_t * map_area,const lv_color_t * map_buf,lv_opa_t * mask,lv_draw_mask_res_t mask_res,lv_opa_t opa,lv_blend_mode_t mode)195 LV_ATTRIBUTE_FAST_MEM void _lv_blend_map(const lv_area_t * clip_area, const lv_area_t * map_area,
196 const lv_color_t * map_buf,
197 lv_opa_t * mask, lv_draw_mask_res_t mask_res,
198 lv_opa_t opa, lv_blend_mode_t mode)
199 {
200 /*Do not draw transparent things*/
201 if(opa < LV_OPA_MIN) return;
202 if(mask_res == LV_DRAW_MASK_RES_TRANSP) return;
203
204 /* Get clipped fill area which is the real draw area.
205 * It is always the same or inside `fill_area` */
206 lv_area_t draw_area;
207 bool is_common;
208 is_common = _lv_area_intersect(&draw_area, clip_area, map_area);
209 if(!is_common) return;
210
211 lv_disp_t * disp = _lv_refr_get_disp_refreshing();
212 lv_disp_buf_t * vdb = lv_disp_get_buf(disp);
213 const lv_area_t * disp_area = &vdb->area;
214 lv_color_t * disp_buf = vdb->buf_act;
215
216 if(disp->driver.gpu_wait_cb) disp->driver.gpu_wait_cb(&disp->driver);
217
218 /* Now `draw_area` has absolute coordinates.
219 * Make it relative to `disp_area` to simplify draw to `disp_buf`*/
220 draw_area.x1 -= disp_area->x1;
221 draw_area.y1 -= disp_area->y1;
222 draw_area.x2 -= disp_area->x1;
223 draw_area.y2 -= disp_area->y1;
224
225 /*Round the values in the mask if anti-aliasing is disabled*/
226 #if LV_ANTIALIAS
227 if(mask && disp->driver.antialiasing == 0)
228 #else
229 if(mask)
230 #endif
231 {
232 int32_t mask_w = lv_area_get_width(&draw_area);
233 int32_t i;
234 for(i = 0; i < mask_w; i++) mask[i] = mask[i] > 128 ? LV_OPA_COVER : LV_OPA_TRANSP;
235 }
236 if(disp->driver.set_px_cb) {
237 map_set_px(disp_area, disp_buf, &draw_area, map_area, map_buf, opa, mask, mask_res);
238 }
239 else if(mode == LV_BLEND_MODE_NORMAL) {
240 map_normal(disp_area, disp_buf, &draw_area, map_area, map_buf, opa, mask, mask_res);
241 }
242 #if LV_USE_BLEND_MODES
243 else {
244 map_blended(disp_area, disp_buf, &draw_area, map_area, map_buf, opa, mask, mask_res, mode);
245 }
246 #endif
247 }
248
249
250 /**********************
251 * STATIC FUNCTIONS
252 **********************/
253
fill_set_px(const lv_area_t * disp_area,lv_color_t * disp_buf,const lv_area_t * draw_area,lv_color_t color,lv_opa_t opa,const lv_opa_t * mask,lv_draw_mask_res_t mask_res)254 static void fill_set_px(const lv_area_t * disp_area, lv_color_t * disp_buf, const lv_area_t * draw_area,
255 lv_color_t color, lv_opa_t opa,
256 const lv_opa_t * mask, lv_draw_mask_res_t mask_res)
257 {
258
259 lv_disp_t * disp = _lv_refr_get_disp_refreshing();
260
261 /*Get the width of the `disp_area` it will be used to go to the next line*/
262 int32_t disp_w = lv_area_get_width(disp_area);
263
264 int32_t x;
265 int32_t y;
266
267 if(mask_res == LV_DRAW_MASK_RES_FULL_COVER) {
268 for(y = draw_area->y1; y <= draw_area->y2; y++) {
269 for(x = draw_area->x1; x <= draw_area->x2; x++) {
270 disp->driver.set_px_cb(&disp->driver, (void *)disp_buf, disp_w, x, y, color, opa);
271 }
272 }
273 }
274 else {
275 /* The mask is relative to the clipped area.
276 * In the cycles below mask will be indexed from `draw_area.x1`
277 * but it corresponds to zero index. So prepare `mask_tmp` accordingly. */
278 const lv_opa_t * mask_tmp = mask - draw_area->x1;
279
280 /*Get the width of the `draw_area` it will be used to go to the next line of the mask*/
281 int32_t draw_area_w = lv_area_get_width(draw_area);
282
283 for(y = draw_area->y1; y <= draw_area->y2; y++) {
284 for(x = draw_area->x1; x <= draw_area->x2; x++) {
285 if(mask_tmp[x]) {
286 disp->driver.set_px_cb(&disp->driver, (void *)disp_buf, disp_w, x, y, color,
287 (uint32_t)((uint32_t)opa * mask_tmp[x]) >> 8);
288 }
289 }
290 mask_tmp += draw_area_w;
291 }
292 }
293 }
294
295 /**
296 * Fill an area with a color
297 * @param disp_area the current display area (destination area)
298 * @param disp_buf destination buffer
299 * @param draw_area fill this area (relative to `disp_area`)
300 * @param color fill color
301 * @param opa overall opacity in 0x00..0xff range
302 * @param mask a mask to apply on every pixel (uint8_t array with 0x00..0xff values).
303 * It fits into draw_area.
304 * @param mask_res LV_MASK_RES_COVER: the mask has only 0xff values (no mask),
305 * LV_MASK_RES_TRANSP: the mask has only 0x00 values (full transparent),
306 * LV_MASK_RES_CHANGED: the mask has mixed values
307 */
fill_normal(const lv_area_t * disp_area,lv_color_t * disp_buf,const lv_area_t * draw_area,lv_color_t color,lv_opa_t opa,const lv_opa_t * mask,lv_draw_mask_res_t mask_res)308 LV_ATTRIBUTE_FAST_MEM static void fill_normal(const lv_area_t * disp_area, lv_color_t * disp_buf,
309 const lv_area_t * draw_area,
310 lv_color_t color, lv_opa_t opa,
311 const lv_opa_t * mask, lv_draw_mask_res_t mask_res)
312 {
313
314 #if LV_USE_GPU || LV_COLOR_SCREEN_TRANSP
315 lv_disp_t * disp = _lv_refr_get_disp_refreshing();
316 #endif
317
318 /*Get the width of the `disp_area` it will be used to go to the next line*/
319 int32_t disp_w = lv_area_get_width(disp_area);
320
321 int32_t draw_area_w = lv_area_get_width(draw_area);
322 int32_t draw_area_h = lv_area_get_height(draw_area);
323
324 /*Create a temp. disp_buf which always point to the first pixel of the destination area*/
325 lv_color_t * disp_buf_first = disp_buf + disp_w * draw_area->y1 + draw_area->x1;
326
327 int32_t x;
328 int32_t y;
329
330 /*Simple fill (maybe with opacity), no masking*/
331 if(mask_res == LV_DRAW_MASK_RES_FULL_COVER) {
332 if(opa > LV_OPA_MAX) {
333 #if LV_USE_GPU
334 if(disp->driver.gpu_fill_cb && lv_area_get_size(draw_area) > GPU_SIZE_LIMIT) {
335 disp->driver.gpu_fill_cb(&disp->driver, disp_buf, disp_w, draw_area, color);
336 return;
337 }
338 #endif
339
340 #if LV_USE_GPU_STM32_DMA2D
341 if(lv_area_get_size(draw_area) >= 240) {
342 lv_gpu_stm32_dma2d_fill(disp_buf_first, disp_w, color, draw_area_w, draw_area_h);
343 return;
344 }
345 #endif
346 /*Software rendering*/
347 for(y = 0; y < draw_area_h; y++) {
348 lv_color_fill(disp_buf_first, color, draw_area_w);
349 disp_buf_first += disp_w;
350 }
351 }
352 /*No mask with opacity*/
353 else {
354 #if LV_USE_GPU
355 if(disp->driver.gpu_blend_cb && lv_area_get_size(draw_area) > GPU_SIZE_LIMIT) {
356 for(x = 0; x < draw_area_w ; x++) blend_buf[x].full = color.full;
357
358 for(y = draw_area->y1; y <= draw_area->y2; y++) {
359 disp->driver.gpu_blend_cb(&disp->driver, disp_buf_first, blend_buf, draw_area_w, opa);
360 disp_buf_first += disp_w;
361 }
362 return;
363 }
364 #endif
365
366
367 #if LV_USE_GPU_STM32_DMA2D
368 if(lv_area_get_size(draw_area) >= 240) {
369 if(blend_buf[0].full != color.full) lv_color_fill(blend_buf, color, LV_HOR_RES_MAX);
370
371 lv_coord_t line_h = LV_HOR_RES_MAX / draw_area_w;
372 for(y = 0; y <= draw_area_h - line_h; y += line_h) {
373 lv_gpu_stm32_dma2d_blend(disp_buf_first, disp_w, blend_buf, opa, draw_area_w, draw_area_w, line_h);
374 disp_buf_first += disp_w * line_h;
375 }
376
377 if(y != draw_area_h) {
378 lv_gpu_stm32_dma2d_blend(disp_buf_first, disp_w, blend_buf, opa, draw_area_w, draw_area_w, draw_area_h - y);
379 }
380
381 return;
382 }
383 #endif
384 lv_color_t last_dest_color = LV_COLOR_BLACK;
385 lv_color_t last_res_color = lv_color_mix(color, last_dest_color, opa);
386
387 uint16_t color_premult[3];
388 lv_color_premult(color, opa, color_premult);
389 lv_opa_t opa_inv = 255 - opa;
390
391 for(y = 0; y < draw_area_h; y++) {
392 for(x = 0; x < draw_area_w; x++) {
393 if(last_dest_color.full != disp_buf_first[x].full) {
394 last_dest_color = disp_buf_first[x];
395
396 #if LV_COLOR_SCREEN_TRANSP
397 if(disp->driver.screen_transp) {
398 lv_color_mix_with_alpha(disp_buf_first[x], disp_buf_first[x].ch.alpha, color, opa, &last_res_color,
399 &last_res_color.ch.alpha);
400 }
401 else
402 #endif
403 {
404 last_res_color = lv_color_mix_premult(color_premult, disp_buf_first[x], opa_inv);
405 }
406 }
407 disp_buf_first[x] = last_res_color;
408 }
409 disp_buf_first += disp_w;
410 }
411 }
412 }
413 /*Masked*/
414 else {
415 /*DMA2D could be used here but it's much slower than software rendering*/
416 #if LV_USE_GPU_STM32_DMA2D && 0
417 if(lv_area_get_size(draw_area) > 240) {
418 lv_gpu_stm32_dma2d_fill_mask(disp_buf_first, disp_w, color, mask, opa, draw_area_w, draw_area_h);
419 return;
420 }
421 #endif
422
423
424 /*Buffer the result color to avoid recalculating the same color*/
425 lv_color_t last_dest_color;
426 lv_color_t last_res_color;
427 lv_opa_t last_mask = LV_OPA_TRANSP;
428 last_dest_color.full = disp_buf_first[0].full;
429 last_res_color.full = disp_buf_first[0].full;
430
431 int32_t x_end4 = draw_area_w - 4;
432
433 /*Only the mask matters*/
434 if(opa > LV_OPA_MAX) {
435 for(y = 0; y < draw_area_h; y++) {
436 const lv_opa_t * mask_tmp_x = mask;
437 #if 0
438 for(x = 0; x < draw_area_w; x++) {
439 #if LV_COLOR_SCREEN_TRANSP
440 FILL_NORMAL_MASK_PX_SCR_TRANSP(x, color)
441 #else
442 FILL_NORMAL_MASK_PX(x, color)
443 #endif
444 }
445 #else
446 for(x = 0; x < draw_area_w && ((lv_uintptr_t)mask_tmp_x & 0x3); x++) {
447 #if LV_COLOR_SCREEN_TRANSP
448 FILL_NORMAL_MASK_PX_SCR_TRANSP(x, color)
449 #else
450 FILL_NORMAL_MASK_PX(x, color)
451 #endif
452 }
453
454 uint32_t * mask32 = (uint32_t *) mask_tmp_x;
455 for(; x <= x_end4; x += 4) {
456 if(*mask32) {
457 if((*mask32) == 0xFFFFFFFF) {
458 disp_buf_first[x] = color;
459 disp_buf_first[x + 1] = color;
460 disp_buf_first[x + 2] = color;
461 disp_buf_first[x + 3] = color;
462 }
463 else {
464 mask_tmp_x = (const lv_opa_t *)mask32;
465 #if LV_COLOR_SCREEN_TRANSP
466 FILL_NORMAL_MASK_PX_SCR_TRANSP(x, color)
467 FILL_NORMAL_MASK_PX_SCR_TRANSP(x + 1, color)
468 FILL_NORMAL_MASK_PX_SCR_TRANSP(x + 2, color)
469 FILL_NORMAL_MASK_PX_SCR_TRANSP(x + 3, color)
470 #else
471 FILL_NORMAL_MASK_PX(x, color)
472 FILL_NORMAL_MASK_PX(x + 1, color)
473 FILL_NORMAL_MASK_PX(x + 2, color)
474 FILL_NORMAL_MASK_PX(x + 3, color)
475 #endif
476 }
477 }
478 mask32++;
479 }
480
481 mask_tmp_x = (const lv_opa_t *)mask32;
482 for(; x < draw_area_w ; x++) {
483 #if LV_COLOR_SCREEN_TRANSP
484 FILL_NORMAL_MASK_PX_SCR_TRANSP(x, color)
485 #else
486 FILL_NORMAL_MASK_PX(x, color)
487 #endif
488 }
489 #endif
490 disp_buf_first += disp_w;
491 mask += draw_area_w;
492 }
493 }
494 /*Handle opa and mask values too*/
495 else {
496 lv_opa_t opa_tmp = LV_OPA_TRANSP;
497 for(y = draw_area->y1; y <= draw_area->y2; y++) {
498 const lv_opa_t * mask_tmp_x = mask;
499 for(x = 0; x < draw_area_w; x++) {
500 if(*mask_tmp_x) {
501 if(*mask_tmp_x != last_mask) opa_tmp = *mask_tmp_x == LV_OPA_COVER ? opa :
502 (uint32_t)((uint32_t)(*mask_tmp_x) * opa) >> 8;
503 if(*mask_tmp_x != last_mask || last_dest_color.full != disp_buf_first[x].full) {
504 #if LV_COLOR_SCREEN_TRANSP
505 if(disp->driver.screen_transp) {
506 lv_color_mix_with_alpha(disp_buf_first[x], disp_buf_first[x].ch.alpha, color, opa_tmp, &last_res_color,
507 &last_res_color.ch.alpha);
508 }
509 else
510 #endif
511 {
512 if(opa_tmp == LV_OPA_COVER) last_res_color = color;
513 else last_res_color = lv_color_mix(color, disp_buf_first[x], opa_tmp);
514 }
515 last_mask = *mask_tmp_x;
516 last_dest_color.full = disp_buf_first[x].full;
517 }
518 disp_buf_first[x] = last_res_color;
519 }
520 mask_tmp_x++;
521 }
522 disp_buf_first += disp_w;
523 mask += draw_area_w;
524 }
525 }
526 }
527 }
528
529 #if LV_USE_BLEND_MODES
530 /**
531 * Fill an area with a color but apply blending algorithms
532 * @param disp_area the current display area (destination area)
533 * @param disp_buf destination buffer
534 * @param draw_area fill this area (relative to `disp_area`)
535 * @param color fill color
536 * @param opa overall opacity in 0x00..0xff range
537 * @param mask a mask to apply on every pixel (uint8_t array with 0x00..0xff values).
538 * It fits into draw_area.
539 * @param mask_res LV_MASK_RES_COVER: the mask has only 0xff values (no mask),
540 * LV_MASK_RES_TRANSP: the mask has only 0x00 values (full transparent),
541 * LV_MASK_RES_CHANGED: the mask has mixed values
542 * @param mode blend mode from `lv_blend_mode_t`
543 */
fill_blended(const lv_area_t * disp_area,lv_color_t * disp_buf,const lv_area_t * draw_area,lv_color_t color,lv_opa_t opa,const lv_opa_t * mask,lv_draw_mask_res_t mask_res,lv_blend_mode_t mode)544 static void fill_blended(const lv_area_t * disp_area, lv_color_t * disp_buf, const lv_area_t * draw_area,
545 lv_color_t color, lv_opa_t opa,
546 const lv_opa_t * mask, lv_draw_mask_res_t mask_res, lv_blend_mode_t mode)
547 {
548 /*Get the width of the `disp_area` it will be used to go to the next line*/
549 int32_t disp_w = lv_area_get_width(disp_area);
550
551 /*Create a temp. disp_buf which always point to current line to draw*/
552 lv_color_t * disp_buf_tmp = disp_buf + disp_w * draw_area->y1;
553
554
555 lv_color_t (*blend_fp)(lv_color_t, lv_color_t, lv_opa_t);
556 switch(mode) {
557 case LV_BLEND_MODE_ADDITIVE:
558 blend_fp = color_blend_true_color_additive;
559 break;
560 case LV_BLEND_MODE_SUBTRACTIVE:
561 blend_fp = color_blend_true_color_subtractive;
562 break;
563 default:
564 LV_LOG_WARN("fill_blended: unsupported blend mode");
565 return;
566 }
567
568 int32_t x;
569 int32_t y;
570
571 /*Simple fill (maybe with opacity), no masking*/
572 if(mask_res == LV_DRAW_MASK_RES_FULL_COVER) {
573 lv_color_t last_dest_color = LV_COLOR_BLACK;
574 lv_color_t last_res_color = lv_color_mix(color, last_dest_color, opa);
575 for(y = draw_area->y1; y <= draw_area->y2; y++) {
576 for(x = draw_area->x1; x <= draw_area->x2; x++) {
577 if(last_dest_color.full != disp_buf_tmp[x].full) {
578 last_dest_color = disp_buf_tmp[x];
579 last_res_color = blend_fp(color, disp_buf_tmp[x], opa);
580 }
581 disp_buf_tmp[x] = last_res_color;
582 }
583 disp_buf_tmp += disp_w;
584 }
585 }
586 /*Masked*/
587 else {
588 /*Get the width of the `draw_area` it will be used to go to the next line of the mask*/
589 int32_t draw_area_w = lv_area_get_width(draw_area);
590
591 /* The mask is relative to the clipped area.
592 * In the cycles below mask will be indexed from `draw_area.x1`
593 * but it corresponds to zero index. So prepare `mask_tmp` accordingly. */
594 const lv_opa_t * mask_tmp = mask - draw_area->x1;
595
596 /*Buffer the result color to avoid recalculating the same color*/
597 lv_color_t last_dest_color;
598 lv_color_t last_res_color;
599 lv_opa_t last_mask = LV_OPA_TRANSP;
600 last_dest_color.full = disp_buf_tmp[0].full;
601 last_res_color.full = disp_buf_tmp[0].full;
602
603 for(y = draw_area->y1; y <= draw_area->y2; y++) {
604 for(x = draw_area->x1; x <= draw_area->x2; x++) {
605 if(mask_tmp[x] == 0) continue;
606 if(mask_tmp[x] != last_mask || last_dest_color.full != disp_buf_tmp[x].full) {
607 lv_opa_t opa_tmp = mask_tmp[x] >= LV_OPA_MAX ? opa : (uint32_t)((uint32_t)mask_tmp[x] * opa) >> 8;
608
609 last_res_color = blend_fp(color, disp_buf_tmp[x], opa_tmp);
610 last_mask = mask_tmp[x];
611 last_dest_color.full = disp_buf_tmp[x].full;
612 }
613 disp_buf_tmp[x] = last_res_color;
614 }
615 disp_buf_tmp += disp_w;
616 mask_tmp += draw_area_w;
617 }
618 }
619 }
620 #endif
621
map_set_px(const lv_area_t * disp_area,lv_color_t * disp_buf,const lv_area_t * draw_area,const lv_area_t * map_area,const lv_color_t * map_buf,lv_opa_t opa,const lv_opa_t * mask,lv_draw_mask_res_t mask_res)622 static void map_set_px(const lv_area_t * disp_area, lv_color_t * disp_buf, const lv_area_t * draw_area,
623 const lv_area_t * map_area, const lv_color_t * map_buf, lv_opa_t opa,
624 const lv_opa_t * mask, lv_draw_mask_res_t mask_res)
625
626 {
627 lv_disp_t * disp = _lv_refr_get_disp_refreshing();
628
629 /*Get the width of the `disp_area` it will be used to go to the next line*/
630 int32_t disp_w = lv_area_get_width(disp_area);
631
632 /*Get the width of the `draw_area` it will be used to go to the next line of the mask*/
633 int32_t draw_area_w = lv_area_get_width(draw_area);
634
635 /*Get the width of the `mask_area` it will be used to go to the next line*/
636 int32_t map_w = lv_area_get_width(map_area);
637
638 /*Create a temp. map_buf which always point to current line to draw*/
639 const lv_color_t * map_buf_tmp = map_buf + map_w * (draw_area->y1 - (map_area->y1 - disp_area->y1));
640
641 map_buf_tmp += (draw_area->x1 - (map_area->x1 - disp_area->x1));
642 map_buf_tmp -= draw_area->x1;
643 int32_t x;
644 int32_t y;
645
646 if(mask_res == LV_DRAW_MASK_RES_FULL_COVER) {
647 for(y = draw_area->y1; y <= draw_area->y2; y++) {
648 for(x = draw_area->x1; x <= draw_area->x2; x++) {
649 disp->driver.set_px_cb(&disp->driver, (void *)disp_buf, disp_w, x, y, map_buf_tmp[x], opa);
650 }
651 map_buf_tmp += map_w;
652 }
653 }
654 else {
655 /* The mask is relative to the clipped area.
656 * In the cycles below mask will be indexed from `draw_area.x1`
657 * but it corresponds to zero index. So prepare `mask_tmp` accordingly. */
658 const lv_opa_t * mask_tmp = mask - draw_area->x1;
659
660 for(y = draw_area->y1; y <= draw_area->y2; y++) {
661 for(x = draw_area->x1; x <= draw_area->x2; x++) {
662 if(mask_tmp[x]) {
663 disp->driver.set_px_cb(&disp->driver, (void *)disp_buf, disp_w, x, y, map_buf_tmp[x],
664 (uint32_t)((uint32_t)opa * mask_tmp[x]) >> 8);
665 }
666 }
667 mask_tmp += draw_area_w;
668 map_buf_tmp += map_w;
669 }
670 }
671 }
672
673 /**
674 * Copy an image to an area
675 * @param disp_area the current display area (destination area)
676 * @param disp_buf destination buffer
677 * @param map_area coordinates of the map (image) to copy. (absolute coordinates)
678 * @param map_buf the pixel of the image
679 * @param opa overall opacity in 0x00..0xff range
680 * @param mask a mask to apply on every pixel (uint8_t array with 0x00..0xff values).
681 * It fits into draw_area.
682 * @param mask_res LV_MASK_RES_COVER: the mask has only 0xff values (no mask),
683 * LV_MASK_RES_TRANSP: the mask has only 0x00 values (full transparent),
684 * LV_MASK_RES_CHANGED: the mask has mixed values
685 */
map_normal(const lv_area_t * disp_area,lv_color_t * disp_buf,const lv_area_t * draw_area,const lv_area_t * map_area,const lv_color_t * map_buf,lv_opa_t opa,const lv_opa_t * mask,lv_draw_mask_res_t mask_res)686 LV_ATTRIBUTE_FAST_MEM static void map_normal(const lv_area_t * disp_area, lv_color_t * disp_buf,
687 const lv_area_t * draw_area,
688 const lv_area_t * map_area, const lv_color_t * map_buf, lv_opa_t opa,
689 const lv_opa_t * mask, lv_draw_mask_res_t mask_res)
690 {
691
692 /*Get the width of the `disp_area` it will be used to go to the next line*/
693 int32_t disp_w = lv_area_get_width(disp_area);
694
695 int32_t draw_area_w = lv_area_get_width(draw_area);
696 int32_t draw_area_h = lv_area_get_height(draw_area);
697
698 /*Get the width of the `mask_area` it will be used to go to the next line*/
699 int32_t map_w = lv_area_get_width(map_area);
700
701 /*Create a temp. disp_buf which always point to first pixel to draw*/
702 lv_color_t * disp_buf_first = disp_buf + disp_w * draw_area->y1 + draw_area->x1;
703
704 /*Create a temp. map_buf which always point to first pixel to draw from the map*/
705 const lv_color_t * map_buf_first = map_buf + map_w * (draw_area->y1 - (map_area->y1 - disp_area->y1));
706 map_buf_first += (draw_area->x1 - (map_area->x1 - disp_area->x1));
707
708 #if LV_COLOR_SCREEN_TRANSP || LV_USE_GPU
709 lv_disp_t * disp = _lv_refr_get_disp_refreshing();
710 #endif
711
712 int32_t x;
713 int32_t y;
714
715 /*Simple fill (maybe with opacity), no masking*/
716 if(mask_res == LV_DRAW_MASK_RES_FULL_COVER) {
717 #if LV_USE_GPU
718 if(disp->driver.gpu_blend_cb && (lv_area_get_size(draw_area) > GPU_SIZE_LIMIT)) {
719 for(y = draw_area->y1; y <= draw_area->y2; y++) {
720 disp->driver.gpu_blend_cb(&disp->driver, disp_buf_first, map_buf_first, draw_area_w, opa);
721 disp_buf_first += disp_w;
722 map_buf_first += map_w;
723 }
724 return;
725 }
726 #endif
727
728 if(opa > LV_OPA_MAX) {
729 #if LV_USE_GPU_STM32_DMA2D
730 if(lv_area_get_size(draw_area) >= 240) {
731 lv_gpu_stm32_dma2d_copy(disp_buf_first, disp_w, map_buf_first, map_w, draw_area_w, draw_area_h);
732 return;
733 }
734 #endif
735
736 /*Software rendering*/
737 for(y = 0; y < draw_area_h; y++) {
738 _lv_memcpy(disp_buf_first, map_buf_first, draw_area_w * sizeof(lv_color_t));
739 disp_buf_first += disp_w;
740 map_buf_first += map_w;
741 }
742 }
743 else {
744 #if LV_USE_GPU_STM32_DMA2D
745 if(lv_area_get_size(draw_area) >= 240) {
746 lv_gpu_stm32_dma2d_blend(disp_buf_first, disp_w, map_buf_first, opa, map_w, draw_area_w, draw_area_h);
747 return;
748 }
749 #endif
750
751 /*Software rendering*/
752
753 for(y = 0; y < draw_area_h; y++) {
754 for(x = 0; x < draw_area_w; x++) {
755 #if LV_COLOR_SCREEN_TRANSP
756 if(disp->driver.screen_transp) {
757 lv_color_mix_with_alpha(disp_buf_first[x], disp_buf_first[x].ch.alpha, map_buf_first[x], opa, &disp_buf_first[x],
758 &disp_buf_first[x].ch.alpha);
759 }
760 else
761 #endif
762 {
763 disp_buf_first[x] = lv_color_mix(map_buf_first[x], disp_buf_first[x], opa);
764 }
765 }
766 disp_buf_first += disp_w;
767 map_buf_first += map_w;
768 }
769 }
770 }
771 /*Masked*/
772 else {
773 /*Only the mask matters*/
774 if(opa > LV_OPA_MAX) {
775 /*Go to the first pixel of the row */
776
777 int32_t x_end4 = draw_area_w - 4;
778
779 for(y = 0; y < draw_area_h; y++) {
780 const lv_opa_t * mask_tmp_x = mask;
781 #if 0
782 for(x = 0; x < draw_area_w; x++) {
783 MAP_NORMAL_MASK_PX(x);
784 }
785 #else
786 for(x = 0; x < draw_area_w && ((lv_uintptr_t)mask_tmp_x & 0x3); x++) {
787 #if LV_COLOR_SCREEN_TRANSP
788 MAP_NORMAL_MASK_PX_SCR_TRANSP(x)
789 #else
790 MAP_NORMAL_MASK_PX(x)
791 #endif
792 }
793
794 uint32_t * mask32 = (uint32_t *) mask_tmp_x;
795 for(; x < x_end4; x += 4) {
796 if(*mask32) {
797 if((*mask32) == 0xFFFFFFFF) {
798 disp_buf_first[x] = map_buf_first[x];
799 disp_buf_first[x + 1] = map_buf_first[x + 1];
800 disp_buf_first[x + 2] = map_buf_first[x + 2];
801 disp_buf_first[x + 3] = map_buf_first[x + 3];
802 }
803 else {
804 mask_tmp_x = (const lv_opa_t *)mask32;
805 #if LV_COLOR_SCREEN_TRANSP
806 MAP_NORMAL_MASK_PX_SCR_TRANSP(x)
807 MAP_NORMAL_MASK_PX_SCR_TRANSP(x + 1)
808 MAP_NORMAL_MASK_PX_SCR_TRANSP(x + 2)
809 MAP_NORMAL_MASK_PX_SCR_TRANSP(x + 3)
810 #else
811 MAP_NORMAL_MASK_PX(x)
812 MAP_NORMAL_MASK_PX(x + 1)
813 MAP_NORMAL_MASK_PX(x + 2)
814 MAP_NORMAL_MASK_PX(x + 3)
815 #endif
816 }
817 }
818 mask32++;
819 }
820
821 mask_tmp_x = (const lv_opa_t *)mask32;
822 for(; x < draw_area_w ; x++) {
823 #if LV_COLOR_SCREEN_TRANSP
824 MAP_NORMAL_MASK_PX_SCR_TRANSP(x)
825 #else
826 MAP_NORMAL_MASK_PX(x)
827 #endif
828 }
829 #endif
830 disp_buf_first += disp_w;
831 mask += draw_area_w;
832 map_buf_first += map_w;
833 }
834 }
835 /*Handle opa and mask values too*/
836 else {
837 for(y = 0; y < draw_area_h; y++) {
838 for(x = 0; x < draw_area_w; x++) {
839 if(mask[x]) {
840 lv_opa_t opa_tmp = mask[x] >= LV_OPA_MAX ? opa : ((opa * mask[x]) >> 8);
841 #if LV_COLOR_SCREEN_TRANSP
842 if(disp->driver.screen_transp) {
843 lv_color_mix_with_alpha(disp_buf_first[x], disp_buf_first[x].ch.alpha, map_buf_first[x], opa_tmp, &disp_buf_first[x],
844 &disp_buf_first[x].ch.alpha);
845 }
846 else
847 #endif
848 {
849 disp_buf_first[x] = lv_color_mix(map_buf_first[x], disp_buf_first[x], opa_tmp);
850 }
851 }
852 }
853 disp_buf_first += disp_w;
854 mask += draw_area_w;
855 map_buf_first += map_w;
856 }
857 }
858 }
859 }
860 #if LV_USE_BLEND_MODES
map_blended(const lv_area_t * disp_area,lv_color_t * disp_buf,const lv_area_t * draw_area,const lv_area_t * map_area,const lv_color_t * map_buf,lv_opa_t opa,const lv_opa_t * mask,lv_draw_mask_res_t mask_res,lv_blend_mode_t mode)861 static void map_blended(const lv_area_t * disp_area, lv_color_t * disp_buf, const lv_area_t * draw_area,
862 const lv_area_t * map_area, const lv_color_t * map_buf, lv_opa_t opa,
863 const lv_opa_t * mask, lv_draw_mask_res_t mask_res, lv_blend_mode_t mode)
864 {
865
866 /*Get the width of the `disp_area` it will be used to go to the next line*/
867 int32_t disp_w = lv_area_get_width(disp_area);
868
869 /*Get the width of the `draw_area` it will be used to go to the next line of the mask*/
870 int32_t draw_area_w = lv_area_get_width(draw_area);
871
872 /*Get the width of the `mask_area` it will be used to go to the next line*/
873 int32_t map_w = lv_area_get_width(map_area);
874
875 /*Create a temp. disp_buf which always point to current line to draw*/
876 lv_color_t * disp_buf_tmp = disp_buf + disp_w * draw_area->y1;
877
878 /*Create a temp. map_buf which always point to current line to draw*/
879 const lv_color_t * map_buf_tmp = map_buf + map_w * (draw_area->y1 - (map_area->y1 - disp_area->y1));
880
881 lv_color_t (*blend_fp)(lv_color_t, lv_color_t, lv_opa_t);
882 switch(mode) {
883 case LV_BLEND_MODE_ADDITIVE:
884 blend_fp = color_blend_true_color_additive;
885 break;
886 case LV_BLEND_MODE_SUBTRACTIVE:
887 blend_fp = color_blend_true_color_subtractive;
888 break;
889 default:
890 LV_LOG_WARN("fill_blended: unsupported blend mode");
891 return;
892 }
893
894 int32_t x;
895 int32_t y;
896
897 /*Simple fill (maybe with opacity), no masking*/
898 if(mask_res == LV_DRAW_MASK_RES_FULL_COVER) {
899 /*Go to the first px of the row*/
900 map_buf_tmp += (draw_area->x1 - (map_area->x1 - disp_area->x1));
901
902 /*The map will be indexed from `draw_area->x1` so compensate it.*/
903 map_buf_tmp -= draw_area->x1;
904
905 for(y = draw_area->y1; y <= draw_area->y2; y++) {
906 for(x = draw_area->x1; x <= draw_area->x2; x++) {
907 disp_buf_tmp[x] = blend_fp(map_buf_tmp[x], disp_buf_tmp[x], opa);
908 }
909 disp_buf_tmp += disp_w;
910 map_buf_tmp += map_w;
911 }
912 }
913 /*Masked*/
914 else {
915 /* The mask is relative to the clipped area.
916 * In the cycles below mask will be indexed from `draw_area.x1`
917 * but it corresponds to zero index. So prepare `mask_tmp` accordingly. */
918 const lv_opa_t * mask_tmp = mask - draw_area->x1;
919
920 map_buf_tmp -= draw_area->x1;
921 for(y = draw_area->y1; y <= draw_area->y2; y++) {
922 for(x = draw_area->x1; x <= draw_area->x2; x++) {
923 if(mask_tmp[x] == 0) continue;
924 lv_opa_t opa_tmp = mask_tmp[x] >= LV_OPA_MAX ? opa : ((opa * mask_tmp[x]) >> 8);
925 disp_buf_tmp[x] = blend_fp(map_buf_tmp[x], disp_buf_tmp[x], opa_tmp);
926 }
927 disp_buf_tmp += disp_w;
928 mask_tmp += draw_area_w;
929 map_buf_tmp += map_w;
930 }
931 }
932 }
933
color_blend_true_color_additive(lv_color_t fg,lv_color_t bg,lv_opa_t opa)934 static inline lv_color_t color_blend_true_color_additive(lv_color_t fg, lv_color_t bg, lv_opa_t opa)
935 {
936
937 if(opa <= LV_OPA_MIN) return bg;
938
939 uint32_t tmp;
940 #if LV_COLOR_DEPTH == 1
941 tmp = bg.full + fg.full;
942 fg.full = LV_MATH_MIN(tmp, 1);
943 #else
944 tmp = bg.ch.red + fg.ch.red;
945 #if LV_COLOR_DEPTH == 8
946 fg.ch.red = LV_MATH_MIN(tmp, 7);
947 #elif LV_COLOR_DEPTH == 16
948 fg.ch.red = LV_MATH_MIN(tmp, 31);
949 #elif LV_COLOR_DEPTH == 32
950 fg.ch.red = LV_MATH_MIN(tmp, 255);
951 #endif
952
953 #if LV_COLOR_DEPTH == 8
954 fg.ch.green = LV_MATH_MIN(tmp, 7);
955 #elif LV_COLOR_DEPTH == 16
956 #if LV_COLOR_16_SWAP == 0
957 tmp = bg.ch.green + fg.ch.green;
958 fg.ch.green = LV_MATH_MIN(tmp, 63);
959 #else
960 tmp = (bg.ch.green_h << 3) + bg.ch.green_l + (fg.ch.green_h << 3) + fg.ch.green_l;
961 tmp = LV_MATH_MIN(tmp, 63);
962 fg.ch.green_h = tmp >> 3;
963 fg.ch.green_l = tmp & 0x7;
964 #endif
965
966 #elif LV_COLOR_DEPTH == 32
967 fg.ch.green = LV_MATH_MIN(tmp, 255);
968 #endif
969
970 tmp = bg.ch.blue + fg.ch.blue;
971 #if LV_COLOR_DEPTH == 8
972 fg.ch.blue = LV_MATH_MIN(tmp, 4);
973 #elif LV_COLOR_DEPTH == 16
974 fg.ch.blue = LV_MATH_MIN(tmp, 31);
975 #elif LV_COLOR_DEPTH == 32
976 fg.ch.blue = LV_MATH_MIN(tmp, 255);
977 #endif
978 #endif
979
980 if(opa == LV_OPA_COVER) return fg;
981
982 return lv_color_mix(fg, bg, opa);
983 }
984
color_blend_true_color_subtractive(lv_color_t fg,lv_color_t bg,lv_opa_t opa)985 static inline lv_color_t color_blend_true_color_subtractive(lv_color_t fg, lv_color_t bg, lv_opa_t opa)
986 {
987
988 if(opa <= LV_OPA_MIN) return bg;
989
990 int32_t tmp;
991 tmp = bg.ch.red - fg.ch.red;
992 fg.ch.red = LV_MATH_MAX(tmp, 0);
993
994 #if LV_COLOR_16_SWAP == 0
995 tmp = bg.ch.green - fg.ch.green;
996 fg.ch.green = LV_MATH_MAX(tmp, 0);
997 #else
998 tmp = (bg.ch.green_h << 3) + bg.ch.green_l + (fg.ch.green_h << 3) + fg.ch.green_l;
999 tmp = LV_MATH_MAX(tmp, 0);
1000 fg.ch.green_h = tmp >> 3;
1001 fg.ch.green_l = tmp & 0x7;
1002 #endif
1003
1004 tmp = bg.ch.blue - fg.ch.blue;
1005 fg.ch.blue = LV_MATH_MAX(tmp, 0);
1006
1007 if(opa == LV_OPA_COVER) return fg;
1008
1009 return lv_color_mix(fg, bg, opa);
1010 }
1011 #endif
1012