fourPointsTransform.js 3.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. /**
  2. * The algoritm is learnt from
  3. * https://franklinta.com/2014/09/08/computing-css-matrix3d-transforms/
  4. * And we made some optimization for matrix inversion.
  5. * Other similar approaches:
  6. * "cv::getPerspectiveTransform", "Direct Linear Transformation".
  7. */
  8. var LN2 = Math.log(2);
  9. function determinant(rows, rank, rowStart, rowMask, colMask, detCache) {
  10. var cacheKey = rowMask + '-' + colMask;
  11. var fullRank = rows.length;
  12. if (detCache.hasOwnProperty(cacheKey)) {
  13. return detCache[cacheKey];
  14. }
  15. if (rank === 1) {
  16. // In this case the colMask must be like: `11101111`. We can find the place of `0`.
  17. var colStart = Math.round(Math.log((1 << fullRank) - 1 & ~colMask) / LN2);
  18. return rows[rowStart][colStart];
  19. }
  20. var subRowMask = rowMask | 1 << rowStart;
  21. var subRowStart = rowStart + 1;
  22. while (rowMask & 1 << subRowStart) {
  23. subRowStart++;
  24. }
  25. var sum = 0;
  26. for (var j = 0, colLocalIdx = 0; j < fullRank; j++) {
  27. var colTag = 1 << j;
  28. if (!(colTag & colMask)) {
  29. sum += (colLocalIdx % 2 ? -1 : 1) * rows[rowStart][j] // det(subMatrix(0, j))
  30. * determinant(rows, rank - 1, subRowStart, subRowMask, colMask | colTag, detCache);
  31. colLocalIdx++;
  32. }
  33. }
  34. detCache[cacheKey] = sum;
  35. return sum;
  36. }
  37. /**
  38. * Usage:
  39. * ```js
  40. * var transformer = buildTransformer(
  41. * [10, 44, 100, 44, 100, 300, 10, 300],
  42. * [50, 54, 130, 14, 140, 330, 14, 220]
  43. * );
  44. * var out = [];
  45. * transformer && transformer([11, 33], out);
  46. * ```
  47. *
  48. * Notice: `buildTransformer` may take more than 10ms in some Android device.
  49. *
  50. * @param {Array.<number>} src source four points, [x0, y0, x1, y1, x2, y2, x3, y3]
  51. * @param {Array.<number>} dest destination four points, [x0, y0, x1, y1, x2, y2, x3, y3]
  52. * @return {Function} transformer If fail, return null/undefined.
  53. */
  54. function buildTransformer(src, dest) {
  55. var mA = [[src[0], src[1], 1, 0, 0, 0, -dest[0] * src[0], -dest[0] * src[1]], [0, 0, 0, src[0], src[1], 1, -dest[1] * src[0], -dest[1] * src[1]], [src[2], src[3], 1, 0, 0, 0, -dest[2] * src[2], -dest[2] * src[3]], [0, 0, 0, src[2], src[3], 1, -dest[3] * src[2], -dest[3] * src[3]], [src[4], src[5], 1, 0, 0, 0, -dest[4] * src[4], -dest[4] * src[5]], [0, 0, 0, src[4], src[5], 1, -dest[5] * src[4], -dest[5] * src[5]], [src[6], src[7], 1, 0, 0, 0, -dest[6] * src[6], -dest[6] * src[7]], [0, 0, 0, src[6], src[7], 1, -dest[7] * src[6], -dest[7] * src[7]]];
  56. var detCache = {};
  57. var det = determinant(mA, 8, 0, 0, 0, detCache);
  58. if (det === 0) {
  59. // can not make transformer when and only when
  60. // any three of the markers are collinear.
  61. return;
  62. } // `invert(mA) * dest`, that is, `adj(mA) / det * dest`.
  63. var vh = [];
  64. for (var i = 0; i < 8; i++) {
  65. for (var j = 0; j < 8; j++) {
  66. vh[j] == null && (vh[j] = 0);
  67. vh[j] += ((i + j) % 2 ? -1 : 1) * // det(subMatrix(i, j))
  68. determinant(mA, 7, i === 0 ? 1 : 0, 1 << i, 1 << j, detCache) / det * dest[i];
  69. }
  70. }
  71. return function (out, srcPointX, srcPointY) {
  72. var pk = srcPointX * vh[6] + srcPointY * vh[7] + 1;
  73. out[0] = (srcPointX * vh[0] + srcPointY * vh[1] + vh[2]) / pk;
  74. out[1] = (srcPointX * vh[3] + srcPointY * vh[4] + vh[5]) / pk;
  75. };
  76. }
  77. exports.buildTransformer = buildTransformer;