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 LV_ATTRIBUTE_FAST_MEM static void 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 LV_ATTRIBUTE_FAST_MEM static void 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 LV_ATTRIBUTE_FAST_MEM static void 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 LV_ATTRIBUTE_FAST_MEM void 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 
111 
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)112 LV_ATTRIBUTE_FAST_MEM static void draw_line_hor(struct _lv_draw_ctx_t * draw_ctx, const lv_draw_line_dsc_t * dsc,
113                                                 const lv_point_t * point1, const lv_point_t * point2)
114 {
115     int32_t w = dsc->width - 1;
116     int32_t w_half0 = w >> 1;
117     int32_t w_half1 = w_half0 + (w & 0x1); /*Compensate rounding error*/
118 
119     lv_area_t blend_area;
120     blend_area.x1 = LV_MIN(point1->x, point2->x);
121     blend_area.x2 = LV_MAX(point1->x, point2->x)  - 1;
122     blend_area.y1 = point1->y - w_half1;
123     blend_area.y2 = point1->y + w_half0;
124 
125     bool is_common;
126     is_common = _lv_area_intersect(&blend_area, &blend_area, draw_ctx->clip_area);
127     if(!is_common) return;
128 
129     bool dashed = dsc->dash_gap && dsc->dash_width ? true : false;
130     bool simple_mode = true;
131     if(lv_draw_mask_is_any(&blend_area)) simple_mode = false;
132     else if(dashed) simple_mode = false;
133 
134     lv_draw_sw_blend_dsc_t blend_dsc;
135     lv_memset_00(&blend_dsc, sizeof(blend_dsc));
136     blend_dsc.blend_area = &blend_area;
137     blend_dsc.color = dsc->color;
138     blend_dsc.opa = dsc->opa;
139 
140     /*If there is no mask then simply draw a rectangle*/
141     if(simple_mode) {
142         lv_draw_sw_blend(draw_ctx, &blend_dsc);
143     }
144 #if LV_DRAW_COMPLEX
145     /*If there other mask apply it*/
146     else {
147 
148         int32_t blend_area_w = lv_area_get_width(&blend_area);
149 
150         lv_coord_t y2 = blend_area.y2;
151         blend_area.y2 = blend_area.y1;
152 
153         lv_coord_t dash_start = 0;
154         if(dashed) {
155             dash_start = (blend_area.x1) % (dsc->dash_gap + dsc->dash_width);
156         }
157 
158         lv_opa_t * mask_buf = lv_mem_buf_get(blend_area_w);
159         blend_dsc.mask_buf = mask_buf;
160         blend_dsc.mask_area = &blend_area;
161         int32_t h;
162         for(h = blend_area.y1; h <= y2; h++) {
163             lv_memset_ff(mask_buf, blend_area_w);
164             blend_dsc.mask_res = lv_draw_mask_apply(mask_buf, blend_area.x1, h, blend_area_w);
165 
166             if(dashed) {
167                 if(blend_dsc.mask_res != LV_DRAW_MASK_RES_TRANSP) {
168                     lv_coord_t dash_cnt = dash_start;
169                     lv_coord_t i;
170                     for(i = 0; i < blend_area_w; i++, dash_cnt++) {
171                         if(dash_cnt <= dsc->dash_width) {
172                             int16_t diff = dsc->dash_width - dash_cnt;
173                             i += diff;
174                             dash_cnt += diff;
175                         }
176                         else if(dash_cnt >= dsc->dash_gap + dsc->dash_width) {
177                             dash_cnt = 0;
178                         }
179                         else {
180                             mask_buf[i] = 0x00;
181                         }
182                     }
183 
184                     blend_dsc.mask_res = LV_DRAW_MASK_RES_CHANGED;
185                 }
186             }
187 
188             lv_draw_sw_blend(draw_ctx, &blend_dsc);
189 
190             blend_area.y1++;
191             blend_area.y2++;
192         }
193         lv_mem_buf_release(mask_buf);
194     }
195 #endif /*LV_DRAW_COMPLEX*/
196 }
197 
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)198 LV_ATTRIBUTE_FAST_MEM static void draw_line_ver(struct _lv_draw_ctx_t * draw_ctx, const lv_draw_line_dsc_t * dsc,
199                                                 const lv_point_t * point1, const lv_point_t * point2)
200 {
201     int32_t w = dsc->width - 1;
202     int32_t w_half0 = w >> 1;
203     int32_t w_half1 = w_half0 + (w & 0x1); /*Compensate rounding error*/
204 
205     lv_area_t blend_area;
206     blend_area.x1 = point1->x - w_half1;
207     blend_area.x2 = point1->x + w_half0;
208     blend_area.y1 = LV_MIN(point1->y, point2->y);
209     blend_area.y2 = LV_MAX(point1->y, point2->y) - 1;
210 
211     bool is_common;
212     is_common = _lv_area_intersect(&blend_area, &blend_area, draw_ctx->clip_area);
213     if(!is_common) return;
214 
215     bool dashed = dsc->dash_gap && dsc->dash_width ? true : false;
216     bool simple_mode = true;
217     if(lv_draw_mask_is_any(&blend_area)) simple_mode = false;
218     else if(dashed) simple_mode = false;
219 
220     lv_draw_sw_blend_dsc_t blend_dsc;
221     lv_memset_00(&blend_dsc, sizeof(blend_dsc));
222     blend_dsc.blend_area = &blend_area;
223     blend_dsc.color = dsc->color;
224     blend_dsc.opa = dsc->opa;
225 
226     /*If there is no mask then simply draw a rectangle*/
227     if(simple_mode) {
228         lv_draw_sw_blend(draw_ctx, &blend_dsc);
229     }
230 
231 #if LV_DRAW_COMPLEX
232     /*If there other mask apply it*/
233     else {
234         int32_t draw_area_w = lv_area_get_width(&blend_area);
235 
236         lv_coord_t y2 = blend_area.y2;
237         blend_area.y2 = blend_area.y1;
238 
239         lv_opa_t * mask_buf = lv_mem_buf_get(draw_area_w);
240         blend_dsc.mask_buf = mask_buf;
241         blend_dsc.mask_area = &blend_area;
242 
243         lv_coord_t dash_start = 0;
244         if(dashed) {
245             dash_start = (blend_area.y1) % (dsc->dash_gap + dsc->dash_width);
246         }
247 
248         lv_coord_t dash_cnt = dash_start;
249 
250         int32_t h;
251         for(h = blend_area.y1; h <= y2; h++) {
252             lv_memset_ff(mask_buf, draw_area_w);
253             blend_dsc.mask_res = lv_draw_mask_apply(mask_buf, blend_area.x1, h, draw_area_w);
254 
255             if(dashed) {
256                 if(blend_dsc.mask_res != LV_DRAW_MASK_RES_TRANSP) {
257                     if(dash_cnt > dsc->dash_width) {
258                         blend_dsc.mask_res = LV_DRAW_MASK_RES_TRANSP;
259                     }
260 
261                     if(dash_cnt >= dsc->dash_gap + dsc->dash_width) {
262                         dash_cnt = 0;
263                     }
264                 }
265                 dash_cnt ++;
266             }
267 
268             lv_draw_sw_blend(draw_ctx, &blend_dsc);
269 
270             blend_area.y1++;
271             blend_area.y2++;
272         }
273         lv_mem_buf_release(mask_buf);
274     }
275 #endif /*LV_DRAW_COMPLEX*/
276 }
277 
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)278 LV_ATTRIBUTE_FAST_MEM static void draw_line_skew(struct _lv_draw_ctx_t * draw_ctx, const lv_draw_line_dsc_t * dsc,
279                                                  const lv_point_t * point1, const lv_point_t * point2)
280 {
281 #if LV_DRAW_COMPLEX
282     /*Keep the great y in p1*/
283     lv_point_t p1;
284     lv_point_t p2;
285     if(point1->y < point2->y) {
286         p1.y = point1->y;
287         p2.y = point2->y;
288         p1.x = point1->x;
289         p2.x = point2->x;
290     }
291     else {
292         p1.y = point2->y;
293         p2.y = point1->y;
294         p1.x = point2->x;
295         p2.x = point1->x;
296     }
297 
298     int32_t xdiff = p2.x - p1.x;
299     int32_t ydiff = p2.y - p1.y;
300     bool flat = LV_ABS(xdiff) > LV_ABS(ydiff) ? true : false;
301 
302     static const uint8_t wcorr[] = {
303         128, 128, 128, 129, 129, 130, 130, 131,
304         132, 133, 134, 135, 137, 138, 140, 141,
305         143, 145, 147, 149, 151, 153, 155, 158,
306         160, 162, 165, 167, 170, 173, 175, 178,
307         181,
308     };
309 
310     int32_t w = dsc->width;
311     int32_t wcorr_i = 0;
312     if(flat) wcorr_i = (LV_ABS(ydiff) << 5) / LV_ABS(xdiff);
313     else wcorr_i = (LV_ABS(xdiff) << 5) / LV_ABS(ydiff);
314 
315     w = (w * wcorr[wcorr_i] + 63) >> 7;     /*+ 63 for rounding*/
316     int32_t w_half0 = w >> 1;
317     int32_t w_half1 = w_half0 + (w & 0x1); /*Compensate rounding error*/
318 
319     lv_area_t blend_area;
320     blend_area.x1 = LV_MIN(p1.x, p2.x) - w;
321     blend_area.x2 = LV_MAX(p1.x, p2.x) + w;
322     blend_area.y1 = LV_MIN(p1.y, p2.y) - w;
323     blend_area.y2 = LV_MAX(p1.y, p2.y) + w;
324 
325     /*Get the union of `coords` and `clip`*/
326     /*`clip` is already truncated to the `draw_buf` size
327      *in 'lv_refr_area' function*/
328     bool is_common = _lv_area_intersect(&blend_area, &blend_area, draw_ctx->clip_area);
329     if(is_common == false) return;
330 
331     lv_draw_mask_line_param_t mask_left_param;
332     lv_draw_mask_line_param_t mask_right_param;
333     lv_draw_mask_line_param_t mask_top_param;
334     lv_draw_mask_line_param_t mask_bottom_param;
335 
336     if(flat) {
337         if(xdiff > 0) {
338             lv_draw_mask_line_points_init(&mask_left_param, p1.x, p1.y - w_half0, p2.x, p2.y - w_half0,
339                                           LV_DRAW_MASK_LINE_SIDE_LEFT);
340             lv_draw_mask_line_points_init(&mask_right_param, p1.x, p1.y + w_half1, p2.x, p2.y + w_half1,
341                                           LV_DRAW_MASK_LINE_SIDE_RIGHT);
342         }
343         else {
344             lv_draw_mask_line_points_init(&mask_left_param, p1.x, p1.y + w_half1, p2.x, p2.y + w_half1,
345                                           LV_DRAW_MASK_LINE_SIDE_LEFT);
346             lv_draw_mask_line_points_init(&mask_right_param, p1.x, p1.y - w_half0, p2.x, p2.y - w_half0,
347                                           LV_DRAW_MASK_LINE_SIDE_RIGHT);
348         }
349     }
350     else {
351         lv_draw_mask_line_points_init(&mask_left_param, p1.x + w_half1, p1.y, p2.x + w_half1, p2.y,
352                                       LV_DRAW_MASK_LINE_SIDE_LEFT);
353         lv_draw_mask_line_points_init(&mask_right_param, p1.x - w_half0, p1.y, p2.x - w_half0, p2.y,
354                                       LV_DRAW_MASK_LINE_SIDE_RIGHT);
355     }
356 
357     /*Use the normal vector for the endings*/
358 
359     int16_t mask_left_id = lv_draw_mask_add(&mask_left_param, NULL);
360     int16_t mask_right_id = lv_draw_mask_add(&mask_right_param, NULL);
361     int16_t mask_top_id = LV_MASK_ID_INV;
362     int16_t mask_bottom_id = LV_MASK_ID_INV;
363 
364     if(!dsc->raw_end) {
365         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);
366         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);
367         mask_top_id = lv_draw_mask_add(&mask_top_param, NULL);
368         mask_bottom_id = lv_draw_mask_add(&mask_bottom_param, NULL);
369     }
370 
371     /*The real draw area is around the line.
372      *It's easy to calculate with steep lines, but the area can be very wide with very flat lines.
373      *So deal with it only with steep lines.*/
374     int32_t draw_area_w = lv_area_get_width(&blend_area);
375 
376     /*Draw the background line by line*/
377     int32_t h;
378     uint32_t hor_res = (uint32_t)lv_disp_get_hor_res(_lv_refr_get_disp_refreshing());
379     size_t mask_buf_size = LV_MIN(lv_area_get_size(&blend_area), hor_res);
380     lv_opa_t * mask_buf = lv_mem_buf_get(mask_buf_size);
381 
382     lv_coord_t y2 = blend_area.y2;
383     blend_area.y2 = blend_area.y1;
384 
385     uint32_t mask_p = 0;
386     lv_memset_ff(mask_buf, mask_buf_size);
387 
388     lv_draw_sw_blend_dsc_t blend_dsc;
389     lv_memset_00(&blend_dsc, sizeof(blend_dsc));
390     blend_dsc.blend_area = &blend_area;
391     blend_dsc.color = dsc->color;
392     blend_dsc.opa = dsc->opa;
393     blend_dsc.mask_buf = mask_buf;
394     blend_dsc.mask_area = &blend_area;
395 
396     /*Fill the first row with 'color'*/
397     for(h = blend_area.y1; h <= y2; h++) {
398         blend_dsc.mask_res = lv_draw_mask_apply(&mask_buf[mask_p], blend_area.x1, h, draw_area_w);
399         if(blend_dsc.mask_res == LV_DRAW_MASK_RES_TRANSP) {
400             lv_memset_00(&mask_buf[mask_p], draw_area_w);
401         }
402 
403         mask_p += draw_area_w;
404         if((uint32_t) mask_p + draw_area_w < mask_buf_size) {
405             blend_area.y2 ++;
406         }
407         else {
408             blend_dsc.mask_res = LV_DRAW_MASK_RES_CHANGED;
409             lv_draw_sw_blend(draw_ctx, &blend_dsc);
410 
411             blend_area.y1 = blend_area.y2 + 1;
412             blend_area.y2 = blend_area.y1;
413             mask_p = 0;
414             lv_memset_ff(mask_buf, mask_buf_size);
415         }
416     }
417 
418     /*Flush the last part*/
419     if(blend_area.y1 != blend_area.y2) {
420         blend_area.y2--;
421         blend_dsc.mask_res = LV_DRAW_MASK_RES_CHANGED;
422         lv_draw_sw_blend(draw_ctx, &blend_dsc);
423     }
424 
425     lv_mem_buf_release(mask_buf);
426 
427     lv_draw_mask_free_param(&mask_left_param);
428     lv_draw_mask_free_param(&mask_right_param);
429     if(mask_top_id != LV_MASK_ID_INV) lv_draw_mask_free_param(&mask_top_param);
430     if(mask_bottom_id != LV_MASK_ID_INV) lv_draw_mask_free_param(&mask_bottom_param);
431     lv_draw_mask_remove_id(mask_left_id);
432     lv_draw_mask_remove_id(mask_right_id);
433     lv_draw_mask_remove_id(mask_top_id);
434     lv_draw_mask_remove_id(mask_bottom_id);
435 #else
436     LV_UNUSED(point1);
437     LV_UNUSED(point2);
438     LV_UNUSED(draw_ctx);
439     LV_UNUSED(dsc);
440     LV_LOG_WARN("Can't draw skewed line with LV_DRAW_COMPLEX == 0");
441 #endif /*LV_DRAW_COMPLEX*/
442 }
443 
444