/* * 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 "SkAutoMalloc.h" #include "SkCanvas.h" #include "SkImage.h" #include "SkPaint.h" #include "SkParticleData.h" #include "SkRect.h" #include "SkSurface.h" #include "SkString.h" #include "SkRSXform.h" static sk_sp 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(); } struct DrawAtlasArrays { DrawAtlasArrays(const SkParticleState particles[], int count, SkPoint center) : fXforms(count) , fRects(count) , fColors(count) { for (int i = 0; i < count; ++i) { fXforms[i] = particles[i].fPose.asRSXform(center); fColors[i] = particles[i].fColor.toSkColor(); } } SkAutoTMalloc fXforms; SkAutoTMalloc fRects; SkAutoTMalloc fColors; }; class SkCircleDrawable : public SkParticleDrawable { public: SkCircleDrawable(int radius = 1) : fRadius(radius) { this->rebuild(); } REFLECTED(SkCircleDrawable, SkParticleDrawable) void draw(SkCanvas* canvas, const SkParticleState particles[], int count, const SkPaint* paint) override { SkPoint center = { SkIntToScalar(fRadius), SkIntToScalar(fRadius) }; DrawAtlasArrays arrays(particles, count, center); for (int i = 0; i < count; ++i) { arrays.fRects[i].set(0.0f, 0.0f, fImage->width(), fImage->height()); } canvas->drawAtlas(fImage, arrays.fXforms.get(), arrays.fRects.get(), arrays.fColors.get(), count, SkBlendMode::kModulate, nullptr, paint); } 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 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 SkParticleState particles[], int count, const SkPaint* paint) override { SkRect baseRect = getBaseRect(); SkPoint center = { baseRect.width() * 0.5f, baseRect.height() * 0.5f }; DrawAtlasArrays arrays(particles, count, center); int frameCount = fCols * fRows; for (int i = 0; i < count; ++i) { int frame = static_cast(particles[i].fFrame * frameCount + 0.5f); frame = SkTPin(frame, 0, frameCount - 1); int row = frame / fCols; int col = frame % fCols; arrays.fRects[i] = baseRect.makeOffset(col * baseRect.width(), row * baseRect.height()); } canvas->drawAtlas(fImage, arrays.fXforms.get(), arrays.fRects.get(), arrays.fColors.get(), count, SkBlendMode::kModulate, nullptr, paint); } 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(fImage->width()) / fCols, static_cast(fImage->height() / fRows)); } void rebuild() { fImage = SkImage::MakeFromEncoded(SkData::MakeFromFileName(fPath.c_str())); if (!fImage) { if (!fPath.isEmpty()) { SkDebugf("Could not load image \"%s\"\n", fPath.c_str()); } fImage = make_circle_image(1); } } // Cached sk_sp fImage; }; void SkParticleDrawable::RegisterDrawableTypes() { REGISTER_REFLECTED(SkParticleDrawable); REGISTER_REFLECTED(SkCircleDrawable); REGISTER_REFLECTED(SkImageDrawable); } sk_sp SkParticleDrawable::MakeCircle(int radius) { return sk_sp(new SkCircleDrawable(radius)); } sk_sp SkParticleDrawable::MakeImage(const SkString& path, int cols, int rows) { return sk_sp(new SkImageDrawable(path, cols, rows)); }