Revision control

Copy as Markdown

// Copyright (C) 2018 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
// Qt-Security score:significant reason:default
#include "qlottiefreeformshape_p.h"
#include <QJsonObject>
#include <QString>
#include "qlottietrimpath_p.h"
QT_BEGIN_NAMESPACE
using namespace Qt::Literals::StringLiterals;
QLottieFreeFormShape::QLottieFreeFormShape(const QLottieFreeFormShape &other)
: QLottieShape(other)
{
m_vertexList = other.m_vertexList;
m_closedShape = other.m_closedShape;
m_vertexMap = other.m_vertexMap;
}
QLottieFreeFormShape::QLottieFreeFormShape(QLottieBase *parent)
{
setParent(parent);
}
QLottieBase *QLottieFreeFormShape::clone() const
{
return new QLottieFreeFormShape(*this);
}
int QLottieFreeFormShape::parse(const QJsonObject &definition)
{
QLottieBase::parse(definition);
if (m_hidden)
return 0;
qCDebug(lcLottieQtLottieParser) << "QLottieFreeFormShape::parse():" << m_name;
m_direction = definition.value("d"_L1).toVariant().toInt();
if (!checkRequiredKeys(definition, "Shape"_L1, { "ks"_L1 }, m_name))
return -1;
QJsonObject vertexObj = definition.value("ks"_L1).toObject();
if (!checkRequiredKeys(vertexObj, "Shape"_L1, { "k"_L1 }, m_name))
return -1;
if (vertexObj.value("a"_L1).toInt())
parseShapeKeyframes(vertexObj);
else
buildShape(vertexObj.value("k"_L1).toObject());
return 0;
}
void QLottieFreeFormShape::updateProperties(int frame)
{
if (m_vertexMap.size()) {
QJsonObject keyframe = m_vertexMap.value(frame);
// If this frame is a keyframe, so values must be updated
if (!keyframe.isEmpty())
buildShape(keyframe.value("s"_L1).toArray().at(0).toObject());
} else {
for (int i =0; i < m_vertexList.size(); i++) {
VertexInfo vi = m_vertexList.at(i);
vi.pos.update(frame);
vi.ci.update(frame);
vi.co.update(frame);
m_vertexList.replace(i, vi);
}
buildShape(frame);
}
}
void QLottieFreeFormShape::render(QLottieRenderer &renderer) const
{
renderer.render(*this);
}
bool QLottieFreeFormShape::acceptsTrim() const
{
return true;
}
bool QLottieFreeFormShape::isAnimated() const
{
return !m_vertexList.isEmpty();
}
void QLottieFreeFormShape::parseShapeKeyframes(QJsonObject &keyframes)
{
QJsonArray vertexKeyframes = keyframes.value("k"_L1).toArray();
for (int i = 0; i < vertexKeyframes.count(); i++) {
QJsonObject keyframe = vertexKeyframes.at(i).toObject();
if (keyframe.value("h"_L1).toInt())
m_vertexMap.insert(keyframe.value("t"_L1).toVariant().toInt(), keyframe);
else
parseEasedVertices(keyframe, keyframe.value("t"_L1).toVariant().toInt());
}
if (m_vertexInfos.size())
finalizeVertices();
}
void QLottieFreeFormShape::addCurve(QPointF start, QPointF ctlOffset1, QPointF ctlOffset2, QPointF end)
{
QPointF c1 = start + ctlOffset1;
QPointF c2 = end + ctlOffset2;
if (isAnimated() && start == c1 && c1 == c2 && c2 == end) {
// QPP::cubicTo() skips null curves, so work around since all paths should have all elements
end += QPointF(0.001, 0.001);
}
m_path.cubicTo(c1, c2, end);
}
void QLottieFreeFormShape::buildShape(const QJsonObject &shape)
{
m_path.clear();
bool needToClose = shape.value("c"_L1).toBool();
QJsonArray bezierIn = shape.value("i"_L1).toArray();
QJsonArray bezierOut = shape.value("o"_L1).toArray();
QJsonArray vertices = shape.value("v"_L1).toArray();
// If there are less than two vertices, cannot make a bezier curve
if (vertices.count() < 2)
return;
QPointF s(vertices.at(0).toArray().at(0).toDouble(),
vertices.at(0).toArray().at(1).toDouble());
QPointF s0(s);
m_path.moveTo(s);
int i=0;
while (i < vertices.count() - 1) {
QPointF v = QPointF(vertices.at(i + 1).toArray().at(0).toDouble(),
vertices.at(i + 1).toArray().at(1).toDouble());
QPointF c1 = QPointF(bezierOut.at(i).toArray().at(0).toDouble(),
bezierOut.at(i).toArray().at(1).toDouble());
QPointF c2 = QPointF(bezierIn.at(i + 1).toArray().at(0).toDouble(),
bezierIn.at(i + 1).toArray().at(1).toDouble());
addCurve(s, c1, c2, v);
s = v;
i++;
}
if (needToClose) {
QPointF v = s0;
QPointF c1 = QPointF(bezierOut.at(i).toArray().at(0).toDouble(),
bezierOut.at(i).toArray().at(1).toDouble());
QPointF c2 = QPointF(bezierIn.at(0).toArray().at(0).toDouble(),
bezierIn.at(0).toArray().at(1).toDouble());
addCurve(s, c1, c2, v);
}
m_path.setFillRule(Qt::WindingFill);
if (hasReversedDirection())
m_path = m_path.toReversed();
}
void QLottieFreeFormShape::buildShape(int frame)
{
if (m_closedShape.size()) {
m_path.clear();
auto it = m_closedShape.constBegin();
bool found = false;
if (frame <= it.key())
found = true;
else {
while (it != m_closedShape.constEnd()) {
if (it.key() <= frame) {
found = true;
break;
}
++it;
}
}
bool needToClose = false;
if (found)
needToClose = (*it);
// If there are less than two vertices, cannot make a bezier curve
if (m_vertexList.size() < 2)
return;
QPointF s(m_vertexList.at(0).pos.value());
QPointF s0(s);
m_path.moveTo(s);
int i = 0;
while (i < m_vertexList.size() - 1) {
QPointF v = m_vertexList.at(i + 1).pos.value();
QPointF c1 = m_vertexList.at(i).co.value();
QPointF c2 = m_vertexList.at(i + 1).ci.value();
addCurve(s, c1, c2, v);
s = v;
i++;
}
if (needToClose) {
QPointF v = s0;
QPointF c1 = m_vertexList.at(i).co.value();
QPointF c2 = m_vertexList.at(0).ci.value();
addCurve(s, c1, c2, v);
}
m_path.setFillRule(Qt::WindingFill);
if (hasReversedDirection())
m_path = m_path.toReversed();
}
}
void QLottieFreeFormShape::parseEasedVertices(const QJsonObject &keyframe, int startFrame)
{
Q_UNUSED(startFrame);
QJsonObject startValue = keyframe.value("s"_L1).toArray().at(0).toObject();
QJsonObject endValue = keyframe.value("e"_L1).toArray().at(0).toObject();
bool closedPathAtStart = keyframe.value("s"_L1).toArray().at(0).toObject().value("c"_L1).toBool();
//bool closedPathAtEnd = keyframe.value("e"_L1).toArray().at(0).toObject().value("c"_L1).toBool();
QJsonArray startVertices = startValue.value("v"_L1).toArray();
QJsonArray startBezierIn = startValue.value("i"_L1).toArray();
QJsonArray startBezierOut = startValue.value("o"_L1).toArray();
QJsonArray endVertices = endValue.value("v"_L1).toArray();
QJsonArray endBezierIn = endValue.value("i"_L1).toArray();
QJsonArray endBezierOut = endValue.value("o"_L1).toArray();
QJsonObject easingIn = keyframe.value("i"_L1).toObject();
QJsonObject easingOut = keyframe.value("o"_L1).toObject();
// if there are no vertices for this keyframe, they keyframe
// is the last one, and it must be processed differently
if (!startVertices.isEmpty()) {
for (int i = 0; i < startVertices.count(); i++) {
VertexBuildInfo *buildInfo = m_vertexInfos.value(i, nullptr);
if (!buildInfo) {
buildInfo = new VertexBuildInfo;
m_vertexInfos.insert(i, buildInfo);
}
QJsonObject posKf = createKeyframe(startVertices.at(i).toArray(),
endVertices.at(i).toArray(),
startFrame, easingIn, easingOut);
buildInfo->posKeyframes.push_back(posKf);
QJsonObject ciKf = createKeyframe(startBezierIn.at(i).toArray(),
endBezierIn.at(i).toArray(),
startFrame, easingIn, easingOut);
buildInfo->ciKeyframes.push_back(ciKf);
QJsonObject coKf = createKeyframe(startBezierOut.at(i).toArray(),
endBezierOut.at(i).toArray(),
startFrame, easingIn, easingOut);
buildInfo->coKeyframes.push_back(coKf);
m_closedShape.insert(startFrame, closedPathAtStart);
}
} else {
// Last keyframe
int vertexCount = m_vertexInfos.size();
for (int i = 0; i < vertexCount; i++) {
VertexBuildInfo *buildInfo = m_vertexInfos.value(i, nullptr);
if (!buildInfo) {
buildInfo = new VertexBuildInfo;
m_vertexInfos.insert(i, buildInfo);
}
QJsonObject posKf;
posKf.insert("t"_L1, startFrame);
buildInfo->posKeyframes.push_back(posKf);
QJsonObject ciKf;
ciKf.insert("t"_L1, startFrame);
buildInfo->ciKeyframes.push_back(ciKf);
QJsonObject coKf;
coKf.insert("t"_L1, startFrame);
buildInfo->coKeyframes.push_back(coKf);
m_closedShape.insert(startFrame, false);
}
}
}
void QLottieFreeFormShape::finalizeVertices()
{
for (int i = 0; i < m_vertexInfos.size(); i++) {
QJsonObject posObj;
posObj.insert("a"_L1, 1);
posObj.insert("k"_L1, m_vertexInfos.value(i)->posKeyframes);
QJsonObject ciObj;
ciObj.insert("a"_L1, 1);
ciObj.insert("k"_L1, m_vertexInfos.value(i)->ciKeyframes);
QJsonObject coObj;
coObj.insert("a"_L1, 1);
coObj.insert("k"_L1, m_vertexInfos.value(i)->coKeyframes);
VertexInfo vertexInfo;
vertexInfo.pos.construct(posObj);
vertexInfo.ci.construct(ciObj);
vertexInfo.co.construct(coObj);
m_vertexList.push_back(vertexInfo);
}
qDeleteAll(m_vertexInfos);
}
QJsonObject QLottieFreeFormShape::createKeyframe(QJsonArray startValue, QJsonArray endValue,
int startFrame, QJsonObject easingIn,
QJsonObject easingOut)
{
QJsonObject keyframe;
keyframe.insert("t"_L1, startFrame);
keyframe.insert("s"_L1, startValue);
keyframe.insert("e"_L1, endValue);
keyframe.insert("i"_L1, easingIn);
keyframe.insert("o"_L1, easingOut);
return keyframe;
}
QT_END_NAMESPACE