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

View File

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