ReedSolomonEncoder.php 3.0 KB

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