From 8eb54d4eef516a0c618a07ae9684bda426c25e30 Mon Sep 17 00:00:00 2001 From: smiley Date: Tue, 31 Jan 2023 19:02:24 +0100 Subject: [PATCH] :octocat: move deinterleaving to ReedSolomonDecoder --- src/Common/ReedSolomonDecoder.php | 93 +++++++++++++++++++++++++++++-- src/Decoder/Decoder.php | 86 ++-------------------------- 2 files changed, 94 insertions(+), 85 deletions(-) diff --git a/src/Common/ReedSolomonDecoder.php b/src/Common/ReedSolomonDecoder.php index a10fc62cc..0cffbf816 100644 --- a/src/Common/ReedSolomonDecoder.php +++ b/src/Common/ReedSolomonDecoder.php @@ -34,11 +34,23 @@ use function array_fill, array_reverse, count; */ final class ReedSolomonDecoder{ + private Version $version; + private EccLevel $eccLevel; + + /** + * ReedSolomonDecoder constructor + */ + public function __construct(Version $version, EccLevel $eccLevel){ + $this->version = $version; + $this->eccLevel = $eccLevel; + } + /** * Error-correct and copy data blocks together into a stream of bytes */ - public function decode(array $dataBlocks):array{ - $resultBytes = []; + public function decode(array $rawCodewords):BitBuffer{ + $dataBlocks = $this->deinterleaveRawBytes($rawCodewords); + $dataBytes = []; foreach($dataBlocks as $dataBlock){ [$numDataCodewords, $codewordBytes] = $dataBlock; @@ -46,11 +58,84 @@ final class ReedSolomonDecoder{ $corrected = $this->correctErrors($codewordBytes, $numDataCodewords); for($i = 0; $i < $numDataCodewords; $i++){ - $resultBytes[] = $corrected[$i]; + $dataBytes[] = $corrected[$i]; } } - return $resultBytes; + return new BitBuffer($dataBytes); + } + + /** + * When QR Codes use multiple data blocks, they are actually interleaved. + * That is, the first byte of data block 1 to n is written, then the second bytes, and so on. This + * method will separate the data into original blocks. + * + * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException + */ + private function deinterleaveRawBytes(array $rawCodewords):array{ + // Figure out the number and size of data blocks used by this version and + // error correction level + [$numEccCodewords, $eccBlocks] = $this->version->getRSBlocks($this->eccLevel); + + // Now establish DataBlocks of the appropriate size and number of data codewords + $result = [];//new DataBlock[$totalBlocks]; + $numResultBlocks = 0; + + foreach($eccBlocks as $blockData){ + [$numEccBlocks, $eccPerBlock] = $blockData; + + for($i = 0; $i < $numEccBlocks; $i++, $numResultBlocks++){ + $result[$numResultBlocks] = [$eccPerBlock, array_fill(0, $numEccCodewords + $eccPerBlock, 0)]; + } + } + + // All blocks have the same amount of data, except that the last n + // (where n may be 0) have 1 more byte. Figure out where these start. + /** @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset */ + $shorterBlocksTotalCodewords = count($result[0][1]); + $longerBlocksStartAt = count($result) - 1; + + while($longerBlocksStartAt >= 0){ + $numCodewords = count($result[$longerBlocksStartAt][1]); + + if($numCodewords == $shorterBlocksTotalCodewords){ + break; + } + + $longerBlocksStartAt--; + } + + $longerBlocksStartAt++; + + $shorterBlocksNumDataCodewords = $shorterBlocksTotalCodewords - $numEccCodewords; + // The last elements of result may be 1 element longer; + // first fill out as many elements as all of them have + $rawCodewordsOffset = 0; + + for($i = 0; $i < $shorterBlocksNumDataCodewords; $i++){ + for($j = 0; $j < $numResultBlocks; $j++){ + $result[$j][1][$i] = $rawCodewords[$rawCodewordsOffset++]; + } + } + + // Fill out the last data block in the longer ones + for($j = $longerBlocksStartAt; $j < $numResultBlocks; $j++){ + $result[$j][1][$shorterBlocksNumDataCodewords] = $rawCodewords[$rawCodewordsOffset++]; + } + + // Now add in error correction blocks + /** @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset */ + $max = count($result[0][1]); + + for($i = $shorterBlocksNumDataCodewords; $i < $max; $i++){ + for($j = 0; $j < $numResultBlocks; $j++){ + $iOffset = $j < $longerBlocksStartAt ? $i : $i + 1; + $result[$j][1][$iOffset] = $rawCodewords[$rawCodewordsOffset++]; + } + } + + // DataBlocks containing original bytes, "de-interleaved" from representation in the QR Code + return $result; } /** diff --git a/src/Decoder/Decoder.php b/src/Decoder/Decoder.php index 95ae74e22..f5beb95f2 100644 --- a/src/Decoder/Decoder.php +++ b/src/Decoder/Decoder.php @@ -78,96 +78,20 @@ final class Decoder{ throw new QRCodeDecoderException('unable to read version or format info'); // @codeCoverageIgnore } - $resultBytes = (new ReedSolomonDecoder)->decode($this->getDataBlocks($rawCodewords)); - // Decode the contents of that stream of bytes + $resultBytes = (new ReedSolomonDecoder($this->version, $this->eccLevel))->decode($rawCodewords); + return $this->decodeBitStream($resultBytes); } /** - * When QR Codes use multiple data blocks, they are actually interleaved. - * That is, the first byte of data block 1 to n is written, then the second bytes, and so on. This - * method will separate the data into original blocks. + * Decode the contents of that stream of bytes * - * @param array $rawCodewords bytes as read directly from the QR Code - * - * @return array DataBlocks containing original bytes, "de-interleaved" from representation in the QR Code * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException */ - private function getDataBlocks(array $rawCodewords):array{ - - // Figure out the number and size of data blocks used by this version and - // error correction level - /** @phan-suppress-next-line PhanTypeMismatchArgumentNullable */ - [$numEccCodewords, $eccBlocks] = $this->version->getRSBlocks($this->eccLevel); - - // Now establish DataBlocks of the appropriate size and number of data codewords - $result = [];//new DataBlock[$totalBlocks]; - $numResultBlocks = 0; - - foreach($eccBlocks as $blockData){ - [$numEccBlocks, $eccPerBlock] = $blockData; - - for($i = 0; $i < $numEccBlocks; $i++, $numResultBlocks++){ - $result[$numResultBlocks] = [$eccPerBlock, array_fill(0, $numEccCodewords + $eccPerBlock, 0)]; - } - } - - // All blocks have the same amount of data, except that the last n - // (where n may be 0) have 1 more byte. Figure out where these start. - /** @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset */ - $shorterBlocksTotalCodewords = count($result[0][1]); - $longerBlocksStartAt = count($result) - 1; - - while($longerBlocksStartAt >= 0){ - $numCodewords = count($result[$longerBlocksStartAt][1]); - - if($numCodewords == $shorterBlocksTotalCodewords){ - break; - } - - $longerBlocksStartAt--; - } - - $longerBlocksStartAt++; - - $shorterBlocksNumDataCodewords = $shorterBlocksTotalCodewords - $numEccCodewords; - // The last elements of result may be 1 element longer; - // first fill out as many elements as all of them have - $rawCodewordsOffset = 0; - - for($i = 0; $i < $shorterBlocksNumDataCodewords; $i++){ - for($j = 0; $j < $numResultBlocks; $j++){ - $result[$j][1][$i] = $rawCodewords[$rawCodewordsOffset++]; - } - } - - // Fill out the last data block in the longer ones - for($j = $longerBlocksStartAt; $j < $numResultBlocks; $j++){ - $result[$j][1][$shorterBlocksNumDataCodewords] = $rawCodewords[$rawCodewordsOffset++]; - } - - // Now add in error correction blocks - /** @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset */ - $max = count($result[0][1]); - - for($i = $shorterBlocksNumDataCodewords; $i < $max; $i++){ - for($j = 0; $j < $numResultBlocks; $j++){ - $iOffset = $j < $longerBlocksStartAt ? $i : $i + 1; - $result[$j][1][$iOffset] = $rawCodewords[$rawCodewordsOffset++]; - } - } - - return $result; - } - - /** - * @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException - */ - private function decodeBitStream(array $bytes):DecoderResult{ - $bitBuffer = new BitBuffer($bytes); + private function decodeBitStream(BitBuffer $bitBuffer):DecoderResult{ + $versionNumber = $this->version->getVersionNumber(); $symbolSequence = -1; $parityData = -1; - $versionNumber = $this->version->getVersionNumber(); $eciCharset = null; $fc1InEffect = false; $result = '';