blob: 6ca874b9688e9dbbdda27a393f27d422a10b82ac [file] [log] [blame]
/*
* Copyright 2020 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "modules/skottie/include/ExternalLayer.h"
#include "modules/skottie/src/SkottiePriv.h"
#include "modules/skottie/src/SkottieValue.h"
#include "modules/skottie/src/animator/Animator.h"
#include "src/utils/SkJSON.h"
#include "tests/Test.h"
#include <cmath>
using namespace skottie;
using namespace skottie::internal;
namespace {
template <typename T>
class MockProperty final : public AnimatablePropertyContainer {
public:
explicit MockProperty(const char* jprop) {
AnimationBuilder abuilder(nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr,
{100, 100}, 10, 1, 0);
skjson::DOM json_dom(jprop, strlen(jprop));
fDidBind = this->bind(abuilder, json_dom.root(), &fValue);
}
explicit operator bool() const { return fDidBind; }
const T& operator()(float t) { this->seek(t); return fValue; }
private:
void onSync() override {}
T fValue = T();
bool fDidBind;
};
} // namespace
DEF_TEST(Skottie_Keyframe, reporter) {
{
MockProperty<ScalarValue> prop(R"({})");
REPORTER_ASSERT(reporter, !prop);
}
{
MockProperty<ScalarValue> prop(R"({ "a": 1, "k": [] })");
REPORTER_ASSERT(reporter, !prop);
}
{
// New style
MockProperty<ScalarValue> prop(R"({
"a": 1,
"k": [
{ "t": 1, "s": 1 },
{ "t": 2, "s": 2 },
{ "t": 3, "s": 4 }
]
})");
REPORTER_ASSERT(reporter, prop);
REPORTER_ASSERT(reporter, !prop.isStatic());
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop( -1), 1));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop( 0), 1));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop( 1), 1));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop(1.5), 1.5f));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop( 2), 2));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop(2.5), 3));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop( 3), 4));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop( 4), 4));
}
{
// New style hold (hard stops)
MockProperty<ScalarValue> prop(R"({
"a": 1,
"k": [
{ "t": 1, "s": 1, "h": true },
{ "t": 2, "s": 2, "h": true },
{ "t": 3, "s": 4, "h": true }
]
})");
REPORTER_ASSERT(reporter, prop);
REPORTER_ASSERT(reporter, !prop.isStatic());
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop(0 ), 1));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop(1 ), 1));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop(1.5), 1));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop(std::nextafter(2.f, 0.f)), 1));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop(2 ), 2));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop(2.5), 2));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop(std::nextafter(3.f, 0.f)), 2));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop(3 ), 4));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop(4 ), 4));
}
{
// Legacy style
MockProperty<ScalarValue> prop(R"({
"a": 1,
"k": [
{ "t": 1, "s": 1, "e": 2 },
{ "t": 2, "s": 2, "e": 4 },
{ "t": 3 }
]
})");
REPORTER_ASSERT(reporter, prop);
REPORTER_ASSERT(reporter, !prop.isStatic());
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop(-1), 1));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop( 0), 1));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop( 1 ), 1));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop( 1.5), 1.5f));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop( 2 ), 2));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop( 2.5), 3));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop( 3 ), 4));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop( 4 ), 4));
}
{
// Legacy style hold (hard stops)
MockProperty<ScalarValue> prop(R"({
"a": 1,
"k": [
{ "t": 1, "s": 1, "e": 2, "h": true },
{ "t": 2, "s": 2, "e": 4, "h": true },
{ "t": 3 }
]
})");
REPORTER_ASSERT(reporter, prop);
REPORTER_ASSERT(reporter, !prop.isStatic());
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop(0 ), 1));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop(1 ), 1));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop(1.5), 1));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop(std::nextafter(2.f, 0.f)), 1));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop(2 ), 2));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop(2.5), 2));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop(std::nextafter(3.f, 0.f)), 2));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop(3 ), 4));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop(4 ), 4));
}
{
// Static scalar prop (all equal keyframes, using float kf Value)
MockProperty<ScalarValue> prop(R"({
"a": 1,
"k": [
{ "t": 1, "s": 42, "e": 42 },
{ "t": 2, "s": 42, "e": 42 },
{ "t": 3 }
]
})");
REPORTER_ASSERT(reporter, prop);
REPORTER_ASSERT(reporter, prop.isStatic());
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop(0), 42));
}
{
// Static vector prop (all equal keyframes, using uint32 kf Value)
MockProperty<Vec2Value> prop(R"({
"a": 1,
"k": [
{ "t": 1, "s": [4,2], "e": [4,2] },
{ "t": 2, "s": [4,2], "e": [4,2] },
{ "t": 3 }
]
})");
REPORTER_ASSERT(reporter, prop);
REPORTER_ASSERT(reporter, prop.isStatic());
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop(0).x, 4));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(prop(0).y, 2));
}
{
// Spatial interpolation [100,100]->[200,200], with supernormal easing:
// https://cubic-bezier.com/#.5,-0.5,.5,1.5
MockProperty<Vec2Value> prop(R"({
"a": 1,
"k": [
{ "t": 0, "s": [100,100],
"o":{"x":[0.5], "y":[-0.5]}, "i":{"x":[0.5], "y":[1.5]},
"to": [10,15], "ti": [-10,-5]
},
{ "t": 1, "s": [200,200]
}
]
})");
REPORTER_ASSERT(reporter, prop);
REPORTER_ASSERT(reporter, !prop.isStatic());
// Not linear.
REPORTER_ASSERT(reporter, !SkScalarNearlyEqual(prop(0.5f).x, 150.f));
REPORTER_ASSERT(reporter, !SkScalarNearlyEqual(prop(0.5f).y, 150.f));
// Subnormal region triggers extrapolation.
REPORTER_ASSERT(reporter, prop(0.15f).x < 100);
REPORTER_ASSERT(reporter, prop(0.15f).y < 100);
// Supernormal region triggers extrapolation.
REPORTER_ASSERT(reporter, prop(0.85f).x > 200);
REPORTER_ASSERT(reporter, prop(0.85f).y > 200);
}
{
// Coincident keyframes (t == 1)
//
// Effective interpolation intervals:
// [0 .. 1) -> [100 .. 200)
// [1 .. 2) -> [300 .. 400)
//
// When more than 2 concident keyframes are present, only the first and last one count.
MockProperty<ScalarValue> prop(R"({
"a": 1,
"k": [
{ "t": 0, "s": [100] },
{ "t": 1, "s": [200] },
{ "t": 1, "s": [1000] },
{ "t": 1, "s": [300] },
{ "t": 2, "s": [400] }
]
})");
REPORTER_ASSERT(reporter, prop);
REPORTER_ASSERT(reporter, !prop.isStatic());
REPORTER_ASSERT(reporter, prop(0.9999f) > 100);
REPORTER_ASSERT(reporter, prop(0.9999f) < 200);
REPORTER_ASSERT(reporter, prop(1) == 300);
REPORTER_ASSERT(reporter, prop(1.0001f) > 300);
REPORTER_ASSERT(reporter, prop(1.0001f) < 400);
}
}