| /**************************************************************************** |
| ** |
| ** 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 |