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