Particles: SkImageBinding to allow sampling an image from script

Provides functionality similar to AE property maps

Change-Id: I1705706a6b7e25fbab55465f2e20d0b145330b0b
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/255977
Reviewed-by: Brian Osman <brianosman@google.com>
Commit-Queue: Brian Osman <brianosman@google.com>
diff --git a/modules/particles/src/SkParticleBinding.cpp b/modules/particles/src/SkParticleBinding.cpp
index e0d3328..e654e05 100644
--- a/modules/particles/src/SkParticleBinding.cpp
+++ b/modules/particles/src/SkParticleBinding.cpp
@@ -7,7 +7,9 @@
 
 #include "modules/particles/include/SkParticleBinding.h"
 
+#include "include/core/SkBitmap.h"
 #include "include/core/SkContourMeasure.h"
+#include "include/core/SkImage.h"
 #include "include/core/SkPath.h"
 #include "include/utils/SkParsePath.h"
 #include "include/utils/SkRandom.h"
@@ -193,6 +195,74 @@
     SkPathContours fContours;
 };
 
+// Exposes an SkBitmap as an external, callable value. p(xy) returns a float4
+class SkBitmapExternalValue : public SkParticleExternalValue {
+public:
+    SkBitmapExternalValue(const char* name, SkSL::Compiler& compiler, const SkBitmap& bitmap)
+            : SkParticleExternalValue(name, compiler, *compiler.context().fFloat4_Type)
+            , fBitmap(bitmap) {
+        SkASSERT(bitmap.colorType() == kRGBA_F32_SkColorType);
+    }
+
+    bool canCall() const override { return true; }
+    int callParameterCount() const override { return 1; }
+    void getCallParameterTypes(const SkSL::Type** outTypes) const override {
+        outTypes[0] = fCompiler.context().fFloat2_Type.get();
+    }
+
+    void call(int index, float* arguments, float* outReturn) override {
+        int x = SkTPin(static_cast<int>(arguments[0] * fBitmap.width()), 0, fBitmap.width() - 1);
+        int y = SkTPin(static_cast<int>(arguments[1] * fBitmap.height()), 0, fBitmap.height() - 1);
+        float* p = static_cast<float*>(fBitmap.getAddr(x, y));
+        memcpy(outReturn, p, 4 * sizeof(float));
+    }
+
+private:
+    SkBitmap fBitmap;
+};
+
+class SkImageBinding : public SkParticleBinding {
+public:
+    SkImageBinding(const char* name = "", const char* imagePath = "", const char* imageName = "")
+            : SkParticleBinding(name)
+            , fImagePath(imagePath)
+            , fImageName(imageName) {}
+
+    REFLECTED(SkImageBinding, SkParticleBinding)
+
+    void visitFields(SkFieldVisitor* v) override {
+        SkParticleBinding::visitFields(v);
+        v->visit("ImagePath", fImagePath);
+        v->visit("ImageName", fImageName);
+    }
+
+    std::unique_ptr<SkParticleExternalValue> toValue(SkSL::Compiler& compiler) override {
+        return std::unique_ptr<SkParticleExternalValue>(
+            new SkBitmapExternalValue(fName.c_str(), compiler, fBitmap));
+    }
+
+    void prepare(const skresources::ResourceProvider* resourceProvider) override {
+        if (auto asset = resourceProvider->loadImageAsset(fImagePath.c_str(), fImageName.c_str(),
+                                                          nullptr)) {
+            if (auto image = asset->getFrame(0)) {
+                fBitmap.allocPixels(image->imageInfo().makeColorType(kRGBA_F32_SkColorType));
+                image->readPixels(fBitmap.pixmap(), 0, 0);
+                return;
+            }
+        }
+
+        fBitmap.allocPixels(SkImageInfo::Make(1, 1, kRGBA_F32_SkColorType, kPremul_SkAlphaType));
+        fBitmap.eraseColor(SK_ColorWHITE);
+    }
+
+private:
+    SkString fImagePath;
+    SkString fImageName;
+
+    // Cached
+    SkBitmap fBitmap;
+};
+
 sk_sp<SkParticleBinding> SkParticleBinding::MakeEffectBinding(
     const char* name, sk_sp<SkParticleEffectParams> params) {
     return sk_sp<SkParticleBinding>(new SkEffectBinding(name, params));
@@ -205,6 +275,7 @@
 void SkParticleBinding::RegisterBindingTypes() {
     REGISTER_REFLECTED(SkParticleBinding);
     REGISTER_REFLECTED(SkEffectBinding);
+    REGISTER_REFLECTED(SkImageBinding);
     REGISTER_REFLECTED(SkPathBinding);
     REGISTER_REFLECTED(SkTextBinding);
 }
diff --git a/resources/particles/mandrill.json b/resources/particles/mandrill.json
new file mode 100644
index 0000000..a53efb8
--- /dev/null
+++ b/resources/particles/mandrill.json
@@ -0,0 +1,38 @@
+{
+   "MaxCount": 4000,
+   "Drawable": {
+      "Type": "SkCircleDrawable",
+      "Radius": 3
+   },
+   "EffectCode": [
+      "void effectSpawn(inout Effect effect) {",
+      "  effect.rate = 4000;",
+      "}",
+      "",
+      "void effectUpdate(inout Effect effect) {",
+      "}",
+      ""
+   ],
+   "Code": [
+      "void spawn(inout Particle p) {",
+      "  p.lifetime = 1;",
+      "  p.pos = float2(rand, rand) * 200;",
+      "  p.vel = float2(rand, rand) * 2 - 1;",
+      "  p.vel *= 20;",
+      "  p.scale = mix(0.25, 1, rand);",
+      "}",
+      "",
+      "void update(inout Particle p) {",
+      "  p.color = img(p.pos / 200);",
+      "}",
+      ""
+   ],
+   "Bindings": [
+      {
+         "Type": "SkImageBinding",
+         "Name": "img",
+         "ImagePath": "images",
+         "ImageName": "mandrill_128.png"
+      }
+   ]
+}
\ No newline at end of file