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

source code of qtlottie/src/lottie/qlottiefreeformshape.cpp