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/Composition.h"
9
10#include "include/core/SkCanvas.h"
11#include "modules/skottie/src/Camera.h"
12#include "modules/skottie/src/SkottieJson.h"
13#include "modules/skottie/src/SkottiePriv.h"
14#include "modules/sksg/include/SkSGGroup.h"
15#include "modules/sksg/include/SkSGTransform.h"
16
17#include <algorithm>
18
19namespace skottie {
20namespace internal {
21
22sk_sp<sksg::RenderNode> AnimationBuilder::attachNestedAnimation(const char* name) const {
23 class SkottieSGAdapter final : public sksg::RenderNode {
24 public:
25 explicit SkottieSGAdapter(sk_sp<Animation> animation)
26 : fAnimation(std::move(animation)) {
27 SkASSERT(fAnimation);
28 }
29
30 protected:
31 SkRect onRevalidate(sksg::InvalidationController*, const SkMatrix&) override {
32 return SkRect::MakeSize(fAnimation->size());
33 }
34
35 const RenderNode* onNodeAt(const SkPoint&) const override { return nullptr; }
36
37 void onRender(SkCanvas* canvas, const RenderContext* ctx) const override {
38 const auto local_scope =
39 ScopedRenderContext(canvas, ctx).setIsolation(this->bounds(),
40 canvas->getTotalMatrix(),
41 true);
42 fAnimation->render(canvas);
43 }
44
45 private:
46 const sk_sp<Animation> fAnimation;
47 };
48
49 class SkottieAnimatorAdapter final : public Animator {
50 public:
51 SkottieAnimatorAdapter(sk_sp<Animation> animation, float time_scale)
52 : fAnimation(std::move(animation))
53 , fTimeScale(time_scale) {
54 SkASSERT(fAnimation);
55 }
56
57 protected:
58 StateChanged onSeek(float t) {
59 // TODO: we prolly need more sophisticated timeline mapping for nested animations.
60 fAnimation->seek(t * fTimeScale);
61
62 // TODO: bubble the real update status to clients?
63 return true;
64 }
65
66 private:
67 const sk_sp<Animation> fAnimation;
68 const float fTimeScale;
69 };
70
71 const auto data = fResourceProvider->load("", name);
72 if (!data) {
73 this->log(Logger::Level::kError, nullptr, "Could not load: %s.", name);
74 return nullptr;
75 }
76
77 auto animation = Animation::Builder()
78 .setResourceProvider(fResourceProvider)
79 .setFontManager(fLazyFontMgr.getMaybeNull())
80 .make(static_cast<const char*>(data->data()), data->size());
81 if (!animation) {
82 this->log(Logger::Level::kError, nullptr, "Could not parse nested animation: %s.", name);
83 return nullptr;
84 }
85
86 fCurrentAnimatorScope->push_back(
87 sk_make_sp<SkottieAnimatorAdapter>(animation, animation->duration() / fDuration));
88
89 return sk_make_sp<SkottieSGAdapter>(std::move(animation));
90}
91
92sk_sp<sksg::RenderNode> AnimationBuilder::attachAssetRef(
93 const skjson::ObjectValue& jlayer,
94 const std::function<sk_sp<sksg::RenderNode>(const skjson::ObjectValue&)>& func) const {
95
96 const auto refId = ParseDefault<SkString>(jlayer["refId"], SkString());
97 if (refId.isEmpty()) {
98 this->log(Logger::Level::kError, nullptr, "Layer missing refId.");
99 return nullptr;
100 }
101
102 if (refId.startsWith("$")) {
103 return this->attachNestedAnimation(refId.c_str() + 1);
104 }
105
106 const auto* asset_info = fAssets.find(refId);
107 if (!asset_info) {
108 this->log(Logger::Level::kError, nullptr, "Asset not found: '%s'.", refId.c_str());
109 return nullptr;
110 }
111
112 if (asset_info->fIsAttaching) {
113 this->log(Logger::Level::kError, nullptr,
114 "Asset cycle detected for: '%s'", refId.c_str());
115 return nullptr;
116 }
117
118 asset_info->fIsAttaching = true;
119 auto asset = func(*asset_info->fAsset);
120 asset_info->fIsAttaching = false;
121
122 return asset;
123}
124
125CompositionBuilder::CompositionBuilder(const AnimationBuilder& abuilder,
126 const SkSize& size,
127 const skjson::ObjectValue& jcomp)
128 : fSize(size) {
129
130 // Optional motion blur params.
131 if (const skjson::ObjectValue* jmb = jcomp["mb"]) {
132 static constexpr size_t kMaxSamplesPerFrame = 64;
133 fMotionBlurSamples = std::min(ParseDefault<size_t>((*jmb)["spf"], 1ul),
134 kMaxSamplesPerFrame);
135 fMotionBlurAngle = SkTPin(ParseDefault((*jmb)["sa"], 0.0f), 0.0f, 720.0f);
136 fMotionBlurPhase = SkTPin(ParseDefault((*jmb)["sp"], 0.0f), -360.0f, 360.0f);
137 }
138
139 int camera_builder_index = -1;
140
141 // Prepare layer builders.
142 if (const skjson::ArrayValue* jlayers = jcomp["layers"]) {
143 fLayerBuilders.reserve(SkToInt(jlayers->size()));
144 for (const skjson::ObjectValue* jlayer : *jlayers) {
145 if (!jlayer) continue;
146
147 const auto lbuilder_index = fLayerBuilders.size();
148 const auto& lbuilder = fLayerBuilders.emplace_back(*jlayer);
149
150 fLayerIndexMap.set(lbuilder.index(), lbuilder_index);
151
152 // Keep track of the camera builder.
153 if (lbuilder.isCamera()) {
154 // We only support one (first) camera for now.
155 if (camera_builder_index < 0) {
156 camera_builder_index = SkToInt(lbuilder_index);
157 } else {
158 abuilder.log(Logger::Level::kWarning, jlayer,
159 "Ignoring duplicate camera layer.");
160 }
161 }
162 }
163 }
164
165 // Attach a camera transform upfront, if needed (required to build
166 // all other 3D transform chains).
167 if (camera_builder_index >= 0) {
168 // Explicit camera.
169 fCameraTransform = fLayerBuilders[camera_builder_index].buildTransform(abuilder, this);
170 } else if (ParseDefault<int>(jcomp["ddd"], 0)) {
171 // Default/implicit camera when 3D layers are present.
172 fCameraTransform = CameraAdaper::DefaultCameraTransform(fSize);
173 }
174}
175
176CompositionBuilder::~CompositionBuilder() = default;
177
178LayerBuilder* CompositionBuilder::layerBuilder(int layer_index) {
179 if (layer_index < 0) {
180 return nullptr;
181 }
182
183 if (const auto* idx = fLayerIndexMap.find(layer_index)) {
184 return &fLayerBuilders[SkToInt(*idx)];
185 }
186
187 return nullptr;
188}
189
190sk_sp<sksg::RenderNode> CompositionBuilder::build(const AnimationBuilder& abuilder) {
191 // First pass - transitively attach layer transform chains.
192 for (auto& lbuilder : fLayerBuilders) {
193 lbuilder.buildTransform(abuilder, this);
194 }
195
196 // Second pass - attach actual layer contents and finalize the layer render tree.
197 std::vector<sk_sp<sksg::RenderNode>> layers;
198 layers.reserve(fLayerBuilders.size());
199
200 LayerBuilder* prev_layer = nullptr;
201 for (auto& lbuilder : fLayerBuilders) {
202 if (auto layer = lbuilder.buildRenderTree(abuilder, this, prev_layer)) {
203 layers.push_back(std::move(layer));
204 }
205 prev_layer = &lbuilder;
206 }
207
208 if (layers.empty()) {
209 return nullptr;
210 }
211
212 if (layers.size() == 1) {
213 return std::move(layers[0]);
214 }
215
216 // Layers are painted in bottom->top order.
217 std::reverse(layers.begin(), layers.end());
218 layers.shrink_to_fit();
219
220 return sksg::Group::Make(std::move(layers));
221}
222
223} // namespace internal
224} // namespace skottie
225