| /* | 
 |  * Copyright 2019 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 "include/core/SkCanvas.h" | 
 | #include "modules/skottie/src/SkottieJson.h" | 
 | #include "modules/sksg/include/SkSGGroup.h" | 
 | #include "modules/sksg/include/SkSGTransform.h" | 
 |  | 
 | #include <algorithm> | 
 |  | 
 | namespace skottie { | 
 | namespace internal { | 
 |  | 
 | sk_sp<sksg::RenderNode> AnimationBuilder::attachNestedAnimation(const char* name) const { | 
 |     class SkottieSGAdapter final : public sksg::RenderNode { | 
 |     public: | 
 |         explicit SkottieSGAdapter(sk_sp<Animation> animation) | 
 |             : fAnimation(std::move(animation)) { | 
 |             SkASSERT(fAnimation); | 
 |         } | 
 |  | 
 |     protected: | 
 |         SkRect onRevalidate(sksg::InvalidationController*, const SkMatrix&) override { | 
 |             return SkRect::MakeSize(fAnimation->size()); | 
 |         } | 
 |  | 
 |         const RenderNode* onNodeAt(const SkPoint&) const override { return nullptr; } | 
 |  | 
 |         void onRender(SkCanvas* canvas, const RenderContext* ctx) const override { | 
 |             const auto local_scope = | 
 |                 ScopedRenderContext(canvas, ctx).setIsolation(this->bounds(), | 
 |                                                               canvas->getTotalMatrix(), | 
 |                                                               true); | 
 |             fAnimation->render(canvas); | 
 |         } | 
 |  | 
 |     private: | 
 |         const sk_sp<Animation> fAnimation; | 
 |     }; | 
 |  | 
 |     class SkottieAnimatorAdapter final : public sksg::Animator { | 
 |     public: | 
 |         SkottieAnimatorAdapter(sk_sp<Animation> animation, float time_scale) | 
 |             : fAnimation(std::move(animation)) | 
 |             , fTimeScale(time_scale) { | 
 |             SkASSERT(fAnimation); | 
 |         } | 
 |  | 
 |     protected: | 
 |         void onTick(float t) { | 
 |             // TODO: we prolly need more sophisticated timeline mapping for nested animations. | 
 |             fAnimation->seek(t * fTimeScale); | 
 |         } | 
 |  | 
 |     private: | 
 |         const sk_sp<Animation> fAnimation; | 
 |         const float            fTimeScale; | 
 |     }; | 
 |  | 
 |     const auto data = fResourceProvider->load("", name); | 
 |     if (!data) { | 
 |         this->log(Logger::Level::kError, nullptr, "Could not load: %s.", name); | 
 |         return nullptr; | 
 |     } | 
 |  | 
 |     auto animation = Animation::Builder() | 
 |             .setResourceProvider(fResourceProvider) | 
 |             .setFontManager(fLazyFontMgr.getMaybeNull()) | 
 |             .make(static_cast<const char*>(data->data()), data->size()); | 
 |     if (!animation) { | 
 |         this->log(Logger::Level::kError, nullptr, "Could not parse nested animation: %s.", name); | 
 |         return nullptr; | 
 |     } | 
 |  | 
 |     fCurrentAnimatorScope->push_back( | 
 |             sk_make_sp<SkottieAnimatorAdapter>(animation, animation->duration() / fDuration)); | 
 |  | 
 |     return sk_make_sp<SkottieSGAdapter>(std::move(animation)); | 
 | } | 
 |  | 
 | sk_sp<sksg::RenderNode> AnimationBuilder::attachAssetRef( | 
 |     const skjson::ObjectValue& jlayer, | 
 |     const std::function<sk_sp<sksg::RenderNode>(const skjson::ObjectValue&)>& func) const { | 
 |  | 
 |     const auto refId = ParseDefault<SkString>(jlayer["refId"], SkString()); | 
 |     if (refId.isEmpty()) { | 
 |         this->log(Logger::Level::kError, nullptr, "Layer missing refId."); | 
 |         return nullptr; | 
 |     } | 
 |  | 
 |     if (refId.startsWith("$")) { | 
 |         return this->attachNestedAnimation(refId.c_str() + 1); | 
 |     } | 
 |  | 
 |     const auto* asset_info = fAssets.find(refId); | 
 |     if (!asset_info) { | 
 |         this->log(Logger::Level::kError, nullptr, "Asset not found: '%s'.", refId.c_str()); | 
 |         return nullptr; | 
 |     } | 
 |  | 
 |     if (asset_info->fIsAttaching) { | 
 |         this->log(Logger::Level::kError, nullptr, | 
 |                   "Asset cycle detected for: '%s'", refId.c_str()); | 
 |         return nullptr; | 
 |     } | 
 |  | 
 |     asset_info->fIsAttaching = true; | 
 |     auto asset = func(*asset_info->fAsset); | 
 |     asset_info->fIsAttaching = false; | 
 |  | 
 |     return asset; | 
 | } | 
 |  | 
 | sk_sp<sksg::RenderNode> AnimationBuilder::attachComposition( | 
 |         const skjson::ObjectValue& jcomp) const { | 
 |     const skjson::ArrayValue* jlayers = jcomp["layers"]; | 
 |     if (!jlayers) return nullptr; | 
 |  | 
 |     std::vector<sk_sp<sksg::RenderNode>> layers; | 
 |     AttachLayerContext                   layerCtx(*jlayers); | 
 |  | 
 |     // Optional motion blur params. | 
 |     if (const skjson::ObjectValue* jmb = jcomp["mb"]) { | 
 |         static constexpr size_t kMaxSamplesPerFrame = 64; | 
 |         layerCtx.fMotionBlurSamples = std::min(ParseDefault<size_t>((*jmb)["spf"], 1ul), | 
 |                                                kMaxSamplesPerFrame); | 
 |         layerCtx.fMotionBlurAngle = SkTPin(ParseDefault((*jmb)["sa"], 0.0f),    0.0f, 720.0f); | 
 |         layerCtx.fMotionBlurPhase = SkTPin(ParseDefault((*jmb)["sp"], 0.0f), -360.0f, 360.0f); | 
 |     } | 
 |  | 
 |     layers.reserve(jlayers->size()); | 
 |     for (const auto& l : *jlayers) { | 
 |         if (auto layer = this->attachLayer(l, &layerCtx)) { | 
 |             layers.push_back(std::move(layer)); | 
 |         } | 
 |     } | 
 |  | 
 |     if (layers.empty()) { | 
 |         return nullptr; | 
 |     } | 
 |  | 
 |     sk_sp<sksg::RenderNode> comp; | 
 |     if (layers.size() == 1) { | 
 |         comp = std::move(layers[0]); | 
 |     } else { | 
 |         // Layers are painted in bottom->top order. | 
 |         std::reverse(layers.begin(), layers.end()); | 
 |         layers.shrink_to_fit(); | 
 |         comp = sksg::Group::Make(std::move(layers)); | 
 |     } | 
 |  | 
 |     // Optional camera. | 
 |     if (layerCtx.fCameraTransform) { | 
 |         comp = sksg::TransformEffect::Make(std::move(comp), std::move(layerCtx.fCameraTransform)); | 
 |     } | 
 |  | 
 |     return comp; | 
 | } | 
 |  | 
 | } // namespace internal | 
 | } // namespace skottie |