mirror of
https://github.com/chillerlan/php-qrcode.git
synced 2026-08-24 09:28:04 +00:00
:octocat: use ZXing classes for RS encoding, cleanup
This commit is contained in:
@@ -1,125 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Class ReedSolomon
|
||||
*
|
||||
* @filesource ReedSolomon.php
|
||||
* @created 07.01.2021
|
||||
* @package chillerlan\QRCode\Common
|
||||
* @author smiley <smiley@chillerlan.net>
|
||||
* @copyright 2021 smiley
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace chillerlan\QRCode\Common;
|
||||
|
||||
use chillerlan\QRCode\Helpers\BitBuffer;
|
||||
use chillerlan\QRCode\Helpers\Polynomial;
|
||||
|
||||
use SplFixedArray;
|
||||
use function array_fill, array_merge, count, max;
|
||||
|
||||
/**
|
||||
* ISO/IEC 18004:2000 Section 8.5 ff
|
||||
*
|
||||
* @see http://www.thonky.com/qr-code-tutorial/error-correction-coding
|
||||
*/
|
||||
final class ReedSolomon{
|
||||
|
||||
private Version $version;
|
||||
private EccLevel $eccLevel;
|
||||
|
||||
private SplFixedArray $interleavedData;
|
||||
private int $interleavedDataIndex;
|
||||
|
||||
/**
|
||||
* ReedSolomon constructor.
|
||||
*/
|
||||
public function __construct(Version $version, EccLevel $eccLevel){
|
||||
$this->version = $version;
|
||||
$this->eccLevel = $eccLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* ECC interleaving
|
||||
*
|
||||
* @return \SplFixedArray<int>
|
||||
*/
|
||||
public function interleaveEcBytes(BitBuffer $bitBuffer):SplFixedArray{
|
||||
[$numEccCodewords, [[$l1, $b1], [$l2, $b2]]] = $this->version->getRSBlocks($this->eccLevel);
|
||||
|
||||
$numRsBlocks = $l1 + $l2;
|
||||
$ecBytes = new SplFixedArray($numRsBlocks);
|
||||
$rsBlocks = array_fill(0, $l1, [$numEccCodewords + $b1, $b1]);
|
||||
|
||||
if($l2 > 0){
|
||||
$rsBlocks = array_merge($rsBlocks, array_fill(0, $l2, [$numEccCodewords + $b2, $b2]));
|
||||
}
|
||||
|
||||
$dataBytes = SplFixedArray::fromArray($rsBlocks);
|
||||
$maxDataBytes = 0;
|
||||
$maxEcBytes = 0;
|
||||
$dataByteOffset = 0;
|
||||
$bitBufferData = $bitBuffer->getBuffer();
|
||||
|
||||
foreach($rsBlocks as $key => $block){
|
||||
[$rsBlockTotal, $dataByteCount] = $block;
|
||||
|
||||
$ecByteCount = $rsBlockTotal - $dataByteCount;
|
||||
$maxDataBytes = max($maxDataBytes, $dataByteCount);
|
||||
$maxEcBytes = max($maxEcBytes, $ecByteCount);
|
||||
$dataBytes[$key] = new SplFixedArray($dataByteCount);
|
||||
|
||||
foreach($dataBytes[$key] as $i => $_){
|
||||
$dataBytes[$key][$i] = $bitBufferData[$i + $dataByteOffset] & 0xff;
|
||||
}
|
||||
|
||||
$rsPoly = new Polynomial;
|
||||
$modPoly = new Polynomial;
|
||||
|
||||
for($i = 0; $i < $ecByteCount; $i++){
|
||||
$modPoly->setNum([1, $modPoly->gexp($i)]);
|
||||
$rsPoly->multiply($modPoly->getNum());
|
||||
}
|
||||
|
||||
$rsPolyCount = count($rsPoly->getNum()) - 1;
|
||||
|
||||
$modPoly
|
||||
->setNum($dataBytes[$key]->toArray(), $rsPolyCount)
|
||||
->mod($rsPoly->getNum())
|
||||
;
|
||||
|
||||
$ecBytes[$key] = new SplFixedArray($rsPolyCount);
|
||||
$num = $modPoly->getNum();
|
||||
$count = count($num) - count($ecBytes[$key]);
|
||||
|
||||
foreach($ecBytes[$key] as $i => $_){
|
||||
$modIndex = $i + $count;
|
||||
$ecBytes[$key][$i] = $modIndex >= 0 ? $num[$modIndex] : 0;
|
||||
}
|
||||
|
||||
$dataByteOffset += $dataByteCount;
|
||||
}
|
||||
|
||||
$this->interleavedData = new SplFixedArray($this->version->getTotalCodewords());
|
||||
$this->interleavedDataIndex = 0;
|
||||
|
||||
$this->interleave($dataBytes, $maxDataBytes, $numRsBlocks);
|
||||
$this->interleave($ecBytes, $maxEcBytes, $numRsBlocks);
|
||||
|
||||
return $this->interleavedData;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private function interleave(SplFixedArray $byteArray, int $maxBytes, int $numRsBlocks):void{
|
||||
for($x = 0; $x < $maxBytes; $x++){
|
||||
for($y = 0; $y < $numRsBlocks; $y++){
|
||||
if($x < count($byteArray[$y])){
|
||||
$this->interleavedData[$this->interleavedDataIndex++] = $byteArray[$y][$x];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
/**
|
||||
* Class ReedSolomonEncoder
|
||||
*
|
||||
* @filesource ReedSolomonEncoder.php
|
||||
* @created 07.01.2021
|
||||
* @package chillerlan\QRCode\Common
|
||||
* @author smiley <smiley@chillerlan.net>
|
||||
* @copyright 2021 smiley
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace chillerlan\QRCode\Common;
|
||||
|
||||
use chillerlan\QRCode\Helpers\BitBuffer;
|
||||
use SplFixedArray;
|
||||
|
||||
use function array_fill, array_merge, count, max;
|
||||
|
||||
/**
|
||||
* ISO/IEC 18004:2000 Section 8.5 ff
|
||||
*
|
||||
* @see http://www.thonky.com/qr-code-tutorial/error-correction-coding
|
||||
*/
|
||||
final class ReedSolomonEncoder{
|
||||
|
||||
private SplFixedArray $interleavedData;
|
||||
private int $interleavedDataIndex;
|
||||
|
||||
/**
|
||||
* ECC interleaving
|
||||
*
|
||||
* @return \SplFixedArray<int>
|
||||
*/
|
||||
public function interleaveEcBytes(BitBuffer $bitBuffer, Version $version, EccLevel $eccLevel):SplFixedArray{
|
||||
[$numEccCodewords, [[$l1, $b1], [$l2, $b2]]] = $version->getRSBlocks($eccLevel);
|
||||
|
||||
$rsBlocks = array_fill(0, $l1, [$numEccCodewords + $b1, $b1]);
|
||||
|
||||
if($l2 > 0){
|
||||
$rsBlocks = array_merge($rsBlocks, array_fill(0, $l2, [$numEccCodewords + $b2, $b2]));
|
||||
}
|
||||
|
||||
$bitBufferData = $bitBuffer->getBuffer();
|
||||
$dataBytes = [];
|
||||
$ecBytes = [];
|
||||
$maxDataBytes = 0;
|
||||
$maxEcBytes = 0;
|
||||
$dataByteOffset = 0;
|
||||
|
||||
foreach($rsBlocks as $key => $block){
|
||||
[$rsBlockTotal, $dataByteCount] = $block;
|
||||
|
||||
$dataBytes[$key] = [];
|
||||
|
||||
for($i = 0; $i < $dataByteCount; $i++){
|
||||
$dataBytes[$key][$i] = $bitBufferData[$i + $dataByteOffset] & 0xff;
|
||||
}
|
||||
|
||||
$ecByteCount = $rsBlockTotal - $dataByteCount;
|
||||
$ecBytes[$key] = $this->generateEcBytes($dataBytes[$key], $ecByteCount);
|
||||
$maxDataBytes = max($maxDataBytes, $dataByteCount);
|
||||
$maxEcBytes = max($maxEcBytes, $ecByteCount);
|
||||
$dataByteOffset += $dataByteCount;
|
||||
}
|
||||
|
||||
$this->interleavedData = new SplFixedArray($version->getTotalCodewords());
|
||||
$this->interleavedDataIndex = 0;
|
||||
$numRsBlocks = $l1 + $l2;
|
||||
|
||||
$this->interleave($dataBytes, $maxDataBytes, $numRsBlocks);
|
||||
$this->interleave($ecBytes, $maxEcBytes, $numRsBlocks);
|
||||
|
||||
return $this->interleavedData;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private function generateEcBytes(array $dataBytes, int $ecByteCount):array{
|
||||
$rsPoly = new GenericGFPoly([1]);
|
||||
|
||||
for($i = 0; $i < $ecByteCount; $i++){
|
||||
$rsPoly = $rsPoly->multiply(new GenericGFPoly([1, GF256::exp($i)]));
|
||||
}
|
||||
|
||||
$rsPolyDegree = $rsPoly->getDegree();
|
||||
|
||||
$num = (new GenericGFPoly($dataBytes, $rsPolyDegree))
|
||||
->mod($rsPoly)
|
||||
->getCoefficients()
|
||||
;
|
||||
|
||||
$ecBytes = array_fill(0, $rsPolyDegree, 0);
|
||||
$count = count($num) - count($ecBytes);
|
||||
|
||||
foreach($ecBytes as $i => &$val){
|
||||
$modIndex = $i + $count;
|
||||
$val = $modIndex >= 0 ? $num[$modIndex] : 0;
|
||||
}
|
||||
|
||||
return $ecBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private function interleave(array $byteArray, int $maxBytes, int $numRsBlocks):void{
|
||||
for($x = 0; $x < $maxBytes; $x++){
|
||||
for($y = 0; $y < $numRsBlocks; $y++){
|
||||
if($x < count($byteArray[$y])){
|
||||
$this->interleavedData[$this->interleavedDataIndex++] = $byteArray[$y][$x];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+2
-3
@@ -12,7 +12,7 @@
|
||||
|
||||
namespace chillerlan\QRCode\Data;
|
||||
|
||||
use chillerlan\QRCode\Common\{EccLevel, Mode, ReedSolomon, Version};
|
||||
use chillerlan\QRCode\Common\{EccLevel, Mode, ReedSolomonEncoder, Version};
|
||||
use chillerlan\QRCode\QRCode;
|
||||
use chillerlan\QRCode\Helpers\BitBuffer;
|
||||
use chillerlan\Settings\SettingsContainerInterface;
|
||||
@@ -99,8 +99,7 @@ class QRData{
|
||||
* returns a fresh matrix object with the data written for the given $maskPattern
|
||||
*/
|
||||
public function initMatrix(int $maskPattern, bool $test = null):QRMatrix{
|
||||
$rs = new ReedSolomon($this->version, $this->eccLevel);
|
||||
$data = $rs->interleaveEcBytes($this->bitBuffer);
|
||||
$data = (new ReedSolomonEncoder)->interleaveEcBytes($this->bitBuffer, $this->version, $this->eccLevel);
|
||||
|
||||
return (new QRMatrix($this->version, $this->eccLevel))
|
||||
->init($maskPattern, $test)
|
||||
|
||||
@@ -1,178 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Polynomial
|
||||
*
|
||||
* @filesource Polynomial.php
|
||||
* @created 25.11.2015
|
||||
* @package chillerlan\QRCode\Helpers
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @copyright 2015 Smiley
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace chillerlan\QRCode\Helpers;
|
||||
|
||||
use chillerlan\QRCode\QRCodeException;
|
||||
|
||||
use function array_fill, count, sprintf;
|
||||
|
||||
/**
|
||||
* Polynomial long division helpers
|
||||
*
|
||||
* @see http://www.thonky.com/qr-code-tutorial/error-correction-coding
|
||||
*/
|
||||
final class Polynomial{
|
||||
|
||||
/**
|
||||
* @see http://www.thonky.com/qr-code-tutorial/log-antilog-table
|
||||
*/
|
||||
protected const table = [
|
||||
[ 1, 0], [ 2, 0], [ 4, 1], [ 8, 25], [ 16, 2], [ 32, 50], [ 64, 26], [128, 198],
|
||||
[ 29, 3], [ 58, 223], [116, 51], [232, 238], [205, 27], [135, 104], [ 19, 199], [ 38, 75],
|
||||
[ 76, 4], [152, 100], [ 45, 224], [ 90, 14], [180, 52], [117, 141], [234, 239], [201, 129],
|
||||
[143, 28], [ 3, 193], [ 6, 105], [ 12, 248], [ 24, 200], [ 48, 8], [ 96, 76], [192, 113],
|
||||
[157, 5], [ 39, 138], [ 78, 101], [156, 47], [ 37, 225], [ 74, 36], [148, 15], [ 53, 33],
|
||||
[106, 53], [212, 147], [181, 142], [119, 218], [238, 240], [193, 18], [159, 130], [ 35, 69],
|
||||
[ 70, 29], [140, 181], [ 5, 194], [ 10, 125], [ 20, 106], [ 40, 39], [ 80, 249], [160, 185],
|
||||
[ 93, 201], [186, 154], [105, 9], [210, 120], [185, 77], [111, 228], [222, 114], [161, 166],
|
||||
[ 95, 6], [190, 191], [ 97, 139], [194, 98], [153, 102], [ 47, 221], [ 94, 48], [188, 253],
|
||||
[101, 226], [202, 152], [137, 37], [ 15, 179], [ 30, 16], [ 60, 145], [120, 34], [240, 136],
|
||||
[253, 54], [231, 208], [211, 148], [187, 206], [107, 143], [214, 150], [177, 219], [127, 189],
|
||||
[254, 241], [225, 210], [223, 19], [163, 92], [ 91, 131], [182, 56], [113, 70], [226, 64],
|
||||
[217, 30], [175, 66], [ 67, 182], [134, 163], [ 17, 195], [ 34, 72], [ 68, 126], [136, 110],
|
||||
[ 13, 107], [ 26, 58], [ 52, 40], [104, 84], [208, 250], [189, 133], [103, 186], [206, 61],
|
||||
[129, 202], [ 31, 94], [ 62, 155], [124, 159], [248, 10], [237, 21], [199, 121], [147, 43],
|
||||
[ 59, 78], [118, 212], [236, 229], [197, 172], [151, 115], [ 51, 243], [102, 167], [204, 87],
|
||||
[133, 7], [ 23, 112], [ 46, 192], [ 92, 247], [184, 140], [109, 128], [218, 99], [169, 13],
|
||||
[ 79, 103], [158, 74], [ 33, 222], [ 66, 237], [132, 49], [ 21, 197], [ 42, 254], [ 84, 24],
|
||||
[168, 227], [ 77, 165], [154, 153], [ 41, 119], [ 82, 38], [164, 184], [ 85, 180], [170, 124],
|
||||
[ 73, 17], [146, 68], [ 57, 146], [114, 217], [228, 35], [213, 32], [183, 137], [115, 46],
|
||||
[230, 55], [209, 63], [191, 209], [ 99, 91], [198, 149], [145, 188], [ 63, 207], [126, 205],
|
||||
[252, 144], [229, 135], [215, 151], [179, 178], [123, 220], [246, 252], [241, 190], [255, 97],
|
||||
[227, 242], [219, 86], [171, 211], [ 75, 171], [150, 20], [ 49, 42], [ 98, 93], [196, 158],
|
||||
[149, 132], [ 55, 60], [110, 57], [220, 83], [165, 71], [ 87, 109], [174, 65], [ 65, 162],
|
||||
[130, 31], [ 25, 45], [ 50, 67], [100, 216], [200, 183], [141, 123], [ 7, 164], [ 14, 118],
|
||||
[ 28, 196], [ 56, 23], [112, 73], [224, 236], [221, 127], [167, 12], [ 83, 111], [166, 246],
|
||||
[ 81, 108], [162, 161], [ 89, 59], [178, 82], [121, 41], [242, 157], [249, 85], [239, 170],
|
||||
[195, 251], [155, 96], [ 43, 134], [ 86, 177], [172, 187], [ 69, 204], [138, 62], [ 9, 90],
|
||||
[ 18, 203], [ 36, 89], [ 72, 95], [144, 176], [ 61, 156], [122, 169], [244, 160], [245, 81],
|
||||
[247, 11], [243, 245], [251, 22], [235, 235], [203, 122], [139, 117], [ 11, 44], [ 22, 215],
|
||||
[ 44, 79], [ 88, 174], [176, 213], [125, 233], [250, 230], [233, 231], [207, 173], [131, 232],
|
||||
[ 27, 116], [ 54, 214], [108, 244], [216, 234], [173, 168], [ 71, 80], [142, 88], [ 1, 175],
|
||||
];
|
||||
|
||||
/**
|
||||
* @var int[]
|
||||
*/
|
||||
protected array $num = [];
|
||||
|
||||
/**
|
||||
* Polynomial constructor.
|
||||
*/
|
||||
public function __construct(array $num = null, int $shift = null){
|
||||
$this->setNum($num ?? [1], $shift);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function getNum():array{
|
||||
return $this->num;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int[] $num
|
||||
* @param int|null $shift
|
||||
*
|
||||
* @return \chillerlan\QRCode\Helpers\Polynomial
|
||||
*/
|
||||
public function setNum(array $num, int $shift = null):Polynomial{
|
||||
$offset = 0;
|
||||
$numCount = count($num);
|
||||
|
||||
while($offset < $numCount && $num[$offset] === 0){
|
||||
$offset++;
|
||||
}
|
||||
|
||||
$this->num = array_fill(0, $numCount - $offset + ($shift ?? 0), 0);
|
||||
|
||||
for($i = 0; $i < $numCount - $offset; $i++){
|
||||
$this->num[$i] = $num[$i + $offset];
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int[] $e
|
||||
*
|
||||
* @return \chillerlan\QRCode\Helpers\Polynomial
|
||||
*/
|
||||
public function multiply(array $e):Polynomial{
|
||||
$n = array_fill(0, count($this->num) + count($e) - 1, 0);
|
||||
|
||||
foreach($this->num as $i => $vi){
|
||||
$vi = $this->glog($vi);
|
||||
|
||||
foreach($e as $j => $vj){
|
||||
$n[$i + $j] ^= $this->gexp($vi + $this->glog($vj));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$this->setNum($n);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int[] $e
|
||||
*
|
||||
* @return \chillerlan\QRCode\Helpers\Polynomial
|
||||
*/
|
||||
public function mod(array $e):Polynomial{
|
||||
$n = $this->num;
|
||||
|
||||
if(count($n) - count($e) < 0){
|
||||
return $this;
|
||||
}
|
||||
|
||||
$ratio = $this->glog($n[0]) - $this->glog($e[0]);
|
||||
|
||||
foreach($e as $i => $v){
|
||||
$n[$i] ^= $this->gexp($this->glog($v) + $ratio);
|
||||
}
|
||||
|
||||
$this->setNum($n)->mod($e);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \chillerlan\QRCode\QRCodeException
|
||||
*/
|
||||
public function glog(int $n):int{
|
||||
|
||||
if($n < 1){
|
||||
throw new QRCodeException(sprintf('log(%s)', $n));
|
||||
}
|
||||
|
||||
return Polynomial::table[$n][1];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function gexp(int $n):int{
|
||||
|
||||
if($n < 0){
|
||||
$n += 255;
|
||||
}
|
||||
elseif($n >= 256){
|
||||
$n -= 255;
|
||||
}
|
||||
|
||||
return Polynomial::table[$n][0];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Class PolynomialTest
|
||||
*
|
||||
* @filesource PolynomialTest.php
|
||||
* @created 09.02.2016
|
||||
* @package chillerlan\QRCodeTest\Helpers
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @copyright 2015 Smiley
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace chillerlan\QRCodeTest\Helpers;
|
||||
|
||||
use chillerlan\QRCode\Helpers\Polynomial;
|
||||
use chillerlan\QRCode\QRCodeException;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Polynomial coverage test
|
||||
*/
|
||||
final class PolynomialTest extends TestCase{
|
||||
|
||||
protected Polynomial $polynomial;
|
||||
|
||||
protected function setUp():void{
|
||||
$this->polynomial = new Polynomial;
|
||||
}
|
||||
|
||||
public function testGexp():void{
|
||||
$this::assertSame(142, $this->polynomial->gexp(-1));
|
||||
$this::assertSame(133, $this->polynomial->gexp(128));
|
||||
$this::assertSame(2, $this->polynomial->gexp(256));
|
||||
}
|
||||
|
||||
public function testGlogException():void{
|
||||
$this->expectException(QRCodeException::class);
|
||||
$this->expectExceptionMessage('log(0)');
|
||||
|
||||
$this->polynomial->glog(0);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user