From 1022f743758b71bcc476e602679282a0acd64ff1 Mon Sep 17 00:00:00 2001 From: Florin Malita Date: Fri, 23 Feb 2018 11:10:22 -0500 Subject: [skottie] Nested animation support Extend composition layers to support referencing external .json animations ("$" syntax). This is a custom extension (not supported in BM/Lottie). Also make skottie::Animation ref-counted, to facilitate sharing. TBR= Change-Id: I062d031e5868d759f3930dea9b261f9b3ec81684 Reviewed-on: https://skia-review.googlesource.com/109806 Reviewed-by: Florin Malita Commit-Queue: Florin Malita --- dm/DMSrcSink.h | 8 +-- experimental/skottie/Skottie.cpp | 93 +++++++++++++++++++++++++---- experimental/skottie/Skottie.h | 11 ++-- resources/skotty/skotty_sample_nested.json | 96 ++++++++++++++++++++++++++++++ tools/viewer/SkottieSlide.cpp | 6 +- tools/viewer/SkottieSlide.h | 10 ++-- 6 files changed, 193 insertions(+), 31 deletions(-) create mode 100644 resources/skotty/skotty_sample_nested.json diff --git a/dm/DMSrcSink.h b/dm/DMSrcSink.h index 157a484d2b..1ad2eca79f 100644 --- a/dm/DMSrcSink.h +++ b/dm/DMSrcSink.h @@ -274,11 +274,11 @@ public: private: // Generates a kTileCount x kTileCount filmstrip with evenly distributed frames. - static constexpr int kTileCount = 5; + static constexpr int kTileCount = 5; - Name fName; - SkISize fTileSize = SkISize::MakeEmpty(); - std::unique_ptr fAnimation; + Name fName; + SkISize fTileSize = SkISize::MakeEmpty(); + sk_sp fAnimation; }; #endif diff --git a/experimental/skottie/Skottie.cpp b/experimental/skottie/Skottie.cpp index aee121020e..5b0e836997 100644 --- a/experimental/skottie/Skottie.cpp +++ b/experimental/skottie/Skottie.cpp @@ -55,6 +55,7 @@ using AssetMap = SkTHashMap; struct AttachContext { const ResourceProvider& fResources; const AssetMap& fAssets; + const float fFrameRate; sksg::AnimatorList& fAnimators; }; @@ -676,6 +677,66 @@ sk_sp AttachShape(const Json::Value& jshape, AttachShapeContex return draws.empty() ? nullptr : shape_wrapper; } +sk_sp AttachNestedAnimation(const char* path, AttachContext* ctx) { + class SkottieSGAdapter final : public sksg::RenderNode { + public: + explicit SkottieSGAdapter(sk_sp animation) + : fAnimation(std::move(animation)) { + SkASSERT(fAnimation); + } + + protected: + SkRect onRevalidate(sksg::InvalidationController*, const SkMatrix&) override { + return SkRect::MakeSize(fAnimation->size()); + } + + void onRender(SkCanvas* canvas) const override { + fAnimation->render(canvas); + } + + private: + const sk_sp fAnimation; + }; + + class SkottieAnimatorAdapter final : public sksg::Animator { + public: + SkottieAnimatorAdapter(sk_sp animation, float frameRate) + : fAnimation(std::move(animation)) + , fFrameRate(frameRate) { + SkASSERT(fAnimation); + SkASSERT(fFrameRate > 0); + } + + protected: + void onTick(float t) { + // map back from frame # to ms. + const auto t_ms = t * 1000 / fFrameRate; + fAnimation->animationTick(t_ms); + } + + private: + const sk_sp fAnimation; + const float fFrameRate; + }; + + const auto resStream = ctx->fResources.openStream(path); + if (!resStream || !resStream->hasLength()) { + LOG("!! Could not open: %s\n", path); + return nullptr; + } + + auto animation = Animation::Make(resStream.get(), ctx->fResources); + if (!animation) { + LOG("!! Could not load nested animation: %s\n", path); + return nullptr; + } + + ctx->fAnimators.push_back(skstd::make_unique(animation, + ctx->fFrameRate)); + + return sk_make_sp(std::move(animation)); +} + sk_sp AttachCompLayer(const Json::Value& jlayer, AttachContext* ctx, float* time_bias, float* time_scale) { SkASSERT(jlayer.isObject()); @@ -686,12 +747,6 @@ sk_sp AttachCompLayer(const Json::Value& jlayer, AttachContext return nullptr; } - const auto* comp = ctx->fAssets.find(refId); - if (!comp) { - LOG("!! Pre-comp not found: '%s'\n", refId.c_str()); - return nullptr; - } - const auto start_time = ParseDefault(jlayer["st"], 0.0f), stretch_time = ParseDefault(jlayer["sr"], 1.0f); @@ -701,6 +756,16 @@ sk_sp AttachCompLayer(const Json::Value& jlayer, AttachContext *time_scale = 1; } + if (refId.startsWith("$")) { + return AttachNestedAnimation(refId.c_str() + 1, ctx); + } + + const auto* comp = ctx->fAssets.find(refId); + if (!comp) { + LOG("!! Pre-comp not found: '%s'\n", refId.c_str()); + return nullptr; + } + // TODO: cycle detection return AttachComposition(**comp, ctx); } @@ -937,8 +1002,10 @@ sk_sp AttachLayer(const Json::Value& jlayer, } sksg::AnimatorList layer_animators; - AttachContext local_ctx = - { layerCtx->fCtx->fResources, layerCtx->fCtx->fAssets, layer_animators}; + AttachContext local_ctx = { layerCtx->fCtx->fResources, + layerCtx->fCtx->fAssets, + layerCtx->fCtx->fFrameRate, + layer_animators}; // Layer attachers may adjust these. float time_bias = 0, @@ -1070,7 +1137,7 @@ sk_sp AttachComposition(const Json::Value& comp, AttachContext } // namespace -std::unique_ptr Animation::Make(SkStream* stream, const ResourceProvider& res) { +sk_sp Animation::Make(SkStream* stream, const ResourceProvider& res) { if (!stream->hasLength()) { // TODO: handle explicit buffering? LOG("!! cannot parse streaming content\n"); @@ -1099,16 +1166,16 @@ std::unique_ptr Animation::Make(SkStream* stream, const ResourceProvi ParseDefault(json["h"], 0.0f)); const auto fps = ParseDefault(json["fr"], -1.0f); - if (size.isEmpty() || version.isEmpty() || fps < 0) { + if (size.isEmpty() || version.isEmpty() || fps <= 0) { LOG("!! invalid animation params (version: %s, size: [%f %f], frame rate: %f)", version.c_str(), size.width(), size.height(), fps); return nullptr; } - return std::unique_ptr(new Animation(res, std::move(version), size, fps, json)); + return sk_sp(new Animation(res, std::move(version), size, fps, json)); } -std::unique_ptr Animation::MakeFromFile(const char path[], const ResourceProvider* res) { +sk_sp Animation::MakeFromFile(const char path[], const ResourceProvider* res) { class DirectoryResourceProvider final : public ResourceProvider { public: explicit DirectoryResourceProvider(SkString dir) : fDir(std::move(dir)) {} @@ -1152,7 +1219,7 @@ Animation::Animation(const ResourceProvider& resources, } sksg::AnimatorList animators; - AttachContext ctx = { resources, assets, animators }; + AttachContext ctx = { resources, assets, fFrameRate, animators }; auto root = AttachComposition(json, &ctx); LOG("** Attached %d animators\n", animators.size()); diff --git a/experimental/skottie/Skottie.h b/experimental/skottie/Skottie.h index d2486dc946..f14c4dc42b 100644 --- a/experimental/skottie/Skottie.h +++ b/experimental/skottie/Skottie.h @@ -34,13 +34,12 @@ public: virtual std::unique_ptr openStream(const char resource[]) const = 0; }; -class Animation : public SkNoncopyable { +class Animation : public SkRefCnt { public: - static std::unique_ptr Make(SkStream*, const ResourceProvider&); - static std::unique_ptr MakeFromFile(const char path[], - const ResourceProvider* = nullptr); + static sk_sp Make(SkStream*, const ResourceProvider&); + static sk_sp MakeFromFile(const char path[], const ResourceProvider* = nullptr); - ~Animation(); + ~Animation() override; void render(SkCanvas*, const SkRect* dst = nullptr) const; @@ -67,7 +66,7 @@ private: std::unique_ptr fScene; - typedef SkNoncopyable INHERITED; + typedef SkRefCnt INHERITED; }; } // namespace skottie diff --git a/resources/skotty/skotty_sample_nested.json b/resources/skotty/skotty_sample_nested.json new file mode 100644 index 0000000000..8059b76ba9 --- /dev/null +++ b/resources/skotty/skotty_sample_nested.json @@ -0,0 +1,96 @@ +{ + "v":"4.6.9", + "fr":60, + "ip":0, + "op":200, + "w":800, + "h":600, + "nm":"External Animation", + "ddd":0, + + + "layers":[ + { + "ddd":0, + "ind":2, + "ty": 0, + "nm":"External Animation Ref", + "refId": "$skotty_sample_1.json", + "ao": 0, + "ip": 0, + "op": 300, + "st": 0, + "sr": 1, + "bm": 0, + "ks": { + "o": { "a":0, "k":100 }, + "r": { "a":1, "k":[ + { "s": 0, "e": 360, "t": 0 }, + { "t": 200 } + ]}, + "p": { "a":0, "k":[ 400, 300, 0 ] }, + "a": { "a":0, "k":[ 200, 100, 0 ] }, + "s": { "a":1, "k":[ + { "s": [ 50, 50, 50 ], "e": [ 200, 200, 200 ], "i": { "x":0, "y":0.5 }, "o": { "x":0.5, "y":0.8 }, "t": 0 }, + { "s": [ 200, 200, 200 ], "e": [ 50, 50, 50 ], "i": { "x":0.5, "y":0.2 }, "o": { "x":1, "y":0.5 }, "t": 100 }, + { "t": 200 } + ] } + } + }, + + { + "ddd":0, + "ind":1, + "ty":4, + "nm":"Custom Path 1", + "ao": 0, + "ip": 0, + "op": 300, + "st": 0, + "sr": 1, + "bm": 0, + "ks": { + "o": { "a":0, "k":100 }, + "r": { "a":0, "k":0 }, + "p": { "a":0, "k":[ 400, 300, 0 ] }, + "a": { "a":0, "k":[ 0, 0, 0 ] }, + "s": { "a":1, "k":[ + { "s": [ 50, 50, 50 ], "e": [ 200, 200, 200 ], "i": { "x":0, "y":0.5 }, "o": { "x":0.5, "y":0.8 }, "t": 0 }, + { "s": [ 200, 200, 200 ], "e": [ 50, 50, 50 ], "i": { "x":0.5, "y":0.2 }, "o": { "x":1, "y":0.5 }, "t": 100 }, + { "t": 200 } + ] } + }, + + "shapes":[ + { + "ty":"gr", + "it":[ + { + "ty": "el", + "nm": "Ellipse", + "p" : { "a": 0, "k": [ 0, 0 ] }, + "s" : { "a": 0, "k": [ 400, 400 ] } + }, + + { + "ty": "fl", + "nm": "Fill", + "o" : { "a": 0, "k": 30 }, + "c" : { "a": 0, "k": [ 0.5, 0.5, 1 ] } + }, + + { + "ty":"tr", + "p" : { "a":0, "k":[ 0, 0 ] }, + "a" : { "a":0, "k":[ 0, 0 ] }, + "s" : { "a":0, "k":[ 100, 100 ] }, + "r" : { "a":0, "k": 0 }, + "o" : { "a":0, "k":100 }, + "nm": "Transform" + } + ] + } + ] + } + ] +} diff --git a/tools/viewer/SkottieSlide.cpp b/tools/viewer/SkottieSlide.cpp index aef28e1efc..77501d8a9f 100644 --- a/tools/viewer/SkottieSlide.cpp +++ b/tools/viewer/SkottieSlide.cpp @@ -17,9 +17,9 @@ SkottieSlide::SkottieSlide(const SkString& name, const SkString& path) } void SkottieSlide::load(SkScalar w, SkScalar h) { - fAnimation = skottie::Animation::MakeFromFile(fPath.c_str()); - fWinSize = SkSize::Make(w, h); - fTimeBase = 0; // force a time reset + fAnimation = skottie::Animation::MakeFromFile(fPath.c_str()); + fWinSize = SkSize::Make(w, h); + fTimeBase = 0; // force a time reset if (fAnimation) { fAnimation->setShowInval(fShowAnimationInval); diff --git a/tools/viewer/SkottieSlide.h b/tools/viewer/SkottieSlide.h index ac26d2f3c1..b5770a0cf8 100644 --- a/tools/viewer/SkottieSlide.h +++ b/tools/viewer/SkottieSlide.h @@ -30,11 +30,11 @@ public: bool onMouse(SkScalar x, SkScalar y, sk_app::Window::InputState, uint32_t modifiers) override; private: - SkString fPath; - std::unique_ptr fAnimation; - SkSize fWinSize = SkSize::MakeEmpty(); - SkMSec fTimeBase = 0; - bool fShowAnimationInval = false; + SkString fPath; + sk_sp fAnimation; + SkSize fWinSize = SkSize::MakeEmpty(); + SkMSec fTimeBase = 0; + bool fShowAnimationInval = false; typedef Slide INHERITED; }; -- cgit v1.2.3