Partially support new spec keyframes parsing.

This commit is contained in:
John Preston 2019-04-30 11:29:08 +04:00
parent 33b3fa68f0
commit fbfd3ddd68
3 changed files with 100 additions and 31 deletions

@ -1 +1 @@
Subproject commit 99604e470f4ebebcb817e6ee5374b34d6b0dd8e4 Subproject commit 5cf5976b1874cb39aabcedf30b2b26b5dbd407a6

View File

@ -56,9 +56,15 @@
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
enum class EasingSegmentState : char {
Complete,
Incomplete,
Final,
};
template<typename T> template<typename T>
struct EasingSegment { struct EasingSegment {
bool complete = false; EasingSegmentState state = EasingSegmentState::Incomplete;
double startFrame = 0; double startFrame = 0;
double endFrame = 0; double endFrame = 0;
T startValue; T startValue;
@ -83,12 +89,32 @@ public:
if (m_animated) { if (m_animated) {
QJsonArray keyframes = definition.value(QLatin1String("k")).toArray(); QJsonArray keyframes = definition.value(QLatin1String("k")).toArray();
QJsonArray::const_iterator it = keyframes.constBegin(); QJsonArray::const_iterator it = keyframes.constBegin();
QJsonArray::const_iterator previous;
while (it != keyframes.constEnd()) { while (it != keyframes.constEnd()) {
EasingSegment<T> easing = parseKeyframe((*it).toObject(), QJsonObject keyframe = (*it).toObject();
EasingSegment<T> easing = parseKeyframe(keyframe,
fromExpression); fromExpression);
addEasing(easing); addEasing(easing);
if (m_easingCurves.length() > 1) {
postprocessEasingCurve(
m_easingCurves.at(m_easingCurves.length() - 2),
(*previous).toObject(),
fromExpression);
}
previous = it;
++it; ++it;
} }
finalizeEasingCurves();
if (m_easingCurves.length() > 0) {
const EasingSegment<T> &last = m_easingCurves.last();
if (last.state == EasingSegmentState::Complete) {
postprocessEasingCurve(
last,
(*previous).toObject(),
fromExpression);
}
}
m_value = T(); m_value = T();
} else } else
m_value = getValue(definition.value(QLatin1String("k"))); m_value = getValue(definition.value(QLatin1String("k")));
@ -129,16 +155,32 @@ protected:
void addEasing(EasingSegment<T>& easing) void addEasing(EasingSegment<T>& easing)
{ {
if (m_easingCurves.length()) { if (m_easingCurves.length()) {
EasingSegment<T> prevEase = m_easingCurves.last(); EasingSegment<T> &prevEase = m_easingCurves.last();
// The end value has to be hand picked to the // The end value has to be hand picked to the
// previous easing segment, as the json data does // previous easing segment, as the json data does
// not contain end values for segments // not contain end values for segments
prevEase.endFrame = easing.startFrame - 1; prevEase.endFrame = easing.startFrame - 1;
m_easingCurves.replace(m_easingCurves.length() - 1, prevEase); if (prevEase.state == EasingSegmentState::Incomplete) {
prevEase.endValue = easing.startValue;
prevEase.state = EasingSegmentState::Complete;
}
} }
m_easingCurves.push_back(easing); m_easingCurves.push_back(easing);
} }
void finalizeEasingCurves()
{
if (m_easingCurves.length()) {
EasingSegment<T> &last = m_easingCurves.last();
if (last.state == EasingSegmentState::Incomplete) {
last.endValue = last.startValue;
last.endFrame = last.startFrame;
this->m_endFrame = last.startFrame;
last.state = EasingSegmentState::Final;
}
}
}
const EasingSegment<T>* getEasingSegment(int frame) const EasingSegment<T>* getEasingSegment(int frame)
{ {
// TODO: Improve with a faster search algorithm // TODO: Improve with a faster search algorithm
@ -178,9 +220,16 @@ protected:
this->m_endFrame = startTime; this->m_endFrame = startTime;
easing.startFrame = startTime; easing.startFrame = startTime;
easing.endFrame = startTime; easing.endFrame = startTime;
easing.state = EasingSegmentState::Final;
if (m_easingCurves.length()) { if (m_easingCurves.length()) {
easing.startValue = m_easingCurves.last().endValue; const EasingSegment<T> &last = m_easingCurves.last();
easing.endValue = m_easingCurves.last().endValue; if (last.state == EasingSegmentState::Complete) {
easing.startValue = last.endValue;
easing.endValue = last.endValue;
} else {
qCWarning(lcLottieQtBodymovinParser())
<< "Last keyframe found after an incomplete one";
}
} }
return easing; return easing;
} }
@ -188,9 +237,12 @@ protected:
if (m_startFrame > startTime) if (m_startFrame > startTime)
m_startFrame = startTime; m_startFrame = startTime;
easing.startValue = getValue(keyframe.value(QLatin1String("s")).toArray());
easing.endValue = getValue(keyframe.value(QLatin1String("e")).toArray());
easing.startFrame = startTime; easing.startFrame = startTime;
easing.startValue = getValue(keyframe.value(QLatin1String("s")).toArray());
if (keyframe.contains(QLatin1String("e"))) {
easing.endValue = getValue(keyframe.value(QLatin1String("e")).toArray());
easing.state = EasingSegmentState::Complete;
}
QJsonObject easingIn = keyframe.value(QLatin1String("i")).toObject(); QJsonObject easingIn = keyframe.value(QLatin1String("i")).toObject();
QJsonObject easingOut = keyframe.value(QLatin1String("o")).toObject(); QJsonObject easingOut = keyframe.value(QLatin1String("o")).toObject();
@ -206,11 +258,15 @@ protected:
easing.easing.addCubicBezierSegment(c1, c2, QPointF(1.0, 1.0)); easing.easing.addCubicBezierSegment(c1, c2, QPointF(1.0, 1.0));
easing.complete = true;
return easing; return easing;
} }
virtual void postprocessEasingCurve(
const EasingSegment<T> &easing,
const QJsonObject keyframe,
bool fromExpression) {
}
virtual T getValue(const QJsonValue &value) virtual T getValue(const QJsonValue &value)
{ {
if (value.isArray()) if (value.isArray())
@ -278,9 +334,16 @@ protected:
this->m_endFrame = startTime; this->m_endFrame = startTime;
easingCurve.startFrame = startTime; easingCurve.startFrame = startTime;
easingCurve.endFrame = startTime; easingCurve.endFrame = startTime;
easingCurve.state = EasingSegmentState::Final;
if (this->m_easingCurves.length()) { if (this->m_easingCurves.length()) {
easingCurve.startValue = this->m_easingCurves.last().endValue; const EasingSegment<T> &last = this->m_easingCurves.last();
easingCurve.endValue = this->m_easingCurves.last().endValue; if (last.state == EasingSegmentState::Complete) {
easingCurve.startValue = last.endValue;
easingCurve.endValue = last.endValue;
} else {
qCWarning(lcLottieQtBodymovinParser())
<< "Last keyframe found after an incomplete one";
}
} }
return easingCurve; return easingCurve;
} }
@ -288,29 +351,38 @@ protected:
if (this->m_startFrame > startTime) if (this->m_startFrame > startTime)
this->m_startFrame = startTime; this->m_startFrame = startTime;
qreal xs, ys, xe, ye; qreal xs, ys;
// Keyframes originating from an expression use only scalar values. // Keyframes originating from an expression use only scalar values.
// They must be expanded for both x and y coordinates // They must be expanded for both x and y coordinates
if (fromExpression) { if (fromExpression) {
xs = startValues.at(0).toDouble(); xs = startValues.at(0).toDouble();
ys = startValues.at(0).toDouble(); ys = startValues.at(0).toDouble();
xe = endValues.at(0).toDouble();
ye = endValues.at(0).toDouble();
} else { } else {
xs = startValues.at(0).toDouble(); xs = startValues.at(0).toDouble();
ys = startValues.at(1).toDouble(); ys = startValues.at(1).toDouble();
xe = endValues.at(0).toDouble();
ye = endValues.at(1).toDouble();
} }
T s(xs, ys); T s(xs, ys);
T e(xe, ye);
QJsonObject easingIn = keyframe.value(QLatin1String("i")).toObject(); QJsonObject easingIn = keyframe.value(QLatin1String("i")).toObject();
QJsonObject easingOut = keyframe.value(QLatin1String("o")).toObject(); QJsonObject easingOut = keyframe.value(QLatin1String("o")).toObject();
easingCurve.startFrame = startTime; easingCurve.startFrame = startTime;
easingCurve.startValue = s; easingCurve.startValue = s;
easingCurve.endValue = e; if (!endValues.isEmpty()) {
qreal xe, ye;
// Keyframes originating from an expression use only scalar values.
// They must be expanded for both x and y coordinates
if (fromExpression) {
xe = endValues.at(0).toDouble();
ye = endValues.at(0).toDouble();
} else {
xe = endValues.at(0).toDouble();
ye = endValues.at(1).toDouble();
}
T e(xe, ye);
easingCurve.endValue = e;
easingCurve.state = EasingSegmentState::Complete;
}
if (easingIn.value(QLatin1String("x")).isArray()) { if (easingIn.value(QLatin1String("x")).isArray()) {
QJsonArray eixArr = easingIn.value(QLatin1String("x")).toArray(); QJsonArray eixArr = easingIn.value(QLatin1String("x")).toArray();
@ -323,7 +395,7 @@ protected:
qreal eix = eixArr.takeAt(0).toDouble(); qreal eix = eixArr.takeAt(0).toDouble();
qreal eiy = eiyArr.takeAt(0).toDouble(); qreal eiy = eiyArr.takeAt(0).toDouble();
qreal eox =eoxArr.takeAt(0).toDouble(); qreal eox = eoxArr.takeAt(0).toDouble();
qreal eoy = eoyArr.takeAt(0).toDouble(); qreal eoy = eoyArr.takeAt(0).toDouble();
QPointF c1 = QPointF(eox, eoy); QPointF c1 = QPointF(eox, eoy);
@ -345,7 +417,6 @@ protected:
easingCurve.easing.addCubicBezierSegment(c1, c2, QPointF(1.0, 1.0)); easingCurve.easing.addCubicBezierSegment(c1, c2, QPointF(1.0, 1.0));
} }
easingCurve.complete = true;
return easingCurve; return easingCurve;
} }
}; };

View File

@ -57,13 +57,13 @@ public:
BMProperty2D<QPointF>::construct(definition); BMProperty2D<QPointF>::construct(definition);
} }
virtual EasingSegment<QPointF> parseKeyframe(const QJsonObject keyframe, bool fromExpression) override virtual void postprocessEasingCurve(
{ const EasingSegment<QPointF> &easing,
EasingSegment<QPointF> easing = BMProperty2D<QPointF>::parseKeyframe(keyframe, fromExpression); const QJsonObject keyframe,
bool fromExpression) override {
// No need to parse further incomplete keyframes (i.e. last keyframes) // No need to parse further incomplete keyframes (i.e. last keyframes)
if (!easing.complete) { if (easing.state != EasingSegmentState::Complete) {
return easing; return;
} }
qreal tix = 0, tiy = 0, tox = 0, toy = 0; qreal tix = 0, tiy = 0, tox = 0, toy = 0;
@ -101,8 +101,6 @@ public:
m_bezierPath.moveTo(s); m_bezierPath.moveTo(s);
m_bezierPath.cubicTo(c1, c2, e); m_bezierPath.cubicTo(c1, c2, e);
return easing;
} }
virtual bool update(int frame) override virtual bool update(int frame) override
@ -112,7 +110,7 @@ public:
int adjustedFrame = qBound(m_startFrame, frame, m_endFrame); int adjustedFrame = qBound(m_startFrame, frame, m_endFrame);
if (const EasingSegment<QPointF> *easing = getEasingSegment(adjustedFrame)) { if (const EasingSegment<QPointF> *easing = getEasingSegment(adjustedFrame)) {
if (easing->complete) { if (easing->state == EasingSegmentState::Complete) {
qreal progress = ((adjustedFrame - m_startFrame) * 1.0) / (m_endFrame - m_startFrame); qreal progress = ((adjustedFrame - m_startFrame) * 1.0) / (m_endFrame - m_startFrame);
qreal easedValue = easing->easing.valueForProgress(progress); qreal easedValue = easing->easing.valueForProgress(progress);
m_value = m_bezierPath.pointAtPercent(easedValue); m_value = m_bezierPath.pointAtPercent(easedValue);