mirror of
https://github.com/chillerlan/php-qrcode.git
synced 2026-08-24 20:57:55 +00:00
🛀 fix/cleanup/move mask pattern evaluation, don't alter matrix for evaluation
This commit is contained in:
@@ -10,11 +10,16 @@
|
||||
|
||||
namespace chillerlan\QRCode\Common;
|
||||
|
||||
use chillerlan\QRCode\Data\QRData;
|
||||
use chillerlan\QRCode\QRCodeException;
|
||||
use Closure;
|
||||
use function abs, array_search, count, min;
|
||||
|
||||
/**
|
||||
* ISO/IEC 18004:2000 Section 8.8.1
|
||||
* ISO/IEC 18004:2000 Section 8.8.2 - Evaluation of masking results
|
||||
*
|
||||
* @see http://www.thonky.com/qr-code-tutorial/data-masking
|
||||
*/
|
||||
final class MaskPattern{
|
||||
|
||||
@@ -95,4 +100,198 @@ final class MaskPattern{
|
||||
][$this->maskPattern];
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates the matrix of the given data interface and returns a new mask pattern instance for the best result
|
||||
*/
|
||||
public static function getBestPattern(QRData $dataInterface):self{
|
||||
$penalties = [];
|
||||
|
||||
foreach(self::PATTERNS as $pattern){
|
||||
$matrix = $dataInterface->writeMatrix(new self($pattern))->matrix(true);
|
||||
$penalty = 0;
|
||||
|
||||
for($level = 1; $level <= 4; $level++){
|
||||
$penalty += self::{'testRule'.$level}($matrix, count($matrix), count($matrix[0]));
|
||||
}
|
||||
|
||||
$penalties[$pattern] = (int)$penalty;
|
||||
}
|
||||
|
||||
return new self(array_search(min($penalties), $penalties, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply mask penalty rule 1 and return the penalty. Find repetitive cells with the same color and
|
||||
* give penalty to them. Example: 00000 or 11111.
|
||||
*/
|
||||
public static function testRule1(array $matrix, int $height, int $width):int{
|
||||
return self::applyRule1($matrix, $height, $width, true) + self::applyRule1($matrix, $height, $width, false);
|
||||
}
|
||||
|
||||
private static function applyRule1(array $matrix, int $height, int $width, bool $isHorizontal):int{
|
||||
$penalty = 0;
|
||||
$iLimit = $isHorizontal ? $height : $width;
|
||||
$jLimit = $isHorizontal ? $width : $height;
|
||||
|
||||
for($i = 0; $i < $iLimit; $i++){
|
||||
$numSameBitCells = 0;
|
||||
$prevBit = -1;
|
||||
|
||||
for($j = 0; $j < $jLimit; $j++){
|
||||
$bit = $isHorizontal ? $matrix[$i][$j] : $matrix[$j][$i];
|
||||
|
||||
if($bit === $prevBit){
|
||||
$numSameBitCells++;
|
||||
}
|
||||
else{
|
||||
|
||||
if($numSameBitCells >= 5){
|
||||
$penalty += 3 + ($numSameBitCells - 5);
|
||||
}
|
||||
|
||||
$numSameBitCells = 1; // Include the cell itself.
|
||||
$prevBit = $bit;
|
||||
}
|
||||
}
|
||||
if($numSameBitCells >= 5){
|
||||
$penalty += 3 + ($numSameBitCells - 5);
|
||||
}
|
||||
}
|
||||
|
||||
return $penalty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply mask penalty rule 2 and return the penalty. Find 2x2 blocks with the same color and give
|
||||
* penalty to them. This is actually equivalent to the spec's rule, which is to find MxN blocks and give a
|
||||
* penalty proportional to (M-1)x(N-1), because this is the number of 2x2 blocks inside such a block.
|
||||
*/
|
||||
public static function testRule2(array $matrix, int $height, int $width):int{
|
||||
$penalty = 0;
|
||||
|
||||
foreach($matrix as $y => $row){
|
||||
|
||||
if($y > $height - 2){
|
||||
break;
|
||||
}
|
||||
|
||||
foreach($row as $x => $val){
|
||||
|
||||
if($x > $width - 2){
|
||||
break;
|
||||
}
|
||||
|
||||
if(
|
||||
$val === $row[$x + 1]
|
||||
&& $val === $matrix[$y + 1][$x]
|
||||
&& $val === $matrix[$y + 1][$x + 1]
|
||||
){
|
||||
$penalty++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 3 * $penalty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply mask penalty rule 3 and return the penalty. Find consecutive runs of 1:1:3:1:1:4
|
||||
* starting with black, or 4:1:1:3:1:1 starting with white, and give penalty to them. If we
|
||||
* find patterns like 000010111010000, we give penalty once.
|
||||
*/
|
||||
public static function testRule3(array $matrix, int $height, int $width):int{
|
||||
$penalties = 0;
|
||||
|
||||
foreach($matrix as $y => $row){
|
||||
foreach($row as $x => $val){
|
||||
|
||||
if(
|
||||
$x + 6 < $width
|
||||
&& $val
|
||||
&& !$row[$x + 1]
|
||||
&& $row[$x + 2]
|
||||
&& $row[$x + 3]
|
||||
&& $row[$x + 4]
|
||||
&& !$row[$x + 5]
|
||||
&& $row[$x + 6]
|
||||
&& (
|
||||
self::isWhiteHorizontal($row, $width, $x - 4, $x)
|
||||
|| self::isWhiteHorizontal($row, $width, $x + 7, $x + 11)
|
||||
)
|
||||
){
|
||||
$penalties++;
|
||||
}
|
||||
|
||||
if(
|
||||
$y + 6 < $height
|
||||
&& $val
|
||||
&& !$matrix[$y + 1][$x]
|
||||
&& $matrix[$y + 2][$x]
|
||||
&& $matrix[$y + 3][$x]
|
||||
&& $matrix[$y + 4][$x]
|
||||
&& !$matrix[$y + 5][$x]
|
||||
&& $matrix[$y + 6][$x]
|
||||
&& (
|
||||
self::isWhiteVertical($matrix, $height, $x, $y - 4, $y)
|
||||
|| self::isWhiteVertical($matrix, $height, $x, $y + 7, $y + 11)
|
||||
)
|
||||
){
|
||||
$penalties++;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return $penalties * 40;
|
||||
}
|
||||
|
||||
private static function isWhiteHorizontal(array $row, int $width, int $from, int $to):bool{
|
||||
|
||||
if($from < 0 || $width < $to){
|
||||
return false;
|
||||
}
|
||||
|
||||
for($x = $from; $x < $to; $x++){
|
||||
if($row[$x]){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static function isWhiteVertical(array $matrix, int $height, int $x, int $from, int $to):bool{
|
||||
|
||||
if($from < 0 || $height < $to){
|
||||
return false;
|
||||
}
|
||||
|
||||
for($y = $from; $y < $to; $y++){
|
||||
if($matrix[$y][$x]){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply mask penalty rule 4 and return the penalty. Calculate the ratio of dark cells and give
|
||||
* penalty if the ratio is far from 50%. It gives 10 penalty for 5% distance.
|
||||
*/
|
||||
public static function testRule4(array $matrix, int $height, int $width):int{
|
||||
$darkCells = 0;
|
||||
$totalCells = $height * $width;
|
||||
|
||||
foreach($matrix as $row){
|
||||
foreach($row as $val){
|
||||
if($val){
|
||||
$darkCells++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (int)(abs($darkCells * 2 - $totalCells) * 10 / $totalCells) * 10;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,208 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Class MaskPatternTester
|
||||
*
|
||||
* @created 22.11.2017
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @copyright 2017 Smiley
|
||||
* @license MIT
|
||||
*
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
|
||||
namespace chillerlan\QRCode\Common;
|
||||
|
||||
use chillerlan\QRCode\Data\QRData;
|
||||
use function abs, array_search, call_user_func_array, min;
|
||||
|
||||
/**
|
||||
* Receives a QRData object and runs the mask pattern tests on it.
|
||||
*
|
||||
* ISO/IEC 18004:2000 Section 8.8.2 - Evaluation of masking results
|
||||
*
|
||||
* @see http://www.thonky.com/qr-code-tutorial/data-masking
|
||||
*/
|
||||
final class MaskPatternTester{
|
||||
|
||||
/**
|
||||
* The data interface that contains the data matrix to test
|
||||
*/
|
||||
private QRData $qrData;
|
||||
|
||||
/**
|
||||
* Receives the QRData object
|
||||
*
|
||||
* @see \chillerlan\QRCode\QROptions::$maskPattern
|
||||
* @see \chillerlan\QRCode\Data\QRMatrix::$maskPattern
|
||||
*/
|
||||
public function __construct(QRData $qrData){
|
||||
$this->qrData = $qrData;
|
||||
}
|
||||
|
||||
/**
|
||||
* shoves a QRMatrix through the MaskPatternTester to find the lowest penalty mask pattern
|
||||
*
|
||||
* @see \chillerlan\QRCode\Data\MaskPatternTester
|
||||
*/
|
||||
public function getBestMaskPattern():MaskPattern{
|
||||
$penalties = [];
|
||||
|
||||
foreach(MaskPattern::PATTERNS as $pattern){
|
||||
$penalties[$pattern] = $this->testPattern(new MaskPattern($pattern));
|
||||
}
|
||||
|
||||
return new MaskPattern(array_search(min($penalties), $penalties, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the penalty for the given mask pattern
|
||||
*
|
||||
* @see \chillerlan\QRCode\QROptions::$maskPattern
|
||||
* @see \chillerlan\QRCode\Data\QRMatrix::$maskPattern
|
||||
*/
|
||||
public function testPattern(MaskPattern $pattern):int{
|
||||
$matrix = $this->qrData->writeMatrix($pattern, true);
|
||||
$penalty = 0;
|
||||
|
||||
for($level = 1; $level <= 4; $level++){
|
||||
$penalty += call_user_func_array([$this, 'testLevel'.$level], [$matrix->matrix(true), $matrix->size()]);
|
||||
}
|
||||
|
||||
return (int)$penalty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply mask penalty rule 1 and return the penalty. Find repetitive cells with the same color and
|
||||
* give penalty to them. Example: 00000 or 11111.
|
||||
*/
|
||||
private function testLevel1(array $m, int $size):int{
|
||||
$penalty = 0;
|
||||
|
||||
foreach($m as $y => $row){
|
||||
foreach($row as $x => $val){
|
||||
$count = 0;
|
||||
|
||||
for($ry = -1; $ry <= 1; $ry++){
|
||||
|
||||
if($y + $ry < 0 || $size <= $y + $ry){
|
||||
continue;
|
||||
}
|
||||
|
||||
for($rx = -1; $rx <= 1; $rx++){
|
||||
|
||||
if(($ry === 0 && $rx === 0) || (($x + $rx) < 0 || $size <= ($x + $rx))){
|
||||
continue;
|
||||
}
|
||||
|
||||
if($m[$y + $ry][$x + $rx] === $val){
|
||||
$count++;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if($count > 5){
|
||||
$penalty += (3 + $count - 5);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return $penalty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply mask penalty rule 2 and return the penalty. Find 2x2 blocks with the same color and give
|
||||
* penalty to them. This is actually equivalent to the spec's rule, which is to find MxN blocks and give a
|
||||
* penalty proportional to (M-1)x(N-1), because this is the number of 2x2 blocks inside such a block.
|
||||
*/
|
||||
private function testLevel2(array $m, int $size):int{
|
||||
$penalty = 0;
|
||||
|
||||
foreach($m as $y => $row){
|
||||
|
||||
if($y > $size - 2){
|
||||
break;
|
||||
}
|
||||
|
||||
foreach($row as $x => $val){
|
||||
|
||||
if($x > $size - 2){
|
||||
break;
|
||||
}
|
||||
|
||||
if(
|
||||
$val === $row[$x + 1]
|
||||
&& $val === $m[$y + 1][$x]
|
||||
&& $val === $m[$y + 1][$x + 1]
|
||||
){
|
||||
$penalty++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 3 * $penalty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply mask penalty rule 3 and return the penalty. Find consecutive runs of 1:1:3:1:1:4
|
||||
* starting with black, or 4:1:1:3:1:1 starting with white, and give penalty to them. If we
|
||||
* find patterns like 000010111010000, we give penalty once.
|
||||
*/
|
||||
private function testLevel3(array $m, int $size):int{
|
||||
$penalties = 0;
|
||||
|
||||
foreach($m as $y => $row){
|
||||
foreach($row as $x => $val){
|
||||
|
||||
if(
|
||||
$x + 6 < $size
|
||||
&& $val
|
||||
&& !$row[$x + 1]
|
||||
&& $row[$x + 2]
|
||||
&& $row[$x + 3]
|
||||
&& $row[$x + 4]
|
||||
&& !$row[$x + 5]
|
||||
&& $row[$x + 6]
|
||||
){
|
||||
$penalties++;
|
||||
}
|
||||
|
||||
if(
|
||||
$y + 6 < $size
|
||||
&& $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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply mask penalty rule 4 and return the penalty. Calculate the ratio of dark cells and give
|
||||
* penalty if the ratio is far from 50%. It gives 10 penalty for 5% distance.
|
||||
*/
|
||||
private function testLevel4(array $m, int $size):float{
|
||||
$count = 0;
|
||||
|
||||
foreach($m as $y => $row){
|
||||
foreach($row as $x => $val){
|
||||
if($val){
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (abs(100 * $count / $size / $size - 50) / 5) * 10;
|
||||
}
|
||||
|
||||
}
|
||||
+2
-2
@@ -93,12 +93,12 @@ final class QRData{
|
||||
/**
|
||||
* returns a fresh matrix object with the data written for the given $maskPattern
|
||||
*/
|
||||
public function writeMatrix(MaskPattern $maskPattern, bool $test = null):QRMatrix{
|
||||
public function writeMatrix(MaskPattern $maskPattern):QRMatrix{
|
||||
$data = (new ReedSolomonEncoder)->interleaveEcBytes($this->bitBuffer, $this->version, $this->eccLevel);
|
||||
|
||||
return (new QRMatrix($this->version, $this->eccLevel))
|
||||
->initFunctionalPatterns()
|
||||
->initFormatInfo($maskPattern, $test)
|
||||
->initFormatInfo($maskPattern)
|
||||
->mapData($data)
|
||||
->mask($maskPattern)
|
||||
;
|
||||
|
||||
@@ -105,10 +105,10 @@ final class QRMatrix{
|
||||
/**
|
||||
* shortcut to set format and version info
|
||||
*/
|
||||
public function initFormatInfo(MaskPattern $maskPattern, bool $test = null):self{
|
||||
public function initFormatInfo(MaskPattern $maskPattern):self{
|
||||
return $this
|
||||
->setVersionNumber($test)
|
||||
->setFormatInfo($maskPattern, $test)
|
||||
->setVersionNumber()
|
||||
->setFormatInfo($maskPattern)
|
||||
;
|
||||
}
|
||||
|
||||
@@ -347,7 +347,7 @@ final class QRMatrix{
|
||||
*
|
||||
* ISO/IEC 18004:2000 Section 8.10
|
||||
*/
|
||||
public function setVersionNumber(bool $test = null):self{
|
||||
public function setVersionNumber():self{
|
||||
$bits = $this->version->getVersionPattern();
|
||||
|
||||
if($bits !== null){
|
||||
@@ -355,7 +355,7 @@ final class QRMatrix{
|
||||
for($i = 0; $i < 18; $i++){
|
||||
$a = (int)($i / 3);
|
||||
$b = $i % 3 + $this->moduleCount - 8 - 3;
|
||||
$v = !$test && (($bits >> $i) & 1) === 1;
|
||||
$v = (($bits >> $i) & 1) === 1;
|
||||
|
||||
$this->set($b, $a, $v, $this::M_VERSION); // ne
|
||||
$this->set($a, $b, $v, $this::M_VERSION); // sw
|
||||
@@ -371,11 +371,11 @@ final class QRMatrix{
|
||||
*
|
||||
* ISO/IEC 18004:2000 Section 8.9
|
||||
*/
|
||||
public function setFormatInfo(MaskPattern $maskPattern, bool $test = null):self{
|
||||
public function setFormatInfo(MaskPattern $maskPattern):self{
|
||||
$bits = $this->eccLevel->getformatPattern($maskPattern);
|
||||
|
||||
for($i = 0; $i < 15; $i++){
|
||||
$v = !$test && (($bits >> $i) & 1) === 1;
|
||||
$v = (($bits >> $i) & 1) === 1;
|
||||
|
||||
if($i < 6){
|
||||
$this->set(8, $i, $v, $this::M_FORMAT);
|
||||
|
||||
+5
-11
@@ -10,7 +10,7 @@
|
||||
|
||||
namespace chillerlan\QRCode;
|
||||
|
||||
use chillerlan\QRCode\Common\{EccLevel, ECICharset, MaskPattern, MaskPatternTester, Mode};
|
||||
use chillerlan\QRCode\Common\{EccLevel, ECICharset, MaskPattern, Mode};
|
||||
use chillerlan\QRCode\Data\{AlphaNum, Byte, ECI, Kanji, Number, QRData, QRCodeDataException, QRDataModeInterface, QRMatrix};
|
||||
use chillerlan\QRCode\Decoder\{Decoder, DecoderResult, LuminanceSourceInterface};
|
||||
use chillerlan\QRCode\Output\{QRCodeOutputException, QRFpdf, QRImage, QRImagick, QRMarkup, QROutputInterface, QRString};
|
||||
@@ -101,11 +101,6 @@ class QRCode{
|
||||
*/
|
||||
protected SettingsContainerInterface $options;
|
||||
|
||||
/**
|
||||
* The selected data interface (Number, AlphaNum, Kanji, Byte)
|
||||
*/
|
||||
protected QRData $dataInterface;
|
||||
|
||||
/**
|
||||
* A collection of one or more data segments of [classname, data] to write
|
||||
*
|
||||
@@ -159,13 +154,12 @@ class QRCode{
|
||||
throw new QRCodeDataException('QRCode::getMatrix() No data given.');
|
||||
}
|
||||
|
||||
$this->dataInterface = new QRData($this->options, $this->dataSegments);
|
||||
|
||||
$maskPattern = $this->options->maskPattern === $this::MASK_PATTERN_AUTO
|
||||
? (new MaskPatternTester($this->dataInterface))->getBestMaskPattern()
|
||||
$dataInterface = new QRData($this->options, $this->dataSegments);
|
||||
$maskPattern = $this->options->maskPattern === $this::MASK_PATTERN_AUTO
|
||||
? MaskPattern::getBestPattern($dataInterface)
|
||||
: new MaskPattern($this->options->maskPattern);
|
||||
|
||||
$matrix = $this->dataInterface->writeMatrix($maskPattern);
|
||||
$matrix = $dataInterface->writeMatrix($maskPattern);
|
||||
|
||||
if($this->options->addQuietzone){
|
||||
$matrix->setQuietZone($this->options->quietzoneSize);
|
||||
|
||||
@@ -120,4 +120,38 @@ final class MaskPatternTest extends TestCase{
|
||||
$maskPattern = new MaskPattern(42);
|
||||
}
|
||||
|
||||
public function testPenaltyRule1():void{
|
||||
// horizontal
|
||||
$this::assertSame(0, MaskPattern::testRule1([[0, 0, 0, 0]], 1, 4));
|
||||
$this::assertSame(3, MaskPattern::testRule1([[0, 0, 0, 0, 0, 1]], 1, 6));
|
||||
$this::assertSame(4, MaskPattern::testRule1([[0, 0, 0, 0, 0, 0]], 1, 6));
|
||||
// vertical
|
||||
$this::assertSame(0, MaskPattern::testRule1([[0], [0], [0], [0]], 4, 1));
|
||||
$this::assertSame(3, MaskPattern::testRule1([[0], [0], [0], [0], [0], [1]], 6, 1));
|
||||
$this::assertSame(4, MaskPattern::testRule1([[0], [0], [0], [0], [0], [0]], 6, 1));
|
||||
}
|
||||
|
||||
public function testPenaltyRule2():void{
|
||||
$this::assertSame(0, MaskPattern::testRule2([[0]], 1, 1));
|
||||
$this::assertSame(0, MaskPattern::testRule2([[0, 0], [0, 1]], 2, 2));
|
||||
$this::assertSame(3, MaskPattern::testRule2([[0, 0], [0, 0]], 2, 2));
|
||||
$this::assertSame(12, MaskPattern::testRule2([[0, 0, 0], [0, 0, 0], [0, 0, 0]], 3, 3));
|
||||
}
|
||||
|
||||
public function testPenaltyRule3():void{
|
||||
// horizontal
|
||||
$this::assertSame(40, MaskPattern::testRule3([[0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1]], 1, 11));
|
||||
$this::assertSame(40, MaskPattern::testRule3([[1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0]], 1, 11));
|
||||
$this::assertSame(0, MaskPattern::testRule3([[1, 0, 1, 1, 1, 0, 1]], 1, 7));
|
||||
// vertical
|
||||
$this::assertSame(40, MaskPattern::testRule3([[0], [0], [0], [0], [1], [0], [1], [1], [1], [0], [1]], 11, 1));
|
||||
$this::assertSame(40, MaskPattern::testRule3([[1], [0], [1], [1], [1], [0], [1], [0], [0], [0], [0]], 11, 1));
|
||||
$this::assertSame(0, MaskPattern::testRule3([[1], [0], [1], [1], [1], [0], [1]], 7, 1));
|
||||
}
|
||||
|
||||
public function testPenaltyRule4():void{
|
||||
$this::assertSame(100, MaskPattern::testRule4([[0]], 1, 1));
|
||||
$this::assertSame(0, MaskPattern::testRule4([[0, 1]], 1, 2));
|
||||
$this::assertSame(30, MaskPattern::testRule4([[0, 1, 1, 1, 1, 0]], 1, 6));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Class MaskPatternTesterTest
|
||||
*
|
||||
* @created 24.11.2017
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @copyright 2017 Smiley
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace chillerlan\QRCodeTest\Common;
|
||||
|
||||
use chillerlan\QRCode\Common\{MaskPattern, MaskPatternTester};
|
||||
use chillerlan\QRCode\Data\{Byte, QRData};
|
||||
use chillerlan\QRCode\QROptions;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* MaskPatternTester coverage test
|
||||
*/
|
||||
final class MaskPatternTesterTest extends TestCase{
|
||||
|
||||
/**
|
||||
* Tests getting the best mask pattern
|
||||
*/
|
||||
public function testMaskpattern():void{
|
||||
$dataInterface = new QRData(new QROptions(['version' => 10]), [new Byte('test')]);
|
||||
|
||||
$this::assertSame(3, (new MaskPatternTester($dataInterface))->getBestMaskPattern()->getPattern());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests getting the penalty value for a given mask pattern
|
||||
*/
|
||||
public function testMaskpatternID():void{
|
||||
$dataInterface = new QRData(new QROptions(['version' => 10]), [new Byte('test')]);
|
||||
|
||||
$this::assertSame(4243, (new MaskPatternTester($dataInterface))->testPattern(new MaskPattern(MaskPattern::PATTERN_011)));
|
||||
}
|
||||
|
||||
}
|
||||
+41
-45
@@ -14,21 +14,18 @@ use chillerlan\QRCode\Common\{EccLevel, MaskPattern, Version};
|
||||
use chillerlan\QRCode\{QRCode, QROptions};
|
||||
use chillerlan\QRCode\Data\{QRCodeDataException, QRMatrix};
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Generator;
|
||||
|
||||
/**
|
||||
* Tests the QRMatix class
|
||||
*/
|
||||
final class QRMatrixTest extends TestCase{
|
||||
|
||||
/** @internal */
|
||||
protected const version = 40;
|
||||
/** @internal */
|
||||
protected QRMatrix $matrix;
|
||||
|
||||
/**
|
||||
* invokes a QRMatrix object
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function setUp():void{
|
||||
$this->matrix = $this->getMatrix($this::version);
|
||||
@@ -36,8 +33,6 @@ final class QRMatrixTest extends TestCase{
|
||||
|
||||
/**
|
||||
* shortcut
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function getMatrix(int $version):QRMatrix{
|
||||
return new QRMatrix(new Version($version), new EccLevel(EccLevel::L));
|
||||
@@ -80,7 +75,7 @@ final class QRMatrixTest extends TestCase{
|
||||
$matrix = (new QRCode)->addByteSegment('testdata')->getMatrix();
|
||||
|
||||
$this::assertInstanceOf(MaskPattern::class, $matrix->maskPattern());
|
||||
$this::assertSame(MaskPattern::PATTERN_010, $matrix->maskPattern()->getPattern());
|
||||
$this::assertSame(MaskPattern::PATTERN_100, $matrix->maskPattern()->getPattern());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,18 +93,11 @@ final class QRMatrixTest extends TestCase{
|
||||
|
||||
/**
|
||||
* Version data provider for several pattern tests
|
||||
*
|
||||
* @return int[][]
|
||||
* @internal
|
||||
*/
|
||||
public function versionProvider():array{
|
||||
$versions = [];
|
||||
|
||||
for($i = 1; $i <= 40; $i++){
|
||||
$versions[] = [$i];
|
||||
public function versionProvider():Generator{
|
||||
foreach(range(1, 40) as $i){
|
||||
yield 'version: '.$i => [$i];
|
||||
}
|
||||
|
||||
return $versions;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -158,10 +146,7 @@ final class QRMatrixTest extends TestCase{
|
||||
public function testSetAlignmentPattern(int $version):void{
|
||||
|
||||
if($version === 1){
|
||||
$this->markTestSkipped('N/A');
|
||||
|
||||
/** @noinspection PhpUnreachableStatementInspection */
|
||||
return;
|
||||
$this->markTestSkipped('N/A (Version 1 has no alignment pattern)');
|
||||
}
|
||||
|
||||
$matrix = $this
|
||||
@@ -175,8 +160,8 @@ final class QRMatrixTest extends TestCase{
|
||||
foreach($alignmentPattern as $py){
|
||||
foreach($alignmentPattern as $px){
|
||||
|
||||
if($matrix->get($px, $py) === (QRMatrix::M_FINDER | QRMatrix::IS_DARK)){
|
||||
$this::assertSame(QRMatrix::M_FINDER | QRMatrix::IS_DARK, $matrix->get($px, $py), 'skipped finder pattern');
|
||||
if($matrix->checkType($px, $py, QRMatrix::M_FINDER)){
|
||||
// skip finder pattern
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -203,14 +188,13 @@ final class QRMatrixTest extends TestCase{
|
||||
|
||||
for($i = 7; $i < $size - 7; $i++){
|
||||
if($i % 2 === 0){
|
||||
$p1 = $matrix->get(6, $i);
|
||||
|
||||
if($p1 === (QRMatrix::M_ALIGNMENT | QRMatrix::IS_DARK)){
|
||||
$this::assertSame(QRMatrix::M_ALIGNMENT | QRMatrix::IS_DARK, $p1, 'skipped alignment pattern');
|
||||
if($matrix->checkType(6, $i, QRMatrix::M_ALIGNMENT)){
|
||||
// skip alignment pattern
|
||||
continue;
|
||||
}
|
||||
|
||||
$this::assertSame(QRMatrix::M_TIMING | QRMatrix::IS_DARK, $p1);
|
||||
$this::assertSame(QRMatrix::M_TIMING | QRMatrix::IS_DARK, $matrix->get(6, $i));
|
||||
$this::assertSame(QRMatrix::M_TIMING | QRMatrix::IS_DARK, $matrix->get($i, 6));
|
||||
}
|
||||
}
|
||||
@@ -224,18 +208,18 @@ final class QRMatrixTest extends TestCase{
|
||||
public function testSetVersionNumber(int $version):void{
|
||||
|
||||
if($version < 7){
|
||||
$this->markTestSkipped('N/A');
|
||||
$this->markTestSkipped('N/A (Version < 7)');
|
||||
|
||||
/** @noinspection PhpUnreachableStatementInspection */
|
||||
return;
|
||||
}
|
||||
|
||||
$matrix = $this->getMatrix($version)->setVersionNumber(true);
|
||||
$matrix = $this->getMatrix($version)->setVersionNumber();
|
||||
|
||||
$this::assertSame(QRMatrix::M_VERSION, $matrix->get($matrix->size() - 9, 0));
|
||||
$this::assertSame(QRMatrix::M_VERSION, $matrix->get($matrix->size() - 11, 5));
|
||||
$this::assertSame(QRMatrix::M_VERSION, $matrix->get(0, $matrix->size() - 9));
|
||||
$this::assertSame(QRMatrix::M_VERSION, $matrix->get(5, $matrix->size() - 11));
|
||||
$this::assertTrue($matrix->checkType($matrix->size() - 9, 0, QRMatrix::M_VERSION));
|
||||
$this::assertTrue($matrix->checkType($matrix->size() - 11, 5, QRMatrix::M_VERSION));
|
||||
$this::assertTrue($matrix->checkType(0, $matrix->size() - 9, QRMatrix::M_VERSION));
|
||||
$this::assertTrue($matrix->checkType(5, $matrix->size() - 11, QRMatrix::M_VERSION));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -244,12 +228,12 @@ final class QRMatrixTest extends TestCase{
|
||||
* @dataProvider versionProvider
|
||||
*/
|
||||
public function testSetFormatInfo(int $version):void{
|
||||
$matrix = $this->getMatrix($version)->setFormatInfo(new MaskPattern(MaskPattern::PATTERN_000), true);
|
||||
$matrix = $this->getMatrix($version)->setFormatInfo(new MaskPattern(MaskPattern::PATTERN_000));
|
||||
|
||||
$this::assertSame(QRMatrix::M_FORMAT, $matrix->get(8, 0));
|
||||
$this::assertSame(QRMatrix::M_FORMAT, $matrix->get(0, 8));
|
||||
$this::assertSame(QRMatrix::M_FORMAT, $matrix->get($matrix->size() - 1, 8));
|
||||
$this::assertSame(QRMatrix::M_FORMAT, $matrix->get($matrix->size() - 8, 8));
|
||||
$this::assertTrue($matrix->checkType(8, 0, QRMatrix::M_FORMAT));
|
||||
$this::assertTrue($matrix->checkType(0, 8, QRMatrix::M_FORMAT));
|
||||
$this::assertTrue($matrix->checkType($matrix->size() - 1, 8, QRMatrix::M_FORMAT));
|
||||
$this::assertTrue($matrix->checkType($matrix->size() - 8, 8, QRMatrix::M_FORMAT));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -272,15 +256,15 @@ final class QRMatrixTest extends TestCase{
|
||||
$this::assertCount($size + 2 * $q, $matrix->matrix()[$size - 1]);
|
||||
|
||||
$size = $matrix->size();
|
||||
$this::assertSame(QRMatrix::M_QUIETZONE, $matrix->get(0, 0));
|
||||
$this::assertSame(QRMatrix::M_QUIETZONE, $matrix->get($size - 1, $size - 1));
|
||||
$this::assertTrue($matrix->checkType(0, 0, QRMatrix::M_QUIETZONE));
|
||||
$this::assertTrue($matrix->checkType($size - 1, $size - 1, QRMatrix::M_QUIETZONE));
|
||||
|
||||
$this::assertSame(QRMatrix::M_TEST | QRMatrix::IS_DARK, $matrix->get($q, $q));
|
||||
$this::assertSame(QRMatrix::M_TEST | QRMatrix::IS_DARK, $matrix->get($size - 1 - $q, $size - 1 - $q));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if an exception is thrown in an attempt to create it before data was written
|
||||
* Tests if an exception is thrown in an attempt to create the quiet zone before data was written
|
||||
*/
|
||||
public function testSetQuietZoneException():void{
|
||||
$this->expectException(QRCodeDataException::class);
|
||||
@@ -289,6 +273,9 @@ final class QRMatrixTest extends TestCase{
|
||||
$this->matrix->setQuietZone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the auto orientation of the logo space
|
||||
*/
|
||||
public function testSetLogoSpaceOrientation():void{
|
||||
$o = new QROptions;
|
||||
$o->version = 10;
|
||||
@@ -300,14 +287,17 @@ final class QRMatrixTest extends TestCase{
|
||||
$matrix->setLogoSpace(20, 14);
|
||||
|
||||
// NW corner
|
||||
$this::assertNotSame(QRMatrix::M_LOGO, $matrix->get(17, 20));
|
||||
$this::assertSame(QRMatrix::M_LOGO, $matrix->get(18, 21));
|
||||
$this::assertFalse($matrix->checkType(17, 20, QRMatrix::M_LOGO));
|
||||
$this::assertTrue($matrix->checkType(18, 21, QRMatrix::M_LOGO));
|
||||
|
||||
// SE corner
|
||||
$this::assertSame(QRMatrix::M_LOGO, $matrix->get(38, 35));
|
||||
$this::assertNotSame(QRMatrix::M_LOGO, $matrix->get(39, 36));
|
||||
$this::assertTrue($matrix->checkType(38, 35, QRMatrix::M_LOGO));
|
||||
$this::assertFalse($matrix->checkType(39, 36, QRMatrix::M_LOGO));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the manual positioning of the logo space
|
||||
*/
|
||||
public function testSetLogoSpacePosition():void{
|
||||
$o = new QROptions;
|
||||
$o->version = 10;
|
||||
@@ -336,6 +326,9 @@ final class QRMatrixTest extends TestCase{
|
||||
$this::assertSame(QRMatrix::M_QUIETZONE, $m->get(67, 67));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether an exception is thrown when an ECC level other than "H" is set when attempting to add logo space
|
||||
*/
|
||||
public function testSetLogoSpaceInvalidEccException():void{
|
||||
$this->expectException(QRCodeDataException::class);
|
||||
$this->expectExceptionMessage('ECC level "H" required to add logo space');
|
||||
@@ -343,6 +336,9 @@ final class QRMatrixTest extends TestCase{
|
||||
(new QRCode)->addByteSegment('testdata')->getMatrix()->setLogoSpace(50, 50);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether an exception is thrown when the logo space size exceeds the maximum ECC capacity
|
||||
*/
|
||||
public function testSetLogoSpaceMaxSizeException():void{
|
||||
$this->expectException(QRCodeDataException::class);
|
||||
$this->expectExceptionMessage('logo space exceeds the maximum error correction capacity');
|
||||
|
||||
Reference in New Issue
Block a user