/* * Copyright 2018 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/src/SkottiePriv.h" #include "modules/skottie/include/ExternalLayer.h" #include "modules/skottie/src/Composition.h" #include "modules/skottie/src/SkottieJson.h" #include "modules/skottie/src/SkottieValue.h" #include "modules/skottie/src/animator/Animator.h" #include "modules/sksg/include/SkSGRenderNode.h" #include "modules/sksg/include/SkSGScene.h" #include "src/core/SkTLazy.h" #include "src/utils/SkJSON.h" namespace skottie { namespace internal { namespace { // "Animates" time based on the layer's "tm" property. class TimeRemapper final : public AnimatablePropertyContainer { public: TimeRemapper(const skjson::ObjectValue& jtm, const AnimationBuilder* abuilder, float scale) : fScale(scale) { this->bind(*abuilder, jtm, fT); } float t() const { return fT * fScale; } private: void onSync() override { // nothing to sync - we just track t } const float fScale; ScalarValue fT = 0; }; // Applies a bias/scale/remap t-adjustment to child animators. class CompTimeMapper final : public Animator { public: CompTimeMapper(AnimatorScope&& layer_animators, sk_sp remapper, float time_bias, float time_scale) : fAnimators(std::move(layer_animators)) , fRemapper(std::move(remapper)) , fTimeBias(time_bias) , fTimeScale(time_scale) {} StateChanged onSeek(float t) override { if (fRemapper) { // When time remapping is active, |t| is fully driven externally. fRemapper->seek(t); t = fRemapper->t(); } else { t = (t + fTimeBias) * fTimeScale; } bool changed = false; for (const auto& anim : fAnimators) { changed |= anim->seek(t); } return changed; } private: const AnimatorScope fAnimators; const sk_sp fRemapper; const float fTimeBias, fTimeScale; }; } // namespace sk_sp AnimationBuilder::attachExternalPrecompLayer( const skjson::ObjectValue& jlayer, const LayerInfo& layer_info) const { if (!fPrecompInterceptor) { return nullptr; } const skjson::StringValue* id = jlayer["refId"]; const skjson::StringValue* nm = jlayer["nm"]; if (!id || !nm) { return nullptr; } auto external_layer = fPrecompInterceptor->onLoadPrecomp(id->begin(), nm->begin(), layer_info.fSize); if (!external_layer) { return nullptr; } // Attaches an ExternalLayer implementation to the animation scene graph. class SGAdapter final : public sksg::RenderNode { public: SG_ATTRIBUTE(T, float, fCurrentT) SGAdapter(sk_sp external, const SkSize& layer_size) : fExternal(std::move(external)) , fSize(layer_size) {} private: SkRect onRevalidate(sksg::InvalidationController*, const SkMatrix&) override { return SkRect::MakeSize(fSize); } void onRender(SkCanvas* canvas, const RenderContext* ctx) const override { // Commit all pending effects via a layer if needed, // since we don't have knowledge of the external content. const auto local_scope = ScopedRenderContext(canvas, ctx).setIsolation(this->bounds(), canvas->getTotalMatrix(), true); fExternal->render(canvas, static_cast(fCurrentT)); } const RenderNode* onNodeAt(const SkPoint& pt) const override { SkASSERT(this->bounds().contains(pt.fX, pt.fY)); return this; } const sk_sp fExternal; const SkSize fSize; float fCurrentT = 0; }; // Connects an SGAdapter to the animator tree and dispatches seek events. class AnimatorAdapter final : public Animator { public: AnimatorAdapter(sk_sp sg_adapter, float fps) : fSGAdapter(std::move(sg_adapter)) , fFps(fps) {} private: StateChanged onSeek(float t) override { fSGAdapter->setT(t / fFps); return true; } const sk_sp fSGAdapter; const float fFps; }; auto sg_adapter = sk_make_sp(std::move(external_layer), layer_info.fSize); fCurrentAnimatorScope->push_back(sk_make_sp(sg_adapter, fFrameRate)); return std::move(sg_adapter); } sk_sp AnimationBuilder::attachPrecompLayer(const skjson::ObjectValue& jlayer, LayerInfo* layer_info) const { sk_sp time_remapper; if (const skjson::ObjectValue* jtm = jlayer["tm"]) { time_remapper = sk_make_sp(*jtm, this, fFrameRate); } const auto start_time = ParseDefault(jlayer["st"], 0.0f), stretch_time = ParseDefault(jlayer["sr"], 1.0f); const auto requires_time_mapping = !SkScalarNearlyEqual(start_time , 0) || !SkScalarNearlyEqual(stretch_time, 1) || time_remapper; // Precomp layers are sized explicitly. layer_info->fSize = SkSize::Make(ParseDefault(jlayer["w"], 0.0f), ParseDefault(jlayer["h"], 0.0f)); SkTLazy local_scope; if (requires_time_mapping) { local_scope.init(this); } auto precomp_layer = this->attachExternalPrecompLayer(jlayer, *layer_info); if (!precomp_layer) { const ScopedAssetRef precomp_asset(this, jlayer); if (precomp_asset) { AutoPropertyTracker apt(this, *precomp_asset, PropertyObserver::NodeType::COMPOSITION); precomp_layer = CompositionBuilder(*this, layer_info->fSize, *precomp_asset).build(*this); } } if (requires_time_mapping) { const auto t_bias = -start_time, t_scale = sk_ieee_float_divide(1, stretch_time); auto time_mapper = sk_make_sp(local_scope->release(), std::move(time_remapper), t_bias, sk_float_isfinite(t_scale) ? t_scale : 0); fCurrentAnimatorScope->push_back(std::move(time_mapper)); } return precomp_layer; } } // namespace internal } // namespace skottie