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