blob: 6abd2227b6b45012dbc0edc6a28236eeba63b1b5 [file] [log] [blame]
/*
* Copyright 2019 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "modules/particles/include/SkParticleDrawable.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkImage.h"
#include "include/core/SkPaint.h"
#include "include/core/SkRSXform.h"
#include "include/core/SkRect.h"
#include "include/core/SkString.h"
#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);
SkPaint paint;
paint.setAntiAlias(true);
paint.setColor(SK_ColorWHITE);
surface->getCanvas()->drawCircle(radius, radius, radius, paint);
return surface->makeImageSnapshot();
}
static inline SkRSXform make_rsxform(SkPoint ofs,
float posX, float posY, float dirX, float dirY, float scale) {
const float s = dirX * scale;
const float c = -dirY * scale;
return SkRSXform::Make(c, s,
posX + -c * ofs.fX + s * ofs.fY,
posY + -s * ofs.fX + -c * ofs.fY);
}
struct DrawAtlasArrays {
DrawAtlasArrays(const SkParticles& particles, int count, SkPoint center)
: fXforms(count)
, fRects(count)
, fColors(count) {
float* c[] = {
particles.fData[SkParticles::kColorR].get(),
particles.fData[SkParticles::kColorG].get(),
particles.fData[SkParticles::kColorB].get(),
particles.fData[SkParticles::kColorA].get(),
};
float* pos[] = {
particles.fData[SkParticles::kPositionX].get(),
particles.fData[SkParticles::kPositionY].get(),
};
float* dir[] = {
particles.fData[SkParticles::kHeadingX].get(),
particles.fData[SkParticles::kHeadingY].get(),
};
float* scale = particles.fData[SkParticles::kScale].get();
for (int i = 0; i < count; ++i) {
fXforms[i] = make_rsxform(center, pos[0][i], pos[1][i], dir[0][i], dir[1][i], scale[i]);
fColors[i] = SkColor4f{ c[0][i], c[1][i], c[2][i], c[3][i] }.toSkColor();
}
}
SkAutoTMalloc<SkRSXform> fXforms;
SkAutoTMalloc<SkRect> fRects;
SkAutoTMalloc<SkColor> fColors;
};
class SkCircleDrawable : public SkParticleDrawable {
public:
SkCircleDrawable(int radius = 1) : fRadius(radius) {}
REFLECTED(SkCircleDrawable, SkParticleDrawable)
void draw(SkCanvas* canvas, const SkParticles& particles, int count) override {
int r = std::max(fRadius, 1);
SkPoint center = { SkIntToScalar(r), SkIntToScalar(r) };
DrawAtlasArrays arrays(particles, count, center);
for (int i = 0; i < count; ++i) {
arrays.fRects[i].setIWH(fImage->width(), fImage->height());
}
SkSamplingOptions sampling(SkFilterMode::kLinear);
canvas->drawAtlas(fImage.get(),
arrays.fXforms.get(),
arrays.fRects.get(),
arrays.fColors.get(),
count,
SkBlendMode::kModulate,
sampling,
/*cullRect=*/nullptr,
/*paint=*/nullptr);
}
void prepare(const skresources::ResourceProvider*) override {
int r = std::max(fRadius, 1);
if (!fImage || fImage->width() != 2 * r) {
fImage = make_circle_image(r);
}
}
void visitFields(SkFieldVisitor* v) override {
v->visit("Radius", fRadius);
}
private:
int fRadius;
// Cached
sk_sp<SkImage> fImage;
};
class SkImageDrawable : public SkParticleDrawable {
public:
SkImageDrawable(const char* imagePath = "", const char* imageName = "",
int cols = 1, int rows = 1)
: fPath(imagePath)
, fName(imageName)
, fCols(cols)
, fRows(rows) {}
REFLECTED(SkImageDrawable, SkParticleDrawable)
void draw(SkCanvas* canvas, const SkParticles& particles, int count) override {
int cols = std::max(fCols, 1),
rows = std::max(fRows, 1);
SkRect baseRect = SkRect::MakeWH(static_cast<float>(fImage->width()) / cols,
static_cast<float>(fImage->height()) / rows);
SkPoint center = { baseRect.width() * 0.5f, baseRect.height() * 0.5f };
DrawAtlasArrays arrays(particles, count, center);
int frameCount = cols * rows;
float* spriteFrames = particles.fData[SkParticles::kSpriteFrame].get();
for (int i = 0; i < count; ++i) {
int frame = static_cast<int>(spriteFrames[i] * frameCount + 0.5f);
frame = SkTPin(frame, 0, frameCount - 1);
int row = frame / cols;
int col = frame % cols;
arrays.fRects[i] = baseRect.makeOffset(col * baseRect.width(), row * baseRect.height());
}
canvas->drawAtlas(fImage.get(),
arrays.fXforms.get(),
arrays.fRects.get(),
arrays.fColors.get(),
count,
SkBlendMode::kModulate,
SkSamplingOptions(SkFilterMode::kLinear),
/*cullRect=*/nullptr,
/*paint=*/nullptr);
}
void prepare(const skresources::ResourceProvider* resourceProvider) override {
fImage.reset();
if (auto asset = resourceProvider->loadImageAsset(fPath.c_str(), fName.c_str(), nullptr)) {
fImage = asset->getFrame(0);
}
if (!fImage) {
SkDebugf("Could not load image \"%s:%s\"\n", fPath.c_str(), fName.c_str());
fImage = make_circle_image(1);
}
}
void visitFields(SkFieldVisitor* v) override {
v->visit("Path", fPath);
v->visit("Name", fName);
v->visit("Columns", fCols);
v->visit("Rows", fRows);
}
private:
SkString fPath;
SkString fName;
int fCols;
int fRows;
// Cached
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) {
return sk_sp<SkParticleDrawable>(new SkCircleDrawable(radius));
}
sk_sp<SkParticleDrawable> SkParticleDrawable::MakeImage(const char* imagePath,
const char* imageName,
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));
}