d91bc6d682219e92ee9febfa5a441cb5f32d48dd
[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::addLine(const VPointF &p)
110 {
111    if (mIsCurrentOperationGap) return;
112
113    if (!mNewSegment) {
114         mDashedPath.moveTo(mCurPt);
115         mNewSegment = true;
116    }
117    mDashedPath.lineTo(p);
118 }
119
120 void VDasher::updateActiveSegment()
121 {
122    if (!mIsCurrentOperationGap) {
123         mIsCurrentOperationGap = true;
124         mNewSegment = false;
125         mCurrentDashLength = mDashArray[mCurrentDashIndex].gap;
126    } else {
127         mIsCurrentOperationGap = false;
128         mCurrentDashIndex = (mCurrentDashIndex + 1) % mArraySize;
129         mCurrentDashLength = mDashArray[mCurrentDashIndex].length;
130    }
131 }
132
133 void VDasher::lineTo(const VPointF &p)
134 {
135     VLine left, right;
136     VLine line(mCurPt, p);
137     float length = line.length();
138     if (length < mCurrentDashLength) {
139         mCurrentDashLength -= length;
140         addLine(p);
141     } else {
142         while (length > mCurrentDashLength) {
143             length -= mCurrentDashLength;
144             line.splitAtLength(mCurrentDashLength, left, right);
145
146             addLine(left.p2());
147             updateActiveSegment();
148
149             line = right;
150             mCurPt = line.p1();
151         }
152
153         // remainder
154         mCurrentDashLength -= length;
155         addLine(line.p2());
156
157         if (mCurrentDashLength < 1.0) {
158             // move to next dash
159             updateActiveSegment();
160         }
161     }
162     mCurPt = p;
163 }
164
165 void VDasher::addCubic(const VPointF &cp1, const VPointF &cp2, const VPointF &e)
166 {
167     if (mIsCurrentOperationGap) return;
168
169     if (!mNewSegment) {
170         mDashedPath.moveTo(mCurPt);
171         mNewSegment = true;
172     }
173     mDashedPath.cubicTo(cp1, cp2, e);
174 }
175
176 void VDasher::cubicTo(const VPointF &cp1, const VPointF &cp2, const VPointF &e)
177 {
178     VBezier left, right;
179     float   bezLen = 0.0;
180     VBezier b = VBezier::fromPoints(mCurPt, cp1, cp2, e);
181     bezLen = b.length();
182     if (bezLen < mCurrentDashLength) {
183         mCurrentDashLength -= bezLen;
184         addCubic(cp1, cp2, e);
185     } else {
186         while (bezLen > mCurrentDashLength) {
187             bezLen -= mCurrentDashLength;
188             b.splitAtLength(mCurrentDashLength, &left, &right);
189
190             addCubic(left.pt2(), left.pt3(), left.pt4());
191             updateActiveSegment();
192
193             b = right;
194             mCurPt = b.pt1();
195         }
196
197         // remainder
198         mCurrentDashLength -= bezLen;
199         addCubic(b.pt2(), b.pt3(), b.pt4());
200
201         if (mCurrentDashLength < 1.0) {
202             // move to next dash
203             updateActiveSegment();
204         }
205     }
206     mCurPt = e;
207 }
208
209 VPath VDasher::dashed(const VPath &path)
210 {
211     if (path.isEmpty()) return VPath();
212
213     mDashedPath = VPath();
214     mCurrentDashIndex = 0;
215     const std::vector<VPath::Element> &elms = path.elements();
216     const std::vector<VPointF> &       pts = path.points();
217     const VPointF *                    ptPtr = pts.data();
218
219     for (auto i : elms) {
220         switch (i) {
221         case VPath::Element::MoveTo: {
222             moveTo(*ptPtr++);
223             break;
224         }
225         case VPath::Element::LineTo: {
226             lineTo(*ptPtr++);
227             break;
228         }
229         case VPath::Element::CubicTo: {
230             cubicTo(*ptPtr, *(ptPtr + 1), *(ptPtr + 2));
231             ptPtr += 3;
232             break;
233         }
234         case VPath::Element::Close: {
235             // The point is already joined to start point in VPath
236             // no need to do anything here.
237             break;
238         }
239         default:
240             break;
241         }
242     }
243     return mDashedPath;
244 }
245
246 V_END_NAMESPACE