1 /**
2  * @file lv_draw_line.c
3  *
4  */
5 
6 /*********************
7  *      INCLUDES
8  *********************/
9 #include <stdio.h>
10 #include <stdbool.h>
11 #include "lv_draw_mask.h"
12 #include "lv_draw_blend.h"
13 #include "../lv_core/lv_refr.h"
14 #include "../lv_misc/lv_math.h"
15 
16 /*********************
17  *      DEFINES
18  *********************/
19 
20 /**********************
21  *      TYPEDEFS
22  **********************/
23 
24 /**********************
25  *  STATIC PROTOTYPES
26  **********************/
27 LV_ATTRIBUTE_FAST_MEM static void draw_line_skew(const lv_point_t * point1, const lv_point_t * point2,
28                                                  const lv_area_t * clip,
29                                                  const lv_draw_line_dsc_t * dsc);
30 LV_ATTRIBUTE_FAST_MEM static void draw_line_hor(const lv_point_t * point1, const lv_point_t * point2,
31                                                 const lv_area_t * clip,
32                                                 const lv_draw_line_dsc_t * dsc);
33 LV_ATTRIBUTE_FAST_MEM static void draw_line_ver(const lv_point_t * point1, const lv_point_t * point2,
34                                                 const lv_area_t * clip,
35                                                 const lv_draw_line_dsc_t * dsc);
36 
37 /**********************
38  *  STATIC VARIABLES
39  **********************/
40 
41 /**********************
42  *      MACROS
43  **********************/
44 
45 /**********************
46  *   GLOBAL FUNCTIONS
47  **********************/
48 
lv_draw_line_dsc_init(lv_draw_line_dsc_t * dsc)49 LV_ATTRIBUTE_FAST_MEM void lv_draw_line_dsc_init(lv_draw_line_dsc_t * dsc)
50 {
51     _lv_memset_00(dsc, sizeof(lv_draw_line_dsc_t));
52     dsc->width = 1;
53     dsc->opa = LV_OPA_COVER;
54     dsc->color = LV_COLOR_BLACK;
55 }
56 
57 /**
58  * Draw a line
59  * @param point1 first point of the line
60  * @param point2 second point of the line
61  * @param clip the line will be drawn only in this area
62  * @param dsc pointer to an initialized `lv_draw_line_dsc_t` variable
63  */
lv_draw_line(const lv_point_t * point1,const lv_point_t * point2,const lv_area_t * clip,const lv_draw_line_dsc_t * dsc)64 LV_ATTRIBUTE_FAST_MEM void lv_draw_line(const lv_point_t * point1, const lv_point_t * point2, const lv_area_t * clip,
65                                         const lv_draw_line_dsc_t * dsc)
66 {
67     if(dsc->width == 0) return;
68     if(dsc->opa <= LV_OPA_MIN) return;
69 
70     if(point1->x == point2->x && point1->y == point2->y) return;
71 
72     lv_area_t clip_line;
73     clip_line.x1 = LV_MATH_MIN(point1->x, point2->x) - dsc->width / 2;
74     clip_line.x2 = LV_MATH_MAX(point1->x, point2->x) + dsc->width / 2;
75     clip_line.y1 = LV_MATH_MIN(point1->y, point2->y) - dsc->width / 2;
76     clip_line.y2 = LV_MATH_MAX(point1->y, point2->y) + dsc->width / 2;
77 
78     bool is_common;
79     is_common = _lv_area_intersect(&clip_line, &clip_line, clip);
80     if(!is_common) return;
81 
82     if(point1->y == point2->y) draw_line_hor(point1, point2, &clip_line, dsc);
83     else if(point1->x == point2->x) draw_line_ver(point1, point2, &clip_line, dsc);
84     else draw_line_skew(point1, point2, &clip_line, dsc);
85 
86 
87     if(dsc->round_end || dsc->round_start) {
88         lv_draw_rect_dsc_t cir_dsc;
89         lv_draw_rect_dsc_init(&cir_dsc);
90         cir_dsc.bg_color = dsc->color;
91         cir_dsc.radius = LV_RADIUS_CIRCLE;
92         cir_dsc.bg_opa = dsc->opa;
93 
94         int32_t r = (dsc->width >> 1);
95         int32_t r_corr = (dsc->width & 1) ? 0 : 1;
96         lv_area_t cir_area;
97 
98         if(dsc->round_start) {
99             cir_area.x1 = point1->x - r;
100             cir_area.y1 = point1->y - r;
101             cir_area.x2 = point1->x + r - r_corr;
102             cir_area.y2 = point1->y + r - r_corr ;
103             lv_draw_rect(&cir_area, clip, &cir_dsc);
104         }
105 
106         if(dsc->round_end) {
107             cir_area.x1 = point2->x - r;
108             cir_area.y1 = point2->y - r;
109             cir_area.x2 = point2->x + r - r_corr;
110             cir_area.y2 = point2->y + r - r_corr ;
111             lv_draw_rect(&cir_area, clip, &cir_dsc);
112         }
113     }
114 }
115 
116 /**********************
117  *   STATIC FUNCTIONS
118  **********************/
119 
draw_line_hor(const lv_point_t * point1,const lv_point_t * point2,const lv_area_t * clip,const lv_draw_line_dsc_t * dsc)120 LV_ATTRIBUTE_FAST_MEM static void draw_line_hor(const lv_point_t * point1, const lv_point_t * point2,
121                                                 const lv_area_t * clip,
122                                                 const lv_draw_line_dsc_t * dsc)
123 {
124     lv_opa_t opa = dsc->opa;
125 
126     lv_disp_t * disp    = _lv_refr_get_disp_refreshing();
127     lv_disp_buf_t * vdb = lv_disp_get_buf(disp);
128 
129     const lv_area_t * disp_area = &vdb->area;
130 
131     int32_t w = dsc->width - 1;
132     int32_t w_half0 = w >> 1;
133     int32_t w_half1 = w_half0 + (w & 0x1); /*Compensate rounding error*/
134 
135     bool dashed = dsc->dash_gap && dsc->dash_width ? true : false;
136 
137     bool simple_mode = true;
138     if(lv_draw_mask_get_cnt()) simple_mode = false;
139     else if(dashed) simple_mode = false;
140 
141     lv_area_t draw_area;
142     draw_area.x1 = LV_MATH_MIN(point1->x, point2->x);
143     draw_area.x2 = LV_MATH_MAX(point1->x, point2->x)  - 1;
144     draw_area.y1 = point1->y - w_half1;
145     draw_area.y2 = point1->y + w_half0;
146 
147     /*If there is no mask then simply draw a rectangle*/
148     if(simple_mode) {
149         _lv_blend_fill(clip, &draw_area,
150                        dsc->color, NULL, LV_DRAW_MASK_RES_FULL_COVER, opa,
151                        dsc->blend_mode);
152     }
153     /*If there other mask apply it*/
154     else {
155         /* Get clipped fill area which is the real draw area.
156          * It is always the same or inside `fill_area` */
157         bool is_common;
158         is_common = _lv_area_intersect(&draw_area, clip, &draw_area);
159         if(!is_common) return;
160 
161         /* Now `draw_area` has absolute coordinates.
162          * Make it relative to `disp_area` to simplify draw to `disp_buf`*/
163         draw_area.x1 -= disp_area->x1;
164         draw_area.y1 -= disp_area->y1;
165         draw_area.x2 -= disp_area->x1;
166         draw_area.y2 -= disp_area->y1;
167 
168         int32_t draw_area_w = lv_area_get_width(&draw_area);
169 
170         lv_area_t fill_area;
171         fill_area.x1 = draw_area.x1 + disp_area->x1;
172         fill_area.x2 = draw_area.x2 + disp_area->x1;
173         fill_area.y1 = draw_area.y1 + disp_area->y1;
174         fill_area.y2 = fill_area.y1;
175 
176         lv_style_int_t dash_start = 0;
177         if(dashed) {
178             dash_start = (vdb->area.x1 + draw_area.x1) % (dsc->dash_gap + dsc->dash_width);
179         }
180 
181         lv_opa_t * mask_buf = _lv_mem_buf_get(draw_area_w);
182         int32_t h;
183         for(h = draw_area.y1; h <= draw_area.y2; h++) {
184             _lv_memset_ff(mask_buf, draw_area_w);
185             lv_draw_mask_res_t mask_res = lv_draw_mask_apply(mask_buf, vdb->area.x1 + draw_area.x1, vdb->area.y1 + h, draw_area_w);
186 
187             if(dashed) {
188                 if(mask_res != LV_DRAW_MASK_RES_TRANSP) {
189                     lv_style_int_t dash_cnt = dash_start;
190                     lv_coord_t i;
191                     for(i = 0; i < draw_area_w; i++, dash_cnt++) {
192                         if(dash_cnt <= dsc->dash_width) {
193                             int16_t diff = dsc->dash_width - dash_cnt;
194                             i += diff;
195                             dash_cnt += diff;
196                         }
197                         else if(dash_cnt >= dsc->dash_gap + dsc->dash_width) {
198                             dash_cnt = 0;
199                         }
200                         else {
201                             mask_buf[i] = 0x00;
202                         }
203                     }
204 
205                     mask_res = LV_DRAW_MASK_RES_CHANGED;
206                 }
207             }
208 
209             _lv_blend_fill(clip, &fill_area,
210                            dsc->color, mask_buf, mask_res, dsc->opa,
211                            dsc->blend_mode);
212 
213             fill_area.y1++;
214             fill_area.y2++;
215         }
216         _lv_mem_buf_release(mask_buf);
217     }
218 }
219 
220 
draw_line_ver(const lv_point_t * point1,const lv_point_t * point2,const lv_area_t * clip,const lv_draw_line_dsc_t * dsc)221 LV_ATTRIBUTE_FAST_MEM static void draw_line_ver(const lv_point_t * point1, const lv_point_t * point2,
222                                                 const lv_area_t * clip,
223                                                 const lv_draw_line_dsc_t * dsc)
224 {
225     lv_opa_t opa = dsc->opa;
226 
227     lv_disp_t * disp    = _lv_refr_get_disp_refreshing();
228     lv_disp_buf_t * vdb = lv_disp_get_buf(disp);
229 
230     const lv_area_t * disp_area = &vdb->area;
231 
232     int32_t w = dsc->width - 1;
233     int32_t w_half0 = w >> 1;
234     int32_t w_half1 = w_half0 + (w & 0x1); /*Compensate rounding error*/
235 
236     bool dashed = dsc->dash_gap && dsc->dash_width ? true : false;
237 
238     bool simple_mode = true;
239     if(lv_draw_mask_get_cnt()) simple_mode = false;
240     else if(dashed) simple_mode = false;
241 
242     lv_area_t draw_area;
243     draw_area.x1 = point1->x - w_half1;
244     draw_area.x2 = point1->x + w_half0;
245     draw_area.y1 = LV_MATH_MIN(point1->y, point2->y);
246     draw_area.y2 = LV_MATH_MAX(point1->y, point2->y) - 1;
247 
248     /*If there is no mask then simply draw a rectangle*/
249     if(simple_mode) {
250         _lv_blend_fill(clip, &draw_area,
251                        dsc->color,  NULL, LV_DRAW_MASK_RES_FULL_COVER, opa,
252                        dsc->blend_mode);
253     }
254     /*If there other mask apply it*/
255     else {
256         /* Get clipped fill area which is the real draw area.
257          * It is always the same or inside `fill_area` */
258         bool is_common;
259         is_common = _lv_area_intersect(&draw_area, clip, &draw_area);
260         if(!is_common) return;
261 
262         /* Now `draw_area` has absolute coordinates.
263          * Make it relative to `disp_area` to simplify draw to `disp_buf`*/
264         draw_area.x1 -= vdb->area.x1;
265         draw_area.y1 -= vdb->area.y1;
266         draw_area.x2 -= vdb->area.x1;
267         draw_area.y2 -= vdb->area.y1;
268 
269         int32_t draw_area_w = lv_area_get_width(&draw_area);
270 
271         lv_area_t fill_area;
272         fill_area.x1 = draw_area.x1 + disp_area->x1;
273         fill_area.x2 = draw_area.x2 + disp_area->x1;
274         fill_area.y1 = draw_area.y1 + disp_area->y1;
275         fill_area.y2 = fill_area.y1;
276 
277         lv_opa_t * mask_buf = _lv_mem_buf_get(draw_area_w);
278 
279         lv_style_int_t dash_start = 0;
280         if(dashed) {
281             dash_start = (vdb->area.y1 + draw_area.y1) % (dsc->dash_gap + dsc->dash_width);
282         }
283 
284         lv_style_int_t dash_cnt = dash_start;
285 
286         int32_t h;
287         for(h = draw_area.y1; h <= draw_area.y2; h++) {
288             _lv_memset_ff(mask_buf, draw_area_w);
289             lv_draw_mask_res_t mask_res = lv_draw_mask_apply(mask_buf, vdb->area.x1 + draw_area.x1, vdb->area.y1 + h, draw_area_w);
290 
291             if(dashed) {
292                 if(mask_res != LV_DRAW_MASK_RES_TRANSP) {
293                     if(dash_cnt > dsc->dash_width) {
294                         mask_res = LV_DRAW_MASK_RES_TRANSP;
295                     }
296 
297                     if(dash_cnt >= dsc->dash_gap + dsc->dash_width) {
298                         dash_cnt = 0;
299                     }
300                 }
301                 dash_cnt ++;
302             }
303 
304             _lv_blend_fill(clip, &fill_area,
305                            dsc->color, mask_buf, mask_res, dsc->opa,
306                            LV_BLEND_MODE_NORMAL);
307 
308             fill_area.y1++;
309             fill_area.y2++;
310         }
311         _lv_mem_buf_release(mask_buf);
312     }
313 }
314 
315 
draw_line_skew(const lv_point_t * point1,const lv_point_t * point2,const lv_area_t * clip,const lv_draw_line_dsc_t * dsc)316 LV_ATTRIBUTE_FAST_MEM static void draw_line_skew(const lv_point_t * point1, const lv_point_t * point2,
317                                                  const lv_area_t * clip,
318                                                  const lv_draw_line_dsc_t * dsc)
319 {
320     /*Keep the great y in p1*/
321     lv_point_t p1;
322     lv_point_t p2;
323     if(point1->y < point2->y) {
324         p1.y = point1->y;
325         p2.y = point2->y;
326         p1.x = point1->x;
327         p2.x = point2->x;
328     }
329     else {
330         p1.y = point2->y;
331         p2.y = point1->y;
332         p1.x = point2->x;
333         p2.x = point1->x;
334     }
335 
336     int32_t xdiff = p2.x - p1.x;
337     int32_t ydiff = p2.y - p1.y;
338     bool flat = LV_MATH_ABS(xdiff) > LV_MATH_ABS(ydiff) ? true : false;
339 
340     static const uint8_t wcorr[] = {
341         128, 128, 128, 129, 129, 130, 130, 131,
342         132, 133, 134, 135, 137, 138, 140, 141,
343         143, 145, 147, 149, 151, 153, 155, 158,
344         160, 162, 165, 167, 170, 173, 175, 178,
345         181,
346     };
347 
348     int32_t w = dsc->width;
349     int32_t wcorr_i = 0;
350     if(flat) wcorr_i = (LV_MATH_ABS(ydiff) << 5) / LV_MATH_ABS(xdiff);
351     else wcorr_i = (LV_MATH_ABS(xdiff) << 5) / LV_MATH_ABS(ydiff);
352 
353     w = (w * wcorr[wcorr_i] + 63) >> 7;     /*+ 63 for rounding*/
354     int32_t w_half0 = w >> 1;
355     int32_t w_half1 = w_half0 + (w & 0x1); /*Compensate rounding error*/
356 
357     lv_area_t draw_area;
358     draw_area.x1 = LV_MATH_MIN(p1.x, p2.x) - w;
359     draw_area.x2 = LV_MATH_MAX(p1.x, p2.x) + w;
360     draw_area.y1 = LV_MATH_MIN(p1.y, p2.y) - w;
361     draw_area.y2 = LV_MATH_MAX(p1.y, p2.y) + w;
362 
363     /* Get the union of `coords` and `clip`*/
364     /* `clip` is already truncated to the `vdb` size
365      * in 'lv_refr_area' function */
366     bool is_common = _lv_area_intersect(&draw_area, &draw_area, clip);
367     if(is_common == false) return;
368 
369     lv_draw_mask_line_param_t mask_left_param;
370     lv_draw_mask_line_param_t mask_right_param;
371     lv_draw_mask_line_param_t mask_top_param;
372     lv_draw_mask_line_param_t mask_bottom_param;
373 
374     if(flat) {
375         if(xdiff > 0) {
376             lv_draw_mask_line_points_init(&mask_left_param, p1.x, p1.y - w_half0, p2.x, p2.y - w_half0,
377                                           LV_DRAW_MASK_LINE_SIDE_LEFT);
378             lv_draw_mask_line_points_init(&mask_right_param, p1.x, p1.y + w_half1, p2.x, p2.y + w_half1,
379                                           LV_DRAW_MASK_LINE_SIDE_RIGHT);
380         }
381         else {
382             lv_draw_mask_line_points_init(&mask_left_param, p1.x, p1.y + w_half1, p2.x, p2.y + w_half1,
383                                           LV_DRAW_MASK_LINE_SIDE_LEFT);
384             lv_draw_mask_line_points_init(&mask_right_param, p1.x, p1.y - w_half0, p2.x, p2.y - w_half0,
385                                           LV_DRAW_MASK_LINE_SIDE_RIGHT);
386         }
387     }
388     else {
389         lv_draw_mask_line_points_init(&mask_left_param, p1.x + w_half1, p1.y, p2.x + w_half1, p2.y,
390                                       LV_DRAW_MASK_LINE_SIDE_LEFT);
391         lv_draw_mask_line_points_init(&mask_right_param, p1.x - w_half0, p1.y, p2.x - w_half0, p2.y,
392                                       LV_DRAW_MASK_LINE_SIDE_RIGHT);
393     }
394 
395     /*Use the normal vector for the endings*/
396 
397     int16_t mask_left_id = lv_draw_mask_add(&mask_left_param, NULL);
398     int16_t mask_right_id = lv_draw_mask_add(&mask_right_param, NULL);
399     int16_t mask_top_id = LV_MASK_ID_INV;
400     int16_t mask_bottom_id = LV_MASK_ID_INV;
401 
402     if(!dsc->raw_end) {
403         lv_draw_mask_line_points_init(&mask_top_param, p1.x, p1.y, p1.x - ydiff, p1.y + xdiff, LV_DRAW_MASK_LINE_SIDE_BOTTOM);
404         lv_draw_mask_line_points_init(&mask_bottom_param, p2.x, p2.y, p2.x - ydiff, p2.y + xdiff,  LV_DRAW_MASK_LINE_SIDE_TOP);
405         mask_top_id = lv_draw_mask_add(&mask_top_param, NULL);
406         mask_bottom_id = lv_draw_mask_add(&mask_bottom_param, NULL);
407     }
408 
409     lv_disp_t * disp    = _lv_refr_get_disp_refreshing();
410     lv_disp_buf_t * vdb = lv_disp_get_buf(disp);
411 
412     const lv_area_t * disp_area = &vdb->area;
413 
414     /*Store the coordinates of the `draw_a` relative to the VDB */
415     draw_area.x1 -= disp_area->x1;
416     draw_area.y1 -= disp_area->y1;
417     draw_area.x2 -= disp_area->x1;
418     draw_area.y2 -= disp_area->y1;
419 
420     /* The real draw area is around the line.
421      * It's easy to calculate with steep lines, but the area can be very wide with very flat lines.
422      * So deal with it only with steep lines. */
423     int32_t draw_area_w = lv_area_get_width(&draw_area);
424 
425     /*Draw the background line by line*/
426     int32_t h;
427     size_t mask_buf_size = LV_MATH_MIN(lv_area_get_size(&draw_area), LV_HOR_RES_MAX);
428     lv_opa_t * mask_buf = _lv_mem_buf_get(mask_buf_size);
429 
430     lv_area_t fill_area;
431     fill_area.x1 = draw_area.x1 + disp_area->x1;
432     fill_area.x2 = draw_area.x2 + disp_area->x1;
433     fill_area.y1 = draw_area.y1 + disp_area->y1;
434     fill_area.y2 = fill_area.y1;
435 
436     int32_t x = vdb->area.x1 + draw_area.x1;
437 
438     uint32_t mask_p = 0;
439 
440     _lv_memset_ff(mask_buf, mask_buf_size);
441     /*Fill the first row with 'color'*/
442     for(h = draw_area.y1 + disp_area->y1; h <= draw_area.y2 + disp_area->y1; h++) {
443 
444         lv_draw_mask_res_t mask_res = lv_draw_mask_apply(&mask_buf[mask_p], x, h, draw_area_w);
445         if(mask_res == LV_DRAW_MASK_RES_TRANSP) {
446             _lv_memset_00(&mask_buf[mask_p], draw_area_w);
447         }
448 
449         mask_p += draw_area_w;
450         if((uint32_t) mask_p + draw_area_w < mask_buf_size) {
451             fill_area.y2 ++;
452         }
453         else {
454             _lv_blend_fill(&fill_area, clip,
455                            dsc->color, mask_buf, LV_DRAW_MASK_RES_CHANGED, dsc->opa,
456                            dsc->blend_mode);
457 
458             fill_area.y1 = fill_area.y2 + 1;
459             fill_area.y2 = fill_area.y1;
460             mask_p = 0;
461             _lv_memset_ff(mask_buf, mask_buf_size);
462         }
463     }
464 
465     /*Flush the last part*/
466     if(fill_area.y1 != fill_area.y2) {
467         fill_area.y2--;
468         _lv_blend_fill(&fill_area, clip,
469                        dsc->color, mask_buf, LV_DRAW_MASK_RES_CHANGED, dsc->opa,
470                        dsc->blend_mode);
471 
472     }
473 
474     _lv_mem_buf_release(mask_buf);
475 
476     lv_draw_mask_remove_id(mask_left_id);
477     lv_draw_mask_remove_id(mask_right_id);
478     lv_draw_mask_remove_id(mask_top_id);
479     lv_draw_mask_remove_id(mask_bottom_id);
480 }
481