Upgrade 1-11.38

This commit is contained in:
xesmyd
2026-03-30 14:10:30 +02:00
parent f2a7e6d1fc
commit ac648ef29d
24665 changed files with 69682 additions and 2205004 deletions
@@ -17,206 +17,205 @@
namespace Zxing\Qrcode;
use Zxing\BarcodeFormat;
use Zxing\BinaryBitmap;
use Zxing\ChecksumException;
use Zxing\DecodeHintType;
use Zxing\Common\BitMatrix;
use Zxing\FormatException;
use Zxing\NotFoundException;
use Zxing\Qrcode\Decoder\Decoder;
use Zxing\Qrcode\Detector\Detector;
use Zxing\Reader;
use Zxing\Result;
use Zxing\ResultMetadataType;
use Zxing\ResultPoint;
use Zxing\Common\BitMatrix;
use Zxing\Common\DecoderResult;
use Zxing\Common\DetectorResult;
use Zxing\Qrcode\Decoder\Decoder;
use Zxing\Qrcode\Decoder\QRCodeDecoderMetaData;
use Zxing\Qrcode\Detector\Detector;
/**
* This implementation can detect and decode QR Codes in an image.
*
* @author Sean Owen
*/
class QRCodeReader implements Reader {
class QRCodeReader implements Reader
{
private static array $NO_POINTS = [];
private readonly \Zxing\Qrcode\Decoder\Decoder $decoder;
public function __construct()
{
$this->decoder = new Decoder();
}
private static $NO_POINTS = array();
private $decoder;
/**
* @param null $hints
*
* @return Result
* @throws \Zxing\FormatException
* @throws \Zxing\NotFoundException
*/
public function decode(BinaryBitmap $image, $hints = null)
{
$decoderResult = null;
if ($hints !== null && $hints['PURE_BARCODE']) {
$bits = self::extractPureBits($image->getBlackMatrix());
$decoderResult = $this->decoder->decode($bits, $hints);
$points = self::$NO_POINTS;
} else {
$detector = new Detector($image->getBlackMatrix());
$detectorResult = $detector->detect($hints);
function __construct(){
$this->decoder = new Decoder();
$decoderResult = $this->decoder->decode($detectorResult->getBits(), $hints);
$points = $detectorResult->getPoints();
}
$result = new Result($decoderResult->getText(), $decoderResult->getRawBytes(), $points, 'QR_CODE');//BarcodeFormat.QR_CODE
}
$byteSegments = $decoderResult->getByteSegments();
if ($byteSegments !== null) {
$result->putMetadata('BYTE_SEGMENTS', $byteSegments);//ResultMetadataType.BYTE_SEGMENTS
}
$ecLevel = $decoderResult->getECLevel();
if ($ecLevel !== null) {
$result->putMetadata('ERROR_CORRECTION_LEVEL', $ecLevel);//ResultMetadataType.ERROR_CORRECTION_LEVEL
}
if ($decoderResult->hasStructuredAppend()) {
$result->putMetadata(
'STRUCTURED_APPEND_SEQUENCE',//ResultMetadataType.STRUCTURED_APPEND_SEQUENCE
$decoderResult->getStructuredAppendSequenceNumber()
);
$result->putMetadata(
'STRUCTURED_APPEND_PARITY',//ResultMetadataType.STRUCTURED_APPEND_PARITY
$decoderResult->getStructuredAppendParity()
);
}
protected final function getDecoder() {
return $this->decoder;
}
return $result;
}
/**
* Locates and decodes a QR code in an image.
*
* @return a String representing the content encoded by the QR code
* @throws NotFoundException if a QR code cannot be found
* @throws FormatException if a QR code cannot be decoded
* @throws ChecksumException if error correction fails
*/
//@Override
/**
* Locates and decodes a QR code in an image.
*
* @return a String representing the content encoded by the QR code
* @throws NotFoundException if a QR code cannot be found
* @throws FormatException if a QR code cannot be decoded
* @throws ChecksumException if error correction fails
*/
/**
* This method detects a code in a "pure" image -- that is, pure monochrome image
* which contains only an unrotated, unskewed, image of a code, with some white border
* around it. This is a specialized method that works exceptionally fast in this special
* case.
*
* @see com.google.zxing.datamatrix.DataMatrixReader#extractPureBits(BitMatrix)
*/
private static function extractPureBits(BitMatrix $image)
{
$leftTopBlack = $image->getTopLeftOnBit();
$rightBottomBlack = $image->getBottomRightOnBit();
if ($leftTopBlack === null || $rightBottomBlack == null) {
throw NotFoundException::getNotFoundInstance();
}
// @Override
public function decode($image, $hints=null){/* Map<DecodeHintType,?> hints*/
$decoderResult = null;
$points = array();
if ($hints != null && $hints['PURE_BARCODE']) {//hints.containsKey(DecodeHintType.PURE_BARCODE)) {
$bits = $this->extractPureBits($image->getBlackMatrix());
$decoderResult = $this->decoder->decode($bits, $hints);
$points = self::$NO_POINTS;
} else {
$detector = new Detector($image->getBlackMatrix());
$detectorResult = $detector->detect($hints);
$moduleSize = self::moduleSize($leftTopBlack, $image);
$decoderResult = $this->decoder->decode($detectorResult->getBits(), $hints);
$points = $detectorResult->getPoints();
}
$top = $leftTopBlack[1];
$bottom = $rightBottomBlack[1];
$left = $leftTopBlack[0];
$right = $rightBottomBlack[0];
// If the code was mirrored: swap the bottom-left and the top-right points.
if ($decoderResult->getOther() instanceof QRCodeDecoderMetaData) {
$decoderResult->getOther()->applyMirroredCorrection($points);
}
// Sanity check!
if ($left >= $right || $top >= $bottom) {
throw NotFoundException::getNotFoundInstance();
}
$result = new Result($decoderResult->getText(), $decoderResult->getRawBytes(), $points, 'QR_CODE');//BarcodeFormat.QR_CODE
$byteSegments = $decoderResult->getByteSegments();
if ($byteSegments != null) {
$result->putMetadata('BYTE_SEGMENTS', $byteSegments);//ResultMetadataType.BYTE_SEGMENTS
}
$ecLevel = $decoderResult->getECLevel();
if ($ecLevel != null) {
$result->putMetadata('ERROR_CORRECTION_LEVEL', $ecLevel);//ResultMetadataType.ERROR_CORRECTION_LEVEL
}
if ($decoderResult->hasStructuredAppend()) {
$result->putMetadata('STRUCTURED_APPEND_SEQUENCE',//ResultMetadataType.STRUCTURED_APPEND_SEQUENCE
$decoderResult->getStructuredAppendSequenceNumber());
$result->putMetadata('STRUCTURED_APPEND_PARITY',//ResultMetadataType.STRUCTURED_APPEND_PARITY
$decoderResult->getStructuredAppendParity());
}
return $result;
}
if ($bottom - $top != $right - $left) {
// Special case, where bottom-right module wasn't black so we found something else in the last row
// Assume it's a square, so use height as the width
$right = $left + ($bottom - $top);
}
//@Override
public function reset() {
// do nothing
}
$matrixWidth = round(($right - $left + 1) / $moduleSize);
$matrixHeight = round(($bottom - $top + 1) / $moduleSize);
if ($matrixWidth <= 0 || $matrixHeight <= 0) {
throw NotFoundException::getNotFoundInstance();
}
if ($matrixHeight != $matrixWidth) {
// Only possibly decode square regions
throw NotFoundException::getNotFoundInstance();
}
/**
* This method detects a code in a "pure" image -- that is, pure monochrome image
* which contains only an unrotated, unskewed, image of a code, with some white border
* around it. This is a specialized method that works exceptionally fast in this special
* case.
*
* @see com.google.zxing.datamatrix.DataMatrixReader#extractPureBits(BitMatrix)
*/
private static function extractPureBits($image) {
// Push in the "border" by half the module width so that we start
// sampling in the middle of the module. Just in case the image is a
// little off, this will help recover.
$nudge = (int)($moduleSize / 2.0);// $nudge = (int) ($moduleSize / 2.0f);
$top += $nudge;
$left += $nudge;
$leftTopBlack = $image->getTopLeftOnBit();
$rightBottomBlack = $image->getBottomRightOnBit();
if ($leftTopBlack == null || $rightBottomBlack == null) {
throw NotFoundException::getNotFoundInstance();
}
// But careful that this does not sample off the edge
// "right" is the farthest-right valid pixel location -- right+1 is not necessarily
// This is positive by how much the inner x loop below would be too large
$nudgedTooFarRight = $left + (int)(($matrixWidth - 1) * $moduleSize) - $right;
if ($nudgedTooFarRight > 0) {
if ($nudgedTooFarRight > $nudge) {
// Neither way fits; abort
throw NotFoundException::getNotFoundInstance();
}
$left -= $nudgedTooFarRight;
}
// See logic above
$nudgedTooFarDown = $top + (int)(($matrixHeight - 1) * $moduleSize) - $bottom;
if ($nudgedTooFarDown > 0) {
if ($nudgedTooFarDown > $nudge) {
// Neither way fits; abort
throw NotFoundException::getNotFoundInstance();
}
$top -= $nudgedTooFarDown;
}
$moduleSize = self::moduleSize($leftTopBlack, $image);
// Now just read off the bits
$bits = new BitMatrix($matrixWidth, $matrixHeight);
for ($y = 0; $y < $matrixHeight; $y++) {
$iOffset = $top + (int)($y * $moduleSize);
for ($x = 0; $x < $matrixWidth; $x++) {
if ($image->get($left + (int)($x * $moduleSize), $iOffset)) {
$bits->set($x, $y);
}
}
}
$top = $leftTopBlack[1];
$bottom = $rightBottomBlack[1];
$left = $leftTopBlack[0];
$right = $rightBottomBlack[0];
return $bits;
}
// Sanity check!
if ($left >= $right || $top >= $bottom) {
throw NotFoundException::getNotFoundInstance();
}
private static function moduleSize($leftTopBlack, BitMatrix $image)
{
$height = $image->getHeight();
$width = $image->getWidth();
$x = $leftTopBlack[0];
$y = $leftTopBlack[1];
/*$x = $leftTopBlack[0];
$y = $leftTopBlack[1];*/
$inBlack = true;
$transitions = 0;
while ($x < $width && $y < $height) {
if ($inBlack != $image->get($x, $y)) {
if (++$transitions == 5) {
break;
}
$inBlack = !$inBlack;
}
$x++;
$y++;
}
if ($x == $width || $y == $height) {
throw NotFoundException::getNotFoundInstance();
}
if ($bottom - $top != $right - $left) {
// Special case, where bottom-right module wasn't black so we found something else in the last row
// Assume it's a square, so use height as the width
$right = $left + ($bottom - $top);
}
return ($x - $leftTopBlack[0]) / 7.0; //return ($x - $leftTopBlack[0]) / 7.0f;
}
$matrixWidth = round(($right - $left + 1) / $moduleSize);
$matrixHeight = round(($bottom - $top + 1) / $moduleSize);
if ($matrixWidth <= 0 || $matrixHeight <= 0) {
throw NotFoundException::getNotFoundInstance();
}
if ($matrixHeight != $matrixWidth) {
// Only possibly decode square regions
throw NotFoundException::getNotFoundInstance();
}
// Push in the "border" by half the module width so that we start
// sampling in the middle of the module. Just in case the image is a
// little off, this will help recover.
$nudge = (int) ($moduleSize / 2.0);// $nudge = (int) ($moduleSize / 2.0f);
$top += $nudge;
$left += $nudge;
// But careful that this does not sample off the edge
// "right" is the farthest-right valid pixel location -- right+1 is not necessarily
// This is positive by how much the inner x loop below would be too large
$nudgedTooFarRight = $left + (int) (($matrixWidth - 1) * $moduleSize) - $right;
if ($nudgedTooFarRight > 0) {
if ($nudgedTooFarRight > $nudge) {
// Neither way fits; abort
throw NotFoundException::getNotFoundInstance();
}
$left -= $nudgedTooFarRight;
}
// See logic above
$nudgedTooFarDown = $top + (int) (($matrixHeight - 1) * $moduleSize) - $bottom;
if ($nudgedTooFarDown > 0) {
if ($nudgedTooFarDown > $nudge) {
// Neither way fits; abort
throw NotFoundException::getNotFoundInstance();
}
$top -= $nudgedTooFarDown;
}
// Now just read off the bits
$bits = new BitMatrix($matrixWidth, $matrixHeight);
for ($y = 0; $y < $matrixHeight; $y++) {
$iOffset = $top + (int) ($y * $moduleSize);
for ($x = 0; $x < $matrixWidth; $x++) {
if ($image->get($left + (int) ($x * $moduleSize), $iOffset)) {
$bits->set($x, $y);
}
}
}
return $bits;
}
private static function moduleSize($leftTopBlack, $image) {
$height = $image->getHeight();
$width = $image->getWidth();
$x = $leftTopBlack[0];
$y = $leftTopBlack[1];
$inBlack = true;
$transitions = 0;
while ($x < $width && $y < $height) {
if ($inBlack != $image->get($x, $y)) {
if (++$transitions == 5) {
break;
}
$inBlack = !$inBlack;
}
$x++;
$y++;
}
if ($x == $width || $y == $height) {
throw NotFoundException::getNotFoundInstance();
}
return ($x - $leftTopBlack[0]) / 7.0; //return ($x - $leftTopBlack[0]) / 7.0f;
}
public function reset(): void
{
// do nothing
}
final protected function getDecoder()
{
return $this->decoder;
}
}
@@ -17,234 +17,246 @@
namespace Zxing\Qrcode\Decoder;
use Zxing\FormatException;
use Zxing\Common\BitMatrix;
use Zxing\FormatException;
/**
* @author Sean Owen
*/
final class BitMatrixParser {
final class BitMatrixParser
{
private $bitMatrix;
/**
* @var mixed|null
*/
private $parsedVersion;
private $parsedFormatInfo;
private $mirror;
private $bitMatrix;
private $parsedVersion;
private $parsedFormatInfo;
private $mirror;
/**
* @param $bitMatrix {@link BitMatrix} to parse
*
* @throws FormatException if dimension is not >= 21 and 1 mod 4
*/
public function __construct($bitMatrix)
{
$dimension = $bitMatrix->getHeight();
if ($dimension < 21 || ($dimension & 0x03) != 1) {
throw FormatException::getFormatInstance();
}
$this->bitMatrix = $bitMatrix;
}
/**
* @param bitMatrix {@link BitMatrix} to parse
* @throws FormatException if dimension is not >= 21 and 1 mod 4
*/
function __construct($bitMatrix) {
$dimension = $bitMatrix->getHeight();
if ($dimension < 21 || ($dimension & 0x03) != 1) {
throw FormatException::getFormatInstance();
}
$this->bitMatrix = $bitMatrix;
}
/**
* <p>Reads the bits in the {@link BitMatrix} representing the finder pattern in the
* correct order in order to reconstruct the codewords bytes contained within the
* QR Code.</p>
*
* @return bytes encoded within the QR Code
* @throws FormatException if the exact number of bytes expected is not read
*/
public function readCodewords()
{
$formatInfo = $this->readFormatInformation();
$version = $this->readVersion();
/**
* <p>Reads format information from one of its two locations within the QR Code.</p>
*
* @return {@link FormatInformation} encapsulating the QR Code's format info
* @throws FormatException if both format information locations cannot be parsed as
* the valid encoding of format information
*/
function readFormatInformation() {
// 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.
$dataMask = DataMask::forReference($formatInfo->getDataMask());
$dimension = $this->bitMatrix->getHeight();
$dataMask->unmaskBitMatrix($this->bitMatrix, $dimension);
if ($this->parsedFormatInfo != null) {
return $this->parsedFormatInfo;
}
$functionPattern = $version->buildFunctionPattern();
// Read top-left format info bits
$formatInfoBits1 = 0;
for ($i = 0; $i < 6; $i++) {
$formatInfoBits1 = $this->copyBit($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);
// .. and skip a bit in the timing pattern ...
for ($j = 5; $j >= 0; $j--) {
$formatInfoBits1 = $this->copyBit(8, $j, $formatInfoBits1);
}
$readingUp = true;
if ($version->getTotalCodewords()) {
$result = fill_array(0, $version->getTotalCodewords(), 0);
} else {
$result = [];
}
$resultOffset = 0;
$currentByte = 0;
$bitsRead = 0;
// Read columns in pairs, from right to left
for ($j = $dimension - 1; $j > 0; $j -= 2) {
if ($j == 6) {
// Skip whole column with vertical alignment pattern;
// saves time and makes the other code proceed more cleanly
$j--;
}
// Read alternatingly from bottom to top then top to bottom
for ($count = 0; $count < $dimension; $count++) {
$i = $readingUp ? $dimension - 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;
if ($this->bitMatrix->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;
}
}
}
}
$readingUp ^= true; // readingUp = !readingUp; // switch directions
}
if ($resultOffset != $version->getTotalCodewords()) {
throw FormatException::getFormatInstance();
}
// Read the top-right/bottom-left pattern too
$dimension = $this->bitMatrix->getHeight();
$formatInfoBits2 = 0;
$jMin = $dimension - 7;
for ($j = $dimension - 1; $j >= $jMin; $j--) {
$formatInfoBits2 = $this->copyBit(8, $j, $formatInfoBits2);
}
for ($i = $dimension - 8; $i < $dimension; $i++) {
$formatInfoBits2 = $this->copyBit($i, 8, $formatInfoBits2);
}
return $result;
}
$parsedFormatInfo = FormatInformation::decodeFormatInformation($formatInfoBits1, $formatInfoBits2);
if ($parsedFormatInfo != null) {
return $parsedFormatInfo;
}
throw FormatException::getFormatInstance();
}
/**
* <p>Reads format information from one of its two locations within the QR Code.</p>
*
* @return {@link FormatInformation} encapsulating the QR Code's format info
* @throws FormatException if both format information locations cannot be parsed as
* the valid encoding of format information
*/
public function readFormatInformation()
{
if ($this->parsedFormatInfo != null) {
return $this->parsedFormatInfo;
}
/**
* <p>Reads version information from one of its two locations within the QR Code.</p>
*
* @return {@link Version} encapsulating the QR Code's version
* @throws FormatException if both version information locations cannot be parsed as
* the valid encoding of version information
*/
function readVersion(){
// Read top-left format info bits
$formatInfoBits1 = 0;
for ($i = 0; $i < 6; $i++) {
$formatInfoBits1 = $this->copyBit($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);
// .. and skip a bit in the timing pattern ...
for ($j = 5; $j >= 0; $j--) {
$formatInfoBits1 = $this->copyBit(8, $j, $formatInfoBits1);
}
if ($this->parsedVersion != null) {
return $this->parsedVersion;
}
// Read the top-right/bottom-left pattern too
$dimension = $this->bitMatrix->getHeight();
$formatInfoBits2 = 0;
$jMin = $dimension - 7;
for ($j = $dimension - 1; $j >= $jMin; $j--) {
$formatInfoBits2 = $this->copyBit(8, $j, $formatInfoBits2);
}
for ($i = $dimension - 8; $i < $dimension; $i++) {
$formatInfoBits2 = $this->copyBit($i, 8, $formatInfoBits2);
}
$dimension = $this->bitMatrix->getHeight();
$parsedFormatInfo = FormatInformation::decodeFormatInformation($formatInfoBits1, $formatInfoBits2);
if ($parsedFormatInfo != null) {
return $parsedFormatInfo;
}
throw FormatException::getFormatInstance();
}
$provisionalVersion = ($dimension - 17) / 4;
if ($provisionalVersion <= 6) {
return Version::getVersionForNumber($provisionalVersion);
}
private function copyBit($i, $j, $versionBits)
{
$bit = $this->mirror ? $this->bitMatrix->get($j, $i) : $this->bitMatrix->get($i, $j);
// Read top-right version info: 3 wide by 6 tall
$versionBits = 0;
$ijMin = $dimension - 11;
for ($j = 5; $j >= 0; $j--) {
for ($i = $dimension - 9; $i >= $ijMin; $i--) {
$versionBits = $this->copyBit($i, $j, $versionBits);
}
}
return $bit ? ($versionBits << 1) | 0x1 : $versionBits << 1;
}
$theParsedVersion = Version::decodeVersionInformation($versionBits);
if ($theParsedVersion != null && $theParsedVersion->getDimensionForVersion() == $dimension) {
$this->parsedVersion = $theParsedVersion;
return $theParsedVersion;
}
/**
* <p>Reads version information from one of its two locations within the QR Code.</p>
*
* @return {@link Version} encapsulating the QR Code's version
* @throws FormatException if both version information locations cannot be parsed as
* the valid encoding of version information
*/
public function readVersion()
{
if ($this->parsedVersion != null) {
return $this->parsedVersion;
}
// Hmm, failed. Try bottom left: 6 wide by 3 tall
$versionBits = 0;
for ($i = 5; $i >= 0; $i--) {
for ($j = $dimension - 9; $j >=$ijMin; $j--) {
$versionBits = $this->copyBit($i, $j, $versionBits);
}
}
$dimension = $this->bitMatrix->getHeight();
$theParsedVersion = Version::decodeVersionInformation($versionBits);
if ($theParsedVersion != null && $theParsedVersion->getDimensionForVersion() == $dimension) {
$this->parsedVersion = $theParsedVersion;
return $theParsedVersion;
}
throw FormatException::getFormatInstance();
}
$provisionalVersion = ($dimension - 17) / 4;
if ($provisionalVersion <= 6) {
return Version::getVersionForNumber($provisionalVersion);
}
private function copyBit($i, $j, $versionBits) {
$bit = $this->mirror ? $this->bitMatrix->get($j, $i) : $this->bitMatrix->get($i, $j);
return $bit ? ($versionBits << 1) | 0x1 : $versionBits << 1;
}
// Read top-right version info: 3 wide by 6 tall
$versionBits = 0;
$ijMin = $dimension - 11;
for ($j = 5; $j >= 0; $j--) {
for ($i = $dimension - 9; $i >= $ijMin; $i--) {
$versionBits = $this->copyBit($i, $j, $versionBits);
}
}
/**
* <p>Reads the bits in the {@link BitMatrix} representing the finder pattern in the
* correct order in order to reconstruct the codewords bytes contained within the
* QR Code.</p>
*
* @return bytes encoded within the QR Code
* @throws FormatException if the exact number of bytes expected is not read
*/
function readCodewords(){
$theParsedVersion = Version::decodeVersionInformation($versionBits);
if ($theParsedVersion != null && $theParsedVersion->getDimensionForVersion() == $dimension) {
$this->parsedVersion = $theParsedVersion;
$formatInfo = $this->readFormatInformation();
$version = $this->readVersion();
return $theParsedVersion;
}
// 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.
$dataMask = DataMask::forReference($formatInfo->getDataMask());
$dimension = $this->bitMatrix->getHeight();
$dataMask->unmaskBitMatrix($this->bitMatrix, $dimension);
// Hmm, failed. Try bottom left: 6 wide by 3 tall
$versionBits = 0;
for ($i = 5; $i >= 0; $i--) {
for ($j = $dimension - 9; $j >= $ijMin; $j--) {
$versionBits = $this->copyBit($i, $j, $versionBits);
}
}
$functionPattern = $version->buildFunctionPattern();
$theParsedVersion = Version::decodeVersionInformation($versionBits);
if ($theParsedVersion != null && $theParsedVersion->getDimensionForVersion() == $dimension) {
$this->parsedVersion = $theParsedVersion;
$readingUp = true;
if($version->getTotalCodewords()) {
$result = fill_array(0, $version->getTotalCodewords(), 0);
}else{
$result = array();
}
$resultOffset = 0;
$currentByte = 0;
$bitsRead = 0;
// Read columns in pairs, from right to left
for ($j = $dimension - 1; $j > 0; $j -= 2) {
if ($j == 6) {
// Skip whole column with vertical alignment pattern;
// saves time and makes the other code proceed more cleanly
$j--;
}
// Read alternatingly from bottom to top then top to bottom
for ($count = 0; $count < $dimension; $count++) {
$i = $readingUp ? $dimension - 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;
if ($this->bitMatrix->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;
}
}
}
}
$readingUp ^= true; // readingUp = !readingUp; // switch directions
}
if ($resultOffset != $version->getTotalCodewords()) {
throw FormatException::getFormatInstance();
}
return $result;
}
return $theParsedVersion;
}
throw FormatException::getFormatInstance();
}
/**
* Revert the mask removal done while reading the code words. The bit matrix should revert to its original state.
*/
function remask() {
if ($this->parsedFormatInfo == null) {
return; // We have no format information, and have no data mask
}
$dataMask = DataMask::forReference($this->parsedFormatInfo->getDataMask());
$dimension = $this->bitMatrix->getHeight();
$dataMask->unmaskBitMatrix($this->bitMatrix, $dimension);
}
/**
* Revert the mask removal done while reading the code words. The bit matrix should revert to its original state.
*/
public function remask(): void
{
if ($this->parsedFormatInfo == null) {
return; // We have no format information, and have no data mask
}
$dataMask = DataMask::forReference($this->parsedFormatInfo->getDataMask());
$dimension = $this->bitMatrix->getHeight();
$dataMask->unmaskBitMatrix($this->bitMatrix, $dimension);
}
/**
* Prepare the parser for a mirrored operation.
* This flag has effect only on the {@link #readFormatInformation()} and the
* {@link #readVersion()}. Before proceeding with {@link #readCodewords()} the
* {@link #mirror()} method should be called.
*
* @param mirror Whether to read version and format information mirrored.
*/
function setMirror($mirror) {
$parsedVersion = null;
$parsedFormatInfo = null;
$this->mirror = $mirror;
}
/** Mirror the bit matrix in order to attempt a second reading. */
function mirror() {
for ($x = 0; $x < $this->bitMatrix->getWidth(); $x++) {
for ($y = $x + 1; $y < $this->bitMatrix->getHeight(); $y++) {
if ($this->bitMatrix->get($x, $y) != $this->bitMatrix->get($y, $x)) {
$this->bitMatrix->flip($y, $x);
$this->bitMatrix->flip($x, $y);
}
}
}
}
/**
* Prepare the parser for a mirrored operation.
* This flag has effect only on the {@link #readFormatInformation()} and the
* {@link #readVersion()}. Before proceeding with {@link #readCodewords()} the
* {@link #mirror()} method should be called.
*
* @param Whether $mirror to read version and format information mirrored.
*/
public function setMirror($mirror): void
{
$parsedVersion = null;
$parsedFormatInfo = null;
$this->mirror = $mirror;
}
/** Mirror the bit matrix in order to attempt a second reading. */
public function mirror(): void
{
for ($x = 0; $x < $this->bitMatrix->getWidth(); $x++) {
for ($y = $x + 1; $y < $this->bitMatrix->getHeight(); $y++) {
if ($this->bitMatrix->get($x, $y) != $this->bitMatrix->get($y, $x)) {
$this->bitMatrix->flip($y, $x);
$this->bitMatrix->flip($x, $y);
}
}
}
}
}
@@ -24,100 +24,104 @@ namespace Zxing\Qrcode\Decoder;
*
* @author Sean Owen
*/
final class DataBlock {
final class DataBlock
{
//byte[]
private $numDataCodewords;
private $codewords; //byte[]
private function __construct(private $numDataCodewords, private $codewords)
{
}
private function __construct($numDataCodewords, $codewords) {
$this->numDataCodewords = $numDataCodewords;
$this->codewords = $codewords;
}
/**
* <p>When QR Codes use multiple data blocks, they are actually interleaved.
* That is, the first byte of data block 1 to n is written, then the second bytes, and so on. This
* method will separate the data into original blocks.</p>
*
* @param bytes $rawCodewords as read directly from the QR Code
* @param version $version of the QR Code
* @param error $ecLevel-correction level of the QR Code
*
* @return array DataBlocks containing original bytes, "de-interleaved" from representation in the
* QR Code
*/
public static function getDataBlocks(
$rawCodewords,
$version,
$ecLevel
)
{
if ((is_countable($rawCodewords) ? count($rawCodewords) : 0) != $version->getTotalCodewords()) {
throw new \InvalidArgumentException();
}
/**
* <p>When QR Codes use multiple data blocks, they are actually interleaved.
* That is, the first byte of data block 1 to n is written, then the second bytes, and so on. This
* method will separate the data into original blocks.</p>
*
* @param rawCodewords bytes as read directly from the QR Code
* @param version version of the QR Code
* @param ecLevel error-correction level of the QR Code
* @return DataBlocks containing original bytes, "de-interleaved" from representation in the
* QR Code
*/
static function getDataBlocks($rawCodewords,
$version,
$ecLevel) {
// Figure out the number and size of data blocks used by this version and
// error correction level
$ecBlocks = $version->getECBlocksForLevel($ecLevel);
if (count($rawCodewords) != $version->getTotalCodewords()) {
throw new \InvalidArgumentException();
}
// First count the total number of data blocks
$totalBlocks = 0;
$ecBlockArray = $ecBlocks->getECBlocks();
foreach ($ecBlockArray as $ecBlock) {
$totalBlocks += $ecBlock->getCount();
}
// Figure out the number and size of data blocks used by this version and
// error correction level
$ecBlocks = $version->getECBlocksForLevel($ecLevel);
// Now establish DataBlocks of the appropriate size and number of data codewords
$result = [];//new DataBlock[$totalBlocks];
$numResultBlocks = 0;
foreach ($ecBlockArray as $ecBlock) {
$ecBlockCount = $ecBlock->getCount();
for ($i = 0; $i < $ecBlockCount; $i++) {
$numDataCodewords = $ecBlock->getDataCodewords();
$numBlockCodewords = $ecBlocks->getECCodewordsPerBlock() + $numDataCodewords;
$result[$numResultBlocks++] = new DataBlock($numDataCodewords, fill_array(0, $numBlockCodewords, 0));
}
}
// First count the total number of data blocks
$totalBlocks = 0;
$ecBlockArray = $ecBlocks->getECBlocks();
foreach ($ecBlockArray as $ecBlock) {
$totalBlocks += $ecBlock->getCount();
}
// All blocks have the same amount of data, except that the last n
// (where n may be 0) have 1 more byte. Figure out where these start.
$shorterBlocksTotalCodewords = is_countable($result[0]->codewords) ? count($result[0]->codewords) : 0;
$longerBlocksStartAt = count($result) - 1;
while ($longerBlocksStartAt >= 0) {
$numCodewords = is_countable($result[$longerBlocksStartAt]->codewords) ? count($result[$longerBlocksStartAt]->codewords) : 0;
if ($numCodewords == $shorterBlocksTotalCodewords) {
break;
}
$longerBlocksStartAt--;
}
$longerBlocksStartAt++;
// Now establish DataBlocks of the appropriate size and number of data codewords
$result = array();//new DataBlock[$totalBlocks];
$numResultBlocks = 0;
foreach ($ecBlockArray as $ecBlock) {
for ($i = 0; $i < $ecBlock->getCount(); $i++) {
$numDataCodewords = $ecBlock->getDataCodewords();
$numBlockCodewords = $ecBlocks->getECCodewordsPerBlock() + $numDataCodewords;
$result[$numResultBlocks++] = new DataBlock($numDataCodewords, fill_array(0,$numBlockCodewords,0));
}
}
$shorterBlocksNumDataCodewords = $shorterBlocksTotalCodewords - $ecBlocks->getECCodewordsPerBlock();
// The last elements of result may be 1 element longer;
// first fill out as many elements as all of them have
$rawCodewordsOffset = 0;
for ($i = 0; $i < $shorterBlocksNumDataCodewords; $i++) {
for ($j = 0; $j < $numResultBlocks; $j++) {
$result[$j]->codewords[$i] = $rawCodewords[$rawCodewordsOffset++];
}
}
// Fill out the last data block in the longer ones
for ($j = $longerBlocksStartAt; $j < $numResultBlocks; $j++) {
$result[$j]->codewords[$shorterBlocksNumDataCodewords] = $rawCodewords[$rawCodewordsOffset++];
}
// Now add in error correction blocks
$max = is_countable($result[0]->codewords) ? count($result[0]->codewords) : 0;
for ($i = $shorterBlocksNumDataCodewords; $i < $max; $i++) {
for ($j = 0; $j < $numResultBlocks; $j++) {
$iOffset = $j < $longerBlocksStartAt ? $i : $i + 1;
$result[$j]->codewords[$iOffset] = $rawCodewords[$rawCodewordsOffset++];
}
}
// All blocks have the same amount of data, except that the last n
// (where n may be 0) have 1 more byte. Figure out where these start.
$shorterBlocksTotalCodewords = count($result[0]->codewords);
$longerBlocksStartAt = count($result) - 1;
while ($longerBlocksStartAt >= 0) {
$numCodewords = count($result[$longerBlocksStartAt]->codewords);
if ($numCodewords == $shorterBlocksTotalCodewords) {
break;
}
$longerBlocksStartAt--;
}
$longerBlocksStartAt++;
return $result;
}
$shorterBlocksNumDataCodewords = $shorterBlocksTotalCodewords - $ecBlocks->getECCodewordsPerBlock();
// The last elements of result may be 1 element longer;
// first fill out as many elements as all of them have
$rawCodewordsOffset = 0;
for ($i = 0; $i < $shorterBlocksNumDataCodewords; $i++) {
for ($j = 0; $j < $numResultBlocks; $j++) {
$result[$j]->codewords[$i] = $rawCodewords[$rawCodewordsOffset++];
}
}
// Fill out the last data block in the longer ones
for ($j = $longerBlocksStartAt; $j < $numResultBlocks; $j++) {
$result[$j]->codewords[$shorterBlocksNumDataCodewords] = $rawCodewords[$rawCodewordsOffset++];
}
// Now add in error correction blocks
$max = count($result[0]->codewords);
for ($i = $shorterBlocksNumDataCodewords; $i < $max; $i++) {
for ($j = 0; $j < $numResultBlocks; $j++) {
$iOffset = $j < $longerBlocksStartAt ? $i : $i + 1;
$result[$j]->codewords[$iOffset] = $rawCodewords[$rawCodewordsOffset++];
}
}
return $result;
}
function getNumDataCodewords() {
return $this->numDataCodewords;
}
function getCodewords() {
return $this->codewords;
}
public function getNumDataCodewords()
{
return $this->numDataCodewords;
}
public function getCodewords()
{
return $this->codewords;
}
}
@@ -32,144 +32,163 @@ use Zxing\Common\BitMatrix;
*/
abstract class DataMask
{
/**
* See ISO 18004:2006 6.8.1
*/
private static array $DATA_MASKS = [];
/**
* See ISO 18004:2006 6.8.1
*/
private static $DATA_MASKS = array();
public function __construct()
{
}
static function Init()
{
self::$DATA_MASKS = array(
new DataMask000(),
new DataMask001(),
new DataMask010(),
new DataMask011(),
new DataMask100(),
new DataMask101(),
new DataMask110(),
new DataMask111(),
);
}
public static function Init(): void
{
self::$DATA_MASKS = [
new DataMask000(),
new DataMask001(),
new DataMask010(),
new DataMask011(),
new DataMask100(),
new DataMask101(),
new DataMask110(),
new DataMask111(),
];
}
function __construct()
{
/**
* @param a $reference value between 0 and 7 indicating one of the eight possible
* data mask patterns a QR Code may use
*
* @return DataMask encapsulating the data mask pattern
*/
public static function forReference($reference)
{
if ($reference < 0 || $reference > 7) {
throw new \InvalidArgumentException();
}
}
return self::$DATA_MASKS[$reference];
}
/**
* <p>Implementations of this method reverse the data masking process applied to a QR Code and
* make its bits ready to read.</p>
*
* @param bits representation of QR Code bits
* @param dimension dimension of QR Code, represented by bits, being unmasked
*/
final function unmaskBitMatrix($bits, $dimension)
{
for ($i = 0; $i < $dimension; $i++) {
for ($j = 0; $j < $dimension; $j++) {
if ($this->isMasked($i, $j)) {
$bits->flip($j, $i);
}
}
}
}
/**
* <p>Implementations of this method reverse the data masking process applied to a QR Code and
* make its bits ready to read.</p>
*
* @param representation $bits of QR Code bits
* @param dimension $dimension of QR Code, represented by bits, being unmasked
*/
final public function unmaskBitMatrix($bits, $dimension): void
{
for ($i = 0; $i < $dimension; $i++) {
for ($j = 0; $j < $dimension; $j++) {
if ($this->isMasked($i, $j)) {
$bits->flip($j, $i);
}
}
}
}
abstract function isMasked($i, $j);
/**
* @param reference a value between 0 and 7 indicating one of the eight possible
* data mask patterns a QR Code may use
* @return DataMask encapsulating the data mask pattern
*/
static function forReference($reference)
{
if ($reference < 0 || $reference > 7) {
throw new \InvalidArgumentException();
}
return self::$DATA_MASKS[$reference];
}
abstract public function isMasked($i, $j);
}
DataMask::Init();
/**
* 000: mask bits for which (x + y) mod 2 == 0
*/
final class DataMask000 extends DataMask {
// @Override
function isMasked($i, $j) {
return (($i + $j) & 0x01) == 0;
}
final class DataMask000 extends DataMask
{
// @Override
public function isMasked($i, $j)
{
return (($i + $j) & 0x01) == 0;
}
}
/**
* 001: mask bits for which x mod 2 == 0
*/
final class DataMask001 extends DataMask {
//@Override
function isMasked($i, $j) {
return ($i & 0x01) == 0;
}
final class DataMask001 extends DataMask
{
//@Override
public function isMasked($i, $j)
{
return ($i & 0x01) == 0;
}
}
/**
* 010: mask bits for which y mod 3 == 0
*/
final class DataMask010 extends DataMask {
//@Override
function isMasked($i, $j) {
return $j % 3 == 0;
}
final class DataMask010 extends DataMask
{
//@Override
public function isMasked($i, $j)
{
return $j % 3 == 0;
}
}
/**
* 011: mask bits for which (x + y) mod 3 == 0
*/
final class DataMask011 extends DataMask {
//@Override
function isMasked($i, $j) {
return ($i + $j) % 3 == 0;
}
final class DataMask011 extends DataMask
{
//@Override
public function isMasked($i, $j)
{
return ($i + $j) % 3 == 0;
}
}
/**
* 100: mask bits for which (x/2 + y/3) mod 2 == 0
*/
final class DataMask100 extends DataMask {
//@Override
function isMasked($i, $j) {
return intval((intval($i / 2) + intval($j /3)) & 0x01) == 0;
}
final class DataMask100 extends DataMask
{
//@Override
public function isMasked($i, $j)
{
return (int)(((int)($i / 2) + (int)($j / 3)) & 0x01) == 0;
}
}
/**
* 101: mask bits for which xy mod 2 + xy mod 3 == 0
*/
final class DataMask101 extends DataMask {
//@Override
function isMasked($i, $j) {
$temp = $i * $j;
return ($temp & 0x01) + ($temp % 3) == 0;
}
final class DataMask101 extends DataMask
{
//@Override
public function isMasked($i, $j)
{
$temp = $i * $j;
return ($temp & 0x01) + ($temp % 3) == 0;
}
}
/**
* 110: mask bits for which (xy mod 2 + xy mod 3) mod 2 == 0
*/
final class DataMask110 extends DataMask {
//@Override
function isMasked($i, $j) {
$temp = $i * $j;
return ((($temp & 0x01) + ($temp % 3)) & 0x01) == 0;
}
final class DataMask110 extends DataMask
{
//@Override
public function isMasked($i, $j)
{
$temp = $i * $j;
return ((($temp & 0x01) + ($temp % 3)) & 0x01) == 0;
}
}
/**
* 111: mask bits for which ((x+y)mod 2 + xy mod 3) mod 2 == 0
*/
final class DataMask111 extends DataMask {
//@Override
function isMasked($i, $j) {
return (((($i + $j) & 0x01) + (($i * $j) % 3)) & 0x01) == 0;
}
final class DataMask111 extends DataMask
{
//@Override
public function isMasked($i, $j)
{
return (((($i + $j) & 0x01) + (($i * $j) % 3)) & 0x01) == 0;
}
}
@@ -17,14 +17,10 @@
namespace Zxing\Qrcode\Decoder;
use Zxing\DecodeHintType;
use Zxing\FormatException;
use Zxing\Common\BitSource;
use Zxing\Common\CharacterSetECI;
use Zxing\Common\DecoderResult;
use Zxing\Common\StringUtils;
use Zxing\FormatException;
/**
* <p>QR Codes can encode text as bits in one of several modes, and can use multiple modes
@@ -34,326 +30,335 @@ use Zxing\Common\StringUtils;
*
* @author Sean Owen
*/
final class DecodedBitStreamParser {
final class DecodedBitStreamParser
{
/**
* See ISO 18004:2006, 6.4.4 Table 5
*/
private static array $ALPHANUMERIC_CHARS = [
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B',
'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
' ', '$', '%', '*', '+', '-', '.', '/', ':',
];
private static int $GB2312_SUBSET = 1;
/**
* See ISO 18004:2006, 6.4.4 Table 5
*/
private static $ALPHANUMERIC_CHARS = array(
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B',
'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
' ', '$', '%', '*', '+', '-', '.', '/', ':'
);
private static $GB2312_SUBSET = 1;
public static function decode(
$bytes,
$version,
$ecLevel,
$hints
): \Zxing\Common\DecoderResult
{
$bits = new BitSource($bytes);
$result = '';//new StringBuilder(50);
$byteSegments = [];
$symbolSequence = -1;
$parityData = -1;
try {
$currentCharacterSetECI = null;
$fc1InEffect = false;
$mode = '';
do {
// While still another segment to read...
if ($bits->available() < 4) {
// OK, assume we're done. Really, a TERMINATOR mode should have been recorded here
$mode = Mode::$TERMINATOR;
} else {
$mode = Mode::forBits($bits->readBits(4)); // mode is encoded by 4 bits
}
if ($mode != Mode::$TERMINATOR) {
if ($mode == Mode::$FNC1_FIRST_POSITION || $mode == Mode::$FNC1_SECOND_POSITION) {
// We do little with FNC1 except alter the parsed result a bit according to the spec
$fc1InEffect = true;
} elseif ($mode == Mode::$STRUCTURED_APPEND) {
if ($bits->available() < 16) {
throw FormatException::getFormatInstance();
}
// sequence number and parity is added later to the result metadata
// Read next 8 bits (symbol sequence #) and 8 bits (parity data), then continue
$symbolSequence = $bits->readBits(8);
$parityData = $bits->readBits(8);
} elseif ($mode == Mode::$ECI) {
// Count doesn't apply to ECI
$value = self::parseECIValue($bits);
$currentCharacterSetECI = CharacterSetECI::getCharacterSetECIByValue($value);
if ($currentCharacterSetECI == null) {
throw FormatException::getFormatInstance();
}
} else {
// First handle Hanzi mode which does not start with character count
if ($mode == Mode::$HANZI) {
//chinese mode contains a sub set indicator right after mode indicator
$subset = $bits->readBits(4);
$countHanzi = $bits->readBits($mode->getCharacterCountBits($version));
if ($subset == self::$GB2312_SUBSET) {
self::decodeHanziSegment($bits, $result, $countHanzi);
}
} else {
// "Normal" QR code modes:
// How many characters will follow, encoded in this mode?
$count = $bits->readBits($mode->getCharacterCountBits($version));
if ($mode == Mode::$NUMERIC) {
self::decodeNumericSegment($bits, $result, $count);
} elseif ($mode == Mode::$ALPHANUMERIC) {
self::decodeAlphanumericSegment($bits, $result, $count, $fc1InEffect);
} elseif ($mode == Mode::$BYTE) {
self::decodeByteSegment($bits, $result, $count, $currentCharacterSetECI, $byteSegments, $hints);
} elseif ($mode == Mode::$KANJI) {
self::decodeKanjiSegment($bits, $result, $count);
} else {
throw FormatException::getFormatInstance();
}
}
}
}
} while ($mode != Mode::$TERMINATOR);
} catch (\InvalidArgumentException) {
// from readBits() calls
throw FormatException::getFormatInstance();
}
private function DecodedBitStreamParser() {
return new DecoderResult(
$bytes,
$result,
empty($byteSegments) ? null : $byteSegments,
$ecLevel == null ? null : 'L',//ErrorCorrectionLevel::toString($ecLevel),
$symbolSequence,
$parityData
);
}
private static function parseECIValue($bits)
{
$firstByte = $bits->readBits(8);
if (($firstByte & 0x80) == 0) {
// just one byte
return $firstByte & 0x7F;
}
if (($firstByte & 0xC0) == 0x80) {
// two bytes
$secondByte = $bits->readBits(8);
}
return (($firstByte & 0x3F) << 8) | $secondByte;
}
if (($firstByte & 0xE0) == 0xC0) {
// three bytes
$secondThirdBytes = $bits->readBits(16);
static function decode($bytes,
$version,
$ecLevel,
$hints) {
$bits = new BitSource($bytes);
$result = '';//new StringBuilder(50);
$byteSegments = array();
$symbolSequence = -1;
$parityData = -1;
return (($firstByte & 0x1F) << 16) | $secondThirdBytes;
}
throw FormatException::getFormatInstance();
}
try {
$currentCharacterSetECI = null;
$fc1InEffect = false;
$mode='';
do {
// While still another segment to read...
if ($bits->available() < 4) {
// OK, assume we're done. Really, a TERMINATOR mode should have been recorded here
$mode = Mode::$TERMINATOR;
} else {
$mode = Mode::forBits($bits->readBits(4)); // mode is encoded by 4 bits
}
if ($mode != Mode::$TERMINATOR) {
if ($mode == Mode::$FNC1_FIRST_POSITION || $mode == Mode::$FNC1_SECOND_POSITION) {
// We do little with FNC1 except alter the parsed result a bit according to the spec
$fc1InEffect = true;
} else if ($mode == Mode::$STRUCTURED_APPEND) {
if ($bits->available() < 16) {
throw FormatException::getFormatInstance();
}
// sequence number and parity is added later to the result metadata
// Read next 8 bits (symbol sequence #) and 8 bits (parity data), then continue
$symbolSequence = $bits->readBits(8);
$parityData = $bits->readBits(8);
} else if ($mode == Mode::$ECI) {
// Count doesn't apply to ECI
$value = self::parseECIValue($bits);
$currentCharacterSetECI = CharacterSetECI::getCharacterSetECIByValue($value);
if ($currentCharacterSetECI == null) {
throw FormatException::getFormatInstance();
}
} else {
// First handle Hanzi mode which does not start with character count
if ($mode == Mode::$HANZI) {
//chinese mode contains a sub set indicator right after mode indicator
$subset = $bits->readBits(4);
$countHanzi = $bits->readBits($mode->getCharacterCountBits($version));
if ($subset == self::$GB2312_SUBSET) {
self::decodeHanziSegment($bits, $result, $countHanzi);
}
} else {
// "Normal" QR code modes:
// How many characters will follow, encoded in this mode?
$count = $bits->readBits($mode->getCharacterCountBits($version));
if ($mode == Mode::$NUMERIC) {
self::decodeNumericSegment($bits, $result, $count);
} else if ($mode == Mode::$ALPHANUMERIC) {
self::decodeAlphanumericSegment($bits, $result, $count, $fc1InEffect);
} else if ($mode == Mode::$BYTE) {
self::decodeByteSegment($bits, $result, $count, $currentCharacterSetECI, $byteSegments, $hints);
} else if ($mode == Mode::$KANJI) {
self::decodeKanjiSegment($bits, $result, $count);
} else {
throw FormatException::getFormatInstance();
}
}
}
}
} while ($mode != Mode::$TERMINATOR);
} catch (IllegalArgumentException $iae) {
// from readBits() calls
throw FormatException::getFormatInstance();
}
/**
* See specification GBT 18284-2000
*/
private static function decodeHanziSegment(
$bits,
&$result,
$count
)
{
// Don't crash trying to read more bits than we have available.
if ($count * 13 > $bits->available()) {
throw FormatException::getFormatInstance();
}
return new DecoderResult($bytes,
$result,
empty($byteSegments) ? null : $byteSegments,
$ecLevel == null ? null : 'L',//ErrorCorrectionLevel::toString($ecLevel),
$symbolSequence,
$parityData);
}
// Each character will require 2 bytes. Read the characters as 2-byte pairs
// and decode as GB2312 afterwards
$buffer = fill_array(0, 2 * $count, 0);
$offset = 0;
while ($count > 0) {
// Each 13 bits encodes a 2-byte character
$twoBytes = $bits->readBits(13);
$assembledTwoBytes = (($twoBytes / 0x060) << 8) | ($twoBytes % 0x060);
if ($assembledTwoBytes < 0x003BF) {
// In the 0xA1A1 to 0xAAFE range
$assembledTwoBytes += 0x0A1A1;
} else {
// In the 0xB0A1 to 0xFAFE range
$assembledTwoBytes += 0x0A6A1;
}
$buffer[$offset] = (($assembledTwoBytes >> 8) & 0xFF);//(byte)
$buffer[$offset + 1] = ($assembledTwoBytes & 0xFF);//(byte)
$offset += 2;
$count--;
}
$result .= iconv('GB2312', 'UTF-8', implode($buffer));
}
/**
* See specification GBT 18284-2000
*/
private static function decodeHanziSegment($bits,
&$result,
$count) {
// Don't crash trying to read more bits than we have available.
if ($count * 13 > $bits->available()) {
throw FormatException::getFormatInstance();
}
private static function decodeNumericSegment(
$bits,
&$result,
$count
)
{
// Read three digits at a time
while ($count >= 3) {
// Each 10 bits encodes three digits
if ($bits->available() < 10) {
throw FormatException::getFormatInstance();
}
$threeDigitsBits = $bits->readBits(10);
if ($threeDigitsBits >= 1000) {
throw FormatException::getFormatInstance();
}
$result .= (self::toAlphaNumericChar($threeDigitsBits / 100));
$result .= (self::toAlphaNumericChar(($threeDigitsBits / 10) % 10));
$result .= (self::toAlphaNumericChar($threeDigitsBits % 10));
$count -= 3;
}
if ($count == 2) {
// Two digits left over to read, encoded in 7 bits
if ($bits->available() < 7) {
throw FormatException::getFormatInstance();
}
$twoDigitsBits = $bits->readBits(7);
if ($twoDigitsBits >= 100) {
throw FormatException::getFormatInstance();
}
$result .= (self::toAlphaNumericChar($twoDigitsBits / 10));
$result .= (self::toAlphaNumericChar($twoDigitsBits % 10));
} elseif ($count == 1) {
// One digit left over to read
if ($bits->available() < 4) {
throw FormatException::getFormatInstance();
}
$digitBits = $bits->readBits(4);
if ($digitBits >= 10) {
throw FormatException::getFormatInstance();
}
$result .= (self::toAlphaNumericChar($digitBits));
}
}
// Each character will require 2 bytes. Read the characters as 2-byte pairs
// and decode as GB2312 afterwards
$buffer = fill_array(0,2 * $count,0);
$offset = 0;
while ($count > 0) {
// Each 13 bits encodes a 2-byte character
$twoBytes = $bits->readBits(13);
$assembledTwoBytes = (($twoBytes / 0x060) << 8) | ($twoBytes % 0x060);
if ($assembledTwoBytes < 0x003BF) {
// In the 0xA1A1 to 0xAAFE range
$assembledTwoBytes += 0x0A1A1;
} else {
// In the 0xB0A1 to 0xFAFE range
$assembledTwoBytes += 0x0A6A1;
}
$buffer[$offset] = (($assembledTwoBytes >> 8) & 0xFF);//(byte)
$buffer[$offset + 1] = ($assembledTwoBytes & 0xFF);//(byte)
$offset += 2;
$count--;
}
private static function toAlphaNumericChar($value)
{
if ($value >= count(self::$ALPHANUMERIC_CHARS)) {
throw FormatException::getFormatInstance();
}
try {
$result .= iconv('GB2312', 'UTF-8', implode($buffer));
} catch (UnsupportedEncodingException $ignored) {
throw FormatException::getFormatInstance();
}
}
return self::$ALPHANUMERIC_CHARS[$value];
}
private static function decodeKanjiSegment($bits,
&$result,
$count) {
// Don't crash trying to read more bits than we have available.
if ($count * 13 > $bits->available()) {
throw FormatException::getFormatInstance();
}
private static function decodeAlphanumericSegment(
$bits,
&$result,
$count,
$fc1InEffect
)
{
// Read two characters at a time
$start = strlen((string) $result);
while ($count > 1) {
if ($bits->available() < 11) {
throw FormatException::getFormatInstance();
}
$nextTwoCharsBits = $bits->readBits(11);
$result .= (self::toAlphaNumericChar($nextTwoCharsBits / 45));
$result .= (self::toAlphaNumericChar($nextTwoCharsBits % 45));
$count -= 2;
}
if ($count == 1) {
// special case: one character left
if ($bits->available() < 6) {
throw FormatException::getFormatInstance();
}
$result .= self::toAlphaNumericChar($bits->readBits(6));
}
// See section 6.4.8.1, 6.4.8.2
if ($fc1InEffect) {
// We need to massage the result a bit if in an FNC1 mode:
for ($i = $start; $i < strlen((string) $result); $i++) {
if ($result[$i] == '%') {
if ($i < strlen((string) $result) - 1 && $result[$i + 1] == '%') {
// %% is rendered as %
$result = substr_replace($result, '', $i + 1, 1);//deleteCharAt(i + 1);
} else {
// In alpha mode, % should be converted to FNC1 separator 0x1D
$result . setCharAt($i, chr(0x1D));
}
}
}
}
}
// Each character will require 2 bytes. Read the characters as 2-byte pairs
// and decode as Shift_JIS afterwards
$buffer = array(0,2 * $count,0);
$offset = 0;
while ($count > 0) {
// Each 13 bits encodes a 2-byte character
$twoBytes = $bits->readBits(13);
$assembledTwoBytes = (($twoBytes / 0x0C0) << 8) | ($twoBytes % 0x0C0);
if ($assembledTwoBytes < 0x01F00) {
// In the 0x8140 to 0x9FFC range
$assembledTwoBytes += 0x08140;
} else {
// In the 0xE040 to 0xEBBF range
$assembledTwoBytes += 0x0C140;
}
$buffer[$offset] = ($assembledTwoBytes >> 8);//(byte)
$buffer[$offset + 1] = $assembledTwoBytes; //(byte)
$offset += 2;
$count--;
}
// Shift_JIS may not be supported in some environments:
try {
$result .= iconv('shift-jis','utf-8',implode($buffer));
private static function decodeByteSegment(
$bits,
&$result,
$count,
$currentCharacterSetECI,
&$byteSegments,
$hints
)
{
// Don't crash trying to read more bits than we have available.
if (8 * $count > $bits->available()) {
throw FormatException::getFormatInstance();
}
$readBytes = fill_array(0, $count, 0);
for ($i = 0; $i < $count; $i++) {
$readBytes[$i] = $bits->readBits(8);//(byte)
}
$text = implode(array_map('chr', $readBytes));
$encoding = '';
if ($currentCharacterSetECI == null) {
// The spec isn't clear on this mode; see
// section 6.4.5: t does not say which encoding to assuming
// upon decoding. I have seen ISO-8859-1 used as well as
// Shift_JIS -- without anything like an ECI designator to
// give a hint.
} catch (UnsupportedEncodingException $ignored) {
throw FormatException::getFormatInstance();
}
}
$encoding = mb_detect_encoding($text, $hints);
} else {
$encoding = $currentCharacterSetECI->name();
}
// $result.= mb_convert_encoding($text ,$encoding);//(new String(readBytes, encoding));
$result .= $text;//(new String(readBytes, encoding));
private static function decodeByteSegment($bits,
&$result,
$count,
$currentCharacterSetECI,
&$byteSegments,
$hints) {
// Don't crash trying to read more bits than we have available.
if (8 * $count > $bits->available()) {
throw FormatException::getFormatInstance();
}
$byteSegments = array_merge($byteSegments, $readBytes);
}
$readBytes = fill_array(0,$count,0);
for ($i = 0; $i < $count; $i++) {
$readBytes[$i] = $bits->readBits(8);//(byte)
}
$text = implode(array_map('chr',$readBytes));
$encoding = '';
if ($currentCharacterSetECI == null) {
// The spec isn't clear on this mode; see
// section 6.4.5: t does not say which encoding to assuming
// upon decoding. I have seen ISO-8859-1 used as well as
// Shift_JIS -- without anything like an ECI designator to
// give a hint.
private static function decodeKanjiSegment(
$bits,
&$result,
$count
)
{
// Don't crash trying to read more bits than we have available.
if ($count * 13 > $bits->available()) {
throw FormatException::getFormatInstance();
}
$encoding = mb_detect_encoding($text, $hints);
} else {
$encoding = $currentCharacterSetECI->name();
}
try {
// $result.= mb_convert_encoding($text ,$encoding);//(new String(readBytes, encoding));
$result.= $text;//(new String(readBytes, encoding));
} catch (UnsupportedEncodingException $ignored) {
throw FormatException::getFormatInstance();
}
$byteSegments = array_merge($byteSegments, $readBytes);
}
// Each character will require 2 bytes. Read the characters as 2-byte pairs
// and decode as Shift_JIS afterwards
$buffer = [0, 2 * $count, 0];
$offset = 0;
while ($count > 0) {
// Each 13 bits encodes a 2-byte character
$twoBytes = $bits->readBits(13);
$assembledTwoBytes = (($twoBytes / 0x0C0) << 8) | ($twoBytes % 0x0C0);
if ($assembledTwoBytes < 0x01F00) {
// In the 0x8140 to 0x9FFC range
$assembledTwoBytes += 0x08140;
} else {
// In the 0xE040 to 0xEBBF range
$assembledTwoBytes += 0x0C140;
}
$buffer[$offset] = ($assembledTwoBytes >> 8);//(byte)
$buffer[$offset + 1] = $assembledTwoBytes; //(byte)
$offset += 2;
$count--;
}
// Shift_JIS may not be supported in some environments:
private static function toAlphaNumericChar($value) {
if ($value >= count(self::$ALPHANUMERIC_CHARS)) {
throw FormatException::getFormatInstance();
}
return self::$ALPHANUMERIC_CHARS[$value];
}
private static function decodeAlphanumericSegment($bits,
&$result,
$count,
$fc1InEffect) {
// Read two characters at a time
$start = strlen($result);
while ($count > 1) {
if ($bits->available() < 11) {
throw FormatException::getFormatInstance();
}
$nextTwoCharsBits = $bits->readBits(11);
$result.=(self::toAlphaNumericChar($nextTwoCharsBits / 45));
$result.=(self::toAlphaNumericChar($nextTwoCharsBits % 45));
$count -= 2;
}
if ($count == 1) {
// special case: one character left
if ($bits->available() < 6) {
throw FormatException::getFormatInstance();
}
$result.=self::toAlphaNumericChar($bits->readBits(6));
}
// See section 6.4.8.1, 6.4.8.2
if ($fc1InEffect) {
// We need to massage the result a bit if in an FNC1 mode:
for ($i = $start; $i < strlen($result); $i++) {
if ($result{$i} == '%') {
if ($i < strlen($result) - 1 && $result{$i + 1} == '%') {
// %% is rendered as %
$result = substr_replace($result,'',$i + 1,1);//deleteCharAt(i + 1);
} else {
// In alpha mode, % should be converted to FNC1 separator 0x1D
$result.setCharAt($i, chr(0x1D));
}
}
}
}
}
private static function decodeNumericSegment($bits,
&$result,
$count) {
// Read three digits at a time
while ($count >= 3) {
// Each 10 bits encodes three digits
if ($bits->available() < 10) {
throw FormatException::getFormatInstance();
}
$threeDigitsBits = $bits->readBits(10);
if ($threeDigitsBits >= 1000) {
throw FormatException::getFormatInstance();
}
$result.=(self::toAlphaNumericChar($threeDigitsBits / 100));
$result.=(self::toAlphaNumericChar(($threeDigitsBits / 10) % 10));
$result.=(self::toAlphaNumericChar($threeDigitsBits % 10));
$count -= 3;
}
if ($count == 2) {
// Two digits left over to read, encoded in 7 bits
if ($bits->available() < 7) {
throw FormatException::getFormatInstance();
}
$twoDigitsBits = $bits->readBits(7);
if ($twoDigitsBits >= 100) {
throw FormatException::getFormatInstance();
}
$result.=(self::toAlphaNumericChar($twoDigitsBits / 10));
$result.=(self::toAlphaNumericChar($twoDigitsBits % 10));
} else if ($count == 1) {
// One digit left over to read
if ($bits->available() < 4) {
throw FormatException::getFormatInstance();
}
$digitBits = $bits->readBits(4);
if ($digitBits >= 10) {
throw FormatException::getFormatInstance();
}
$result.=(self::toAlphaNumericChar($digitBits));
}
}
private static function parseECIValue($bits) {
$firstByte = $bits->readBits(8);
if (($firstByte & 0x80) == 0) {
// just one byte
return $firstByte & 0x7F;
}
if (($firstByte & 0xC0) == 0x80) {
// two bytes
$secondByte = $bits->readBits(8);
return (($firstByte & 0x3F) << 8) | $secondByte;
}
if (($firstByte & 0xE0) == 0xC0) {
// three bytes
$secondThirdBytes = $bits->readBits(16);
return (($firstByte & 0x1F) << 16) | $secondThirdBytes;
}
throw FormatException::getFormatInstance();
}
$result .= iconv('shift-jis', 'utf-8', implode($buffer));
}
private function DecodedBitStreamParser(): void
{
}
}
@@ -18,15 +18,11 @@
namespace Zxing\Qrcode\Decoder;
use Zxing\ChecksumException;
use Zxing\DecodeHintType;
use Zxing\FormatException;
use Zxing\Common\BitMatrix;
use Zxing\Common\DecoderResult;
use Zxing\Common\Reedsolomon\GenericGF;
use Zxing\Common\Reedsolomon\ReedSolomonDecoder;
use Zxing\Common\Reedsolomon\ReedSolomonException;
use Zxing\FormatException;
/**
* <p>The main class which implements QR Code decoding -- as opposed to locating and extracting
@@ -34,181 +30,179 @@ use Zxing\Common\Reedsolomon\ReedSolomonException;
*
* @author Sean Owen
*/
final class Decoder {
final class Decoder
{
private readonly \Zxing\Common\Reedsolomon\ReedSolomonDecoder $rsDecoder;
private $rsDecoder;
public function __construct()
{
$this->rsDecoder = new ReedSolomonDecoder(GenericGF::$QR_CODE_FIELD_256);
}
public function __construct() {
$this->rsDecoder = new ReedSolomonDecoder(GenericGF::$QR_CODE_FIELD_256);
}
public function decode($variable, $hints = null)
{
if (is_array($variable)) {
return $this->decodeImage($variable, $hints);
} elseif ($variable instanceof BitMatrix) {
return $this->decodeBits($variable, $hints);
} elseif ($variable instanceof BitMatrixParser) {
return $this->decodeParser($variable, $hints);
}
die('decode error Decoder.php');
}
/**
* <p>Convenience method that can decode a QR Code represented as a 2D array of booleans.
* "true" is taken to mean a black module.</p>
*
* @param array $image booleans representing white/black QR Code modules
* @param decoding $hints hints that should be used to influence decoding
*
* @return text and bytes encoded within the QR Code
* @throws FormatException if the QR Code cannot be decoded
* @throws ChecksumException if error correction fails
*/
public function decodeImage($image, $hints = null)
{
$dimension = count($image);
$bits = new BitMatrix($dimension);
for ($i = 0; $i < $dimension; $i++) {
for ($j = 0; $j < $dimension; $j++) {
if ($image[$i][$j]) {
$bits->set($j, $i);
}
}
}
return $this->decode($bits, $hints);
}
function decode($variable, $hints=null){
if(is_array($variable)){
return $this->decodeImage($variable,$hints);
}elseif(is_object($variable)&&$variable instanceof BitMatrix){
return $this->decodeBits($variable,$hints);
}elseif(is_object($variable)&&$variable instanceof BitMatrixParser){
return $this->decodeParser($variable,$hints);
}else{
die('decode error Decoder.php');
}
}
/**
* <p>Convenience method that can decode a QR Code represented as a 2D array of booleans.
* "true" is taken to mean a black module.</p>
*
* @param image booleans representing white/black QR Code modules
* @param hints decoding hints that should be used to influence decoding
* @return text and bytes encoded within the QR Code
* @throws FormatException if the QR Code cannot be decoded
* @throws ChecksumException if error correction fails
*/
public function decodeImage($image, $hints=null)
{
$dimension = count($image);
$bits = new BitMatrix($dimension);
for ($i = 0; $i < $dimension; $i++) {
for ($j = 0; $j < $dimension; $j++) {
if ($image[$i][$j]) {
$bits->set($j, $i);
}
}
}
return $this->decode($bits, $hints);
}
/**
* <p>Decodes a QR Code represented as a {@link BitMatrix}. A 1 or "true" is taken to mean a black module.</p>
*
* @param bits booleans representing white/black QR Code modules
* @param hints decoding hints that should be used to influence decoding
* @return text and bytes encoded within the QR Code
* @throws FormatException if the QR Code cannot be decoded
* @throws ChecksumException if error correction fails
*/
public function decodeBits($bits, $hints=null)
{
/**
* <p>Decodes a QR Code represented as a {@link BitMatrix}. A 1 or "true" is taken to mean a black module.</p>
*
* @param BitMatrix $bits booleans representing white/black QR Code modules
* @param decoding $hints hints that should be used to influence decoding
*
* @return text and bytes encoded within the QR Code
* @throws FormatException if the QR Code cannot be decoded
* @throws ChecksumException if error correction fails
*/
public function decodeBits($bits, $hints = null)
{
// Construct a parser and read version, error-correction level
$parser = new BitMatrixParser($bits);
$fe = null;
$ce = null;
try {
return $this->decode($parser, $hints);
} catch (FormatException $e) {
$fe = $e;
} catch (ChecksumException $e) {
$ce = $e;
}
$parser = new BitMatrixParser($bits);
$fe = null;
$ce = null;
try {
return $this->decode($parser, $hints);
} catch (FormatException $e) {
$fe = $e;
} catch (ChecksumException $e) {
$ce = $e;
}
try {
try {
// Revert the bit matrix
$parser->remask();
// Revert the bit matrix
$parser->remask();
// Will be attempting a mirrored reading of the version and format info.
$parser->setMirror(true);
// Will be attempting a mirrored reading of the version and format info.
$parser->setMirror(true);
// Preemptively read the version.
$parser->readVersion();
// Preemptively read the version.
$parser->readVersion();
// Preemptively read the format information.
$parser->readFormatInformation();
// Preemptively read the format information.
$parser->readFormatInformation();
/*
* Since we're here, this means we have successfully detected some kind
* of version and format information when mirrored. This is a good sign,
* that the QR code may be mirrored, and we should try once more with a
* mirrored content.
*/
// Prepare for a mirrored reading.
$parser->mirror();
/*
* Since we're here, this means we have successfully detected some kind
* of version and format information when mirrored. This is a good sign,
* that the QR code may be mirrored, and we should try once more with a
* mirrored content.
*/
// Prepare for a mirrored reading.
$parser->mirror();
$result = $this->decode($parser, $hints);
$result = $this->decode($parser, $hints);
// Success! Notify the caller that the code was mirrored.
$result->setOther(new QRCodeDecoderMetaData(true));
// Success! Notify the caller that the code was mirrored.
$result->setOther(new QRCodeDecoderMetaData(true));
return $result;
return $result;
} catch (FormatException $e) {// catch (FormatException | ChecksumException e) {
// Throw the exception from the original reading
if ($fe != null) {
throw $fe;
}
if ($ce != null) {
throw $ce;
}
throw $e;
}
}
} catch (FormatException $e ) {// catch (FormatException | ChecksumException e) {
// Throw the exception from the original reading
if ($fe != null) {
throw $fe;
}
if ($ce != null) {
throw $ce;
}
throw $e;
private function decodeParser($parser, $hints = null)
{
$version = $parser->readVersion();
$ecLevel = $parser->readFormatInformation()->getErrorCorrectionLevel();
}
}
// Read codewords
$codewords = $parser->readCodewords();
// Separate into data blocks
$dataBlocks = DataBlock::getDataBlocks($codewords, $version, $ecLevel);
private function decodeParser($parser,$hints=null)
{
$version = $parser->readVersion();
$ecLevel = $parser->readFormatInformation()->getErrorCorrectionLevel();
// Count total number of data bytes
$totalBytes = 0;
foreach ($dataBlocks as $dataBlock) {
$totalBytes += $dataBlock->getNumDataCodewords();
}
$resultBytes = fill_array(0, $totalBytes, 0);
$resultOffset = 0;
// Read codewords
$codewords = $parser->readCodewords();
// Separate into data blocks
$dataBlocks = DataBlock::getDataBlocks($codewords, $version, $ecLevel);
// Error-correct and copy data blocks together into a stream of bytes
foreach ($dataBlocks as $dataBlock) {
$codewordBytes = $dataBlock->getCodewords();
$numDataCodewords = $dataBlock->getNumDataCodewords();
$this->correctErrors($codewordBytes, $numDataCodewords);
for ($i = 0; $i < $numDataCodewords; $i++) {
$resultBytes[$resultOffset++] = $codewordBytes[$i];
}
}
// Count total number of data bytes
$totalBytes = 0;
foreach ($dataBlocks as $dataBlock) {
$totalBytes += $dataBlock->getNumDataCodewords();
}
$resultBytes = fill_array(0,$totalBytes,0);
$resultOffset = 0;
// Error-correct and copy data blocks together into a stream of bytes
foreach ($dataBlocks as $dataBlock) {
$codewordBytes = $dataBlock->getCodewords();
$numDataCodewords = $dataBlock->getNumDataCodewords();
$this->correctErrors($codewordBytes, $numDataCodewords);
for ($i = 0; $i < $numDataCodewords; $i++) {
$resultBytes[$resultOffset++] = $codewordBytes[$i];
}
}
// Decode the contents of that stream of bytes
return DecodedBitStreamParser::decode($resultBytes, $version, $ecLevel, $hints);
}
/**
* <p>Given data and error-correction codewords received, possibly corrupted by errors, attempts to
* correct the errors in-place using Reed-Solomon error correction.</p>
*
* @param codewordBytes data and error correction codewords
* @param numDataCodewords number of codewords that are data bytes
* @throws ChecksumException if error correction fails
*/
private function correctErrors(&$codewordBytes, $numDataCodewords){
$numCodewords = count($codewordBytes);
// First read into an array of ints
$codewordsInts =fill_array(0,$numCodewords,0);
for ($i = 0; $i < $numCodewords; $i++) {
$codewordsInts[$i] = $codewordBytes[$i] & 0xFF;
}
$numECCodewords = count($codewordBytes)- $numDataCodewords;
try {
$this->rsDecoder->decode($codewordsInts, $numECCodewords);
} catch (ReedSolomonException $ignored) {
throw ChecksumException::getChecksumInstance();
}
// Copy back into array of bytes -- only need to worry about the bytes that were data
// We don't care about errors in the error-correction codewords
for ($i = 0; $i < $numDataCodewords; $i++) {
$codewordBytes[$i] = $codewordsInts[$i];
}
}
// Decode the contents of that stream of bytes
return DecodedBitStreamParser::decode($resultBytes, $version, $ecLevel, $hints);
}
/**
* <p>Given data and error-correction codewords received, possibly corrupted by errors, attempts to
* correct the errors in-place using Reed-Solomon error correction.</p>
*
* @param data $codewordBytes and error correction codewords
* @param number $numDataCodewords of codewords that are data bytes
*
* @throws ChecksumException if error correction fails
*/
private function correctErrors(&$codewordBytes, $numDataCodewords)
{
$numCodewords = is_countable($codewordBytes) ? count($codewordBytes) : 0;
// First read into an array of ints
$codewordsInts = fill_array(0, $numCodewords, 0);
for ($i = 0; $i < $numCodewords; $i++) {
$codewordsInts[$i] = $codewordBytes[$i] & 0xFF;
}
$numECCodewords = (is_countable($codewordBytes) ? count($codewordBytes) : 0) - $numDataCodewords;
try {
$this->rsDecoder->decode($codewordsInts, $numECCodewords);
} catch (ReedSolomonException) {
throw ChecksumException::getChecksumInstance();
}
// Copy back into array of bytes -- only need to worry about the bytes that were data
// We don't care about errors in the error-correction codewords
for ($i = 0; $i < $numDataCodewords; $i++) {
$codewordBytes[$i] = $codewordsInts[$i];
}
}
}
@@ -23,64 +23,68 @@ namespace Zxing\Qrcode\Decoder;
*
* @author Sean Owen
*/
class ErrorCorrectionLevel {
class ErrorCorrectionLevel
{
/**
* @var \Zxing\Qrcode\Decoder\ErrorCorrectionLevel[]|null
*/
private static ?array $FOR_BITS = null;
public function __construct(private $bits, private $ordinal = 0)
{
}
public static function Init(): void
{
self::$FOR_BITS = [
private static $FOR_BITS;
new ErrorCorrectionLevel(0x00, 1), //M
new ErrorCorrectionLevel(0x01, 0), //L
new ErrorCorrectionLevel(0x02, 3), //H
new ErrorCorrectionLevel(0x03, 2), //Q
];
}
/** L = ~7% correction */
// self::$L = new ErrorCorrectionLevel(0x01);
/** M = ~15% correction */
//self::$M = new ErrorCorrectionLevel(0x00);
/** Q = ~25% correction */
//self::$Q = new ErrorCorrectionLevel(0x03);
/** H = ~30% correction */
//self::$H = new ErrorCorrectionLevel(0x02);
/**
* @param int $bits containing the two bits encoding a QR Code's error correction level
*
* @return ErrorCorrectionLevel representing the encoded error correction level
*/
public static function forBits($bits)
{
if ($bits < 0 || $bits >= (is_countable(self::$FOR_BITS) ? count(self::$FOR_BITS) : 0)) {
throw new \InvalidArgumentException();
}
$level = self::$FOR_BITS[$bits];
// $lev = self::$$bit;
return $level;
}
private $bits;
private $ordinal;
function __construct($bits,$ordinal=0) {
$this->bits = $bits;
$this->ordinal = $ordinal;
}
public static function Init(){
self::$FOR_BITS = array(
new ErrorCorrectionLevel(0x00,1), //M
new ErrorCorrectionLevel(0x01,0), //L
new ErrorCorrectionLevel(0x02,3), //H
new ErrorCorrectionLevel(0x03,2), //Q
);
}
/** L = ~7% correction */
// self::$L = new ErrorCorrectionLevel(0x01);
/** M = ~15% correction */
//self::$M = new ErrorCorrectionLevel(0x00);
/** Q = ~25% correction */
//self::$Q = new ErrorCorrectionLevel(0x03);
/** H = ~30% correction */
//self::$H = new ErrorCorrectionLevel(0x02);
public function getBits() {
return $this->bits;
}
public function toString() {
return $this->bits;
}
public function getOrdinal() {
return $this->ordinal;
}
/**
* @param bits int containing the two bits encoding a QR Code's error correction level
* @return ErrorCorrectionLevel representing the encoded error correction level
*/
public static function forBits($bits) {
if ($bits < 0 || $bits >= count(self::$FOR_BITS)) {
throw new \InvalidArgumentException();
}
$level = self::$FOR_BITS[$bits];
// $lev = self::$$bit;
return $level;
}
public function getBits()
{
return $this->bits;
}
public function toString()
{
return $this->bits;
}
public function getOrdinal()
{
return $this->ordinal;
}
}
ErrorCorrectionLevel::Init();
@@ -22,158 +22,172 @@ namespace Zxing\Qrcode\Decoder;
* error correction level.</p>
*
* @author Sean Owen
* @see DataMask
* @see ErrorCorrectionLevel
* @see DataMask
* @see ErrorCorrectionLevel
*/
final class FormatInformation {
final class FormatInformation
{
public static $FORMAT_INFO_MASK_QR;
public static $FORMAT_INFO_MASK_QR;
/**
* See ISO 18004:2006, Annex C, Table C.1
*/
public static $FORMAT_INFO_DECODE_LOOKUP;
/**
* Offset i holds the number of 1 bits in the binary representation of i
* @var int[]|null
*/
private static ?array $BITS_SET_IN_HALF_BYTE = null;
/**
* See ISO 18004:2006, Annex C, Table C.1
*/
public static $FORMAT_INFO_DECODE_LOOKUP;
/**
* Offset i holds the number of 1 bits in the binary representation of i
*/
private static $BITS_SET_IN_HALF_BYTE;
private readonly \Zxing\Qrcode\Decoder\ErrorCorrectionLevel $errorCorrectionLevel;
private readonly int $dataMask;
private $errorCorrectionLevel;
private $dataMask;
private function __construct($formatInfo)
{
// Bits 3,4
$this->errorCorrectionLevel = ErrorCorrectionLevel::forBits(($formatInfo >> 3) & 0x03);
// Bottom 3 bits
$this->dataMask = ($formatInfo & 0x07);//(byte)
}
public static function Init(){
public static function Init(): void
{
self::$FORMAT_INFO_MASK_QR = 0x5412;
self::$BITS_SET_IN_HALF_BYTE = [0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4];
self::$FORMAT_INFO_DECODE_LOOKUP = [
[0x5412, 0x00],
[0x5125, 0x01],
[0x5E7C, 0x02],
[0x5B4B, 0x03],
[0x45F9, 0x04],
[0x40CE, 0x05],
[0x4F97, 0x06],
[0x4AA0, 0x07],
[0x77C4, 0x08],
[0x72F3, 0x09],
[0x7DAA, 0x0A],
[0x789D, 0x0B],
[0x662F, 0x0C],
[0x6318, 0x0D],
[0x6C41, 0x0E],
[0x6976, 0x0F],
[0x1689, 0x10],
[0x13BE, 0x11],
[0x1CE7, 0x12],
[0x19D0, 0x13],
[0x0762, 0x14],
[0x0255, 0x15],
[0x0D0C, 0x16],
[0x083B, 0x17],
[0x355F, 0x18],
[0x3068, 0x19],
[0x3F31, 0x1A],
[0x3A06, 0x1B],
[0x24B4, 0x1C],
[0x2183, 0x1D],
[0x2EDA, 0x1E],
[0x2BED, 0x1F],
];
}
self::$FORMAT_INFO_MASK_QR= 0x5412;
self::$BITS_SET_IN_HALF_BYTE = array(0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4);
self::$FORMAT_INFO_DECODE_LOOKUP = array(
array(0x5412, 0x00),
array (0x5125, 0x01),
array(0x5E7C, 0x02),
array(0x5B4B, 0x03),
array(0x45F9, 0x04),
array(0x40CE, 0x05),
array(0x4F97, 0x06),
array(0x4AA0, 0x07),
array(0x77C4, 0x08),
array(0x72F3, 0x09),
array(0x7DAA, 0x0A),
array(0x789D, 0x0B),
array(0x662F, 0x0C),
array(0x6318, 0x0D),
array(0x6C41, 0x0E),
array(0x6976, 0x0F),
array(0x1689, 0x10),
array(0x13BE, 0x11),
array(0x1CE7, 0x12),
array(0x19D0, 0x13),
array(0x0762, 0x14),
array(0x0255, 0x15),
array(0x0D0C, 0x16),
array(0x083B, 0x17),
array(0x355F, 0x18),
array(0x3068, 0x19),
array(0x3F31, 0x1A),
array(0x3A06, 0x1B),
array(0x24B4, 0x1C),
array(0x2183, 0x1D),
array(0x2EDA, 0x1E),
array(0x2BED, 0x1F),
);
/**
* @param $maskedFormatInfo1 ; format info indicator, with mask still applied
* @param $maskedFormatInfo2 ; second copy of same info; both are checked at the same time
* to establish best match
*
* @return information about the format it specifies, or {@code null}
* if doesn't seem to match any known pattern
*/
public static function decodeFormatInformation($maskedFormatInfo1, $maskedFormatInfo2)
{
$formatInfo = self::doDecodeFormatInformation($maskedFormatInfo1, $maskedFormatInfo2);
if ($formatInfo != null) {
return $formatInfo;
}
// Should return null, but, some QR codes apparently
// do not mask this info. Try again by actually masking the pattern
// first
return self::doDecodeFormatInformation(
$maskedFormatInfo1 ^ self::$FORMAT_INFO_MASK_QR,
$maskedFormatInfo2 ^ self::$FORMAT_INFO_MASK_QR
);
}
}
private function __construct($formatInfo) {
// Bits 3,4
$this->errorCorrectionLevel = ErrorCorrectionLevel::forBits(($formatInfo >> 3) & 0x03);
// Bottom 3 bits
$this->dataMask = ($formatInfo & 0x07);//(byte)
}
private static function doDecodeFormatInformation($maskedFormatInfo1, $maskedFormatInfo2)
{
// Find the int in FORMAT_INFO_DECODE_LOOKUP with fewest bits differing
$bestDifference = PHP_INT_MAX;
$bestFormatInfo = 0;
foreach (self::$FORMAT_INFO_DECODE_LOOKUP as $decodeInfo) {
$targetInfo = $decodeInfo[0];
if ($targetInfo == $maskedFormatInfo1 || $targetInfo == $maskedFormatInfo2) {
// Found an exact match
return new FormatInformation($decodeInfo[1]);
}
$bitsDifference = self::numBitsDiffering($maskedFormatInfo1, $targetInfo);
if ($bitsDifference < $bestDifference) {
$bestFormatInfo = $decodeInfo[1];
$bestDifference = $bitsDifference;
}
if ($maskedFormatInfo1 != $maskedFormatInfo2) {
// also try the other option
$bitsDifference = self::numBitsDiffering($maskedFormatInfo2, $targetInfo);
if ($bitsDifference < $bestDifference) {
$bestFormatInfo = $decodeInfo[1];
$bestDifference = $bitsDifference;
}
}
}
// 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);
}
static function numBitsDiffering($a, $b) {
$a ^= $b; // a now has a 1 bit exactly where its bit differs with b's
// Count bits set quickly with a series of lookups:
return self::$BITS_SET_IN_HALF_BYTE[$a & 0x0F] +
self::$BITS_SET_IN_HALF_BYTE[intval(uRShift($a, 4) & 0x0F)] +
self::$BITS_SET_IN_HALF_BYTE[(uRShift($a ,8) & 0x0F)] +
self::$BITS_SET_IN_HALF_BYTE[(uRShift($a , 12) & 0x0F)] +
self::$BITS_SET_IN_HALF_BYTE[(uRShift($a, 16) & 0x0F)] +
self::$BITS_SET_IN_HALF_BYTE[(uRShift($a , 20) & 0x0F)] +
self::$BITS_SET_IN_HALF_BYTE[(uRShift($a, 24) & 0x0F)] +
self::$BITS_SET_IN_HALF_BYTE[(uRShift($a ,28) & 0x0F)];
}
return null;
}
/**
* @param maskedFormatInfo1; format info indicator, with mask still applied
* @param maskedFormatInfo2; second copy of same info; both are checked at the same time
* to establish best match
* @return information about the format it specifies, or {@code null}
* if doesn't seem to match any known pattern
*/
static function decodeFormatInformation($maskedFormatInfo1, $maskedFormatInfo2) {
$formatInfo = self::doDecodeFormatInformation($maskedFormatInfo1, $maskedFormatInfo2);
if ($formatInfo != null) {
return $formatInfo;
}
// Should return null, but, some QR codes apparently
// do not mask this info. Try again by actually masking the pattern
// first
return self::doDecodeFormatInformation($maskedFormatInfo1 ^ self::$FORMAT_INFO_MASK_QR,
$maskedFormatInfo2 ^ self::$FORMAT_INFO_MASK_QR);
}
public static function numBitsDiffering($a, $b)
{
$a ^= $b; // a now has a 1 bit exactly where its bit differs with b's
// Count bits set quickly with a series of lookups:
return self::$BITS_SET_IN_HALF_BYTE[$a & 0x0F] +
self::$BITS_SET_IN_HALF_BYTE[(int)(uRShift($a, 4) & 0x0F)] +
self::$BITS_SET_IN_HALF_BYTE[(uRShift($a, 8) & 0x0F)] +
self::$BITS_SET_IN_HALF_BYTE[(uRShift($a, 12) & 0x0F)] +
self::$BITS_SET_IN_HALF_BYTE[(uRShift($a, 16) & 0x0F)] +
self::$BITS_SET_IN_HALF_BYTE[(uRShift($a, 20) & 0x0F)] +
self::$BITS_SET_IN_HALF_BYTE[(uRShift($a, 24) & 0x0F)] +
self::$BITS_SET_IN_HALF_BYTE[(uRShift($a, 28) & 0x0F)];
}
private static function doDecodeFormatInformation($maskedFormatInfo1, $maskedFormatInfo2) {
// Find the int in FORMAT_INFO_DECODE_LOOKUP with fewest bits differing
$bestDifference = PHP_INT_MAX;
$bestFormatInfo = 0;
foreach (self::$FORMAT_INFO_DECODE_LOOKUP as $decodeInfo ) {
$targetInfo = $decodeInfo[0];
if ($targetInfo == $maskedFormatInfo1 || $targetInfo == $maskedFormatInfo2) {
// Found an exact match
return new FormatInformation($decodeInfo[1]);
}
$bitsDifference = self::numBitsDiffering($maskedFormatInfo1, $targetInfo);
if ($bitsDifference < $bestDifference) {
$bestFormatInfo = $decodeInfo[1];
$bestDifference = $bitsDifference;
}
if ($maskedFormatInfo1 != $maskedFormatInfo2) {
// also try the other option
$bitsDifference = self::numBitsDiffering($maskedFormatInfo2, $targetInfo);
if ($bitsDifference < $bestDifference) {
$bestFormatInfo = $decodeInfo[1];
$bestDifference = $bitsDifference;
}
}
}
// 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 null;
}
public function getErrorCorrectionLevel()
{
return $this->errorCorrectionLevel;
}
function getErrorCorrectionLevel() {
return $this->errorCorrectionLevel;
}
public function getDataMask()
{
return $this->dataMask;
}
function getDataMask() {
return $this->dataMask;
}
//@Override
public function hashCode()
{
return ($this->errorCorrectionLevel->ordinal() << 3) | (int)($this->dataMask);
}
//@Override
public function hashCode() {
return ($this->errorCorrectionLevel->ordinal() << 3) | intval($this->dataMask);
}
//@Override
public function equals($o) {
if (!($o instanceof FormatInformation)) {
return false;
}
$other =$o;
return $this->errorCorrectionLevel == $other->errorCorrectionLevel &&
$this->dataMask == $other->dataMask;
}
//@Override
public function equals($o)
{
if (!($o instanceof FormatInformation)) {
return false;
}
$other = $o;
return $this->errorCorrectionLevel == $other->errorCorrectionLevel &&
$this->dataMask == $other->dataMask;
}
}
FormatInformation::Init();
@@ -23,96 +23,86 @@ namespace Zxing\Qrcode\Decoder;
*
* @author Sean Owen
*/
class Mode {
static $TERMINATOR;
static $NUMERIC;
static $ALPHANUMERIC;
static $STRUCTURED_APPEND;
static $BYTE;
static $ECI;
static $KANJI;
static $FNC1_FIRST_POSITION;
static $FNC1_SECOND_POSITION;
static $HANZI;
class Mode
{
public static $TERMINATOR;
public static $NUMERIC;
public static $ALPHANUMERIC;
public static $STRUCTURED_APPEND;
public static $BYTE;
public static $ECI;
public static $KANJI;
public static $FNC1_FIRST_POSITION;
public static $FNC1_SECOND_POSITION;
public static $HANZI;
public function __construct(private $characterCountBitsForVersions, private $bits)
{
}
private $characterCountBitsForVersions;
private $bits;
public static function Init(): void
{
self::$TERMINATOR = new Mode([0, 0, 0], 0x00); // Not really a mode...
self::$NUMERIC = new Mode([10, 12, 14], 0x01);
self::$ALPHANUMERIC = new Mode([9, 11, 13], 0x02);
self::$STRUCTURED_APPEND = new Mode([0, 0, 0], 0x03); // Not supported
self::$BYTE = new Mode([8, 16, 16], 0x04);
self::$ECI = new Mode([0, 0, 0], 0x07); // character counts don't apply
self::$KANJI = new Mode([8, 10, 12], 0x08);
self::$FNC1_FIRST_POSITION = new Mode([0, 0, 0], 0x05);
self::$FNC1_SECOND_POSITION = new Mode([0, 0, 0], 0x09);
/** See GBT 18284-2000; "Hanzi" is a transliteration of this mode name. */
self::$HANZI = new Mode([8, 10, 12], 0x0D);
}
function __construct($characterCountBitsForVersions, $bits) {
$this->characterCountBitsForVersions = $characterCountBitsForVersions;
$this->bits = $bits;
}
static function Init()
{
/**
* @param four $bits bits encoding a QR Code data mode
*
* @return Mode encoded by these bits
* @throws InvalidArgumentException if bits do not correspond to a known mode
*/
public static function forBits($bits)
{
return match ($bits) {
0x0 => self::$TERMINATOR,
0x1 => self::$NUMERIC,
0x2 => self::$ALPHANUMERIC,
0x3 => self::$STRUCTURED_APPEND,
0x4 => self::$BYTE,
0x5 => self::$FNC1_FIRST_POSITION,
0x7 => self::$ECI,
0x8 => self::$KANJI,
0x9 => self::$FNC1_SECOND_POSITION,
0xD => self::$HANZI,
default => throw new \InvalidArgumentException(),
};
}
/**
* @param version $version in question
*
* @return number of bits used, in this QR Code symbol {@link Version}, to encode the
* count of characters that will follow encoded in this Mode
*/
public function getCharacterCountBits($version)
{
$number = $version->getVersionNumber();
$offset = 0;
if ($number <= 9) {
$offset = 0;
} elseif ($number <= 26) {
$offset = 1;
} else {
$offset = 2;
}
self::$TERMINATOR = new Mode(array(0, 0, 0), 0x00); // Not really a mode...
self::$NUMERIC = new Mode(array(10, 12, 14), 0x01);
self::$ALPHANUMERIC = new Mode(array(9, 11, 13), 0x02);
self::$STRUCTURED_APPEND = new Mode(array(0, 0, 0), 0x03); // Not supported
self::$BYTE = new Mode(array(8, 16, 16), 0x04);
self::$ECI = new Mode(array(0, 0, 0), 0x07); // character counts don't apply
self::$KANJI = new Mode(array(8, 10, 12), 0x08);
self::$FNC1_FIRST_POSITION = new Mode(array(0, 0, 0), 0x05);
self::$FNC1_SECOND_POSITION =new Mode(array(0, 0, 0), 0x09);
/** See GBT 18284-2000; "Hanzi" is a transliteration of this mode name. */
self::$HANZI = new Mode(array(8, 10, 12), 0x0D);
}
/**
* @param bits four bits encoding a QR Code data mode
* @return Mode encoded by these bits
* @throws IllegalArgumentException if bits do not correspond to a known mode
*/
public static function forBits($bits) {
switch ($bits) {
case 0x0:
return self::$TERMINATOR;
case 0x1:
return self::$NUMERIC;
case 0x2:
return self::$ALPHANUMERIC;
case 0x3:
return self::$STRUCTURED_APPEND;
case 0x4:
return self::$BYTE;
case 0x5:
return self::$FNC1_FIRST_POSITION;
case 0x7:
return self::$ECI;
case 0x8:
return self::$KANJI;
case 0x9:
return self::$FNC1_SECOND_POSITION;
case 0xD:
// 0xD is defined in GBT 18284-2000, may not be supported in foreign country
return self::$HANZI;
default:
throw new \InvalidArgumentException();
}
}
/**
* @param version version in question
* @return number of bits used, in this QR Code symbol {@link Version}, to encode the
* count of characters that will follow encoded in this Mode
*/
public function getCharacterCountBits($version) {
$number = $version->getVersionNumber();
$offset = 0;
if ($number <= 9) {
$offset = 0;
} else if ($number <= 26) {
$offset = 1;
} else {
$offset = 2;
}
return $this->characterCountBitsForVersions[$offset];
}
public function getBits() {
return $this->bits;
}
return $this->characterCountBitsForVersions[$offset];
}
public function getBits()
{
return $this->bits;
}
}
Mode::Init();
Mode::Init();
File diff suppressed because it is too large Load Diff
@@ -25,36 +25,38 @@ use Zxing\ResultPoint;
*
* @author Sean Owen
*/
final class AlignmentPattern extends ResultPoint {
final class AlignmentPattern extends ResultPoint
{
public function __construct($posX, $posY, private $estimatedModuleSize)
{
parent::__construct($posX, $posY);
}
private $estimatedModuleSize;
/**
* <p>Determines if this alignment pattern "about equals" an alignment pattern at the stated
* position and size -- meaning, it is at nearly the same center with nearly the same size.</p>
*/
public function aboutEquals($moduleSize, $i, $j)
{
if (abs($i - $this->getY()) <= $moduleSize && abs($j - $this->getX()) <= $moduleSize) {
$moduleSizeDiff = abs($moduleSize - $this->estimatedModuleSize);
function __construct($posX, $posY, $estimatedModuleSize) {
parent::__construct($posX, $posY);
$this->estimatedModuleSize = $estimatedModuleSize;
return $moduleSizeDiff <= 1.0 || $moduleSizeDiff <= $this->estimatedModuleSize;
}
return false;
}
/**
* Combines this object's current estimate of a finder pattern position and module size
* with a new estimate. It returns a new {@code FinderPattern} containing an average of the two.
*/
public function combineEstimate($i, $j, $newModuleSize): \Zxing\Qrcode\Detector\AlignmentPattern
{
$combinedX = ($this->getX() + $j) / 2.0;
$combinedY = ($this->getY() + $i) / 2.0;
$combinedModuleSize = ($this->estimatedModuleSize + $newModuleSize) / 2.0;
return new AlignmentPattern($combinedX, $combinedY, $combinedModuleSize);
}
}
/**
* <p>Determines if this alignment pattern "about equals" an alignment pattern at the stated
* position and size -- meaning, it is at nearly the same center with nearly the same size.</p>
*/
function aboutEquals($moduleSize, $i, $j) {
if (abs($i - $this->getY()) <= $moduleSize && abs($j - $this->getX()) <= $moduleSize) {
$moduleSizeDiff = abs($moduleSize - $this->estimatedModuleSize);
return $moduleSizeDiff <= 1.0 || $moduleSizeDiff <= $this->estimatedModuleSize;
}
return false;
}
/**
* Combines this object's current estimate of a finder pattern position and module size
* with a new estimate. It returns a new {@code FinderPattern} containing an average of the two.
*/
function combineEstimate($i, $j, $newModuleSize) {
$combinedX = ($this->getX() + $j) / 2.0;
$combinedY = ($this->getY() + $i) / 2.0;
$combinedModuleSize = ($this->estimatedModuleSize + $newModuleSize) / 2.0;
return new AlignmentPattern($combinedX, $combinedY, $combinedModuleSize);
}
}
@@ -18,9 +18,6 @@
namespace Zxing\Qrcode\Detector;
use Zxing\NotFoundException;
use Zxing\ResultPointCallback;
use Zxing\Common\BitMatrix;
/**
* <p>This class attempts to find alignment patterns in a QR Code. Alignment patterns look like finder
@@ -36,242 +33,233 @@ use Zxing\Common\BitMatrix;
*
* @author Sean Owen
*/
final class AlignmentPatternFinder {
final class AlignmentPatternFinder
{
private array $possibleCenters = [];
private array $crossCheckStateCount = [];
private $image;
private $possibleCenters;
private $startX;
private $startY;
private $width;
private $height;
private $moduleSize;
private $crossCheckStateCount;
private $resultPointCallback;
/**
* <p>Creates a finder that will look in a portion of the whole image.</p>
*
* @param \Imagick image $image to search
* @param int left $startX column from which to start searching
* @param int top $startY row from which to start searching
* @param float width $width of region to search
* @param float height $height of region to search
* @param float estimated $moduleSize module size so far
*/
public function __construct(private $image, private $startX, private $startY, private $width, private $height, private $moduleSize, private $resultPointCallback)
{
}
/**
* <p>Creates a finder that will look in a portion of the whole image.</p>
*
* @param image image to search
* @param startX left column from which to start searching
* @param startY top row from which to start searching
* @param width width of region to search
* @param height height of region to search
* @param moduleSize estimated module size so far
*/
function __construct($image,
$startX,
$startY,
$width,
$height,
$moduleSize,
$resultPointCallback) {
$this->image = $image;
$this->possibleCenters = array();
$this->startX = $startX;
$this->startY = $startY;
$this->width = $width;
$this->height = $height;
$this->moduleSize = $moduleSize;
$this->crossCheckStateCount = array();
$this->resultPointCallback = $resultPointCallback;
}
/**
* <p>This method attempts to find the bottom-right alignment pattern in the image. It is a bit messy since
* it's pretty performance-critical and so is written to be fast foremost.</p>
*
* @return {@link AlignmentPattern} if found
* @throws NotFoundException if not found
*/
public function find()
{
$startX = $this->startX;
$height = $this->height;
$maxJ = $startX + $this->width;
$middleI = $this->startY + ($height / 2);
// We are looking for black/white/black modules in 1:1:1 ratio;
// this tracks the number of black/white/black modules seen so far
$stateCount = [];
for ($iGen = 0; $iGen < $height; $iGen++) {
// Search from middle outwards
$i = $middleI + (($iGen & 0x01) == 0 ? ($iGen + 1) / 2 : -(($iGen + 1) / 2));
$i = (int)($i);
$stateCount[0] = 0;
$stateCount[1] = 0;
$stateCount[2] = 0;
$j = $startX;
// 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->image->get($j, $i)) {
$j++;
}
$currentState = 0;
while ($j < $maxJ) {
if ($this->image->get($j, $i)) {
// Black pixel
if ($currentState == 1) { // Counting black pixels
$stateCount[$currentState]++;
} else { // Counting white pixels
if ($currentState == 2) { // A winner?
if ($this->foundPatternCross($stateCount)) { // Yes
$confirmed = $this->handlePossibleCenter($stateCount, $i, $j);
if ($confirmed != null) {
return $confirmed;
}
}
$stateCount[0] = $stateCount[2];
$stateCount[1] = 1;
$stateCount[2] = 0;
$currentState = 1;
} else {
$stateCount[++$currentState]++;
}
}
} else { // White pixel
if ($currentState == 1) { // Counting black pixels
$currentState++;
}
$stateCount[$currentState]++;
}
$j++;
}
if ($this->foundPatternCross($stateCount)) {
$confirmed = $this->handlePossibleCenter($stateCount, $i, $maxJ);
if ($confirmed != null) {
return $confirmed;
}
}
}
/**
* <p>This method attempts to find the bottom-right alignment pattern in the image. It is a bit messy since
* it's pretty performance-critical and so is written to be fast foremost.</p>
*
* @return {@link AlignmentPattern} if found
* @throws NotFoundException if not found
*/
function find() {
$startX = $this->startX;
$height = $this->height;
$maxJ = $startX + $this->width;
$middleI = $this->startY + ($height / 2);
// We are looking for black/white/black modules in 1:1:1 ratio;
// this tracks the number of black/white/black modules seen so far
$stateCount = array();
for ($iGen = 0; $iGen < $height; $iGen++) {
// Search from middle outwards
$i = $middleI + (($iGen & 0x01) == 0 ? ($iGen + 1) / 2 : -(($iGen + 1) / 2));
$i = intval($i);
$stateCount[0] = 0;
$stateCount[1] = 0;
$stateCount[2] = 0;
$j = $startX;
// 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->image->get($j, $i)) {
$j++;
}
$currentState = 0;
while ($j < $maxJ) {
if ($this->image->get($j, $i)) {
// Black pixel
if ($currentState == 1) { // Counting black pixels
$stateCount[$currentState]++;
} else { // Counting white pixels
if ($currentState == 2) { // A winner?
if ($this->foundPatternCross($stateCount)) { // Yes
$confirmed = $this->handlePossibleCenter($stateCount, $i, $j);
if ($confirmed != null) {
return $confirmed;
}
}
$stateCount[0] = $stateCount[2];
$stateCount[1] = 1;
$stateCount[2] = 0;
$currentState = 1;
} else {
$stateCount[++$currentState]++;
}
}
} else { // White pixel
if ($currentState == 1) { // Counting black pixels
$currentState++;
}
$stateCount[$currentState]++;
}
$j++;
}
if ($this->foundPatternCross($stateCount)) {
$confirmed = $this->handlePossibleCenter($stateCount, $i, $maxJ);
if ($confirmed != null) {
return $confirmed;
}
}
// Hmm, nothing we saw was observed and confirmed twice. If we had
// any guess at all, return it.
if (count($this->possibleCenters)) {
return $this->possibleCenters[0];
}
}
throw NotFoundException::getNotFoundInstance();
}
// Hmm, nothing we saw was observed and confirmed twice. If we had
// any guess at all, return it.
if (count($this->possibleCenters)) {
return $this->possibleCenters[0];
}
/**
* @param count $stateCount of black/white/black pixels just read
*
* @return true iff the proportions of the counts is close enough to the 1/1/1 ratios
* used by alignment patterns to be considered a match
*/
private function foundPatternCross($stateCount)
{
$moduleSize = $this->moduleSize;
$maxVariance = $moduleSize / 2.0;
for ($i = 0; $i < 3; $i++) {
if (abs($moduleSize - $stateCount[$i]) >= $maxVariance) {
return false;
}
}
throw NotFoundException::getNotFoundInstance();
}
return true;
}
/**
* Given a count of black/white/black pixels just seen and an end position,
* figures the location of the center of this black/white/black run.
*/
private static function centerFromEnd($stateCount, $end) {
return (float) ($end - $stateCount[2]) - $stateCount[1] / 2.0;
}
/**
* <p>This is called when a horizontal scan finds a possible alignment pattern. It will
* cross check with a vertical scan, and if successful, will see if this pattern had been
* found on a previous horizontal scan. If so, we consider it confirmed and conclude we have
* found the alignment pattern.</p>
*
* @param reading $stateCount state module counts from horizontal scan
* @param row $i where alignment pattern may be found
* @param end $j of possible alignment pattern in row
*
* @return {@link AlignmentPattern} if we have found the same pattern twice, or null if not
*/
private function handlePossibleCenter($stateCount, $i, $j)
{
$stateCountTotal = $stateCount[0] + $stateCount[1] + $stateCount[2];
$centerJ = self::centerFromEnd($stateCount, $j);
$centerI = $this->crossCheckVertical($i, (int)$centerJ, 2 * $stateCount[1], $stateCountTotal);
if (!is_nan($centerI)) {
$estimatedModuleSize = (float)($stateCount[0] + $stateCount[1] + $stateCount[2]) / 3.0;
foreach ($this->possibleCenters as $center) {
// Look for about the same center and module size:
if ($center->aboutEquals($estimatedModuleSize, $centerI, $centerJ)) {
return $center->combineEstimate($centerI, $centerJ, $estimatedModuleSize);
}
}
// Hadn't found this before; save it
$point = new AlignmentPattern($centerJ, $centerI, $estimatedModuleSize);
$this->possibleCenters[] = $point;
if ($this->resultPointCallback != null) {
$this->resultPointCallback->foundPossibleResultPoint($point);
}
}
/**
* @param stateCount count of black/white/black pixels just read
* @return true iff the proportions of the counts is close enough to the 1/1/1 ratios
* used by alignment patterns to be considered a match
*/
private function foundPatternCross($stateCount) {
$moduleSize = $this->moduleSize;
$maxVariance = $moduleSize / 2.0;
for ($i = 0; $i < 3; $i++) {
if (abs($moduleSize - $stateCount[$i]) >= $maxVariance) {
return false;
}
}
return true;
}
return null;
}
/**
* <p>After a horizontal scan finds a potential alignment pattern, this method
* "cross-checks" by scanning down vertically through the center of the possible
* alignment pattern to see if the same proportion is detected.</p>
*
* @param startI row where an alignment pattern was detected
* @param centerJ center of the section that appears to cross an alignment pattern
* @param maxCount maximum reasonable number of modules that should be
* observed in any reading state, based on the results of the horizontal scan
* @return vertical center of alignment pattern, or {@link Float#NaN} if not found
*/
private function crossCheckVertical($startI, $centerJ, $maxCount,
$originalStateCountTotal) {
$image = $this->image;
/**
* Given a count of black/white/black pixels just seen and an end position,
* figures the location of the center of this black/white/black run.
*/
private static function centerFromEnd($stateCount, $end)
{
return (float)($end - $stateCount[2]) - $stateCount[1] / 2.0;
}
$maxI = $image->getHeight();
$stateCount = $this->crossCheckStateCount;
$stateCount[0] = 0;
$stateCount[1] = 0;
$stateCount[2] = 0;
/**
* <p>After a horizontal scan finds a potential alignment pattern, this method
* "cross-checks" by scanning down vertically through the center of the possible
* alignment pattern to see if the same proportion is detected.</p>
*
* @param int row $startI where an alignment pattern was detected
* @param float center $centerJ of the section that appears to cross an alignment pattern
* @param int maximum $maxCount reasonable number of modules that should be
* observed in any reading state, based on the results of the horizontal scan
*
* @return float vertical center of alignment pattern, or {@link Float#NaN} if not found
*/
private function crossCheckVertical(
$startI,
$centerJ,
$maxCount,
$originalStateCountTotal
)
{
$image = $this->image;
// Start counting up from center
$i = $startI;
while ($i >= 0 && $image->get($centerJ, $i) && $stateCount[1] <= $maxCount) {
$stateCount[1]++;
$i--;
}
// If already too many modules in this state or ran off the edge:
if ($i < 0 || $stateCount[1] > $maxCount) {
return NAN;
}
while ($i >= 0 && !$image->get($centerJ, $i) && $stateCount[0] <= $maxCount) {
$stateCount[0]++;
$i--;
}
if ($stateCount[0] > $maxCount) {
return NAN;
}
$maxI = $image->getHeight();
$stateCount = $this->crossCheckStateCount;
$stateCount[0] = 0;
$stateCount[1] = 0;
$stateCount[2] = 0;
// Now also count down from center
$i = $startI + 1;
while ($i < $maxI && $image->get($centerJ, $i) && $stateCount[1] <= $maxCount) {
$stateCount[1]++;
$i++;
}
if ($i == $maxI || $stateCount[1] > $maxCount) {
return NAN;
}
while ($i < $maxI && !$image->get($centerJ, $i) && $stateCount[2] <= $maxCount) {
$stateCount[2]++;
$i++;
}
if ($stateCount[2] > $maxCount) {
return NAN;
}
// Start counting up from center
$i = $startI;
while ($i >= 0 && $image->get($centerJ, $i) && $stateCount[1] <= $maxCount) {
$stateCount[1]++;
$i--;
}
// If already too many modules in this state or ran off the edge:
if ($i < 0 || $stateCount[1] > $maxCount) {
return NAN;
}
while ($i >= 0 && !$image->get($centerJ, $i) && $stateCount[0] <= $maxCount) {
$stateCount[0]++;
$i--;
}
if ($stateCount[0] > $maxCount) {
return NAN;
}
$stateCountTotal = $stateCount[0] + $stateCount[1] + $stateCount[2];
if (5 * abs($stateCountTotal - $originalStateCountTotal) >= 2 * $originalStateCountTotal) {
return NAN;
}
// Now also count down from center
$i = $startI + 1;
while ($i < $maxI && $image->get($centerJ, $i) && $stateCount[1] <= $maxCount) {
$stateCount[1]++;
$i++;
}
if ($i == $maxI || $stateCount[1] > $maxCount) {
return NAN;
}
while ($i < $maxI && !$image->get($centerJ, $i) && $stateCount[2] <= $maxCount) {
$stateCount[2]++;
$i++;
}
if ($stateCount[2] > $maxCount) {
return NAN;
}
return $this->foundPatternCross($stateCount) ? $this->centerFromEnd($stateCount, $i) : NAN;
}
/**
* <p>This is called when a horizontal scan finds a possible alignment pattern. It will
* cross check with a vertical scan, and if successful, will see if this pattern had been
* found on a previous horizontal scan. If so, we consider it confirmed and conclude we have
* found the alignment pattern.</p>
*
* @param stateCount reading state module counts from horizontal scan
* @param i row where alignment pattern may be found
* @param j end of possible alignment pattern in row
* @return {@link AlignmentPattern} if we have found the same pattern twice, or null if not
*/
private function handlePossibleCenter($stateCount, $i, $j) {
$stateCountTotal = $stateCount[0] + $stateCount[1] + $stateCount[2];
$centerJ = $this->centerFromEnd($stateCount, $j);
$centerI = $this->crossCheckVertical($i, (int) $centerJ, 2 * $stateCount[1], $stateCountTotal);
if (!is_nan($centerI)) {
$estimatedModuleSize = (float) ($stateCount[0] + $stateCount[1] + $stateCount[2]) / 3.0;
foreach ($this->possibleCenters as $center) {
// Look for about the same center and module size:
if ($center->aboutEquals($estimatedModuleSize, $centerI, $centerJ)) {
return $center->combineEstimate($centerI, $centerJ, $estimatedModuleSize);
}
}
// Hadn't found this before; save it
$point = new AlignmentPattern($centerJ, $centerI, $estimatedModuleSize);
$this->possibleCenters[] = $point;
if ($this->resultPointCallback != null) {
$this->resultPointCallback->foundPossibleResultPoint($point);
}
}
return null;
}
$stateCountTotal = $stateCount[0] + $stateCount[1] + $stateCount[2];
if (5 * abs($stateCountTotal - $originalStateCountTotal) >= 2 * $originalStateCountTotal) {
return NAN;
}
return $this->foundPatternCross($stateCount) ? self::centerFromEnd($stateCount, $i) : NAN;
}
}
@@ -17,18 +17,16 @@
namespace Zxing\Qrcode\Detector;
use Zxing\DecodeHintType;
use Zxing\FormatException;
use Zxing\NotFoundException;
use Zxing\ResultPoint;
use Zxing\ResultPointCallback;
use Zxing\Common\BitMatrix;
use Zxing\Common\Detector\MathUtils;
use Zxing\Common\DetectorResult;
use Zxing\Common\GridSampler;
use Zxing\Common\PerspectiveTransform;
use Zxing\Common\Detector\MathUtils;
use Zxing\DecodeHintType;
use Zxing\FormatException;
use Zxing\NotFoundException;
use Zxing\Qrcode\Decoder\Version;
use Zxing\ResultPoint;
use Zxing\ResultPointCallback;
/**
* <p>Encapsulates logic that can detect a QR Code in an image, even if the QR Code
@@ -36,370 +34,395 @@ use Zxing\Qrcode\Decoder\Version;
*
* @author Sean Owen
*/
?>
<?php
class Detector {
class Detector
{
private $resultPointCallback;
private $image;
private $resultPointCallback;
public function __construct(private $image)
{
}
public function __construct($image) {
$this->image = $image;
}
/**
* <p>Detects a QR Code in an image.</p>
*
* @param array|null optional $hints hints to detector
*
* @return {@link DetectorResult} encapsulating results of detecting a QR Code
* @throws NotFoundException if QR Code cannot be found
* @throws FormatException if a QR Code cannot be decoded
*/
final public function detect($hints = null)
{/*Map<DecodeHintType,?>*/
protected final function getImage() {
return $this->image;
}
$resultPointCallback = $hints == null ? null :
$hints->get('NEED_RESULT_POINT_CALLBACK');
/* resultPointCallback = hints == null ? null :
(ResultPointCallback) hints.get(DecodeHintType.NEED_RESULT_POINT_CALLBACK);*/
$finder = new FinderPatternFinder($this->image, $resultPointCallback);
$info = $finder->find($hints);
protected final function getResultPointCallback() {
return $this->resultPointCallback;
}
return $this->processFinderPatternInfo($info);
}
/**
* <p>Detects a QR Code in an image.</p>
*
* @return {@link DetectorResult} encapsulating results of detecting a QR Code
* @throws NotFoundException if QR Code cannot be found
* @throws FormatException if a QR Code cannot be decoded
*/
final protected function processFinderPatternInfo($info): \Zxing\Common\DetectorResult
{
$topLeft = $info->getTopLeft();
$topRight = $info->getTopRight();
$bottomLeft = $info->getBottomLeft();
$moduleSize = (float)$this->calculateModuleSize($topLeft, $topRight, $bottomLeft);
if ($moduleSize < 1.0) {
throw NotFoundException::getNotFoundInstance();
}
$dimension = (int)self::computeDimension($topLeft, $topRight, $bottomLeft, $moduleSize);
$provisionalVersion = \Zxing\Qrcode\Decoder\Version::getProvisionalVersionForDimension($dimension);
$modulesBetweenFPCenters = $provisionalVersion->getDimensionForVersion() - 7;
/**
* <p>Detects a QR Code in an image.</p>
*
* @param hints optional hints to detector
* @return {@link DetectorResult} encapsulating results of detecting a QR Code
* @throws NotFoundException if QR Code cannot be found
* @throws FormatException if a QR Code cannot be decoded
*/
public final function detect($hints=null){/*Map<DecodeHintType,?>*/
$resultPointCallback = $hints == null ? null :
$hints->get('NEED_RESULT_POINT_CALLBACK');
/* resultPointCallback = hints == null ? null :
(ResultPointCallback) hints.get(DecodeHintType.NEED_RESULT_POINT_CALLBACK);*/
$finder = new FinderPatternFinder($this->image, $resultPointCallback);
$info = $finder->find($hints);
return $this->processFinderPatternInfo($info);
}
protected final function processFinderPatternInfo($info){
$topLeft = $info->getTopLeft();
$topRight = $info->getTopRight();
$bottomLeft = $info->getBottomLeft();
$moduleSize = (float) $this->calculateModuleSize($topLeft, $topRight, $bottomLeft);
if ($moduleSize < 1.0) {
throw NotFoundException::getNotFoundInstance();
}
$dimension =(int) $this->computeDimension($topLeft, $topRight, $bottomLeft, $moduleSize);
$provisionalVersion = Version::getProvisionalVersionForDimension($dimension);
$modulesBetweenFPCenters = $provisionalVersion->getDimensionForVersion() - 7;
$alignmentPattern = null;
// Anything above version 1 has an alignment pattern
if (count($provisionalVersion->getAlignmentPatternCenters())> 0) {
$alignmentPattern = null;
// Anything above version 1 has an alignment pattern
if ((is_countable($provisionalVersion->getAlignmentPatternCenters()) ? count($provisionalVersion->getAlignmentPatternCenters()) : 0) > 0) {
// Guess where a "bottom right" finder pattern would have been
$bottomRightX = $topRight->getX() - $topLeft->getX() + $bottomLeft->getX();
$bottomRightY = $topRight->getY() - $topLeft->getY() + $bottomLeft->getY();
$bottomRightX = $topRight->getX() - $topLeft->getX() + $bottomLeft->getX();
$bottomRightY = $topRight->getY() - $topLeft->getY() + $bottomLeft->getY();
// Estimate that alignment pattern is closer by 3 modules
// from "bottom right" to known top left location
$correctionToTopLeft = 1.0 - 3.0 / (float) $modulesBetweenFPCenters;
$estAlignmentX = (int) ($topLeft->getX() + $correctionToTopLeft * ($bottomRightX - $topLeft->getX()));
$estAlignmentY = (int) ($topLeft->getY() + $correctionToTopLeft * ($bottomRightY - $topLeft->getY()));
// Estimate that alignment pattern is closer by 3 modules
// from "bottom right" to known top left location
$correctionToTopLeft = 1.0 - 3.0 / (float)$modulesBetweenFPCenters;
$estAlignmentX = (int)($topLeft->getX() + $correctionToTopLeft * ($bottomRightX - $topLeft->getX()));
$estAlignmentY = (int)($topLeft->getY() + $correctionToTopLeft * ($bottomRightY - $topLeft->getY()));
// Kind of arbitrary -- expand search radius before giving up
for ($i = 4; $i <= 16; $i <<= 1) {//??????????
try {
$alignmentPattern = $this->findAlignmentInRegion($moduleSize,
$estAlignmentX,
$estAlignmentY,
(float) $i);
break;
} catch (NotFoundException $re) {
// try next round
}
}
// If we didn't find alignment pattern... well try anyway without it
}
// Kind of arbitrary -- expand search radius before giving up
for ($i = 4; $i <= 16; $i <<= 1) {//??????????
try {
$alignmentPattern = $this->findAlignmentInRegion(
$moduleSize,
$estAlignmentX,
$estAlignmentY,
(float)$i
);
break;
} catch (NotFoundException) {
// try next round
}
}
// If we didn't find alignment pattern... well try anyway without it
}
$transform =
$this->createTransform($topLeft, $topRight, $bottomLeft, $alignmentPattern, $dimension);
$transform = self::createTransform($topLeft, $topRight, $bottomLeft, $alignmentPattern, $dimension);
$bits = $this->sampleGrid($this->image, $transform, $dimension);
$bits = self::sampleGrid($this->image, $transform, $dimension);
$points = array();
if ($alignmentPattern == null) {
$points = array($bottomLeft, $topLeft, $topRight);
} else {
// die('$points = new ResultPoint[]{bottomLeft, topLeft, topRight, alignmentPattern};');
$points = array($bottomLeft, $topLeft, $topRight, $alignmentPattern);
}
return new DetectorResult($bits, $points);
}
$points = [];
if ($alignmentPattern == null) {
$points = [$bottomLeft, $topLeft, $topRight];
} else {
// die('$points = new ResultPoint[]{bottomLeft, topLeft, topRight, alignmentPattern};');
$points = [$bottomLeft, $topLeft, $topRight, $alignmentPattern];
}
private static function createTransform($topLeft,
$topRight,
$bottomLeft,
$alignmentPattern,
$dimension) {
$dimMinusThree = (float) $dimension - 3.5;
$bottomRightX = 0.0;
$bottomRightY = 0.0;
$sourceBottomRightX = 0.0;
$sourceBottomRightY = 0.0;
if ($alignmentPattern != null) {
$bottomRightX = $alignmentPattern->getX();
$bottomRightY = $alignmentPattern->getY();
$sourceBottomRightX = $dimMinusThree - 3.0;
$sourceBottomRightY = $sourceBottomRightX;
} else {
// Don't have an alignment pattern, just make up the bottom-right point
$bottomRightX = ($topRight->getX() - $topLeft->getX()) + $bottomLeft->getX();
$bottomRightY = ($topRight->getY() - $topLeft->getY()) + $bottomLeft->getY();
$sourceBottomRightX = $dimMinusThree;
$sourceBottomRightY = $dimMinusThree;
}
return new DetectorResult($bits, $points);
}
return PerspectiveTransform::quadrilateralToQuadrilateral(
3.5,
3.5,
$dimMinusThree,
3.5,
$sourceBottomRightX,
$sourceBottomRightY,
3.5,
$dimMinusThree,
$topLeft->getX(),
$topLeft->getY(),
$topRight->getX(),
$topRight->getY(),
$bottomRightX,
$bottomRightY,
$bottomLeft->getX(),
$bottomLeft->getY());
}
/**
* <p>Detects a QR Code in an image.</p>
*
* @return {@link DetectorResult} encapsulating results of detecting a QR Code
* @throws NotFoundException if QR Code cannot be found
* @throws FormatException if a QR Code cannot be decoded
*/
private static function sampleGrid($image, $transform,
$dimension) {
/**
* <p>Computes an average estimated module size based on estimated derived from the positions
* of the three finder patterns.</p>
*
* @param detected $topLeft top-left finder pattern center
* @param detected $topRight top-right finder pattern center
* @param detected $bottomLeft bottom-left finder pattern center
*
* @return estimated module size
*/
final protected function calculateModuleSize($topLeft, $topRight, $bottomLeft)
{
// Take the average
return ($this->calculateModuleSizeOneWay($topLeft, $topRight) +
$this->calculateModuleSizeOneWay($topLeft, $bottomLeft)) / 2.0;
}
$sampler = GridSampler::getInstance();
return $sampler->sampleGrid_($image, $dimension, $dimension, $transform);
}
/**
* <p>Estimates module size based on two finder patterns -- it uses
* {@link #sizeOfBlackWhiteBlackRunBothWays(int, int, int, int)} to figure the
* width of each, measuring along the axis between their centers.</p>
*/
private function calculateModuleSizeOneWay($pattern, $otherPattern)
{
$moduleSizeEst1 = $this->sizeOfBlackWhiteBlackRunBothWays(
$pattern->getX(),
(int)$pattern->getY(),
(int)$otherPattern->getX(),
(int)$otherPattern->getY()
);
$moduleSizeEst2 = $this->sizeOfBlackWhiteBlackRunBothWays(
(int)$otherPattern->getX(),
(int)$otherPattern->getY(),
(int)$pattern->getX(),
(int)$pattern->getY()
);
if (is_nan($moduleSizeEst1)) {
return $moduleSizeEst2 / 7.0;
}
if (is_nan($moduleSizeEst2)) {
return $moduleSizeEst1 / 7.0;
}
// Average them, and divide by 7 since we've counted the width of 3 black modules,
// and 1 white and 1 black module on either side. Ergo, divide sum by 14.
return ($moduleSizeEst1 + $moduleSizeEst2) / 14.0;
}
/**
* <p>Computes the dimension (number of modules on a size) of the QR Code based on the position
* of the finder patterns and estimated module size.</p>
*/
private static function computeDimension($topLeft,
$topRight,
$bottomLeft,
$moduleSize) {
$tltrCentersDimension = MathUtils::round(ResultPoint::distance($topLeft, $topRight) / $moduleSize);
$tlblCentersDimension = MathUtils::round(ResultPoint::distance($topLeft, $bottomLeft) / $moduleSize);
$dimension = (($tltrCentersDimension + $tlblCentersDimension) / 2) + 7;
switch ($dimension & 0x03) { // mod 4
case 0:
$dimension++;
break;
/**
* See {@link #sizeOfBlackWhiteBlackRun(int, int, int, int)}; computes the total width of
* a finder pattern by looking for a black-white-black run from the center in the direction
* of another po$(another finder pattern center), and in the opposite direction too.</p>
*/
private function sizeOfBlackWhiteBlackRunBothWays($fromX, $fromY, $toX, $toY)
{
$result = $this->sizeOfBlackWhiteBlackRun($fromX, $fromY, $toX, $toY);
// Now count other way -- don't run off image though of course
$scale = 1.0;
$otherToX = $fromX - ($toX - $fromX);
if ($otherToX < 0) {
$scale = (float)$fromX / (float)($fromX - $otherToX);
$otherToX = 0;
} elseif ($otherToX >= $this->image->getWidth()) {
$scale = (float)($this->image->getWidth() - 1 - $fromX) / (float)($otherToX - $fromX);
$otherToX = $this->image->getWidth() - 1;
}
$otherToY = (int)($fromY - ($toY - $fromY) * $scale);
$scale = 1.0;
if ($otherToY < 0) {
$scale = (float)$fromY / (float)($fromY - $otherToY);
$otherToY = 0;
} elseif ($otherToY >= $this->image->getHeight()) {
$scale = (float)($this->image->getHeight() - 1 - $fromY) / (float)($otherToY - $fromY);
$otherToY = $this->image->getHeight() - 1;
}
$otherToX = (int)($fromX + ($otherToX - $fromX) * $scale);
$result += $this->sizeOfBlackWhiteBlackRun($fromX, $fromY, $otherToX, $otherToY);
// Middle pixel is double-counted this way; subtract 1
return $result - 1.0;
}
/**
* <p>This method traces a line from a po$in the image, in the direction towards another point.
* It begins in a black region, and keeps going until it finds white, then black, then white again.
* It reports the distance from the start to this point.</p>
*
* <p>This is used when figuring out how wide a finder pattern is, when the finder pattern
* may be skewed or rotated.</p>
*/
private function sizeOfBlackWhiteBlackRun($fromX, $fromY, $toX, $toY)
{
// Mild variant of Bresenham's algorithm;
// see http://en.wikipedia.org/wiki/Bresenham's_line_algorithm
$steep = abs($toY - $fromY) > abs($toX - $fromX);
if ($steep) {
$temp = $fromX;
$fromX = $fromY;
$fromY = $temp;
$temp = $toX;
$toX = $toY;
$toY = $temp;
}
$dx = abs($toX - $fromX);
$dy = abs($toY - $fromY);
$error = -$dx / 2;
$xstep = $fromX < $toX ? 1 : -1;
$ystep = $fromY < $toY ? 1 : -1;
// In black pixels, looking for white, first or second time.
$state = 0;
// Loop up until x == toX, but not beyond
$xLimit = $toX + $xstep;
for ($x = $fromX, $y = $fromY; $x != $xLimit; $x += $xstep) {
$realX = $steep ? $y : $x;
$realY = $steep ? $x : $y;
// 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->image->get($realX, $realY)) {
if ($state == 2) {
return MathUtils::distance($x, $y, $fromX, $fromY);
}
$state++;
}
$error += $dy;
if ($error > 0) {
if ($y == $toY) {
break;
}
$y += $ystep;
$error -= $dx;
}
}
// Found black-white-black; give the benefit of the doubt that the next pixel outside the image
// is "white" so this last po$at (toX+xStep,toY) is the right ending. This is really a
// small approximation; (toX+xStep,toY+yStep) might be really correct. Ignore this.
if ($state == 2) {
return MathUtils::distance($toX + $xstep, $toY, $fromX, $fromY);
}
// else we didn't find even black-white-black; no estimate is really possible
return NAN;
}
/**
* <p>Computes the dimension (number of modules on a size) of the QR Code based on the position
* of the finder patterns and estimated module size.</p>
*/
private static function computeDimension(
$topLeft,
$topRight,
$bottomLeft,
$moduleSize
)
{
$tltrCentersDimension = MathUtils::round(ResultPoint::distance($topLeft, $topRight) / $moduleSize);
$tlblCentersDimension = MathUtils::round(ResultPoint::distance($topLeft, $bottomLeft) / $moduleSize);
$dimension = (($tltrCentersDimension + $tlblCentersDimension) / 2) + 7;
switch ($dimension & 0x03) { // mod 4
case 0:
$dimension++;
break;
// 1? do nothing
case 2:
$dimension--;
break;
case 3:
throw NotFoundException::getNotFoundInstance();
}
return $dimension;
}
case 2:
$dimension--;
break;
case 3:
throw NotFoundException::getNotFoundInstance();
}
/**
* <p>Computes an average estimated module size based on estimated derived from the positions
* of the three finder patterns.</p>
*
* @param topLeft detected top-left finder pattern center
* @param topRight detected top-right finder pattern center
* @param bottomLeft detected bottom-left finder pattern center
* @return estimated module size
*/
protected final function calculateModuleSize($topLeft,
$topRight,
$bottomLeft) {
// Take the average
return ($this->calculateModuleSizeOneWay($topLeft, $topRight) +
$this->calculateModuleSizeOneWay($topLeft, $bottomLeft)) / 2.0;
}
return $dimension;
}
/**
* <p>Estimates module size based on two finder patterns -- it uses
* {@link #sizeOfBlackWhiteBlackRunBothWays(int, int, int, int)} to figure the
* width of each, measuring along the axis between their centers.</p>
*/
private function calculateModuleSizeOneWay($pattern, $otherPattern) {
$moduleSizeEst1 = $this->sizeOfBlackWhiteBlackRunBothWays($pattern->getX(),
(int) $pattern->getY(),
(int) $otherPattern->getX(),
(int) $otherPattern->getY());
$moduleSizeEst2 = $this->sizeOfBlackWhiteBlackRunBothWays((int) $otherPattern->getX(),
(int) $otherPattern->getY(),
(int) $pattern->getX(),
(int) $pattern->getY());
if (is_nan($moduleSizeEst1)) {
return $moduleSizeEst2 / 7.0;
}
if (is_nan($moduleSizeEst2)) {
return $moduleSizeEst1 / 7.0;
}
// Average them, and divide by 7 since we've counted the width of 3 black modules,
// and 1 white and 1 black module on either side. Ergo, divide sum by 14.
return ($moduleSizeEst1 + $moduleSizeEst2) / 14.0;
}
/**
* <p>Attempts to locate an alignment pattern in a limited region of the image, which is
* guessed to contain it. This method uses {@link AlignmentPattern}.</p>
*
* @param estimated $overallEstModuleSize module size so far
* @param x $estAlignmentX coordinate of center of area probably containing alignment pattern
* @param y $estAlignmentY coordinate of above
* @param number $allowanceFactor of pixels in all directions to search from the center
*
* @return {@link AlignmentPattern} if found, or null otherwise
* @throws NotFoundException if an unexpected error occurs during detection
*/
final protected function findAlignmentInRegion(
$overallEstModuleSize,
$estAlignmentX,
$estAlignmentY,
$allowanceFactor
)
{
// Look for an alignment pattern (3 modules in size) around where it
// should be
$allowance = (int)($allowanceFactor * $overallEstModuleSize);
$alignmentAreaLeftX = max(0, $estAlignmentX - $allowance);
$alignmentAreaRightX = min($this->image->getWidth() - 1, $estAlignmentX + $allowance);
if ($alignmentAreaRightX - $alignmentAreaLeftX < $overallEstModuleSize * 3) {
throw NotFoundException::getNotFoundInstance();
}
/**
* See {@link #sizeOfBlackWhiteBlackRun(int, int, int, int)}; computes the total width of
* a finder pattern by looking for a black-white-black run from the center in the direction
* of another po$(another finder pattern center), and in the opposite direction too.</p>
*/
private function sizeOfBlackWhiteBlackRunBothWays($fromX, $fromY, $toX, $toY) {
$alignmentAreaTopY = max(0, $estAlignmentY - $allowance);
$alignmentAreaBottomY = min($this->image->getHeight() - 1, $estAlignmentY + $allowance);
if ($alignmentAreaBottomY - $alignmentAreaTopY < $overallEstModuleSize * 3) {
throw NotFoundException::getNotFoundInstance();
}
$result = $this->sizeOfBlackWhiteBlackRun($fromX, $fromY, $toX, $toY);
$alignmentFinder =
new AlignmentPatternFinder(
$this->image,
$alignmentAreaLeftX,
$alignmentAreaTopY,
$alignmentAreaRightX - $alignmentAreaLeftX,
$alignmentAreaBottomY - $alignmentAreaTopY,
$overallEstModuleSize,
$this->resultPointCallback
);
// Now count other way -- don't run off image though of course
$scale = 1.0;
$otherToX = $fromX - ($toX - $fromX);
if ($otherToX < 0) {
$scale = (float) $fromX / (float) ($fromX - $otherToX);
$otherToX = 0;
} else if ($otherToX >= $this->image->getWidth()) {
$scale = (float) ($this->image->getWidth() - 1 - $fromX) / (float) ($otherToX - $fromX);
$otherToX = $this->image->getWidth() - 1;
}
$otherToY = (int) ($fromY - ($toY - $fromY) * $scale);
return $alignmentFinder->find();
}
$scale = 1.0;
if ($otherToY < 0) {
$scale = (float) $fromY / (float) ($fromY - $otherToY);
$otherToY = 0;
} else if ($otherToY >= $this->image->getHeight()) {
$scale = (float) ($this->image->getHeight() - 1 - $fromY) / (float) ($otherToY - $fromY);
$otherToY = $this->image->getHeight() - 1;
}
$otherToX = (int) ($fromX + ($otherToX - $fromX) * $scale);
private static function createTransform(
$topLeft,
$topRight,
$bottomLeft,
$alignmentPattern,
$dimension
)
{
$dimMinusThree = (float)$dimension - 3.5;
$bottomRightX = 0.0;
$bottomRightY = 0.0;
$sourceBottomRightX = 0.0;
$sourceBottomRightY = 0.0;
if ($alignmentPattern != null) {
$bottomRightX = $alignmentPattern->getX();
$bottomRightY = $alignmentPattern->getY();
$sourceBottomRightX = $dimMinusThree - 3.0;
$sourceBottomRightY = $sourceBottomRightX;
} else {
// Don't have an alignment pattern, just make up the bottom-right point
$bottomRightX = ($topRight->getX() - $topLeft->getX()) + $bottomLeft->getX();
$bottomRightY = ($topRight->getY() - $topLeft->getY()) + $bottomLeft->getY();
$sourceBottomRightX = $dimMinusThree;
$sourceBottomRightY = $dimMinusThree;
}
$result += $this->sizeOfBlackWhiteBlackRun($fromX, $fromY, $otherToX, $otherToY);
return PerspectiveTransform::quadrilateralToQuadrilateral(
3.5,
3.5,
$dimMinusThree,
3.5,
$sourceBottomRightX,
$sourceBottomRightY,
3.5,
$dimMinusThree,
$topLeft->getX(),
$topLeft->getY(),
$topRight->getX(),
$topRight->getY(),
$bottomRightX,
$bottomRightY,
$bottomLeft->getX(),
$bottomLeft->getY()
);
}
// Middle pixel is double-counted this way; subtract 1
return $result - 1.0;
}
private static function sampleGrid(
$image,
$transform,
$dimension
)
{
$sampler = GridSampler::getInstance();
/**
* <p>This method traces a line from a po$in the image, in the direction towards another point.
* It begins in a black region, and keeps going until it finds white, then black, then white again.
* It reports the distance from the start to this point.</p>
*
* <p>This is used when figuring out how wide a finder pattern is, when the finder pattern
* may be skewed or rotated.</p>
*/
private function sizeOfBlackWhiteBlackRun($fromX, $fromY, $toX, $toY) {
// Mild variant of Bresenham's algorithm;
// see http://en.wikipedia.org/wiki/Bresenham's_line_algorithm
$steep = abs($toY - $fromY) > abs($toX - $fromX);
if ($steep) {
$temp = $fromX;
$fromX = $fromY;
$fromY = $temp;
$temp = $toX;
$toX = $toY;
$toY = $temp;
}
return $sampler->sampleGrid_($image, $dimension, $dimension, $transform);
}
$dx = abs($toX - $fromX);
$dy = abs($toY - $fromY);
$error = -$dx / 2;
$xstep = $fromX < $toX ? 1 : -1;
$ystep = $fromY < $toY ? 1 : -1;
// In black pixels, looking for white, first or second time.
$state = 0;
// Loop up until x == toX, but not beyond
$xLimit = $toX + $xstep;
for ($x = $fromX, $y = $fromY; $x != $xLimit; $x += $xstep) {
$realX = $steep ? $y : $x;
$realY = $steep ? $x : $y;
// 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->image->get($realX, $realY)) {
if ($state == 2) {
return MathUtils::distance($x, $y, $fromX, $fromY);
}
$state++;
}
$error += $dy;
if ($error > 0) {
if ($y == $toY) {
break;
}
$y += $ystep;
$error -= $dx;
}
}
// Found black-white-black; give the benefit of the doubt that the next pixel outside the image
// is "white" so this last po$at (toX+xStep,toY) is the right ending. This is really a
// small approximation; (toX+xStep,toY+yStep) might be really correct. Ignore this.
if ($state == 2) {
return MathUtils::distance($toX + $xstep, $toY, $fromX, $fromY);
}
// else we didn't find even black-white-black; no estimate is really possible
return NAN;
}
/**
* <p>Attempts to locate an alignment pattern in a limited region of the image, which is
* guessed to contain it. This method uses {@link AlignmentPattern}.</p>
*
* @param overallEstModuleSize estimated module size so far
* @param estAlignmentX x coordinate of center of area probably containing alignment pattern
* @param estAlignmentY y coordinate of above
* @param allowanceFactor number of pixels in all directions to search from the center
* @return {@link AlignmentPattern} if found, or null otherwise
* @throws NotFoundException if an unexpected error occurs during detection
*/
protected final function findAlignmentInRegion($overallEstModuleSize,
$estAlignmentX,
$estAlignmentY,
$allowanceFactor)
{
// Look for an alignment pattern (3 modules in size) around where it
// should be
$allowance = (int) ($allowanceFactor * $overallEstModuleSize);
$alignmentAreaLeftX = max(0, $estAlignmentX - $allowance);
$alignmentAreaRightX = min($this->image->getWidth() - 1, $estAlignmentX + $allowance);
if ($alignmentAreaRightX - $alignmentAreaLeftX < $overallEstModuleSize * 3) {
throw NotFoundException::getNotFoundInstance();
}
$alignmentAreaTopY = max(0, $estAlignmentY - $allowance);
$alignmentAreaBottomY = min($this->image->getHeight() - 1, $estAlignmentY + $allowance);
if ($alignmentAreaBottomY - $alignmentAreaTopY < $overallEstModuleSize * 3) {
throw NotFoundException::getNotFoundInstance();
}
$alignmentFinder =
new AlignmentPatternFinder(
$this->image,
$alignmentAreaLeftX,
$alignmentAreaTopY,
$alignmentAreaRightX - $alignmentAreaLeftX,
$alignmentAreaBottomY - $alignmentAreaTopY,
$overallEstModuleSize,
$this->resultPointCallback);
return $alignmentFinder->find();
}
final protected function getImage()
{
return $this->image;
}
final protected function getResultPointCallback()
{
return $this->resultPointCallback;
}
}
@@ -26,56 +26,56 @@ use Zxing\ResultPoint;
*
* @author Sean Owen
*/
final class FinderPattern extends ResultPoint {
final class FinderPattern extends ResultPoint
{
public function __construct($posX, $posY, private $estimatedModuleSize, private $count = 1)
{
parent::__construct($posX, $posY);
}
private $estimatedModuleSize;
private $count;
public function getEstimatedModuleSize()
{
return $this->estimatedModuleSize;
}
public function getCount()
{
return $this->count;
}
/*
void incrementCount() {
this.count++;
}
*/
function __construct($posX, $posY, $estimatedModuleSize, $count=1) {
parent::__construct($posX, $posY);
$this->estimatedModuleSize = $estimatedModuleSize;
$this->count = $count;
}
public function getEstimatedModuleSize() {
return $this->estimatedModuleSize;
}
function getCount() {
return $this->count;
}
/*
void incrementCount() {
this.count++;
}
*/
/**
* <p>Determines if this finder pattern "about equals" a finder pattern at the stated
* position and size -- meaning, it is at nearly the same center with nearly the same size.</p>
*/
function aboutEquals($moduleSize, $i, $j) {
if (abs($i - $this->getY()) <= $moduleSize && abs($j - $this->getX()) <= $moduleSize) {
$moduleSizeDiff = abs($moduleSize - $this->estimatedModuleSize);
return $moduleSizeDiff <= 1.0 || $moduleSizeDiff <= $this->estimatedModuleSize;
}
return false;
}
/**
* Combines this object's current estimate of a finder pattern position and module size
* with a new estimate. It returns a new {@code FinderPattern} containing a weighted average
* based on count.
*/
function combineEstimate($i, $j, $newModuleSize) {
$combinedCount = $this->count + 1;
$combinedX = ($this->count * $this->getX() + $j) / $combinedCount;
$combinedY = ($this->count * $this->getY() + $i) / $combinedCount;
$combinedModuleSize = ($this->count * $this->estimatedModuleSize + $newModuleSize) / $combinedCount;
return new FinderPattern($combinedX, $combinedY, $combinedModuleSize, $combinedCount);
}
/**
* <p>Determines if this finder pattern "about equals" a finder pattern at the stated
* position and size -- meaning, it is at nearly the same center with nearly the same size.</p>
*/
public function aboutEquals($moduleSize, $i, $j)
{
if (abs($i - $this->getY()) <= $moduleSize && abs($j - $this->getX()) <= $moduleSize) {
$moduleSizeDiff = abs($moduleSize - $this->estimatedModuleSize);
return $moduleSizeDiff <= 1.0 || $moduleSizeDiff <= $this->estimatedModuleSize;
}
return false;
}
/**
* Combines this object's current estimate of a finder pattern position and module size
* with a new estimate. It returns a new {@code FinderPattern} containing a weighted average
* based on count.
*/
public function combineEstimate($i, $j, $newModuleSize): \Zxing\Qrcode\Detector\FinderPattern
{
$combinedCount = $this->count + 1;
$combinedX = ($this->count * $this->getX() + $j) / $combinedCount;
$combinedY = ($this->count * $this->getY() + $i) / $combinedCount;
$combinedModuleSize = ($this->count * $this->estimatedModuleSize + $newModuleSize) / $combinedCount;
return new FinderPattern($combinedX, $combinedY, $combinedModuleSize, $combinedCount);
}
}
File diff suppressed because it is too large Load Diff
@@ -23,28 +23,31 @@ namespace Zxing\Qrcode\Detector;
*
* @author Sean Owen
*/
final class FinderPatternInfo {
final class FinderPatternInfo
{
private $bottomLeft;
private $topLeft;
private $topRight;
private $bottomLeft;
private $topLeft;
private $topRight;
public function __construct($patternCenters) {
$this->bottomLeft = $patternCenters[0];
$this->topLeft = $patternCenters[1];
$this->topRight = $patternCenters[2];
}
public function getBottomLeft() {
return $this->bottomLeft;
}
public function getTopLeft() {
return $this->topLeft;
}
public function getTopRight() {
return $this->topRight;
}
public function __construct($patternCenters)
{
$this->bottomLeft = $patternCenters[0];
$this->topLeft = $patternCenters[1];
$this->topRight = $patternCenters[2];
}
public function getBottomLeft()
{
return $this->bottomLeft;
}
public function getTopLeft()
{
return $this->topLeft;
}
public function getTopRight()
{
return $this->topRight;
}
}