1*c8dee2aaSAndroid Build Coastguard Worker// 2*c8dee2aaSAndroid Build Coastguard Worker// Add some helpers for matrices. This is ported from SkMatrix.cpp and others 3*c8dee2aaSAndroid Build Coastguard Worker// to save complexity and overhead of going back and forth between C++ and JS layers. 4*c8dee2aaSAndroid Build Coastguard Worker// I would have liked to use something like DOMMatrix, except it 5*c8dee2aaSAndroid Build Coastguard Worker// isn't widely supported (would need polyfills) and it doesn't 6*c8dee2aaSAndroid Build Coastguard Worker// have a mapPoints() function (which could maybe be tacked on here). 7*c8dee2aaSAndroid Build Coastguard Worker// If DOMMatrix catches on, it would be worth re-considering this usage. 8*c8dee2aaSAndroid Build Coastguard Worker// 9*c8dee2aaSAndroid Build Coastguard Worker 10*c8dee2aaSAndroid Build Coastguard WorkerCanvasKit.Matrix = {}; 11*c8dee2aaSAndroid Build Coastguard Workerfunction sdot() { // to be called with an even number of scalar args 12*c8dee2aaSAndroid Build Coastguard Worker var acc = 0; 13*c8dee2aaSAndroid Build Coastguard Worker for (var i=0; i < arguments.length-1; i+=2) { 14*c8dee2aaSAndroid Build Coastguard Worker acc += arguments[i] * arguments[i+1]; 15*c8dee2aaSAndroid Build Coastguard Worker } 16*c8dee2aaSAndroid Build Coastguard Worker return acc; 17*c8dee2aaSAndroid Build Coastguard Worker} 18*c8dee2aaSAndroid Build Coastguard Worker 19*c8dee2aaSAndroid Build Coastguard Worker// Private general matrix functions used in both 3x3s and 4x4s. 20*c8dee2aaSAndroid Build Coastguard Worker// Return a square identity matrix of size n. 21*c8dee2aaSAndroid Build Coastguard Workervar identityN = function(n) { 22*c8dee2aaSAndroid Build Coastguard Worker var size = n*n; 23*c8dee2aaSAndroid Build Coastguard Worker var m = new Array(size); 24*c8dee2aaSAndroid Build Coastguard Worker while(size--) { 25*c8dee2aaSAndroid Build Coastguard Worker m[size] = size%(n+1) === 0 ? 1.0 : 0.0; 26*c8dee2aaSAndroid Build Coastguard Worker } 27*c8dee2aaSAndroid Build Coastguard Worker return m; 28*c8dee2aaSAndroid Build Coastguard Worker}; 29*c8dee2aaSAndroid Build Coastguard Worker 30*c8dee2aaSAndroid Build Coastguard Worker// Stride, a function for compactly representing several ways of copying an array into another. 31*c8dee2aaSAndroid Build Coastguard Worker// Write vector `v` into matrix `m`. `m` is a matrix encoded as an array in row-major 32*c8dee2aaSAndroid Build Coastguard Worker// order. Its width is passed as `width`. `v` is an array with length < (m.length/width). 33*c8dee2aaSAndroid Build Coastguard Worker// An element of `v` is copied into `m` starting at `offset` and moving `colStride` cols right 34*c8dee2aaSAndroid Build Coastguard Worker// each row. 35*c8dee2aaSAndroid Build Coastguard Worker// 36*c8dee2aaSAndroid Build Coastguard Worker// For example, a width of 4, offset of 3, and stride of -1 would put the vector here. 37*c8dee2aaSAndroid Build Coastguard Worker// _ _ 0 _ 38*c8dee2aaSAndroid Build Coastguard Worker// _ 1 _ _ 39*c8dee2aaSAndroid Build Coastguard Worker// 2 _ _ _ 40*c8dee2aaSAndroid Build Coastguard Worker// _ _ _ 3 41*c8dee2aaSAndroid Build Coastguard Worker// 42*c8dee2aaSAndroid Build Coastguard Workervar stride = function(v, m, width, offset, colStride) { 43*c8dee2aaSAndroid Build Coastguard Worker for (var i=0; i<v.length; i++) { 44*c8dee2aaSAndroid Build Coastguard Worker m[i * width + // column 45*c8dee2aaSAndroid Build Coastguard Worker (i * colStride + offset + width) % width // row 46*c8dee2aaSAndroid Build Coastguard Worker ] = v[i]; 47*c8dee2aaSAndroid Build Coastguard Worker } 48*c8dee2aaSAndroid Build Coastguard Worker return m; 49*c8dee2aaSAndroid Build Coastguard Worker}; 50*c8dee2aaSAndroid Build Coastguard Worker 51*c8dee2aaSAndroid Build Coastguard WorkerCanvasKit.Matrix.identity = function() { 52*c8dee2aaSAndroid Build Coastguard Worker return identityN(3); 53*c8dee2aaSAndroid Build Coastguard Worker}; 54*c8dee2aaSAndroid Build Coastguard Worker 55*c8dee2aaSAndroid Build Coastguard Worker// Return the inverse (if it exists) of this matrix. 56*c8dee2aaSAndroid Build Coastguard Worker// Otherwise, return null. 57*c8dee2aaSAndroid Build Coastguard WorkerCanvasKit.Matrix.invert = function(m) { 58*c8dee2aaSAndroid Build Coastguard Worker // Find the determinant by the sarrus rule. https://en.wikipedia.org/wiki/Rule_of_Sarrus 59*c8dee2aaSAndroid Build Coastguard Worker var det = m[0]*m[4]*m[8] + m[1]*m[5]*m[6] + m[2]*m[3]*m[7] 60*c8dee2aaSAndroid Build Coastguard Worker - m[2]*m[4]*m[6] - m[1]*m[3]*m[8] - m[0]*m[5]*m[7]; 61*c8dee2aaSAndroid Build Coastguard Worker if (!det) { 62*c8dee2aaSAndroid Build Coastguard Worker Debug('Warning, uninvertible matrix'); 63*c8dee2aaSAndroid Build Coastguard Worker return null; 64*c8dee2aaSAndroid Build Coastguard Worker } 65*c8dee2aaSAndroid Build Coastguard Worker // Return the inverse by the formula adj(m)/det. 66*c8dee2aaSAndroid Build Coastguard Worker // adj (adjugate) of a 3x3 is the transpose of it's cofactor matrix. 67*c8dee2aaSAndroid Build Coastguard Worker // a cofactor matrix is a matrix where each term is +-det(N) where matrix N is the 2x2 formed 68*c8dee2aaSAndroid Build Coastguard Worker // by removing the row and column we're currently setting from the source. 69*c8dee2aaSAndroid Build Coastguard Worker // the sign alternates in a checkerboard pattern with a `+` at the top left. 70*c8dee2aaSAndroid Build Coastguard Worker // that's all been combined here into one expression. 71*c8dee2aaSAndroid Build Coastguard Worker return [ 72*c8dee2aaSAndroid Build Coastguard Worker (m[4]*m[8] - m[5]*m[7])/det, (m[2]*m[7] - m[1]*m[8])/det, (m[1]*m[5] - m[2]*m[4])/det, 73*c8dee2aaSAndroid Build Coastguard Worker (m[5]*m[6] - m[3]*m[8])/det, (m[0]*m[8] - m[2]*m[6])/det, (m[2]*m[3] - m[0]*m[5])/det, 74*c8dee2aaSAndroid Build Coastguard Worker (m[3]*m[7] - m[4]*m[6])/det, (m[1]*m[6] - m[0]*m[7])/det, (m[0]*m[4] - m[1]*m[3])/det, 75*c8dee2aaSAndroid Build Coastguard Worker ]; 76*c8dee2aaSAndroid Build Coastguard Worker}; 77*c8dee2aaSAndroid Build Coastguard Worker 78*c8dee2aaSAndroid Build Coastguard Worker// Maps the given points according to the passed in matrix. 79*c8dee2aaSAndroid Build Coastguard Worker// Results are done in place. 80*c8dee2aaSAndroid Build Coastguard Worker// See SkMatrix.h::mapPoints for the docs on the math. 81*c8dee2aaSAndroid Build Coastguard WorkerCanvasKit.Matrix.mapPoints = function(matrix, ptArr) { 82*c8dee2aaSAndroid Build Coastguard Worker if (IsDebug && (ptArr.length % 2)) { 83*c8dee2aaSAndroid Build Coastguard Worker throw 'mapPoints requires an even length arr'; 84*c8dee2aaSAndroid Build Coastguard Worker } 85*c8dee2aaSAndroid Build Coastguard Worker for (var i = 0; i < ptArr.length; i+=2) { 86*c8dee2aaSAndroid Build Coastguard Worker var x = ptArr[i], y = ptArr[i+1]; 87*c8dee2aaSAndroid Build Coastguard Worker // Gx+Hy+I 88*c8dee2aaSAndroid Build Coastguard Worker var denom = matrix[6]*x + matrix[7]*y + matrix[8]; 89*c8dee2aaSAndroid Build Coastguard Worker // Ax+By+C 90*c8dee2aaSAndroid Build Coastguard Worker var xTrans = matrix[0]*x + matrix[1]*y + matrix[2]; 91*c8dee2aaSAndroid Build Coastguard Worker // Dx+Ey+F 92*c8dee2aaSAndroid Build Coastguard Worker var yTrans = matrix[3]*x + matrix[4]*y + matrix[5]; 93*c8dee2aaSAndroid Build Coastguard Worker ptArr[i] = xTrans/denom; 94*c8dee2aaSAndroid Build Coastguard Worker ptArr[i+1] = yTrans/denom; 95*c8dee2aaSAndroid Build Coastguard Worker } 96*c8dee2aaSAndroid Build Coastguard Worker return ptArr; 97*c8dee2aaSAndroid Build Coastguard Worker}; 98*c8dee2aaSAndroid Build Coastguard Worker 99*c8dee2aaSAndroid Build Coastguard Workerfunction isnumber(val) { return !isNaN(val); } 100*c8dee2aaSAndroid Build Coastguard Worker 101*c8dee2aaSAndroid Build Coastguard Worker// generalized iterative algorithm for multiplying two matrices. 102*c8dee2aaSAndroid Build Coastguard Workerfunction multiply(m1, m2, size) { 103*c8dee2aaSAndroid Build Coastguard Worker 104*c8dee2aaSAndroid Build Coastguard Worker if (IsDebug && (!m1.every(isnumber) || !m2.every(isnumber))) { 105*c8dee2aaSAndroid Build Coastguard Worker throw 'Some members of matrices are NaN m1='+m1+', m2='+m2+''; 106*c8dee2aaSAndroid Build Coastguard Worker } 107*c8dee2aaSAndroid Build Coastguard Worker if (IsDebug && (m1.length !== m2.length)) { 108*c8dee2aaSAndroid Build Coastguard Worker throw 'Undefined for matrices of different sizes. m1.length='+m1.length+', m2.length='+m2.length; 109*c8dee2aaSAndroid Build Coastguard Worker } 110*c8dee2aaSAndroid Build Coastguard Worker if (IsDebug && (size*size !== m1.length)) { 111*c8dee2aaSAndroid Build Coastguard Worker throw 'Undefined for non-square matrices. array size was '+size; 112*c8dee2aaSAndroid Build Coastguard Worker } 113*c8dee2aaSAndroid Build Coastguard Worker 114*c8dee2aaSAndroid Build Coastguard Worker var result = Array(m1.length); 115*c8dee2aaSAndroid Build Coastguard Worker for (var r = 0; r < size; r++) { 116*c8dee2aaSAndroid Build Coastguard Worker for (var c = 0; c < size; c++) { 117*c8dee2aaSAndroid Build Coastguard Worker // accumulate a sum of m1[r,k]*m2[k, c] 118*c8dee2aaSAndroid Build Coastguard Worker var acc = 0; 119*c8dee2aaSAndroid Build Coastguard Worker for (var k = 0; k < size; k++) { 120*c8dee2aaSAndroid Build Coastguard Worker acc += m1[size * r + k] * m2[size * k + c]; 121*c8dee2aaSAndroid Build Coastguard Worker } 122*c8dee2aaSAndroid Build Coastguard Worker result[r * size + c] = acc; 123*c8dee2aaSAndroid Build Coastguard Worker } 124*c8dee2aaSAndroid Build Coastguard Worker } 125*c8dee2aaSAndroid Build Coastguard Worker return result; 126*c8dee2aaSAndroid Build Coastguard Worker} 127*c8dee2aaSAndroid Build Coastguard Worker 128*c8dee2aaSAndroid Build Coastguard Worker// Accept an integer indicating the size of the matrices being multiplied (3 for 3x3), and any 129*c8dee2aaSAndroid Build Coastguard Worker// number of matrices following it. 130*c8dee2aaSAndroid Build Coastguard Workerfunction multiplyMany(size, listOfMatrices) { 131*c8dee2aaSAndroid Build Coastguard Worker if (IsDebug && (listOfMatrices.length < 2)) { 132*c8dee2aaSAndroid Build Coastguard Worker throw 'multiplication expected two or more matrices'; 133*c8dee2aaSAndroid Build Coastguard Worker } 134*c8dee2aaSAndroid Build Coastguard Worker var result = multiply(listOfMatrices[0], listOfMatrices[1], size); 135*c8dee2aaSAndroid Build Coastguard Worker var next = 2; 136*c8dee2aaSAndroid Build Coastguard Worker while (next < listOfMatrices.length) { 137*c8dee2aaSAndroid Build Coastguard Worker result = multiply(result, listOfMatrices[next], size); 138*c8dee2aaSAndroid Build Coastguard Worker next++; 139*c8dee2aaSAndroid Build Coastguard Worker } 140*c8dee2aaSAndroid Build Coastguard Worker return result; 141*c8dee2aaSAndroid Build Coastguard Worker} 142*c8dee2aaSAndroid Build Coastguard Worker 143*c8dee2aaSAndroid Build Coastguard Worker// Accept any number 3x3 of matrices as arguments, multiply them together. 144*c8dee2aaSAndroid Build Coastguard Worker// Matrix multiplication is associative but not commutative. the order of the arguments 145*c8dee2aaSAndroid Build Coastguard Worker// matters, but it does not matter that this implementation multiplies them left to right. 146*c8dee2aaSAndroid Build Coastguard WorkerCanvasKit.Matrix.multiply = function() { 147*c8dee2aaSAndroid Build Coastguard Worker return multiplyMany(3, arguments); 148*c8dee2aaSAndroid Build Coastguard Worker}; 149*c8dee2aaSAndroid Build Coastguard Worker 150*c8dee2aaSAndroid Build Coastguard Worker// Return a matrix representing a rotation by n radians. 151*c8dee2aaSAndroid Build Coastguard Worker// px, py optionally say which point the rotation should be around 152*c8dee2aaSAndroid Build Coastguard Worker// with the default being (0, 0); 153*c8dee2aaSAndroid Build Coastguard WorkerCanvasKit.Matrix.rotated = function(radians, px, py) { 154*c8dee2aaSAndroid Build Coastguard Worker px = px || 0; 155*c8dee2aaSAndroid Build Coastguard Worker py = py || 0; 156*c8dee2aaSAndroid Build Coastguard Worker var sinV = Math.sin(radians); 157*c8dee2aaSAndroid Build Coastguard Worker var cosV = Math.cos(radians); 158*c8dee2aaSAndroid Build Coastguard Worker return [ 159*c8dee2aaSAndroid Build Coastguard Worker cosV, -sinV, sdot( sinV, py, 1 - cosV, px), 160*c8dee2aaSAndroid Build Coastguard Worker sinV, cosV, sdot(-sinV, px, 1 - cosV, py), 161*c8dee2aaSAndroid Build Coastguard Worker 0, 0, 1, 162*c8dee2aaSAndroid Build Coastguard Worker ]; 163*c8dee2aaSAndroid Build Coastguard Worker}; 164*c8dee2aaSAndroid Build Coastguard Worker 165*c8dee2aaSAndroid Build Coastguard WorkerCanvasKit.Matrix.scaled = function(sx, sy, px, py) { 166*c8dee2aaSAndroid Build Coastguard Worker px = px || 0; 167*c8dee2aaSAndroid Build Coastguard Worker py = py || 0; 168*c8dee2aaSAndroid Build Coastguard Worker var m = stride([sx, sy], identityN(3), 3, 0, 1); 169*c8dee2aaSAndroid Build Coastguard Worker return stride([px-sx*px, py-sy*py], m, 3, 2, 0); 170*c8dee2aaSAndroid Build Coastguard Worker}; 171*c8dee2aaSAndroid Build Coastguard Worker 172*c8dee2aaSAndroid Build Coastguard WorkerCanvasKit.Matrix.skewed = function(kx, ky, px, py) { 173*c8dee2aaSAndroid Build Coastguard Worker px = px || 0; 174*c8dee2aaSAndroid Build Coastguard Worker py = py || 0; 175*c8dee2aaSAndroid Build Coastguard Worker var m = stride([kx, ky], identityN(3), 3, 1, -1); 176*c8dee2aaSAndroid Build Coastguard Worker return stride([-kx*px, -ky*py], m, 3, 2, 0); 177*c8dee2aaSAndroid Build Coastguard Worker}; 178*c8dee2aaSAndroid Build Coastguard Worker 179*c8dee2aaSAndroid Build Coastguard WorkerCanvasKit.Matrix.translated = function(dx, dy) { 180*c8dee2aaSAndroid Build Coastguard Worker return stride(arguments, identityN(3), 3, 2, 0); 181*c8dee2aaSAndroid Build Coastguard Worker}; 182*c8dee2aaSAndroid Build Coastguard Worker 183*c8dee2aaSAndroid Build Coastguard Worker// Functions for manipulating vectors. 184*c8dee2aaSAndroid Build Coastguard Worker// Loosely based off of SkV3 in SkM44.h but skia also has SkVec2 and Skv4. This combines them and 185*c8dee2aaSAndroid Build Coastguard Worker// works on vectors of any length. 186*c8dee2aaSAndroid Build Coastguard WorkerCanvasKit.Vector = {}; 187*c8dee2aaSAndroid Build Coastguard WorkerCanvasKit.Vector.dot = function(a, b) { 188*c8dee2aaSAndroid Build Coastguard Worker if (IsDebug && (a.length !== b.length)) { 189*c8dee2aaSAndroid Build Coastguard Worker throw 'Cannot perform dot product on arrays of different length ('+a.length+' vs '+b.length+')'; 190*c8dee2aaSAndroid Build Coastguard Worker } 191*c8dee2aaSAndroid Build Coastguard Worker return a.map(function(v, i) { return v*b[i] }).reduce(function(acc, cur) { return acc + cur; }); 192*c8dee2aaSAndroid Build Coastguard Worker}; 193*c8dee2aaSAndroid Build Coastguard WorkerCanvasKit.Vector.lengthSquared = function(v) { 194*c8dee2aaSAndroid Build Coastguard Worker return CanvasKit.Vector.dot(v, v); 195*c8dee2aaSAndroid Build Coastguard Worker}; 196*c8dee2aaSAndroid Build Coastguard WorkerCanvasKit.Vector.length = function(v) { 197*c8dee2aaSAndroid Build Coastguard Worker return Math.sqrt(CanvasKit.Vector.lengthSquared(v)); 198*c8dee2aaSAndroid Build Coastguard Worker}; 199*c8dee2aaSAndroid Build Coastguard WorkerCanvasKit.Vector.mulScalar = function(v, s) { 200*c8dee2aaSAndroid Build Coastguard Worker return v.map(function(i) { return i*s }); 201*c8dee2aaSAndroid Build Coastguard Worker}; 202*c8dee2aaSAndroid Build Coastguard WorkerCanvasKit.Vector.add = function(a, b) { 203*c8dee2aaSAndroid Build Coastguard Worker return a.map(function(v, i) { return v+b[i] }); 204*c8dee2aaSAndroid Build Coastguard Worker}; 205*c8dee2aaSAndroid Build Coastguard WorkerCanvasKit.Vector.sub = function(a, b) { 206*c8dee2aaSAndroid Build Coastguard Worker return a.map(function(v, i) { return v-b[i]; }); 207*c8dee2aaSAndroid Build Coastguard Worker}; 208*c8dee2aaSAndroid Build Coastguard WorkerCanvasKit.Vector.dist = function(a, b) { 209*c8dee2aaSAndroid Build Coastguard Worker return CanvasKit.Vector.length(CanvasKit.Vector.sub(a, b)); 210*c8dee2aaSAndroid Build Coastguard Worker}; 211*c8dee2aaSAndroid Build Coastguard WorkerCanvasKit.Vector.normalize = function(v) { 212*c8dee2aaSAndroid Build Coastguard Worker return CanvasKit.Vector.mulScalar(v, 1/CanvasKit.Vector.length(v)); 213*c8dee2aaSAndroid Build Coastguard Worker}; 214*c8dee2aaSAndroid Build Coastguard WorkerCanvasKit.Vector.cross = function(a, b) { 215*c8dee2aaSAndroid Build Coastguard Worker if (IsDebug && (a.length !== 3 || a.length !== 3)) { 216*c8dee2aaSAndroid Build Coastguard Worker throw 'Cross product is only defined for 3-dimensional vectors (a.length='+a.length+', b.length='+b.length+')'; 217*c8dee2aaSAndroid Build Coastguard Worker } 218*c8dee2aaSAndroid Build Coastguard Worker return [ 219*c8dee2aaSAndroid Build Coastguard Worker a[1]*b[2] - a[2]*b[1], 220*c8dee2aaSAndroid Build Coastguard Worker a[2]*b[0] - a[0]*b[2], 221*c8dee2aaSAndroid Build Coastguard Worker a[0]*b[1] - a[1]*b[0], 222*c8dee2aaSAndroid Build Coastguard Worker ]; 223*c8dee2aaSAndroid Build Coastguard Worker}; 224*c8dee2aaSAndroid Build Coastguard Worker 225*c8dee2aaSAndroid Build Coastguard Worker// Functions for creating and manipulating (row-major) 4x4 matrices. Accepted in place of 226*c8dee2aaSAndroid Build Coastguard Worker// SkM44 in canvas methods, for the same reasons as the 3x3 matrices above. 227*c8dee2aaSAndroid Build Coastguard Worker// ported from C++ code in SkM44.cpp 228*c8dee2aaSAndroid Build Coastguard WorkerCanvasKit.M44 = {}; 229*c8dee2aaSAndroid Build Coastguard Worker// Create a 4x4 identity matrix 230*c8dee2aaSAndroid Build Coastguard WorkerCanvasKit.M44.identity = function() { 231*c8dee2aaSAndroid Build Coastguard Worker return identityN(4); 232*c8dee2aaSAndroid Build Coastguard Worker}; 233*c8dee2aaSAndroid Build Coastguard Worker 234*c8dee2aaSAndroid Build Coastguard Worker// Anything named vec below is an array of length 3 representing a vector/point in 3D space. 235*c8dee2aaSAndroid Build Coastguard Worker// Create a 4x4 matrix representing a translate by the provided 3-vec 236*c8dee2aaSAndroid Build Coastguard WorkerCanvasKit.M44.translated = function(vec) { 237*c8dee2aaSAndroid Build Coastguard Worker return stride(vec, identityN(4), 4, 3, 0); 238*c8dee2aaSAndroid Build Coastguard Worker}; 239*c8dee2aaSAndroid Build Coastguard Worker// Create a 4x4 matrix representing a scaling by the provided 3-vec 240*c8dee2aaSAndroid Build Coastguard WorkerCanvasKit.M44.scaled = function(vec) { 241*c8dee2aaSAndroid Build Coastguard Worker return stride(vec, identityN(4), 4, 0, 1); 242*c8dee2aaSAndroid Build Coastguard Worker}; 243*c8dee2aaSAndroid Build Coastguard Worker// Create a 4x4 matrix representing a rotation about the provided axis 3-vec. 244*c8dee2aaSAndroid Build Coastguard Worker// axis does not need to be normalized. 245*c8dee2aaSAndroid Build Coastguard WorkerCanvasKit.M44.rotated = function(axisVec, radians) { 246*c8dee2aaSAndroid Build Coastguard Worker return CanvasKit.M44.rotatedUnitSinCos( 247*c8dee2aaSAndroid Build Coastguard Worker CanvasKit.Vector.normalize(axisVec), Math.sin(radians), Math.cos(radians)); 248*c8dee2aaSAndroid Build Coastguard Worker}; 249*c8dee2aaSAndroid Build Coastguard Worker// Create a 4x4 matrix representing a rotation about the provided normalized axis 3-vec. 250*c8dee2aaSAndroid Build Coastguard Worker// Rotation is provided redundantly as both sin and cos values. 251*c8dee2aaSAndroid Build Coastguard Worker// This rotate can be used when you already have the cosAngle and sinAngle values 252*c8dee2aaSAndroid Build Coastguard Worker// so you don't have to atan(cos/sin) to call roatated() which expects an angle in radians. 253*c8dee2aaSAndroid Build Coastguard Worker// this does no checking! Behavior for invalid sin or cos values or non-normalized axis vectors 254*c8dee2aaSAndroid Build Coastguard Worker// is incorrect. Prefer rotated(). 255*c8dee2aaSAndroid Build Coastguard WorkerCanvasKit.M44.rotatedUnitSinCos = function(axisVec, sinAngle, cosAngle) { 256*c8dee2aaSAndroid Build Coastguard Worker var x = axisVec[0]; 257*c8dee2aaSAndroid Build Coastguard Worker var y = axisVec[1]; 258*c8dee2aaSAndroid Build Coastguard Worker var z = axisVec[2]; 259*c8dee2aaSAndroid Build Coastguard Worker var c = cosAngle; 260*c8dee2aaSAndroid Build Coastguard Worker var s = sinAngle; 261*c8dee2aaSAndroid Build Coastguard Worker var t = 1 - c; 262*c8dee2aaSAndroid Build Coastguard Worker return [ 263*c8dee2aaSAndroid Build Coastguard Worker t*x*x + c, t*x*y - s*z, t*x*z + s*y, 0, 264*c8dee2aaSAndroid Build Coastguard Worker t*x*y + s*z, t*y*y + c, t*y*z - s*x, 0, 265*c8dee2aaSAndroid Build Coastguard Worker t*x*z - s*y, t*y*z + s*x, t*z*z + c, 0, 266*c8dee2aaSAndroid Build Coastguard Worker 0, 0, 0, 1 267*c8dee2aaSAndroid Build Coastguard Worker ]; 268*c8dee2aaSAndroid Build Coastguard Worker}; 269*c8dee2aaSAndroid Build Coastguard Worker// Create a 4x4 matrix representing a camera at eyeVec, pointed at centerVec. 270*c8dee2aaSAndroid Build Coastguard WorkerCanvasKit.M44.lookat = function(eyeVec, centerVec, upVec) { 271*c8dee2aaSAndroid Build Coastguard Worker var f = CanvasKit.Vector.normalize(CanvasKit.Vector.sub(centerVec, eyeVec)); 272*c8dee2aaSAndroid Build Coastguard Worker var u = CanvasKit.Vector.normalize(upVec); 273*c8dee2aaSAndroid Build Coastguard Worker var s = CanvasKit.Vector.normalize(CanvasKit.Vector.cross(f, u)); 274*c8dee2aaSAndroid Build Coastguard Worker 275*c8dee2aaSAndroid Build Coastguard Worker var m = CanvasKit.M44.identity(); 276*c8dee2aaSAndroid Build Coastguard Worker // set each column's top three numbers 277*c8dee2aaSAndroid Build Coastguard Worker stride(s, m, 4, 0, 0); 278*c8dee2aaSAndroid Build Coastguard Worker stride(CanvasKit.Vector.cross(s, f), m, 4, 1, 0); 279*c8dee2aaSAndroid Build Coastguard Worker stride(CanvasKit.Vector.mulScalar(f, -1), m, 4, 2, 0); 280*c8dee2aaSAndroid Build Coastguard Worker stride(eyeVec, m, 4, 3, 0); 281*c8dee2aaSAndroid Build Coastguard Worker 282*c8dee2aaSAndroid Build Coastguard Worker var m2 = CanvasKit.M44.invert(m); 283*c8dee2aaSAndroid Build Coastguard Worker if (m2 === null) { 284*c8dee2aaSAndroid Build Coastguard Worker return CanvasKit.M44.identity(); 285*c8dee2aaSAndroid Build Coastguard Worker } 286*c8dee2aaSAndroid Build Coastguard Worker return m2; 287*c8dee2aaSAndroid Build Coastguard Worker}; 288*c8dee2aaSAndroid Build Coastguard Worker// Create a 4x4 matrix representing a perspective. All arguments are scalars. 289*c8dee2aaSAndroid Build Coastguard Worker// angle is in radians. 290*c8dee2aaSAndroid Build Coastguard WorkerCanvasKit.M44.perspective = function(near, far, angle) { 291*c8dee2aaSAndroid Build Coastguard Worker if (IsDebug && (far <= near)) { 292*c8dee2aaSAndroid Build Coastguard Worker throw 'far must be greater than near when constructing M44 using perspective.'; 293*c8dee2aaSAndroid Build Coastguard Worker } 294*c8dee2aaSAndroid Build Coastguard Worker var dInv = 1 / (far - near); 295*c8dee2aaSAndroid Build Coastguard Worker var halfAngle = angle / 2; 296*c8dee2aaSAndroid Build Coastguard Worker var cot = Math.cos(halfAngle) / Math.sin(halfAngle); 297*c8dee2aaSAndroid Build Coastguard Worker return [ 298*c8dee2aaSAndroid Build Coastguard Worker cot, 0, 0, 0, 299*c8dee2aaSAndroid Build Coastguard Worker 0, cot, 0, 0, 300*c8dee2aaSAndroid Build Coastguard Worker 0, 0, (far+near)*dInv, 2*far*near*dInv, 301*c8dee2aaSAndroid Build Coastguard Worker 0, 0, -1, 1, 302*c8dee2aaSAndroid Build Coastguard Worker ]; 303*c8dee2aaSAndroid Build Coastguard Worker}; 304*c8dee2aaSAndroid Build Coastguard Worker// Returns the number at the given row and column in matrix m. 305*c8dee2aaSAndroid Build Coastguard WorkerCanvasKit.M44.rc = function(m, r, c) { 306*c8dee2aaSAndroid Build Coastguard Worker return m[r*4+c]; 307*c8dee2aaSAndroid Build Coastguard Worker}; 308*c8dee2aaSAndroid Build Coastguard Worker// Accepts any number of 4x4 matrix arguments, multiplies them left to right. 309*c8dee2aaSAndroid Build Coastguard WorkerCanvasKit.M44.multiply = function() { 310*c8dee2aaSAndroid Build Coastguard Worker return multiplyMany(4, arguments); 311*c8dee2aaSAndroid Build Coastguard Worker}; 312*c8dee2aaSAndroid Build Coastguard Worker 313*c8dee2aaSAndroid Build Coastguard Worker// Invert the 4x4 matrix if it is invertible and return it. if not, return null. 314*c8dee2aaSAndroid Build Coastguard Worker// taken from SkM44.cpp (altered to use row-major order) 315*c8dee2aaSAndroid Build Coastguard Worker// m is not altered. 316*c8dee2aaSAndroid Build Coastguard WorkerCanvasKit.M44.invert = function(m) { 317*c8dee2aaSAndroid Build Coastguard Worker if (IsDebug && !m.every(isnumber)) { 318*c8dee2aaSAndroid Build Coastguard Worker throw 'some members of matrix are NaN m='+m; 319*c8dee2aaSAndroid Build Coastguard Worker } 320*c8dee2aaSAndroid Build Coastguard Worker 321*c8dee2aaSAndroid Build Coastguard Worker var a00 = m[0]; 322*c8dee2aaSAndroid Build Coastguard Worker var a01 = m[4]; 323*c8dee2aaSAndroid Build Coastguard Worker var a02 = m[8]; 324*c8dee2aaSAndroid Build Coastguard Worker var a03 = m[12]; 325*c8dee2aaSAndroid Build Coastguard Worker var a10 = m[1]; 326*c8dee2aaSAndroid Build Coastguard Worker var a11 = m[5]; 327*c8dee2aaSAndroid Build Coastguard Worker var a12 = m[9]; 328*c8dee2aaSAndroid Build Coastguard Worker var a13 = m[13]; 329*c8dee2aaSAndroid Build Coastguard Worker var a20 = m[2]; 330*c8dee2aaSAndroid Build Coastguard Worker var a21 = m[6]; 331*c8dee2aaSAndroid Build Coastguard Worker var a22 = m[10]; 332*c8dee2aaSAndroid Build Coastguard Worker var a23 = m[14]; 333*c8dee2aaSAndroid Build Coastguard Worker var a30 = m[3]; 334*c8dee2aaSAndroid Build Coastguard Worker var a31 = m[7]; 335*c8dee2aaSAndroid Build Coastguard Worker var a32 = m[11]; 336*c8dee2aaSAndroid Build Coastguard Worker var a33 = m[15]; 337*c8dee2aaSAndroid Build Coastguard Worker 338*c8dee2aaSAndroid Build Coastguard Worker var b00 = a00 * a11 - a01 * a10; 339*c8dee2aaSAndroid Build Coastguard Worker var b01 = a00 * a12 - a02 * a10; 340*c8dee2aaSAndroid Build Coastguard Worker var b02 = a00 * a13 - a03 * a10; 341*c8dee2aaSAndroid Build Coastguard Worker var b03 = a01 * a12 - a02 * a11; 342*c8dee2aaSAndroid Build Coastguard Worker var b04 = a01 * a13 - a03 * a11; 343*c8dee2aaSAndroid Build Coastguard Worker var b05 = a02 * a13 - a03 * a12; 344*c8dee2aaSAndroid Build Coastguard Worker var b06 = a20 * a31 - a21 * a30; 345*c8dee2aaSAndroid Build Coastguard Worker var b07 = a20 * a32 - a22 * a30; 346*c8dee2aaSAndroid Build Coastguard Worker var b08 = a20 * a33 - a23 * a30; 347*c8dee2aaSAndroid Build Coastguard Worker var b09 = a21 * a32 - a22 * a31; 348*c8dee2aaSAndroid Build Coastguard Worker var b10 = a21 * a33 - a23 * a31; 349*c8dee2aaSAndroid Build Coastguard Worker var b11 = a22 * a33 - a23 * a32; 350*c8dee2aaSAndroid Build Coastguard Worker 351*c8dee2aaSAndroid Build Coastguard Worker // calculate determinate 352*c8dee2aaSAndroid Build Coastguard Worker var det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06; 353*c8dee2aaSAndroid Build Coastguard Worker var invdet = 1.0 / det; 354*c8dee2aaSAndroid Build Coastguard Worker 355*c8dee2aaSAndroid Build Coastguard Worker // bail out if the matrix is not invertible 356*c8dee2aaSAndroid Build Coastguard Worker if (det === 0 || invdet === Infinity) { 357*c8dee2aaSAndroid Build Coastguard Worker Debug('Warning, uninvertible matrix'); 358*c8dee2aaSAndroid Build Coastguard Worker return null; 359*c8dee2aaSAndroid Build Coastguard Worker } 360*c8dee2aaSAndroid Build Coastguard Worker 361*c8dee2aaSAndroid Build Coastguard Worker b00 *= invdet; 362*c8dee2aaSAndroid Build Coastguard Worker b01 *= invdet; 363*c8dee2aaSAndroid Build Coastguard Worker b02 *= invdet; 364*c8dee2aaSAndroid Build Coastguard Worker b03 *= invdet; 365*c8dee2aaSAndroid Build Coastguard Worker b04 *= invdet; 366*c8dee2aaSAndroid Build Coastguard Worker b05 *= invdet; 367*c8dee2aaSAndroid Build Coastguard Worker b06 *= invdet; 368*c8dee2aaSAndroid Build Coastguard Worker b07 *= invdet; 369*c8dee2aaSAndroid Build Coastguard Worker b08 *= invdet; 370*c8dee2aaSAndroid Build Coastguard Worker b09 *= invdet; 371*c8dee2aaSAndroid Build Coastguard Worker b10 *= invdet; 372*c8dee2aaSAndroid Build Coastguard Worker b11 *= invdet; 373*c8dee2aaSAndroid Build Coastguard Worker 374*c8dee2aaSAndroid Build Coastguard Worker // store result in row major order 375*c8dee2aaSAndroid Build Coastguard Worker var tmp = [ 376*c8dee2aaSAndroid Build Coastguard Worker a11 * b11 - a12 * b10 + a13 * b09, 377*c8dee2aaSAndroid Build Coastguard Worker a12 * b08 - a10 * b11 - a13 * b07, 378*c8dee2aaSAndroid Build Coastguard Worker a10 * b10 - a11 * b08 + a13 * b06, 379*c8dee2aaSAndroid Build Coastguard Worker a11 * b07 - a10 * b09 - a12 * b06, 380*c8dee2aaSAndroid Build Coastguard Worker 381*c8dee2aaSAndroid Build Coastguard Worker a02 * b10 - a01 * b11 - a03 * b09, 382*c8dee2aaSAndroid Build Coastguard Worker a00 * b11 - a02 * b08 + a03 * b07, 383*c8dee2aaSAndroid Build Coastguard Worker a01 * b08 - a00 * b10 - a03 * b06, 384*c8dee2aaSAndroid Build Coastguard Worker a00 * b09 - a01 * b07 + a02 * b06, 385*c8dee2aaSAndroid Build Coastguard Worker 386*c8dee2aaSAndroid Build Coastguard Worker a31 * b05 - a32 * b04 + a33 * b03, 387*c8dee2aaSAndroid Build Coastguard Worker a32 * b02 - a30 * b05 - a33 * b01, 388*c8dee2aaSAndroid Build Coastguard Worker a30 * b04 - a31 * b02 + a33 * b00, 389*c8dee2aaSAndroid Build Coastguard Worker a31 * b01 - a30 * b03 - a32 * b00, 390*c8dee2aaSAndroid Build Coastguard Worker 391*c8dee2aaSAndroid Build Coastguard Worker a22 * b04 - a21 * b05 - a23 * b03, 392*c8dee2aaSAndroid Build Coastguard Worker a20 * b05 - a22 * b02 + a23 * b01, 393*c8dee2aaSAndroid Build Coastguard Worker a21 * b02 - a20 * b04 - a23 * b00, 394*c8dee2aaSAndroid Build Coastguard Worker a20 * b03 - a21 * b01 + a22 * b00, 395*c8dee2aaSAndroid Build Coastguard Worker ]; 396*c8dee2aaSAndroid Build Coastguard Worker 397*c8dee2aaSAndroid Build Coastguard Worker 398*c8dee2aaSAndroid Build Coastguard Worker if (!tmp.every(function(val) { return !isNaN(val) && val !== Infinity && val !== -Infinity; })) { 399*c8dee2aaSAndroid Build Coastguard Worker Debug('inverted matrix contains infinities or NaN '+tmp); 400*c8dee2aaSAndroid Build Coastguard Worker return null; 401*c8dee2aaSAndroid Build Coastguard Worker } 402*c8dee2aaSAndroid Build Coastguard Worker return tmp; 403*c8dee2aaSAndroid Build Coastguard Worker}; 404*c8dee2aaSAndroid Build Coastguard Worker 405*c8dee2aaSAndroid Build Coastguard WorkerCanvasKit.M44.transpose = function(m) { 406*c8dee2aaSAndroid Build Coastguard Worker return [ 407*c8dee2aaSAndroid Build Coastguard Worker m[0], m[4], m[8], m[12], 408*c8dee2aaSAndroid Build Coastguard Worker m[1], m[5], m[9], m[13], 409*c8dee2aaSAndroid Build Coastguard Worker m[2], m[6], m[10], m[14], 410*c8dee2aaSAndroid Build Coastguard Worker m[3], m[7], m[11], m[15], 411*c8dee2aaSAndroid Build Coastguard Worker ]; 412*c8dee2aaSAndroid Build Coastguard Worker}; 413*c8dee2aaSAndroid Build Coastguard Worker 414*c8dee2aaSAndroid Build Coastguard Worker// Return the inverse of an SkM44. throw an error if it's not invertible 415*c8dee2aaSAndroid Build Coastguard WorkerCanvasKit.M44.mustInvert = function(m) { 416*c8dee2aaSAndroid Build Coastguard Worker var m2 = CanvasKit.M44.invert(m); 417*c8dee2aaSAndroid Build Coastguard Worker if (m2 === null) { 418*c8dee2aaSAndroid Build Coastguard Worker throw 'Matrix not invertible'; 419*c8dee2aaSAndroid Build Coastguard Worker } 420*c8dee2aaSAndroid Build Coastguard Worker return m2; 421*c8dee2aaSAndroid Build Coastguard Worker}; 422*c8dee2aaSAndroid Build Coastguard Worker 423*c8dee2aaSAndroid Build Coastguard Worker// returns a matrix that sets up a 3D perspective view from a given camera. 424*c8dee2aaSAndroid Build Coastguard Worker// 425*c8dee2aaSAndroid Build Coastguard Worker// area - a rect describing the viewport. (0, 0, canvas_width, canvas_height) suggested 426*c8dee2aaSAndroid Build Coastguard Worker// zscale - a scalar describing the scale of the z axis. min(width, height)/2 suggested 427*c8dee2aaSAndroid Build Coastguard Worker// cam - an object with the following attributes 428*c8dee2aaSAndroid Build Coastguard Worker// const cam = { 429*c8dee2aaSAndroid Build Coastguard Worker// 'eye' : [0, 0, 1 / Math.tan(Math.PI / 24) - 1], // a 3D point locating the camera 430*c8dee2aaSAndroid Build Coastguard Worker// 'coa' : [0, 0, 0], // center of attention - the 3D point the camera is looking at. 431*c8dee2aaSAndroid Build Coastguard Worker// 'up' : [0, 1, 0], // a unit vector pointing in the camera's up direction, because eye and 432*c8dee2aaSAndroid Build Coastguard Worker// // coa alone leave roll unspecified. 433*c8dee2aaSAndroid Build Coastguard Worker// 'near' : 0.02, // near clipping plane 434*c8dee2aaSAndroid Build Coastguard Worker// 'far' : 4, // far clipping plane 435*c8dee2aaSAndroid Build Coastguard Worker// 'angle': Math.PI / 12, // field of view in radians 436*c8dee2aaSAndroid Build Coastguard Worker// }; 437*c8dee2aaSAndroid Build Coastguard WorkerCanvasKit.M44.setupCamera = function(area, zscale, cam) { 438*c8dee2aaSAndroid Build Coastguard Worker var camera = CanvasKit.M44.lookat(cam['eye'], cam['coa'], cam['up']); 439*c8dee2aaSAndroid Build Coastguard Worker var perspective = CanvasKit.M44.perspective(cam['near'], cam['far'], cam['angle']); 440*c8dee2aaSAndroid Build Coastguard Worker var center = [(area[0] + area[2])/2, (area[1] + area[3])/2, 0]; 441*c8dee2aaSAndroid Build Coastguard Worker var viewScale = [(area[2] - area[0])/2, (area[3] - area[1])/2, zscale]; 442*c8dee2aaSAndroid Build Coastguard Worker var viewport = CanvasKit.M44.multiply( 443*c8dee2aaSAndroid Build Coastguard Worker CanvasKit.M44.translated(center), 444*c8dee2aaSAndroid Build Coastguard Worker CanvasKit.M44.scaled(viewScale)); 445*c8dee2aaSAndroid Build Coastguard Worker return CanvasKit.M44.multiply( 446*c8dee2aaSAndroid Build Coastguard Worker viewport, perspective, camera, CanvasKit.M44.mustInvert(viewport)); 447*c8dee2aaSAndroid Build Coastguard Worker}; 448*c8dee2aaSAndroid Build Coastguard Worker 449*c8dee2aaSAndroid Build Coastguard Worker// An ColorMatrix is a 4x4 color matrix that transforms the 4 color channels 450*c8dee2aaSAndroid Build Coastguard Worker// with a 1x4 matrix that post-translates those 4 channels. 451*c8dee2aaSAndroid Build Coastguard Worker// For example, the following is the layout with the scale (S) and post-transform 452*c8dee2aaSAndroid Build Coastguard Worker// (PT) items indicated. 453*c8dee2aaSAndroid Build Coastguard Worker// RS, 0, 0, 0 | RPT 454*c8dee2aaSAndroid Build Coastguard Worker// 0, GS, 0, 0 | GPT 455*c8dee2aaSAndroid Build Coastguard Worker// 0, 0, BS, 0 | BPT 456*c8dee2aaSAndroid Build Coastguard Worker// 0, 0, 0, AS | APT 457*c8dee2aaSAndroid Build Coastguard Worker// 458*c8dee2aaSAndroid Build Coastguard Worker// Much of this was hand-transcribed from SkColorMatrix.cpp, because it's easier to 459*c8dee2aaSAndroid Build Coastguard Worker// deal with a Float32Array of length 20 than to try to expose the SkColorMatrix object. 460*c8dee2aaSAndroid Build Coastguard Worker 461*c8dee2aaSAndroid Build Coastguard Workervar rScale = 0; 462*c8dee2aaSAndroid Build Coastguard Workervar gScale = 6; 463*c8dee2aaSAndroid Build Coastguard Workervar bScale = 12; 464*c8dee2aaSAndroid Build Coastguard Workervar aScale = 18; 465*c8dee2aaSAndroid Build Coastguard Worker 466*c8dee2aaSAndroid Build Coastguard Workervar rPostTrans = 4; 467*c8dee2aaSAndroid Build Coastguard Workervar gPostTrans = 9; 468*c8dee2aaSAndroid Build Coastguard Workervar bPostTrans = 14; 469*c8dee2aaSAndroid Build Coastguard Workervar aPostTrans = 19; 470*c8dee2aaSAndroid Build Coastguard Worker 471*c8dee2aaSAndroid Build Coastguard WorkerCanvasKit.ColorMatrix = {}; 472*c8dee2aaSAndroid Build Coastguard WorkerCanvasKit.ColorMatrix.identity = function() { 473*c8dee2aaSAndroid Build Coastguard Worker var m = new Float32Array(20); 474*c8dee2aaSAndroid Build Coastguard Worker m[rScale] = 1; 475*c8dee2aaSAndroid Build Coastguard Worker m[gScale] = 1; 476*c8dee2aaSAndroid Build Coastguard Worker m[bScale] = 1; 477*c8dee2aaSAndroid Build Coastguard Worker m[aScale] = 1; 478*c8dee2aaSAndroid Build Coastguard Worker return m; 479*c8dee2aaSAndroid Build Coastguard Worker}; 480*c8dee2aaSAndroid Build Coastguard Worker 481*c8dee2aaSAndroid Build Coastguard WorkerCanvasKit.ColorMatrix.scaled = function(rs, gs, bs, as) { 482*c8dee2aaSAndroid Build Coastguard Worker var m = new Float32Array(20); 483*c8dee2aaSAndroid Build Coastguard Worker m[rScale] = rs; 484*c8dee2aaSAndroid Build Coastguard Worker m[gScale] = gs; 485*c8dee2aaSAndroid Build Coastguard Worker m[bScale] = bs; 486*c8dee2aaSAndroid Build Coastguard Worker m[aScale] = as; 487*c8dee2aaSAndroid Build Coastguard Worker return m; 488*c8dee2aaSAndroid Build Coastguard Worker}; 489*c8dee2aaSAndroid Build Coastguard Worker 490*c8dee2aaSAndroid Build Coastguard Workervar rotateIndices = [ 491*c8dee2aaSAndroid Build Coastguard Worker [6, 7, 11, 12], 492*c8dee2aaSAndroid Build Coastguard Worker [0, 10, 2, 12], 493*c8dee2aaSAndroid Build Coastguard Worker [0, 1, 5, 6], 494*c8dee2aaSAndroid Build Coastguard Worker]; 495*c8dee2aaSAndroid Build Coastguard Worker// axis should be 0, 1, 2 for r, g, b 496*c8dee2aaSAndroid Build Coastguard WorkerCanvasKit.ColorMatrix.rotated = function(axis, sine, cosine) { 497*c8dee2aaSAndroid Build Coastguard Worker var m = CanvasKit.ColorMatrix.identity(); 498*c8dee2aaSAndroid Build Coastguard Worker var indices = rotateIndices[axis]; 499*c8dee2aaSAndroid Build Coastguard Worker m[indices[0]] = cosine; 500*c8dee2aaSAndroid Build Coastguard Worker m[indices[1]] = sine; 501*c8dee2aaSAndroid Build Coastguard Worker m[indices[2]] = -sine; 502*c8dee2aaSAndroid Build Coastguard Worker m[indices[3]] = cosine; 503*c8dee2aaSAndroid Build Coastguard Worker return m; 504*c8dee2aaSAndroid Build Coastguard Worker}; 505*c8dee2aaSAndroid Build Coastguard Worker 506*c8dee2aaSAndroid Build Coastguard Worker// m is a ColorMatrix (i.e. a Float32Array), and this sets the 4 "special" 507*c8dee2aaSAndroid Build Coastguard Worker// params that will translate the colors after they are multiplied by the 4x4 matrix. 508*c8dee2aaSAndroid Build Coastguard WorkerCanvasKit.ColorMatrix.postTranslate = function(m, dr, dg, db, da) { 509*c8dee2aaSAndroid Build Coastguard Worker m[rPostTrans] += dr; 510*c8dee2aaSAndroid Build Coastguard Worker m[gPostTrans] += dg; 511*c8dee2aaSAndroid Build Coastguard Worker m[bPostTrans] += db; 512*c8dee2aaSAndroid Build Coastguard Worker m[aPostTrans] += da; 513*c8dee2aaSAndroid Build Coastguard Worker return m; 514*c8dee2aaSAndroid Build Coastguard Worker}; 515*c8dee2aaSAndroid Build Coastguard Worker 516*c8dee2aaSAndroid Build Coastguard Worker// concat returns a new ColorMatrix that is the result of multiplying outer*inner 517*c8dee2aaSAndroid Build Coastguard WorkerCanvasKit.ColorMatrix.concat = function(outer, inner) { 518*c8dee2aaSAndroid Build Coastguard Worker var m = new Float32Array(20); 519*c8dee2aaSAndroid Build Coastguard Worker var index = 0; 520*c8dee2aaSAndroid Build Coastguard Worker for (var j = 0; j < 20; j += 5) { 521*c8dee2aaSAndroid Build Coastguard Worker for (var i = 0; i < 4; i++) { 522*c8dee2aaSAndroid Build Coastguard Worker m[index++] = outer[j + 0] * inner[i + 0] + 523*c8dee2aaSAndroid Build Coastguard Worker outer[j + 1] * inner[i + 5] + 524*c8dee2aaSAndroid Build Coastguard Worker outer[j + 2] * inner[i + 10] + 525*c8dee2aaSAndroid Build Coastguard Worker outer[j + 3] * inner[i + 15]; 526*c8dee2aaSAndroid Build Coastguard Worker } 527*c8dee2aaSAndroid Build Coastguard Worker m[index++] = outer[j + 0] * inner[4] + 528*c8dee2aaSAndroid Build Coastguard Worker outer[j + 1] * inner[9] + 529*c8dee2aaSAndroid Build Coastguard Worker outer[j + 2] * inner[14] + 530*c8dee2aaSAndroid Build Coastguard Worker outer[j + 3] * inner[19] + 531*c8dee2aaSAndroid Build Coastguard Worker outer[j + 4]; 532*c8dee2aaSAndroid Build Coastguard Worker } 533*c8dee2aaSAndroid Build Coastguard Worker 534*c8dee2aaSAndroid Build Coastguard Worker return m; 535*c8dee2aaSAndroid Build Coastguard Worker};