Просмотр исходного кода

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

codemasher 4 лет назад
Родитель
Сommit
5c2c92667a

+ 199 - 0
src/Common/MaskPattern.php

@@ -10,11 +10,16 @@
 
 
 namespace chillerlan\QRCode\Common;
 namespace chillerlan\QRCode\Common;
 
 
+use chillerlan\QRCode\Data\QRData;
 use chillerlan\QRCode\QRCodeException;
 use chillerlan\QRCode\QRCodeException;
 use Closure;
 use Closure;
+use function abs, array_search, count, min;
 
 
 /**
 /**
  * ISO/IEC 18004:2000 Section 8.8.1
  * 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{
 final class MaskPattern{
 
 
@@ -95,4 +100,198 @@ final class MaskPattern{
 		][$this->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;
+	}
+
 }
 }

+ 0 - 208
src/Common/MaskPatternTester.php

@@ -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
src/Data/QRData.php

@@ -93,12 +93,12 @@ final class QRData{
 	/**
 	/**
 	 * returns a fresh matrix object with the data written for the given $maskPattern
 	 * 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);
 		$data = (new ReedSolomonEncoder)->interleaveEcBytes($this->bitBuffer, $this->version, $this->eccLevel);
 
 
 		return (new QRMatrix($this->version, $this->eccLevel))
 		return (new QRMatrix($this->version, $this->eccLevel))
 			->initFunctionalPatterns()
 			->initFunctionalPatterns()
-			->initFormatInfo($maskPattern, $test)
+			->initFormatInfo($maskPattern)
 			->mapData($data)
 			->mapData($data)
 			->mask($maskPattern)
 			->mask($maskPattern)
 		;
 		;

+ 7 - 7
src/Data/QRMatrix.php

@@ -105,10 +105,10 @@ final class QRMatrix{
 	/**
 	/**
 	 * shortcut to set format and version info
 	 * shortcut to set format and version info
 	 */
 	 */
-	public function initFormatInfo(MaskPattern $maskPattern, bool $test = null):self{
+	public function initFormatInfo(MaskPattern $maskPattern):self{
 		return $this
 		return $this
-			->setVersionNumber($test)
-			->setFormatInfo($maskPattern, $test)
+			->setVersionNumber()
+			->setFormatInfo($maskPattern)
 		;
 		;
 	}
 	}
 
 
@@ -347,7 +347,7 @@ final class QRMatrix{
 	 *
 	 *
 	 * ISO/IEC 18004:2000 Section 8.10
 	 * ISO/IEC 18004:2000 Section 8.10
 	 */
 	 */
-	public function setVersionNumber(bool $test = null):self{
+	public function setVersionNumber():self{
 		$bits = $this->version->getVersionPattern();
 		$bits = $this->version->getVersionPattern();
 
 
 		if($bits !== null){
 		if($bits !== null){
@@ -355,7 +355,7 @@ final class QRMatrix{
 			for($i = 0; $i < 18; $i++){
 			for($i = 0; $i < 18; $i++){
 				$a = (int)($i / 3);
 				$a = (int)($i / 3);
 				$b = $i % 3 + $this->moduleCount - 8 - 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($b, $a, $v, $this::M_VERSION); // ne
 				$this->set($a, $b, $v, $this::M_VERSION); // sw
 				$this->set($a, $b, $v, $this::M_VERSION); // sw
@@ -371,11 +371,11 @@ final class QRMatrix{
 	 *
 	 *
 	 * ISO/IEC 18004:2000 Section 8.9
 	 * 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);
 		$bits = $this->eccLevel->getformatPattern($maskPattern);
 
 
 		for($i = 0; $i < 15; $i++){
 		for($i = 0; $i < 15; $i++){
-			$v = !$test && (($bits >> $i) & 1) === 1;
+			$v = (($bits >> $i) & 1) === 1;
 
 
 			if($i < 6){
 			if($i < 6){
 				$this->set(8, $i, $v, $this::M_FORMAT);
 				$this->set(8, $i, $v, $this::M_FORMAT);

+ 5 - 11
src/QRCode.php

@@ -10,7 +10,7 @@
 
 
 namespace chillerlan\QRCode;
 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\Data\{AlphaNum, Byte, ECI, Kanji, Number, QRData, QRCodeDataException, QRDataModeInterface, QRMatrix};
 use chillerlan\QRCode\Decoder\{Decoder, DecoderResult, LuminanceSourceInterface};
 use chillerlan\QRCode\Decoder\{Decoder, DecoderResult, LuminanceSourceInterface};
 use chillerlan\QRCode\Output\{QRCodeOutputException, QRFpdf, QRImage, QRImagick, QRMarkup, QROutputInterface, QRString};
 use chillerlan\QRCode\Output\{QRCodeOutputException, QRFpdf, QRImage, QRImagick, QRMarkup, QROutputInterface, QRString};
@@ -101,11 +101,6 @@ class QRCode{
 	 */
 	 */
 	protected SettingsContainerInterface $options;
 	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
 	 * 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.');
 			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);
 			: new MaskPattern($this->options->maskPattern);
 
 
-		$matrix = $this->dataInterface->writeMatrix($maskPattern);
+		$matrix = $dataInterface->writeMatrix($maskPattern);
 
 
 		if($this->options->addQuietzone){
 		if($this->options->addQuietzone){
 			$matrix->setQuietZone($this->options->quietzoneSize);
 			$matrix->setQuietZone($this->options->quietzoneSize);

+ 34 - 0
tests/Common/MaskPatternTest.php

@@ -120,4 +120,38 @@ final class MaskPatternTest extends TestCase{
 		$maskPattern = new MaskPattern(42);
 		$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));
+	}
 }
 }

+ 0 - 41
tests/Common/MaskPatternTesterTest.php

@@ -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
tests/Data/QRMatrixTest.php

@@ -14,21 +14,18 @@ use chillerlan\QRCode\Common\{EccLevel, MaskPattern, Version};
 use chillerlan\QRCode\{QRCode, QROptions};
 use chillerlan\QRCode\{QRCode, QROptions};
 use chillerlan\QRCode\Data\{QRCodeDataException, QRMatrix};
 use chillerlan\QRCode\Data\{QRCodeDataException, QRMatrix};
 use PHPUnit\Framework\TestCase;
 use PHPUnit\Framework\TestCase;
+use Generator;
 
 
 /**
 /**
  * Tests the QRMatix class
  * Tests the QRMatix class
  */
  */
 final class QRMatrixTest extends TestCase{
 final class QRMatrixTest extends TestCase{
 
 
-	/** @internal */
 	protected const version = 40;
 	protected const version = 40;
-	/** @internal */
 	protected QRMatrix $matrix;
 	protected QRMatrix $matrix;
 
 
 	/**
 	/**
 	 * invokes a QRMatrix object
 	 * invokes a QRMatrix object
-	 *
-	 * @internal
 	 */
 	 */
 	protected function setUp():void{
 	protected function setUp():void{
 		$this->matrix = $this->getMatrix($this::version);
 		$this->matrix = $this->getMatrix($this::version);
@@ -36,8 +33,6 @@ final class QRMatrixTest extends TestCase{
 
 
 	/**
 	/**
 	 * shortcut
 	 * shortcut
-	 *
-	 * @internal
 	 */
 	 */
 	protected function getMatrix(int $version):QRMatrix{
 	protected function getMatrix(int $version):QRMatrix{
 		return  new QRMatrix(new Version($version), new EccLevel(EccLevel::L));
 		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();
 		$matrix = (new QRCode)->addByteSegment('testdata')->getMatrix();
 
 
 		$this::assertInstanceOf(MaskPattern::class, $matrix->maskPattern());
 		$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
 	 * 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{
 	public function testSetAlignmentPattern(int $version):void{
 
 
 		if($version === 1){
 		if($version === 1){
-			$this->markTestSkipped('N/A');
-
-			/** @noinspection PhpUnreachableStatementInspection */
-			return;
+			$this->markTestSkipped('N/A (Version 1 has no alignment pattern)');
 		}
 		}
 
 
 		$matrix = $this
 		$matrix = $this
@@ -175,8 +160,8 @@ final class QRMatrixTest extends TestCase{
 		foreach($alignmentPattern as $py){
 		foreach($alignmentPattern as $py){
 			foreach($alignmentPattern as $px){
 			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;
 					continue;
 				}
 				}
 
 
@@ -203,14 +188,13 @@ final class QRMatrixTest extends TestCase{
 
 
 		for($i = 7; $i < $size - 7; $i++){
 		for($i = 7; $i < $size - 7; $i++){
 			if($i % 2 === 0){
 			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;
 					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));
 				$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{
 	public function testSetVersionNumber(int $version):void{
 
 
 		if($version < 7){
 		if($version < 7){
-			$this->markTestSkipped('N/A');
+			$this->markTestSkipped('N/A (Version < 7)');
 
 
 			/** @noinspection PhpUnreachableStatementInspection */
 			/** @noinspection PhpUnreachableStatementInspection */
 			return;
 			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
 	 * @dataProvider versionProvider
 	 */
 	 */
 	public function testSetFormatInfo(int $version):void{
 	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]);
 		$this::assertCount($size + 2 * $q, $matrix->matrix()[$size - 1]);
 
 
 		$size = $matrix->size();
 		$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($q, $q));
 		$this::assertSame(QRMatrix::M_TEST | QRMatrix::IS_DARK, $matrix->get($size - 1 - $q, $size - 1 - $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{
 	public function testSetQuietZoneException():void{
 		$this->expectException(QRCodeDataException::class);
 		$this->expectException(QRCodeDataException::class);
@@ -289,6 +273,9 @@ final class QRMatrixTest extends TestCase{
 		$this->matrix->setQuietZone();
 		$this->matrix->setQuietZone();
 	}
 	}
 
 
+	/**
+	 * Tests the auto orientation of the logo space
+	 */
 	public function testSetLogoSpaceOrientation():void{
 	public function testSetLogoSpaceOrientation():void{
 		$o = new QROptions;
 		$o = new QROptions;
 		$o->version      = 10;
 		$o->version      = 10;
@@ -300,14 +287,17 @@ final class QRMatrixTest extends TestCase{
 		$matrix->setLogoSpace(20, 14);
 		$matrix->setLogoSpace(20, 14);
 
 
 		// NW corner
 		// 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
 		// 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{
 	public function testSetLogoSpacePosition():void{
 		$o = new QROptions;
 		$o = new QROptions;
 		$o->version       = 10;
 		$o->version       = 10;
@@ -336,6 +326,9 @@ final class QRMatrixTest extends TestCase{
 		$this::assertSame(QRMatrix::M_QUIETZONE, $m->get(67, 67));
 		$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{
 	public function testSetLogoSpaceInvalidEccException():void{
 		$this->expectException(QRCodeDataException::class);
 		$this->expectException(QRCodeDataException::class);
 		$this->expectExceptionMessage('ECC level "H" required to add logo space');
 		$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);
 		(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{
 	public function testSetLogoSpaceMaxSizeException():void{
 		$this->expectException(QRCodeDataException::class);
 		$this->expectException(QRCodeDataException::class);
 		$this->expectExceptionMessage('logo space exceeds the maximum error correction capacity');
 		$this->expectExceptionMessage('logo space exceeds the maximum error correction capacity');