| 1 | /* |
| 2 | * Copyright 2018 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 "modules/skottie/include/ExternalLayer.h" |
| 11 | #include "modules/skottie/src/Composition.h" |
| 12 | #include "modules/skottie/src/SkottieJson.h" |
| 13 | #include "modules/skottie/src/SkottieValue.h" |
| 14 | #include "modules/skottie/src/animator/Animator.h" |
| 15 | #include "modules/sksg/include/SkSGRenderNode.h" |
| 16 | #include "modules/sksg/include/SkSGScene.h" |
| 17 | #include "src/core/SkTLazy.h" |
| 18 | #include "src/utils/SkJSON.h" |
| 19 | |
| 20 | namespace skottie { |
| 21 | namespace internal { |
| 22 | |
| 23 | namespace { |
| 24 | |
| 25 | // "Animates" time based on the layer's "tm" property. |
| 26 | class TimeRemapper final : public AnimatablePropertyContainer { |
| 27 | public: |
| 28 | TimeRemapper(const skjson::ObjectValue& jtm, const AnimationBuilder* abuilder, float scale) |
| 29 | : fScale(scale) { |
| 30 | this->bind(*abuilder, jtm, fT); |
| 31 | } |
| 32 | |
| 33 | float t() const { return fT * fScale; } |
| 34 | |
| 35 | private: |
| 36 | void onSync() override { |
| 37 | // nothing to sync - we just track t |
| 38 | } |
| 39 | |
| 40 | const float fScale; |
| 41 | |
| 42 | ScalarValue fT = 0; |
| 43 | }; |
| 44 | |
| 45 | // Applies a bias/scale/remap t-adjustment to child animators. |
| 46 | class CompTimeMapper final : public Animator { |
| 47 | public: |
| 48 | CompTimeMapper(AnimatorScope&& layer_animators, |
| 49 | sk_sp<TimeRemapper> remapper, |
| 50 | float time_bias, float time_scale) |
| 51 | : fAnimators(std::move(layer_animators)) |
| 52 | , fRemapper(std::move(remapper)) |
| 53 | , fTimeBias(time_bias) |
| 54 | , fTimeScale(time_scale) {} |
| 55 | |
| 56 | StateChanged onSeek(float t) override { |
| 57 | if (fRemapper) { |
| 58 | // When time remapping is active, |t| is fully driven externally. |
| 59 | fRemapper->seek(t); |
| 60 | t = fRemapper->t(); |
| 61 | } else { |
| 62 | t = (t + fTimeBias) * fTimeScale; |
| 63 | } |
| 64 | |
| 65 | bool changed = false; |
| 66 | |
| 67 | for (const auto& anim : fAnimators) { |
| 68 | changed |= anim->seek(t); |
| 69 | } |
| 70 | |
| 71 | return changed; |
| 72 | } |
| 73 | |
| 74 | private: |
| 75 | const AnimatorScope fAnimators; |
| 76 | const sk_sp<TimeRemapper> fRemapper; |
| 77 | const float fTimeBias, |
| 78 | fTimeScale; |
| 79 | }; |
| 80 | |
| 81 | } // namespace |
| 82 | |
| 83 | sk_sp<sksg::RenderNode> AnimationBuilder::attachExternalPrecompLayer( |
| 84 | const skjson::ObjectValue& jlayer, |
| 85 | const LayerInfo& layer_info) const { |
| 86 | |
| 87 | if (!fPrecompInterceptor) { |
| 88 | return nullptr; |
| 89 | } |
| 90 | |
| 91 | const skjson::StringValue* id = jlayer["refId" ]; |
| 92 | const skjson::StringValue* nm = jlayer["nm" ]; |
| 93 | |
| 94 | if (!id || !nm) { |
| 95 | return nullptr; |
| 96 | } |
| 97 | |
| 98 | auto external_layer = fPrecompInterceptor->onLoadPrecomp(id->begin(), |
| 99 | nm->begin(), |
| 100 | layer_info.fSize); |
| 101 | if (!external_layer) { |
| 102 | return nullptr; |
| 103 | } |
| 104 | |
| 105 | // Attaches an ExternalLayer implementation to the animation scene graph. |
| 106 | class SGAdapter final : public sksg::RenderNode { |
| 107 | public: |
| 108 | SG_ATTRIBUTE(T, float, fCurrentT) |
| 109 | |
| 110 | SGAdapter(sk_sp<ExternalLayer> external, const SkSize& layer_size) |
| 111 | : fExternal(std::move(external)) |
| 112 | , fSize(layer_size) {} |
| 113 | |
| 114 | private: |
| 115 | SkRect onRevalidate(sksg::InvalidationController*, const SkMatrix&) override { |
| 116 | return SkRect::MakeSize(fSize); |
| 117 | } |
| 118 | |
| 119 | void onRender(SkCanvas* canvas, const RenderContext* ctx) const override { |
| 120 | // Commit all pending effects via a layer if needed, |
| 121 | // since we don't have knowledge of the external content. |
| 122 | const auto local_scope = |
| 123 | ScopedRenderContext(canvas, ctx).setIsolation(this->bounds(), |
| 124 | canvas->getTotalMatrix(), |
| 125 | true); |
| 126 | fExternal->render(canvas, static_cast<double>(fCurrentT)); |
| 127 | } |
| 128 | |
| 129 | const RenderNode* onNodeAt(const SkPoint& pt) const override { |
| 130 | SkASSERT(this->bounds().contains(pt.fX, pt.fY)); |
| 131 | return this; |
| 132 | } |
| 133 | |
| 134 | const sk_sp<ExternalLayer> fExternal; |
| 135 | const SkSize fSize; |
| 136 | float fCurrentT = 0; |
| 137 | }; |
| 138 | |
| 139 | // Connects an SGAdapter to the animator tree and dispatches seek events. |
| 140 | class AnimatorAdapter final : public Animator { |
| 141 | public: |
| 142 | AnimatorAdapter(sk_sp<SGAdapter> sg_adapter, float fps) |
| 143 | : fSGAdapter(std::move(sg_adapter)) |
| 144 | , fFps(fps) {} |
| 145 | |
| 146 | private: |
| 147 | StateChanged onSeek(float t) override { |
| 148 | fSGAdapter->setT(t / fFps); |
| 149 | |
| 150 | return true; |
| 151 | } |
| 152 | |
| 153 | const sk_sp<SGAdapter> fSGAdapter; |
| 154 | const float fFps; |
| 155 | }; |
| 156 | |
| 157 | auto sg_adapter = sk_make_sp<SGAdapter>(std::move(external_layer), layer_info.fSize); |
| 158 | |
| 159 | fCurrentAnimatorScope->push_back(sk_make_sp<AnimatorAdapter>(sg_adapter, fFrameRate)); |
| 160 | |
| 161 | return std::move(sg_adapter); |
| 162 | } |
| 163 | |
| 164 | sk_sp<sksg::RenderNode> AnimationBuilder::attachPrecompLayer(const skjson::ObjectValue& jlayer, |
| 165 | LayerInfo* layer_info) const { |
| 166 | sk_sp<TimeRemapper> time_remapper; |
| 167 | if (const skjson::ObjectValue* jtm = jlayer["tm" ]) { |
| 168 | time_remapper = sk_make_sp<TimeRemapper>(*jtm, this, fFrameRate); |
| 169 | } |
| 170 | |
| 171 | const auto start_time = ParseDefault<float>(jlayer["st" ], 0.0f), |
| 172 | stretch_time = ParseDefault<float>(jlayer["sr" ], 1.0f); |
| 173 | const auto requires_time_mapping = !SkScalarNearlyEqual(start_time , 0) || |
| 174 | !SkScalarNearlyEqual(stretch_time, 1) || |
| 175 | time_remapper; |
| 176 | |
| 177 | // Precomp layers are sized explicitly. |
| 178 | layer_info->fSize = SkSize::Make(ParseDefault<float>(jlayer["w" ], 0.0f), |
| 179 | ParseDefault<float>(jlayer["h" ], 0.0f)); |
| 180 | |
| 181 | SkTLazy<AutoScope> local_scope; |
| 182 | if (requires_time_mapping) { |
| 183 | local_scope.init(this); |
| 184 | } |
| 185 | |
| 186 | auto precomp_layer = this->attachExternalPrecompLayer(jlayer, *layer_info); |
| 187 | |
| 188 | if (!precomp_layer) { |
| 189 | const ScopedAssetRef precomp_asset(this, jlayer); |
| 190 | if (precomp_asset) { |
| 191 | precomp_layer = |
| 192 | CompositionBuilder(*this, layer_info->fSize, *precomp_asset).build(*this); |
| 193 | } |
| 194 | } |
| 195 | |
| 196 | if (requires_time_mapping) { |
| 197 | const auto t_bias = -start_time, |
| 198 | t_scale = sk_ieee_float_divide(1, stretch_time); |
| 199 | auto time_mapper = sk_make_sp<CompTimeMapper>(local_scope->release(), |
| 200 | std::move(time_remapper), |
| 201 | t_bias, |
| 202 | sk_float_isfinite(t_scale) ? t_scale : 0); |
| 203 | |
| 204 | fCurrentAnimatorScope->push_back(std::move(time_mapper)); |
| 205 | } |
| 206 | |
| 207 | return precomp_layer; |
| 208 | } |
| 209 | |
| 210 | } // namespace internal |
| 211 | } // namespace skottie |
| 212 | |