PerspectiveTransform.php 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  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. * <p>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.</p>
  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. private function set(
  31. float $a11, float $a21, float $a31,
  32. float $a12, float $a22, float $a32,
  33. float $a13, float $a23, float $a33
  34. ):self{
  35. $this->a11 = $a11;
  36. $this->a12 = $a12;
  37. $this->a13 = $a13;
  38. $this->a21 = $a21;
  39. $this->a22 = $a22;
  40. $this->a23 = $a23;
  41. $this->a31 = $a31;
  42. $this->a32 = $a32;
  43. $this->a33 = $a33;
  44. return $this;
  45. }
  46. public function quadrilateralToQuadrilateral(
  47. float $x0, float $y0, float $x1, float $y1, float $x2, float $y2, float $x3, float $y3,
  48. float $x0p, float $y0p, float $x1p, float $y1p, float $x2p, float $y2p, float $x3p, float $y3p
  49. ):self{
  50. return (new self)
  51. ->squareToQuadrilateral($x0p, $y0p, $x1p, $y1p, $x2p, $y2p, $x3p, $y3p)
  52. ->times($this->quadrilateralToSquare($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3));
  53. }
  54. private function quadrilateralToSquare(
  55. float $x0, float $y0, float $x1, float $y1,
  56. float $x2, float $y2, float $x3, float $y3
  57. ):self{
  58. // Here, the adjoint serves as the inverse:
  59. return $this
  60. ->squareToQuadrilateral($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3)
  61. ->buildAdjoint();
  62. }
  63. private function buildAdjoint():self{
  64. // Adjoint is the transpose of the cofactor matrix:
  65. return $this->set(
  66. $this->a22 * $this->a33 - $this->a23 * $this->a32,
  67. $this->a23 * $this->a31 - $this->a21 * $this->a33,
  68. $this->a21 * $this->a32 - $this->a22 * $this->a31,
  69. $this->a13 * $this->a32 - $this->a12 * $this->a33,
  70. $this->a11 * $this->a33 - $this->a13 * $this->a31,
  71. $this->a12 * $this->a31 - $this->a11 * $this->a32,
  72. $this->a12 * $this->a23 - $this->a13 * $this->a22,
  73. $this->a13 * $this->a21 - $this->a11 * $this->a23,
  74. $this->a11 * $this->a22 - $this->a12 * $this->a21
  75. );
  76. }
  77. private function squareToQuadrilateral(
  78. float $x0, float $y0, float $x1, float $y1,
  79. float $x2, float $y2, float $x3, float $y3
  80. ):self{
  81. $dx3 = $x0 - $x1 + $x2 - $x3;
  82. $dy3 = $y0 - $y1 + $y2 - $y3;
  83. if($dx3 === 0.0 && $dy3 === 0.0){
  84. // Affine
  85. return $this->set($x1 - $x0, $x2 - $x1, $x0, $y1 - $y0, $y2 - $y1, $y0, 0.0, 0.0, 1.0);
  86. }
  87. $dx1 = $x1 - $x2;
  88. $dx2 = $x3 - $x2;
  89. $dy1 = $y1 - $y2;
  90. $dy2 = $y3 - $y2;
  91. $denominator = $dx1 * $dy2 - $dx2 * $dy1;
  92. $a13 = ($dx3 * $dy2 - $dx2 * $dy3) / $denominator;
  93. $a23 = ($dx1 * $dy3 - $dx3 * $dy1) / $denominator;
  94. return $this->set(
  95. $x1 - $x0 + $a13 * $x1, $x3 - $x0 + $a23 * $x3, $x0,
  96. $y1 - $y0 + $a13 * $y1, $y3 - $y0 + $a23 * $y3, $y0,
  97. $a13, $a23, 1.0
  98. );
  99. }
  100. private function times(PerspectiveTransform $other):self{
  101. return $this->set(
  102. $this->a11 * $other->a11 + $this->a21 * $other->a12 + $this->a31 * $other->a13,
  103. $this->a11 * $other->a21 + $this->a21 * $other->a22 + $this->a31 * $other->a23,
  104. $this->a11 * $other->a31 + $this->a21 * $other->a32 + $this->a31 * $other->a33,
  105. $this->a12 * $other->a11 + $this->a22 * $other->a12 + $this->a32 * $other->a13,
  106. $this->a12 * $other->a21 + $this->a22 * $other->a22 + $this->a32 * $other->a23,
  107. $this->a12 * $other->a31 + $this->a22 * $other->a32 + $this->a32 * $other->a33,
  108. $this->a13 * $other->a11 + $this->a23 * $other->a12 + $this->a33 * $other->a13,
  109. $this->a13 * $other->a21 + $this->a23 * $other->a22 + $this->a33 * $other->a23,
  110. $this->a13 * $other->a31 + $this->a23 * $other->a32 + $this->a33 * $other->a33
  111. );
  112. }
  113. public function transformPoints(array &$xValues, array &$yValues = null):void{
  114. $max = count($xValues);
  115. if($yValues !== null){
  116. for($i = 0; $i < $max; $i++){
  117. $x = $xValues[$i];
  118. $y = $yValues[$i];
  119. $denominator = $this->a13 * $x + $this->a23 * $y + $this->a33;
  120. $xValues[$i] = ($this->a11 * $x + $this->a21 * $y + $this->a31) / $denominator;
  121. $yValues[$i] = ($this->a12 * $x + $this->a22 * $y + $this->a32) / $denominator;
  122. }
  123. return;
  124. }
  125. for($i = 0; $i < $max; $i += 2){
  126. $x = $xValues[$i];
  127. $y = $xValues[$i + 1];
  128. $denominator = $this->a13 * $x + $this->a23 * $y + $this->a33;
  129. $xValues[$i] = ($this->a11 * $x + $this->a21 * $y + $this->a31) / $denominator;
  130. $xValues[$i + 1] = ($this->a12 * $x + $this->a22 * $y + $this->a32) / $denominator;
  131. }
  132. }
  133. }