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