1 /*
2 * Copyright 2019 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8 #include "modules/skottie/src/SkottiePriv.h"
9
10 #include "include/core/SkCanvas.h"
11 #include "modules/skottie/src/SkottieJson.h"
12 #include "modules/sksg/include/SkSGGroup.h"
13 #include "modules/sksg/include/SkSGTransform.h"
14
15 #include <algorithm>
16
17 namespace skottie {
18 namespace internal {
19
attachNestedAnimation(const char * name) const20 sk_sp<sksg::RenderNode> AnimationBuilder::attachNestedAnimation(const char* name) const {
21 class SkottieSGAdapter final : public sksg::RenderNode {
22 public:
23 explicit SkottieSGAdapter(sk_sp<Animation> animation)
24 : fAnimation(std::move(animation)) {
25 SkASSERT(fAnimation);
26 }
27
28 protected:
29 SkRect onRevalidate(sksg::InvalidationController*, const SkMatrix&) override {
30 return SkRect::MakeSize(fAnimation->size());
31 }
32
33 const RenderNode* onNodeAt(const SkPoint&) const override { return nullptr; }
34
35 void onRender(SkCanvas* canvas, const RenderContext* ctx) const override {
36 const auto local_scope =
37 ScopedRenderContext(canvas, ctx).setIsolation(this->bounds(),
38 canvas->getTotalMatrix(),
39 true);
40 fAnimation->render(canvas);
41 }
42
43 private:
44 const sk_sp<Animation> fAnimation;
45 };
46
47 class SkottieAnimatorAdapter final : public sksg::Animator {
48 public:
49 SkottieAnimatorAdapter(sk_sp<Animation> animation, float time_scale)
50 : fAnimation(std::move(animation))
51 , fTimeScale(time_scale) {
52 SkASSERT(fAnimation);
53 }
54
55 protected:
56 void onTick(float t) {
57 // TODO: we prolly need more sophisticated timeline mapping for nested animations.
58 fAnimation->seek(t * fTimeScale);
59 }
60
61 private:
62 const sk_sp<Animation> fAnimation;
63 const float fTimeScale;
64 };
65
66 const auto data = fResourceProvider->load("", name);
67 if (!data) {
68 this->log(Logger::Level::kError, nullptr, "Could not load: %s.", name);
69 return nullptr;
70 }
71
72 auto animation = Animation::Builder()
73 .setResourceProvider(fResourceProvider)
74 .setFontManager(fLazyFontMgr.getMaybeNull())
75 .make(static_cast<const char*>(data->data()), data->size());
76 if (!animation) {
77 this->log(Logger::Level::kError, nullptr, "Could not parse nested animation: %s.", name);
78 return nullptr;
79 }
80
81 fCurrentAnimatorScope->push_back(
82 sk_make_sp<SkottieAnimatorAdapter>(animation, animation->duration() / fDuration));
83
84 return sk_make_sp<SkottieSGAdapter>(std::move(animation));
85 }
86
attachAssetRef(const skjson::ObjectValue & jlayer,const std::function<sk_sp<sksg::RenderNode> (const skjson::ObjectValue &)> & func) const87 sk_sp<sksg::RenderNode> AnimationBuilder::attachAssetRef(
88 const skjson::ObjectValue& jlayer,
89 const std::function<sk_sp<sksg::RenderNode>(const skjson::ObjectValue&)>& func) const {
90
91 const auto refId = ParseDefault<SkString>(jlayer["refId"], SkString());
92 if (refId.isEmpty()) {
93 this->log(Logger::Level::kError, nullptr, "Layer missing refId.");
94 return nullptr;
95 }
96
97 if (refId.startsWith("$")) {
98 return this->attachNestedAnimation(refId.c_str() + 1);
99 }
100
101 const auto* asset_info = fAssets.find(refId);
102 if (!asset_info) {
103 this->log(Logger::Level::kError, nullptr, "Asset not found: '%s'.", refId.c_str());
104 return nullptr;
105 }
106
107 if (asset_info->fIsAttaching) {
108 this->log(Logger::Level::kError, nullptr,
109 "Asset cycle detected for: '%s'", refId.c_str());
110 return nullptr;
111 }
112
113 asset_info->fIsAttaching = true;
114 auto asset = func(*asset_info->fAsset);
115 asset_info->fIsAttaching = false;
116
117 return asset;
118 }
119
attachComposition(const skjson::ObjectValue & jcomp) const120 sk_sp<sksg::RenderNode> AnimationBuilder::attachComposition(
121 const skjson::ObjectValue& jcomp) const {
122 const skjson::ArrayValue* jlayers = jcomp["layers"];
123 if (!jlayers) return nullptr;
124
125 std::vector<sk_sp<sksg::RenderNode>> layers;
126 AttachLayerContext layerCtx(*jlayers);
127
128 // Optional motion blur params.
129 if (const skjson::ObjectValue* jmb = jcomp["mb"]) {
130 static constexpr size_t kMaxSamplesPerFrame = 64;
131 layerCtx.fMotionBlurSamples = std::min(ParseDefault<size_t>((*jmb)["spf"], 1ul),
132 kMaxSamplesPerFrame);
133 layerCtx.fMotionBlurAngle = SkTPin(ParseDefault((*jmb)["sa"], 0.0f), 0.0f, 720.0f);
134 layerCtx.fMotionBlurPhase = SkTPin(ParseDefault((*jmb)["sp"], 0.0f), -360.0f, 360.0f);
135 }
136
137 layers.reserve(jlayers->size());
138 for (const auto& l : *jlayers) {
139 if (auto layer = this->attachLayer(l, &layerCtx)) {
140 layers.push_back(std::move(layer));
141 }
142 }
143
144 if (layers.empty()) {
145 return nullptr;
146 }
147
148 sk_sp<sksg::RenderNode> comp;
149 if (layers.size() == 1) {
150 comp = std::move(layers[0]);
151 } else {
152 // Layers are painted in bottom->top order.
153 std::reverse(layers.begin(), layers.end());
154 layers.shrink_to_fit();
155 comp = sksg::Group::Make(std::move(layers));
156 }
157
158 // Optional camera.
159 if (layerCtx.fCameraTransform) {
160 comp = sksg::TransformEffect::Make(std::move(comp), std::move(layerCtx.fCameraTransform));
161 }
162
163 return comp;
164 }
165
166 } // namespace internal
167 } // namespace skottie
168