1 /*
2  * Copyright (c) 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 /**
17  * @file geometry_math_stroke.h
18  * @brief Defines
19  * @since 1.0
20  * @version 1.0
21  */
22 
23 #ifndef GRAPHIC_LITE_GEOERTY_MATH_STROKE_H
24 #define GRAPHIC_LITE_GEOERTY_MATH_STROKE_H
25 
26 #include "gfx_utils/diagram/common/common_math.h"
27 #include "gfx_utils/diagram/vertexprimitive/geometry_vertex_sequence.h"
28 #include "gfx_utils/graphic_math.h"
29 #include "gfx_utils/vector.h"
30 namespace OHOS {
31 /**
32  * @brief The style of the line end cap.
33  */
34 enum LineCap {
35     /** Add straight edges to each end of the line */
36     BUTT_CAP,
37     /** Add a square cap to each end of the line */
38     SQUARE_CAP,
39     /** Add a circular cap to each end of the line */
40     ROUND_CAP
41 };
42 
43 /**
44  * @brief The type of corner created when two lines intersect
45  */
46 enum LineJoin {
47     /** Create sharp corners */
48     MITER_JOIN = 0,
49     MITER_JOIN_REVERT = 1,
50     /** Create Fillets */
51     ROUND_JOIN = 2,
52     /** Create bevel */
53     BEVEL_JOIN = 3,
54     MITER_JOIN_ROUND = 4
55 };
56 class GeometryMathStroke {
57 public:
GeometryMathStroke()58     GeometryMathStroke()
59         : strokeWidth_(ALPHA_HALF),
60           strokeWidthUsingAbs_(ALPHA_HALF),
61           strokeWidthPercentDivision_(ALPHA_HALF / BUF_SIZE),
62           strokeWidthSignal_(1),
63 #if defined(GRAPHIC_ENABLE_LINECAP_FLAG) && GRAPHIC_ENABLE_LINECAP_FLAG
64           lineCap_(BUTT_CAP),
65 #endif
66 #if defined(GRAPHIC_ENABLE_LINEJOIN_FLAG) && GRAPHIC_ENABLE_LINEJOIN_FLAG
67           lineJoin_(MITER_JOIN),
68           miterLimitMeasure_(DEFAULTMITERLIMIT),
69 #endif
70           approxScaleRadio_(1.0f) {}
71 
72 #if defined(GRAPHIC_ENABLE_LINECAP_FLAG) && GRAPHIC_ENABLE_LINECAP_FLAG
73     /**
74      * @brief SetLineCap Defines the end style of the line
75      */
SetLineCap(LineCap lineCapE)76     void SetLineCap(LineCap lineCapE)
77     {
78         lineCap_ = lineCapE;
79     }
80 
GetLineCap()81     LineCap GetLineCap() const
82     {
83         return lineCap_;
84     }
85     /**
86      * @brief Calculate end style.
87      * Pay attention to 90 degree rotation at both ends of the corner.
88      */
CalcCap(Graphic::Vector<PointF> & vertexConsumer,const VertexDist & vd0,const VertexDist & vd1,float len)89     void CalcCap(Graphic::Vector<PointF>& vertexConsumer, const VertexDist& vd0, const VertexDist& vd1, float len)
90     {
91         vertexConsumer.Clear();
92         if (len == 0.0f) {
93             len += VERTEX_DIST_EPSILON;
94         }
95         float dx1;
96         float dy1;
97         if (len != 0) {
98             dx1 = (vd1.vertexYCoord - vd0.vertexYCoord) / len;
99             dy1 = (vd1.vertexXCoord - vd0.vertexXCoord) / len;
100         } else {
101             return;
102         }
103         float dx2 = 0;
104         float dy2 = 0;
105 
106         dx1 *= strokeWidth_;
107         dy1 *= strokeWidth_;
108 
109         if (lineCap_ != ROUND_CAP) {
110             if (lineCap_ == SQUARE_CAP) {
111                 dx2 = dy1 * strokeWidthSignal_;
112                 dy2 = dx1 * strokeWidthSignal_;
113             }
114             AddVertex(vertexConsumer, vd0.vertexXCoord - dx1 - dx2, vd0.vertexYCoord + dy1 - dy2);
115             AddVertex(vertexConsumer, vd0.vertexXCoord + dx1 - dx2, vd0.vertexYCoord - dy1 - dy2);
116         } else {
117             float deltaAngle = Acos(strokeWidthUsingAbs_ /
118                                         (strokeWidthUsingAbs_ +RADDALETAELPS / approxScaleRadio_)) * TWO_TIMES;
119             float angleStart;
120             int32_t nIndex;
121             int32_t divNumber = int32_t(PI / deltaAngle);
122 
123             deltaAngle = PI / (divNumber + 1);
124             AddVertex(vertexConsumer, vd0.vertexXCoord - dx1, vd0.vertexYCoord + dy1);
125             if (strokeWidthSignal_ > 0) {
126                 angleStart = FastAtan2F(dy1, -dx1);
127                 angleStart += deltaAngle;
128                 for (nIndex = 0; nIndex < divNumber; nIndex++) {
129                     AddVertex(vertexConsumer, vd0.vertexXCoord + Cos(angleStart * RADIAN_TO_ANGLE) * strokeWidth_,
130                               vd0.vertexYCoord + Sin(angleStart * RADIAN_TO_ANGLE) * strokeWidth_);
131                     angleStart += deltaAngle;
132                 }
133             } else {
134                 angleStart = FastAtan2F(-dy1, dx1);
135                 angleStart -= deltaAngle;
136                 for (nIndex = 0; nIndex < divNumber; nIndex++) {
137                     AddVertex(vertexConsumer, vd0.vertexXCoord + Cos(angleStart * RADIAN_TO_ANGLE) * strokeWidth_,
138                               vd0.vertexYCoord + Sin(angleStart * RADIAN_TO_ANGLE) * strokeWidth_);
139                     angleStart -= deltaAngle;
140                 }
141             }
142             AddVertex(vertexConsumer, vd0.vertexXCoord + dx1, vd0.vertexYCoord - dy1);
143         }
144     }
145 #endif
146 
147 #if defined(GRAPHIC_ENABLE_LINEJOIN_FLAG) && GRAPHIC_ENABLE_LINEJOIN_FLAG
148     /**
149      * @brief SetLineJoin Defines the type of corner created when two lines intersect.
150      * Pay attention to 90 degree rotation at both ends of the corner.
151      */
SetLineJoin(LineJoin lineJoinE)152     void SetLineJoin(LineJoin lineJoinE)
153     {
154         lineJoin_ = lineJoinE;
155     }
156 
157     /**
158      * @brief SetMiterLimit Sets the maximum miter length.
159      */
SetMiterLimit(float miterLimit)160     void SetMiterLimit(float miterLimit)
161     {
162         miterLimitMeasure_ = miterLimit;
163     }
164 
165     /**
166      * @brief Calculate intersections and corners.
167      * Pay attention to 90 degree rotation at both ends of the corner.
168      */
CalcJoin(Graphic::Vector<PointF> & vertexConsumer,const VertexDist & vertexDistBegin,const VertexDist & vertexDistMiddle,const VertexDist & vertexDistLast,float deltaLengthPrev,float deltaLengthLast)169     void CalcJoin(Graphic::Vector<PointF>& vertexConsumer,
170                   const VertexDist& vertexDistBegin,
171                   const VertexDist& vertexDistMiddle,
172                   const VertexDist& vertexDistLast,
173                   float deltaLengthPrev,
174                   float deltaLengthLast)
175     {
176         bool isEnable = true;
177         if (deltaLengthPrev == 0.0f) {
178             deltaLengthPrev += VERTEX_DIST_EPSILON;
179         }
180         if (deltaLengthLast == 0.0f) {
181             deltaLengthPrev += VERTEX_DIST_EPSILON;
182         }
183         float dx1 = 0;
184         float dy1 = 0;
185         float dx2 = 0;
186         float dy2 = 0;
187         if (deltaLengthPrev != 0) {
188             dx1 = strokeWidth_ * (vertexDistMiddle.vertexYCoord - vertexDistBegin.vertexYCoord) / deltaLengthPrev;
189             dy1 = strokeWidth_ * (vertexDistMiddle.vertexXCoord - vertexDistBegin.vertexXCoord) / deltaLengthPrev;
190         } else {
191             isEnable = false;
192         }
193         if (isEnable && deltaLengthLast != 0) {
194             dx2 = strokeWidth_ * (vertexDistLast.vertexYCoord - vertexDistMiddle.vertexYCoord) / deltaLengthLast;
195             dy2 = strokeWidth_ * (vertexDistLast.vertexXCoord - vertexDistMiddle.vertexXCoord) / deltaLengthLast;
196         } else {
197             isEnable = false;
198         }
199         if (!isEnable) {
200             return;
201         }
202         vertexConsumer.Clear();
203         float crossProduct =
204             CrossProduct(vertexDistBegin.vertexXCoord, vertexDistBegin.vertexYCoord, vertexDistMiddle.vertexXCoord,
205                          vertexDistMiddle.vertexYCoord, vertexDistLast.vertexXCoord, vertexDistLast.vertexYCoord);
206         if (crossProduct != 0 && (crossProduct > 0) == (strokeWidth_ > 0)) {
207             float limit =
208                 ((deltaLengthPrev < deltaLengthLast) ? deltaLengthPrev : deltaLengthLast) / strokeWidthUsingAbs_;
209             CalcMiter(vertexConsumer, vertexDistBegin, vertexDistMiddle, vertexDistLast, dx1, dy1, dx2, dy2,
210                       MITER_JOIN_REVERT, limit, 0);
211         } else {
212             float dx = (dx1 + dx2) * HALFNUM;
213             float dy = (dy1 + dy2) * HALFNUM;
214             float dbevel = Sqrt(dx * dx + dy * dy);
215             float lim = strokeWidthUsingAbs_ * miterLimitMeasure_;
216             bool isIntersection =
217                 CalcIntersection(vertexDistBegin.vertexXCoord + dx1, vertexDistBegin.vertexYCoord - dy1,
218                                  vertexDistMiddle.vertexXCoord + dx1, vertexDistMiddle.vertexYCoord - dy1,
219                                  vertexDistMiddle.vertexXCoord + dx2, vertexDistMiddle.vertexYCoord - dy2,
220                                  vertexDistLast.vertexXCoord + dx2, vertexDistLast.vertexYCoord - dy2, &dx, &dy);
221             LineJoin lineJoin = lineJoin_;
222             if (lineJoin == MITER_JOIN) {
223                 if (CalcDistance(vertexDistMiddle.vertexXCoord, vertexDistMiddle.vertexYCoord, dx, dy) > lim) {
224                     lineJoin = BEVEL_JOIN;
225                 }
226             }
227             bool isRoundOrBevel = false;
228             if (lineJoin == ROUND_JOIN || lineJoin == BEVEL_JOIN) {
229                 if (approxScaleRadio_ * (strokeWidthUsingAbs_ - dbevel) < strokeWidthPercentDivision_) {
230                     if (isIntersection) {
231                         AddVertex(vertexConsumer, dx, dy);
232                     } else {
233                         AddVertex(vertexConsumer, vertexDistMiddle.vertexXCoord + dx1,
234                                   vertexDistMiddle.vertexYCoord - dy1);
235                     }
236                     isRoundOrBevel = true;
237                 }
238             }
239             if (isRoundOrBevel) {
240                 return;
241             }
242 
243             switch (lineJoin) {
244                 case MITER_JOIN:
245                 case MITER_JOIN_REVERT:
246                 case MITER_JOIN_ROUND:
247                     CalcMiter(vertexConsumer, vertexDistBegin, vertexDistMiddle,
248                               vertexDistLast, dx1, dy1, dx2, dy2, lineJoin_, miterLimitMeasure_, dbevel);
249                     break;
250                 case ROUND_JOIN:
251                     CalcArc(vertexConsumer, vertexDistMiddle.vertexXCoord,
252                             vertexDistMiddle.vertexYCoord, dx1, -dy1, dx2, -dy2);
253                     break;
254 
255                 default:
256                     AddVertex(vertexConsumer, vertexDistMiddle.vertexXCoord + dx1,
257                               vertexDistMiddle.vertexYCoord - dy1);
258                     AddVertex(vertexConsumer, vertexDistMiddle.vertexXCoord + dx2,
259                               vertexDistMiddle.vertexYCoord - dy2);
260                     break;
261             }
262         }
263     }
264 
265     /**
266      * @brief Calculate miter length
267      */
CalcMiter(Graphic::Vector<PointF> & vertexConsumer,const VertexDist & vd0,const VertexDist & vd1,const VertexDist & vd2,float dx1,float dy1,float dx2,float dy2,LineJoin linejoin,float mlimit,float dbevel)268     void CalcMiter(Graphic::Vector<PointF>& vertexConsumer,
269                    const VertexDist& vd0,
270                    const VertexDist& vd1,
271                    const VertexDist& vd2,
272                    float dx1,
273                    float dy1,
274                    float dx2,
275                    float dy2,
276                    LineJoin linejoin,
277                    float mlimit,
278                    float dbevel)
279     {
280         float xi = vd1.vertexXCoord;
281         float yi = vd1.vertexYCoord;
282         float di = 1;
283         float lim = strokeWidthUsingAbs_ * mlimit;
284         bool miterLimitExceeded = true;
285         bool intersectionFailed = true;
286         if (CalcIntersection(vd0.vertexXCoord + dx1, vd0.vertexYCoord - dy1, vd1.vertexXCoord + dx1,
287                              vd1.vertexYCoord - dy1, vd1.vertexXCoord + dx2, vd1.vertexYCoord - dy2,
288                              vd2.vertexXCoord + dx2, vd2.vertexYCoord - dy2, &xi, &yi)) {
289             di = CalcDistance(vd1.vertexXCoord, vd1.vertexYCoord, xi, yi);
290             if (di <= lim) {
291                 AddVertex(vertexConsumer, xi, yi);
292                 miterLimitExceeded = false;
293             }
294             intersectionFailed = false;
295         } else {
296             float x2 = vd1.vertexXCoord + dx1;
297             float y2 = vd1.vertexYCoord - dy1;
298             if ((CrossProduct(vd0.vertexXCoord, vd0.vertexYCoord,
299                               vd1.vertexXCoord, vd1.vertexYCoord, x2, y2) < 0.0f) ==
300                 (CrossProduct(vd1.vertexXCoord, vd1.vertexYCoord,
301                               vd2.vertexXCoord, vd2.vertexYCoord, x2, y2) < 0.0f)) {
302                 AddVertex(vertexConsumer, vd1.vertexXCoord + dx1, vd1.vertexYCoord - dy1);
303                 miterLimitExceeded = false;
304             }
305         }
306 
307         if (miterLimitExceeded) {
308             switch (linejoin) {
309                 case MITER_JOIN_REVERT:
310                     AddVertex(vertexConsumer, vd1.vertexXCoord + dx1, vd1.vertexYCoord - dy1);
311                     AddVertex(vertexConsumer, vd1.vertexXCoord + dx2, vd1.vertexYCoord - dy2);
312                     break;
313                 case MITER_JOIN_ROUND:
314                     CalcArc(vertexConsumer, vd1.vertexXCoord, vd1.vertexYCoord, dx1, -dy1, dx2, -dy2);
315                     break;
316                 default:
317                     if (intersectionFailed) {
318                         mlimit *= strokeWidthSignal_;
319                         AddVertex(vertexConsumer, vd1.vertexXCoord + dx1 + dy1 * mlimit,
320                                   vd1.vertexYCoord - dy1 + dx1 * mlimit);
321                         AddVertex(vertexConsumer, vd1.vertexXCoord + dx2 - dy2 * mlimit,
322                                   vd1.vertexYCoord - dy2 - dx2 * mlimit);
323                     } else {
324                         float x1 = vd1.vertexXCoord + dx1;
325                         float y1 = vd1.vertexYCoord - dy1;
326                         float x2 = vd1.vertexXCoord + dx2;
327                         float y2 = vd1.vertexYCoord - dy2;
328                         di = (lim - dbevel) / (di - dbevel);
329                         AddVertex(vertexConsumer, x1 + (xi - x1) * di, y1 + (yi - y1) * di);
330                         AddVertex(vertexConsumer, x2 + (xi - x2) * di, y2 + (yi - y2) * di);
331                     }
332                     break;
333             }
334         }
335     }
CalcArc(Graphic::Vector<PointF> & vertexConsumer,float x,float y,float dx1,float dy1,float dx2,float dy2)336     void CalcArc(Graphic::Vector<PointF>& vertexConsumer, float x, float y, float dx1, float dy1, float dx2, float dy2)
337     {
338         const float limitScale = 0.125f;
339         float angleStart = FastAtan2F(dy1 * strokeWidthSignal_, dx1 * strokeWidthSignal_);
340         float angleEnd = FastAtan2F(dy2 * strokeWidthSignal_, dx2 * strokeWidthSignal_);
341         float deltaAngle = angleStart - angleEnd;
342         int32_t nIndex;
343         int32_t divNumber;
344 
345         deltaAngle = Acos(strokeWidthUsingAbs_ / (strokeWidthUsingAbs_ + limitScale / approxScaleRadio_))
346                 * TWO_TIMES;
347 
348         AddVertex(vertexConsumer, x + dx1, y + dy1);
349         if (strokeWidthSignal_ > 0) {
350             if (angleStart > angleEnd) {
351                 angleEnd += TWO_TIMES * PI;
352             }
353             divNumber = int32_t((angleEnd - angleStart) / deltaAngle);
354             deltaAngle = (angleEnd - angleStart) / (divNumber + 1);
355             angleStart += deltaAngle;
356             for (nIndex = 0; nIndex < divNumber; nIndex++) {
357                 AddVertex(vertexConsumer, x + Cos(angleStart * RADIAN_TO_ANGLE) * strokeWidth_,
358                           y + Sin(angleStart * RADIAN_TO_ANGLE) * strokeWidth_);
359                 angleStart += deltaAngle;
360             }
361         } else {
362             if (angleStart < angleEnd) {
363                 angleEnd -= TWO_TIMES * PI;
364             }
365             divNumber = int32_t((angleStart - angleEnd) / deltaAngle);
366             deltaAngle = (angleStart - angleEnd) / (divNumber + 1);
367             angleStart -= deltaAngle;
368             for (nIndex = 0; nIndex < divNumber; nIndex++) {
369                 AddVertex(vertexConsumer, x + Cos(angleStart * RADIAN_TO_ANGLE) * strokeWidth_,
370                           y + Sin(angleStart * RADIAN_TO_ANGLE) * strokeWidth_);
371                 angleStart -= deltaAngle;
372             }
373         }
374         AddVertex(vertexConsumer, x + dx2, y + dy2);
375     }
GetLineJoin()376     LineJoin GetLineJoin() const
377     {
378         return lineJoin_;
379     }
380     /**
381      * @brief GetMiterLimit Returns the maximum miter length
382      */
GetMiterLimit()383     float GetMiterLimit() const
384     {
385         return miterLimitMeasure_;
386     }
387 #endif
388 
389     /**
390      * @brief width Set area width
391      */
SetWidth(float width)392     void SetWidth(float width)
393     {
394         strokeWidth_ = width * ALPHA_HALF;
395         if (strokeWidth_ < 0) {
396             strokeWidthUsingAbs_ = -strokeWidth_;
397             strokeWidthSignal_ = -1;
398         } else {
399             strokeWidthUsingAbs_ = strokeWidth_;
400             strokeWidthSignal_ = 1;
401         }
402         strokeWidthPercentDivision_ = strokeWidth_ / BUF_SIZE;
403     }
404 
405     /**
406      * @brief Add approximation
407      */
SetApproximationScale(float approximationScale)408     void SetApproximationScale(float approximationScale)
409     {
410         approxScaleRadio_ = approximationScale;
411     }
412 
413     /**
414      * @brief width Return width
415      */
GetWidth()416     float GetWidth() const
417     {
418         return strokeWidth_ * TWO_TIMES;
419     }
420 
421     /**
422      * @brief Returns the set approximate value
423      */
GetApproximationScale()424     float GetApproximationScale() const
425     {
426         return approxScaleRadio_;
427     }
428 
429 private:
AddVertex(Graphic::Vector<PointF> & vertexConsumer,float x,float y)430     inline void AddVertex(Graphic::Vector<PointF>& vertexConsumer, float x, float y)
431     {
432         vertexConsumer.PushBack(PointF(x, y));
433     }
434 
435     float strokeWidth_;
436     float strokeWidthUsingAbs_;
437     float strokeWidthPercentDivision_;
438     int32_t strokeWidthSignal_;
439 #if defined(GRAPHIC_ENABLE_LINECAP_FLAG) && GRAPHIC_ENABLE_LINECAP_FLAG
440     LineCap lineCap_;
441 #endif
442 #if defined(GRAPHIC_ENABLE_LINEJOIN_FLAG) && GRAPHIC_ENABLE_LINEJOIN_FLAG
443     LineJoin lineJoin_;
444     float miterLimitMeasure_;
445 #endif
446     float approxScaleRadio_;
447 };
448 } // namespace OHOS
449 #endif
450