fourPointsTransform.ts 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  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. const LN2 = Math.log(2);
  9. function determinant(
  10. rows: number[][],
  11. rank: number,
  12. rowStart: number,
  13. rowMask: number,
  14. colMask: number,
  15. detCache: {[key: string]: number}
  16. ) {
  17. const cacheKey = rowMask + '-' + colMask;
  18. const fullRank = rows.length;
  19. if (detCache.hasOwnProperty(cacheKey)) {
  20. return detCache[cacheKey];
  21. }
  22. if (rank === 1) {
  23. // In this case the colMask must be like: `11101111`. We can find the place of `0`.
  24. const colStart = Math.round(Math.log(((1 << fullRank) - 1) & ~colMask) / LN2);
  25. return rows[rowStart][colStart];
  26. }
  27. const subRowMask = rowMask | (1 << rowStart);
  28. let subRowStart = rowStart + 1;
  29. while (rowMask & (1 << subRowStart)) {
  30. subRowStart++;
  31. }
  32. let sum = 0;
  33. for (let j = 0, colLocalIdx = 0; j < fullRank; j++) {
  34. const colTag = 1 << j;
  35. if (!(colTag & colMask)) {
  36. sum += (colLocalIdx % 2 ? -1 : 1) * rows[rowStart][j]
  37. // det(subMatrix(0, j))
  38. * determinant(rows, rank - 1, subRowStart, subRowMask, colMask | colTag, detCache);
  39. colLocalIdx++;
  40. }
  41. }
  42. detCache[cacheKey] = sum;
  43. return sum;
  44. }
  45. /**
  46. * Usage:
  47. * ```js
  48. * const transformer = buildTransformer(
  49. * [10, 44, 100, 44, 100, 300, 10, 300],
  50. * [50, 54, 130, 14, 140, 330, 14, 220]
  51. * );
  52. * const out = [];
  53. * transformer && transformer([11, 33], out);
  54. * ```
  55. *
  56. * Notice: `buildTransformer` may take more than 10ms in some Android device.
  57. *
  58. * @param src source four points, [x0, y0, x1, y1, x2, y2, x3, y3]
  59. * @param dest destination four points, [x0, y0, x1, y1, x2, y2, x3, y3]
  60. * @return transformer If fail, return null/undefined.
  61. */
  62. export function buildTransformer(src: number[], dest: number[]) {
  63. const mA = [
  64. [src[0], src[1], 1, 0, 0, 0, -dest[0] * src[0], -dest[0] * src[1]],
  65. [0, 0, 0, src[0], src[1], 1, -dest[1] * src[0], -dest[1] * src[1]],
  66. [src[2], src[3], 1, 0, 0, 0, -dest[2] * src[2], -dest[2] * src[3]],
  67. [0, 0, 0, src[2], src[3], 1, -dest[3] * src[2], -dest[3] * src[3]],
  68. [src[4], src[5], 1, 0, 0, 0, -dest[4] * src[4], -dest[4] * src[5]],
  69. [0, 0, 0, src[4], src[5], 1, -dest[5] * src[4], -dest[5] * src[5]],
  70. [src[6], src[7], 1, 0, 0, 0, -dest[6] * src[6], -dest[6] * src[7]],
  71. [0, 0, 0, src[6], src[7], 1, -dest[7] * src[6], -dest[7] * src[7]]
  72. ];
  73. const detCache = {};
  74. const det = determinant(mA, 8, 0, 0, 0, detCache);
  75. if (det === 0) {
  76. // can not make transformer when and only when
  77. // any three of the markers are collinear.
  78. return;
  79. }
  80. // `invert(mA) * dest`, that is, `adj(mA) / det * dest`.
  81. const vh: number[] = [];
  82. for (let i = 0; i < 8; i++) {
  83. for (let j = 0; j < 8; j++) {
  84. vh[j] == null && (vh[j] = 0);
  85. vh[j] += ((i + j) % 2 ? -1 : 1)
  86. // det(subMatrix(i, j))
  87. * determinant(mA, 7, i === 0 ? 1 : 0, 1 << i, 1 << j, detCache)
  88. / det * dest[i];
  89. }
  90. }
  91. return function (out: number[], srcPointX: number, srcPointY: number) {
  92. const pk = srcPointX * vh[6] + srcPointY * vh[7] + 1;
  93. out[0] = (srcPointX * vh[0] + srcPointY * vh[1] + vh[2]) / pk;
  94. out[1] = (srcPointX * vh[3] + srcPointY * vh[4] + vh[5]) / pk;
  95. };
  96. }