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