1 | // Copyright (C) 2018 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only |
3 | |
4 | #include "bmfreeformshape_p.h" |
5 | |
6 | #include <QJsonObject> |
7 | |
8 | #include "bmtrimpath_p.h" |
9 | |
10 | QT_BEGIN_NAMESPACE |
11 | |
12 | BMFreeFormShape::BMFreeFormShape() = default; |
13 | |
14 | BMFreeFormShape::BMFreeFormShape(const BMFreeFormShape &other) |
15 | : BMShape(other) |
16 | { |
17 | m_vertexList = other.m_vertexList; |
18 | m_closedShape = other.m_closedShape; |
19 | m_vertexMap = other.m_vertexMap; |
20 | } |
21 | |
22 | BMFreeFormShape::BMFreeFormShape(const QJsonObject &definition, const QVersionNumber &version, |
23 | BMBase *parent) |
24 | { |
25 | setParent(parent); |
26 | construct(definition, version); |
27 | } |
28 | |
29 | BMBase *BMFreeFormShape::clone() const |
30 | { |
31 | return new BMFreeFormShape(*this); |
32 | } |
33 | |
34 | void BMFreeFormShape::construct(const QJsonObject &definition, const QVersionNumber &version) |
35 | { |
36 | BMBase::parse(definition); |
37 | m_version = version; |
38 | if (m_hidden) |
39 | return; |
40 | |
41 | qCDebug(lcLottieQtBodymovinParser) << "BMFreeFormShape::construct():" << m_name; |
42 | |
43 | m_direction = definition.value(key: QLatin1String("d" )).toVariant().toInt(); |
44 | |
45 | QJsonObject vertexObj = definition.value(key: QLatin1String("ks" )).toObject(); |
46 | if (vertexObj.value(key: QLatin1String("a" )).toInt()) |
47 | parseShapeKeyframes(keyframes&: vertexObj); |
48 | else |
49 | buildShape(keyframe: vertexObj.value(key: QLatin1String("k" )).toObject()); |
50 | } |
51 | |
52 | void BMFreeFormShape::updateProperties(int frame) |
53 | { |
54 | if (m_vertexMap.size()) { |
55 | QJsonObject keyframe = m_vertexMap.value(key: frame); |
56 | // If this frame is a keyframe, so values must be updated |
57 | if (!keyframe.isEmpty()) |
58 | buildShape(keyframe: keyframe.value(key: QLatin1String("s" )).toArray().at(i: 0).toObject()); |
59 | } else { |
60 | for (int i =0; i < m_vertexList.size(); i++) { |
61 | VertexInfo vi = m_vertexList.at(i); |
62 | vi.pos.update(frame); |
63 | vi.ci.update(frame); |
64 | vi.co.update(frame); |
65 | m_vertexList.replace(i, t: vi); |
66 | } |
67 | buildShape(frame); |
68 | } |
69 | } |
70 | |
71 | void BMFreeFormShape::render(LottieRenderer &renderer) const |
72 | { |
73 | renderer.render(shape: *this); |
74 | } |
75 | |
76 | bool BMFreeFormShape::acceptsTrim() const |
77 | { |
78 | return true; |
79 | } |
80 | |
81 | void BMFreeFormShape::parseShapeKeyframes(QJsonObject &keyframes) |
82 | { |
83 | QJsonArray vertexKeyframes = keyframes.value(key: QLatin1String("k" )).toArray(); |
84 | for (int i = 0; i < vertexKeyframes.count(); i++) { |
85 | QJsonObject keyframe = vertexKeyframes.at(i).toObject(); |
86 | if (keyframe.value(key: QLatin1String("h" )).toInt()) { |
87 | m_vertexMap.insert(key: keyframe.value(key: QLatin1String("t" )).toVariant().toInt(), value: keyframe); |
88 | } else |
89 | parseEasedVertices(keyframe, startFrame: keyframe.value(key: QLatin1String("t" )).toVariant().toInt()); |
90 | } |
91 | if (m_vertexInfos.size()) |
92 | finalizeVertices(); |
93 | } |
94 | |
95 | void BMFreeFormShape::buildShape(const QJsonObject &shape) |
96 | { |
97 | bool needToClose = shape.value(key: QLatin1String("c" )).toBool(); |
98 | QJsonArray bezierIn = shape.value(key: QLatin1String("i" )).toArray(); |
99 | QJsonArray bezierOut = shape.value(key: QLatin1String("o" )).toArray(); |
100 | QJsonArray vertices = shape.value(key: QLatin1String("v" )).toArray(); |
101 | |
102 | // If there are less than two vertices, cannot make a bezier curve |
103 | if (vertices.count() < 2) |
104 | return; |
105 | |
106 | QPointF s(vertices.at(i: 0).toArray().at(i: 0).toDouble(), |
107 | vertices.at(i: 0).toArray().at(i: 1).toDouble()); |
108 | QPointF s0(s); |
109 | |
110 | m_path.moveTo(p: s); |
111 | int i=0; |
112 | |
113 | while (i < vertices.count() - 1) { |
114 | QPointF v = QPointF(vertices.at(i: i + 1).toArray().at(i: 0).toDouble(), |
115 | vertices.at(i: i + 1).toArray().at(i: 1).toDouble()); |
116 | QPointF c1 = QPointF(bezierOut.at(i).toArray().at(i: 0).toDouble(), |
117 | bezierOut.at(i).toArray().at(i: 1).toDouble()); |
118 | QPointF c2 = QPointF(bezierIn.at(i: i + 1).toArray().at(i: 0).toDouble(), |
119 | bezierIn.at(i: i + 1).toArray().at(i: 1).toDouble()); |
120 | c1 += s; |
121 | c2 += v; |
122 | |
123 | m_path.cubicTo(ctrlPt1: c1, ctrlPt2: c2, endPt: v); |
124 | |
125 | s = v; |
126 | i++; |
127 | } |
128 | |
129 | if (needToClose) { |
130 | QPointF v = s0; |
131 | QPointF c1 = QPointF(bezierOut.at(i).toArray().at(i: 0).toDouble(), |
132 | bezierOut.at(i).toArray().at(i: 1).toDouble()); |
133 | QPointF c2 = QPointF(bezierIn.at(i: 0).toArray().at(i: 0).toDouble(), |
134 | bezierIn.at(i: 0).toArray().at(i: 1).toDouble()); |
135 | c1 += s; |
136 | c2 += v; |
137 | |
138 | m_path.cubicTo(ctrlPt1: c1, ctrlPt2: c2, endPt: v); |
139 | } |
140 | |
141 | m_path.setFillRule(Qt::WindingFill); |
142 | |
143 | if (m_direction) |
144 | m_path = m_path.toReversed(); |
145 | } |
146 | |
147 | void BMFreeFormShape::buildShape(int frame) |
148 | { |
149 | if (m_closedShape.size()) { |
150 | auto it = m_closedShape.constBegin(); |
151 | bool found = false; |
152 | |
153 | if (frame <= it.key()) |
154 | found = true; |
155 | else { |
156 | while (it != m_closedShape.constEnd()) { |
157 | if (it.key() <= frame) { |
158 | found = true; |
159 | break; |
160 | } |
161 | ++it; |
162 | } |
163 | } |
164 | |
165 | bool needToClose = false; |
166 | if (found) |
167 | needToClose = (*it); |
168 | |
169 | // If there are less than two vertices, cannot make a bezier curve |
170 | if (m_vertexList.size() < 2) |
171 | return; |
172 | |
173 | QPointF s(m_vertexList.at(i: 0).pos.value()); |
174 | QPointF s0(s); |
175 | |
176 | m_path.moveTo(p: s); |
177 | int i = 0; |
178 | |
179 | while (i < m_vertexList.size() - 1) { |
180 | QPointF v = m_vertexList.at(i: i + 1).pos.value(); |
181 | QPointF c1 = m_vertexList.at(i).co.value(); |
182 | QPointF c2 = m_vertexList.at(i: i + 1).ci.value(); |
183 | c1 += s; |
184 | c2 += v; |
185 | |
186 | m_path.cubicTo(ctrlPt1: c1, ctrlPt2: c2, endPt: v); |
187 | |
188 | s = v; |
189 | i++; |
190 | } |
191 | |
192 | if (needToClose) { |
193 | QPointF v = s0; |
194 | QPointF c1 = m_vertexList.at(i).co.value(); |
195 | QPointF c2 = m_vertexList.at(i: 0).ci.value(); |
196 | c1 += s; |
197 | c2 += v; |
198 | |
199 | m_path.cubicTo(ctrlPt1: c1, ctrlPt2: c2, endPt: v); |
200 | } |
201 | |
202 | m_path.setFillRule(Qt::WindingFill); |
203 | |
204 | if (m_direction) |
205 | m_path = m_path.toReversed(); |
206 | } |
207 | } |
208 | |
209 | void BMFreeFormShape::parseEasedVertices(const QJsonObject &keyframe, int startFrame) |
210 | { |
211 | QJsonObject startValue = keyframe.value(key: QLatin1String("s" )).toArray().at(i: 0).toObject(); |
212 | QJsonObject endValue = keyframe.value(key: QLatin1String("e" )).toArray().at(i: 0).toObject(); |
213 | bool closedPathAtStart = keyframe.value(key: QLatin1String("s" )).toArray().at(i: 0).toObject().value(key: QLatin1String("c" )).toBool(); |
214 | //bool closedPathAtEnd = keyframe.value(QLatin1String("e")).toArray().at(0).toObject().value(QLatin1String("c")).toBool(); |
215 | QJsonArray startVertices = startValue.value(key: QLatin1String("v" )).toArray(); |
216 | QJsonArray startBezierIn = startValue.value(key: QLatin1String("i" )).toArray(); |
217 | QJsonArray startBezierOut = startValue.value(key: QLatin1String("o" )).toArray(); |
218 | QJsonArray endVertices = endValue.value(key: QLatin1String("v" )).toArray(); |
219 | QJsonArray endBezierIn = endValue.value(key: QLatin1String("i" )).toArray(); |
220 | QJsonArray endBezierOut = endValue.value(key: QLatin1String("o" )).toArray(); |
221 | QJsonObject easingIn = keyframe.value(key: QLatin1String("i" )).toObject(); |
222 | QJsonObject easingOut = keyframe.value(key: QLatin1String("o" )).toObject(); |
223 | |
224 | // if there are no vertices for this keyframe, they keyframe |
225 | // is the last one, and it must be processed differently |
226 | if (!startVertices.isEmpty()) { |
227 | for (int i = 0; i < startVertices.count(); i++) { |
228 | VertexBuildInfo *buildInfo = m_vertexInfos.value(key: i, defaultValue: nullptr); |
229 | if (!buildInfo) { |
230 | buildInfo = new VertexBuildInfo; |
231 | m_vertexInfos.insert(key: i, value: buildInfo); |
232 | } |
233 | QJsonObject posKf = createKeyframe(startValue: startVertices.at(i).toArray(), |
234 | endValue: endVertices.at(i).toArray(), |
235 | startFrame, easingIn, easingOut); |
236 | buildInfo->posKeyframes.push_back(t: posKf); |
237 | |
238 | QJsonObject ciKf = createKeyframe(startValue: startBezierIn.at(i).toArray(), |
239 | endValue: endBezierIn.at(i).toArray(), |
240 | startFrame, easingIn, easingOut); |
241 | buildInfo->ciKeyframes.push_back(t: ciKf); |
242 | |
243 | QJsonObject coKf = createKeyframe(startValue: startBezierOut.at(i).toArray(), |
244 | endValue: endBezierOut.at(i).toArray(), |
245 | startFrame, easingIn, easingOut); |
246 | buildInfo->coKeyframes.push_back(t: coKf); |
247 | |
248 | m_closedShape.insert(key: startFrame, value: closedPathAtStart); |
249 | } |
250 | } else { |
251 | // Last keyframe |
252 | |
253 | int vertexCount = m_vertexInfos.size(); |
254 | for (int i = 0; i < vertexCount; i++) { |
255 | VertexBuildInfo *buildInfo = m_vertexInfos.value(key: i, defaultValue: nullptr); |
256 | if (!buildInfo) { |
257 | buildInfo = new VertexBuildInfo; |
258 | m_vertexInfos.insert(key: i, value: buildInfo); |
259 | } |
260 | QJsonObject posKf; |
261 | posKf.insert(key: QLatin1String("t" ), value: startFrame); |
262 | buildInfo->posKeyframes.push_back(t: posKf); |
263 | |
264 | QJsonObject ciKf; |
265 | ciKf.insert(key: QLatin1String("t" ), value: startFrame); |
266 | buildInfo->ciKeyframes.push_back(t: ciKf); |
267 | |
268 | QJsonObject coKf; |
269 | coKf.insert(key: QLatin1String("t" ), value: startFrame); |
270 | buildInfo->coKeyframes.push_back(t: coKf); |
271 | |
272 | m_closedShape.insert(key: startFrame, value: false); |
273 | } |
274 | } |
275 | } |
276 | |
277 | void BMFreeFormShape::finalizeVertices() |
278 | { |
279 | |
280 | for (int i = 0; i < m_vertexInfos.size(); i++) { |
281 | QJsonObject posObj; |
282 | posObj.insert(key: QLatin1String("a" ), value: 1); |
283 | posObj.insert(key: QLatin1String("k" ), value: m_vertexInfos.value(key: i)->posKeyframes); |
284 | |
285 | QJsonObject ciObj; |
286 | ciObj.insert(key: QLatin1String("a" ), value: 1); |
287 | ciObj.insert(key: QLatin1String("k" ), value: m_vertexInfos.value(key: i)->ciKeyframes); |
288 | |
289 | QJsonObject coObj; |
290 | coObj.insert(key: QLatin1String("a" ), value: 1); |
291 | coObj.insert(key: QLatin1String("k" ), value: m_vertexInfos.value(key: i)->coKeyframes); |
292 | |
293 | VertexInfo vertexInfo; |
294 | vertexInfo.pos.construct(definition: posObj, version: m_version); |
295 | vertexInfo.ci.construct(definition: ciObj, version: m_version); |
296 | vertexInfo.co.construct(definition: coObj, version: m_version); |
297 | m_vertexList.push_back(t: vertexInfo); |
298 | } |
299 | qDeleteAll(c: m_vertexInfos); |
300 | } |
301 | |
302 | QJsonObject BMFreeFormShape::createKeyframe(QJsonArray startValue, QJsonArray endValue, |
303 | int startFrame, QJsonObject easingIn, |
304 | QJsonObject easingOut) |
305 | { |
306 | QJsonObject keyframe; |
307 | keyframe.insert(key: QLatin1String("t" ), value: startFrame); |
308 | keyframe.insert(key: QLatin1String("s" ), value: startValue); |
309 | keyframe.insert(key: QLatin1String("e" ), value: endValue); |
310 | keyframe.insert(key: QLatin1String("i" ), value: easingIn); |
311 | keyframe.insert(key: QLatin1String("o" ), value: easingOut); |
312 | return keyframe; |
313 | } |
314 | |
315 | QT_END_NAMESPACE |
316 | |