mirror of
https://github.com/chillerlan/php-qrcode.git
synced 2026-09-03 05:57:19 +00:00
🛀 merge QRMatrix and BitMatrix
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
|
||||
namespace chillerlan\QRCode\Common;
|
||||
|
||||
use chillerlan\QRCode\Data\QRMatrix;
|
||||
use function array_fill, array_merge, count, max;
|
||||
|
||||
/**
|
||||
@@ -25,10 +24,11 @@ final class ReedSolomonEncoder{
|
||||
|
||||
/**
|
||||
* 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]);
|
||||
|
||||
@@ -66,7 +66,7 @@ final class ReedSolomonEncoder{
|
||||
$this->interleave($dataBytes, $maxDataBytes, $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
@@ -10,7 +10,7 @@
|
||||
|
||||
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\Settings\SettingsContainerInterface;
|
||||
|
||||
@@ -94,14 +94,10 @@ final class QRData{
|
||||
* returns a fresh matrix object with the data written and masked with the given $maskPattern
|
||||
*/
|
||||
public function writeMatrix(MaskPattern $maskPattern):QRMatrix{
|
||||
$matrix = (new QRMatrix($this->version, $this->eccLevel))
|
||||
return (new QRMatrix($this->version, $this->eccLevel, $maskPattern))
|
||||
->initFunctionalPatterns()
|
||||
->initFormatInfo($maskPattern)
|
||||
;
|
||||
|
||||
return (new ReedSolomonEncoder)
|
||||
->interleaveEcBytes($this->bitBuffer, $matrix)
|
||||
->mask($maskPattern)
|
||||
->writeCodewords($this->bitBuffer)
|
||||
->mask()
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
+74
-39
@@ -10,10 +10,8 @@
|
||||
|
||||
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;
|
||||
@@ -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
|
||||
*/
|
||||
final class QRMatrix{
|
||||
class QRMatrix{
|
||||
|
||||
/** @var int */
|
||||
public const M_NULL = 0b000000000000;
|
||||
@@ -55,36 +53,37 @@ final class QRMatrix{
|
||||
/**
|
||||
* the used mask pattern, set via QRMatrix::mask()
|
||||
*/
|
||||
private ?MaskPattern $maskPattern = null;
|
||||
protected ?MaskPattern $maskPattern = null;
|
||||
|
||||
/**
|
||||
* the current ECC level
|
||||
*/
|
||||
protected ?EccLevel $eccLevel = null;
|
||||
|
||||
/**
|
||||
* a Version instance
|
||||
*/
|
||||
protected ?Version $version = null;
|
||||
|
||||
/**
|
||||
* the size (side length) of the matrix, including quiet zone (if created)
|
||||
*/
|
||||
private int $moduleCount;
|
||||
protected int $moduleCount;
|
||||
|
||||
/**
|
||||
* the actual matrix data array
|
||||
*
|
||||
* @var int[][]
|
||||
*/
|
||||
private array $matrix;
|
||||
|
||||
/**
|
||||
* the current ECC level
|
||||
*/
|
||||
private EccLevel $eccLevel;
|
||||
|
||||
/**
|
||||
* a Version instance
|
||||
*/
|
||||
private Version $version;
|
||||
protected array $matrix;
|
||||
|
||||
/**
|
||||
* QRMatrix constructor.
|
||||
*/
|
||||
public function __construct(Version $version, EccLevel $eccLevel){
|
||||
public function __construct(Version $version, EccLevel $eccLevel, MaskPattern $maskPattern){
|
||||
$this->version = $version;
|
||||
$this->eccLevel = $eccLevel;
|
||||
$this->maskPattern = $maskPattern;
|
||||
$this->moduleCount = $this->version->getDimension();
|
||||
$this->matrix = array_fill(0, $this->moduleCount, array_fill(0, $this->moduleCount, $this::M_NULL));
|
||||
}
|
||||
@@ -99,16 +98,8 @@ final class QRMatrix{
|
||||
->setAlignmentPattern()
|
||||
->setTimingPattern()
|
||||
->setDarkModule()
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* shortcut to set format and version info
|
||||
*/
|
||||
public function initFormatInfo(MaskPattern $maskPattern):self{
|
||||
return $this
|
||||
->setVersionNumber()
|
||||
->setFormatInfo($maskPattern)
|
||||
->setFormatInfo()
|
||||
;
|
||||
}
|
||||
|
||||
@@ -139,14 +130,14 @@ final class QRMatrix{
|
||||
/**
|
||||
* Returns the current version number
|
||||
*/
|
||||
public function version():Version{
|
||||
public function version():?Version{
|
||||
return $this->version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current ECC level
|
||||
*/
|
||||
public function eccLevel():EccLevel{
|
||||
public function eccLevel():?EccLevel{
|
||||
return $this->eccLevel;
|
||||
}
|
||||
|
||||
@@ -385,8 +376,8 @@ final class QRMatrix{
|
||||
*
|
||||
* 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++){
|
||||
$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
|
||||
*/
|
||||
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){
|
||||
$val ^= $this::IS_DARK;
|
||||
$this->flip($x, $y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
namespace chillerlan\QRCode\Decoder;
|
||||
|
||||
use chillerlan\QRCode\Data\QRMatrix;
|
||||
use function array_fill, count, max;
|
||||
|
||||
/**
|
||||
@@ -160,7 +161,6 @@ final class Binarizer{
|
||||
*
|
||||
*/
|
||||
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
|
||||
// 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
|
||||
// "fail quickly" which is necessary for continuous scanning.
|
||||
$localLuminances = $this->source->getMatrix();
|
||||
$matrix = new BitMatrix(max($width, $height));
|
||||
|
||||
for($y = 0; $y < $height; $y++){
|
||||
$offset = $y * $width;
|
||||
|
||||
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($i = 0; $i < self::BLOCK_SIZE; $i++){
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+185
-294
@@ -11,214 +11,66 @@
|
||||
|
||||
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 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]
|
||||
*/
|
||||
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;
|
||||
|
||||
/**
|
||||
* @noinspection PhpMissingParentConstructorInspection
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException
|
||||
*/
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
*/
|
||||
public function mirror():self{
|
||||
|
||||
for($x = 0; $x < $this->dimension; $x++){
|
||||
for($y = $x + 1; $y < $this->dimension; $y++){
|
||||
if($this->get($x, $y) !== $this->get($y, $x)){
|
||||
$this->flip($y, $x);
|
||||
$this->flip($x, $y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->moduleCount = $dimension;
|
||||
$this->matrix = array_fill(0, $this->moduleCount, array_fill(0, $this->moduleCount, $this::M_NULL));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -230,23 +82,29 @@ final class BitMatrix{
|
||||
* @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;
|
||||
$this->version = null;
|
||||
$this->eccLevel = null;
|
||||
$this->maskPattern = null;
|
||||
$this->mirror = $mirror;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Mirror the bit matrix in order to attempt a second reading.
|
||||
*/
|
||||
private function copyBit(int $i, int $j, int $versionBits):int{
|
||||
public function mirror():self{
|
||||
|
||||
$bit = $this->mirror
|
||||
? $this->get($j, $i)
|
||||
: $this->get($i, $j);
|
||||
for($x = 0; $x < $this->moduleCount; $x++){
|
||||
for($y = $x + 1; $y < $this->moduleCount; $y++){
|
||||
if($this->get($x, $y) !== $this->get($y, $x)){
|
||||
$this->flip($y, $x);
|
||||
$this->flip($x, $y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $bit ? ($versionBits << 1) | 0x1 : $versionBits << 1;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -258,56 +116,60 @@ final class BitMatrix{
|
||||
* @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException if the exact number of bytes expected is not read
|
||||
*/
|
||||
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();
|
||||
$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;
|
||||
|
||||
$readingUp = true;
|
||||
$result = [];
|
||||
$resultOffset = 0;
|
||||
$currentByte = 0;
|
||||
$bitsRead = 0;
|
||||
// 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
|
||||
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++){
|
||||
// Ignore bits covered by the function pattern
|
||||
if(!$functionPattern->get($j - $col, $i)){
|
||||
// Read a bit
|
||||
$bitsRead++;
|
||||
$currentByte <<= 1;
|
||||
$x = $i - $col;
|
||||
|
||||
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;
|
||||
}
|
||||
// Ignore bits covered by the function pattern
|
||||
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;
|
||||
@@ -316,62 +178,77 @@ final class BitMatrix{
|
||||
/**
|
||||
* 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
|
||||
* 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
|
||||
$formatInfoBits1 = 0;
|
||||
|
||||
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 ...
|
||||
$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 ...
|
||||
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
|
||||
$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($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
|
||||
);
|
||||
|
||||
// still nothing???
|
||||
if($formatInfo === null){
|
||||
throw new QRCodeDecoderException('failed to read format info'); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
if($this->formatInfo !== null){
|
||||
return $this->formatInfo;
|
||||
}
|
||||
|
||||
// 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
|
||||
);
|
||||
$this->eccLevel = new EccLevel(($formatInfo >> 3) & 0x03); // Bits 3,4
|
||||
$this->maskPattern = new MaskPattern($formatInfo & 0x07); // Bottom 3 bits
|
||||
|
||||
if($this->formatInfo !== null){
|
||||
return $this->formatInfo;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
throw new QRCodeDecoderException('failed to read format info');
|
||||
/**
|
||||
*
|
||||
*/
|
||||
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
|
||||
* 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
|
||||
$bestDifference = PHP_INT_MAX;
|
||||
$bestFormatInfo = 0;
|
||||
|
||||
foreach(FormatInformation::DECODE_LOOKUP as $decodeInfo){
|
||||
[$maskedBits, $dataBits] = $decodeInfo;
|
||||
foreach($this::DECODE_LOOKUP as $maskedBits => $dataBits){
|
||||
|
||||
if($maskedFormatInfo1 === $dataBits || $maskedFormatInfo2 === $dataBits){
|
||||
// Found an exact match
|
||||
return new FormatInformation($maskedBits);
|
||||
return $maskedBits;
|
||||
}
|
||||
|
||||
$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
|
||||
if($bestDifference <= 3){
|
||||
return new FormatInformation($bestFormatInfo);
|
||||
return $bestFormatInfo;
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -423,61 +298,61 @@ final class BitMatrix{
|
||||
/**
|
||||
* 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
|
||||
* the valid encoding of version information
|
||||
* @noinspection DuplicatedCode
|
||||
*/
|
||||
private function readVersion():Version{
|
||||
private function readVersion():self{
|
||||
|
||||
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
|
||||
$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);
|
||||
|
||||
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
|
||||
$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);
|
||||
|
||||
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');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $versionBits
|
||||
*
|
||||
* @return \chillerlan\QRCode\Common\Version|null
|
||||
*/
|
||||
private function decodeVersionInformation(int $versionBits):?Version{
|
||||
$bestDifference = PHP_INT_MAX;
|
||||
@@ -542,4 +417,20 @@ final class BitMatrix{
|
||||
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
@@ -12,7 +12,7 @@
|
||||
namespace chillerlan\QRCode\Decoder;
|
||||
|
||||
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\Detector\Detector;
|
||||
use function count, array_fill, mb_convert_encoding, mb_detect_encoding;
|
||||
@@ -28,8 +28,8 @@ final class Decoder{
|
||||
# private const GB2312_SUBSET = 1;
|
||||
|
||||
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.
|
||||
@@ -41,11 +41,11 @@ final class Decoder{
|
||||
* @throws \Throwable|\chillerlan\QRCode\Decoder\QRCodeDecoderException if the QR Code cannot be decoded
|
||||
*/
|
||||
public function decode(LuminanceSourceInterface $source):DecoderResult{
|
||||
$bitMatrix = (new Detector($source))->detect();
|
||||
$matrix = (new Detector($source))->detect();
|
||||
|
||||
try{
|
||||
// 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){
|
||||
|
||||
@@ -58,7 +58,7 @@ final class Decoder{
|
||||
* that the QR code may be mirrored, and we should try once more with a
|
||||
* mirrored content.
|
||||
*/
|
||||
return $this->decodeMatrix($bitMatrix->setMirror(true)->mirror());
|
||||
return $this->decodeMatrix($matrix->setMirror(true)->mirror());
|
||||
}
|
||||
catch(Throwable $f){
|
||||
// Throw the exception from the original reading
|
||||
@@ -72,18 +72,18 @@ final class Decoder{
|
||||
/**
|
||||
* @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException
|
||||
*/
|
||||
private function decodeMatrix(BitMatrix $bitMatrix):DecoderResult{
|
||||
private function decodeMatrix(BitMatrix $matrix):DecoderResult{
|
||||
// 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
|
||||
}
|
||||
|
||||
$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
|
||||
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
|
||||
// error correction level
|
||||
/** @phan-suppress-next-line PhanTypeMismatchArgumentNullable */
|
||||
[$numEccCodewords, $eccBlocks] = $this->version->getRSBlocks($this->eccLevel);
|
||||
|
||||
// Now establish DataBlocks of the appropriate size and number of data codewords
|
||||
@@ -278,7 +279,7 @@ final class Decoder{
|
||||
'data' => $result,
|
||||
'version' => $this->version,
|
||||
'eccLevel' => $this->eccLevel,
|
||||
'maskPattern' => $this->formatInfo->getMaskPattern(),
|
||||
'maskPattern' => $this->maskPattern,
|
||||
'structuredAppendParity' => $parityData,
|
||||
'structuredAppendSequence' => $symbolSequence
|
||||
]);
|
||||
|
||||
@@ -30,7 +30,7 @@ use function abs, count;
|
||||
*/
|
||||
final class AlignmentPatternFinder{
|
||||
|
||||
private BitMatrix $bitMatrix;
|
||||
private BitMatrix $matrix;
|
||||
private float $moduleSize;
|
||||
/** @var \chillerlan\QRCode\Detector\AlignmentPattern[] */
|
||||
private array $possibleCenters;
|
||||
@@ -38,12 +38,12 @@ final class AlignmentPatternFinder{
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
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 = [];
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ final class AlignmentPatternFinder{
|
||||
// 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
|
||||
// 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++;
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ final class AlignmentPatternFinder{
|
||||
|
||||
while($j < $maxJ){
|
||||
|
||||
if($this->bitMatrix->get($j, $i)){
|
||||
if($this->matrix->check($j, $i)){
|
||||
// Black pixel
|
||||
if($currentState === 1){ // Counting black pixels
|
||||
$stateCount[$currentState]++;
|
||||
@@ -224,7 +224,7 @@ final class AlignmentPatternFinder{
|
||||
* @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{
|
||||
$maxI = $this->bitMatrix->getDimension();
|
||||
$maxI = $this->matrix->size();
|
||||
$stateCount = [];
|
||||
$stateCount[0] = 0;
|
||||
$stateCount[1] = 0;
|
||||
@@ -232,7 +232,7 @@ final class AlignmentPatternFinder{
|
||||
|
||||
// Start counting up from center
|
||||
$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]++;
|
||||
$i--;
|
||||
}
|
||||
@@ -241,7 +241,7 @@ final class AlignmentPatternFinder{
|
||||
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]++;
|
||||
$i--;
|
||||
}
|
||||
@@ -252,7 +252,7 @@ final class AlignmentPatternFinder{
|
||||
|
||||
// Now also count down from center
|
||||
$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]++;
|
||||
$i++;
|
||||
}
|
||||
@@ -261,7 +261,7 @@ final class AlignmentPatternFinder{
|
||||
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]++;
|
||||
$i++;
|
||||
}
|
||||
|
||||
@@ -25,20 +25,20 @@ use const NAN;
|
||||
*/
|
||||
final class Detector{
|
||||
|
||||
private BitMatrix $bitMatrix;
|
||||
private BitMatrix $matrix;
|
||||
|
||||
/**
|
||||
* Detector constructor.
|
||||
*/
|
||||
public function __construct(LuminanceSourceInterface $source){
|
||||
$this->bitMatrix = (new Binarizer($source))->getBlackMatrix();
|
||||
$this->matrix = (new Binarizer($source))->getBlackMatrix();
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects a QR Code in an image.
|
||||
*/
|
||||
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);
|
||||
$dimension = $this->computeDimension($topLeft, $topRight, $bottomLeft, $moduleSize);
|
||||
@@ -70,7 +70,7 @@ final class Detector{
|
||||
|
||||
$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{
|
||||
$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
|
||||
$scale = 1.0;
|
||||
$otherToX = $fromX - ($toX - $fromX);
|
||||
@@ -208,7 +208,7 @@ final class Detector{
|
||||
// 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
|
||||
// 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){
|
||||
return FinderPattern::distance($x, $y, $fromX, $fromY);
|
||||
@@ -294,7 +294,7 @@ final class Detector{
|
||||
float $allowanceFactor
|
||||
):?AlignmentPattern{
|
||||
// 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);
|
||||
$alignmentAreaLeftX = max(0, $estAlignmentX - $allowance);
|
||||
$alignmentAreaRightX = min($dimension - 1, $estAlignmentX + $allowance);
|
||||
@@ -310,7 +310,7 @@ final class Detector{
|
||||
return null;
|
||||
}
|
||||
|
||||
return (new AlignmentPatternFinder($this->bitMatrix, $overallEstModuleSize))->find(
|
||||
return (new AlignmentPatternFinder($this->matrix, $overallEstModuleSize))->find(
|
||||
$alignmentAreaLeftX,
|
||||
$alignmentAreaTopY,
|
||||
$alignmentAreaRightX - $alignmentAreaLeftX,
|
||||
|
||||
@@ -30,7 +30,7 @@ final class FinderPatternFinder{
|
||||
private const MIN_SKIP = 2;
|
||||
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 BitMatrix $bitMatrix;
|
||||
private BitMatrix $matrix;
|
||||
/** @var \chillerlan\QRCode\Detector\FinderPattern[] */
|
||||
private array $possibleCenters;
|
||||
private bool $hasSkipped = false;
|
||||
@@ -38,10 +38,10 @@ final class FinderPatternFinder{
|
||||
/**
|
||||
* 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 = [];
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ final class FinderPatternFinder{
|
||||
* @return \chillerlan\QRCode\Detector\FinderPattern[]
|
||||
*/
|
||||
public function find():array{
|
||||
$dimension = $this->bitMatrix->getDimension();
|
||||
$dimension = $this->matrix->size();
|
||||
|
||||
// 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
|
||||
@@ -72,7 +72,7 @@ final class FinderPatternFinder{
|
||||
for($j = 0; $j < $dimension; $j++){
|
||||
|
||||
// Black pixel
|
||||
if($this->bitMatrix->get($j, $i)){
|
||||
if($this->matrix->check($j, $i)){
|
||||
// Counting white pixels
|
||||
if(($currentState & 1) === 1){
|
||||
$currentState++;
|
||||
@@ -257,7 +257,7 @@ final class FinderPatternFinder{
|
||||
// Start counting up, left from center finding black center mass
|
||||
$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]++;
|
||||
$i++;
|
||||
}
|
||||
@@ -267,7 +267,7 @@ final class FinderPatternFinder{
|
||||
}
|
||||
|
||||
// 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]++;
|
||||
$i++;
|
||||
}
|
||||
@@ -277,7 +277,7 @@ final class FinderPatternFinder{
|
||||
}
|
||||
|
||||
// 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]++;
|
||||
$i++;
|
||||
}
|
||||
@@ -286,16 +286,16 @@ final class FinderPatternFinder{
|
||||
return false;
|
||||
}
|
||||
|
||||
$dimension = $this->bitMatrix->getDimension();
|
||||
$dimension = $this->matrix->size();
|
||||
|
||||
// Now also count down, right from center
|
||||
$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]++;
|
||||
$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]++;
|
||||
$i++;
|
||||
}
|
||||
@@ -304,7 +304,7 @@ final class FinderPatternFinder{
|
||||
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]++;
|
||||
$i++;
|
||||
}
|
||||
@@ -328,14 +328,15 @@ final class FinderPatternFinder{
|
||||
* @param int $originalStateCountTotal
|
||||
*
|
||||
* @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{
|
||||
$maxI = $this->bitMatrix->getDimension();
|
||||
$maxI = $this->matrix->size();
|
||||
$stateCount = $this->getCrossCheckStateCount();
|
||||
|
||||
// Start counting up from center
|
||||
$i = $startI;
|
||||
while($i >= 0 && $this->bitMatrix->get($centerJ, $i)){
|
||||
while($i >= 0 && $this->matrix->check($centerJ, $i)){
|
||||
$stateCount[2]++;
|
||||
$i--;
|
||||
}
|
||||
@@ -344,7 +345,7 @@ final class FinderPatternFinder{
|
||||
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]++;
|
||||
$i--;
|
||||
}
|
||||
@@ -354,7 +355,7 @@ final class FinderPatternFinder{
|
||||
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]++;
|
||||
$i--;
|
||||
}
|
||||
@@ -365,7 +366,7 @@ final class FinderPatternFinder{
|
||||
|
||||
// Now also count down from center
|
||||
$i = $startI + 1;
|
||||
while($i < $maxI && $this->bitMatrix->get($centerJ, $i)){
|
||||
while($i < $maxI && $this->matrix->check($centerJ, $i)){
|
||||
$stateCount[2]++;
|
||||
$i++;
|
||||
}
|
||||
@@ -374,7 +375,7 @@ final class FinderPatternFinder{
|
||||
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]++;
|
||||
$i++;
|
||||
}
|
||||
@@ -383,7 +384,7 @@ final class FinderPatternFinder{
|
||||
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]++;
|
||||
$i++;
|
||||
}
|
||||
@@ -411,13 +412,14 @@ final class FinderPatternFinder{
|
||||
* 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
|
||||
* 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{
|
||||
$maxJ = $this->bitMatrix->getDimension();
|
||||
$maxJ = $this->matrix->size();
|
||||
$stateCount = $this->getCrossCheckStateCount();
|
||||
|
||||
$j = $startJ;
|
||||
while($j >= 0 && $this->bitMatrix->get($j, $centerI)){
|
||||
while($j >= 0 && $this->matrix->check($j, $centerI)){
|
||||
$stateCount[2]++;
|
||||
$j--;
|
||||
}
|
||||
@@ -426,7 +428,7 @@ final class FinderPatternFinder{
|
||||
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]++;
|
||||
$j--;
|
||||
}
|
||||
@@ -435,7 +437,7 @@ final class FinderPatternFinder{
|
||||
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]++;
|
||||
$j--;
|
||||
}
|
||||
@@ -445,7 +447,7 @@ final class FinderPatternFinder{
|
||||
}
|
||||
|
||||
$j = $startJ + 1;
|
||||
while($j < $maxJ && $this->bitMatrix->get($j, $centerI)){
|
||||
while($j < $maxJ && $this->matrix->check($j, $centerI)){
|
||||
$stateCount[2]++;
|
||||
$j++;
|
||||
}
|
||||
@@ -454,7 +456,7 @@ final class FinderPatternFinder{
|
||||
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]++;
|
||||
$j++;
|
||||
}
|
||||
@@ -463,7 +465,7 @@ final class FinderPatternFinder{
|
||||
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]++;
|
||||
$j++;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
namespace chillerlan\QRCode\Detector;
|
||||
|
||||
use chillerlan\QRCode\Data\QRMatrix;
|
||||
use chillerlan\QRCode\Decoder\BitMatrix;
|
||||
use Throwable;
|
||||
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
|
||||
* 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
|
||||
*/
|
||||
private function checkAndNudgePoints(BitMatrix $bitMatrix, array $points):void{
|
||||
$dimension = $bitMatrix->getDimension();
|
||||
private function checkAndNudgePoints(BitMatrix $matrix, array $points):void{
|
||||
$dimension = $matrix->size();
|
||||
$nudged = true;
|
||||
$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
|
||||
* 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){
|
||||
throw new QRCodeDetectorException('invalid matrix size');
|
||||
@@ -142,14 +143,12 @@ final class GridSampler{
|
||||
$transform->transformPoints($points);
|
||||
// Quick check to see if points transformed to something inside the image;
|
||||
// sufficient to check the endpoints
|
||||
$this->checkAndNudgePoints($image, $points);
|
||||
$this->checkAndNudgePoints($matrix, $points);
|
||||
|
||||
try{
|
||||
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
|
||||
|
||||
@@ -35,7 +35,7 @@ final class QRMatrixTest extends TestCase{
|
||||
* shortcut
|
||||
*/
|
||||
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
|
||||
*/
|
||||
public function testMaskPattern():void{
|
||||
$this::assertSame(null, $this->matrix->maskPattern());
|
||||
|
||||
// set via matrix evaluation
|
||||
$matrix = (new QRCode)->addByteSegment('testdata')->getMatrix();
|
||||
|
||||
$this::assertInstanceOf(MaskPattern::class, $matrix->maskPattern());
|
||||
@@ -225,7 +224,7 @@ final class QRMatrixTest extends TestCase{
|
||||
* @dataProvider versionProvider
|
||||
*/
|
||||
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(0, 8, QRMatrix::M_FORMAT));
|
||||
|
||||
Reference in New Issue
Block a user