mirror of
https://github.com/chillerlan/php-qrcode.git
synced 2026-08-27 18:48:13 +00:00
:octocat: moved some files
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
|
||||
namespace chillerlan\QRCode\Decoder;
|
||||
|
||||
use chillerlan\QRCode\Common\LuminanceSourceInterface;
|
||||
use chillerlan\QRCode\Data\QRMatrix;
|
||||
use function array_fill, count, intdiv, max;
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
namespace chillerlan\QRCode\Decoder;
|
||||
|
||||
use chillerlan\QRCode\Common\{BitBuffer, EccLevel, MaskPattern, Mode, ReedSolomonDecoder, Version};
|
||||
use chillerlan\QRCode\Common\{BitBuffer, EccLevel, LuminanceSourceInterface, MaskPattern, Mode, Version};
|
||||
use chillerlan\QRCode\Data\{AlphaNum, Byte, ECI, Hanzi, Kanji, Number};
|
||||
use chillerlan\QRCode\Detector\Detector;
|
||||
use Throwable;
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Class GDLuminanceSource
|
||||
*
|
||||
* @created 17.01.2021
|
||||
* @author Ashot Khanamiryan
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @copyright 2021 Smiley
|
||||
* @license MIT
|
||||
*
|
||||
* @noinspection PhpComposerExtensionStubsInspection
|
||||
*/
|
||||
|
||||
namespace chillerlan\QRCode\Decoder;
|
||||
|
||||
use chillerlan\Settings\SettingsContainerInterface;
|
||||
use function file_get_contents, get_resource_type, imagecolorat, imagecolorsforindex,
|
||||
imagecreatefromstring, imagefilter, imagesx, imagesy, is_resource;
|
||||
use const IMG_FILTER_BRIGHTNESS, IMG_FILTER_CONTRAST, IMG_FILTER_GRAYSCALE, IMG_FILTER_NEGATE, PHP_MAJOR_VERSION;
|
||||
|
||||
/**
|
||||
* This class is used to help decode images from files which arrive as GD Resource
|
||||
* It does not support rotation.
|
||||
*/
|
||||
class GDLuminanceSource extends LuminanceSourceAbstract{
|
||||
|
||||
/**
|
||||
* @var resource|\GdImage
|
||||
*/
|
||||
protected $gdImage;
|
||||
|
||||
/**
|
||||
* GDLuminanceSource constructor.
|
||||
*
|
||||
* @param resource|\GdImage $gdImage
|
||||
* @param \chillerlan\Settings\SettingsContainerInterface|null $options
|
||||
*
|
||||
* @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException
|
||||
*/
|
||||
public function __construct($gdImage, SettingsContainerInterface $options = null){
|
||||
|
||||
/** @noinspection PhpFullyQualifiedNameUsageInspection */
|
||||
if(
|
||||
(PHP_MAJOR_VERSION >= 8 && !$gdImage instanceof \GdImage) // @todo: remove version check in v6
|
||||
|| (PHP_MAJOR_VERSION < 8 && (!is_resource($gdImage) || get_resource_type($gdImage) !== 'gd'))
|
||||
){
|
||||
throw new QRCodeDecoderException('Invalid GD image source.'); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
parent::__construct(imagesx($gdImage), imagesy($gdImage), $options);
|
||||
|
||||
$this->gdImage = $gdImage;
|
||||
|
||||
if($this->options->readerGrayscale){
|
||||
imagefilter($this->gdImage, IMG_FILTER_GRAYSCALE);
|
||||
}
|
||||
|
||||
if($this->options->readerInvertColors){
|
||||
imagefilter($this->gdImage, IMG_FILTER_NEGATE);
|
||||
}
|
||||
|
||||
if($this->options->readerIncreaseContrast){
|
||||
imagefilter($this->gdImage, IMG_FILTER_BRIGHTNESS, -100);
|
||||
imagefilter($this->gdImage, IMG_FILTER_CONTRAST, -100);
|
||||
}
|
||||
|
||||
$this->setLuminancePixels();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
protected function setLuminancePixels():void{
|
||||
|
||||
for($j = 0; $j < $this->height; $j++){
|
||||
for($i = 0; $i < $this->width; $i++){
|
||||
$argb = imagecolorat($this->gdImage, $i, $j);
|
||||
$pixel = imagecolorsforindex($this->gdImage, $argb);
|
||||
|
||||
$this->setLuminancePixel($pixel['red'], $pixel['green'], $pixel['blue']);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public static function fromFile(string $path, SettingsContainerInterface $options = null):self{
|
||||
return new self(imagecreatefromstring(file_get_contents(self::checkFile($path))), $options);
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public static function fromBlob(string $blob, SettingsContainerInterface $options = null):self{
|
||||
return new self(imagecreatefromstring($blob), $options);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Class IMagickLuminanceSource
|
||||
*
|
||||
* @created 17.01.2021
|
||||
* @author Ashot Khanamiryan
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @copyright 2021 Smiley
|
||||
* @license MIT
|
||||
*
|
||||
* @noinspection PhpComposerExtensionStubsInspection
|
||||
*/
|
||||
|
||||
namespace chillerlan\QRCode\Decoder;
|
||||
|
||||
use chillerlan\Settings\SettingsContainerInterface;
|
||||
use Imagick;
|
||||
use function count;
|
||||
|
||||
/**
|
||||
* This class is used to help decode images from files which arrive as Imagick Resource
|
||||
* It does not support rotation.
|
||||
*/
|
||||
class IMagickLuminanceSource extends LuminanceSourceAbstract{
|
||||
|
||||
protected Imagick $imagick;
|
||||
|
||||
/**
|
||||
* IMagickLuminanceSource constructor.
|
||||
*/
|
||||
public function __construct(Imagick $imagick, SettingsContainerInterface $options = null){
|
||||
parent::__construct($imagick->getImageWidth(), $imagick->getImageHeight(), $options);
|
||||
|
||||
$this->imagick = $imagick;
|
||||
|
||||
if($this->options->readerGrayscale){
|
||||
$this->imagick->setImageColorspace(Imagick::COLORSPACE_GRAY);
|
||||
}
|
||||
|
||||
if($this->options->readerInvertColors){
|
||||
$this->imagick->negateImage($this->options->readerGrayscale);
|
||||
}
|
||||
|
||||
if($this->options->readerIncreaseContrast){
|
||||
for($i = 0; $i < 10; $i++){
|
||||
$this->imagick->contrastImage(false); // misleading docs
|
||||
}
|
||||
}
|
||||
|
||||
$this->setLuminancePixels();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
protected function setLuminancePixels():void{
|
||||
$pixels = $this->imagick->exportImagePixels(1, 1, $this->width, $this->height, 'RGB', Imagick::PIXEL_CHAR);
|
||||
$count = count($pixels);
|
||||
|
||||
for($i = 0; $i < $count; $i += 3){
|
||||
$this->setLuminancePixel(($pixels[$i] & 0xff), ($pixels[($i + 1)] & 0xff), ($pixels[($i + 2)] & 0xff));
|
||||
}
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public static function fromFile(string $path, SettingsContainerInterface $options = null):self{
|
||||
return new self(new Imagick(self::checkFile($path)), $options);
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public static function fromBlob(string $blob, SettingsContainerInterface $options = null):self{
|
||||
$im = new Imagick;
|
||||
$im->readImageBlob($blob);
|
||||
|
||||
return new self($im, $options);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Class LuminanceSourceAbstract
|
||||
*
|
||||
* @created 24.01.2021
|
||||
* @author ZXing Authors
|
||||
* @author Ashot Khanamiryan
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @copyright 2021 Smiley
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
namespace chillerlan\QRCode\Decoder;
|
||||
|
||||
use chillerlan\QRCode\QROptions;
|
||||
use chillerlan\Settings\SettingsContainerInterface;
|
||||
use function array_slice, array_splice, file_exists, is_file, is_readable, realpath;
|
||||
|
||||
/**
|
||||
* The purpose of this class hierarchy is to abstract different bitmap implementations across
|
||||
* platforms into a standard interface for requesting greyscale luminance values.
|
||||
*
|
||||
* @author dswitkin@google.com (Daniel Switkin)
|
||||
*/
|
||||
abstract class LuminanceSourceAbstract implements LuminanceSourceInterface{
|
||||
|
||||
/** @var \chillerlan\QRCode\QROptions|\chillerlan\Settings\SettingsContainerInterface */
|
||||
protected SettingsContainerInterface $options;
|
||||
protected array $luminances;
|
||||
protected int $width;
|
||||
protected int $height;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function __construct(int $width, int $height, SettingsContainerInterface $options = null){
|
||||
$this->width = $width;
|
||||
$this->height = $height;
|
||||
$this->options = ($options ?? new QROptions);
|
||||
|
||||
$this->luminances = [];
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function getLuminances():array{
|
||||
return $this->luminances;
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function getWidth():int{
|
||||
return $this->width;
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function getHeight():int{
|
||||
return $this->height;
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function getRow(int $y):array{
|
||||
|
||||
if($y < 0 || $y >= $this->getHeight()){
|
||||
throw new QRCodeDecoderException('Requested row is outside the image: '.$y);
|
||||
}
|
||||
|
||||
$arr = [];
|
||||
|
||||
array_splice($arr, 0, $this->width, array_slice($this->luminances, ($y * $this->width), $this->width));
|
||||
|
||||
return $arr;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
protected function setLuminancePixel(int $r, int $g, int $b):void{
|
||||
$this->luminances[] = ($r === $g && $g === $b)
|
||||
// Image is already greyscale, so pick any channel.
|
||||
? $r // (($r + 128) % 256) - 128;
|
||||
// Calculate luminance cheaply, favoring green.
|
||||
: (($r + 2 * $g + $b) / 4); // (((($r + 2 * $g + $b) / 4) + 128) % 256) - 128;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException
|
||||
*/
|
||||
protected static function checkFile(string $path):string{
|
||||
$path = trim($path);
|
||||
|
||||
if(!file_exists($path) || !is_file($path) || !is_readable($path)){
|
||||
throw new QRCodeDecoderException('invalid file: '.$path);
|
||||
}
|
||||
|
||||
$realpath = realpath($path);
|
||||
|
||||
if($realpath === false){
|
||||
throw new QRCodeDecoderException('unable to resolve path: '.$path);
|
||||
}
|
||||
|
||||
return $realpath;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Interface LuminanceSourceInterface
|
||||
*
|
||||
* @created 18.11.2021
|
||||
* @author smiley <smiley@chillerlan.net>
|
||||
* @copyright 2021 smiley
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace chillerlan\QRCode\Decoder;
|
||||
|
||||
/**
|
||||
*/
|
||||
interface LuminanceSourceInterface{
|
||||
|
||||
/**
|
||||
* Fetches luminance data for the underlying bitmap. Values should be fetched using:
|
||||
* `int luminance = array[y * width + x] & 0xff`
|
||||
*
|
||||
* @return array A row-major 2D array of luminance values. Do not use result $length as it may be
|
||||
* larger than $width * $height bytes on some platforms. Do not modify the contents
|
||||
* of the result.
|
||||
*/
|
||||
public function getLuminances():array;
|
||||
|
||||
/**
|
||||
* @return int The width of the bitmap.
|
||||
*/
|
||||
public function getWidth():int;
|
||||
|
||||
/**
|
||||
* @return int The height of the bitmap.
|
||||
*/
|
||||
public function getHeight():int;
|
||||
|
||||
/**
|
||||
* Fetches one row of luminance data from the underlying platform's bitmap. Values range from
|
||||
* 0 (black) to 255 (white). Because Java does not have an unsigned byte type, callers will have
|
||||
* to bitwise and with 0xff for each value. It is preferable for implementations of this method
|
||||
* to only fetch this row rather than the whole image, since no 2D Readers may be installed and
|
||||
* getLuminances() may never be called.
|
||||
*
|
||||
* @param int $y The row to fetch, which must be in [0,getHeight())
|
||||
*
|
||||
* @return array An array containing the luminance data.
|
||||
* @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException
|
||||
*/
|
||||
public function getRow(int $y):array;
|
||||
|
||||
/**
|
||||
* Creates a LuminanceSource instance from the given file
|
||||
*/
|
||||
public static function fromFile(string $path):self;
|
||||
|
||||
/**
|
||||
* Creates a LuminanceSource instance from the given data blob
|
||||
*/
|
||||
public static function fromBlob(string $blob):self;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,314 @@
|
||||
<?php
|
||||
/**
|
||||
* Class ReedSolomonDecoder
|
||||
*
|
||||
* @created 24.01.2021
|
||||
* @author ZXing Authors
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @copyright 2021 Smiley
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
namespace chillerlan\QRCode\Decoder;
|
||||
|
||||
use chillerlan\QRCode\Common\{BitBuffer, EccLevel, GenericGFPoly, GF256, Version};
|
||||
use chillerlan\QRCode\QRCodeException;
|
||||
use function array_fill, array_reverse, count;
|
||||
|
||||
/**
|
||||
* Implements Reed-Solomon decoding
|
||||
*
|
||||
* The algorithm will not be explained here, but the following references were helpful
|
||||
* in creating this implementation:
|
||||
*
|
||||
* - Bruce Maggs "Decoding Reed-Solomon Codes" (see discussion of Forney's Formula)
|
||||
* http://www.cs.cmu.edu/afs/cs.cmu.edu/project/pscico-guyb/realworld/www/rs_decode.ps
|
||||
* - J.I. Hall. "Chapter 5. Generalized Reed-Solomon Codes" (see discussion of Euclidean algorithm)
|
||||
* https://users.math.msu.edu/users/halljo/classes/codenotes/GRS.pdf
|
||||
*
|
||||
* Much credit is due to William Rucklidge since portions of this code are an indirect
|
||||
* port of his C++ Reed-Solomon implementation.
|
||||
*
|
||||
* @author Sean Owen
|
||||
* @author William Rucklidge
|
||||
* @author sanfordsquires
|
||||
*/
|
||||
final class ReedSolomonDecoder{
|
||||
|
||||
private Version $version;
|
||||
private EccLevel $eccLevel;
|
||||
|
||||
/**
|
||||
* ReedSolomonDecoder constructor
|
||||
*/
|
||||
public function __construct(Version $version, EccLevel $eccLevel){
|
||||
$this->version = $version;
|
||||
$this->eccLevel = $eccLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Error-correct and copy data blocks together into a stream of bytes
|
||||
*/
|
||||
public function decode(array $rawCodewords):BitBuffer{
|
||||
$dataBlocks = $this->deinterleaveRawBytes($rawCodewords);
|
||||
$dataBytes = [];
|
||||
|
||||
foreach($dataBlocks as [$numDataCodewords, $codewordBytes]){
|
||||
$corrected = $this->correctErrors($codewordBytes, $numDataCodewords);
|
||||
|
||||
for($i = 0; $i < $numDataCodewords; $i++){
|
||||
$dataBytes[] = $corrected[$i];
|
||||
}
|
||||
}
|
||||
|
||||
return new BitBuffer($dataBytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException
|
||||
*/
|
||||
private function deinterleaveRawBytes(array $rawCodewords):array{
|
||||
// Figure out the number and size of data blocks used by this version and
|
||||
// error correction level
|
||||
[$numEccCodewords, $eccBlocks] = $this->version->getRSBlocks($this->eccLevel);
|
||||
|
||||
// Now establish DataBlocks of the appropriate size and number of data codewords
|
||||
$result = [];//new DataBlock[$totalBlocks];
|
||||
$numResultBlocks = 0;
|
||||
|
||||
foreach($eccBlocks as [$numEccBlocks, $eccPerBlock]){
|
||||
for($i = 0; $i < $numEccBlocks; $i++, $numResultBlocks++){
|
||||
$result[$numResultBlocks] = [$eccPerBlock, array_fill(0, ($numEccCodewords + $eccPerBlock), 0)];
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
/** @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset */
|
||||
$shorterBlocksTotalCodewords = count($result[0][1]);
|
||||
$longerBlocksStartAt = (count($result) - 1);
|
||||
|
||||
while($longerBlocksStartAt >= 0){
|
||||
$numCodewords = count($result[$longerBlocksStartAt][1]);
|
||||
|
||||
if($numCodewords == $shorterBlocksTotalCodewords){
|
||||
break;
|
||||
}
|
||||
|
||||
$longerBlocksStartAt--;
|
||||
}
|
||||
|
||||
$longerBlocksStartAt++;
|
||||
|
||||
$shorterBlocksNumDataCodewords = ($shorterBlocksTotalCodewords - $numEccCodewords);
|
||||
// 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][1][$i] = $rawCodewords[$rawCodewordsOffset++];
|
||||
}
|
||||
}
|
||||
|
||||
// Fill out the last data block in the longer ones
|
||||
for($j = $longerBlocksStartAt; $j < $numResultBlocks; $j++){
|
||||
$result[$j][1][$shorterBlocksNumDataCodewords] = $rawCodewords[$rawCodewordsOffset++];
|
||||
}
|
||||
|
||||
// Now add in error correction blocks
|
||||
/** @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset */
|
||||
$max = count($result[0][1]);
|
||||
|
||||
for($i = $shorterBlocksNumDataCodewords; $i < $max; $i++){
|
||||
for($j = 0; $j < $numResultBlocks; $j++){
|
||||
$iOffset = ($j < $longerBlocksStartAt) ? $i : ($i + 1);
|
||||
$result[$j][1][$iOffset] = $rawCodewords[$rawCodewordsOffset++];
|
||||
}
|
||||
}
|
||||
|
||||
// DataBlocks containing original bytes, "de-interleaved" from representation in the QR Code
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given data and error-correction codewords received, possibly corrupted by errors, attempts to
|
||||
* correct the errors in-place using Reed-Solomon error correction.
|
||||
*/
|
||||
private function correctErrors(array $codewordBytes, int $numDataCodewords):array{
|
||||
// First read into an array of ints
|
||||
$codewordsInts = [];
|
||||
|
||||
foreach($codewordBytes as $codewordByte){
|
||||
$codewordsInts[] = ($codewordByte & 0xFF);
|
||||
}
|
||||
|
||||
$decoded = $this->decodeWords($codewordsInts, (count($codewordBytes) - $numDataCodewords));
|
||||
|
||||
// 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] = $decoded[$i];
|
||||
}
|
||||
|
||||
return $codewordBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes given set of received codewords, which include both data and error-correction
|
||||
* codewords. Really, this means it uses Reed-Solomon to detect and correct errors, in-place,
|
||||
* in the input.
|
||||
*
|
||||
* @param array $received data and error-correction codewords
|
||||
* @param int $numEccCodewords number of error-correction codewords available
|
||||
*
|
||||
* @return int[]
|
||||
* @throws \chillerlan\QRCode\QRCodeException if decoding fails for any reason
|
||||
*/
|
||||
private function decodeWords(array $received, int $numEccCodewords):array{
|
||||
$poly = new GenericGFPoly($received);
|
||||
$syndromeCoefficients = [];
|
||||
$error = false;
|
||||
|
||||
for($i = 0; $i < $numEccCodewords; $i++){
|
||||
$syndromeCoefficients[$i] = $poly->evaluateAt(GF256::exp($i));
|
||||
|
||||
if($syndromeCoefficients[$i] !== 0){
|
||||
$error = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(!$error){
|
||||
return $received;
|
||||
}
|
||||
|
||||
[$sigma, $omega] = $this->runEuclideanAlgorithm(
|
||||
GF256::buildMonomial($numEccCodewords, 1),
|
||||
new GenericGFPoly(array_reverse($syndromeCoefficients)),
|
||||
$numEccCodewords
|
||||
);
|
||||
|
||||
$errorLocations = $this->findErrorLocations($sigma);
|
||||
$errorMagnitudes = $this->findErrorMagnitudes($omega, $errorLocations);
|
||||
$errorLocationsCount = count($errorLocations);
|
||||
$receivedCount = count($received);
|
||||
|
||||
for($i = 0; $i < $errorLocationsCount; $i++){
|
||||
$position = ($receivedCount - 1 - GF256::log($errorLocations[$i]));
|
||||
|
||||
if($position < 0){
|
||||
throw new QRCodeException('Bad error location');
|
||||
}
|
||||
|
||||
$received[$position] ^= $errorMagnitudes[$i];
|
||||
}
|
||||
|
||||
return $received;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \chillerlan\QRCode\Common\GenericGFPoly[] [sigma, omega]
|
||||
* @throws \chillerlan\QRCode\QRCodeException
|
||||
*/
|
||||
private function runEuclideanAlgorithm(GenericGFPoly $a, GenericGFPoly $b, int $z):array{
|
||||
// Assume a's degree is >= b's
|
||||
if($a->getDegree() < $b->getDegree()){
|
||||
$temp = $a;
|
||||
$a = $b;
|
||||
$b = $temp;
|
||||
}
|
||||
|
||||
$rLast = $a;
|
||||
$r = $b;
|
||||
$tLast = new GenericGFPoly([0]);
|
||||
$t = new GenericGFPoly([1]);
|
||||
|
||||
// Run Euclidean algorithm until r's degree is less than z/2
|
||||
while((2 * $r->getDegree()) >= $z){
|
||||
$rLastLast = $rLast;
|
||||
$tLastLast = $tLast;
|
||||
$rLast = $r;
|
||||
$tLast = $t;
|
||||
|
||||
// Divide rLastLast by rLast, with quotient in q and remainder in r
|
||||
[$q, $r] = $rLastLast->divide($rLast);
|
||||
|
||||
$t = $q->multiply($tLast)->addOrSubtract($tLastLast);
|
||||
|
||||
if($r->getDegree() >= $rLast->getDegree()){
|
||||
throw new QRCodeException('Division algorithm failed to reduce polynomial?');
|
||||
}
|
||||
}
|
||||
|
||||
$sigmaTildeAtZero = $t->getCoefficient(0);
|
||||
|
||||
if($sigmaTildeAtZero === 0){
|
||||
throw new QRCodeException('sigmaTilde(0) was zero');
|
||||
}
|
||||
|
||||
$inverse = GF256::inverse($sigmaTildeAtZero);
|
||||
|
||||
return [$t->multiplyInt($inverse), $r->multiplyInt($inverse)];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \chillerlan\QRCode\QRCodeException
|
||||
*/
|
||||
private function findErrorLocations(GenericGFPoly $errorLocator):array{
|
||||
// This is a direct application of Chien's search
|
||||
$numErrors = $errorLocator->getDegree();
|
||||
|
||||
if($numErrors === 1){ // shortcut
|
||||
return [$errorLocator->getCoefficient(1)];
|
||||
}
|
||||
|
||||
$result = array_fill(0, $numErrors, 0);
|
||||
$e = 0;
|
||||
|
||||
for($i = 1; $i < 256 && $e < $numErrors; $i++){
|
||||
if($errorLocator->evaluateAt($i) === 0){
|
||||
$result[$e] = GF256::inverse($i);
|
||||
$e++;
|
||||
}
|
||||
}
|
||||
|
||||
if($e !== $numErrors){
|
||||
throw new QRCodeException('Error locator degree does not match number of roots');
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private function findErrorMagnitudes(GenericGFPoly $errorEvaluator, array $errorLocations):array{
|
||||
// This is directly applying Forney's Formula
|
||||
$s = count($errorLocations);
|
||||
$result = [];
|
||||
|
||||
for($i = 0; $i < $s; $i++){
|
||||
$xiInverse = GF256::inverse($errorLocations[$i]);
|
||||
$denominator = 1;
|
||||
|
||||
for($j = 0; $j < $s; $j++){
|
||||
if($i !== $j){
|
||||
# $denominator = GF256::multiply($denominator, GF256::addOrSubtract(1, GF256::multiply($errorLocations[$j], $xiInverse)));
|
||||
// Above should work but fails on some Apple and Linux JDKs due to a Hotspot bug.
|
||||
// Below is a funny-looking workaround from Steven Parkes
|
||||
$term = GF256::multiply($errorLocations[$j], $xiInverse);
|
||||
$denominator = GF256::multiply($denominator, ((($term & 0x1) === 0) ? ($term | 1) : ($term & ~1)));
|
||||
}
|
||||
}
|
||||
|
||||
$result[$i] = GF256::multiply($errorEvaluator->evaluateAt($xiInverse), GF256::inverse($denominator));
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user