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