lottie/vdasher: remove connected dash lines between pathes
[platform/core/uifw/lottie-player.git] / src / vector / vdasher.cpp
1 #include "vdasher.h"
2 #include "vbezier.h"
3
4 V_BEGIN_NAMESPACE
5
6 class VLine {
7 public:
8     VLine() : mX1(0), mY1(0), mX2(0), mY2(0) {}
9     VLine(float x1, float y1, float x2, float y2)
10         : mX1(x1), mY1(y1), mX2(x2), mY2(y2)
11     {
12     }
13     VLine(const VPointF &p1, const VPointF &p2)
14         : mX1(p1.x()), mY1(p1.y()), mX2(p2.x()), mY2(p2.y())
15     {
16     }
17     float   length() const;
18     void    splitAtLength(float length, VLine &left, VLine &right) const;
19     VPointF p1() const { return VPointF(mX1, mY1); }
20     VPointF p2() const { return VPointF(mX2, mY2); }
21
22 private:
23     float mX1;
24     float mY1;
25     float mX2;
26     float mY2;
27 };
28
29 // approximate sqrt(x*x + y*y) using alpha max plus beta min algorithm.
30 // With alpha = 1, beta = 3/8, giving results with the largest error less
31 // than 7% compared to the exact value.
32 float VLine::length() const
33 {
34     float x = mX2 - mX1;
35     float y = mY2 - mY1;
36     x = x < 0 ? -x : x;
37     y = y < 0 ? -y : y;
38     return (x > y ? x + 0.375 * y : y + 0.375 * x);
39 }
40
41 void VLine::splitAtLength(float lengthAt, VLine &left, VLine &right) const
42 {
43     float  len = length();
44     double dx = ((mX2 - mX1) / len) * lengthAt;
45     double dy = ((mY2 - mY1) / len) * lengthAt;
46
47     left.mX1 = mX1;
48     left.mY1 = mY1;
49     left.mX2 = left.mX1 + dx;
50     left.mY2 = left.mY1 + dy;
51
52     right.mX1 = left.mX2;
53     right.mY1 = left.mY2;
54     right.mX2 = mX2;
55     right.mY2 = mY2;
56 }
57
58 VDasher::VDasher(const float *dashArray, int size)
59 {
60     if (!(size % 2)) vCritical << "invalid dashArray format";
61
62     mDashArray = reinterpret_cast<const VDasher::Dash *>(dashArray);
63     mArraySize = size / 2;
64     mDashOffset = dashArray[size - 1];
65     mCurrentDashIndex = 0;
66     mCurrentDashLength = 0;
67     mIsCurrentOperationGap = false;
68 }
69
70 void VDasher::moveTo(const VPointF &p)
71 {
72     mIsCurrentOperationGap = false;
73     mNewSegment = false;
74     mStartPt = p;
75     mCurPt = p;
76
77     if (!vCompare(mDashOffset, 0.0f)) {
78         float totalLength = 0.0;
79         for (int i = 0; i < mArraySize; i++) {
80             totalLength = mDashArray[i].length + mDashArray[i].gap;
81         }
82         float normalizeLen = fmod(mDashOffset, totalLength);
83         if (normalizeLen < 0.0) {
84             normalizeLen = totalLength + normalizeLen;
85         }
86         // now the length is less than total length and +ve
87         // findout the current dash index , dashlength and gap.
88         for (int i = 0; i < mArraySize; i++) {
89             if (normalizeLen < mDashArray[i].length) {
90                 mCurrentDashIndex = i;
91                 mCurrentDashLength = mDashArray[i].length - normalizeLen;
92                 mIsCurrentOperationGap = false;
93                 break;
94             }
95             normalizeLen -= mDashArray[i].length;
96             if (normalizeLen < mDashArray[i].gap) {
97                 mCurrentDashIndex = i;
98                 mCurrentDashLength = mDashArray[i].gap - normalizeLen;
99                 mIsCurrentOperationGap = true;
100                 break;
101             }
102             normalizeLen -= mDashArray[i].gap;
103         }
104     } else {
105         mCurrentDashLength = mDashArray[mCurrentDashIndex].length;
106     }
107 }
108
109 void VDasher::lineTo(const VPointF &p)
110 {
111     VLine left, right;
112     VLine line(mCurPt, p);
113     float length = line.length();
114     if (length < mCurrentDashLength) {
115         mCurrentDashLength -= length;
116         if (!mIsCurrentOperationGap) {
117             if (!mNewSegment) {
118                 mDashedPath.moveTo(mCurPt);
119                 mNewSegment = true;
120             }
121             mDashedPath.lineTo(p);
122         }
123     } else {
124         while (length > mCurrentDashLength) {
125             length -= mCurrentDashLength;
126             line.splitAtLength(mCurrentDashLength, left, right);
127             if (!mIsCurrentOperationGap) {
128                 if (!mNewSegment) {
129                     mDashedPath.moveTo(left.p1());
130                     mNewSegment = true;
131                 }
132                 mDashedPath.lineTo(left.p2());
133                 mCurrentDashLength = mDashArray[mCurrentDashIndex].gap;
134             } else {
135                 mCurrentDashIndex = (mCurrentDashIndex + 1) % mArraySize;
136                 mCurrentDashLength = mDashArray[mCurrentDashIndex].length;
137             }
138             mIsCurrentOperationGap = !mIsCurrentOperationGap;
139             if (mIsCurrentOperationGap)
140                 mNewSegment = false;
141             line = right;
142             mCurPt = line.p1();
143         }
144         // remainder
145         mCurrentDashLength -= length;
146         if (!mIsCurrentOperationGap) {
147             mDashedPath.moveTo(line.p1());
148             mDashedPath.lineTo(line.p2());
149         }
150         if (mCurrentDashLength < 1.0) {
151             // move to next dash
152             if (!mIsCurrentOperationGap) {
153                 mIsCurrentOperationGap = true;
154                 mCurrentDashLength = mDashArray[mCurrentDashIndex].gap;
155                 mNewSegment = false;
156             } else {
157                 mIsCurrentOperationGap = false;
158                 mCurrentDashIndex = (mCurrentDashIndex + 1) % mArraySize;
159                 mCurrentDashLength = mDashArray[mCurrentDashIndex].length;
160             }
161         }
162     }
163     mCurPt = p;
164 }
165
166 void VDasher::cubicTo(const VPointF &cp1, const VPointF &cp2, const VPointF &e)
167 {
168     VBezier left, right;
169     float   bezLen = 0.0;
170     VBezier b = VBezier::fromPoints(mCurPt, cp1, cp2, e);
171     bezLen = b.length();
172     if (bezLen < mCurrentDashLength) {
173         mCurrentDashLength -= bezLen;
174         if (!mIsCurrentOperationGap) {
175             if (!mNewSegment) {
176                 mDashedPath.moveTo(mCurPt);
177                 mNewSegment = true;
178             }
179             mDashedPath.cubicTo(cp1, cp2, e);
180         }
181     } else {
182         while (bezLen > mCurrentDashLength) {
183             bezLen -= mCurrentDashLength;
184             b.splitAtLength(mCurrentDashLength, &left, &right);
185             if (!mIsCurrentOperationGap) {
186                 if (!mNewSegment) {
187                     mDashedPath.moveTo(left.pt1());
188                     mNewSegment = true;
189                 }
190                 mDashedPath.cubicTo(left.pt2(), left.pt3(), left.pt4());
191                 mCurrentDashLength = mDashArray[mCurrentDashIndex].gap;
192             } else {
193                 mCurrentDashIndex = (mCurrentDashIndex + 1) % mArraySize;
194                 mCurrentDashLength = mDashArray[mCurrentDashIndex].length;
195             }
196             mIsCurrentOperationGap = !mIsCurrentOperationGap;
197             if (mIsCurrentOperationGap)
198                 mNewSegment = false;
199             b = right;
200             mCurPt = b.pt1();
201         }
202         // remainder
203         mCurrentDashLength -= bezLen;
204         if (!mIsCurrentOperationGap) {
205             if (!mNewSegment) {
206                 mDashedPath.moveTo(b.pt1());
207                 mNewSegment = true;
208             }
209             mDashedPath.cubicTo(b.pt2(), b.pt3(), b.pt4());
210         }
211         if (mCurrentDashLength < 1.0) {
212             // move to next dash
213             if (!mIsCurrentOperationGap) {
214                 mIsCurrentOperationGap = true;
215                 mCurrentDashLength = mDashArray[mCurrentDashIndex].gap;
216                 mNewSegment = false;
217             } else {
218                 mIsCurrentOperationGap = false;
219                 mCurrentDashIndex = (mCurrentDashIndex + 1) % mArraySize;
220                 mCurrentDashLength = mDashArray[mCurrentDashIndex].length;
221             }
222         }
223     }
224     mCurPt = e;
225 }
226
227 VPath VDasher::dashed(const VPath &path)
228 {
229     if (path.isEmpty()) return VPath();
230
231     mDashedPath = VPath();
232     mCurrentDashIndex = 0;
233     const std::vector<VPath::Element> &elms = path.elements();
234     const std::vector<VPointF> &       pts = path.points();
235     const VPointF *                    ptPtr = pts.data();
236
237     for (auto i : elms) {
238         switch (i) {
239         case VPath::Element::MoveTo: {
240             moveTo(*ptPtr++);
241             break;
242         }
243         case VPath::Element::LineTo: {
244             lineTo(*ptPtr++);
245             break;
246         }
247         case VPath::Element::CubicTo: {
248             cubicTo(*ptPtr, *(ptPtr + 1), *(ptPtr + 2));
249             ptPtr += 3;
250             break;
251         }
252         case VPath::Element::Close: {
253             // The point is already joined to start point in VPath
254             // no need to do anything here.
255             break;
256         }
257         default:
258             break;
259         }
260     }
261     return mDashedPath;
262 }
263
264 V_END_NAMESPACE