PerspectiveTransform.php 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. <?php
  2. /**
  3. * Class PerspectiveTransform
  4. *
  5. * @created 17.01.2021
  6. * @author ZXing Authors
  7. * @author Smiley <smiley@chillerlan.net>
  8. * @copyright 2021 Smiley
  9. * @license Apache-2.0
  10. */
  11. namespace chillerlan\QRCode\Detector;
  12. use function count;
  13. /**
  14. * This class implements a perspective transform in two dimensions. Given four source and four
  15. * destination points, it will compute the transformation implied between them. The code is based
  16. * directly upon section 3.4.2 of George Wolberg's "Digital Image Warping"; see pages 54-56.
  17. *
  18. * @author Sean Owen
  19. */
  20. final class PerspectiveTransform{
  21. private float $a11;
  22. private float $a12;
  23. private float $a13;
  24. private float $a21;
  25. private float $a22;
  26. private float $a23;
  27. private float $a31;
  28. private float $a32;
  29. private float $a33;
  30. /**
  31. *
  32. */
  33. private function set(
  34. float $a11, float $a21, float $a31,
  35. float $a12, float $a22, float $a32,
  36. float $a13, float $a23, float $a33
  37. ):self{
  38. $this->a11 = $a11;
  39. $this->a12 = $a12;
  40. $this->a13 = $a13;
  41. $this->a21 = $a21;
  42. $this->a22 = $a22;
  43. $this->a23 = $a23;
  44. $this->a31 = $a31;
  45. $this->a32 = $a32;
  46. $this->a33 = $a33;
  47. return $this;
  48. }
  49. /**
  50. *
  51. */
  52. public function quadrilateralToQuadrilateral(
  53. float $x0, float $y0, float $x1, float $y1, float $x2, float $y2, float $x3, float $y3,
  54. float $x0p, float $y0p, float $x1p, float $y1p, float $x2p, float $y2p, float $x3p, float $y3p
  55. ):self{
  56. return (new self)
  57. ->squareToQuadrilateral($x0p, $y0p, $x1p, $y1p, $x2p, $y2p, $x3p, $y3p)
  58. ->times($this->quadrilateralToSquare($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3));
  59. }
  60. /**
  61. *
  62. */
  63. private function quadrilateralToSquare(
  64. float $x0, float $y0, float $x1, float $y1,
  65. float $x2, float $y2, float $x3, float $y3
  66. ):self{
  67. // Here, the adjoint serves as the inverse:
  68. return $this
  69. ->squareToQuadrilateral($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3)
  70. ->buildAdjoint();
  71. }
  72. /**
  73. *
  74. */
  75. private function buildAdjoint():self{
  76. // Adjoint is the transpose of the cofactor matrix:
  77. return $this->set(
  78. $this->a22 * $this->a33 - $this->a23 * $this->a32,
  79. $this->a23 * $this->a31 - $this->a21 * $this->a33,
  80. $this->a21 * $this->a32 - $this->a22 * $this->a31,
  81. $this->a13 * $this->a32 - $this->a12 * $this->a33,
  82. $this->a11 * $this->a33 - $this->a13 * $this->a31,
  83. $this->a12 * $this->a31 - $this->a11 * $this->a32,
  84. $this->a12 * $this->a23 - $this->a13 * $this->a22,
  85. $this->a13 * $this->a21 - $this->a11 * $this->a23,
  86. $this->a11 * $this->a22 - $this->a12 * $this->a21
  87. );
  88. }
  89. /**
  90. *
  91. */
  92. private function squareToQuadrilateral(
  93. float $x0, float $y0, float $x1, float $y1,
  94. float $x2, float $y2, float $x3, float $y3
  95. ):self{
  96. $dx3 = $x0 - $x1 + $x2 - $x3;
  97. $dy3 = $y0 - $y1 + $y2 - $y3;
  98. if($dx3 === 0.0 && $dy3 === 0.0){
  99. // Affine
  100. return $this->set($x1 - $x0, $x2 - $x1, $x0, $y1 - $y0, $y2 - $y1, $y0, 0.0, 0.0, 1.0);
  101. }
  102. $dx1 = $x1 - $x2;
  103. $dx2 = $x3 - $x2;
  104. $dy1 = $y1 - $y2;
  105. $dy2 = $y3 - $y2;
  106. $denominator = $dx1 * $dy2 - $dx2 * $dy1;
  107. $a13 = ($dx3 * $dy2 - $dx2 * $dy3) / $denominator;
  108. $a23 = ($dx1 * $dy3 - $dx3 * $dy1) / $denominator;
  109. return $this->set(
  110. $x1 - $x0 + $a13 * $x1, $x3 - $x0 + $a23 * $x3, $x0,
  111. $y1 - $y0 + $a13 * $y1, $y3 - $y0 + $a23 * $y3, $y0,
  112. $a13, $a23, 1.0
  113. );
  114. }
  115. /**
  116. *
  117. */
  118. private function times(PerspectiveTransform $other):self{
  119. return $this->set(
  120. $this->a11 * $other->a11 + $this->a21 * $other->a12 + $this->a31 * $other->a13,
  121. $this->a11 * $other->a21 + $this->a21 * $other->a22 + $this->a31 * $other->a23,
  122. $this->a11 * $other->a31 + $this->a21 * $other->a32 + $this->a31 * $other->a33,
  123. $this->a12 * $other->a11 + $this->a22 * $other->a12 + $this->a32 * $other->a13,
  124. $this->a12 * $other->a21 + $this->a22 * $other->a22 + $this->a32 * $other->a23,
  125. $this->a12 * $other->a31 + $this->a22 * $other->a32 + $this->a32 * $other->a33,
  126. $this->a13 * $other->a11 + $this->a23 * $other->a12 + $this->a33 * $other->a13,
  127. $this->a13 * $other->a21 + $this->a23 * $other->a22 + $this->a33 * $other->a23,
  128. $this->a13 * $other->a31 + $this->a23 * $other->a32 + $this->a33 * $other->a33
  129. );
  130. }
  131. /**
  132. *
  133. */
  134. public function transformPoints(array &$xValues, array &$yValues = null):void{
  135. $max = count($xValues);
  136. // @codeCoverageIgnoreStart (unused)
  137. if($yValues !== null){
  138. for($i = 0; $i < $max; $i++){
  139. $x = $xValues[$i];
  140. $y = $yValues[$i];
  141. $denominator = $this->a13 * $x + $this->a23 * $y + $this->a33;
  142. $xValues[$i] = ($this->a11 * $x + $this->a21 * $y + $this->a31) / $denominator;
  143. $yValues[$i] = ($this->a12 * $x + $this->a22 * $y + $this->a32) / $denominator;
  144. }
  145. return;
  146. }
  147. // @codeCoverageIgnoreEnd
  148. for($i = 0; $i < $max; $i += 2){
  149. $x = $xValues[$i];
  150. $y = $xValues[$i + 1];
  151. $denominator = $this->a13 * $x + $this->a23 * $y + $this->a33;
  152. $xValues[$i] = ($this->a11 * $x + $this->a21 * $y + $this->a31) / $denominator;
  153. $xValues[$i + 1] = ($this->a12 * $x + $this->a22 * $y + $this->a32) / $denominator;
  154. }
  155. }
  156. }