[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();
}));
- })
+ });
});