Passed
Push — v5 ( 043137...387bee )
by smiley
09:56
created

ReedSolomonEncoder   A

Complexity

Total Complexity 12

Size/Duplication

Total Lines 88
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 45
dl 0
loc 88
rs 10
c 1
b 0
f 0
wmc 12

3 Methods

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