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 <fmalita@google.com>
Commit-Queue: Jorge Betancourt <jmbetancourt@google.com>
This commit is contained in:
Jorge Betancourt 2022-05-25 09:45:28 -04:00 committed by SkCQ
parent 2d51639e4b
commit 13fda2a4e7
8 changed files with 145 additions and 6 deletions

View File

@ -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

View File

@ -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',

View File

@ -22,6 +22,7 @@ static_library("particles") {
include_dirs = [ "../../tools/timer" ]
deps = [
"../..:skia",
"../skottie",
"../skresources",
]
sources = skia_particle_sources

View File

@ -27,6 +27,8 @@ public:
static sk_sp<SkParticleDrawable> MakeCircle(int radius);
static sk_sp<SkParticleDrawable> MakeImage(const char* imagePath, const char* imageName,
int cols, int rows);
static sk_sp<SkParticleDrawable> MakeSkottie(const char* animPath, const char* animName,
int cols, int rows);
};
#endif // SkParticleEffect_DEFINED

View File

@ -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",
],

View File

@ -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 <math.h>
static sk_sp<SkImage> 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<SkImage> 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<const char*>(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<skottie::Animation> fAnimation;
};
void SkParticleDrawable::RegisterDrawableTypes() {
REGISTER_REFLECTED(SkParticleDrawable);
REGISTER_REFLECTED(SkCircleDrawable);
REGISTER_REFLECTED(SkImageDrawable);
REGISTER_REFLECTED(SkSkottieDrawable);
}
sk_sp<SkParticleDrawable> SkParticleDrawable::MakeCircle(int radius) {
@ -196,3 +265,9 @@ sk_sp<SkParticleDrawable> SkParticleDrawable::MakeImage(const char* imagePath,
int cols, int rows) {
return sk_sp<SkParticleDrawable>(new SkImageDrawable(imagePath, imageName, cols, rows));
}
sk_sp<SkParticleDrawable> SkParticleDrawable::MakeSkottie(const char* animPath,
const char* animName,
int cols, int rows) {
return sk_sp<SkParticleDrawable>(new SkSkottieDrawable(animPath, animName));
}

View File

@ -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": []
}

View File

@ -110,12 +110,41 @@ public:
}
};
class TestingResourceProvider : public skresources::ResourceProvider {
public:
TestingResourceProvider() {}
sk_sp<SkData> 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<skresources::ImageAsset> 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<std::string, sk_sp<SkData>> fResources;
};
class ParticleMarker final : public Decorator {
public:
~ParticleMarker() override = default;
static std::unique_ptr<Decorator> MakeConfetti() { return std::make_unique<ParticleMarker>("confetti.json"); }
static std::unique_ptr<Decorator> MakeSine() { return std::make_unique<ParticleMarker>("sinusoidal_emitter.json"); }
static std::unique_ptr<Decorator> MakeSkottie() { return std::make_unique<ParticleMarker>("skottie_particle.json"); }
explicit ParticleMarker(const char* effect_file) {
SkParticleEffect::RegisterParticleTypes();
@ -126,9 +155,8 @@ public:
skjson::DOM dom(static_cast<const char*>(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<TestingResourceProvider>();
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<Decorator>(*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