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