[canvaskit] expose SkColorFilter and add SkColorMatrix helpers

of note, this is kjlubick's 2^9 = 512'th commit into the Skia repo.

Change-Id: I635cb1db6812217358ab138cd833c0c61f676232
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/241037
Reviewed-by: Mike Reed <reed@google.com>
diff --git a/modules/canvaskit/CHANGELOG.md b/modules/canvaskit/CHANGELOG.md
index 203448f..4622029 100644
--- a/modules/canvaskit/CHANGELOG.md
+++ b/modules/canvaskit/CHANGELOG.md
@@ -11,7 +11,12 @@
  - `SkPath.offset()`, `SkPath.drawOval`
  - `SkRRect` support (`SkCanvas.drawRRect`, `SkCanvas.drawDRRect`, `CanvasKit.RRectXY`).
    Advanced users can specify the 8 individual radii, if needed.
- - `CanvasKit.computeTonalColors()`, which returns an SkColor.
+ - `CanvasKit.computeTonalColors()`, which  returns TonalColors, which has an
+   ambient SkColor and a spot SkColor.
+ - `CanvasKit.SkColorFilter` and a variety of factories. `SkPaint.setColorFilter` is the only
+   consumer of these at the moment.
+ - `CanvasKit.SkColorMatrix` with functions `.identity()`, `.scaled()`, `.concat()` and
+   others. Primarily for use with `CanvasKit.SkColorFilter.MakeMatrix`.
 
 ### Changed
  - `MakeSkVertices` uses a builder to save a copy.
diff --git a/modules/canvaskit/canvaskit_bindings.cpp b/modules/canvaskit/canvaskit_bindings.cpp
index 7750908..df91ac2 100644
--- a/modules/canvaskit/canvaskit_bindings.cpp
+++ b/modules/canvaskit/canvaskit_bindings.cpp
@@ -9,6 +9,7 @@
 #include "include/core/SkBlurTypes.h"
 #include "include/core/SkCanvas.h"
 #include "include/core/SkColor.h"
+#include "include/core/SkColorFilter.h"
 #include "include/core/SkData.h"
 #include "include/core/SkEncodedImageFormat.h"
 #include "include/core/SkFilterQuality.h"
@@ -881,6 +882,18 @@
         }))
         ;
 
+    class_<SkColorFilter>("SkColorFilter")
+        .smart_ptr<sk_sp<SkColorFilter>>("sk_sp<SkColorFilter>>")
+        .class_function("MakeBlend", &SkColorFilters::Blend)
+        .class_function("MakeCompose", &SkColorFilters::Compose)
+        .class_function("MakeLerp", &SkColorFilters::Lerp)
+        .class_function("MakeLinearToSRGBGamma", &SkColorFilters::LinearToSRGBGamma)
+        .class_function("_makeMatrix", optional_override([](uintptr_t /* float* */ fPtr) {
+            float* twentyFloats = reinterpret_cast<float*>(fPtr);
+            return SkColorFilters::Matrix(twentyFloats);
+        }))
+        .class_function("MakeSRGBToLinearGamma", &SkColorFilters::SRGBToLinearGamma);
+
     class_<SkData>("SkData")
         .smart_ptr<sk_sp<SkData>>("sk_sp<SkData>>")
         .function("size", &SkData::size);
@@ -1006,6 +1019,7 @@
                                                     float r, float g, float b, float a) {
             self.setColor({r, g, b, a});
         }))
+        .function("setColorFilter", &SkPaint::setColorFilter)
         .function("setFilterQuality", &SkPaint::setFilterQuality)
         .function("setMaskFilter", &SkPaint::setMaskFilter)
         .function("setPathEffect", &SkPaint::setPathEffect)
diff --git a/modules/canvaskit/externs.js b/modules/canvaskit/externs.js
index 07bfe1d..27f3385 100644
--- a/modules/canvaskit/externs.js
+++ b/modules/canvaskit/externs.js
@@ -146,6 +146,26 @@
 		delete: function() {},
 	},
 
+	SkColorFilter: {
+		// public API (from C++ bindings and JS interface)
+		MakeBlend: function() {},
+		MakeCompose: function() {},
+		MakeLerp: function() {},
+		MakeLinearToSRGBGamma: function() {},
+		MakeMatrix: function() {},
+		MakeSRGBToLinearGamma: function() {},
+		// private API (from C++ bindings)
+		_makeMatrix: function() {},
+	},
+
+	SkColorMatrix: {
+		concat: function() {},
+		identity: function() {},
+		postTranslate: function() {},
+		rotated: function() {},
+		scaled: function() {},
+	},
+
 	SkFont: {
 		// public API (from C++ bindings)
 		getScaleX: function() {},
diff --git a/modules/canvaskit/interface.js b/modules/canvaskit/interface.js
index d0e8d19..02c415f 100644
--- a/modules/canvaskit/interface.js
+++ b/modules/canvaskit/interface.js
@@ -123,6 +123,94 @@
     ];
   };
 
+  // An SkColorMatrix is a 4x4 color matrix that transforms the 4 color channels
+  //  with a 1x4 matrix that post-translates those 4 channels.
+  // For example, the following is the layout with the scale (S) and post-transform
+  // (PT) items indicated.
+  // RS,  0,  0,  0 | RPT
+  //  0, GS,  0,  0 | GPT
+  //  0,  0, BS,  0 | BPT
+  //  0,  0,  0, AS | APT
+  //
+  // Much of this was hand-transcribed from SkColorMatrix.cpp, because it's easier to
+  // deal with a Float32Array of length 20 than to try to expose the SkColorMatrix object.
+
+  var rScale = 0;
+  var gScale = 6;
+  var bScale = 12;
+  var aScale = 18;
+
+  var rPostTrans = 4;
+  var gPostTrans = 9;
+  var bPostTrans = 14;
+  var aPostTrans = 19;
+
+  CanvasKit.SkColorMatrix = {};
+  CanvasKit.SkColorMatrix.identity = function() {
+    var m = new Float32Array(20);
+    m[rScale] = 1;
+    m[gScale] = 1;
+    m[bScale] = 1;
+    m[aScale] = 1;
+    return m;
+  }
+
+  CanvasKit.SkColorMatrix.scaled = function(rs, gs, bs, as) {
+    var m = new Float32Array(20);
+    m[rScale] = rs;
+    m[gScale] = gs;
+    m[bScale] = bs;
+    m[aScale] = as;
+    return m;
+  }
+
+  var rotateIndices = [
+    [6, 7, 11, 12],
+    [0, 10, 2, 12],
+    [0, 1,  5,  6],
+  ];
+  // axis should be 0, 1, 2 for r, g, b
+  CanvasKit.SkColorMatrix.rotated = function(axis, sine, cosine) {
+    var m = CanvasKit.SkColorMatrix.identity();
+    var indices = rotateIndices[axis];
+    m[indices[0]] = cosine;
+    m[indices[1]] = sine;
+    m[indices[2]] = -sine;
+    m[indices[3]] = cosine;
+    return m;
+  }
+
+  // m is a SkColorMatrix (i.e. a Float32Array), and this sets the 4 "special"
+  // params that will translate the colors after they are multiplied by the 4x4 matrix.
+  CanvasKit.SkColorMatrix.postTranslate = function(m, dr, dg, db, da) {
+    m[rPostTrans] += dr;
+    m[gPostTrans] += dg;
+    m[bPostTrans] += db;
+    m[aPostTrans] += da;
+    return m;
+  }
+
+  // concat returns a new SkColorMatrix that is the result of multiplying outer*inner;
+  CanvasKit.SkColorMatrix.concat = function(outer, inner) {
+    var m = new Float32Array(20);
+    var index = 0;
+    for (var j = 0; j < 20; j += 5) {
+        for (var i = 0; i < 4; i++) {
+            m[index++] =  outer[j + 0] * inner[i + 0] +
+                          outer[j + 1] * inner[i + 5] +
+                          outer[j + 2] * inner[i + 10] +
+                          outer[j + 3] * inner[i + 15];
+        }
+        m[index++] =  outer[j + 0] * inner[4] +
+                      outer[j + 1] * inner[9] +
+                      outer[j + 2] * inner[14] +
+                      outer[j + 3] * inner[19] +
+                      outer[j + 4];
+    }
+
+    return m;
+  }
+
   CanvasKit.SkPath.prototype.addArc = function(oval, startAngle, sweepAngle) {
     // see arc() for the HTMLCanvas version
     // note input angles are degrees.
@@ -573,6 +661,19 @@
     return ok;
   }
 
+  // colorMatrix is an SkColorMatrix (e.g. Float32Array of length 20)
+  CanvasKit.SkColorFilter.MakeMatrix = function(colorMatrix) {
+    if (!colorMatrix || colorMatrix.length !== 20) {
+      SkDebug('ignoring invalid color matrix');
+      return;
+    }
+    var fptr = copy1dArray(colorMatrix, CanvasKit.HEAPF32);
+    // We know skia memcopies the floats, so we can free our memory after the call returns.
+    var m = CanvasKit.SkColorFilter._makeMatrix(fptr);
+    CanvasKit._free(fptr);
+    return m;
+  }
+
   // Returns an array of the widths of the glyphs in this string.
   CanvasKit.SkFont.prototype.getWidths = function(str) {
     // add 1 for null terminator
diff --git a/modules/canvaskit/tests/canvas.spec.js b/modules/canvaskit/tests/canvas.spec.js
index b4f4cc9..9a73cf2 100644
--- a/modules/canvaskit/tests/canvas.spec.js
+++ b/modules/canvaskit/tests/canvas.spec.js
@@ -275,4 +275,61 @@
         }));
     });
 
+    it('draws with color filters', function(done) {
+        LoadCanvasKit.then(catchException(done, () => {
+            const surface = CanvasKit.MakeCanvasSurface('test');
+            expect(surface).toBeTruthy('Could not make surface')
+            if (!surface) {
+                done();
+                return;
+            }
+            const canvas = surface.getCanvas();
+            const path = starPath(CanvasKit);
+
+            const paint = new CanvasKit.SkPaint();
+
+            const blue = CanvasKit.SkColorFilter.MakeBlend(
+                CanvasKit.BLUE, CanvasKit.BlendMode.SrcIn);
+            const red =  CanvasKit.SkColorFilter.MakeBlend(
+                CanvasKit.Color(255, 0, 0, 0.8), CanvasKit.BlendMode.SrcOver);
+            const lerp = CanvasKit.SkColorFilter.MakeLerp(0.6, red, blue);
+
+            paint.setStyle(CanvasKit.PaintStyle.Fill);
+            paint.setAntiAlias(true);
+
+            canvas.clear(CanvasKit.Color(230, 230, 230));
+
+            paint.setColorFilter(blue)
+            canvas.drawRect(CanvasKit.LTRBRect(10, 10, 60, 60), paint);
+            paint.setColorFilter(lerp)
+            canvas.drawRect(CanvasKit.LTRBRect(50, 10, 100, 60), paint);
+            paint.setColorFilter(red)
+            canvas.drawRect(CanvasKit.LTRBRect(90, 10, 140, 60), paint);
+
+            const r = CanvasKit.SkColorMatrix.rotated(0, .707, -.707);
+            const b = CanvasKit.SkColorMatrix.rotated(2, .5, .866);
+            const s = CanvasKit.SkColorMatrix.scaled(0.9, 1.5, 0.8, 0.8);
+            let cm = CanvasKit.SkColorMatrix.concat(r, s);
+            cm = CanvasKit.SkColorMatrix.concat(cm, b);
+            CanvasKit.SkColorMatrix.postTranslate(cm, 20, 0, -10, 0);
+
+            const mat = CanvasKit.SkColorFilter.MakeMatrix(cm);
+
+            const final = CanvasKit.SkColorFilter.MakeCompose(mat, lerp);
+
+            paint.setColorFilter(final)
+            canvas.drawRect(CanvasKit.LTRBRect(10, 70, 140, 120), paint);
+
+            surface.flush();
+            path.delete();
+            paint.delete();
+            blue.delete();
+            red.delete();
+            lerp.delete();
+            final.delete();
+
+            reportSurface(surface, 'colorfilters_canvas', done);
+        }));
+    });
+
 });
\ No newline at end of file
diff --git a/modules/canvaskit/tests/core.spec.js b/modules/canvaskit/tests/core.spec.js
index 1c7043d..9a6591d 100644
--- a/modules/canvaskit/tests/core.spec.js
+++ b/modules/canvaskit/tests/core.spec.js
@@ -64,6 +64,6 @@
             expect(a).toBeCloseTo(0.969, 2);
             done();
         }));
-    })
+    });
 
 });