Decoder.php 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. <?php
  2. /*
  3. * Copyright 2007 ZXing authors
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. namespace Zxing\Qrcode\Decoder;
  18. use Zxing\ChecksumException;
  19. use Zxing\Common\BitMatrix;
  20. use Zxing\Common\Reedsolomon\GenericGF;
  21. use Zxing\Common\Reedsolomon\ReedSolomonDecoder;
  22. use Zxing\Common\Reedsolomon\ReedSolomonException;
  23. use Zxing\FormatException;
  24. /**
  25. * <p>The main class which implements QR Code decoding -- as opposed to locating and extracting
  26. * the QR Code from an image.</p>
  27. *
  28. * @author Sean Owen
  29. */
  30. final class Decoder
  31. {
  32. private readonly \Zxing\Common\Reedsolomon\ReedSolomonDecoder $rsDecoder;
  33. public function __construct()
  34. {
  35. $this->rsDecoder = new ReedSolomonDecoder(GenericGF::$QR_CODE_FIELD_256);
  36. }
  37. public function decode($variable, $hints = null)
  38. {
  39. if (is_array($variable)) {
  40. return $this->decodeImage($variable, $hints);
  41. } elseif ($variable instanceof BitMatrix) {
  42. return $this->decodeBits($variable, $hints);
  43. } elseif ($variable instanceof BitMatrixParser) {
  44. return $this->decodeParser($variable, $hints);
  45. }
  46. die('decode error Decoder.php');
  47. }
  48. /**
  49. * <p>Convenience method that can decode a QR Code represented as a 2D array of booleans.
  50. * "true" is taken to mean a black module.</p>
  51. *
  52. * @param array $image booleans representing white/black QR Code modules
  53. * @param decoding $hints hints that should be used to influence decoding
  54. *
  55. * @return text and bytes encoded within the QR Code
  56. * @throws FormatException if the QR Code cannot be decoded
  57. * @throws ChecksumException if error correction fails
  58. */
  59. public function decodeImage($image, $hints = null)
  60. {
  61. $dimension = count($image);
  62. $bits = new BitMatrix($dimension);
  63. for ($i = 0; $i < $dimension; $i++) {
  64. for ($j = 0; $j < $dimension; $j++) {
  65. if ($image[$i][$j]) {
  66. $bits->set($j, $i);
  67. }
  68. }
  69. }
  70. return $this->decode($bits, $hints);
  71. }
  72. /**
  73. * <p>Decodes a QR Code represented as a {@link BitMatrix}. A 1 or "true" is taken to mean a black module.</p>
  74. *
  75. * @param BitMatrix $bits booleans representing white/black QR Code modules
  76. * @param decoding $hints hints that should be used to influence decoding
  77. *
  78. * @return text and bytes encoded within the QR Code
  79. * @throws FormatException if the QR Code cannot be decoded
  80. * @throws ChecksumException if error correction fails
  81. */
  82. public function decodeBits($bits, $hints = null)
  83. {
  84. // Construct a parser and read version, error-correction level
  85. $parser = new BitMatrixParser($bits);
  86. $fe = null;
  87. $ce = null;
  88. try {
  89. return $this->decode($parser, $hints);
  90. } catch (FormatException $e) {
  91. $fe = $e;
  92. } catch (ChecksumException $e) {
  93. $ce = $e;
  94. }
  95. try {
  96. // Revert the bit matrix
  97. $parser->remask();
  98. // Will be attempting a mirrored reading of the version and format info.
  99. $parser->setMirror(true);
  100. // Preemptively read the version.
  101. $parser->readVersion();
  102. // Preemptively read the format information.
  103. $parser->readFormatInformation();
  104. /*
  105. * Since we're here, this means we have successfully detected some kind
  106. * of version and format information when mirrored. This is a good sign,
  107. * that the QR code may be mirrored, and we should try once more with a
  108. * mirrored content.
  109. */
  110. // Prepare for a mirrored reading.
  111. $parser->mirror();
  112. $result = $this->decode($parser, $hints);
  113. // Success! Notify the caller that the code was mirrored.
  114. $result->setOther(new QRCodeDecoderMetaData(true));
  115. return $result;
  116. } catch (FormatException $e) {// catch (FormatException | ChecksumException e) {
  117. // Throw the exception from the original reading
  118. if ($fe != null) {
  119. throw $fe;
  120. }
  121. if ($ce != null) {
  122. throw $ce;
  123. }
  124. throw $e;
  125. }
  126. }
  127. private function decodeParser($parser, $hints = null)
  128. {
  129. $version = $parser->readVersion();
  130. $ecLevel = $parser->readFormatInformation()->getErrorCorrectionLevel();
  131. // Read codewords
  132. $codewords = $parser->readCodewords();
  133. // Separate into data blocks
  134. $dataBlocks = DataBlock::getDataBlocks($codewords, $version, $ecLevel);
  135. // Count total number of data bytes
  136. $totalBytes = 0;
  137. foreach ($dataBlocks as $dataBlock) {
  138. $totalBytes += $dataBlock->getNumDataCodewords();
  139. }
  140. $resultBytes = fill_array(0, $totalBytes, 0);
  141. $resultOffset = 0;
  142. // Error-correct and copy data blocks together into a stream of bytes
  143. foreach ($dataBlocks as $dataBlock) {
  144. $codewordBytes = $dataBlock->getCodewords();
  145. $numDataCodewords = $dataBlock->getNumDataCodewords();
  146. $this->correctErrors($codewordBytes, $numDataCodewords);
  147. for ($i = 0; $i < $numDataCodewords; $i++) {
  148. $resultBytes[$resultOffset++] = $codewordBytes[$i];
  149. }
  150. }
  151. // Decode the contents of that stream of bytes
  152. return DecodedBitStreamParser::decode($resultBytes, $version, $ecLevel, $hints);
  153. }
  154. /**
  155. * <p>Given data and error-correction codewords received, possibly corrupted by errors, attempts to
  156. * correct the errors in-place using Reed-Solomon error correction.</p>
  157. *
  158. * @param data $codewordBytes and error correction codewords
  159. * @param number $numDataCodewords of codewords that are data bytes
  160. *
  161. * @throws ChecksumException if error correction fails
  162. */
  163. private function correctErrors(&$codewordBytes, $numDataCodewords)
  164. {
  165. $numCodewords = is_countable($codewordBytes) ? count($codewordBytes) : 0;
  166. // First read into an array of ints
  167. $codewordsInts = fill_array(0, $numCodewords, 0);
  168. for ($i = 0; $i < $numCodewords; $i++) {
  169. $codewordsInts[$i] = $codewordBytes[$i] & 0xFF;
  170. }
  171. $numECCodewords = (is_countable($codewordBytes) ? count($codewordBytes) : 0) - $numDataCodewords;
  172. try {
  173. $this->rsDecoder->decode($codewordsInts, $numECCodewords);
  174. } catch (ReedSolomonException) {
  175. throw ChecksumException::getChecksumInstance();
  176. }
  177. // Copy back into array of bytes -- only need to worry about the bytes that were data
  178. // We don't care about errors in the error-correction codewords
  179. for ($i = 0; $i < $numDataCodewords; $i++) {
  180. $codewordBytes[$i] = $codewordsInts[$i];
  181. }
  182. }
  183. }