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:
parent
2d51639e4b
commit
13fda2a4e7
@ -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
|
||||
|
@ -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',
|
||||
|
@ -22,6 +22,7 @@ static_library("particles") {
|
||||
include_dirs = [ "../../tools/timer" ]
|
||||
deps = [
|
||||
"../..:skia",
|
||||
"../skottie",
|
||||
"../skresources",
|
||||
]
|
||||
sources = skia_particle_sources
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
],
|
||||
|
@ -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));
|
||||
}
|
||||
|
28
resources/particles/skottie_particle.json
Normal file
28
resources/particles/skottie_particle.json
Normal 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": []
|
||||
}
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user