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
10QT_BEGIN_NAMESPACE
11
12BMFreeFormShape::BMFreeFormShape() = default;
13
14BMFreeFormShape::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
22BMFreeFormShape::BMFreeFormShape(const QJsonObject &definition, const QVersionNumber &version,
23 BMBase *parent)
24{
25 setParent(parent);
26 construct(definition, version);
27}
28
29BMBase *BMFreeFormShape::clone() const
30{
31 return new BMFreeFormShape(*this);
32}
33
34void 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
52void 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
71void BMFreeFormShape::render(LottieRenderer &renderer) const
72{
73 renderer.render(shape: *this);
74}
75
76bool BMFreeFormShape::acceptsTrim() const
77{
78 return true;
79}
80
81void 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
95void 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
147void 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
209void 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
277void 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
302QJsonObject 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
315QT_END_NAMESPACE
316

source code of qtlottie/src/bodymovin/bmfreeformshape.cpp