lottie/vector: refactor VDasher.
[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     mIndex = 0;
66     mCurrentLength = 0;
67     mDiscard = false;
68 }
69
70 void VDasher::moveTo(const VPointF &p)
71 {
72     mDiscard = false;
73     mStartNewSegment = true;
74     mCurPt = p;
75
76     if (!vCompare(mDashOffset, 0.0f)) {
77         float totalLength = 0.0;
78         for (int i = 0; i < mArraySize; i++) {
79             totalLength = mDashArray[i].length + mDashArray[i].gap;
80         }
81         float normalizeLen = fmod(mDashOffset, totalLength);
82         if (normalizeLen < 0.0) {
83             normalizeLen = totalLength + normalizeLen;
84         }
85         // now the length is less than total length and +ve
86         // findout the current dash index , dashlength and gap.
87         for (int i = 0; i < mArraySize; i++) {
88             if (normalizeLen < mDashArray[i].length) {
89                 mIndex = i;
90                 mCurrentLength = mDashArray[i].length - normalizeLen;
91                 mDiscard = false;
92                 break;
93             }
94             normalizeLen -= mDashArray[i].length;
95             if (normalizeLen < mDashArray[i].gap) {
96                 mIndex = i;
97                 mCurrentLength = mDashArray[i].gap - normalizeLen;
98                 mDiscard = true;
99                 break;
100             }
101             normalizeLen -= mDashArray[i].gap;
102         }
103     } else {
104         mCurrentLength = mDashArray[mIndex].length;
105     }
106 }
107
108 void VDasher::addLine(const VPointF &p)
109 {
110    if (mDiscard) return;
111
112    if (mStartNewSegment) {
113         mResult.moveTo(mCurPt);
114         mStartNewSegment = false;
115    }
116    mResult.lineTo(p);
117 }
118
119 void VDasher::updateActiveSegment()
120 {
121     mStartNewSegment = true;
122
123     if (mDiscard) {
124         mDiscard = false;
125         mIndex = (mIndex + 1) % mArraySize;
126         mCurrentLength = mDashArray[mIndex].length;
127     } else {
128         mDiscard = true;
129         mCurrentLength = mDashArray[mIndex].gap;
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
139     if (length <= mCurrentLength) {
140         mCurrentLength -= length;
141         addLine(p);
142     } else {
143         while (length > mCurrentLength) {
144             length -= mCurrentLength;
145             line.splitAtLength(mCurrentLength, left, right);
146
147             addLine(left.p2());
148             updateActiveSegment();
149
150             line = right;
151             mCurPt = line.p1();
152         }
153         // handle remainder
154         if (length > 1.0) {
155             mCurrentLength -= length;
156             addLine(line.p2());
157         }
158     }
159
160     if (mCurrentLength < 1.0) updateActiveSegment();
161
162     mCurPt = p;
163 }
164
165 void VDasher::addCubic(const VPointF &cp1, const VPointF &cp2, const VPointF &e)
166 {
167     if (mDiscard) return;
168
169     if (mStartNewSegment) {
170         mResult.moveTo(mCurPt);
171         mStartNewSegment = false;
172     }
173     mResult.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
183     if (bezLen <= mCurrentLength) {
184         mCurrentLength -= bezLen;
185         addCubic(cp1, cp2, e);
186     } else {
187         while (bezLen > mCurrentLength) {
188             bezLen -= mCurrentLength;
189             b.splitAtLength(mCurrentLength, &left, &right);
190
191             addCubic(left.pt2(), left.pt3(), left.pt4());
192             updateActiveSegment();
193
194             b = right;
195             mCurPt = b.pt1();
196         }
197         // handle remainder
198         if (bezLen > 1.0) {
199             mCurrentLength -= bezLen;
200             addCubic(b.pt2(), b.pt3(), b.pt4());
201         }
202     }
203
204     if (mCurrentLength < 1.0) updateActiveSegment();
205
206     mCurPt = e;
207 }
208
209 VPath VDasher::dashed(const VPath &path)
210 {
211     if (path.isEmpty()) return VPath();
212
213     mResult = VPath();
214     mIndex = 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 std::move(mResult);
244 }
245
246 V_END_NAMESPACE