blob: fd5c46beef1a40234300361c931efe0485d3da52 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the lottie-qt module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 or (at your option) any later version
** approved by the KDE Free Qt Foundation. The licenses are as published by
** the Free Software Foundation and appearing in the file LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "bmfreeformshape_p.h"
#include <QJsonObject>
#include "bmtrimpath_p.h"
QT_BEGIN_NAMESPACE
BMFreeFormShape::BMFreeFormShape() = default;
BMFreeFormShape::BMFreeFormShape(const BMFreeFormShape &other)
: BMShape(other)
{
m_vertexList = other.m_vertexList;
m_closedShape = other.m_closedShape;
m_vertexMap = other.m_vertexMap;
}
BMFreeFormShape::BMFreeFormShape(const QJsonObject &definition, BMBase *parent)
{
setParent(parent);
construct(definition);
}
BMBase *BMFreeFormShape::clone() const
{
return new BMFreeFormShape(*this);
}
void BMFreeFormShape::construct(const QJsonObject &definition)
{
BMBase::parse(definition);
if (m_hidden)
return;
qCDebug(lcLottieQtBodymovinParser) << "BMFreeFormShape::construct():" << m_name;
m_direction = definition.value(QLatin1String("d")).toVariant().toInt();
QJsonObject vertexObj = definition.value(QLatin1String("ks")).toObject();
if (vertexObj.value(QLatin1String("a")).toInt())
parseShapeKeyframes(vertexObj);
else
buildShape(vertexObj.value(QLatin1String("k")).toObject());
}
void BMFreeFormShape::updateProperties(int frame)
{
if (m_vertexMap.count()) {
QJsonObject keyframe = m_vertexMap.value(frame);
// If this frame is a keyframe, so values must be updated
if (!keyframe.isEmpty())
buildShape(keyframe.value(QLatin1String("s")).toArray().at(0).toObject());
} else {
for (int i =0; i < m_vertexList.count(); 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 BMFreeFormShape::render(LottieRenderer &renderer) const
{
renderer.render(*this);
}
bool BMFreeFormShape::acceptsTrim() const
{
return true;
}
void BMFreeFormShape::parseShapeKeyframes(QJsonObject &keyframes)
{
QJsonArray vertexKeyframes = keyframes.value(QLatin1String("k")).toArray();
for (int i = 0; i < vertexKeyframes.count(); i++) {
QJsonObject keyframe = vertexKeyframes.at(i).toObject();
if (keyframe.value(QLatin1String("h")).toInt()) {
m_vertexMap.insert(keyframe.value(QLatin1String("t")).toVariant().toInt(), keyframe);
} else
parseEasedVertices(keyframe, keyframe.value(QLatin1String("t")).toVariant().toInt());
}
if (m_vertexInfos.count())
finalizeVertices();
}
void BMFreeFormShape::buildShape(const QJsonObject &shape)
{
bool needToClose = shape.value(QLatin1String("c")).toBool();
QJsonArray bezierIn = shape.value(QLatin1String("i")).toArray();
QJsonArray bezierOut = shape.value(QLatin1String("o")).toArray();
QJsonArray vertices = shape.value(QLatin1String("v")).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());
c1 += s;
c2 += v;
m_path.cubicTo(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());
c1 += s;
c2 += v;
m_path.cubicTo(c1, c2, v);
}
m_path.setFillRule(Qt::WindingFill);
if (m_direction)
m_path = m_path.toReversed();
}
void BMFreeFormShape::buildShape(int frame)
{
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.count() < 2)
return;
QPointF s(m_vertexList.at(0).pos.value());
QPointF s0(s);
m_path.moveTo(s);
int i=0;
while (i < m_vertexList.count() - 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();
c1 += s;
c2 += v;
m_path.cubicTo(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();
c1 += s;
c2 += v;
m_path.cubicTo(c1, c2, v);
}
m_path.setFillRule(Qt::WindingFill);
if (m_direction)
m_path = m_path.toReversed();
}
void BMFreeFormShape::parseEasedVertices(const QJsonObject &keyframe, int startFrame)
{
QJsonObject startValue = keyframe.value(QLatin1String("s")).toArray().at(0).toObject();
QJsonObject endValue = keyframe.value(QLatin1String("e")).toArray().at(0).toObject();
bool closedPathAtStart = keyframe.value(QLatin1String("s")).toArray().at(0).toObject().value(QLatin1String("c")).toBool();
//bool closedPathAtEnd = keyframe.value(QLatin1String("e")).toArray().at(0).toObject().value(QLatin1String("c")).toBool();
QJsonArray startVertices = startValue.value(QLatin1String("v")).toArray();
QJsonArray startBezierIn = startValue.value(QLatin1String("i")).toArray();
QJsonArray startBezierOut = startValue.value(QLatin1String("o")).toArray();
QJsonArray endVertices = endValue.value(QLatin1String("v")).toArray();
QJsonArray endBezierIn = endValue.value(QLatin1String("i")).toArray();
QJsonArray endBezierOut = endValue.value(QLatin1String("o")).toArray();
QJsonObject easingIn = keyframe.value(QLatin1String("i")).toObject();
QJsonObject easingOut = keyframe.value(QLatin1String("o")).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.count();
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(QLatin1String("t"), startFrame);
buildInfo->posKeyframes.push_back(posKf);
QJsonObject ciKf;
ciKf.insert(QLatin1String("t"), startFrame);
buildInfo->ciKeyframes.push_back(ciKf);
QJsonObject coKf;
coKf.insert(QLatin1String("t"), startFrame);
buildInfo->coKeyframes.push_back(coKf);
m_closedShape.insert(startFrame, false);
}
}
}
void BMFreeFormShape::finalizeVertices()
{
for (int i = 0; i < m_vertexInfos.count(); i++) {
QJsonObject posObj;
posObj.insert(QLatin1String("a"), 1);
posObj.insert(QLatin1String("k"), m_vertexInfos.value(i)->posKeyframes);
QJsonObject ciObj;
ciObj.insert(QLatin1String("a"), 1);
ciObj.insert(QLatin1String("k"), m_vertexInfos.value(i)->ciKeyframes);
QJsonObject coObj;
coObj.insert(QLatin1String("a"), 1);
coObj.insert(QLatin1String("k"), 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 BMFreeFormShape::createKeyframe(QJsonArray startValue, QJsonArray endValue,
int startFrame, QJsonObject easingIn,
QJsonObject easingOut)
{
QJsonObject keyframe;
keyframe.insert(QLatin1String("t"), startFrame);
keyframe.insert(QLatin1String("s"), startValue);
keyframe.insert(QLatin1String("e"), endValue);
keyframe.insert(QLatin1String("i"), easingIn);
keyframe.insert(QLatin1String("o"), easingOut);
return keyframe;
}
QT_END_NAMESPACE