diff --git a/modules/canvaskit/CHANGELOG.md b/modules/canvaskit/CHANGELOG.md index c98ed21619..6a34866204 100644 --- a/modules/canvaskit/CHANGELOG.md +++ b/modules/canvaskit/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added - - Support for DOMMatrix on all APIs that take SkMatrix (i.e. arrays or Float32Arrays of length 9). + - Support for DOMMatrix on all APIs that take SkMatrix (i.e. arrays or Float32Arrays of length 6/9/16). ### Removed - Previously deprecated functions MakeSkDashPathEffect, MakeLinearGradientShader, @@ -22,6 +22,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 always composed of floats. - localmatrix option for `SkShader.Lerp` and `SkShader.Blend`. +### Deprecated + - `SkCanvas.concat44` has been folded into concat (which now takes 3x2, 3x3, or 4x4 matrices). It will + be removed soon. ## [0.14.0] - 2020-03-18 diff --git a/modules/canvaskit/canvaskit/extra.html b/modules/canvaskit/canvaskit/extra.html index 5a9510960b..7d1e93e15e 100644 --- a/modules/canvaskit/canvaskit/extra.html +++ b/modules/canvaskit/canvaskit/extra.html @@ -734,7 +734,7 @@ const curves = { function drawCubeFace(canvas, m, color) { const trans = new CanvasKit.SkM44.translated([vSphereRadius/2, vSphereRadius/2, 0]); - canvas.concat44(CanvasKit.SkM44.multiply(trans, m, mustInvert(trans))); + canvas.concat(CanvasKit.SkM44.multiply(trans, m, mustInvert(trans))); const znormal = front(canvas.getLocalToDevice()); if (znormal < 0) { return; // skip faces facing backwards diff --git a/modules/canvaskit/canvaskit_bindings.cpp b/modules/canvaskit/canvaskit_bindings.cpp index 958d502b53..cce6e5ca6a 100644 --- a/modules/canvaskit/canvaskit_bindings.cpp +++ b/modules/canvaskit/canvaskit_bindings.cpp @@ -96,33 +96,6 @@ struct OptionalMatrix : SkMatrix { } }; -// Experimental 4x4 matrices, also represented in JS with arrays. -struct SimpleM44 { - SkScalar m0, m1, m2, m3; - SkScalar m4, m5, m6, m7; - SkScalar m8, m9, m10, m11; - SkScalar m12, m13, m14, m15; -}; - -SkM44 toSkM44(const SimpleM44& sm) { - SkM44 result( - sm.m0, sm.m1, sm.m2, sm.m3, - sm.m4, sm.m5, sm.m6, sm.m7, - sm.m8, sm.m9, sm.m10, sm.m11, - sm.m12, sm.m13, sm.m14, sm.m15); - return result; -} - -SimpleM44 toSimpleM44(const SkM44& sm) { - SimpleM44 m { - sm.rc(0,0), sm.rc(0,1), sm.rc(0,2), sm.rc(0,3), - sm.rc(1,0), sm.rc(1,1), sm.rc(1,2), sm.rc(1,3), - sm.rc(2,0), sm.rc(2,1), sm.rc(2,2), sm.rc(2,3), - sm.rc(3,0), sm.rc(3,1), sm.rc(3,2), sm.rc(3,3), - }; - return m; -} - SimpleColor4f toSimpleColor4f(const SkColor4f c) { SimpleColor4f color { c.fR, c.fG, c.fB, c.fA, @@ -903,8 +876,10 @@ EMSCRIPTEN_BINDINGS(Skia) { .function("clipRect", select_overload(&SkCanvas::clipRect)) .function("_concat", optional_override([](SkCanvas& self, uintptr_t /* SkScalar* */ mPtr) { // See comment above for uintptr_t explanation - OptionalMatrix localMatrix(mPtr); - self.concat(localMatrix); + //TODO(skbug.com/10108): make the JS side be column major. + const SkScalar* sixteenMatrixValues = reinterpret_cast(mPtr); + SkM44 m = SkM44::RowMajor(sixteenMatrixValues); + self.concat(m); })) .function("drawArc", &SkCanvas::drawArc) // _drawAtlas takes an SkColor, unlike most private functions handling color. @@ -1043,30 +1018,47 @@ EMSCRIPTEN_BINDINGS(Skia) { .function("_writePixels", optional_override([](SkCanvas& self, SimpleImageInfo di, uintptr_t /* uint8_t* */ pPtr, size_t srcRowBytes, int dstX, int dstY) { + // See comment above for uintptr_t explanation uint8_t* pixels = reinterpret_cast(pPtr); SkImageInfo dstInfo = toSkImageInfo(di); return self.writePixels(dstInfo, pixels, srcRowBytes, dstX, dstY); })) // 4x4 matrix functions - .function("saveCamera", optional_override([](SkCanvas& self, - const SimpleM44& projection, const SimpleM44& camera) { - self.saveCamera(toSkM44(projection), toSkM44(camera)); + .function("_saveCamera", optional_override([](SkCanvas& self, + uintptr_t /* SkScalar* */ pPtr, uintptr_t /* SkScalar* */ cPtr) { + // See comment above for uintptr_t explanation + const SkScalar* projectionMatrixValues = reinterpret_cast(pPtr); + const SkScalar* cameraMatrixValues = reinterpret_cast(cPtr); + SkM44 projection = SkM44::RowMajor(projectionMatrixValues); + SkM44 camera = SkM44::RowMajor(cameraMatrixValues); + self.saveCamera(projection, camera); })) - .function("concat44", optional_override([](SkCanvas& self, const SimpleM44& m) { - self.concat(toSkM44(m)); - })) - .function("getLocalToDevice", optional_override([](const SkCanvas& self)->SimpleM44 { + // Just like with getTotalMatrix, we allocate the buffer for the 16 floats to go in from + // interface.js, so it can also free them when its done. + .function("_getLocalToDevice", optional_override([](const SkCanvas& self, uintptr_t /* SkScalar* */ mPtr) { + SkScalar* sixteenMatrixValues = reinterpret_cast(mPtr); + if (!sixteenMatrixValues) { + return; // matrix cannot be null + } SkM44 m = self.getLocalToDevice(); - return toSimpleM44(m); + m.getRowMajor(sixteenMatrixValues); })) - .function("getLocalToWorld", optional_override([](const SkCanvas& self)->SimpleM44 { + .function("_getLocalToWorld", optional_override([](const SkCanvas& self, uintptr_t /* SkScalar* */ mPtr) { + SkScalar* sixteenMatrixValues = reinterpret_cast(mPtr); + if (!sixteenMatrixValues) { + return; // matrix cannot be null + } SkM44 m = self.experimental_getLocalToWorld(); - return toSimpleM44(m); + m.getRowMajor(sixteenMatrixValues); })) - .function("getLocalToCamera", optional_override([](const SkCanvas& self)->SimpleM44 { + .function("_getLocalToCamera", optional_override([](const SkCanvas& self, uintptr_t /* SkScalar* */ mPtr) { + SkScalar* sixteenMatrixValues = reinterpret_cast(mPtr); + if (!sixteenMatrixValues) { + return; // matrix cannot be null + } SkM44 m = self.experimental_getLocalToCamera(); - return toSimpleM44(m); + m.getRowMajor(sixteenMatrixValues); })); class_("SkColorFilter") @@ -1741,12 +1733,6 @@ EMSCRIPTEN_BINDINGS(Skia) { .field("cap", &StrokeOpts::cap) .field("precision", &StrokeOpts::precision); - value_array("SkM44") - .element(&SimpleM44::m0).element(&SimpleM44::m1).element(&SimpleM44::m2).element(&SimpleM44::m3) - .element(&SimpleM44::m4).element(&SimpleM44::m5).element(&SimpleM44::m6).element(&SimpleM44::m7) - .element(&SimpleM44::m8).element(&SimpleM44::m9).element(&SimpleM44::m10).element(&SimpleM44::m11) - .element(&SimpleM44::m12).element(&SimpleM44::m13).element(&SimpleM44::m14).element(&SimpleM44::m15); - value_array("SkColor4f") .element(&SimpleColor4f::r) .element(&SimpleColor4f::g) diff --git a/modules/canvaskit/externs.js b/modules/canvaskit/externs.js index 24c474298c..e71acf0851 100644 --- a/modules/canvaskit/externs.js +++ b/modules/canvaskit/externs.js @@ -196,6 +196,9 @@ var CanvasKit = { _drawAtlas: function() {}, _drawPoints: function() {}, _drawSimpleText: function() {}, + _getLocalToCamera: function() {}, + _getLocalToDevice: function() {}, + _getLocalToWorld: function() {}, _getTotalMatrix: function() {}, _readPixels: function() {}, _writePixels: function() {}, @@ -838,9 +841,13 @@ CanvasKit.SkImage.prototype.encodeToData = function() {}; CanvasKit.SkImage.prototype.makeShader = function() {}; CanvasKit.SkCanvas.prototype.concat = function() {}; +CanvasKit.SkCanvas.prototype.concat44 = function() {}; // deprecated CanvasKit.SkCanvas.prototype.drawAtlas = function() {}; CanvasKit.SkCanvas.prototype.drawPoints = function() {}; CanvasKit.SkCanvas.prototype.drawText = function() {}; +CanvasKit.SkCanvas.prototype.getLocalToCamera = function() {}; +CanvasKit.SkCanvas.prototype.getLocalToDevice = function() {}; +CanvasKit.SkCanvas.prototype.getLocalToWorld = function() {}; CanvasKit.SkCanvas.prototype.getTotalMatrix = function() {}; /** @return {Uint8Array} */ CanvasKit.SkCanvas.prototype.readPixels = function() {}; diff --git a/modules/canvaskit/helper.js b/modules/canvaskit/helper.js index bd06f3710c..b1052b7d8e 100644 --- a/modules/canvaskit/helper.js +++ b/modules/canvaskit/helper.js @@ -284,8 +284,7 @@ function copy3x3MatrixToWasm(matr) { CanvasKit.HEAPF32.set(defaultPerspective, 6 + mPtr / 4); } } else { - // Try as if it's a DOMMatrix. Reminder that DOMMatrix lists their - // column first, then row. + // Try as if it's a DOMMatrix. Reminder that DOMMatrix is column-major. var floats = Float32Array.of( matr.m11, matr.m21, matr.m41, matr.m12, matr.m22, matr.m42, @@ -296,6 +295,62 @@ function copy3x3MatrixToWasm(matr) { return mPtr; } +function copy4x4MatrixToWasm(matr) { + if (!matr) { + return nullptr; + } + var mPtr = CanvasKit._malloc(16 * 4); // 9 matrix scalars, each at 4 bytes. + if (matr.length) { + if (matr.length !== 16 && matr.length !== 6 && matr.length !== 9) { + throw 'invalid matrix size'; + } + if (matr.length === 16) { + // This should be an array or typed array. + // have to divide the pointer by 4 to "cast" it from bytes to float. + CanvasKit.HEAPF32.set(matr, mPtr / 4); + } else { + // Upscale the row-major 3x3 or 3x2 matrix into a 4x4 row-major matrix + // TODO(skbug.com/10108) This will need to change when we convert our + // JS 4x4 to be column-major. + var floats = Float32Array.of( + matr[0], matr[1], 0, matr[2], + matr[3], matr[4], 0, matr[5], + 0, 0, 0, 0, + matr[6], matr[7], 0, matr[8]); + if (matr.length === 6) { + // fix perspective for the 3x2 case (from above, they will be undefined). + floats[4*3+0]=0; + floats[4*3+1]=0; + floats[4*3+3]=1; + } + CanvasKit.HEAPF32.set(floats, mPtr / 4); + } + } else { + // Try as if it's a DOMMatrix. Reminder that DOMMatrix is column-major. + // TODO(skbug.com/10108) use toFloat32Array(). + var floats = Float32Array.of( + matr.m11, matr.m21, matr.m31, matr.m41, + matr.m12, matr.m22, matr.m32, matr.m42, + matr.m13, matr.m23, matr.m33, matr.m43, + matr.m14, matr.m24, matr.m34, matr.m44); + // have to divide the pointer by 4 to "cast" it from bytes to float. + CanvasKit.HEAPF32.set(floats, mPtr / 4); + } + return mPtr; +} + +// copies a 4x4 matrix at the given pointer into a JS array. +function copy4x4MatrixFromWasm(matrPtr) { + // read them out into an array. TODO(kjlubick): If we change SkMatrix to be + // typedArrays, then we should return a typed array here too. + var rv = new Array(16); + for (var i = 0; i < 16; i++) { + rv[i] = CanvasKit.HEAPF32[matrPtr/4 + i]; // divide by 4 to "cast" to float. + } + CanvasKit._free(matrPtr); + return rv; +} + // Caching the Float32Arrays can save having to reallocate them // over and over again. var Float32ArrayCache = {}; diff --git a/modules/canvaskit/interface.js b/modules/canvaskit/interface.js index 21eff636ee..16878613cb 100644 --- a/modules/canvaskit/interface.js +++ b/modules/canvaskit/interface.js @@ -231,8 +231,8 @@ CanvasKit.onRuntimeInitialized = function() { ]; } - // Functions for creating and manipulating 4x4 matrices. Accepted in place of SkM44 in canvas - // methods, for the same reasons as the 3x3 matrices above. + // Functions for creating and manipulating (row-major) 4x4 matrices. Accepted in place of + // SkM44 in canvas methods, for the same reasons as the 3x3 matrices above. // ported from C++ code in SkM44.cpp CanvasKit.SkM44 = {}; // Create a 4x4 identity matrix @@ -283,10 +283,10 @@ CanvasKit.onRuntimeInitialized = function() { var m = CanvasKit.SkM44.identity(); // set each column's top three numbers - stride(s, m, 4, 0, 0); + stride(s, m, 4, 0, 0); stride(CanvasKit.SkVector.cross(s, f), m, 4, 1, 0); stride(CanvasKit.SkVector.mulScalar(f, -1), m, 4, 2, 0); - stride(eyeVec, m, 4, 3, 0); + stride(eyeVec, m, 4, 3, 0); var m2 = CanvasKit.SkM44.invert(m); if (m2 === null) { @@ -808,7 +808,7 @@ CanvasKit.onRuntimeInitialized = function() { CanvasKit.SkImage.prototype.makeShader = function(xTileMode, yTileMode, localMatrix) { var localMatrixPtr = copy3x3MatrixToWasm(localMatrix); var shader = this._makeShader(xTileMode, yTileMode, localMatrixPtr); - localMatrixPtr && CanvasKit._free(localMatrixPtr); + CanvasKit._free(localMatrixPtr); return shader; } @@ -852,12 +852,17 @@ CanvasKit.onRuntimeInitialized = function() { return retVal; } + // concat takes a 3x2, a 3x3, or a 4x4 matrix and upscales it (if needed) to 4x4. This is because + // under the hood, SkCanvas uses a 4x4 matrix. CanvasKit.SkCanvas.prototype.concat = function(matr) { - var matrPtr = copy3x3MatrixToWasm(matr); + var matrPtr = copy4x4MatrixToWasm(matr); this._concat(matrPtr); - matrPtr && CanvasKit._free(matrPtr); + CanvasKit._free(matrPtr); } + // Deprecated - just use concat + CanvasKit.SkCanvas.prototype.concat44 = CanvasKit.SkCanvas.prototype.concat; + // atlas is an SkImage, e.g. from CanvasKit.MakeImageFromEncoded // srcRects and dstXforms should be CanvasKit.SkRectBuilder and CanvasKit.RSXFormBuilder // or just arrays of floats in groups of 4. @@ -940,6 +945,31 @@ CanvasKit.onRuntimeInitialized = function() { CanvasKit._free(ptr); } + // getLocalToCamera returns a 4x4 matrix. + CanvasKit.SkCanvas.prototype.getLocalToCamera = function() { + var matrPtr = CanvasKit._malloc(16 * 4); // allocate space for the matrix + // _getLocalToCamera will copy the values into the pointer. + this._getLocalToCamera(matrPtr); + return copy4x4MatrixFromWasm(matrPtr); + } + + // getLocalToDevice returns a 4x4 matrix. + CanvasKit.SkCanvas.prototype.getLocalToDevice = function() { + var matrPtr = CanvasKit._malloc(16 * 4); // allocate space for the matrix + // _getLocalToDevice will copy the values into the pointer. + this._getLocalToDevice(matrPtr); + return copy4x4MatrixFromWasm(matrPtr); + } + + // getLocalToWorld returns a 4x4 matrix. + CanvasKit.SkCanvas.prototype.getLocalToWorld = function() { + var matrPtr = CanvasKit._malloc(16 * 4); // allocate space for the matrix + // _getLocalToWorld will copy the values into the pointer. + this._getLocalToWorld(matrPtr); + return copy4x4MatrixFromWasm(matrPtr); + } + + // getTotalMatrix returns the current matrix as a 3x3 matrix. CanvasKit.SkCanvas.prototype.getTotalMatrix = function() { var matrPtr = CanvasKit._malloc(9 * 4); // allocate space for the matrix // _getTotalMatrix will copy the values into the pointer. @@ -982,6 +1012,14 @@ CanvasKit.onRuntimeInitialized = function() { return pixels; } + CanvasKit.SkCanvas.prototype.saveCamera = function(projection, camera) { + var pPtr = copy4x4MatrixToWasm(projection); + var cPtr = copy4x4MatrixToWasm(camera); + this._saveCamera(pPtr, cPtr); + CanvasKit._free(pPtr) + CanvasKit._free(cPtr); + } + // pixels is a TypedArray. No matter the input size, it will be treated as // a Uint8Array (essentially, a byte array). CanvasKit.SkCanvas.prototype.writePixels = function(pixels, srcWidth, srcHeight, @@ -1025,7 +1063,7 @@ CanvasKit.onRuntimeInitialized = function() { var matrPtr = copy3x3MatrixToWasm(matr); var imgF = CanvasKit.SkImageFilter._MakeMatrixTransform(matrPtr, filterQuality, input); - matrPtr && CanvasKit._free(matrPtr); + CanvasKit._free(matrPtr); return imgF; } @@ -1099,7 +1137,7 @@ CanvasKit.onRuntimeInitialized = function() { var lgs = CanvasKit._MakeLinearGradientShader(start, end, colorPtr, posPtr, colors.length, mode, flags, localMatrixPtr); - localMatrixPtr && CanvasKit._free(localMatrixPtr); + CanvasKit._free(localMatrixPtr); CanvasKit._free(colorPtr); CanvasKit._free(posPtr); return lgs; @@ -1114,7 +1152,7 @@ CanvasKit.onRuntimeInitialized = function() { var rgs = CanvasKit._MakeRadialGradientShader(center, radius, colorPtr, posPtr, colors.length, mode, flags, localMatrixPtr); - localMatrixPtr && CanvasKit._free(localMatrixPtr); + CanvasKit._free(localMatrixPtr); CanvasKit._free(colorPtr); CanvasKit._free(posPtr); return rgs; @@ -1133,7 +1171,7 @@ CanvasKit.onRuntimeInitialized = function() { startAngle, endAngle, flags, localMatrixPtr); - localMatrixPtr && CanvasKit._free(localMatrixPtr); + CanvasKit._free(localMatrixPtr); CanvasKit._free(colorPtr); CanvasKit._free(posPtr); return sgs; @@ -1150,7 +1188,7 @@ CanvasKit.onRuntimeInitialized = function() { start, startRadius, end, endRadius, colorPtr, posPtr, colors.length, mode, flags, localMatrixPtr); - localMatrixPtr && CanvasKit._free(localMatrixPtr); + CanvasKit._free(localMatrixPtr); CanvasKit._free(colorPtr); CanvasKit._free(posPtr); return rgs; diff --git a/modules/canvaskit/perf/matrix.bench.js b/modules/canvaskit/perf/matrix.bench.js index d3bb49d053..68b85bcc45 100644 --- a/modules/canvaskit/perf/matrix.bench.js +++ b/modules/canvaskit/perf/matrix.bench.js @@ -137,7 +137,7 @@ describe('SkM44 (4x4 matrix)', () => { } function test(ctx) { - ctx.canvas.concat44(matr); + ctx.canvas.concat(matr); } function teardown(ctx) { diff --git a/modules/canvaskit/rt_shader.js b/modules/canvaskit/rt_shader.js index 94bf072c91..c3771693d1 100644 --- a/modules/canvaskit/rt_shader.js +++ b/modules/canvaskit/rt_shader.js @@ -7,7 +7,7 @@ CanvasKit._extraInitializations.push(function() { // Our array has 4 bytes per float, so be sure to account for that before // sending it over the wire. var rts = this._makeShader(fptr, floats.length * 4, !!isOpaque, localMatrixPtr); - localMatrixPtr && CanvasKit._free(localMatrixPtr); + CanvasKit._free(localMatrixPtr); return rts; } @@ -27,7 +27,7 @@ CanvasKit._extraInitializations.push(function() { // sending it over the wire. var rts = this._makeShaderWithChildren(fptr, floats.length * 4, !!isOpaque, childrenPointers, barePointers.length, localMatrixPtr); - localMatrixPtr && CanvasKit._free(localMatrixPtr); + CanvasKit._free(localMatrixPtr); return rts; } }); \ No newline at end of file diff --git a/modules/canvaskit/tests/canvas.spec.js b/modules/canvaskit/tests/canvas.spec.js index 3270194413..056783f07f 100644 --- a/modules/canvaskit/tests/canvas.spec.js +++ b/modules/canvaskit/tests/canvas.spec.js @@ -591,8 +591,8 @@ describe('Canvas Behavior', () => { matr = canvas.getLocalToCamera(); expect(matr).toEqual(CanvasKit.SkM44.identity()); - canvas.concat44(CanvasKit.SkM44.rotated([0, 1, 0], Math.PI/4)); - canvas.concat44(CanvasKit.SkM44.rotated([1, 0, 1], Math.PI/8)); + canvas.concat(CanvasKit.SkM44.rotated([0, 1, 0], Math.PI/4)); + canvas.concat(CanvasKit.SkM44.rotated([1, 0, 1], Math.PI/8)); const expected = CanvasKit.SkM44.multiply( CanvasKit.SkM44.rotated([0, 1, 0], Math.PI/4), @@ -621,7 +621,7 @@ describe('Canvas Behavior', () => { CanvasKit.SkM44.rotated([0, 0, 1], Math.PI/16), CanvasKit.SkM44.translated([-CANVAS_WIDTH/2, 0, 0]), ); - canvas.concat44(turn); + canvas.concat(turn); // Draw some stripes to help the eye detect the turn const stripeWidth = 10; diff --git a/modules/canvaskit/tests/core.spec.js b/modules/canvaskit/tests/core.spec.js index 76085ba129..137753aa95 100644 --- a/modules/canvaskit/tests/core.spec.js +++ b/modules/canvaskit/tests/core.spec.js @@ -606,6 +606,35 @@ describe('Core canvas behavior', () => { paint.delete(); shader.delete(); }); + + const radiansToDegrees = (rad) => { + return (rad / Math.PI) * 180; + } + + // this should draw the same as concat_with4x4_canvas + gm('concat_dommatrix', (canvas) => { + const path = starPath(CanvasKit, CANVAS_WIDTH/2, CANVAS_HEIGHT/2); + const paint = new CanvasKit.SkPaint(); + paint.setAntiAlias(true); + canvas.clear(CanvasKit.WHITE); + canvas.concat(new DOMMatrix().translate(CANVAS_WIDTH/2, 0, 0)); + canvas.concat(new DOMMatrix().rotateAxisAngle(1, 0, 0, radiansToDegrees(Math.PI/3))); + canvas.concat(new DOMMatrix().rotateAxisAngle(0, 1, 0, radiansToDegrees(Math.PI/4))); + canvas.concat(new DOMMatrix().rotateAxisAngle(0, 0, 1, radiansToDegrees(Math.PI/16))); + canvas.concat(new DOMMatrix().translate(-CANVAS_WIDTH/2, 0, 0)); + + // Draw some stripes to help the eye detect the turn + const stripeWidth = 10; + paint.setColor(CanvasKit.BLACK); + for (let i = 0; i < CANVAS_WIDTH; i += 2*stripeWidth) { + canvas.drawRect(CanvasKit.LTRBRect(i, 0, i + stripeWidth, CANVAS_HEIGHT), paint); + } + + paint.setColor(CanvasKit.YELLOW); + canvas.drawPath(path, paint); + paint.delete(); + path.delete(); + }); }); // end describe('DOMMatrix support') });