1 /**
2  * @file lv_draw_sw_line.c
3  *
4  */
5 
6 /*********************
7  *      INCLUDES
8  *********************/
9 #include "../../misc/lv_area_private.h"
10 #include "lv_draw_sw_mask_private.h"
11 #include "blend/lv_draw_sw_blend_private.h"
12 #include "../lv_draw_private.h"
13 #include "lv_draw_sw.h"
14 
15 #if LV_USE_DRAW_SW
16 
17 #include "../../misc/lv_math.h"
18 #include "../../misc/lv_types.h"
19 #include "../../core/lv_refr_private.h"
20 #include "../../stdlib/lv_string.h"
21 
22 /*********************
23  *      DEFINES
24  *********************/
25 
26 /**********************
27  *      TYPEDEFS
28  **********************/
29 
30 /**********************
31  *  STATIC PROTOTYPES
32  **********************/
33 
34 static void /* LV_ATTRIBUTE_FAST_MEM */ draw_line_skew(lv_draw_unit_t * draw_unit, const lv_draw_line_dsc_t * dsc);
35 static void /* LV_ATTRIBUTE_FAST_MEM */ draw_line_hor(lv_draw_unit_t * draw_unit, const lv_draw_line_dsc_t * dsc);
36 static void /* LV_ATTRIBUTE_FAST_MEM */ draw_line_ver(lv_draw_unit_t * draw_unit, const lv_draw_line_dsc_t * dsc);
37 
38 /**********************
39  *  STATIC VARIABLES
40  **********************/
41 
42 /**********************
43  *      MACROS
44  **********************/
45 
46 /**********************
47  *   GLOBAL FUNCTIONS
48  **********************/
49 
lv_draw_sw_line(lv_draw_unit_t * draw_unit,const lv_draw_line_dsc_t * dsc)50 void lv_draw_sw_line(lv_draw_unit_t * draw_unit, const lv_draw_line_dsc_t * dsc)
51 {
52     if(dsc->width == 0) return;
53     if(dsc->opa <= LV_OPA_MIN) return;
54 
55     if(dsc->p1.x == dsc->p2.x && dsc->p1.y == dsc->p2.y) return;
56 
57     lv_area_t clip_line;
58     clip_line.x1 = (int32_t)LV_MIN(dsc->p1.x, dsc->p2.x) - dsc->width / 2;
59     clip_line.x2 = (int32_t)LV_MAX(dsc->p1.x, dsc->p2.x) + dsc->width / 2;
60     clip_line.y1 = (int32_t)LV_MIN(dsc->p1.y, dsc->p2.y) - dsc->width / 2;
61     clip_line.y2 = (int32_t)LV_MAX(dsc->p1.y, dsc->p2.y) + dsc->width / 2;
62 
63     bool is_common;
64     is_common = lv_area_intersect(&clip_line, &clip_line, draw_unit->clip_area);
65     if(!is_common) return;
66 
67     LV_PROFILER_DRAW_BEGIN;
68     if((int32_t)dsc->p1.y == (int32_t)dsc->p2.y) draw_line_hor(draw_unit, dsc);
69     else if((int32_t)dsc->p1.x == (int32_t)dsc->p2.x) draw_line_ver(draw_unit, dsc);
70     else draw_line_skew(draw_unit, dsc);
71 
72     if(dsc->round_end || dsc->round_start) {
73         lv_draw_fill_dsc_t cir_dsc;
74         lv_draw_fill_dsc_init(&cir_dsc);
75         cir_dsc.color = dsc->color;
76         cir_dsc.radius = LV_RADIUS_CIRCLE;
77         cir_dsc.opa = dsc->opa;
78 
79         int32_t r = (dsc->width >> 1);
80         int32_t r_corr = (dsc->width & 1) ? 0 : 1;
81         lv_area_t cir_area;
82 
83         if(dsc->round_start) {
84             cir_area.x1 = (int32_t)dsc->p1.x - r;
85             cir_area.y1 = (int32_t)dsc->p1.y - r;
86             cir_area.x2 = (int32_t)dsc->p1.x + r - r_corr;
87             cir_area.y2 = (int32_t)dsc->p1.y + r - r_corr ;
88             lv_draw_sw_fill(draw_unit, &cir_dsc, &cir_area);
89         }
90 
91         if(dsc->round_end) {
92             cir_area.x1 = (int32_t)dsc->p2.x - r;
93             cir_area.y1 = (int32_t)dsc->p2.y - r;
94             cir_area.x2 = (int32_t)dsc->p2.x + r - r_corr;
95             cir_area.y2 = (int32_t)dsc->p2.y + r - r_corr ;
96             lv_draw_sw_fill(draw_unit, &cir_dsc, &cir_area);
97         }
98     }
99     LV_PROFILER_DRAW_END;
100 }
101 
102 /**********************
103  *   STATIC FUNCTIONS
104  **********************/
draw_line_hor(lv_draw_unit_t * draw_unit,const lv_draw_line_dsc_t * dsc)105 static void LV_ATTRIBUTE_FAST_MEM draw_line_hor(lv_draw_unit_t * draw_unit, const lv_draw_line_dsc_t * dsc)
106 {
107     int32_t w = dsc->width - 1;
108     int32_t w_half0 = w >> 1;
109     int32_t w_half1 = w_half0 + (w & 0x1); /*Compensate rounding error*/
110 
111     lv_area_t blend_area;
112     blend_area.x1 = (int32_t)LV_MIN(dsc->p1.x, dsc->p2.x);
113     blend_area.x2 = (int32_t)LV_MAX(dsc->p1.x, dsc->p2.x)  - 1;
114     blend_area.y1 = (int32_t)dsc->p1.y - w_half1;
115     blend_area.y2 = (int32_t)dsc->p1.y + w_half0;
116 
117     bool is_common;
118     is_common = lv_area_intersect(&blend_area, &blend_area, draw_unit->clip_area);
119     if(!is_common) return;
120 
121     bool dashed = dsc->dash_gap && dsc->dash_width;
122 
123     lv_draw_sw_blend_dsc_t blend_dsc;
124     lv_memzero(&blend_dsc, sizeof(blend_dsc));
125     blend_dsc.blend_area = &blend_area;
126     blend_dsc.color = dsc->color;
127     blend_dsc.opa = dsc->opa;
128 
129     /*If there is no mask then simply draw a rectangle*/
130     if(!dashed) {
131         lv_draw_sw_blend(draw_unit, &blend_dsc);
132     }
133 #if LV_DRAW_SW_COMPLEX
134     /*If there other mask apply it*/
135     else {
136 
137         int32_t blend_area_w = lv_area_get_width(&blend_area);
138 
139         int32_t y2 = blend_area.y2;
140         blend_area.y2 = blend_area.y1;
141 
142         int32_t dash_start = blend_area.x1 % (dsc->dash_gap + dsc->dash_width);
143 
144         lv_opa_t * mask_buf = lv_malloc(blend_area_w);
145         blend_dsc.mask_buf = mask_buf;
146         blend_dsc.mask_area = &blend_area;
147         blend_dsc.mask_res = LV_DRAW_SW_MASK_RES_CHANGED;
148         int32_t h;
149         for(h = blend_area.y1; h <= y2; h++) {
150             lv_memset(mask_buf, 0xff, blend_area_w);
151 
152             int32_t dash_cnt = dash_start;
153             int32_t i;
154             for(i = 0; i < blend_area_w; i++, dash_cnt++) {
155                 if(dash_cnt <= dsc->dash_width) {
156                     int16_t diff = dsc->dash_width - dash_cnt;
157                     i += diff;
158                     dash_cnt += diff;
159                 }
160                 else if(dash_cnt > dsc->dash_gap + dsc->dash_width) {
161                     dash_cnt = 0;
162                 }
163                 else {
164                     mask_buf[i] = 0x00;
165                 }
166 
167                 blend_dsc.mask_res = LV_DRAW_SW_MASK_RES_CHANGED;
168             }
169 
170             lv_draw_sw_blend(draw_unit, &blend_dsc);
171 
172             blend_area.y1++;
173             blend_area.y2++;
174         }
175         lv_free(mask_buf);
176     }
177 #endif /*LV_DRAW_SW_COMPLEX*/
178 }
179 
draw_line_ver(lv_draw_unit_t * draw_unit,const lv_draw_line_dsc_t * dsc)180 static void LV_ATTRIBUTE_FAST_MEM draw_line_ver(lv_draw_unit_t * draw_unit, const lv_draw_line_dsc_t * dsc)
181 {
182     int32_t w = dsc->width - 1;
183     int32_t w_half0 = w >> 1;
184     int32_t w_half1 = w_half0 + (w & 0x1); /*Compensate rounding error*/
185 
186     lv_area_t blend_area;
187     blend_area.x1 = (int32_t)dsc->p1.x - w_half1;
188     blend_area.x2 = (int32_t)dsc->p1.x + w_half0;
189     blend_area.y1 = (int32_t)LV_MIN(dsc->p1.y, dsc->p2.y);
190     blend_area.y2 = (int32_t)LV_MAX(dsc->p1.y, dsc->p2.y) - 1;
191 
192     bool is_common;
193     is_common = lv_area_intersect(&blend_area, &blend_area, draw_unit->clip_area);
194     if(!is_common) return;
195 
196     bool dashed = dsc->dash_gap && dsc->dash_width;
197 
198     lv_draw_sw_blend_dsc_t blend_dsc;
199     lv_memzero(&blend_dsc, sizeof(blend_dsc));
200     blend_dsc.blend_area = &blend_area;
201     blend_dsc.color = dsc->color;
202     blend_dsc.opa = dsc->opa;
203 
204     /*If there is no mask then simply draw a rectangle*/
205     if(!dashed) {
206         lv_draw_sw_blend(draw_unit, &blend_dsc);
207     }
208 
209 #if LV_DRAW_SW_COMPLEX
210     /*If there other mask apply it*/
211     else {
212         int32_t draw_area_w = lv_area_get_width(&blend_area);
213 
214         int32_t y2 = blend_area.y2;
215         blend_area.y2 = blend_area.y1;
216 
217         lv_opa_t * mask_buf = lv_malloc(draw_area_w);
218         blend_dsc.mask_buf = mask_buf;
219         blend_dsc.mask_area = &blend_area;
220         blend_dsc.mask_res = LV_DRAW_SW_MASK_RES_CHANGED;
221         int32_t dash_start = (blend_area.y1) % (dsc->dash_gap + dsc->dash_width);
222 
223         int32_t dash_cnt = dash_start;
224 
225         int32_t h;
226         for(h = blend_area.y1; h <= y2; h++) {
227             lv_memset(mask_buf, 0xff, draw_area_w);
228 
229             if(dash_cnt > dsc->dash_width) {
230                 blend_dsc.mask_res = LV_DRAW_SW_MASK_RES_TRANSP;
231             }
232             else {
233                 blend_dsc.mask_res = LV_DRAW_SW_MASK_RES_FULL_COVER;
234             }
235 
236             if(dash_cnt >= dsc->dash_gap + dsc->dash_width) {
237                 dash_cnt = 0;
238             }
239             dash_cnt ++;
240 
241             lv_draw_sw_blend(draw_unit, &blend_dsc);
242 
243             blend_area.y1++;
244             blend_area.y2++;
245         }
246         lv_free(mask_buf);
247     }
248 #endif /*LV_DRAW_SW_COMPLEX*/
249 }
250 
draw_line_skew(lv_draw_unit_t * draw_unit,const lv_draw_line_dsc_t * dsc)251 static void LV_ATTRIBUTE_FAST_MEM draw_line_skew(lv_draw_unit_t * draw_unit, const lv_draw_line_dsc_t * dsc)
252 {
253 #if LV_DRAW_SW_COMPLEX
254     /*Keep the great y in p1*/
255     lv_point_t p1;
256     lv_point_t p2;
257     if(dsc->p1.y < dsc->p2.y) {
258         p1 = lv_point_from_precise(&dsc->p1);
259         p2 = lv_point_from_precise(&dsc->p2);
260     }
261     else {
262         p1 = lv_point_from_precise(&dsc->p2);
263         p2 = lv_point_from_precise(&dsc->p1);
264     }
265 
266     int32_t xdiff = p2.x - p1.x;
267     int32_t ydiff = p2.y - p1.y;
268     bool flat = LV_ABS(xdiff) > LV_ABS(ydiff);
269 
270     static const uint8_t wcorr[] = {
271         128, 128, 128, 129, 129, 130, 130, 131,
272         132, 133, 134, 135, 137, 138, 140, 141,
273         143, 145, 147, 149, 151, 153, 155, 158,
274         160, 162, 165, 167, 170, 173, 175, 178,
275         181,
276     };
277 
278     int32_t w = dsc->width;
279     int32_t wcorr_i = 0;
280     if(flat) wcorr_i = (LV_ABS(ydiff) << 5) / LV_ABS(xdiff);
281     else wcorr_i = (LV_ABS(xdiff) << 5) / LV_ABS(ydiff);
282 
283     w = (w * wcorr[wcorr_i] + 63) >> 7;     /*+ 63 for rounding*/
284     int32_t w_half0 = w >> 1;
285     int32_t w_half1 = w_half0 + (w & 0x1); /*Compensate rounding error*/
286 
287     lv_area_t blend_area;
288     blend_area.x1 = LV_MIN(p1.x, p2.x) - w;
289     blend_area.x2 = LV_MAX(p1.x, p2.x) + w;
290     blend_area.y1 = LV_MIN(p1.y, p2.y) - w;
291     blend_area.y2 = LV_MAX(p1.y, p2.y) + w;
292 
293     /*Get the union of `coords` and `clip`*/
294     /*`clip` is already truncated to the `draw_buf` size
295      *in 'lv_refr_area' function*/
296     bool is_common = lv_area_intersect(&blend_area, &blend_area, draw_unit->clip_area);
297     if(is_common == false) return;
298 
299     lv_draw_sw_mask_line_param_t mask_left_param;
300     lv_draw_sw_mask_line_param_t mask_right_param;
301     lv_draw_sw_mask_line_param_t mask_top_param;
302     lv_draw_sw_mask_line_param_t mask_bottom_param;
303 
304     void * masks[5] = {&mask_left_param, & mask_right_param, NULL, NULL, NULL};
305 
306     if(flat) {
307         if(xdiff > 0) {
308             lv_draw_sw_mask_line_points_init(&mask_left_param, p1.x, p1.y - w_half0, p2.x, p2.y - w_half0,
309                                              LV_DRAW_SW_MASK_LINE_SIDE_LEFT);
310             lv_draw_sw_mask_line_points_init(&mask_right_param, p1.x, p1.y + w_half1, p2.x, p2.y + w_half1,
311                                              LV_DRAW_SW_MASK_LINE_SIDE_RIGHT);
312         }
313         else {
314             lv_draw_sw_mask_line_points_init(&mask_left_param, p1.x, p1.y + w_half1, p2.x, p2.y + w_half1,
315                                              LV_DRAW_SW_MASK_LINE_SIDE_LEFT);
316             lv_draw_sw_mask_line_points_init(&mask_right_param, p1.x, p1.y - w_half0, p2.x, p2.y - w_half0,
317                                              LV_DRAW_SW_MASK_LINE_SIDE_RIGHT);
318         }
319     }
320     else {
321         lv_draw_sw_mask_line_points_init(&mask_left_param, p1.x + w_half1, p1.y, p2.x + w_half1, p2.y,
322                                          LV_DRAW_SW_MASK_LINE_SIDE_LEFT);
323         lv_draw_sw_mask_line_points_init(&mask_right_param, p1.x - w_half0, p1.y, p2.x - w_half0, p2.y,
324                                          LV_DRAW_SW_MASK_LINE_SIDE_RIGHT);
325 
326     }
327 
328     /*Use the normal vector for the endings*/
329 
330     if(!dsc->raw_end) {
331         lv_draw_sw_mask_line_points_init(&mask_top_param, p1.x, p1.y, p1.x - ydiff, p1.y + xdiff,
332                                          LV_DRAW_SW_MASK_LINE_SIDE_BOTTOM);
333         lv_draw_sw_mask_line_points_init(&mask_bottom_param, p2.x, p2.y, p2.x - ydiff, p2.y + xdiff,
334                                          LV_DRAW_SW_MASK_LINE_SIDE_TOP);
335         masks[2] = &mask_top_param;
336         masks[3] = &mask_bottom_param;
337     }
338 
339     /*The real draw area is around the line.
340      *It's easy to calculate with steep lines, but the area can be very wide with very flat lines.
341      *So deal with it only with steep lines.*/
342     int32_t draw_area_w = lv_area_get_width(&blend_area);
343 
344     /*Draw the background line by line*/
345     int32_t h;
346     uint32_t hor_res = (uint32_t)lv_display_get_horizontal_resolution(lv_refr_get_disp_refreshing());
347     size_t mask_buf_size = LV_MIN(lv_area_get_size(&blend_area), hor_res);
348     lv_opa_t * mask_buf = lv_malloc(mask_buf_size);
349 
350     int32_t y2 = blend_area.y2;
351     blend_area.y2 = blend_area.y1;
352 
353     uint32_t mask_p = 0;
354     lv_memset(mask_buf, 0xff, mask_buf_size);
355 
356     lv_draw_sw_blend_dsc_t blend_dsc;
357     lv_memzero(&blend_dsc, sizeof(blend_dsc));
358     blend_dsc.blend_area = &blend_area;
359     blend_dsc.color = dsc->color;
360     blend_dsc.opa = dsc->opa;
361     blend_dsc.mask_buf = mask_buf;
362     blend_dsc.mask_area = &blend_area;
363 
364     /*Fill the first row with 'color'*/
365     for(h = blend_area.y1; h <= y2; h++) {
366         blend_dsc.mask_res = lv_draw_sw_mask_apply(masks, &mask_buf[mask_p], blend_area.x1, h, draw_area_w);
367         if(blend_dsc.mask_res == LV_DRAW_SW_MASK_RES_TRANSP) {
368             lv_memzero(&mask_buf[mask_p], draw_area_w);
369         }
370 
371         mask_p += draw_area_w;
372         if((uint32_t) mask_p + draw_area_w < mask_buf_size) {
373             blend_area.y2 ++;
374         }
375         else {
376             blend_dsc.mask_res = LV_DRAW_SW_MASK_RES_CHANGED;
377             lv_draw_sw_blend(draw_unit, &blend_dsc);
378 
379             blend_area.y1 = blend_area.y2 + 1;
380             blend_area.y2 = blend_area.y1;
381             mask_p = 0;
382             lv_memset(mask_buf, 0xff, mask_buf_size);
383         }
384     }
385 
386     /*Flush the last part*/
387     if(blend_area.y1 != blend_area.y2) {
388         blend_area.y2--;
389         blend_dsc.mask_res = LV_DRAW_SW_MASK_RES_CHANGED;
390         lv_draw_sw_blend(draw_unit, &blend_dsc);
391     }
392 
393     lv_free(mask_buf);
394 
395     lv_draw_sw_mask_free_param(&mask_left_param);
396     lv_draw_sw_mask_free_param(&mask_right_param);
397     if(!dsc->raw_end) {
398         lv_draw_sw_mask_free_param(&mask_top_param);
399         lv_draw_sw_mask_free_param(&mask_bottom_param);
400     }
401 #else
402     LV_UNUSED(draw_unit);
403     LV_UNUSED(dsc);
404     LV_LOG_WARN("Can't draw skewed line with LV_DRAW_SW_COMPLEX == 0");
405 #endif /*LV_DRAW_SW_COMPLEX*/
406 }
407 
408 #endif /*LV_USE_DRAW_SW*/
409