1/*
2 * Copyright (c) 2020-2022 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16#include "draw/draw_line.h"
17#include "draw/draw_utils.h"
18#include "gfx_utils/graphic_math.h"
19
20namespace OHOS {
21#define INCREASE_ACC(acc, accTemp, adj, step, dir)           \
22    do {                                                     \
23        (accTemp) = (acc);                                   \
24        (acc) += (adj);                                      \
25        if ((acc) <= (accTemp)) {                            \
26            (step) += (dir);                                 \
27        }                                                    \
28    } while (0)
29
30#define SWAP_START_END(sx, sy, ex, ey, dx, dy, dir)          \
31    do {                                                     \
32        if ((dy) >= (dx)) {                                  \
33            if ((sy) > (ey)) {                               \
34                SWAP_POINTS((sx), (ex), (sy), (ey));         \
35            }                                                \
36            if ((ex) < (sx)) {                               \
37                (dir) = -1;                                  \
38            }                                                \
39        } else {                                             \
40            if ((sx) < (ex)) {                               \
41                SWAP_POINTS((sx), (ex), (sy), (ey));         \
42            }                                                \
43            if ((ey) < (sy)) {                               \
44                (dir) = -1;                                  \
45            }                                                \
46        }                                                    \
47    } while (0)
48
49#define SWAP_IF_Y_LARGER(x1, x2, y1, y2)                     \
50    if ((y1) > (y2)) {                                       \
51        SWAP_POINTS((x1), (x2), (y1), (y2));                 \
52    }
53
54#define SWAP_IF_X_SMALLER(x1, x2, y1, y2)                    \
55    if ((x1) < (x2)) {                                       \
56        SWAP_POINTS((x1), (x2), (y1), (y2));                 \
57    }
58
59void DrawLine::Draw(BufferInfo& gfxDstBuffer,
60                    const Point& start,
61                    const Point& end,
62                    const Rect& mask,
63                    int16_t width,
64                    const ColorType& color,
65                    OpacityType opacity)
66{
67    if ((width == 0) || (opacity == OPA_TRANSPARENT)) {
68        return;
69    }
70
71    int16_t yTop;
72    int16_t yBottom;
73
74    if (start.y < end.y) {
75        yTop = start.y - width / 2;  // 2: half
76        yBottom = end.y + width / 2; // 2: half
77    } else {
78        yTop = end.y - width / 2;      // 2: half
79        yBottom = start.y + width / 2; // 2: half
80    }
81
82    if ((yBottom < mask.GetTop()) || (yTop > mask.GetBottom())) {
83        return;
84    }
85
86    if (start.y == end.y) {
87        DrawHorizontalLine(gfxDstBuffer, start, end, mask, width, color, opacity);
88    } else if (start.x == end.x) {
89        DrawVerticalLine(gfxDstBuffer, start, end, mask, width, color, opacity);
90    } else {
91        DrawWuLine(gfxDstBuffer, start, end, mask, width, color, opacity);
92    }
93}
94
95void DrawLine::DrawVerticalLine(BufferInfo& gfxDstBuffer,
96                                const Point& start,
97                                const Point& end,
98                                const Rect& mask,
99                                int16_t width,
100                                const ColorType& color,
101                                OpacityType opacity)
102{
103    Rect rect;
104
105    if (start.y < end.y) {
106        rect.SetX(start.x - width / 2); // 2: half
107        rect.SetY(start.y);
108        rect.SetWidth(width);
109        rect.SetHeight(end.y - start.y + 1);
110    } else {
111        rect.SetX(end.x - width / 2); // 2: half
112        rect.SetY(end.y);
113        rect.SetWidth(width);
114        rect.SetHeight(start.y - end.y + 1);
115    }
116
117    DrawUtils::GetInstance()->DrawColorArea(gfxDstBuffer, rect, mask, color, opacity);
118}
119
120void DrawLine::DrawHorizontalLine(BufferInfo& gfxDstBuffer, const Point& start,
121                                  const Point& end,
122                                  const Rect& mask,
123                                  int16_t width,
124                                  const ColorType& color,
125                                  OpacityType opacity)
126{
127    Rect rect;
128
129    if (start.x < end.x) {
130        rect.SetX(start.x);
131        rect.SetY(start.y - width / 2); // 2: half
132        rect.SetWidth(end.x - start.x + 1);
133        rect.SetHeight(width);
134    } else {
135        rect.SetX(end.x);
136        rect.SetY(end.y - width / 2); // 2: half
137        rect.SetWidth(start.x - end.x + 1);
138        rect.SetHeight(width);
139    }
140
141    DrawUtils::GetInstance()->DrawColorArea(gfxDstBuffer, rect, mask, color, opacity);
142}
143
144void DrawLine::DrawWuLine(BufferInfo& gfxDstBuffer, const Point& start, const Point& end,
145    const Rect& mask, int16_t width, const ColorType& color, OpacityType opacity)
146{
147    if (width <= 2) { // 2 : thin line width
148        DrawThinWuLine(gfxDstBuffer, start, end, mask, width, color, opacity);
149        return;
150    }
151
152    int16_t sx = start.x;
153    int16_t sy = start.y;
154    int16_t ex = end.x;
155    int16_t ey = end.y;
156    uint16_t dx = MATH_ABS(ex - sx);
157    uint16_t dy = MATH_ABS(ey - sy);
158    int8_t dir = 1;
159    SWAP_START_END(sx, sy, ex, ey, dx, dy, dir);
160
161    // calculate four vertex ordered according to dy and dx
162    float plot = -static_cast<float>(ex - sx) / static_cast<float>(ey - sy);
163    float offset = 1 / (1 + plot * plot);
164    offset = Sqrt(offset) * width / 2; // 2: half
165    float x0 = sx + offset;
166    float y0 = sy + (x0 - sx) * plot;
167    float x1 = sx - offset;
168    float y1 = sy + (x1 - sx) * plot;
169    float x2 = ex + offset;
170    float y2 = ey + (x2 - ex) * plot;
171    float x3 = ex - offset;
172    float y3 = ey + (x3 - ex) * plot;
173    int16_t x0Int = MATH_ROUND(x0);
174    int16_t y0Int = MATH_ROUND(y0);
175    int16_t x1Int = MATH_ROUND(x1);
176    int16_t y1Int = MATH_ROUND(y1);
177    int16_t x2Int = MATH_ROUND(x2);
178    int16_t y2Int = MATH_ROUND(y2);
179    int16_t x3Int = MATH_ROUND(x3);
180    int16_t y3Int = MATH_ROUND(y3);
181    // width is longer than distance between start point and end point, need swap direction of line.
182    if (dx * dx + dy * dy < width * width) {
183        if ((dx == 1) && (dy == 1)) {
184            DrawThinWuLine(gfxDstBuffer, { x0Int, y0Int }, { x3Int, y3Int }, mask, 2, color, opacity); // 2 : line width
185            return;
186        }
187        dx = MATH_ABS(x0Int - x1Int);
188        dy = MATH_ABS(y0Int - y1Int);
189        if (dy == dx) {
190            dir = -dir;
191        }
192    }
193    if (dy >= dx) {
194        SWAP_IF_Y_LARGER(x0Int, x1Int, y0Int, y1Int);
195        SWAP_IF_Y_LARGER(x1Int, x2Int, y1Int, y2Int);
196        SWAP_IF_Y_LARGER(x2Int, x3Int, y2Int, y3Int);
197        SWAP_IF_Y_LARGER(x0Int, x1Int, y0Int, y1Int);
198        SWAP_IF_Y_LARGER(x1Int, x2Int, y1Int, y2Int);
199        SWAP_IF_Y_LARGER(x0Int, x1Int, y0Int, y1Int);
200        if (dir == -1) {
201            SWAP_IF_X_SMALLER(x1Int, x0Int, y1Int, y0Int);
202            SWAP_IF_X_SMALLER(x3Int, x2Int, y3Int, y2Int);
203        } else {
204            SWAP_IF_X_SMALLER(x0Int, x1Int, y0Int, y1Int);
205            SWAP_IF_X_SMALLER(x2Int, x3Int, y2Int, y3Int);
206        }
207    } else {
208        SWAP_IF_X_SMALLER(x0Int, x1Int, y0Int, y1Int);
209        SWAP_IF_X_SMALLER(x1Int, x2Int, y1Int, y2Int);
210        SWAP_IF_X_SMALLER(x2Int, x3Int, y2Int, y3Int);
211        SWAP_IF_X_SMALLER(x0Int, x1Int, y0Int, y1Int);
212        SWAP_IF_X_SMALLER(x1Int, x2Int, y1Int, y2Int);
213        SWAP_IF_X_SMALLER(x0Int, x1Int, y0Int, y1Int);
214        if (dir == 1) {
215            SWAP_IF_Y_LARGER(x1Int, x0Int, y1Int, y0Int);
216            SWAP_IF_Y_LARGER(x3Int, x2Int, y3Int, y2Int);
217        } else {
218            SWAP_IF_Y_LARGER(x0Int, x1Int, y0Int, y1Int);
219            SWAP_IF_Y_LARGER(x2Int, x3Int, y2Int, y3Int);
220        }
221    }
222
223    uint64_t adj0;
224    uint16_t accTemp0;
225    uint16_t acc0 = 0;
226    uint64_t adj1;
227    uint16_t accTemp1;
228    uint16_t acc1 = 0;
229    uint16_t accTemp2;
230    uint16_t acc2 = 0;
231
232    int16_t endPoints0[MAX_LINE_WIDTH] = { 0 };
233    int16_t endPoints1[MAX_LINE_WIDTH] = { 0 };
234    int16_t temp0 = 0;
235    int16_t temp1 = 0;
236    int16_t edge0 = 0;
237    int16_t edge1 = 0;
238    Rect rect;
239    DrawUtils* drawUtils = DrawUtils::GetInstance();
240    // sort points
241    if (dy >= dx) {
242        adj0 = static_cast<uint64_t>(dx << SHIFT_16) / static_cast<uint64_t>(dy);
243        adj1 = static_cast<uint64_t>(MATH_ABS(y1Int - y0Int) << SHIFT_16) /
244            static_cast<uint64_t>(MATH_ABS(x1Int - x0Int));
245        if (adj1 != 0) {
246            // draw top line
247            dx = MATH_ABS(x1Int - x0Int);
248            sx = x0Int;
249            sy = y0Int;
250            drawUtils->DrawPixel(gfxDstBuffer, x0Int, y0Int, mask, color, opacity);
251            while (--dx) {
252                accTemp1 = acc1;
253                acc1 += adj1;
254                if (acc1 <= accTemp1) {
255                    if (sy - y0Int < MAX_LINE_WIDTH) {
256                        endPoints0[sy - y0Int] = sx;
257                    }
258                    sy++;
259                }
260                sx -= dir;
261                drawUtils->DrawPixelInLine(gfxDstBuffer, sx, sy, mask, color, opacity,
262                    (acc1 >> SHIFT_8) ^ OPA_OPAQUE);
263            }
264            if (sy - y0Int < MAX_LINE_WIDTH) {
265                endPoints0[sy - y0Int] = sx - dir;
266            }
267
268            // draw botttom line
269            acc1 = 0;
270            dx = MATH_ABS(x3Int - x2Int);
271            sy = y3Int;
272            sx = x3Int;
273            drawUtils->DrawPixel(gfxDstBuffer, x3Int, y3Int, mask, color, opacity);
274            while (--dx) {
275                accTemp1 = acc1;
276                acc1 += adj1;
277                if (acc1 <= accTemp1) {
278                    if (temp1 < MAX_LINE_WIDTH) {
279                        endPoints1[temp1++] = sx;
280                    }
281                    sy--;
282                }
283                sx += dir;
284                drawUtils->DrawPixelInLine(gfxDstBuffer, sx, sy, mask, color, opacity,
285                                           (acc1 >> SHIFT_8) ^ OPA_OPAQUE);
286            }
287            if (temp1 < MAX_LINE_WIDTH) {
288                endPoints1[temp1++] = sx + dir;
289            }
290        } else {
291            /* If y0 is equal to y1, draw two horizontal lines as the top line and bottom line. */
292            rect.SetRect(MATH_MIN(x0Int, x1Int), y0Int, MATH_MAX(x0Int, x1Int), y1Int);
293            drawUtils->DrawColorArea(gfxDstBuffer, rect, mask, color, opacity);
294            rect.SetRect(MATH_MIN(x2Int, x3Int), y3Int, MATH_MAX(x2Int, x3Int), y2Int);
295            drawUtils->DrawColorArea(gfxDstBuffer, rect, mask, color, opacity);
296        }
297
298        sx = x0Int;
299        sy = y0Int + 1;
300        dy = MATH_ABS(y3Int - y0Int);
301        if (dy == 0) {
302            return;
303        }
304        int16_t sxTemp = x1Int;
305        while (--dy) {
306            if (sy <= y1Int) {
307                INCREASE_ACC(acc0, accTemp0, adj0, sx, dir);
308                drawUtils->DrawPixelInLine(gfxDstBuffer, sx + dir, sy, mask,
309                                           color, opacity, acc0 >> SHIFT_8);
310                if (temp0 < MAX_LINE_WIDTH) {
311                    edge0 = endPoints0[temp0++];
312                }
313                edge1 = sx;
314            } else if (sy < y2Int) {
315                INCREASE_ACC(acc0, accTemp0, adj0, sx, dir);
316                INCREASE_ACC(acc2, accTemp2, adj0, sxTemp, dir);
317                drawUtils->DrawPixelInLine(gfxDstBuffer, sx + dir, sy, mask,
318                                           color, opacity, acc0 >> SHIFT_8);
319                drawUtils->DrawPixelInLine(gfxDstBuffer, sxTemp, sy, mask, color, opacity,
320                                           (acc2 >> SHIFT_8) ^ OPA_OPAQUE);
321                edge0 = sxTemp + dir;
322                edge1 = sx;
323            } else if (sy < y3Int) {
324                INCREASE_ACC(acc2, accTemp2, adj0, sxTemp, dir);
325                drawUtils->DrawPixelInLine(gfxDstBuffer, sxTemp, sy, mask, color, opacity,
326                                           (acc2 >> SHIFT_8) ^ OPA_OPAQUE);
327                edge0 = sxTemp + dir;
328                if (temp1 > 0) {
329                    edge1 = endPoints1[--temp1];
330                }
331            }
332            if ((dir < 0) && (edge0 > edge1)) {
333                SWAP_INT16(edge0, edge1);
334            }
335            rect.SetRect(edge0, sy, edge1, sy);
336            drawUtils->DrawColorArea(gfxDstBuffer, rect, mask, color, opacity);
337            sy++;
338        }
339    } else {
340        adj0 = static_cast<uint64_t>(dy << SHIFT_16) / static_cast<uint64_t>(dx);
341        adj1 = static_cast<uint64_t>(MATH_ABS(x1Int - x0Int) << SHIFT_16) /
342            static_cast<uint64_t>(MATH_ABS(y1Int - y0Int));
343        if (adj1 != 0) {
344            // draw top line
345            dy = MATH_ABS(y1Int - y0Int);
346            sx = x0Int;
347            sy = y0Int;
348            drawUtils->DrawPixel(gfxDstBuffer, sx, sy, mask, color, opacity);
349            while (--dy) {
350                accTemp1 = acc1;
351                acc1 += adj1;
352                if (acc1 <= accTemp1) {
353                    if (x0Int - sx < MAX_LINE_WIDTH) {
354                        endPoints0[x0Int - sx] = sy;
355                    }
356                    sx--;
357                }
358                sy -= dir;
359                drawUtils->DrawPixelInLine(gfxDstBuffer, sx, sy, mask, color, opacity,
360                                           (acc1 >> SHIFT_8) ^ OPA_OPAQUE);
361            }
362            if (x0Int - sx < MAX_LINE_WIDTH) {
363                endPoints0[x0Int - sx] = sy - dir;
364            }
365
366            // draw botttom line
367            acc1 = 0;
368            dy = MATH_ABS(y3Int - y2Int);
369            sy = y3Int;
370            sx = x3Int;
371            while (--dy) {
372                accTemp1 = acc1;
373                acc1 += adj1;
374                if (acc1 <= accTemp1) {
375                    if (temp1 < MAX_LINE_WIDTH) {
376                        endPoints1[temp1++] = sy;
377                    }
378                    sx++;
379                }
380                sy += dir;
381                drawUtils->DrawPixelInLine(gfxDstBuffer, sx, sy, mask, color, opacity,
382                                           (acc1 >> SHIFT_8) ^ OPA_OPAQUE);
383            }
384            drawUtils->DrawPixel(gfxDstBuffer, x3Int, y3Int, mask, color, opacity);
385            if (temp1 < MAX_LINE_WIDTH) {
386                endPoints1[temp1++] = sy + dir;
387            }
388        } else {
389            /* If x0 is equal to x1, draw two vertical lines as the top line and bottom line. */
390            rect.SetRect(x1Int, MATH_MIN(y0Int, y1Int), x0Int, MATH_MAX(y0Int, y1Int));
391            drawUtils->DrawColorArea(gfxDstBuffer, rect, mask, color, opacity);
392            rect.SetRect(x3Int, MATH_MIN(y2Int, y3Int), x2Int, MATH_MAX(y2Int, y3Int));
393            drawUtils->DrawColorArea(gfxDstBuffer, rect, mask, color, opacity);
394        }
395
396        sx = x0Int - 1;
397        sy = y0Int;
398        dx = MATH_ABS(x3Int - x0Int);
399        int16_t syTemp = y1Int;
400        if (dx == 0) {
401            return;
402        }
403        while (--dx) {
404            if (sx >= x1Int) {
405                INCREASE_ACC(acc0, accTemp0, adj0, sy, dir);
406                drawUtils->DrawPixelInLine(gfxDstBuffer, sx, sy + dir, mask,
407                                           color, opacity, acc0 >> SHIFT_8);
408                if (temp0 < MAX_LINE_WIDTH) {
409                    edge0 = endPoints0[temp0++];
410                }
411                edge1 = sy;
412            } else if (sx > x2Int) {
413                INCREASE_ACC(acc0, accTemp0, adj0, sy, dir);
414                INCREASE_ACC(acc2, accTemp2, adj0, syTemp, dir);
415                drawUtils->DrawPixelInLine(gfxDstBuffer, sx, sy + dir, mask,
416                                           color, opacity, acc0 >> SHIFT_8);
417                drawUtils->DrawPixelInLine(gfxDstBuffer, sx, syTemp, mask, color,
418                                           opacity, (acc2 >> SHIFT_8) ^ OPA_OPAQUE);
419                edge0 = syTemp + dir;
420                edge1 = sy;
421            } else if (sx > x3Int) {
422                INCREASE_ACC(acc2, accTemp2, adj0, syTemp, dir);
423                drawUtils->DrawPixelInLine(gfxDstBuffer, sx, syTemp, mask, color, opacity,
424                                           (acc2 >> SHIFT_8) ^ OPA_OPAQUE);
425                edge0 = syTemp + dir;
426                if (temp1 > 0) {
427                    edge1 = endPoints1[--temp1];
428                }
429            }
430            if ((dir < 0) && (edge0 > edge1)) {
431                SWAP_INT16(edge0, edge1);
432            }
433            rect.SetRect(sx, edge0, sx, edge1);
434            drawUtils->DrawColorArea(gfxDstBuffer, rect, mask, color, opacity);
435            sx--;
436        }
437    }
438}
439
440void DrawLine::DrawThinWuLine(BufferInfo& gfxDstBuffer, const Point& start, const Point& end,
441    const Rect& mask, int16_t width, const ColorType& color, OpacityType opacity)
442{
443    int16_t sx = start.x;
444    int16_t sy = start.y;
445    int16_t ex = end.x;
446    int16_t ey = end.y;
447    uint16_t dx = MATH_ABS(ex - sx);
448    uint16_t dy = MATH_ABS(ey - sy);
449    uint64_t adj;
450    uint16_t accTemp;
451    uint16_t acc = 0;
452    int8_t dir = 1;
453    SWAP_START_END(sx, sy, ex, ey, dx, dy, dir);
454    DrawUtils* drawUtils = DrawUtils::GetInstance();
455    if (dy >= dx) {
456        adj = static_cast<uint64_t>(dx << SHIFT_16) / static_cast<uint64_t>(dy);
457        while (dy--) {
458            INCREASE_ACC(acc, accTemp, adj, sx, dir);
459            sy++;
460            if (width == 1) {
461                drawUtils->DrawAdjPixelInLine(gfxDstBuffer, sx, sy, sx + dir, sy, mask,
462                                              color, opacity, acc >> SHIFT_8);
463            } else {
464                drawUtils->DrawVerPixelInLine(gfxDstBuffer, sx, sy, dir, mask,
465                                              color, opacity, acc >> SHIFT_8);
466            }
467        }
468    } else {
469        adj = static_cast<uint64_t>(dy << SHIFT_16) / static_cast<uint64_t>(dx);
470        while (dx--) {
471            INCREASE_ACC(acc, accTemp, adj, sy, dir);
472            sx--;
473            if (width == 1) {
474                drawUtils->DrawAdjPixelInLine(gfxDstBuffer, sx, sy, sx, sy + dir, mask,
475                                              color, opacity, acc >> SHIFT_8);
476            } else {
477                drawUtils->DrawHorPixelInLine(gfxDstBuffer, sx, sy, dir, mask,
478                                              color, opacity, acc >> SHIFT_8);
479            }
480        }
481    }
482}
483} // namespace OHOS
484