blob: 2df765752feac207990e6fdcafe2af80c0261c65 [file] [log] [blame]
/*
* Copyright 2023 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "include/core/SkColor.h"
#include "include/core/SkPictureRecorder.h"
#include "include/core/SkStream.h"
#include "modules/skottie/include/Skottie.h"
#include "modules/skottie/include/SkottieProperty.h"
#include "tests/Test.h"
#include "tools/ToolUtils.h"
using namespace skottie;
namespace {
TextPropertyValue make_text_prop(const char* str) {
TextPropertyValue prop;
prop.fTypeface = ToolUtils::create_portable_typeface();
prop.fText = SkString(str);
return prop;
}
class MockPropertyObserver final : public PropertyObserver {
public:
explicit MockPropertyObserver(bool override_props = false) : fOverrideProps(override_props) {}
struct ColorInfo {
SkString node_name;
std::unique_ptr<skottie::ColorPropertyHandle> handle;
};
struct OpacityInfo {
SkString node_name;
std::unique_ptr<skottie::OpacityPropertyHandle> handle;
};
struct TextInfo {
SkString node_name;
std::unique_ptr<skottie::TextPropertyHandle> handle;
};
struct TransformInfo {
SkString node_name;
std::unique_ptr<skottie::TransformPropertyHandle> handle;
};
void onColorProperty(const char node_name[],
const PropertyObserver::LazyHandle<ColorPropertyHandle>& lh) override {
auto prop_handle = lh();
if (fOverrideProps) {
static constexpr ColorPropertyValue kOverrideColor = 0xffffff00;
prop_handle->set(kOverrideColor);
}
fColors.push_back({SkString(node_name), std::move(prop_handle)});
fColorsWithFullKeypath.push_back({SkString(fCurrentNode.c_str()), lh()});
}
void onOpacityProperty(const char node_name[],
const PropertyObserver::LazyHandle<OpacityPropertyHandle>& lh) override {
auto prop_handle = lh();
if (fOverrideProps) {
static constexpr OpacityPropertyValue kOverrideOpacity = 0.75f;
prop_handle->set(kOverrideOpacity);
}
fOpacities.push_back({SkString(node_name), std::move(prop_handle)});
}
void onTextProperty(const char node_name[],
const PropertyObserver::LazyHandle<TextPropertyHandle>& lh) override {
auto prop_handle = lh();
if (fOverrideProps) {
static const TextPropertyValue kOverrideText = make_text_prop("foo");
prop_handle->set(kOverrideText);
}
fTexts.push_back({SkString(node_name), std::move(prop_handle)});
}
void onTransformProperty(const char node_name[],
const PropertyObserver::LazyHandle<TransformPropertyHandle>& lh) override {
auto prop_handle = lh();
if (fOverrideProps) {
static constexpr TransformPropertyValue kOverrideTransform = {
{ 100, 100 },
{ 200, 200 },
{ 2, 2 },
45,
0,
0,
};
prop_handle->set(kOverrideTransform);
}
fTransforms.push_back({SkString(node_name), std::move(prop_handle)});
}
void onEnterNode(const char node_name[], PropertyObserver::NodeType node_type) override {
if (node_name == nullptr) {
return;
}
fCurrentNode = fCurrentNode.empty() ? node_name : fCurrentNode + "." + node_name;
}
void onLeavingNode(const char node_name[], PropertyObserver::NodeType node_type) override {
if (node_name == nullptr) {
return;
}
auto length = strlen(node_name);
fCurrentNode =
fCurrentNode.length() > length
? fCurrentNode.substr(0, fCurrentNode.length() - strlen(node_name) - 1)
: "";
}
const std::vector<ColorInfo>& colors() const { return fColors; }
const std::vector<OpacityInfo>& opacities() const { return fOpacities; }
const std::vector<TextInfo>& texts() const { return fTexts; }
const std::vector<TransformInfo>& transforms() const { return fTransforms; }
const std::vector<ColorInfo>& colorsWithFullKeypath() const {
return fColorsWithFullKeypath;
}
private:
const bool fOverrideProps;
std::vector<ColorInfo> fColors;
std::vector<OpacityInfo> fOpacities;
std::vector<TextInfo> fTexts;
std::vector<TransformInfo> fTransforms;
std::string fCurrentNode;
std::vector<ColorInfo> fColorsWithFullKeypath;
};
// Returns a single specified typeface for all requests.
class MockFontMgr : public SkFontMgr {
public:
MockFontMgr(sk_sp<SkTypeface> test_font) : fTestFont(test_font) {}
int onCountFamilies() const override { return 1; }
void onGetFamilyName(int index, SkString* familyName) const override {}
sk_sp<SkFontStyleSet> onCreateStyleSet(int index) const override { return nullptr; }
sk_sp<SkFontStyleSet> onMatchFamily(const char familyName[]) const override {
return nullptr;
}
sk_sp<SkTypeface> onMatchFamilyStyle(const char familyName[],
const SkFontStyle& fontStyle) const override {
return nullptr;
}
sk_sp<SkTypeface> onMatchFamilyStyleCharacter(const char familyName[], const SkFontStyle&,
const char* bcp47[], int bcp47Count,
SkUnichar character) const override {
return nullptr;
}
sk_sp<SkTypeface> onMakeFromData(sk_sp<SkData>, int ttcIndex) const override {
return fTestFont;
}
sk_sp<SkTypeface> onMakeFromStreamIndex(std::unique_ptr<SkStreamAsset>,
int ttcIndex) const override {
return fTestFont;
}
sk_sp<SkTypeface> onMakeFromStreamArgs(std::unique_ptr<SkStreamAsset>,
const SkFontArguments&) const override {
return fTestFont;
}
sk_sp<SkTypeface> onMakeFromFile(const char path[], int ttcIndex) const override {
return fTestFont;
}
sk_sp<SkTypeface> onLegacyMakeTypeface(const char familyName[], SkFontStyle) const override {
return fTestFont;
}
private:
sk_sp<SkTypeface> fTestFont;
};
static const char gTestJson[] = R"({
"v": "5.2.1",
"w": 100,
"h": 100,
"fr": 1,
"ip": 0,
"op": 1,
"fonts": {
"list": [
{
"fName": "test_font",
"fFamily": "test-family",
"fStyle": "TestFontStyle"
}
]
},
"layers": [
{
"ty": 4,
"nm": "layer_0",
"ind": 0,
"ip": 0,
"op": 1,
"ks": {
"o": { "a": 0, "k": 50 }
},
"ef": [{
"ef": [
{},
{},
{ "v": { "a": 0, "k": [ 0, 1, 0 ] }},
{},
{},
{},
{ "v": { "a": 0, "k": 1 }}
],
"nm": "fill_effect_0",
"mn": "ADBE Fill",
"ty": 21
}],
"shapes": [
{
"ty": "el",
"nm": "geometry_0",
"p": { "a": 0, "k": [ 50, 50 ] },
"s": { "a": 0, "k": [ 50, 50 ] }
},
{
"ty": "fl",
"nm": "fill_0",
"c": { "a": 0, "k": [ 1, 0, 0] }
},
{
"ty": "tr",
"nm": "shape_transform_0",
"o": { "a": 0, "k": 100 },
"s": { "a": 0, "k": [ 50, 50 ] }
}
]
},
{
"ty": 5,
"nm": "layer_1",
"ip": 0,
"op": 1,
"ks": {
"p": { "a": 0, "k": [25, 25] }
},
"t": {
"d": {
"k": [
{
"t": 0,
"s": {
"f": "test_font",
"s": 100,
"t": "inline_text",
"lh": 120,
"ls": 12
}
}
]
}
}
}
]
})";
} // anonymous namespace
DEF_TEST(Skottie_Props, reporter) {
auto test_typeface = ToolUtils::create_portable_typeface();
REPORTER_ASSERT(reporter, test_typeface);
auto test_font_manager = sk_make_sp<MockFontMgr>(test_typeface);
SkMemoryStream stream(gTestJson, strlen(gTestJson));
auto observer = sk_make_sp<MockPropertyObserver>();
auto animation = skottie::Animation::Builder()
.setPropertyObserver(observer)
.setFontManager(test_font_manager)
.make(&stream);
REPORTER_ASSERT(reporter, animation);
const auto& colors = observer->colors();
REPORTER_ASSERT(reporter, colors.size() == 2);
REPORTER_ASSERT(reporter, colors[0].node_name.equals("fill_0"));
REPORTER_ASSERT(reporter, colors[0].handle->get() == 0xffff0000);
REPORTER_ASSERT(reporter, colors[1].node_name.equals("fill_effect_0"));
REPORTER_ASSERT(reporter, colors[1].handle->get() == 0xff00ff00);
const auto& colorsWithFullKeypath = observer->colorsWithFullKeypath();
REPORTER_ASSERT(reporter, colorsWithFullKeypath.size() == 2);
REPORTER_ASSERT(reporter, colorsWithFullKeypath[0].node_name.equals("layer_0.fill_0"));
REPORTER_ASSERT(reporter, colorsWithFullKeypath[0].handle->get() == 0xffff0000);
REPORTER_ASSERT(reporter, colorsWithFullKeypath[1].node_name.equals("layer_0.fill_effect_0"));
REPORTER_ASSERT(reporter, colorsWithFullKeypath[1].handle->get() == 0xff00ff00);
const auto& opacities = observer->opacities();
REPORTER_ASSERT(reporter, opacities.size() == 3);
REPORTER_ASSERT(reporter, opacities[0].node_name.equals("shape_transform_0"));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(opacities[0].handle->get(), 100));
REPORTER_ASSERT(reporter, opacities[1].node_name.equals("layer_0"));
REPORTER_ASSERT(reporter, SkScalarNearlyEqual(opacities[1].handle->get(), 50));
const auto& transforms = observer->transforms();
REPORTER_ASSERT(reporter, transforms.size() == 3);
REPORTER_ASSERT(reporter, transforms[0].node_name.equals("layer_0"));
REPORTER_ASSERT(reporter, transforms[0].handle->get() == skottie::TransformPropertyValue({
SkPoint::Make(0, 0),
SkPoint::Make(0, 0),
SkVector::Make(100, 100),
0,
0,
0
}));
REPORTER_ASSERT(reporter, transforms[1].node_name.equals("layer_1"));
REPORTER_ASSERT(reporter, transforms[1].handle->get() == skottie::TransformPropertyValue({
SkPoint::Make(0, 0),
SkPoint::Make(25, 25),
SkVector::Make(100, 100),
0,
0,
0
}));
REPORTER_ASSERT(reporter, transforms[2].node_name.equals("shape_transform_0"));
REPORTER_ASSERT(reporter, transforms[2].handle->get() == skottie::TransformPropertyValue({
SkPoint::Make(0, 0),
SkPoint::Make(0, 0),
SkVector::Make(50, 50),
0,
0,
0
}));
const auto& texts = observer->texts();
REPORTER_ASSERT(reporter, texts.size() == 1);
REPORTER_ASSERT(reporter, texts[0].node_name.equals("layer_1"));
REPORTER_ASSERT(reporter, texts[0].handle->get() == skottie::TextPropertyValue({
test_typeface,
SkString("inline_text"),
100,
0, 100,
0,
120,
12,
0,
0,
SkTextUtils::kLeft_Align,
Shaper::VAlign::kTopBaseline,
Shaper::ResizePolicy::kNone,
Shaper::LinebreakPolicy::kExplicit,
Shaper::Direction::kLTR,
Shaper::Capitalization::kNone,
SkRect::MakeEmpty(),
SK_ColorTRANSPARENT,
SK_ColorTRANSPARENT,
TextPaintOrder::kFillStroke,
SkPaint::Join::kDefault_Join,
false,
false,
nullptr
}));
}
DEF_TEST(Skottie_Props_Revalidation, reporter) {
auto test_typeface = ToolUtils::create_portable_typeface();
REPORTER_ASSERT(reporter, test_typeface);
auto test_font_manager = sk_make_sp<MockFontMgr>(test_typeface);
SkMemoryStream stream(gTestJson, strlen(gTestJson));
auto observer = sk_make_sp<MockPropertyObserver>(true);
auto animation = skottie::Animation::Builder()
.setPropertyObserver(observer)
.setFontManager(test_font_manager)
.make(&stream);
REPORTER_ASSERT(reporter, animation);
SkPictureRecorder recorder;
// Rendering without seek() should not trigger revalidation asserts.
animation->render(recorder.beginRecording(100, 100));
// Mutate all props.
for (const auto& c : observer->colors()) {
c.handle->set(SK_ColorGRAY);
}
for (const auto& o : observer->opacities()) {
o.handle->set(0.25f);
}
for (const auto& t : observer->texts()) {
t.handle->set(make_text_prop("bar"));
}
for (const auto& t : observer->transforms()) {
t.handle->set({
{ 1000, 1000 },
{ 2000, 2000 },
{ 20, 20 },
5,
0,
0,
});
}
// Rendering without seek() after property mutation should not trigger revalidation asserts.
animation->render(recorder.beginRecording(100, 100));
}