mirror of
https://github.com/chillerlan/php-qrcode.git
synced 2026-10-11 22:35:53 +00:00
:octocat: extract ECC/RS operations
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
<?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{
|
||||
[$l1, $l2, $b1, $b2] = $this->version->getRSBlocks($this->eccLevel->getLevel());
|
||||
|
||||
$numRsBlocks = $l1 + $l2;
|
||||
$ecBytes = new SplFixedArray($numRsBlocks);
|
||||
$rsBlocks = array_fill(0, $l1, [$b1, $b2]);
|
||||
|
||||
if($l2 > 0){
|
||||
$rsBlocks = array_merge($rsBlocks, array_fill(0, $l2, [$b1 + 1, $b2 + 1]));
|
||||
}
|
||||
|
||||
$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];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+7
-111
@@ -12,12 +12,12 @@
|
||||
|
||||
namespace chillerlan\QRCode\Data;
|
||||
|
||||
use chillerlan\QRCode\Common\{EccLevel, Mode, Version};
|
||||
use chillerlan\QRCode\Common\{EccLevel, Mode, ReedSolomon, Version};
|
||||
use chillerlan\QRCode\QRCode;
|
||||
use chillerlan\QRCode\Helpers\{BitBuffer, Polynomial};
|
||||
use chillerlan\QRCode\Helpers\BitBuffer;
|
||||
use chillerlan\Settings\SettingsContainerInterface;
|
||||
|
||||
use function array_fill, array_merge, count, max, range, sprintf;
|
||||
use function range, sprintf;
|
||||
|
||||
/**
|
||||
* Processes the binary data and maps it on a matrix which is then being returned
|
||||
@@ -29,16 +29,6 @@ class QRData{
|
||||
*/
|
||||
protected Version $version;
|
||||
|
||||
/**
|
||||
* ECC temp data
|
||||
*/
|
||||
protected array $ecdata;
|
||||
|
||||
/**
|
||||
* ECC temp data
|
||||
*/
|
||||
protected array $dcdata;
|
||||
|
||||
/**
|
||||
* @var \chillerlan\QRCode\Data\QRDataModeInterface[]
|
||||
*/
|
||||
@@ -109,9 +99,12 @@ 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);
|
||||
|
||||
return (new QRMatrix($this->version, $this->eccLevel))
|
||||
->init($maskPattern, $test)
|
||||
->mapData($this->maskECC(), $maskPattern)
|
||||
->mapData($data, $maskPattern)
|
||||
;
|
||||
}
|
||||
|
||||
@@ -223,101 +216,4 @@ class QRData{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* ECC masking
|
||||
*
|
||||
* ISO/IEC 18004:2000 Section 8.5 ff
|
||||
*
|
||||
* @see http://www.thonky.com/qr-code-tutorial/error-correction-coding
|
||||
*/
|
||||
protected function maskECC():array{
|
||||
[$l1, $l2, $b1, $b2] = $this->eccLevel->getRSBlocks($this->version->getVersionNumber());
|
||||
|
||||
$rsBlocks = array_fill(0, $l1, [$b1, $b2]);
|
||||
$rsCount = $l1 + $l2;
|
||||
$this->ecdata = array_fill(0, $rsCount, []);
|
||||
$this->dcdata = $this->ecdata;
|
||||
|
||||
if($l2 > 0){
|
||||
$rsBlocks = array_merge($rsBlocks, array_fill(0, $l2, [$b1 + 1, $b2 + 1]));
|
||||
}
|
||||
|
||||
$totalCodeCount = 0;
|
||||
$maxDcCount = 0;
|
||||
$maxEcCount = 0;
|
||||
$offset = 0;
|
||||
|
||||
$bitBuffer = $this->bitBuffer->getBuffer();
|
||||
|
||||
foreach($rsBlocks as $key => $block){
|
||||
[$rsBlockTotal, $dcCount] = $block;
|
||||
|
||||
$ecCount = $rsBlockTotal - $dcCount;
|
||||
$maxDcCount = max($maxDcCount, $dcCount);
|
||||
$maxEcCount = max($maxEcCount, $ecCount);
|
||||
$this->dcdata[$key] = array_fill(0, $dcCount, null);
|
||||
|
||||
foreach($this->dcdata[$key] as $a => $_z){
|
||||
$this->dcdata[$key][$a] = 0xff & $bitBuffer[$a + $offset];
|
||||
}
|
||||
|
||||
[$num, $add] = $this->poly($key, $ecCount);
|
||||
|
||||
foreach($this->ecdata[$key] as $c => $_){
|
||||
$modIndex = $c + $add;
|
||||
$this->ecdata[$key][$c] = $modIndex >= 0 ? $num[$modIndex] : 0;
|
||||
}
|
||||
|
||||
$offset += $dcCount;
|
||||
$totalCodeCount += $rsBlockTotal;
|
||||
}
|
||||
|
||||
$data = array_fill(0, $totalCodeCount, null);
|
||||
$index = 0;
|
||||
|
||||
$mask = function(array $arr, int $count) use (&$data, &$index, $rsCount):void{
|
||||
for($x = 0; $x < $count; $x++){
|
||||
for($y = 0; $y < $rsCount; $y++){
|
||||
if($x < count($arr[$y])){
|
||||
$data[$index] = $arr[$y][$x];
|
||||
$index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$mask($this->dcdata, $maxDcCount);
|
||||
$mask($this->ecdata, $maxEcCount);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* helper method for the polynomial operations
|
||||
*/
|
||||
protected function poly(int $key, int $count):array{
|
||||
$rsPoly = new Polynomial;
|
||||
$modPoly = new Polynomial;
|
||||
|
||||
for($i = 0; $i < $count; $i++){
|
||||
$modPoly->setNum([1, $modPoly->gexp($i)]);
|
||||
$rsPoly->multiply($modPoly->getNum());
|
||||
}
|
||||
|
||||
$rsPolyCount = count($rsPoly->getNum());
|
||||
|
||||
$modPoly
|
||||
->setNum($this->dcdata[$key], $rsPolyCount - 1)
|
||||
->mod($rsPoly->getNum())
|
||||
;
|
||||
|
||||
$this->ecdata[$key] = array_fill(0, $rsPolyCount - 1, null);
|
||||
$num = $modPoly->getNum();
|
||||
|
||||
return [
|
||||
$num,
|
||||
count($num) - count($this->ecdata[$key]),
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ use chillerlan\QRCode\Common\{EccLevel, Version};
|
||||
use chillerlan\QRCode\QRCode;
|
||||
use Closure;
|
||||
|
||||
use SplFixedArray;
|
||||
use function array_fill, array_push, array_unshift, count, floor, max, min, range;
|
||||
|
||||
/**
|
||||
@@ -493,14 +494,14 @@ final class QRMatrix{
|
||||
*
|
||||
* @see \chillerlan\QRCode\Data\QRData::maskECC()
|
||||
*
|
||||
* @param int[] $data
|
||||
* @param int $maskPattern
|
||||
* @param \SplFixedArray<int> $data
|
||||
* @param int $maskPattern
|
||||
*
|
||||
* @return \chillerlan\QRCode\Data\QRMatrix
|
||||
*/
|
||||
public function mapData(array $data, int $maskPattern):QRMatrix{
|
||||
public function mapData(SplFixedArray $data, int $maskPattern):QRMatrix{
|
||||
$this->maskPattern = $maskPattern;
|
||||
$byteCount = count($data);
|
||||
$byteCount = $data->count();
|
||||
$y = $this->moduleCount - 1;
|
||||
$inc = -1;
|
||||
$byteIndex = 0;
|
||||
|
||||
@@ -52,14 +52,18 @@ abstract class DatainterfaceTestAbstract extends TestCase{
|
||||
/**
|
||||
* Tests ecc masking and verifies against a sample
|
||||
*/
|
||||
public function testMaskEcc():void{
|
||||
/* public function testMaskEcc():void{
|
||||
$this->dataInterface->setData([$this->testdata]);
|
||||
|
||||
$maskECC = $this->reflection->getMethod('maskECC');
|
||||
$maskECC->setAccessible(true);
|
||||
|
||||
$this::assertSame($this->expected, $maskECC->invoke($this->dataInterface));
|
||||
}
|
||||
$bitBuffer = $this->reflection->getProperty('bitBuffer');
|
||||
$bitBuffer->setAccessible(true);
|
||||
$bb = $bitBuffer->getValue($this->dataInterface);
|
||||
|
||||
$this::assertSame($this->expected, $maskECC->invokeArgs($this->dataInterface, [$bb->getBuffer()]));
|
||||
}*/
|
||||
|
||||
/**
|
||||
* @see testInitMatrix()
|
||||
|
||||
Reference in New Issue
Block a user