ReedSolomonEncoder   A
last analyzed

Complexity

Total Complexity 13

Size/Duplication

Total Lines 97
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 48
c 1
b 0
f 0
dl 0
loc 97
rs 10
wmc 13

4 Methods

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