Moved image params out to SkParticleDrawable
Added a simpler circle drawable, moved drawing code out so that frame
calculation is handled by the drawable. Fixed all the sample effects,
including some size adjustments to better create the intended effect.
Bug: skia:
Change-Id: I60af9cd6262ff98352ca8ceaf6768aef9c7e164c
Reviewed-on: https://skia-review.googlesource.com/c/193029
Reviewed-by: Brian Osman <brianosman@google.com>
Commit-Queue: Brian Osman <brianosman@google.com>
diff --git a/modules/particles/include/SkParticleDrawable.h b/modules/particles/include/SkParticleDrawable.h
new file mode 100644
index 0000000..aedfdb1
--- /dev/null
+++ b/modules/particles/include/SkParticleDrawable.h
@@ -0,0 +1,34 @@
+/*
+* Copyright 2019 Google LLC
+*
+* Use of this source code is governed by a BSD-style license that can be
+* found in the LICENSE file.
+*/
+
+#ifndef SkParticleDrawable_DEFINED
+#define SkParticleDrawable_DEFINED
+
+#include "SkReflected.h"
+
+#include "SkColor.h"
+
+class SkCanvas;
+class SkPaint;
+struct SkRSXform;
+class SkString;
+
+class SkParticleDrawable : public SkReflected {
+public:
+ REFLECTED_ABSTRACT(SkParticleDrawable, SkReflected)
+
+ virtual void draw(SkCanvas* canvas, const SkRSXform xform[], const float tex[],
+ const SkColor colors[], int count, const SkPaint* paint) = 0;
+ virtual SkPoint center() const = 0;
+
+ static void RegisterDrawableTypes();
+
+ static sk_sp<SkParticleDrawable> MakeCircle(int radius);
+ static sk_sp<SkParticleDrawable> MakeImage(const SkString& path, int cols, int rows);
+};
+
+#endif // SkParticleEffect_DEFINED
diff --git a/modules/particles/include/SkParticleEffect.h b/modules/particles/include/SkParticleEffect.h
index 22fb683..7397976 100644
--- a/modules/particles/include/SkParticleEffect.h
+++ b/modules/particles/include/SkParticleEffect.h
@@ -13,16 +13,14 @@
#include "SkCurve.h"
#include "SkParticleData.h"
#include "SkRandom.h"
-#include "SkRect.h"
#include "SkRefCnt.h"
-#include "SkString.h"
#include "SkTArray.h"
class SkAnimTimer;
class SkCanvas;
class SkFieldVisitor;
-class SkImage;
class SkParticleAffector;
+class SkParticleDrawable;
class SkParticleEmitter;
struct SkRSXform;
@@ -35,11 +33,8 @@
SkColor4f fStartColor = { 1.0f, 1.0f, 1.0f, 1.0f };
SkColor4f fEndColor = { 1.0f, 1.0f, 1.0f, 1.0f };
- // Sprite image parameters
- // TODO: Move sprite stuff in here, out of effect
- SkString fImage;
- int fImageCols = 1;
- int fImageRows = 1;
+ // Drawable (image, sprite sheet, etc.)
+ sk_sp<SkParticleDrawable> fDrawable;
// Emitter shape & parameters
sk_sp<SkParticleEmitter> fEmitter;
@@ -68,17 +63,6 @@
private:
void setCapacity(int capacity);
- int spriteCount() const { return fParams->fImageCols * fParams->fImageRows; }
- SkRect spriteRect(int i) const {
- SkASSERT(i >= 0 && i < this->spriteCount());
- int row = i / fParams->fImageCols;
- int col = i % fParams->fImageCols;
- return fImageRect.makeOffset(col * fImageRect.width(), row * fImageRect.height());
- }
- SkPoint spriteCenter() const {
- return { fImageRect.width() * 0.5f, fImageRect.height() * 0.5f };
- }
-
struct Particle {
double fTimeOfBirth;
double fTimeOfDeath;
@@ -89,8 +73,6 @@
};
sk_sp<SkParticleEffectParams> fParams;
- sk_sp<SkImage> fImage;
- SkRect fImageRect;
SkRandom fRandom;
@@ -103,7 +85,7 @@
SkAutoTMalloc<Particle> fParticles;
SkAutoTMalloc<SkRSXform> fXforms;
- SkAutoTMalloc<SkRect> fSpriteRects;
+ SkAutoTMalloc<float> fFrames;
SkAutoTMalloc<SkColor> fColors;
// Cached
diff --git a/modules/particles/particles.gni b/modules/particles/particles.gni
index 502b08c..970fc8a 100644
--- a/modules/particles/particles.gni
+++ b/modules/particles/particles.gni
@@ -9,6 +9,7 @@
skia_particle_sources = [
"$_src/SkCurve.cpp",
"$_src/SkParticleAffector.cpp",
+ "$_src/SkParticleDrawable.cpp",
"$_src/SkParticleEffect.cpp",
"$_src/SkParticleEmitter.cpp",
"$_src/SkReflected.cpp",
diff --git a/modules/particles/src/SkParticleDrawable.cpp b/modules/particles/src/SkParticleDrawable.cpp
new file mode 100644
index 0000000..fb0c150
--- /dev/null
+++ b/modules/particles/src/SkParticleDrawable.cpp
@@ -0,0 +1,153 @@
+/*
+* 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 "SkParticleDrawable.h"
+
+#include "Resources.h"
+#include "SkAutoMalloc.h"
+#include "SkCanvas.h"
+#include "SkImage.h"
+#include "SkPaint.h"
+#include "SkRect.h"
+#include "SkSurface.h"
+#include "SkString.h"
+#include "SkRSXform.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();
+}
+
+class SkCircleDrawable : public SkParticleDrawable {
+public:
+ SkCircleDrawable(int radius = 1)
+ : fRadius(radius) {
+ this->rebuild();
+ }
+
+ REFLECTED(SkCircleDrawable, SkParticleDrawable)
+
+ void draw(SkCanvas* canvas, const SkRSXform xform[], const float tex[], const SkColor colors[],
+ int count, const SkPaint* paint) override {
+ SkAutoTMalloc<SkRect> texRects(count);
+ for (int i = 0; i < count; ++i) {
+ texRects[i].set(0.0f, 0.0f, fImage->width(), fImage->height());
+ }
+ canvas->drawAtlas(fImage, xform, texRects.get(), colors, count,
+ SkBlendMode::kModulate, nullptr, paint);
+ }
+
+ SkPoint center() const override {
+ return { SkIntToScalar(fRadius), SkIntToScalar(fRadius) };
+ }
+
+ void visitFields(SkFieldVisitor* v) override {
+ v->visit("Radius", fRadius);
+ this->rebuild();
+ }
+
+private:
+ int fRadius;
+
+ void rebuild() {
+ fRadius = SkTMax(fRadius, 1);
+ if (!fImage || fImage->width() != 2 * fRadius) {
+ fImage = make_circle_image(fRadius);
+ }
+ }
+
+ // Cached
+ sk_sp<SkImage> fImage;
+};
+
+class SkImageDrawable : public SkParticleDrawable {
+public:
+ SkImageDrawable(const SkString& path = SkString(), int cols = 1, int rows = 1)
+ : fPath(path)
+ , fCols(cols)
+ , fRows(rows) {
+ this->rebuild();
+ }
+
+ REFLECTED(SkImageDrawable, SkParticleDrawable)
+
+ void draw(SkCanvas* canvas, const SkRSXform xform[], const float tex[], const SkColor colors[],
+ int count, const SkPaint* paint) override {
+ SkAutoTMalloc<SkRect> texRects(count);
+
+ SkRect baseRect = getBaseRect();
+ int frameCount = fCols * fRows;
+
+ for (int i = 0; i < count; ++i) {
+ int frame = static_cast<int>(tex[i] * frameCount + 0.5f);
+ frame = SkTPin(frame, 0, frameCount - 1);
+ int row = frame / fCols;
+ int col = frame % fCols;
+ texRects[i] = baseRect.makeOffset(col * baseRect.width(), row * baseRect.height());
+ }
+ canvas->drawAtlas(fImage, xform, texRects.get(), colors, count,
+ SkBlendMode::kModulate, nullptr, paint);
+ }
+
+ SkPoint center() const override {
+ SkRect baseRect = getBaseRect();
+ return { baseRect.width() * 0.5f, baseRect.height() * 0.5f };
+ }
+
+ void visitFields(SkFieldVisitor* v) override {
+ SkString oldPath = fPath;
+
+ v->visit("Path", fPath);
+ v->visit("Columns", fCols);
+ v->visit("Rows", fRows);
+
+ fCols = SkTMax(fCols, 1);
+ fRows = SkTMax(fRows, 1);
+ if (oldPath != fPath) {
+ this->rebuild();
+ }
+ }
+
+private:
+ SkString fPath;
+ int fCols;
+ int fRows;
+
+ SkRect getBaseRect() const {
+ return SkRect::MakeWH(static_cast<float>(fImage->width()) / fCols,
+ static_cast<float>(fImage->height() / fRows));
+ }
+
+ void rebuild() {
+ fImage = GetResourceAsImage(fPath.c_str());
+ if (!fImage) {
+ fImage = make_circle_image(1);
+ }
+ }
+
+ // Cached
+ sk_sp<SkImage> fImage;
+};
+
+void SkParticleDrawable::RegisterDrawableTypes() {
+ REGISTER_REFLECTED(SkParticleDrawable);
+ REGISTER_REFLECTED(SkCircleDrawable);
+ REGISTER_REFLECTED(SkImageDrawable);
+}
+
+sk_sp<SkParticleDrawable> SkParticleDrawable::MakeCircle(int radius) {
+ return sk_sp<SkParticleDrawable>(new SkCircleDrawable(radius));
+}
+
+sk_sp<SkParticleDrawable> SkParticleDrawable::MakeImage(const SkString& path, int cols, int rows) {
+ return sk_sp<SkParticleDrawable>(new SkImageDrawable(path, cols, rows));
+}
diff --git a/modules/particles/src/SkParticleEffect.cpp b/modules/particles/src/SkParticleEffect.cpp
index 24262da..39b8dd6 100644
--- a/modules/particles/src/SkParticleEffect.cpp
+++ b/modules/particles/src/SkParticleEffect.cpp
@@ -14,6 +14,7 @@
#include "SkNx.h"
#include "SkPaint.h"
#include "SkParticleAffector.h"
+#include "SkParticleDrawable.h"
#include "SkParticleEmitter.h"
#include "SkReflected.h"
#include "SkRSXform.h"
@@ -26,10 +27,7 @@
v->visit("StartColor", fStartColor);
v->visit("EndColor", fEndColor);
- v->visit("Image", fImage);
- v->visit("ImageCols", fImageCols);
- v->visit("ImageRows", fImageRows);
-
+ v->visit("Drawable", fDrawable);
v->visit("Emitter", fEmitter);
v->visit("Spawn", fSpawnAffectors);
@@ -45,19 +43,6 @@
, fLastTime(-1.0)
, fSpawnRemainder(0.0f) {
this->setCapacity(fParams->fMaxCount);
-
- // Load image, determine sprite rect size
- fImage = GetResourceAsImage(fParams->fImage.c_str());
- if (!fImage) {
- uint32_t whitePixel = ~0;
- SkPixmap pmap(SkImageInfo::MakeN32Premul(1, 1), &whitePixel, sizeof(uint32_t));
- fImage = SkImage::MakeRasterCopy(pmap);
- }
- int w = fImage->width();
- int h = fImage->height();
- SkASSERT(w % fParams->fImageCols == 0);
- SkASSERT(h % fParams->fImageRows == 0);
- fImageRect = SkRect::MakeIWH(w / fParams->fImageCols, h / fParams->fImageRows);
}
void SkParticleEffect::start(const SkAnimTimer& timer, bool looping) {
@@ -68,7 +53,7 @@
}
void SkParticleEffect::update(const SkAnimTimer& timer) {
- if (!timer.isRunning() || !this->isAlive()) {
+ if (!timer.isRunning() || !this->isAlive() || !fParams->fDrawable) {
return;
}
@@ -92,9 +77,9 @@
for (int i = 0; i < fCount; ++i) {
if (now > fParticles[i].fTimeOfDeath) {
// NOTE: This is fast, but doesn't preserve drawing order. Could be a problem...
- fParticles[i] = fParticles[fCount - 1];
- fSpriteRects[i] = fSpriteRects[fCount - 1];
- fColors[i] = fColors[fCount - 1];
+ fParticles[i] = fParticles[fCount - 1];
+ fFrames[i] = fFrames[fCount - 1];
+ fColors[i] = fColors[fCount - 1];
--i;
--fCount;
}
@@ -123,7 +108,7 @@
fParticles[fCount].fPV.fVelocity.fAngular = 0.0f;
fParticles[fCount].fStableRandom = fRandom;
- fSpriteRects[fCount] = this->spriteRect(0);
+ fFrames[fCount] = 0.0f;
fCount++;
}
@@ -147,10 +132,8 @@
updateParams.fStableRandom = &stableRandom;
updateParams.fParticleT = t;
- // Set sprite rect by lifetime
- int frame = static_cast<int>(t * this->spriteCount() + 0.5);
- frame = SkTPin(frame, 0, this->spriteCount() - 1);
- fSpriteRects[i] = this->spriteRect(frame);
+ // Set sprite frame by lifetime (TODO: Remove, add affector)
+ fFrames[i] = t;
// Set color by lifetime
fColors[i] = Sk4f_toL32(swizzle_rb(startColor + (colorScale * t)));
@@ -171,7 +154,7 @@
}
// Re-generate all xforms
- SkPoint ofs = this->spriteCenter();
+ SkPoint ofs = fParams->fDrawable ? fParams->fDrawable->center() : SkPoint{ 0.0f, 0.0f };
for (int i = 0; i < fCount; ++i) {
fXforms[i] = fParticles[i].fPV.fPose.asRSXform(ofs);
}
@@ -183,18 +166,18 @@
}
void SkParticleEffect::draw(SkCanvas* canvas) {
- if (this->isAlive()) {
+ if (this->isAlive() && fParams->fDrawable) {
SkPaint paint;
paint.setFilterQuality(SkFilterQuality::kMedium_SkFilterQuality);
- canvas->drawAtlas(fImage, fXforms.get(), fSpriteRects.get(), fColors.get(), fCount,
- SkBlendMode::kModulate, nullptr, &paint);
+ fParams->fDrawable->draw(
+ canvas, fXforms.get(), fFrames.get(), fColors.get(), fCount, &paint);
}
}
void SkParticleEffect::setCapacity(int capacity) {
fParticles.realloc(capacity);
fXforms.realloc(capacity);
- fSpriteRects.realloc(capacity);
+ fFrames.realloc(capacity);
fColors.realloc(capacity);
fCapacity = capacity;