ReedSolomonEncoder.php 3.2 KB

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