From 13fda2a4e785980a8c9ac83fcfb299de12a6a4d5 Mon Sep 17 00:00:00 2001 From: Jorge Betancourt Date: Wed, 25 May 2022 09:45:28 -0400 Subject: [PATCH] implement animatable particle drawables this change relands: https://skia-review.googlesource.com/c/skia/+/538042 It addresses the problem that skqp was excluding skottie source files but still loading particles. Change-Id: I8849c5285bd2c41b76c2e5fd77b06eca0184719e Reviewed-on: https://skia-review.googlesource.com/c/skia/+/542144 Reviewed-by: Florin Malita Commit-Queue: Jorge Betancourt --- gm/particles.cpp | 2 + gn/skqp_gn_args.py | 1 + modules/particles/BUILD.gn | 1 + .../particles/include/SkParticleDrawable.h | 2 + modules/particles/src/BUILD.bazel | 1 + modules/particles/src/SkParticleDrawable.cpp | 75 +++++++++++++++++++ resources/particles/skottie_particle.json | 28 +++++++ tools/viewer/SkottieSlide.cpp | 41 ++++++++-- 8 files changed, 145 insertions(+), 6 deletions(-) create mode 100644 resources/particles/skottie_particle.json diff --git a/gm/particles.cpp b/gm/particles.cpp index dafc90fe07..75118c5b4d 100644 --- a/gm/particles.cpp +++ b/gm/particles.cpp @@ -8,6 +8,7 @@ #include "gm/gm.h" #if !defined(SK_BUILD_FOR_GOOGLE3) // Google3 doesn't build particles module +#if !defined(SK_BUILD_FOR_SKQP) // SkQP doesn't need to test particles #include "include/core/SkCanvas.h" #include "include/core/SkColor.h" @@ -99,4 +100,5 @@ DEF_GM(return new ParticlesGM("uniforms", 2.0, {250, 250}, {125, 125}, {"spin", {4.0f}}, {"color", {0.25f, 0.75f, 0.75f}}});) +#endif // SK_BUILD_FOR_SKQP #endif // SK_BUILD_FOR_GOOGLE3 diff --git a/gn/skqp_gn_args.py b/gn/skqp_gn_args.py index 5f90bb1a14..31c8f624c1 100644 --- a/gn/skqp_gn_args.py +++ b/gn/skqp_gn_args.py @@ -8,6 +8,7 @@ def GetGNArgs(api_level, debug, arch=None, ndk=None, is_android_bp=False): 'is_debug': 'true' if debug else 'false', 'skia_enable_fontmgr_android': 'false', 'skia_enable_fontmgr_empty': 'true', + 'skia_enable_particles': 'false', 'skia_enable_pdf': 'false', 'skia_enable_skottie': 'false', 'skia_enable_skshaper': 'false', diff --git a/modules/particles/BUILD.gn b/modules/particles/BUILD.gn index 746c2782ae..9c12f5a73f 100644 --- a/modules/particles/BUILD.gn +++ b/modules/particles/BUILD.gn @@ -22,6 +22,7 @@ static_library("particles") { include_dirs = [ "../../tools/timer" ] deps = [ "../..:skia", + "../skottie", "../skresources", ] sources = skia_particle_sources diff --git a/modules/particles/include/SkParticleDrawable.h b/modules/particles/include/SkParticleDrawable.h index 82a7bf0c28..5e9f4a2804 100644 --- a/modules/particles/include/SkParticleDrawable.h +++ b/modules/particles/include/SkParticleDrawable.h @@ -27,6 +27,8 @@ public: static sk_sp MakeCircle(int radius); static sk_sp MakeImage(const char* imagePath, const char* imageName, int cols, int rows); + static sk_sp MakeSkottie(const char* animPath, const char* animName, + int cols, int rows); }; #endif // SkParticleEffect_DEFINED diff --git a/modules/particles/src/BUILD.bazel b/modules/particles/src/BUILD.bazel index 7a9a5bc398..9124fcfdb2 100644 --- a/modules/particles/src/BUILD.bazel +++ b/modules/particles/src/BUILD.bazel @@ -42,6 +42,7 @@ generated_cc_atom( "//include/private:SkTPin_hdr", "//modules/particles/include:SkParticleData_hdr", "//modules/particles/include:SkParticleDrawable_hdr", + "//modules/skottie/include:Skottie_hdr", "//modules/skresources/include:SkResources_hdr", "//src/core:SkAutoMalloc_hdr", ], diff --git a/modules/particles/src/SkParticleDrawable.cpp b/modules/particles/src/SkParticleDrawable.cpp index 790eb7bf22..6abd2227b6 100644 --- a/modules/particles/src/SkParticleDrawable.cpp +++ b/modules/particles/src/SkParticleDrawable.cpp @@ -16,9 +16,12 @@ #include "include/core/SkSurface.h" #include "include/private/SkTPin.h" #include "modules/particles/include/SkParticleData.h" +#include "modules/skottie/include/Skottie.h" #include "modules/skresources/include/SkResources.h" #include "src/core/SkAutoMalloc.h" +#include + static sk_sp make_circle_image(int radius) { auto surface = SkSurface::MakeRasterN32Premul(radius * 2, radius * 2); surface->getCanvas()->clear(SK_ColorTRANSPARENT); @@ -181,10 +184,76 @@ private: sk_sp fImage; }; +class SkSkottieDrawable : public SkParticleDrawable { +public: + SkSkottieDrawable(const char* animationPath = "", const char* animationName = "") + : fPath(animationPath) + , fName(animationName) {} + + REFLECTED(SkSkottieDrawable, SkParticleDrawable) + + void draw(SkCanvas* canvas, const SkParticles& particles, int count) override { + float* animationFrames = particles.fData[SkParticles::kSpriteFrame].get(); + float* scales = particles.fData[SkParticles::kScale].get(); + float* dir[] = { + particles.fData[SkParticles::kHeadingX].get(), + particles.fData[SkParticles::kHeadingY].get(), + }; + float width = fAnimation->size().width(); + float height = fAnimation->size().height(); + + for (int i = 0; i < count; ++i) { + // get skottie frame + double frame = animationFrames[i] * fAnimation->duration() * fAnimation->fps(); + frame = SkTPin(frame, 0.0, fAnimation->duration() * fAnimation->fps()); + + // move and scale + SkAutoCanvasRestore acr(canvas, true); + float s = scales[i]; + float rads = atan2(dir[0][i], -dir[1][i]); + auto mat = SkMatrix::Translate(particles.fData[SkParticles::kPositionX][i], + particles.fData[SkParticles::kPositionY][i]) + * SkMatrix::Scale(s, s) + * SkMatrix::RotateRad(rads) + * SkMatrix::Translate(width / -2, height / -2); + canvas->concat(mat); + + // draw + fAnimation->seekFrame(frame); + fAnimation->render(canvas); + } + } + + void prepare(const skresources::ResourceProvider* resourceProvider) override { + skottie::Animation::Builder builder; + if (auto asset = resourceProvider->load(fPath.c_str(), fName.c_str())) { + SkDebugf("Loading lottie particle \"%s:%s\"\n", fPath.c_str(), fName.c_str()); + fAnimation = builder.make(reinterpret_cast(asset->data()), asset->size()); + } + if (!fAnimation) { + SkDebugf("Could not load bodymovin animation \"%s:%s\"\n", fPath.c_str(), + fName.c_str()); + } + } + + void visitFields(SkFieldVisitor* v) override { + v->visit("Path", fPath); + v->visit("Name", fName); + } + +private: + SkString fPath; + SkString fName; + + // Cached + sk_sp fAnimation; +}; + void SkParticleDrawable::RegisterDrawableTypes() { REGISTER_REFLECTED(SkParticleDrawable); REGISTER_REFLECTED(SkCircleDrawable); REGISTER_REFLECTED(SkImageDrawable); + REGISTER_REFLECTED(SkSkottieDrawable); } sk_sp SkParticleDrawable::MakeCircle(int radius) { @@ -196,3 +265,9 @@ sk_sp SkParticleDrawable::MakeImage(const char* imagePath, int cols, int rows) { return sk_sp(new SkImageDrawable(imagePath, imageName, cols, rows)); } + +sk_sp SkParticleDrawable::MakeSkottie(const char* animPath, + const char* animName, + int cols, int rows) { + return sk_sp(new SkSkottieDrawable(animPath, animName)); +} diff --git a/resources/particles/skottie_particle.json b/resources/particles/skottie_particle.json new file mode 100644 index 0000000000..1419bfc165 --- /dev/null +++ b/resources/particles/skottie_particle.json @@ -0,0 +1,28 @@ +{ + "MaxCount": 32, + "Drawable": { + "Type": "SkSkottieDrawable", + "Path": "skottie", + "Name": "skottie_sample_2.json" + }, + "Code": [ + "void effectSpawn(inout Effect effect) {", + " effect.rate = 15;", + "}", + "", + "void spawn(inout Particle p) {", + " p.lifetime = 1.0 + rand(p.seed) * 2.0;", + " p.scale = 0.25;", + "}", + "", + "void update(inout Particle p) {", + " p.frame = p.age;", + " float a = radians(rand(p.seed) * 360);", + " float invAge = 1 - p.age;", + " p.vel = float2(cos(a), sin(a)) * mix(250, 550, rand(p.seed)) * invAge * invAge;", + " p.dir = normalize(p.vel);", + "}", + "" + ], + "Bindings": [] + } \ No newline at end of file diff --git a/tools/viewer/SkottieSlide.cpp b/tools/viewer/SkottieSlide.cpp index 046d57672d..87544ea4d5 100644 --- a/tools/viewer/SkottieSlide.cpp +++ b/tools/viewer/SkottieSlide.cpp @@ -110,12 +110,41 @@ public: } }; +class TestingResourceProvider : public skresources::ResourceProvider { +public: + TestingResourceProvider() {} + + sk_sp load(const char resource_path[], const char resource_name[]) const override { + auto it = fResources.find(resource_name); + if (it != fResources.end()) { + return it->second; + } else { + return GetResourceAsData(SkOSPath::Join(resource_path, resource_name).c_str()); + } + } + + sk_sp loadImageAsset(const char resource_path[], + const char resource_name[], + const char /*resource_id*/[]) const override { + auto data = this->load(resource_path, resource_name); + return skresources::MultiFrameImageAsset::Make(data); + } + + void addPath(const char resource_name[], const SkPath& path) { + fResources[resource_name] = path.serialize(); + } + +private: + std::unordered_map> fResources; +}; + class ParticleMarker final : public Decorator { public: ~ParticleMarker() override = default; static std::unique_ptr MakeConfetti() { return std::make_unique("confetti.json"); } static std::unique_ptr MakeSine() { return std::make_unique("sinusoidal_emitter.json"); } + static std::unique_ptr MakeSkottie() { return std::make_unique("skottie_particle.json"); } explicit ParticleMarker(const char* effect_file) { SkParticleEffect::RegisterParticleTypes(); @@ -126,9 +155,8 @@ public: skjson::DOM dom(static_cast(fileData->data()), fileData->size()); SkFromJsonVisitor fromJson(dom.root()); params->visitFields(&fromJson); - // We can pass in a null pointer because the SkCircleDrawable used in confetti.json - // doesn't use the resource provider - params->prepare(nullptr); + auto provider = sk_make_sp(); + params->prepare(provider.get()); } else { SkDebugf("no particle effect file found at: %s\n", effectJsonPath.c_str()); } @@ -157,9 +185,10 @@ static const struct DecoratorRec { const char* fName; std::unique_ptr(*fFactory)(); } kDecorators[] = { - { "Simple marker", SimpleMarker::Make }, - { "Confetti", ParticleMarker::MakeConfetti }, - { "Sine Wave", ParticleMarker::MakeSine }, + { "Simple marker", SimpleMarker::Make }, + { "Confetti", ParticleMarker::MakeConfetti }, + { "Sine Wave", ParticleMarker::MakeSine }, + { "Nested Skotties", ParticleMarker::MakeSkottie }, }; } // namespace