MaskPatternTester.php 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. <?php
  2. /**
  3. * Class MaskPatternTester
  4. *
  5. * @filesource MaskPatternTester.php
  6. * @created 22.11.2017
  7. * @package chillerlan\QRCode\Data
  8. * @author Smiley <smiley@chillerlan.net>
  9. * @copyright 2017 Smiley
  10. * @license MIT
  11. */
  12. namespace chillerlan\QRCode\Data;
  13. use function abs, call_user_func;
  14. /**
  15. * Receives a QRDataInterface object and runs the mask pattern tests on it.
  16. *
  17. * @link http://www.thonky.com/qr-code-tutorial/data-masking
  18. */
  19. final class MaskPatternTester{
  20. protected QRMatrix $matrix;
  21. protected QRDataInterface $dataInterface;
  22. protected int $moduleCount;
  23. /**
  24. * Receives the QRDataInterface
  25. *
  26. * @see \chillerlan\QRCode\QROptions::$maskPattern
  27. * @see \chillerlan\QRCode\Data\QRMatrix::$maskPattern
  28. */
  29. public function __construct(QRDataInterface $dataInterface){
  30. $this->dataInterface = $dataInterface;
  31. }
  32. /**
  33. * shoves a QRMatrix through the MaskPatternTester to find the lowest penalty mask pattern
  34. *
  35. * @see \chillerlan\QRCode\Data\MaskPatternTester
  36. */
  37. public function getBestMaskPattern():int{
  38. $penalties = [];
  39. for($pattern = 0; $pattern < 8; $pattern++){
  40. $penalties[$pattern] = $this->testPattern($pattern);
  41. }
  42. return array_search(min($penalties), $penalties, true);
  43. }
  44. /**
  45. * Returns the penalty for the given mask pattern
  46. *
  47. * @see \chillerlan\QRCode\QROptions::$maskPattern
  48. * @see \chillerlan\QRCode\Data\QRMatrix::$maskPattern
  49. */
  50. public function testPattern(int $pattern):int{
  51. $this->matrix = $this->dataInterface->initMatrix($pattern, true);
  52. $this->moduleCount = $this->matrix->size();
  53. $penalty = 0;
  54. for($level = 1; $level <= 4; $level++){
  55. $penalty += call_user_func([$this, 'testLevel'.$level]);
  56. }
  57. return (int)$penalty;
  58. }
  59. /**
  60. * Checks for each group of five or more same-colored modules in a row (or column)
  61. */
  62. protected function testLevel1():float{
  63. $penalty = 0;
  64. foreach($this->matrix->matrix() as $y => $row){
  65. foreach($row as $x => $val){
  66. $count = 0;
  67. for($ry = -1; $ry <= 1; $ry++){
  68. if($y + $ry < 0 || $this->moduleCount <= $y + $ry){
  69. continue;
  70. }
  71. for($rx = -1; $rx <= 1; $rx++){
  72. if(($ry === 0 && $rx === 0) || ($x + $rx < 0 || $this->moduleCount <= $x + $rx)){
  73. continue;
  74. }
  75. if($this->matrix->check($x + $rx, $y + $ry) === (($val >> 8) > 0)){
  76. $count++;
  77. }
  78. }
  79. }
  80. if($count > 5){
  81. $penalty += (3 + $count - 5);
  82. }
  83. }
  84. }
  85. return $penalty;
  86. }
  87. /**
  88. * Checks for each 2x2 area of same-colored modules in the matrix
  89. */
  90. protected function testLevel2():float{
  91. $penalty = 0;
  92. foreach($this->matrix->matrix() as $y => $row){
  93. if($y > $this->moduleCount - 2){
  94. break;
  95. }
  96. foreach($row as $x => $val){
  97. if($x > $this->moduleCount - 2){
  98. break;
  99. }
  100. $count = 0;
  101. if($val >> 8 > 0){
  102. $count++;
  103. }
  104. if($this->matrix->check($y, $x + 1)){
  105. $count++;
  106. }
  107. if($this->matrix->check($y + 1, $x)){
  108. $count++;
  109. }
  110. if($this->matrix->check($y + 1, $x + 1)){
  111. $count++;
  112. }
  113. if($count === 0 || $count === 4){
  114. $penalty += 3;
  115. }
  116. }
  117. }
  118. return $penalty;
  119. }
  120. /**
  121. * Checks if there are patterns that look similar to the finder patterns
  122. */
  123. protected function testLevel3():float{
  124. $penalty = 0;
  125. foreach($this->matrix->matrix() as $y => $row){
  126. foreach($row as $x => $val){
  127. if($x <= $this->moduleCount - 7){
  128. if(
  129. $this->matrix->check($x , $y)
  130. && !$this->matrix->check($x + 1, $y)
  131. && $this->matrix->check($x + 2, $y)
  132. && $this->matrix->check($x + 3, $y)
  133. && $this->matrix->check($x + 4, $y)
  134. && !$this->matrix->check($x + 5, $y)
  135. && $this->matrix->check($x + 6, $y)
  136. ){
  137. $penalty += 40;
  138. }
  139. }
  140. if($y <= $this->moduleCount - 7){
  141. if(
  142. $this->matrix->check($x, $y)
  143. && !$this->matrix->check($x, $y + 1)
  144. && $this->matrix->check($x, $y + 2)
  145. && $this->matrix->check($x, $y + 3)
  146. && $this->matrix->check($x, $y + 4)
  147. && !$this->matrix->check($x, $y + 5)
  148. && $this->matrix->check($x, $y + 6)
  149. ){
  150. $penalty += 40;
  151. }
  152. }
  153. }
  154. }
  155. return $penalty;
  156. }
  157. /**
  158. * Checks if more than half of the modules are dark or light, with a larger penalty for a larger difference
  159. */
  160. protected function testLevel4():float{
  161. $count = 0;
  162. foreach($this->matrix->matrix() as $y => $row){
  163. foreach($row as $x => $val){
  164. if($val >> 8 > 0){
  165. $count++;
  166. }
  167. }
  168. }
  169. return (abs(100 * $count / $this->moduleCount / $this->moduleCount - 50) / 5) * 10;
  170. }
  171. }