1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
|
<?php
/**
* Class ReedSolomonEncoder
*
* @created 07.01.2021
* @author smiley <smiley@chillerlan.net>
* @copyright 2021 smiley
* @license MIT
*/
namespace chillerlan\QRCode\Data;
use chillerlan\QRCode\Common\{BitBuffer, EccLevel, GenericGFPoly, GF256, Version};
use function array_fill, array_merge, count, max;
/**
* Reed-Solomon encoding - ISO/IEC 18004:2000 Section 8.5 ff
*
* @see http://www.thonky.com/qr-code-tutorial/error-correction-coding
*/
final class ReedSolomonEncoder{
private Version $version;
private EccLevel $eccLevel;
private array $interleavedData;
private int $interleavedDataIndex;
/**
* ReedSolomonDecoder constructor
*/
public function __construct(Version $version, EccLevel $eccLevel){
$this->version = $version;
$this->eccLevel = $eccLevel;
}
/**
* ECC encoding and interleaving
*
* @throws \chillerlan\QRCode\QRCodeException
*/
public function interleaveEcBytes(BitBuffer $bitBuffer):array{
[$numEccCodewords, [[$l1, $b1], [$l2, $b2]]] = $this->version->getRSBlocks($this->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 => [$rsBlockTotal, $dataByteCount]){
$dataBytes[$key] = [];
for($i = 0; $i < $dataByteCount; $i++){
$dataBytes[$key][$i] = ($bitBufferData[($i + $dataByteOffset)] & 0xff);
}
$ecByteCount = ($rsBlockTotal - $dataByteCount);
$ecBytes[$key] = $this->encode($dataBytes[$key], $ecByteCount);
$maxDataBytes = max($maxDataBytes, $dataByteCount);
$maxEcBytes = max($maxEcBytes, $ecByteCount);
$dataByteOffset += $dataByteCount;
}
$this->interleavedData = array_fill(0, $this->version->getTotalCodewords(), 0);
$this->interleavedDataIndex = 0;
$numRsBlocks = ($l1 + $l2);
$this->interleave($dataBytes, $maxDataBytes, $numRsBlocks);
$this->interleave($ecBytes, $maxEcBytes, $numRsBlocks);
return $this->interleavedData;
}
/**
*
*/
private function encode(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();
$modCoefficients = (new GenericGFPoly($dataBytes, $rsPolyDegree))
->mod($rsPoly)
->getCoefficients()
;
$ecBytes = array_fill(0, $rsPolyDegree, 0);
$count = (count($modCoefficients) - $rsPolyDegree);
foreach($ecBytes as $i => &$val){
$modIndex = ($i + $count);
$val = 0;
if($modIndex >= 0){
$val = $modCoefficients[$modIndex];
}
}
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];
}
}
}
}
}
|