🛀 fix/cleanup/move mask pattern evaluation, don't alter matrix for evaluation

This commit is contained in:
codemasher
2021-12-01 16:52:18 +01:00
parent b7a757d296
commit 5c2c92667a
8 changed files with 288 additions and 314 deletions
+199
View File
@@ -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;
}
}
-208
View File
@@ -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
View File
@@ -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)
;
+7 -7
View File
@@ -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
View File
@@ -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);
+34
View File
@@ -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));
}
}
-41
View File
@@ -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
View File
@@ -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');