2021-02-26 16:16:17 +00:00
|
|
|
<?php
|
|
|
|
/**
|
|
|
|
* Class MaskPatternTester
|
|
|
|
*
|
|
|
|
* @filesource MaskPatternTester.php
|
|
|
|
* @created 22.11.2017
|
|
|
|
* @package chillerlan\QRCode\Data
|
|
|
|
* @author Smiley <smiley@chillerlan.net>
|
|
|
|
* @copyright 2017 Smiley
|
|
|
|
* @license MIT
|
2022-07-02 14:01:51 +00:00
|
|
|
*
|
|
|
|
* @noinspection PhpUnused
|
2021-02-26 16:16:17 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
namespace chillerlan\QRCode\Data;
|
|
|
|
|
2022-07-02 14:01:51 +00:00
|
|
|
use function abs, array_search, call_user_func_array, min;
|
2021-02-26 16:16:17 +00:00
|
|
|
|
|
|
|
/**
|
2022-07-02 14:01:51 +00:00
|
|
|
* Receives a QRDataInterface object and runs the mask pattern tests on it.
|
|
|
|
*
|
|
|
|
* ISO/IEC 18004:2000 Section 8.8.2 - Evaluation of masking results
|
2021-02-26 16:16:17 +00:00
|
|
|
*
|
2022-07-02 14:01:51 +00:00
|
|
|
* @see http://www.thonky.com/qr-code-tutorial/data-masking
|
2021-02-26 16:16:17 +00:00
|
|
|
*/
|
2022-07-02 14:01:51 +00:00
|
|
|
final class MaskPatternTester{
|
2021-02-26 16:16:17 +00:00
|
|
|
|
|
|
|
/**
|
2022-07-02 14:01:51 +00:00
|
|
|
* The data interface that contains the data matrix to test
|
2021-02-26 16:16:17 +00:00
|
|
|
*/
|
2022-07-02 14:01:51 +00:00
|
|
|
protected QRDataInterface $dataInterface;
|
2021-02-26 16:16:17 +00:00
|
|
|
|
|
|
|
/**
|
2022-07-02 14:01:51 +00:00
|
|
|
* Receives the QRDataInterface
|
2021-02-26 16:16:17 +00:00
|
|
|
*
|
|
|
|
* @see \chillerlan\QRCode\QROptions::$maskPattern
|
|
|
|
* @see \chillerlan\QRCode\Data\QRMatrix::$maskPattern
|
2022-07-02 14:01:51 +00:00
|
|
|
*/
|
|
|
|
public function __construct(QRDataInterface $dataInterface){
|
|
|
|
$this->dataInterface = $dataInterface;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* shoves a QRMatrix through the MaskPatternTester to find the lowest penalty mask pattern
|
2021-02-26 16:16:17 +00:00
|
|
|
*
|
2022-07-02 14:01:51 +00:00
|
|
|
* @see \chillerlan\QRCode\Data\MaskPatternTester
|
2021-02-26 16:16:17 +00:00
|
|
|
*/
|
2022-07-02 14:01:51 +00:00
|
|
|
public function getBestMaskPattern():int{
|
|
|
|
$penalties = [];
|
|
|
|
|
|
|
|
for($pattern = 0; $pattern < 8; $pattern++){
|
|
|
|
$penalties[$pattern] = $this->testPattern($pattern);
|
|
|
|
}
|
|
|
|
|
|
|
|
return array_search(min($penalties), $penalties, true);
|
2021-02-26 16:16:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the penalty for the given mask pattern
|
|
|
|
*
|
|
|
|
* @see \chillerlan\QRCode\QROptions::$maskPattern
|
|
|
|
* @see \chillerlan\QRCode\Data\QRMatrix::$maskPattern
|
|
|
|
*/
|
2022-07-02 14:01:51 +00:00
|
|
|
public function testPattern(int $pattern):int{
|
|
|
|
$matrix = $this->dataInterface->initMatrix($pattern, true);
|
|
|
|
$penalty = 0;
|
2021-02-26 16:16:17 +00:00
|
|
|
|
|
|
|
for($level = 1; $level <= 4; $level++){
|
2022-07-02 14:01:51 +00:00
|
|
|
$penalty += call_user_func_array([$this, 'testLevel'.$level], [$matrix->matrix(true), $matrix->size()]);
|
2021-02-26 16:16:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return (int)$penalty;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks for each group of five or more same-colored modules in a row (or column)
|
|
|
|
*/
|
2022-07-02 14:01:51 +00:00
|
|
|
protected function testLevel1(array $m, int $size):int{
|
2021-02-26 16:16:17 +00:00
|
|
|
$penalty = 0;
|
|
|
|
|
|
|
|
foreach($m as $y => $row){
|
|
|
|
foreach($row as $x => $val){
|
|
|
|
$count = 0;
|
|
|
|
|
|
|
|
for($ry = -1; $ry <= 1; $ry++){
|
|
|
|
|
2022-07-02 14:01:51 +00:00
|
|
|
if($y + $ry < 0 || $size <= $y + $ry){
|
2021-02-26 16:16:17 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
for($rx = -1; $rx <= 1; $rx++){
|
|
|
|
|
2022-07-02 14:01:51 +00:00
|
|
|
if(($ry === 0 && $rx === 0) || (($x + $rx) < 0 || $size <= ($x + $rx))){
|
2021-02-26 16:16:17 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if($m[$y + $ry][$x + $rx] === $val){
|
|
|
|
$count++;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if($count > 5){
|
|
|
|
$penalty += (3 + $count - 5);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $penalty;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks for each 2x2 area of same-colored modules in the matrix
|
|
|
|
*/
|
2022-07-02 14:01:51 +00:00
|
|
|
protected function testLevel2(array $m, int $size):int{
|
2021-02-26 16:16:17 +00:00
|
|
|
$penalty = 0;
|
|
|
|
|
|
|
|
foreach($m as $y => $row){
|
|
|
|
|
2022-07-02 14:01:51 +00:00
|
|
|
if($y > $size - 2){
|
2021-02-26 16:16:17 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach($row as $x => $val){
|
|
|
|
|
2022-07-02 14:01:51 +00:00
|
|
|
if($x > $size - 2){
|
2021-02-26 16:16:17 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(
|
|
|
|
$val === $m[$y][$x + 1]
|
|
|
|
&& $val === $m[$y + 1][$x]
|
|
|
|
&& $val === $m[$y + 1][$x + 1]
|
|
|
|
){
|
|
|
|
$penalty++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 3 * $penalty;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks if there are patterns that look similar to the finder patterns (1:1:3:1:1 ratio)
|
|
|
|
*/
|
2022-07-02 14:01:51 +00:00
|
|
|
protected function testLevel3(array $m, int $size):int{
|
2021-02-26 16:16:17 +00:00
|
|
|
$penalties = 0;
|
|
|
|
|
|
|
|
foreach($m as $y => $row){
|
|
|
|
foreach($row as $x => $val){
|
|
|
|
|
|
|
|
if(
|
2022-07-02 14:01:51 +00:00
|
|
|
$x + 6 < $size
|
2021-02-26 16:16:17 +00:00
|
|
|
&& $val
|
|
|
|
&& !$m[$y][$x + 1]
|
|
|
|
&& $m[$y][$x + 2]
|
|
|
|
&& $m[$y][$x + 3]
|
|
|
|
&& $m[$y][$x + 4]
|
|
|
|
&& !$m[$y][$x + 5]
|
|
|
|
&& $m[$y][$x + 6]
|
|
|
|
){
|
|
|
|
$penalties++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(
|
2022-07-02 14:01:51 +00:00
|
|
|
$y + 6 < $size
|
2021-02-26 16:16:17 +00:00
|
|
|
&& $val
|
|
|
|
&& !$m[$y + 1][$x]
|
|
|
|
&& $m[$y + 2][$x]
|
|
|
|
&& $m[$y + 3][$x]
|
|
|
|
&& $m[$y + 4][$x]
|
|
|
|
&& !$m[$y + 5][$x]
|
|
|
|
&& $m[$y + 6][$x]
|
|
|
|
){
|
|
|
|
$penalties++;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $penalties * 40;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks if more than half of the modules are dark or light, with a larger penalty for a larger difference
|
|
|
|
*/
|
2022-07-02 14:01:51 +00:00
|
|
|
protected function testLevel4(array $m, int $size):float{
|
2021-02-26 16:16:17 +00:00
|
|
|
$count = 0;
|
|
|
|
|
|
|
|
foreach($m as $y => $row){
|
|
|
|
foreach($row as $x => $val){
|
|
|
|
if($val){
|
|
|
|
$count++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-02 14:01:51 +00:00
|
|
|
return (abs(100 * $count / $size / $size - 50) / 5) * 10;
|
2021-02-26 16:16:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|