Passed
Push — main ( 06a3ca...9c0f6a )
by smiley
01:54
created

ReedSolomonEncoder::interleaveEcBytes()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 40
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 26
c 1
b 0
f 0
nc 6
nop 1
dl 0
loc 40
rs 9.504
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
 * 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 => $block){
58
			[$rsBlockTotal, $dataByteCount] = $block;
59
60
			$dataBytes[$key] = [];
61
62
			for($i = 0; $i < $dataByteCount; $i++){
63
				$dataBytes[$key][$i] = $bitBufferData[$i + $dataByteOffset] & 0xff;
64
			}
65
66
			$ecByteCount    = $rsBlockTotal - $dataByteCount;
67
			$ecBytes[$key]  = $this->encode($dataBytes[$key], $ecByteCount);
68
			$maxDataBytes   = max($maxDataBytes, $dataByteCount);
69
			$maxEcBytes     = max($maxEcBytes, $ecByteCount);
70
			$dataByteOffset += $dataByteCount;
71
		}
72
73
		$this->interleavedData      = array_fill(0, $this->version->getTotalCodewords(), 0);
74
		$this->interleavedDataIndex = 0;
75
		$numRsBlocks                = $l1 + $l2;
76
77
		$this->interleave($dataBytes, $maxDataBytes, $numRsBlocks);
78
		$this->interleave($ecBytes, $maxEcBytes, $numRsBlocks);
79
80
		return $this->interleavedData;
81
	}
82
83
	/**
84
	 *
85
	 */
86
	private function encode(array $dataBytes, int $ecByteCount):array{
87
		$rsPoly = new GenericGFPoly([1]);
88
89
		for($i = 0; $i < $ecByteCount; $i++){
90
			$rsPoly = $rsPoly->multiply(new GenericGFPoly([1, GF256::exp($i)]));
91
		}
92
93
		$rsPolyDegree = $rsPoly->getDegree();
94
95
		$modCoefficients = (new GenericGFPoly($dataBytes, $rsPolyDegree))
96
			->mod($rsPoly)
97
			->getCoefficients()
98
		;
99
100
		$ecBytes = array_fill(0, $rsPolyDegree, 0);
101
		$count   = count($modCoefficients) - $rsPolyDegree;
102
103
		foreach($ecBytes as $i => &$val){
104
			$modIndex = $i + $count;
105
			$val      = $modIndex >= 0 ? $modCoefficients[$modIndex] : 0;
106
		}
107
108
		return $ecBytes;
109
	}
110
111
	/**
112
	 *
113
	 */
114
	private function interleave(array $byteArray, int $maxBytes, int $numRsBlocks):void{
115
		for($x = 0; $x < $maxBytes; $x++){
116
			for($y = 0; $y < $numRsBlocks; $y++){
117
				if($x < count($byteArray[$y])){
118
					$this->interleavedData[$this->interleavedDataIndex++] = $byteArray[$y][$x];
119
				}
120
			}
121
		}
122
	}
123
124
}
125