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