Explorar o código

:bath: merge QRMatrix and BitMatrix

smiley %!s(int64=3) %!d(string=hai) anos
pai
achega
7c183e7e88

+ 0 - 102
src/Common/FormatInformation.php

@@ -1,102 +0,0 @@
-<?php
-/**
- * Class FormatInformation
- *
- * @created      24.01.2021
- * @author       ZXing Authors
- * @author       Smiley <smiley@chillerlan.net>
- * @copyright    2021 Smiley
- * @license      Apache-2.0
- */
-
-namespace chillerlan\QRCode\Common;
-
-/**
- * Encapsulates a QR Code's format information, including the data mask used and error correction level.
- *
- * @author Sean Owen
- * @see    \chillerlan\QRCode\Common\EccLevel
- */
-final class FormatInformation{
-
-	public const FORMAT_INFO_MASK_QR = 0x5412;
-
-	/**
-	 * See ISO 18004:2006, Annex C, Table C.1
-	 *
-	 * [data bits, sequence after masking]
-	 */
-	public const DECODE_LOOKUP = [
-		[0x00, 0x5412],
-		[0x01, 0x5125],
-		[0x02, 0x5E7C],
-		[0x03, 0x5B4B],
-		[0x04, 0x45F9],
-		[0x05, 0x40CE],
-		[0x06, 0x4F97],
-		[0x07, 0x4AA0],
-		[0x08, 0x77C4],
-		[0x09, 0x72F3],
-		[0x0A, 0x7DAA],
-		[0x0B, 0x789D],
-		[0x0C, 0x662F],
-		[0x0D, 0x6318],
-		[0x0E, 0x6C41],
-		[0x0F, 0x6976],
-		[0x10, 0x1689],
-		[0x11, 0x13BE],
-		[0x12, 0x1CE7],
-		[0x13, 0x19D0],
-		[0x14, 0x0762],
-		[0x15, 0x0255],
-		[0x16, 0x0D0C],
-		[0x17, 0x083B],
-		[0x18, 0x355F],
-		[0x19, 0x3068],
-		[0x1A, 0x3F31],
-		[0x1B, 0x3A06],
-		[0x1C, 0x24B4],
-		[0x1D, 0x2183],
-		[0x1E, 0x2EDA],
-		[0x1F, 0x2BED],
-	];
-
-	/**
-	 * The current ECC level value
-	 *
-	 * L: 0b01
-	 * M: 0b00
-	 * Q: 0b11
-	 * H: 0b10
-	 */
-	private int $errorCorrectionLevel;
-
-	/**
-	 * The current mask pattern (0-7)
-	 */
-	private int $maskPattern;
-
-	/**
-	 * Receives the format information from a parsed QR Code, detects ECC level and mask pattern
-	 */
-	public function __construct(int $formatInfo){
-		$this->errorCorrectionLevel = ($formatInfo >> 3) & 0x03; // Bits 3,4
-		$this->maskPattern          = ($formatInfo & 0x07); // Bottom 3 bits
-	}
-
-	/**
-	 * Returns and EccLevel instance ith the detected ECC level set
-	 */
-	public function getErrorCorrectionLevel():EccLevel{
-		return new EccLevel($this->errorCorrectionLevel);
-	}
-
-	/**
-	 * Returns a MaskPattern instance with the detected mask pattern set
-	 */
-	public function getMaskPattern():MaskPattern{
-		return new MaskPattern($this->maskPattern);
-	}
-
-}
-

+ 5 - 61
src/Common/ReedSolomonEncoder.php

@@ -10,7 +10,6 @@
 
 
 namespace chillerlan\QRCode\Common;
 namespace chillerlan\QRCode\Common;
 
 
-use chillerlan\QRCode\Data\QRMatrix;
 use function array_fill, array_merge, count, max;
 use function array_fill, array_merge, count, max;
 
 
 /**
 /**
@@ -25,10 +24,11 @@ final class ReedSolomonEncoder{
 
 
 	/**
 	/**
 	 * ECC interleaving
 	 * ECC interleaving
+	 *
+	 * @throws \chillerlan\QRCode\QRCodeException
 	 */
 	 */
-	public function interleaveEcBytes(BitBuffer $bitBuffer, QRMatrix $matrix):QRMatrix{
-		$version = $matrix->version();
-		[$numEccCodewords, [[$l1, $b1], [$l2, $b2]]] = $version->getRSBlocks($matrix->eccLevel());
+	public function interleaveEcBytes(BitBuffer $bitBuffer, Version $version, EccLevel $eccLevel):array{
+		[$numEccCodewords, [[$l1, $b1], [$l2, $b2]]] = $version->getRSBlocks($eccLevel);
 
 
 		$rsBlocks = array_fill(0, $l1, [$numEccCodewords + $b1, $b1]);
 		$rsBlocks = array_fill(0, $l1, [$numEccCodewords + $b1, $b1]);
 
 
@@ -66,7 +66,7 @@ final class ReedSolomonEncoder{
 		$this->interleave($dataBytes, $maxDataBytes, $numRsBlocks);
 		$this->interleave($dataBytes, $maxDataBytes, $numRsBlocks);
 		$this->interleave($ecBytes, $maxEcBytes, $numRsBlocks);
 		$this->interleave($ecBytes, $maxEcBytes, $numRsBlocks);
 
 
-		return $this->mapData($matrix);
+		return $this->interleavedData;
 	}
 	}
 
 
 	/**
 	/**
@@ -110,60 +110,4 @@ final class ReedSolomonEncoder{
 		}
 		}
 	}
 	}
 
 
-	/**
-	 * Maps the interleaved binary $data on the matrix
-	 */
-	private function mapData(QRMatrix $matrix):QRMatrix{
-		$byteCount = count($this->interleavedData);
-		$size      = $matrix->size();
-		$y         = $size - 1;
-		$inc       = -1;
-		$byteIndex = 0;
-		$bitIndex  = 7;
-
-		for($i = $y; $i > 0; $i -= 2){
-
-			if($i === 6){
-				$i--;
-			}
-
-			while(true){
-				for($c = 0; $c < 2; $c++){
-					$x = $i - $c;
-
-					if($matrix->get($x, $y) !== QRMatrix::M_NULL){
-						continue;
-					}
-
-					$v = false;
-
-					if($byteIndex < $byteCount){
-						$v = (($this->interleavedData[$byteIndex] >> $bitIndex) & 1) === 1;
-					}
-
-					$matrix->set($x, $y, $v, QRMatrix::M_DATA);
-					$bitIndex--;
-
-					if($bitIndex === -1){
-						$byteIndex++;
-						$bitIndex = 7;
-					}
-
-				}
-
-				$y += $inc;
-
-				if($y < 0 || $size <= $y){
-					$y   -=  $inc;
-					$inc  = -$inc;
-
-					break;
-				}
-
-			}
-		}
-
-		return $matrix;
-	}
-
 }
 }

+ 4 - 8
src/Data/QRData.php

@@ -10,7 +10,7 @@
 
 
 namespace chillerlan\QRCode\Data;
 namespace chillerlan\QRCode\Data;
 
 
-use chillerlan\QRCode\Common\{BitBuffer, EccLevel, MaskPattern, Mode, ReedSolomonEncoder, Version};
+use chillerlan\QRCode\Common\{BitBuffer, EccLevel, MaskPattern, Mode, Version};
 use chillerlan\QRCode\QRCode;
 use chillerlan\QRCode\QRCode;
 use chillerlan\Settings\SettingsContainerInterface;
 use chillerlan\Settings\SettingsContainerInterface;
 
 
@@ -94,14 +94,10 @@ final class QRData{
 	 * returns a fresh matrix object with the data written and masked with the given $maskPattern
 	 * returns a fresh matrix object with the data written and masked with the given $maskPattern
 	 */
 	 */
 	public function writeMatrix(MaskPattern $maskPattern):QRMatrix{
 	public function writeMatrix(MaskPattern $maskPattern):QRMatrix{
-		$matrix = (new QRMatrix($this->version, $this->eccLevel))
+		return (new QRMatrix($this->version, $this->eccLevel, $maskPattern))
 			->initFunctionalPatterns()
 			->initFunctionalPatterns()
-			->initFormatInfo($maskPattern)
-		;
-
-		return (new ReedSolomonEncoder)
-			->interleaveEcBytes($this->bitBuffer, $matrix)
-			->mask($maskPattern)
+			->writeCodewords($this->bitBuffer)
+			->mask()
 		;
 		;
 	}
 	}
 
 

+ 72 - 37
src/Data/QRMatrix.php

@@ -10,10 +10,8 @@
 
 
 namespace chillerlan\QRCode\Data;
 namespace chillerlan\QRCode\Data;
 
 
-use chillerlan\QRCode\Common\{EccLevel, MaskPattern, Version};
-
-use SplFixedArray;
-use function array_fill, array_unshift, floor, max, min, range;
+use chillerlan\QRCode\Common\{BitBuffer, EccLevel, MaskPattern, ReedSolomonEncoder, Version};
+use function array_fill, array_unshift, count, floor, max, min, range;
 
 
 /**
 /**
  * Holds a numerical representation of the final QR Code;
  * Holds a numerical representation of the final QR Code;
@@ -21,7 +19,7 @@ use function array_fill, array_unshift, floor, max, min, range;
  *
  *
  * @see http://www.thonky.com/qr-code-tutorial/format-version-information
  * @see http://www.thonky.com/qr-code-tutorial/format-version-information
  */
  */
-final class QRMatrix{
+class QRMatrix{
 
 
 	/** @var int */
 	/** @var int */
 	public const M_NULL       = 0b000000000000;
 	public const M_NULL       = 0b000000000000;
@@ -55,36 +53,37 @@ final class QRMatrix{
 	/**
 	/**
 	 * the used mask pattern, set via QRMatrix::mask()
 	 * the used mask pattern, set via QRMatrix::mask()
 	 */
 	 */
-	private ?MaskPattern $maskPattern = null;
+	protected ?MaskPattern $maskPattern = null;
 
 
 	/**
 	/**
-	 * the size (side length) of the matrix, including quiet zone (if created)
+	 * the current ECC level
 	 */
 	 */
-	private int $moduleCount;
+	protected ?EccLevel $eccLevel = null;
 
 
 	/**
 	/**
-	 * the actual matrix data array
-	 *
-	 * @var int[][]
+	 * a Version instance
 	 */
 	 */
-	private array $matrix;
+	protected ?Version $version = null;
 
 
 	/**
 	/**
-	 * the current ECC level
+	 * the size (side length) of the matrix, including quiet zone (if created)
 	 */
 	 */
-	private EccLevel $eccLevel;
+	protected int $moduleCount;
 
 
 	/**
 	/**
-	 * a Version instance
+	 * the actual matrix data array
+	 *
+	 * @var int[][]
 	 */
 	 */
-	private Version $version;
+	protected array $matrix;
 
 
 	/**
 	/**
 	 * QRMatrix constructor.
 	 * QRMatrix constructor.
 	 */
 	 */
-	public function __construct(Version $version, EccLevel $eccLevel){
+	public function __construct(Version $version, EccLevel $eccLevel, MaskPattern $maskPattern){
 		$this->version     = $version;
 		$this->version     = $version;
 		$this->eccLevel    = $eccLevel;
 		$this->eccLevel    = $eccLevel;
+		$this->maskPattern = $maskPattern;
 		$this->moduleCount = $this->version->getDimension();
 		$this->moduleCount = $this->version->getDimension();
 		$this->matrix      = array_fill(0, $this->moduleCount, array_fill(0, $this->moduleCount, $this::M_NULL));
 		$this->matrix      = array_fill(0, $this->moduleCount, array_fill(0, $this->moduleCount, $this::M_NULL));
 	}
 	}
@@ -99,16 +98,8 @@ final class QRMatrix{
 			->setAlignmentPattern()
 			->setAlignmentPattern()
 			->setTimingPattern()
 			->setTimingPattern()
 			->setDarkModule()
 			->setDarkModule()
-		;
-	}
-
-	/**
-	 * shortcut to set format and version info
-	 */
-	public function initFormatInfo(MaskPattern $maskPattern):self{
-		return $this
 			->setVersionNumber()
 			->setVersionNumber()
-			->setFormatInfo($maskPattern)
+			->setFormatInfo()
 		;
 		;
 	}
 	}
 
 
@@ -139,14 +130,14 @@ final class QRMatrix{
 	/**
 	/**
 	 * Returns the current version number
 	 * Returns the current version number
 	 */
 	 */
-	public function version():Version{
+	public function version():?Version{
 		return $this->version;
 		return $this->version;
 	}
 	}
 
 
 	/**
 	/**
 	 * Returns the current ECC level
 	 * Returns the current ECC level
 	 */
 	 */
-	public function eccLevel():EccLevel{
+	public function eccLevel():?EccLevel{
 		return $this->eccLevel;
 		return $this->eccLevel;
 	}
 	}
 
 
@@ -385,8 +376,8 @@ final class QRMatrix{
 	 *
 	 *
 	 * ISO/IEC 18004:2000 Section 8.9
 	 * ISO/IEC 18004:2000 Section 8.9
 	 */
 	 */
-	public function setFormatInfo(MaskPattern $maskPattern):self{
-		$bits = $this->eccLevel->getformatPattern($maskPattern);
+	public function setFormatInfo():self{
+		$bits = $this->eccLevel->getformatPattern($this->maskPattern);
 
 
 		for($i = 0; $i < 15; $i++){
 		for($i = 0; $i < 15; $i++){
 			$v = (($bits >> $i) & 1) === 1;
 			$v = (($bits >> $i) & 1) === 1;
@@ -533,18 +524,62 @@ final class QRMatrix{
 	}
 	}
 
 
 	/**
 	/**
-	 * Applies the mask pattern
+	 * Maps the interleaved binary $data on the matrix
+	 */
+	public function writeCodewords(BitBuffer $bitBuffer):self{
+		$data      = (new ReedSolomonEncoder)->interleaveEcBytes($bitBuffer, $this->version, $this->eccLevel);
+		$byteCount = count($data);
+		$iByte     = 0;
+		$iBit      = 7;
+		$direction = true;
+
+		for($i = $this->moduleCount - 1; $i > 0; $i -= 2){
+
+			// skip vertical alignment pattern
+			if($i === 6){
+				$i--;
+			}
+
+			for($count = 0; $count < $this->moduleCount; $count++){
+				$y = $direction ? $this->moduleCount - 1 - $count : $count;
+
+				for($col = 0; $col < 2; $col++){
+					$x = $i - $col;
+
+					// skip functional patterns
+					if($this->get($x, $y) !== $this::M_NULL){
+						continue;
+					}
+
+					$v = $iByte < $byteCount && (($data[$iByte] >> $iBit--) & 1) === 1;
+
+					$this->set($x, $y, $v, $this::M_DATA);
+
+					if($iBit === -1){
+						$iByte++;
+						$iBit = 7;
+					}
+				}
+			}
+
+			$direction = !$direction; // switch directions
+		}
+
+		return $this;
+	}
+
+	/**
+	 * Applies/reverses the mask pattern
 	 *
 	 *
 	 * ISO/IEC 18004:2000 Section 8.8.1
 	 * ISO/IEC 18004:2000 Section 8.8.1
 	 */
 	 */
-	public function mask(MaskPattern $maskPattern):self{
-		$this->maskPattern = $maskPattern;
-		$mask              = $this->maskPattern->getMask();
+	public function mask():self{
+		$mask = $this->maskPattern->getMask();
 
 
-		foreach($this->matrix as $y => &$row){
-			foreach($row as $x => &$val){
+		foreach($this->matrix as $y => $row){
+			foreach($row as $x => $val){
 				if($mask($x, $y) && ($val & $this::M_DATA) === $this::M_DATA){
 				if($mask($x, $y) && ($val & $this::M_DATA) === $this::M_DATA){
-					$val ^= $this::IS_DARK;
+					$this->flip($x, $y);
 				}
 				}
 			}
 			}
 		}
 		}

+ 4 - 9
src/Decoder/Binarizer.php

@@ -11,6 +11,7 @@
 
 
 namespace chillerlan\QRCode\Decoder;
 namespace chillerlan\QRCode\Decoder;
 
 
+use chillerlan\QRCode\Data\QRMatrix;
 use function array_fill, count, max;
 use function array_fill, count, max;
 
 
 /**
 /**
@@ -160,7 +161,6 @@ final class Binarizer{
 	 *
 	 *
 	 */
 	 */
 	private function getHistogramBlackMatrix(int $width, int $height):BitMatrix{
 	private function getHistogramBlackMatrix(int $width, int $height):BitMatrix{
-		$matrix = new BitMatrix(max($width, $height));
 
 
 		// Quickly calculates the histogram by sampling four rows from the image. This proved to be
 		// Quickly calculates the histogram by sampling four rows from the image. This proved to be
 		// more robust on the blackbox tests than sampling a diagonal as we used to do.
 		// more robust on the blackbox tests than sampling a diagonal as we used to do.
@@ -183,16 +183,13 @@ final class Binarizer{
 		// Although we end up reading four rows twice, it is consistent with our motto of
 		// Although we end up reading four rows twice, it is consistent with our motto of
 		// "fail quickly" which is necessary for continuous scanning.
 		// "fail quickly" which is necessary for continuous scanning.
 		$localLuminances = $this->source->getMatrix();
 		$localLuminances = $this->source->getMatrix();
+		$matrix          = new BitMatrix(max($width, $height));
 
 
 		for($y = 0; $y < $height; $y++){
 		for($y = 0; $y < $height; $y++){
 			$offset = $y * $width;
 			$offset = $y * $width;
 
 
 			for($x = 0; $x < $width; $x++){
 			for($x = 0; $x < $width; $x++){
-				$pixel = $localLuminances[$offset + $x] & 0xff;
-
-				if($pixel < $blackPoint){
-					$matrix->set($x, $y);
-				}
+				$matrix->set($x, $y, (($localLuminances[$offset + $x] & 0xff) < $blackPoint), QRMatrix::M_DATA);
 			}
 			}
 		}
 		}
 
 
@@ -339,9 +336,7 @@ final class Binarizer{
 				for($j = 0, $o = $yoffset * $width + $xoffset; $j < self::BLOCK_SIZE; $j++, $o += $width){
 				for($j = 0, $o = $yoffset * $width + $xoffset; $j < self::BLOCK_SIZE; $j++, $o += $width){
 					for($i = 0; $i < self::BLOCK_SIZE; $i++){
 					for($i = 0; $i < self::BLOCK_SIZE; $i++){
 						// Comparison needs to be <= so that black == 0 pixels are black even if the threshold is 0.
 						// Comparison needs to be <= so that black == 0 pixels are black even if the threshold is 0.
-						if(((int)($luminances[$o + $i]) & 0xff) <= $average){
-							$matrix->set($xoffset + $i, $yoffset + $j);
-						}
+						$matrix->set($xoffset + $i, $yoffset + $j, (((int)($luminances[$o + $i]) & 0xff) <= $average), QRMatrix::M_DATA);
 					}
 					}
 				}
 				}
 			}
 			}

+ 182 - 291
src/Decoder/BitMatrix.php

@@ -11,189 +11,92 @@
 
 
 namespace chillerlan\QRCode\Decoder;
 namespace chillerlan\QRCode\Decoder;
 
 
-use chillerlan\QRCode\Common\{FormatInformation, Version};
+use chillerlan\QRCode\Common\{EccLevel, MaskPattern, Version};
+use chillerlan\QRCode\Data\{QRCodeDataException, QRMatrix};
 use function array_fill, count;
 use function array_fill, count;
 use const PHP_INT_MAX, PHP_INT_SIZE;
 use const PHP_INT_MAX, PHP_INT_SIZE;
 
 
 /**
 /**
  *
  *
  */
  */
-final class BitMatrix{
-
-	private int                $dimension;
-	private int                $rowSize;
-	private array              $bits;
-	private ?Version           $version    = null;
-	private ?FormatInformation $formatInfo = null;
-	private bool               $mirror     = false;
+final class BitMatrix extends QRMatrix{
 
 
 	/**
 	/**
+	 * See ISO 18004:2006, Annex C, Table C.1
 	 *
 	 *
+	 * [data bits, sequence after masking]
 	 */
 	 */
-	public function __construct(int $dimension){
-		$this->dimension = $dimension;
-		$this->rowSize   = ((int)(($this->dimension + 0x1f) / 0x20));
-		$this->bits      = array_fill(0, $this->rowSize * $this->dimension, 0);
-	}
+	private const DECODE_LOOKUP = [
+		0x00 => 0x5412,
+		0x01 => 0x5125,
+		0x02 => 0x5E7C,
+		0x03 => 0x5B4B,
+		0x04 => 0x45F9,
+		0x05 => 0x40CE,
+		0x06 => 0x4F97,
+		0x07 => 0x4AA0,
+		0x08 => 0x77C4,
+		0x09 => 0x72F3,
+		0x0A => 0x7DAA,
+		0x0B => 0x789D,
+		0x0C => 0x662F,
+		0x0D => 0x6318,
+		0x0E => 0x6C41,
+		0x0F => 0x6976,
+		0x10 => 0x1689,
+		0x11 => 0x13BE,
+		0x12 => 0x1CE7,
+		0x13 => 0x19D0,
+		0x14 => 0x0762,
+		0x15 => 0x0255,
+		0x16 => 0x0D0C,
+		0x17 => 0x083B,
+		0x18 => 0x355F,
+		0x19 => 0x3068,
+		0x1A => 0x3F31,
+		0x1B => 0x3A06,
+		0x1C => 0x24B4,
+		0x1D => 0x2183,
+		0x1E => 0x2EDA,
+		0x1F => 0x2BED,
+	];
+
+	private const FORMAT_INFO_MASK_QR = 0x5412;
+
+	private bool $mirror = false;
 
 
 	/**
 	/**
-	 * Sets the given bit to true.
-	 *
-	 * @param int $x ;  The horizontal component (i.e. which column)
-	 * @param int $y ;  The vertical component (i.e. which row)
+	 * @noinspection PhpMissingParentConstructorInspection
 	 */
 	 */
-	public function set(int $x, int $y):self{
-		$offset = (int)($y * $this->rowSize + ($x / 0x20));
-
-		$this->bits[$offset] ??= 0;
-		$this->bits[$offset] |= ($this->bits[$offset] |= 1 << ($x & 0x1f));
-
-		return $this;
-	}
-
-	/**
-	 * Flips the given bit. 1 << (0xf9 & 0x1f)
-	 *
-	 * @param int $x ;  The horizontal component (i.e. which column)
-	 * @param int $y ;  The vertical component (i.e. which row)
-	 */
-	public function flip(int $x, int $y):self{
-		$offset = $y * $this->rowSize + (int)($x / 0x20);
-
-		$this->bits[$offset] = ($this->bits[$offset] ^ (1 << ($x & 0x1f)));
-
-		return $this;
+	public function __construct(int $dimension){
+		$this->moduleCount = $dimension;
+		$this->matrix      = array_fill(0, $this->moduleCount, array_fill(0, $this->moduleCount, $this::M_NULL));
 	}
 	}
 
 
 	/**
 	/**
-	 * Sets a square region of the bit matrix to true.
-	 *
-	 * @param int $left   ;  The horizontal position to begin at (inclusive)
-	 * @param int $top    ;  The vertical position to begin at (inclusive)
-	 * @param int $width  ;  The width of the region
-	 * @param int $height ;  The height of the region
+	 * Prepare the parser for a mirrored operation.
+	 * This flag has effect only on the readFormatInformation() and the
+	 * readVersion() methods. Before proceeding with readCodewords() the
+	 * mirror() method should be called.
 	 *
 	 *
-	 * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException
+	 * @param bool $mirror Whether to read version and format information mirrored.
 	 */
 	 */
-	public function setRegion(int $left, int $top, int $width, int $height):self{
-
-		if($top < 0 || $left < 0){
-			throw new QRCodeDecoderException('Left and top must be non-negative');
-		}
-
-		if($height < 1 || $width < 1){
-			throw new QRCodeDecoderException('Height and width must be at least 1');
-		}
-
-		$right  = $left + $width;
-		$bottom = $top + $height;
-
-		if($bottom > $this->dimension || $right > $this->dimension){
-			throw new QRCodeDecoderException('The region must fit inside the matrix');
-		}
-
-		for($y = $top; $y < $bottom; $y++){
-			$yOffset = $y * $this->rowSize;
-
-			for($x = $left; $x < $right; $x++){
-				$xOffset              = $yOffset + (int)($x / 0x20);
-				$this->bits[$xOffset] = ($this->bits[$xOffset] |= 1 << ($x & 0x1f));
-			}
-		}
+	public function setMirror(bool $mirror):self{
+		$this->version     = null;
+		$this->eccLevel    = null;
+		$this->maskPattern = null;
+		$this->mirror      = $mirror;
 
 
 		return $this;
 		return $this;
 	}
 	}
 
 
-	/**
-	 * @return int The dimension (width/height) of the matrix
-	 */
-	public function getDimension():int{
-		return $this->dimension;
-	}
-
-	/**
-	 *
-	 */
-	public function getFormatInfo():?FormatInformation{
-		return $this->formatInfo;
-	}
-
-	/**
-	 *
-	 */
-	public function getVersion():?Version{
-		return $this->version;
-	}
-
-	/**
-	 * Gets the requested bit, where true means black.
-	 *
-	 * @param int $x The horizontal component (i.e. which column)
-	 * @param int $y The vertical component (i.e. which row)
-	 *
-	 * @return bool value of given bit in matrix
-	 */
-	public function get(int $x, int $y):bool{
-		$offset = (int)($y * $this->rowSize + ($x / 0x20));
-
-		$this->bits[$offset] ??= 0;
-
-		return ($this->uRShift($this->bits[$offset], ($x & 0x1f)) & 1) !== 0;
-	}
-
-	/**
-	 * See ISO 18004:2006 Annex E
-	 */
-	private function buildFunctionPattern():self{
-		$dimension = $this->version->getDimension();
-		$bitMatrix = new self($dimension);
-
-		// Top left finder pattern + separator + format
-		$bitMatrix->setRegion(0, 0, 9, 9);
-		// Top right finder pattern + separator + format
-		$bitMatrix->setRegion($dimension - 8, 0, 8, 9);
-		// Bottom left finder pattern + separator + format
-		$bitMatrix->setRegion(0, $dimension - 8, 9, 8);
-
-		// Alignment patterns
-		$apc = $this->version->getAlignmentPattern();
-		$max = count($apc);
-
-		for($x = 0; $x < $max; $x++){
-			$i = $apc[$x] - 2;
-
-			for($y = 0; $y < $max; $y++){
-				if(($x === 0 && ($y === 0 || $y === $max - 1)) || ($x === $max - 1 && $y === 0)){
-					// No alignment patterns near the three finder paterns
-					continue;
-				}
-
-				$bitMatrix->setRegion($apc[$y] - 2, $i, 5, 5);
-			}
-		}
-
-		// Vertical timing pattern
-		$bitMatrix->setRegion(6, 9, 1, $dimension - 17);
-		// Horizontal timing pattern
-		$bitMatrix->setRegion(9, 6, $dimension - 17, 1);
-
-		if($this->version->getVersionNumber() > 6){
-			// Version info, top right
-			$bitMatrix->setRegion($dimension - 11, 0, 3, 6);
-			// Version info, bottom left
-			$bitMatrix->setRegion(0, $dimension - 11, 6, 3);
-		}
-
-		return $bitMatrix;
-	}
-
 	/**
 	/**
 	 * Mirror the bit matrix in order to attempt a second reading.
 	 * Mirror the bit matrix in order to attempt a second reading.
 	 */
 	 */
 	public function mirror():self{
 	public function mirror():self{
 
 
-		for($x = 0; $x < $this->dimension; $x++){
-			for($y = $x + 1; $y < $this->dimension; $y++){
+		for($x = 0; $x < $this->moduleCount; $x++){
+			for($y = $x + 1; $y < $this->moduleCount; $y++){
 				if($this->get($x, $y) !== $this->get($y, $x)){
 				if($this->get($x, $y) !== $this->get($y, $x)){
 					$this->flip($y, $x);
 					$this->flip($y, $x);
 					$this->flip($x, $y);
 					$this->flip($x, $y);
@@ -204,51 +107,6 @@ final class BitMatrix{
 		return $this;
 		return $this;
 	}
 	}
 
 
-	/**
-	 * Implementations of this method reverse the data masking process applied to a QR Code and
-	 * make its bits ready to read.
-	 */
-	private function unmask():void{
-		$mask = $this->formatInfo->getMaskPattern()->getMask();
-
-		for($y = 0; $y < $this->dimension; $y++){
-			for($x = 0; $x < $this->dimension; $x++){
-				if($mask($x, $y)){
-					$this->flip($x, $y);
-				}
-			}
-		}
-
-	}
-
-	/**
-	 * Prepare the parser for a mirrored operation.
-	 * This flag has effect only on the readFormatInformation() and the
-	 * readVersion() methods. Before proceeding with readCodewords() the
-	 * mirror() method should be called.
-	 *
-	 * @param bool $mirror Whether to read version and format information mirrored.
-	 */
-	public function setMirror(bool $mirror):self{
-		$this->version    = null;
-		$this->formatInfo = null;
-		$this->mirror     = $mirror;
-
-		return $this;
-	}
-
-	/**
-	 *
-	 */
-	private function copyBit(int $i, int $j, int $versionBits):int{
-
-		$bit = $this->mirror
-			? $this->get($j, $i)
-			: $this->get($i, $j);
-
-		return $bit ? ($versionBits << 1) | 0x1 : $versionBits << 1;
-	}
-
 	/**
 	/**
 	 * Reads the bits in the BitMatrix representing the finder pattern in the
 	 * Reads the bits in the BitMatrix representing the finder pattern in the
 	 * correct order in order to reconstruct the codewords bytes contained within the
 	 * correct order in order to reconstruct the codewords bytes contained within the
@@ -258,56 +116,60 @@ final class BitMatrix{
 	 * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException if the exact number of bytes expected is not read
 	 * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException if the exact number of bytes expected is not read
 	 */
 	 */
 	public function readCodewords():array{
 	public function readCodewords():array{
-		$this->formatInfo = $this->readFormatInformation();
-		$this->version    = $this->readVersion();
-
-		// Get the data mask for the format used in this QR Code. This will exclude
-		// some bits from reading as we wind through the bit matrix.
-		$this->unmask();
-		$functionPattern = $this->buildFunctionPattern();
-
-		$readingUp    = true;
-		$result       = [];
-		$resultOffset = 0;
-		$currentByte  = 0;
-		$bitsRead     = 0;
+
+		$this
+			->readFormatInformation()
+			->readVersion()
+			->mask() // reverse the mask pattern
+		;
+
+		// invoke a fresh matrix with only the function & format patterns to compare against
+		$fp        = (new QRMatrix($this->version, $this->eccLevel, $this->maskPattern))->initFunctionalPatterns();
+		$result    = [];
+		$byte      = 0;
+		$bitsRead  = 0;
+		$direction = true;
+
 		// Read columns in pairs, from right to left
 		// Read columns in pairs, from right to left
-		for($j = $this->dimension - 1; $j > 0; $j -= 2){
+		for($i = $this->moduleCount - 1; $i > 0; $i -= 2){
 
 
-			if($j === 6){
-				// Skip whole column with vertical alignment pattern;
-				// saves time and makes the other code proceed more cleanly
-				$j--;
+			// Skip whole column with vertical alignment pattern;
+			// saves time and makes the other code proceed more cleanly
+			if($i === 6){
+				$i--;
 			}
 			}
 			// Read alternatingly from bottom to top then top to bottom
 			// Read alternatingly from bottom to top then top to bottom
-			for($count = 0; $count < $this->dimension; $count++){
-				$i = $readingUp ? $this->dimension - 1 - $count : $count;
+			for($count = 0; $count < $this->moduleCount; $count++){
+				$y = $direction ? $this->moduleCount - 1 - $count : $count;
 
 
 				for($col = 0; $col < 2; $col++){
 				for($col = 0; $col < 2; $col++){
+					$x = $i - $col;
+
 					// Ignore bits covered by the function pattern
 					// Ignore bits covered by the function pattern
-					if(!$functionPattern->get($j - $col, $i)){
-						// Read a bit
-						$bitsRead++;
-						$currentByte <<= 1;
-
-						if($this->get($j - $col, $i)){
-							$currentByte |= 1;
-						}
-						// If we've made a whole byte, save it off
-						if($bitsRead === 8){
-							$result[$resultOffset++] = $currentByte; //(byte)
-							$bitsRead                = 0;
-							$currentByte             = 0;
-						}
+					if($fp->get($x, $y) !== $this::M_NULL){
+						continue;
+					}
+
+					$bitsRead++;
+					$byte <<= 1;
+
+					if($this->check($x, $y)){
+						$byte |= 1;
+					}
+					// If we've made a whole byte, save it off
+					if($bitsRead === 8){
+						$result[] = $byte;
+						$bitsRead = 0;
+						$byte     = 0;
 					}
 					}
 				}
 				}
 			}
 			}
 
 
-			$readingUp = !$readingUp; // switch directions
+			$direction = !$direction; // switch directions
 		}
 		}
 
 
-		if($resultOffset !== $this->version->getTotalCodewords()){
-			throw new QRCodeDecoderException('offset differs from total codewords for version');
+		if(count($result) !== $this->version->getTotalCodewords()){
+			throw new QRCodeDecoderException('result count differs from total codewords for version');
 		}
 		}
 
 
 		return $result;
 		return $result;
@@ -316,62 +178,77 @@ final class BitMatrix{
 	/**
 	/**
 	 * Reads format information from one of its two locations within the QR Code.
 	 * Reads format information from one of its two locations within the QR Code.
 	 *
 	 *
-	 * @return \chillerlan\QRCode\Common\FormatInformation       encapsulating the QR Code's format info
 	 * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException if both format information locations cannot be parsed as
 	 * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException if both format information locations cannot be parsed as
 	 *                                                           the valid encoding of format information
 	 *                                                           the valid encoding of format information
 	 */
 	 */
-	private function readFormatInformation():FormatInformation{
+	private function readFormatInformation():self{
 
 
-		if($this->formatInfo !== null){
-			return $this->formatInfo;
+		if($this->eccLevel !== null && $this->maskPattern !== null){
+			return $this;
 		}
 		}
 
 
 		// Read top-left format info bits
 		// Read top-left format info bits
 		$formatInfoBits1 = 0;
 		$formatInfoBits1 = 0;
 
 
 		for($i = 0; $i < 6; $i++){
 		for($i = 0; $i < 6; $i++){
-			$formatInfoBits1 = $this->copyBit($i, 8, $formatInfoBits1);
+			$formatInfoBits1 = $this->copyVersionBit($i, 8, $formatInfoBits1);
 		}
 		}
 
 
 		// .. and skip a bit in the timing pattern ...
 		// .. and skip a bit in the timing pattern ...
-		$formatInfoBits1 = $this->copyBit(7, 8, $formatInfoBits1);
-		$formatInfoBits1 = $this->copyBit(8, 8, $formatInfoBits1);
-		$formatInfoBits1 = $this->copyBit(8, 7, $formatInfoBits1);
+		$formatInfoBits1 = $this->copyVersionBit(7, 8, $formatInfoBits1);
+		$formatInfoBits1 = $this->copyVersionBit(8, 8, $formatInfoBits1);
+		$formatInfoBits1 = $this->copyVersionBit(8, 7, $formatInfoBits1);
 		// .. and skip a bit in the timing pattern ...
 		// .. and skip a bit in the timing pattern ...
 		for($j = 5; $j >= 0; $j--){
 		for($j = 5; $j >= 0; $j--){
-			$formatInfoBits1 = $this->copyBit(8, $j, $formatInfoBits1);
+			$formatInfoBits1 = $this->copyVersionBit(8, $j, $formatInfoBits1);
 		}
 		}
 
 
 		// Read the top-right/bottom-left pattern too
 		// Read the top-right/bottom-left pattern too
 		$formatInfoBits2 = 0;
 		$formatInfoBits2 = 0;
-		$jMin            = $this->dimension - 7;
+		$jMin            = $this->moduleCount - 7;
 
 
-		for($j = $this->dimension - 1; $j >= $jMin; $j--){
-			$formatInfoBits2 = $this->copyBit(8, $j, $formatInfoBits2);
+		for($j = $this->moduleCount - 1; $j >= $jMin; $j--){
+			$formatInfoBits2 = $this->copyVersionBit(8, $j, $formatInfoBits2);
 		}
 		}
 
 
-		for($i = $this->dimension - 8; $i < $this->dimension; $i++){
-			$formatInfoBits2 = $this->copyBit($i, 8, $formatInfoBits2);
+		for($i = $this->moduleCount - 8; $i < $this->moduleCount; $i++){
+			$formatInfoBits2 = $this->copyVersionBit($i, 8, $formatInfoBits2);
 		}
 		}
 
 
-		$this->formatInfo = $this->doDecodeFormatInformation($formatInfoBits1, $formatInfoBits2);
+		$formatInfo = $this->doDecodeFormatInformation($formatInfoBits1, $formatInfoBits2);
 
 
-		if($this->formatInfo !== null){
-			return $this->formatInfo;
-		}
+		if($formatInfo === null){
+
+			// Should return null, but, some QR codes apparently do not mask this info.
+			// Try again by actually masking the pattern first.
+			$formatInfo = $this->doDecodeFormatInformation(
+				$formatInfoBits1 ^ $this::FORMAT_INFO_MASK_QR,
+				$formatInfoBits2 ^ $this::FORMAT_INFO_MASK_QR
+			);
 
 
-		// Should return null, but, some QR codes apparently do not mask this info.
-		// Try again by actually masking the pattern first.
-		$this->formatInfo = $this->doDecodeFormatInformation(
-			$formatInfoBits1 ^ FormatInformation::FORMAT_INFO_MASK_QR,
-			$formatInfoBits2 ^ FormatInformation::FORMAT_INFO_MASK_QR
-		);
+			// still nothing???
+			if($formatInfo === null){
+				throw new QRCodeDecoderException('failed to read format info'); // @codeCoverageIgnore
+			}
 
 
-		if($this->formatInfo !== null){
-			return $this->formatInfo;
 		}
 		}
 
 
-		throw new QRCodeDecoderException('failed to read format info');
+		$this->eccLevel    = new EccLevel(($formatInfo >> 3) & 0x03); // Bits 3,4
+		$this->maskPattern = new MaskPattern($formatInfo & 0x07); // Bottom 3 bits
+
+		return $this;
+	}
+
+	/**
+	 *
+	 */
+	private function copyVersionBit(int $i, int $j, int $versionBits):int{
+
+		$bit = $this->mirror
+			? $this->check($j, $i)
+			: $this->check($i, $j);
+
+		return $bit ? ($versionBits << 1) | 0x1 : $versionBits << 1;
 	}
 	}
 
 
 	/**
 	/**
@@ -379,20 +256,18 @@ final class BitMatrix{
 	 * @param int $maskedFormatInfo2 second copy of same info; both are checked at the same time
 	 * @param int $maskedFormatInfo2 second copy of same info; both are checked at the same time
 	 *                               to establish best match
 	 *                               to establish best match
 	 *
 	 *
-	 * @return \chillerlan\QRCode\Common\FormatInformation|null information about the format it specifies, or null
-	 *                                                          if doesn't seem to match any known pattern
+	 * @return int|null information about the format it specifies, or null if doesn't seem to match any known pattern
 	 */
 	 */
-	private function doDecodeFormatInformation(int $maskedFormatInfo1, int $maskedFormatInfo2):?FormatInformation{
+	private function doDecodeFormatInformation(int $maskedFormatInfo1, int $maskedFormatInfo2):?int{
 		// Find the int in FORMAT_INFO_DECODE_LOOKUP with fewest bits differing
 		// Find the int in FORMAT_INFO_DECODE_LOOKUP with fewest bits differing
 		$bestDifference = PHP_INT_MAX;
 		$bestDifference = PHP_INT_MAX;
 		$bestFormatInfo = 0;
 		$bestFormatInfo = 0;
 
 
-		foreach(FormatInformation::DECODE_LOOKUP as $decodeInfo){
-			[$maskedBits, $dataBits] = $decodeInfo;
+		foreach($this::DECODE_LOOKUP as $maskedBits => $dataBits){
 
 
 			if($maskedFormatInfo1 === $dataBits || $maskedFormatInfo2 === $dataBits){
 			if($maskedFormatInfo1 === $dataBits || $maskedFormatInfo2 === $dataBits){
 				// Found an exact match
 				// Found an exact match
-				return new FormatInformation($maskedBits);
+				return $maskedBits;
 			}
 			}
 
 
 			$bitsDifference = $this->numBitsDiffering($maskedFormatInfo1, $dataBits);
 			$bitsDifference = $this->numBitsDiffering($maskedFormatInfo1, $dataBits);
@@ -414,7 +289,7 @@ final class BitMatrix{
 		}
 		}
 		// Hamming distance of the 32 masked codes is 7, by construction, so <= 3 bits differing means we found a match
 		// Hamming distance of the 32 masked codes is 7, by construction, so <= 3 bits differing means we found a match
 		if($bestDifference <= 3){
 		if($bestDifference <= 3){
-			return new FormatInformation($bestFormatInfo);
+			return $bestFormatInfo;
 		}
 		}
 
 
 		return null;
 		return null;
@@ -423,61 +298,61 @@ final class BitMatrix{
 	/**
 	/**
 	 * Reads version information from one of its two locations within the QR Code.
 	 * Reads version information from one of its two locations within the QR Code.
 	 *
 	 *
-	 * @return \chillerlan\QRCode\Common\Version                 encapsulating the QR Code's version
 	 * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException if both version information locations cannot be parsed as
 	 * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException if both version information locations cannot be parsed as
 	 *                                                           the valid encoding of version information
 	 *                                                           the valid encoding of version information
 	 * @noinspection DuplicatedCode
 	 * @noinspection DuplicatedCode
 	 */
 	 */
-	private function readVersion():Version{
+	private function readVersion():self{
 
 
 		if($this->version !== null){
 		if($this->version !== null){
-			return $this->version;
+			return $this;
 		}
 		}
 
 
-		$provisionalVersion = ($this->dimension - 17) / 4;
+		$provisionalVersion = ($this->moduleCount - 17) / 4;
 
 
-		if($provisionalVersion <= 6){
-			return new Version($provisionalVersion);
+		// no version info if v < 7
+		if($provisionalVersion < 7){
+			$this->version = new Version($provisionalVersion);
+
+			return $this;
 		}
 		}
 
 
 		// Read top-right version info: 3 wide by 6 tall
 		// Read top-right version info: 3 wide by 6 tall
 		$versionBits = 0;
 		$versionBits = 0;
-		$ijMin       = $this->dimension - 11;
+		$ijMin       = $this->moduleCount - 11;
 
 
-		for($j = 5; $j >= 0; $j--){
-			for($i = $this->dimension - 9; $i >= $ijMin; $i--){
-				$versionBits = $this->copyBit($i, $j, $versionBits);
+		for($y = 5; $y >= 0; $y--){
+			for($x = $this->moduleCount - 9; $x >= $ijMin; $x--){
+				$versionBits = $this->copyVersionBit($x, $y, $versionBits);
 			}
 			}
 		}
 		}
 
 
 		$this->version = $this->decodeVersionInformation($versionBits);
 		$this->version = $this->decodeVersionInformation($versionBits);
 
 
-		if($this->version !== null && $this->version->getDimension() === $this->dimension){
-			return $this->version;
+		if($this->version !== null && $this->version->getDimension() === $this->moduleCount){
+			return $this;
 		}
 		}
 
 
 		// Hmm, failed. Try bottom left: 6 wide by 3 tall
 		// Hmm, failed. Try bottom left: 6 wide by 3 tall
 		$versionBits = 0;
 		$versionBits = 0;
 
 
-		for($i = 5; $i >= 0; $i--){
-			for($j = $this->dimension - 9; $j >= $ijMin; $j--){
-				$versionBits = $this->copyBit($i, $j, $versionBits);
+		for($x = 5; $x >= 0; $x--){
+			for($y = $this->moduleCount - 9; $y >= $ijMin; $y--){
+				$versionBits = $this->copyVersionBit($x, $y, $versionBits);
 			}
 			}
 		}
 		}
 
 
 		$this->version = $this->decodeVersionInformation($versionBits);
 		$this->version = $this->decodeVersionInformation($versionBits);
 
 
-		if($this->version !== null && $this->version->getDimension() === $this->dimension){
-			return $this->version;
+		if($this->version !== null && $this->version->getDimension() === $this->moduleCount){
+			return $this;
 		}
 		}
 
 
 		throw new QRCodeDecoderException('failed to read version');
 		throw new QRCodeDecoderException('failed to read version');
 	}
 	}
 
 
 	/**
 	/**
-	 * @param int $versionBits
 	 *
 	 *
-	 * @return \chillerlan\QRCode\Common\Version|null
 	 */
 	 */
 	private function decodeVersionInformation(int $versionBits):?Version{
 	private function decodeVersionInformation(int $versionBits):?Version{
 		$bestDifference = PHP_INT_MAX;
 		$bestDifference = PHP_INT_MAX;
@@ -542,4 +417,20 @@ final class BitMatrix{
 		return $count;
 		return $count;
 	}
 	}
 
 
+	/**
+	 * @codeCoverageIgnore
+	 * @throws \chillerlan\QRCode\Data\QRCodeDataException
+	 */
+	public function setQuietZone(int $size = null):self{
+		throw new QRCodeDataException('not supported');
+	}
+
+	/**
+	 * @codeCoverageIgnore
+	 * @throws \chillerlan\QRCode\Data\QRCodeDataException
+	 */
+	public function setLogoSpace(int $width, int $height, int $startX = null, int $startY = null):self{
+		throw new QRCodeDataException('not supported');
+	}
+
 }
 }

+ 15 - 14
src/Decoder/Decoder.php

@@ -12,7 +12,7 @@
 namespace chillerlan\QRCode\Decoder;
 namespace chillerlan\QRCode\Decoder;
 
 
 use Throwable;
 use Throwable;
-use chillerlan\QRCode\Common\{BitBuffer, EccLevel, FormatInformation, Mode, ReedSolomonDecoder, Version};
+use chillerlan\QRCode\Common\{BitBuffer, EccLevel, MaskPattern, Mode, ReedSolomonDecoder, Version};
 use chillerlan\QRCode\Data\{AlphaNum, Byte, ECI, Kanji, Number};
 use chillerlan\QRCode\Data\{AlphaNum, Byte, ECI, Kanji, Number};
 use chillerlan\QRCode\Detector\Detector;
 use chillerlan\QRCode\Detector\Detector;
 use function count, array_fill, mb_convert_encoding, mb_detect_encoding;
 use function count, array_fill, mb_convert_encoding, mb_detect_encoding;
@@ -28,8 +28,8 @@ final class Decoder{
 #	private const GB2312_SUBSET = 1;
 #	private const GB2312_SUBSET = 1;
 
 
 	private ?Version $version = null;
 	private ?Version $version = null;
-	private ?FormatInformation $formatInfo = null;
-	private EccLevel $eccLevel;
+	private ?EccLevel $eccLevel = null;
+	private ?MaskPattern $maskPattern = null;
 
 
 	/**
 	/**
 	 * Decodes a QR Code represented as a BitMatrix.
 	 * Decodes a QR Code represented as a BitMatrix.
@@ -41,11 +41,11 @@ final class Decoder{
 	 * @throws \Throwable|\chillerlan\QRCode\Decoder\QRCodeDecoderException if the QR Code cannot be decoded
 	 * @throws \Throwable|\chillerlan\QRCode\Decoder\QRCodeDecoderException if the QR Code cannot be decoded
 	 */
 	 */
 	public function decode(LuminanceSourceInterface $source):DecoderResult{
 	public function decode(LuminanceSourceInterface $source):DecoderResult{
-		$bitMatrix = (new Detector($source))->detect();
+		$matrix = (new Detector($source))->detect();
 
 
 		try{
 		try{
 			// clone the BitMatrix to avoid errors in case we run into mirroring
 			// clone the BitMatrix to avoid errors in case we run into mirroring
-			return $this->decodeMatrix(clone $bitMatrix);
+			return $this->decodeMatrix(clone $matrix);
 		}
 		}
 		catch(Throwable $e){
 		catch(Throwable $e){
 
 
@@ -58,7 +58,7 @@ final class Decoder{
 				 * that the QR code may be mirrored, and we should try once more with a
 				 * that the QR code may be mirrored, and we should try once more with a
 				 * mirrored content.
 				 * mirrored content.
 				 */
 				 */
-				return $this->decodeMatrix($bitMatrix->setMirror(true)->mirror());
+				return $this->decodeMatrix($matrix->setMirror(true)->mirror());
 			}
 			}
 			catch(Throwable $f){
 			catch(Throwable $f){
 				// Throw the exception from the original reading
 				// Throw the exception from the original reading
@@ -72,18 +72,18 @@ final class Decoder{
 	/**
 	/**
 	 * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException
 	 * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException
 	 */
 	 */
-	private function decodeMatrix(BitMatrix $bitMatrix):DecoderResult{
+	private function decodeMatrix(BitMatrix $matrix):DecoderResult{
 		// Read raw codewords
 		// Read raw codewords
-		$rawCodewords     = $bitMatrix->readCodewords();
-		$this->version    = $bitMatrix->getVersion();
-		$this->formatInfo = $bitMatrix->getFormatInfo();
+		$rawCodewords      = $matrix->readCodewords();
+		$this->version     = $matrix->version();
+		$this->eccLevel    = $matrix->eccLevel();
+		$this->maskPattern = $matrix->maskPattern();
 
 
-		if($this->version === null || $this->formatInfo === null){
+		if($this->version === null || $this->eccLevel === null || $this->maskPattern === null){
 			throw new QRCodeDecoderException('unable to read version or format info'); // @codeCoverageIgnore
 			throw new QRCodeDecoderException('unable to read version or format info'); // @codeCoverageIgnore
 		}
 		}
 
 
-		$this->eccLevel = $this->formatInfo->getErrorCorrectionLevel();
-		$resultBytes    = (new ReedSolomonDecoder)->decode($this->getDataBlocks($rawCodewords));
+		$resultBytes = (new ReedSolomonDecoder)->decode($this->getDataBlocks($rawCodewords));
 		// Decode the contents of that stream of bytes
 		// Decode the contents of that stream of bytes
 		return $this->decodeBitStream($resultBytes);
 		return $this->decodeBitStream($resultBytes);
 	}
 	}
@@ -102,6 +102,7 @@ final class Decoder{
 
 
 		// Figure out the number and size of data blocks used by this version and
 		// Figure out the number and size of data blocks used by this version and
 		// error correction level
 		// error correction level
+		/** @phan-suppress-next-line PhanTypeMismatchArgumentNullable */
 		[$numEccCodewords, $eccBlocks] = $this->version->getRSBlocks($this->eccLevel);
 		[$numEccCodewords, $eccBlocks] = $this->version->getRSBlocks($this->eccLevel);
 
 
 		// Now establish DataBlocks of the appropriate size and number of data codewords
 		// Now establish DataBlocks of the appropriate size and number of data codewords
@@ -278,7 +279,7 @@ final class Decoder{
 			'data'                     => $result,
 			'data'                     => $result,
 			'version'                  => $this->version,
 			'version'                  => $this->version,
 			'eccLevel'                 => $this->eccLevel,
 			'eccLevel'                 => $this->eccLevel,
-			'maskPattern'              => $this->formatInfo->getMaskPattern(),
+			'maskPattern'              => $this->maskPattern,
 			'structuredAppendParity'   => $parityData,
 			'structuredAppendParity'   => $parityData,
 			'structuredAppendSequence' => $symbolSequence
 			'structuredAppendSequence' => $symbolSequence
 		]);
 		]);

+ 12 - 12
src/Detector/AlignmentPatternFinder.php

@@ -30,7 +30,7 @@ use function abs, count;
  */
  */
 final class AlignmentPatternFinder{
 final class AlignmentPatternFinder{
 
 
-	private BitMatrix $bitMatrix;
+	private BitMatrix $matrix;
 	private float     $moduleSize;
 	private float     $moduleSize;
 	/** @var \chillerlan\QRCode\Detector\AlignmentPattern[] */
 	/** @var \chillerlan\QRCode\Detector\AlignmentPattern[] */
 	private array $possibleCenters;
 	private array $possibleCenters;
@@ -38,12 +38,12 @@ final class AlignmentPatternFinder{
 	/**
 	/**
 	 * Creates a finder that will look in a portion of the whole image.
 	 * Creates a finder that will look in a portion of the whole image.
 	 *
 	 *
-	 * @param \chillerlan\QRCode\Decoder\BitMatrix $image      image to search
+	 * @param \chillerlan\QRCode\Decoder\BitMatrix $matrix     image to search
 	 * @param float                                $moduleSize estimated module size so far
 	 * @param float                                $moduleSize estimated module size so far
 	 */
 	 */
-	public function __construct(BitMatrix $image, float $moduleSize){
-		$this->bitMatrix            = $image;
-		$this->moduleSize           = $moduleSize;
+	public function __construct(BitMatrix $matrix, float $moduleSize){
+		$this->matrix     = $matrix;
+		$this->moduleSize = $moduleSize;
 		$this->possibleCenters      = [];
 		$this->possibleCenters      = [];
 	}
 	}
 
 
@@ -75,7 +75,7 @@ final class AlignmentPatternFinder{
 			// Burn off leading white pixels before anything else; if we start in the middle of
 			// Burn off leading white pixels before anything else; if we start in the middle of
 			// a white run, it doesn't make sense to count its length, since we don't know if the
 			// a white run, it doesn't make sense to count its length, since we don't know if the
 			// white run continued to the left of the start point
 			// white run continued to the left of the start point
-			while($j < $maxJ && !$this->bitMatrix->get($j, $i)){
+			while($j < $maxJ && !$this->matrix->check($j, $i)){
 				$j++;
 				$j++;
 			}
 			}
 
 
@@ -83,7 +83,7 @@ final class AlignmentPatternFinder{
 
 
 			while($j < $maxJ){
 			while($j < $maxJ){
 
 
-				if($this->bitMatrix->get($j, $i)){
+				if($this->matrix->check($j, $i)){
 					// Black pixel
 					// Black pixel
 					if($currentState === 1){ // Counting black pixels
 					if($currentState === 1){ // Counting black pixels
 						$stateCount[$currentState]++;
 						$stateCount[$currentState]++;
@@ -224,7 +224,7 @@ final class AlignmentPatternFinder{
 	 * @return float|null vertical center of alignment pattern, or null if not found
 	 * @return float|null vertical center of alignment pattern, or null if not found
 	 */
 	 */
 	private function crossCheckVertical(int $startI, int $centerJ, int $maxCount, int $originalStateCountTotal):?float{
 	private function crossCheckVertical(int $startI, int $centerJ, int $maxCount, int $originalStateCountTotal):?float{
-		$maxI          = $this->bitMatrix->getDimension();
+		$maxI          = $this->matrix->size();
 		$stateCount    = [];
 		$stateCount    = [];
 		$stateCount[0] = 0;
 		$stateCount[0] = 0;
 		$stateCount[1] = 0;
 		$stateCount[1] = 0;
@@ -232,7 +232,7 @@ final class AlignmentPatternFinder{
 
 
 		// Start counting up from center
 		// Start counting up from center
 		$i = $startI;
 		$i = $startI;
-		while($i >= 0 && $this->bitMatrix->get($centerJ, $i) && $stateCount[1] <= $maxCount){
+		while($i >= 0 && $this->matrix->check($centerJ, $i) && $stateCount[1] <= $maxCount){
 			$stateCount[1]++;
 			$stateCount[1]++;
 			$i--;
 			$i--;
 		}
 		}
@@ -241,7 +241,7 @@ final class AlignmentPatternFinder{
 			return null;
 			return null;
 		}
 		}
 
 
-		while($i >= 0 && !$this->bitMatrix->get($centerJ, $i) && $stateCount[0] <= $maxCount){
+		while($i >= 0 && !$this->matrix->check($centerJ, $i) && $stateCount[0] <= $maxCount){
 			$stateCount[0]++;
 			$stateCount[0]++;
 			$i--;
 			$i--;
 		}
 		}
@@ -252,7 +252,7 @@ final class AlignmentPatternFinder{
 
 
 		// Now also count down from center
 		// Now also count down from center
 		$i = $startI + 1;
 		$i = $startI + 1;
-		while($i < $maxI && $this->bitMatrix->get($centerJ, $i) && $stateCount[1] <= $maxCount){
+		while($i < $maxI && $this->matrix->check($centerJ, $i) && $stateCount[1] <= $maxCount){
 			$stateCount[1]++;
 			$stateCount[1]++;
 			$i++;
 			$i++;
 		}
 		}
@@ -261,7 +261,7 @@ final class AlignmentPatternFinder{
 			return null;
 			return null;
 		}
 		}
 
 
-		while($i < $maxI && !$this->bitMatrix->get($centerJ, $i) && $stateCount[2] <= $maxCount){
+		while($i < $maxI && !$this->matrix->check($centerJ, $i) && $stateCount[2] <= $maxCount){
 			$stateCount[2]++;
 			$stateCount[2]++;
 			$i++;
 			$i++;
 		}
 		}

+ 8 - 8
src/Detector/Detector.php

@@ -25,20 +25,20 @@ use const NAN;
  */
  */
 final class Detector{
 final class Detector{
 
 
-	private BitMatrix $bitMatrix;
+	private BitMatrix $matrix;
 
 
 	/**
 	/**
 	 * Detector constructor.
 	 * Detector constructor.
 	 */
 	 */
 	public function __construct(LuminanceSourceInterface $source){
 	public function __construct(LuminanceSourceInterface $source){
-		$this->bitMatrix = (new Binarizer($source))->getBlackMatrix();
+		$this->matrix = (new Binarizer($source))->getBlackMatrix();
 	}
 	}
 
 
 	/**
 	/**
 	 * Detects a QR Code in an image.
 	 * Detects a QR Code in an image.
 	 */
 	 */
 	public function detect():BitMatrix{
 	public function detect():BitMatrix{
-		[$bottomLeft, $topLeft, $topRight] = (new FinderPatternFinder($this->bitMatrix))->find();
+		[$bottomLeft, $topLeft, $topRight] = (new FinderPatternFinder($this->matrix))->find();
 
 
 		$moduleSize         = $this->calculateModuleSize($topLeft, $topRight, $bottomLeft);
 		$moduleSize         = $this->calculateModuleSize($topLeft, $topRight, $bottomLeft);
 		$dimension          = $this->computeDimension($topLeft, $topRight, $bottomLeft, $moduleSize);
 		$dimension          = $this->computeDimension($topLeft, $topRight, $bottomLeft, $moduleSize);
@@ -70,7 +70,7 @@ final class Detector{
 
 
 		$transform = $this->createTransform($topLeft, $topRight, $bottomLeft, $dimension, $alignmentPattern);
 		$transform = $this->createTransform($topLeft, $topRight, $bottomLeft, $dimension, $alignmentPattern);
 
 
-		return (new GridSampler)->sampleGrid($this->bitMatrix, $dimension, $transform);
+		return (new GridSampler)->sampleGrid($this->matrix, $dimension, $transform);
 	}
 	}
 
 
 	/**
 	/**
@@ -135,7 +135,7 @@ final class Detector{
 	 */
 	 */
 	private function sizeOfBlackWhiteBlackRunBothWays(float $fromX, float $fromY, float $toX, float $toY):float{
 	private function sizeOfBlackWhiteBlackRunBothWays(float $fromX, float $fromY, float $toX, float $toY):float{
 		$result    = $this->sizeOfBlackWhiteBlackRun((int)$fromX, (int)$fromY, (int)$toX, (int)$toY);
 		$result    = $this->sizeOfBlackWhiteBlackRun((int)$fromX, (int)$fromY, (int)$toX, (int)$toY);
-		$dimension = $this->bitMatrix->getDimension();
+		$dimension = $this->matrix->size();
 		// Now count other way -- don't run off image though of course
 		// Now count other way -- don't run off image though of course
 		$scale     = 1.0;
 		$scale     = 1.0;
 		$otherToX  = $fromX - ($toX - $fromX);
 		$otherToX  = $fromX - ($toX - $fromX);
@@ -208,7 +208,7 @@ final class Detector{
 			// Does current pixel mean we have moved white to black or vice versa?
 			// Does current pixel mean we have moved white to black or vice versa?
 			// Scanning black in state 0,2 and white in state 1, so if we find the wrong
 			// Scanning black in state 0,2 and white in state 1, so if we find the wrong
 			// color, advance to next state or end if we are in state 2 already
 			// color, advance to next state or end if we are in state 2 already
-			if(($state === 1) === $this->bitMatrix->get($realX, $realY)){
+			if(($state === 1) === $this->matrix->check($realX, $realY)){
 
 
 				if($state === 2){
 				if($state === 2){
 					return FinderPattern::distance($x, $y, $fromX, $fromY);
 					return FinderPattern::distance($x, $y, $fromX, $fromY);
@@ -294,7 +294,7 @@ final class Detector{
 		float $allowanceFactor
 		float $allowanceFactor
 	):?AlignmentPattern{
 	):?AlignmentPattern{
 		// Look for an alignment pattern (3 modules in size) around where it should be
 		// Look for an alignment pattern (3 modules in size) around where it should be
-		$dimension           = $this->bitMatrix->getDimension();
+		$dimension           = $this->matrix->size();
 		$allowance           = (int)($allowanceFactor * $overallEstModuleSize);
 		$allowance           = (int)($allowanceFactor * $overallEstModuleSize);
 		$alignmentAreaLeftX  = max(0, $estAlignmentX - $allowance);
 		$alignmentAreaLeftX  = max(0, $estAlignmentX - $allowance);
 		$alignmentAreaRightX = min($dimension - 1, $estAlignmentX + $allowance);
 		$alignmentAreaRightX = min($dimension - 1, $estAlignmentX + $allowance);
@@ -310,7 +310,7 @@ final class Detector{
 			return null;
 			return null;
 		}
 		}
 
 
-		return (new AlignmentPatternFinder($this->bitMatrix, $overallEstModuleSize))->find(
+		return (new AlignmentPatternFinder($this->matrix, $overallEstModuleSize))->find(
 			$alignmentAreaLeftX,
 			$alignmentAreaLeftX,
 			$alignmentAreaTopY,
 			$alignmentAreaTopY,
 			$alignmentAreaRightX - $alignmentAreaLeftX,
 			$alignmentAreaRightX - $alignmentAreaLeftX,

+ 29 - 27
src/Detector/FinderPatternFinder.php

@@ -30,7 +30,7 @@ final class FinderPatternFinder{
 	private const MIN_SKIP      = 2;
 	private const MIN_SKIP      = 2;
 	private const MAX_MODULES   = 177; // 1 pixel/module times 3 modules/center
 	private const MAX_MODULES   = 177; // 1 pixel/module times 3 modules/center
 	private const CENTER_QUORUM = 2; // support up to version 10 for mobile clients
 	private const CENTER_QUORUM = 2; // support up to version 10 for mobile clients
-	private BitMatrix $bitMatrix;
+	private BitMatrix $matrix;
 	/** @var \chillerlan\QRCode\Detector\FinderPattern[] */
 	/** @var \chillerlan\QRCode\Detector\FinderPattern[] */
 	private array $possibleCenters;
 	private array $possibleCenters;
 	private bool  $hasSkipped = false;
 	private bool  $hasSkipped = false;
@@ -38,10 +38,10 @@ final class FinderPatternFinder{
 	/**
 	/**
 	 * Creates a finder that will search the image for three finder patterns.
 	 * Creates a finder that will search the image for three finder patterns.
 	 *
 	 *
-	 * @param BitMatrix $bitMatrix image to search
+	 * @param BitMatrix $matrix image to search
 	 */
 	 */
-	public function __construct(BitMatrix $bitMatrix){
-		$this->bitMatrix       = $bitMatrix;
+	public function __construct(BitMatrix $matrix){
+		$this->matrix          = $matrix;
 		$this->possibleCenters = [];
 		$this->possibleCenters = [];
 	}
 	}
 
 
@@ -49,7 +49,7 @@ final class FinderPatternFinder{
 	 * @return \chillerlan\QRCode\Detector\FinderPattern[]
 	 * @return \chillerlan\QRCode\Detector\FinderPattern[]
 	 */
 	 */
 	public function find():array{
 	public function find():array{
-		$dimension = $this->bitMatrix->getDimension();
+		$dimension = $this->matrix->size();
 
 
 		// We are looking for black/white/black/white/black modules in
 		// We are looking for black/white/black/white/black modules in
 		// 1:1:3:1:1 ratio; this tracks the number of such modules seen so far
 		// 1:1:3:1:1 ratio; this tracks the number of such modules seen so far
@@ -72,7 +72,7 @@ final class FinderPatternFinder{
 			for($j = 0; $j < $dimension; $j++){
 			for($j = 0; $j < $dimension; $j++){
 
 
 				// Black pixel
 				// Black pixel
-				if($this->bitMatrix->get($j, $i)){
+				if($this->matrix->check($j, $i)){
 					// Counting white pixels
 					// Counting white pixels
 					if(($currentState & 1) === 1){
 					if(($currentState & 1) === 1){
 						$currentState++;
 						$currentState++;
@@ -257,7 +257,7 @@ final class FinderPatternFinder{
 		// Start counting up, left from center finding black center mass
 		// Start counting up, left from center finding black center mass
 		$i = 0;
 		$i = 0;
 
 
-		while($centerI >= $i && $centerJ >= $i && $this->bitMatrix->get($centerJ - $i, $centerI - $i)){
+		while($centerI >= $i && $centerJ >= $i && $this->matrix->check($centerJ - $i, $centerI - $i)){
 			$stateCount[2]++;
 			$stateCount[2]++;
 			$i++;
 			$i++;
 		}
 		}
@@ -267,7 +267,7 @@ final class FinderPatternFinder{
 		}
 		}
 
 
 		// Continue up, left finding white space
 		// Continue up, left finding white space
-		while($centerI >= $i && $centerJ >= $i && !$this->bitMatrix->get($centerJ - $i, $centerI - $i)){
+		while($centerI >= $i && $centerJ >= $i && !$this->matrix->check($centerJ - $i, $centerI - $i)){
 			$stateCount[1]++;
 			$stateCount[1]++;
 			$i++;
 			$i++;
 		}
 		}
@@ -277,7 +277,7 @@ final class FinderPatternFinder{
 		}
 		}
 
 
 		// Continue up, left finding black border
 		// Continue up, left finding black border
-		while($centerI >= $i && $centerJ >= $i && $this->bitMatrix->get($centerJ - $i, $centerI - $i)){
+		while($centerI >= $i && $centerJ >= $i && $this->matrix->check($centerJ - $i, $centerI - $i)){
 			$stateCount[0]++;
 			$stateCount[0]++;
 			$i++;
 			$i++;
 		}
 		}
@@ -286,16 +286,16 @@ final class FinderPatternFinder{
 			return false;
 			return false;
 		}
 		}
 
 
-		$dimension = $this->bitMatrix->getDimension();
+		$dimension = $this->matrix->size();
 
 
 		// Now also count down, right from center
 		// Now also count down, right from center
 		$i = 1;
 		$i = 1;
-		while($centerI + $i < $dimension && $centerJ + $i < $dimension && $this->bitMatrix->get($centerJ + $i, $centerI + $i)){
+		while($centerI + $i < $dimension && $centerJ + $i < $dimension && $this->matrix->check($centerJ + $i, $centerI + $i)){
 			$stateCount[2]++;
 			$stateCount[2]++;
 			$i++;
 			$i++;
 		}
 		}
 
 
-		while($centerI + $i < $dimension && $centerJ + $i < $dimension && !$this->bitMatrix->get($centerJ + $i, $centerI + $i)){
+		while($centerI + $i < $dimension && $centerJ + $i < $dimension && !$this->matrix->check($centerJ + $i, $centerI + $i)){
 			$stateCount[3]++;
 			$stateCount[3]++;
 			$i++;
 			$i++;
 		}
 		}
@@ -304,7 +304,7 @@ final class FinderPatternFinder{
 			return false;
 			return false;
 		}
 		}
 
 
-		while($centerI + $i < $dimension && $centerJ + $i < $dimension && $this->bitMatrix->get($centerJ + $i, $centerI + $i)){
+		while($centerI + $i < $dimension && $centerJ + $i < $dimension && $this->matrix->check($centerJ + $i, $centerI + $i)){
 			$stateCount[4]++;
 			$stateCount[4]++;
 			$i++;
 			$i++;
 		}
 		}
@@ -328,14 +328,15 @@ final class FinderPatternFinder{
 	 * @param int $originalStateCountTotal
 	 * @param int $originalStateCountTotal
 	 *
 	 *
 	 * @return float|null vertical center of finder pattern, or null if not found
 	 * @return float|null vertical center of finder pattern, or null if not found
+	 * @noinspection DuplicatedCode
 	 */
 	 */
 	private function crossCheckVertical(int $startI, int $centerJ, int $maxCount, int $originalStateCountTotal):?float{
 	private function crossCheckVertical(int $startI, int $centerJ, int $maxCount, int $originalStateCountTotal):?float{
-		$maxI       = $this->bitMatrix->getDimension();
+		$maxI       = $this->matrix->size();
 		$stateCount = $this->getCrossCheckStateCount();
 		$stateCount = $this->getCrossCheckStateCount();
 
 
 		// Start counting up from center
 		// Start counting up from center
 		$i = $startI;
 		$i = $startI;
-		while($i >= 0 && $this->bitMatrix->get($centerJ, $i)){
+		while($i >= 0 && $this->matrix->check($centerJ, $i)){
 			$stateCount[2]++;
 			$stateCount[2]++;
 			$i--;
 			$i--;
 		}
 		}
@@ -344,7 +345,7 @@ final class FinderPatternFinder{
 			return null;
 			return null;
 		}
 		}
 
 
-		while($i >= 0 && !$this->bitMatrix->get($centerJ, $i) && $stateCount[1] <= $maxCount){
+		while($i >= 0 && !$this->matrix->check($centerJ, $i) && $stateCount[1] <= $maxCount){
 			$stateCount[1]++;
 			$stateCount[1]++;
 			$i--;
 			$i--;
 		}
 		}
@@ -354,7 +355,7 @@ final class FinderPatternFinder{
 			return null;
 			return null;
 		}
 		}
 
 
-		while($i >= 0 && $this->bitMatrix->get($centerJ, $i) && $stateCount[0] <= $maxCount){
+		while($i >= 0 && $this->matrix->check($centerJ, $i) && $stateCount[0] <= $maxCount){
 			$stateCount[0]++;
 			$stateCount[0]++;
 			$i--;
 			$i--;
 		}
 		}
@@ -365,7 +366,7 @@ final class FinderPatternFinder{
 
 
 		// Now also count down from center
 		// Now also count down from center
 		$i = $startI + 1;
 		$i = $startI + 1;
-		while($i < $maxI && $this->bitMatrix->get($centerJ, $i)){
+		while($i < $maxI && $this->matrix->check($centerJ, $i)){
 			$stateCount[2]++;
 			$stateCount[2]++;
 			$i++;
 			$i++;
 		}
 		}
@@ -374,7 +375,7 @@ final class FinderPatternFinder{
 			return null;
 			return null;
 		}
 		}
 
 
-		while($i < $maxI && !$this->bitMatrix->get($centerJ, $i) && $stateCount[3] < $maxCount){
+		while($i < $maxI && !$this->matrix->check($centerJ, $i) && $stateCount[3] < $maxCount){
 			$stateCount[3]++;
 			$stateCount[3]++;
 			$i++;
 			$i++;
 		}
 		}
@@ -383,7 +384,7 @@ final class FinderPatternFinder{
 			return null;
 			return null;
 		}
 		}
 
 
-		while($i < $maxI && $this->bitMatrix->get($centerJ, $i) && $stateCount[4] < $maxCount){
+		while($i < $maxI && $this->matrix->check($centerJ, $i) && $stateCount[4] < $maxCount){
 			$stateCount[4]++;
 			$stateCount[4]++;
 			$i++;
 			$i++;
 		}
 		}
@@ -411,13 +412,14 @@ final class FinderPatternFinder{
 	 * Like #crossCheckVertical(int, int, int, int), and in fact is basically identical,
 	 * Like #crossCheckVertical(int, int, int, int), and in fact is basically identical,
 	 * except it reads horizontally instead of vertically. This is used to cross-cross
 	 * except it reads horizontally instead of vertically. This is used to cross-cross
 	 * check a vertical cross check and locate the real center of the alignment pattern.
 	 * check a vertical cross check and locate the real center of the alignment pattern.
+	 * @noinspection DuplicatedCode
 	 */
 	 */
 	private function crossCheckHorizontal(int $startJ, int $centerI, int $maxCount, int $originalStateCountTotal):?float{
 	private function crossCheckHorizontal(int $startJ, int $centerI, int $maxCount, int $originalStateCountTotal):?float{
-		$maxJ       = $this->bitMatrix->getDimension();
+		$maxJ       = $this->matrix->size();
 		$stateCount = $this->getCrossCheckStateCount();
 		$stateCount = $this->getCrossCheckStateCount();
 
 
 		$j = $startJ;
 		$j = $startJ;
-		while($j >= 0 && $this->bitMatrix->get($j, $centerI)){
+		while($j >= 0 && $this->matrix->check($j, $centerI)){
 			$stateCount[2]++;
 			$stateCount[2]++;
 			$j--;
 			$j--;
 		}
 		}
@@ -426,7 +428,7 @@ final class FinderPatternFinder{
 			return null;
 			return null;
 		}
 		}
 
 
-		while($j >= 0 && !$this->bitMatrix->get($j, $centerI) && $stateCount[1] <= $maxCount){
+		while($j >= 0 && !$this->matrix->check($j, $centerI) && $stateCount[1] <= $maxCount){
 			$stateCount[1]++;
 			$stateCount[1]++;
 			$j--;
 			$j--;
 		}
 		}
@@ -435,7 +437,7 @@ final class FinderPatternFinder{
 			return null;
 			return null;
 		}
 		}
 
 
-		while($j >= 0 && $this->bitMatrix->get($j, $centerI) && $stateCount[0] <= $maxCount){
+		while($j >= 0 && $this->matrix->check($j, $centerI) && $stateCount[0] <= $maxCount){
 			$stateCount[0]++;
 			$stateCount[0]++;
 			$j--;
 			$j--;
 		}
 		}
@@ -445,7 +447,7 @@ final class FinderPatternFinder{
 		}
 		}
 
 
 		$j = $startJ + 1;
 		$j = $startJ + 1;
-		while($j < $maxJ && $this->bitMatrix->get($j, $centerI)){
+		while($j < $maxJ && $this->matrix->check($j, $centerI)){
 			$stateCount[2]++;
 			$stateCount[2]++;
 			$j++;
 			$j++;
 		}
 		}
@@ -454,7 +456,7 @@ final class FinderPatternFinder{
 			return null;
 			return null;
 		}
 		}
 
 
-		while($j < $maxJ && !$this->bitMatrix->get($j, $centerI) && $stateCount[3] < $maxCount){
+		while($j < $maxJ && !$this->matrix->check($j, $centerI) && $stateCount[3] < $maxCount){
 			$stateCount[3]++;
 			$stateCount[3]++;
 			$j++;
 			$j++;
 		}
 		}
@@ -463,7 +465,7 @@ final class FinderPatternFinder{
 			return null;
 			return null;
 		}
 		}
 
 
-		while($j < $maxJ && $this->bitMatrix->get($j, $centerI) && $stateCount[4] < $maxCount){
+		while($j < $maxJ && $this->matrix->check($j, $centerI) && $stateCount[4] < $maxCount){
 			$stateCount[4]++;
 			$stateCount[4]++;
 			$j++;
 			$j++;
 		}
 		}

+ 9 - 10
src/Detector/GridSampler.php

@@ -11,6 +11,7 @@
 
 
 namespace chillerlan\QRCode\Detector;
 namespace chillerlan\QRCode\Detector;
 
 
+use chillerlan\QRCode\Data\QRMatrix;
 use chillerlan\QRCode\Decoder\BitMatrix;
 use chillerlan\QRCode\Decoder\BitMatrix;
 use Throwable;
 use Throwable;
 use function array_fill, count, sprintf;
 use function array_fill, count, sprintf;
@@ -41,13 +42,13 @@ final class GridSampler{
 	 * For efficiency, the method will check points from either end of the line until one is found
 	 * For efficiency, the method will check points from either end of the line until one is found
 	 * to be within the image. Because the set of points are assumed to be linear, this is valid.
 	 * to be within the image. Because the set of points are assumed to be linear, this is valid.
 	 *
 	 *
-	 * @param \chillerlan\QRCode\Decoder\BitMatrix $bitMatrix image into which the points should map
-	 * @param float[]                  $points    actual points in x1,y1,...,xn,yn form
+	 * @param \chillerlan\QRCode\Decoder\BitMatrix $matrix image into which the points should map
+	 * @param float[]                              $points actual points in x1,y1,...,xn,yn form
 	 *
 	 *
 	 * @throws \chillerlan\QRCode\Detector\QRCodeDetectorException if an endpoint is lies outside the image boundaries
 	 * @throws \chillerlan\QRCode\Detector\QRCodeDetectorException if an endpoint is lies outside the image boundaries
 	 */
 	 */
-	private function checkAndNudgePoints(BitMatrix $bitMatrix, array $points):void{
-		$dimension = $bitMatrix->getDimension();
+	private function checkAndNudgePoints(BitMatrix $matrix, array $points):void{
+		$dimension = $matrix->size();
 		$nudged    = true;
 		$nudged    = true;
 		$max       = count($points);
 		$max       = count($points);
 
 
@@ -121,7 +122,7 @@ final class GridSampler{
 	 * @throws \chillerlan\QRCode\Detector\QRCodeDetectorException if image can't be sampled, for example, if the transformation defined
 	 * @throws \chillerlan\QRCode\Detector\QRCodeDetectorException if image can't be sampled, for example, if the transformation defined
 	 *   by the given points is invalid or results in sampling outside the image boundaries
 	 *   by the given points is invalid or results in sampling outside the image boundaries
 	 */
 	 */
-	public function sampleGrid(BitMatrix $image, int $dimension, PerspectiveTransform $transform):BitMatrix{
+	public function sampleGrid(BitMatrix $matrix, int $dimension, PerspectiveTransform $transform):BitMatrix{
 
 
 		if($dimension <= 0){
 		if($dimension <= 0){
 			throw new QRCodeDetectorException('invalid matrix size');
 			throw new QRCodeDetectorException('invalid matrix size');
@@ -142,14 +143,12 @@ final class GridSampler{
 			$transform->transformPoints($points);
 			$transform->transformPoints($points);
 			// Quick check to see if points transformed to something inside the image;
 			// Quick check to see if points transformed to something inside the image;
 			// sufficient to check the endpoints
 			// sufficient to check the endpoints
-			$this->checkAndNudgePoints($image, $points);
+			$this->checkAndNudgePoints($matrix, $points);
 
 
 			try{
 			try{
 				for($x = 0; $x < $max; $x += 2){
 				for($x = 0; $x < $max; $x += 2){
-					if($image->get((int)$points[$x], (int)$points[$x + 1])){
-						// Black(-ish) pixel
-						$bits->set($x / 2, $y);
-					}
+					// Black(-ish) pixel
+					$bits->set($x / 2, $y, $matrix->check((int)$points[$x], (int)$points[$x + 1]), QRMatrix::M_DATA);
 				}
 				}
 			}
 			}
 			// @codeCoverageIgnoreStart
 			// @codeCoverageIgnoreStart

+ 3 - 4
tests/Data/QRMatrixTest.php

@@ -35,7 +35,7 @@ final class QRMatrixTest extends TestCase{
 	 * shortcut
 	 * shortcut
 	 */
 	 */
 	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), new MaskPattern(MaskPattern::PATTERN_000));
 	}
 	}
 
 
 	/**
 	/**
@@ -70,8 +70,7 @@ final class QRMatrixTest extends TestCase{
 	 * Tests if maskPattern() returns the current (or default) mask pattern
 	 * Tests if maskPattern() returns the current (or default) mask pattern
 	 */
 	 */
 	public function testMaskPattern():void{
 	public function testMaskPattern():void{
-		$this::assertSame(null, $this->matrix->maskPattern());
-
+		// set via matrix evaluation
 		$matrix = (new QRCode)->addByteSegment('testdata')->getMatrix();
 		$matrix = (new QRCode)->addByteSegment('testdata')->getMatrix();
 
 
 		$this::assertInstanceOf(MaskPattern::class, $matrix->maskPattern());
 		$this::assertInstanceOf(MaskPattern::class, $matrix->maskPattern());
@@ -225,7 +224,7 @@ 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));
+		$matrix = $this->getMatrix($version)->setFormatInfo();
 
 
 		$this::assertTrue($matrix->checkType(8, 0, QRMatrix::M_FORMAT));
 		$this::assertTrue($matrix->checkType(8, 0, QRMatrix::M_FORMAT));
 		$this::assertTrue($matrix->checkType(0, 8, QRMatrix::M_FORMAT));
 		$this::assertTrue($matrix->checkType(0, 8, QRMatrix::M_FORMAT));