QRCode.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617
  1. <?php
  2. /**
  3. * Class QRCode
  4. *
  5. * @filesource QRCode.php
  6. * @created 26.11.2015
  7. * @package chillerlan\QRCode
  8. * @author Smiley <smiley@chillerlan.net>
  9. * @copyright 2015 Smiley
  10. * @license MIT
  11. */
  12. namespace chillerlan\QRCode;
  13. use chillerlan\QRCode\Data\AlphaNum;
  14. use chillerlan\QRCode\Data\Byte;
  15. use chillerlan\QRCode\Data\Kanji;
  16. use chillerlan\QRCode\Data\Number;
  17. use chillerlan\QRCode\Output\QROutputInterface;
  18. /**
  19. * @link https://github.com/kazuhikoarase/qrcode-generator/tree/master/php
  20. * @link http://www.thonky.com/qr-code-tutorial/
  21. */
  22. class QRCode{
  23. /**
  24. * API constants
  25. */
  26. const OUTPUT_STRING_TEXT = 0;
  27. const OUTPUT_STRING_JSON = 1;
  28. const OUTPUT_STRING_HTML = 2;
  29. const OUTPUT_IMAGE_PNG = 'png';
  30. const OUTPUT_IMAGE_JPG = 'jpg';
  31. const OUTPUT_IMAGE_GIF = 'gif';
  32. const ERROR_CORRECT_LEVEL_L = 1; // 7%.
  33. const ERROR_CORRECT_LEVEL_M = 0; // 15%.
  34. const ERROR_CORRECT_LEVEL_Q = 3; // 25%.
  35. const ERROR_CORRECT_LEVEL_H = 2; // 30%.
  36. // max bits @ ec level L:07 M:15 Q:25 H:30 %
  37. const TYPE_01 = 1; // 152 128 104 72
  38. const TYPE_02 = 2; // 272 224 176 128
  39. const TYPE_03 = 3; // 440 352 272 208
  40. const TYPE_04 = 4; // 640 512 384 288
  41. const TYPE_05 = 5; // 864 688 496 368
  42. const TYPE_06 = 6; // 1088 864 608 480
  43. const TYPE_07 = 7; // 1248 992 704 528
  44. const TYPE_08 = 8; // 1552 1232 880 688
  45. const TYPE_09 = 9; // 1856 1456 1056 800
  46. const TYPE_10 = 10; // 2192 1728 1232 976
  47. /**
  48. * @var array
  49. */
  50. protected $matrix = [];
  51. /**
  52. * @var int
  53. */
  54. protected $pixelCount = 0;
  55. /**
  56. * @var int
  57. */
  58. protected $typeNumber;
  59. /**
  60. * @var int
  61. */
  62. protected $errorCorrectLevel;
  63. /**
  64. * @var \chillerlan\QRCode\BitBuffer
  65. */
  66. protected $bitBuffer;
  67. /**
  68. * @var \chillerlan\QRCode\Data\QRDataInterface
  69. */
  70. protected $qrDataInterface;
  71. /**
  72. * @var \chillerlan\QRCode\Output\QROutputInterface
  73. */
  74. protected $qrOutputInterface;
  75. /**
  76. * QRCode constructor.
  77. *
  78. * @param string $data
  79. * @param \chillerlan\QRCode\Output\QROutputInterface $output
  80. * @param \chillerlan\QRCode\QROptions|null $options
  81. */
  82. public function __construct($data, QROutputInterface $output, QROptions $options = null){
  83. $this->qrOutputInterface = $output;
  84. $this->bitBuffer = new BitBuffer;
  85. $this->setData($data, $options);
  86. }
  87. /**
  88. * @param string $data
  89. * @param \chillerlan\QRCode\QROptions|null $options
  90. *
  91. * @return $this
  92. * @throws \chillerlan\QRCode\QRCodeException
  93. */
  94. public function setData($data, QROptions $options = null){
  95. $data = trim($data);
  96. if(empty($data)){
  97. throw new QRCodeException('No data given.');
  98. }
  99. if(!$options instanceof QROptions){
  100. $options = new QROptions;
  101. }
  102. if(!in_array($options->errorCorrectLevel, QRConst::RSBLOCK, true)){
  103. throw new QRCodeException('Invalid error correct level: '.$options->errorCorrectLevel);
  104. }
  105. $this->errorCorrectLevel = $options->errorCorrectLevel;
  106. switch(true){
  107. case Util::isAlphaNum($data):
  108. $mode = Util::isNumber($data) ? QRConst::MODE_NUMBER : QRConst::MODE_ALPHANUM;
  109. break;
  110. case Util::isKanji($data):
  111. $mode = QRConst::MODE_KANJI;
  112. break;
  113. default:
  114. $mode = QRConst::MODE_BYTE;
  115. break;
  116. }
  117. $qrDataInterface = [
  118. QRConst::MODE_ALPHANUM => AlphaNum::class,
  119. QRConst::MODE_BYTE => Byte::class,
  120. QRConst::MODE_KANJI => Kanji::class,
  121. QRConst::MODE_NUMBER => Number::class,
  122. ][$mode];
  123. $this->qrDataInterface = new $qrDataInterface($data);
  124. $this->typeNumber = intval($options->typeNumber);
  125. if($this->typeNumber < 1 || $this->typeNumber > 10){
  126. $this->typeNumber = $this->getTypeNumber($mode);
  127. }
  128. return $this;
  129. }
  130. /**
  131. * @param $mode
  132. *
  133. * @return int
  134. * @throws \chillerlan\QRCode\QRCodeException
  135. */
  136. protected function getTypeNumber($mode){
  137. $length = $this->qrDataInterface->dataLength;
  138. if($this->qrDataInterface->mode === QRConst::MODE_KANJI){
  139. $length = floor($this->qrDataInterface->dataLength / 2);
  140. }
  141. foreach(range(1, 10) as $type){
  142. if($length <= Util::getMaxLength($type, $mode, $this->errorCorrectLevel)){
  143. return $type;
  144. }
  145. }
  146. throw new QRCodeException('Unable to determine type number.'); // @codeCoverageIgnore
  147. }
  148. /**
  149. * @return mixed
  150. */
  151. public function output(){
  152. $this->qrOutputInterface->setMatrix($this->getRawData());
  153. return $this->qrOutputInterface->dump();
  154. }
  155. /**
  156. * @return array
  157. */
  158. public function getRawData(){
  159. $minLostPoint = 0;
  160. $maskPattern = 0;
  161. for($pattern = 0; $pattern <= 7; $pattern++){
  162. $this->getMatrix(true, $pattern);
  163. $lostPoint = 0;
  164. $darkCount = 0;
  165. $range1 = range(0, $this->pixelCount-1);
  166. $range2 = range(0, $this->pixelCount-2);
  167. $range3 = range(0, $this->pixelCount-7);
  168. $range4 = range(-1, 1);
  169. // LEVEL1
  170. foreach($range1 as $row){
  171. foreach($range1 as $col){
  172. $sameCount = 0;
  173. foreach($range4 as $rr){
  174. if($row + $rr < 0 || $this->pixelCount <= $row + $rr){
  175. continue;
  176. }
  177. foreach($range4 as $cr){
  178. if(($rr === 0 && $cr === 0) || ($col + $cr < 0 || $this->pixelCount <= $col + $cr)){
  179. continue;
  180. }
  181. if($this->matrix[$row + $rr][$col + $cr] === $this->matrix[$row][$col]){
  182. $sameCount++;
  183. }
  184. }
  185. }
  186. if($sameCount > 5){
  187. $lostPoint += (3 + $sameCount - 5);
  188. }
  189. }
  190. }
  191. // LEVEL2
  192. foreach($range2 as $row){
  193. foreach($range2 as $col){
  194. $count = 0;
  195. if(
  196. $this->matrix[$row ][$col ]
  197. || $this->matrix[$row ][$col + 1]
  198. || $this->matrix[$row + 1][$col ]
  199. || $this->matrix[$row + 1][$col + 1]
  200. ){
  201. $count++;
  202. }
  203. if($count === 0 || $count === 4){
  204. $lostPoint += 3;
  205. }
  206. }
  207. }
  208. // LEVEL3
  209. foreach($range1 as $row){
  210. foreach($range3 as $col){
  211. if(
  212. $this->matrix[$row][$col ]
  213. && !$this->matrix[$row][$col + 1]
  214. && $this->matrix[$row][$col + 2]
  215. && $this->matrix[$row][$col + 3]
  216. && $this->matrix[$row][$col + 4]
  217. && !$this->matrix[$row][$col + 5]
  218. && $this->matrix[$row][$col + 6]
  219. ){
  220. $lostPoint += 40;
  221. }
  222. }
  223. }
  224. foreach($range1 as $col){
  225. foreach($range3 as $row){
  226. if(
  227. $this->matrix[$row ][$col]
  228. && !$this->matrix[$row + 1][$col]
  229. && $this->matrix[$row + 2][$col]
  230. && $this->matrix[$row + 3][$col]
  231. && $this->matrix[$row + 4][$col]
  232. && !$this->matrix[$row + 5][$col]
  233. && $this->matrix[$row + 6][$col]
  234. ){
  235. $lostPoint += 40;
  236. }
  237. }
  238. }
  239. // LEVEL4
  240. foreach($range1 as $col){
  241. foreach($range1 as $row){
  242. if($this->matrix[$row][$col]){
  243. $darkCount++;
  244. }
  245. }
  246. }
  247. $lostPoint += (abs(100 * $darkCount / $this->pixelCount / $this->pixelCount - 50) / 5) * 10;
  248. if($pattern === 0 || $minLostPoint > $lostPoint){
  249. $minLostPoint = $lostPoint;
  250. $maskPattern = $pattern;
  251. }
  252. }
  253. $this->getMatrix(false, $maskPattern);
  254. return $this->matrix;
  255. }
  256. /**
  257. * @param bool $test
  258. */
  259. protected function setTypeNumber($test){
  260. $bits = Util::getBCHTypeNumber($this->typeNumber);
  261. for($i = 0; $i < 18; $i++){
  262. $a = (int)floor($i / 3);
  263. $b = $i % 3 + $this->pixelCount - 8 - 3;
  264. $this->matrix[$a][$b] = $this->matrix[$b][$a] = !$test && (($bits >> $i) & 1) === 1;
  265. }
  266. }
  267. /**
  268. * @param bool $test
  269. * @param int $pattern
  270. */
  271. protected function setTypeInfo($test, $pattern){
  272. $this->setPattern();
  273. $bits = Util::getBCHTypeInfo(($this->errorCorrectLevel << 3) | $pattern);
  274. for($i = 0; $i < 15; $i++){
  275. $mod = !$test && (($bits >> $i) & 1) === 1;
  276. switch(true){
  277. case $i < 6: $this->matrix[$i ][8] = $mod; break;
  278. case $i < 8: $this->matrix[$i + 1][8] = $mod; break;
  279. default:
  280. $this->matrix[$this->pixelCount - 15 + $i][8] = $mod;
  281. }
  282. switch(true){
  283. case $i < 8: $this->matrix[8][$this->pixelCount - $i - 1] = $mod; break;
  284. case $i < 9: $this->matrix[8][ 15 + 1 - $i - 1] = $mod; break;
  285. default:
  286. $this->matrix[8][15 - $i - 1] = $mod;
  287. }
  288. }
  289. $this->matrix[$this->pixelCount - 8][8] = !$test;
  290. }
  291. /**
  292. * @throws \chillerlan\QRCode\QRCodeException
  293. */
  294. protected function createData(){
  295. $this->bitBuffer->clear();
  296. $MAX_BITS = QRConst::MAX_BITS; // php5 compat
  297. $MAX_BITS = $MAX_BITS[$this->typeNumber][$this->errorCorrectLevel];
  298. /** @noinspection PhpUndefinedFieldInspection */
  299. $this->bitBuffer->put($this->qrDataInterface->mode, 4);
  300. /** @noinspection PhpUndefinedFieldInspection */
  301. $this->bitBuffer->put(
  302. $this->qrDataInterface->mode === QRConst::MODE_KANJI ? floor($this->qrDataInterface->dataLength / 2) : $this->qrDataInterface->dataLength,
  303. $this->qrDataInterface->getLengthInBits($this->typeNumber)
  304. );
  305. $this->qrDataInterface->write($this->bitBuffer);
  306. if($this->bitBuffer->length > $MAX_BITS){
  307. throw new QRCodeException('code length overflow. ('.$this->bitBuffer->length.' > '.$MAX_BITS.'bit)');
  308. }
  309. // end code.
  310. if($this->bitBuffer->length + 4 <= $MAX_BITS){
  311. $this->bitBuffer->put(0, 4);
  312. }
  313. // padding
  314. while($this->bitBuffer->length % 8 !== 0){
  315. $this->bitBuffer->putBit(false);
  316. }
  317. // padding
  318. while(true){
  319. if($this->bitBuffer->length >= $MAX_BITS){
  320. break;
  321. }
  322. $this->bitBuffer->put(QRConst::PAD0, 8);
  323. if($this->bitBuffer->length >= $MAX_BITS){
  324. break;
  325. }
  326. $this->bitBuffer->put(QRConst::PAD1, 8);
  327. }
  328. }
  329. /**
  330. * @return array
  331. * @throws \chillerlan\QRCode\QRCodeException
  332. */
  333. protected function createBytes(){
  334. $totalCodeCount = $maxDcCount = $maxEcCount = $offset = $index = 0;
  335. $rsBlocks = Util::getRSBlocks($this->typeNumber, $this->errorCorrectLevel);
  336. $rsBlockCount = count($rsBlocks);
  337. $dcdata = $ecdata = array_fill(0, $rsBlockCount, null);
  338. foreach($rsBlocks as $key => $value){
  339. $rsBlockTotal = $value[0];
  340. $rsBlockDataCount = $value[1];
  341. $maxDcCount = max($maxDcCount, $rsBlockDataCount);
  342. $maxEcCount = max($maxEcCount, $rsBlockTotal - $rsBlockDataCount);
  343. $dcdata[$key] = array_fill(0, $rsBlockDataCount, null);
  344. foreach($dcdata[$key] as $i => &$_dcdata){
  345. $bdata = $this->bitBuffer->buffer;
  346. $_dcdata = 0xff & $bdata[$i + $offset];
  347. }
  348. $offset += $rsBlockDataCount;
  349. $rsPoly = new Polynomial;
  350. $modPoly = new Polynomial;
  351. foreach(range(0, $rsBlockTotal - $rsBlockDataCount - 1) as $i){
  352. $modPoly->setNum([1, $modPoly->gexp($i)]);
  353. $rsPoly->multiply($modPoly->num);
  354. }
  355. $rsPolyCount = count($rsPoly->num);
  356. $modPoly->setNum($dcdata[$key], $rsPolyCount - 1)->mod($rsPoly->num);
  357. $ecdata[$key] = array_fill(0, $rsPolyCount - 1, null);
  358. $add = count($modPoly->num) - count($ecdata[$key]);
  359. foreach($ecdata[$key] as $i => &$_ecdata){
  360. $modIndex = $i + $add;
  361. $_ecdata = $modIndex >= 0 ? $modPoly->num[$modIndex] : 0;
  362. }
  363. $totalCodeCount += $rsBlockTotal;
  364. }
  365. $data = array_fill(0, $totalCodeCount, null);
  366. $rsrange = range(0, $rsBlockCount - 1);
  367. foreach(range(0, $maxDcCount - 1) as $i){
  368. foreach($rsrange as $key){
  369. if($i < count($dcdata[$key])){
  370. $data[$index++] = $dcdata[$key][$i];
  371. }
  372. }
  373. }
  374. foreach(range(0, $maxEcCount - 1) as $i){
  375. foreach($rsrange as $key){
  376. if($i < count($ecdata[$key])){
  377. $data[$index++] = $ecdata[$key][$i];
  378. }
  379. }
  380. }
  381. return $data;
  382. }
  383. /**
  384. * @param int $pattern
  385. *
  386. * @throws \chillerlan\QRCode\QRCodeException
  387. */
  388. protected function mapData($pattern){
  389. $this->createData();
  390. $data = $this->createBytes();
  391. $inc = -1;
  392. $row = $this->pixelCount - 1;
  393. $bitIndex = 7;
  394. $byteIndex = 0;
  395. $dataCount = count($data);
  396. for($col = $this->pixelCount - 1; $col > 0; $col -= 2){
  397. if($col === 6){
  398. $col--;
  399. }
  400. while(true){
  401. foreach([0, 1] as $c){
  402. $_col = $col - $c;
  403. if($this->matrix[$row][$_col] === null){
  404. $dark = false;
  405. if($byteIndex < $dataCount){
  406. $dark = (($data[$byteIndex] >> $bitIndex) & 1) === 1;
  407. }
  408. $a = $row + $_col;
  409. $m = $row * $_col;
  410. $MASK_PATTERN = [
  411. QRConst::MASK_PATTERN000 => $a % 2,
  412. QRConst::MASK_PATTERN001 => $row % 2,
  413. QRConst::MASK_PATTERN010 => $_col % 3,
  414. QRConst::MASK_PATTERN011 => $a % 3,
  415. QRConst::MASK_PATTERN100 => (floor($row / 2) + floor($_col / 3)) % 2,
  416. QRConst::MASK_PATTERN101 => $m % 2 + $m % 3,
  417. QRConst::MASK_PATTERN110 => ($m % 2 + $m % 3) % 2,
  418. QRConst::MASK_PATTERN111 => ($m % 3 + $a % 2) % 2,
  419. ][$pattern];
  420. if($MASK_PATTERN === 0){
  421. $dark = !$dark;
  422. }
  423. $this->matrix[$row][$_col] = $dark;
  424. $bitIndex--;
  425. if($bitIndex === -1){
  426. $byteIndex++;
  427. $bitIndex = 7;
  428. }
  429. }
  430. }
  431. $row += $inc;
  432. if($row < 0 || $this->pixelCount <= $row){
  433. $row -= $inc;
  434. $inc = -$inc;
  435. break;
  436. }
  437. }
  438. }
  439. }
  440. /**
  441. * @throws \chillerlan\QRCode\QRCodeException
  442. */
  443. protected function setPattern(){
  444. // setupPositionProbePattern
  445. $range = range(-1, 7);
  446. foreach([[0, 0], [$this->pixelCount - 7, 0], [0, $this->pixelCount - 7]] as $grid){
  447. $row = $grid[0];
  448. $col = $grid[1];
  449. foreach($range as $r){
  450. foreach($range as $c){
  451. if($row + $r <= -1 || $this->pixelCount <= $row + $r || $col + $c <= -1 || $this->pixelCount <= $col + $c){
  452. continue;
  453. }
  454. $this->matrix[$row + $r][$col + $c] =
  455. (0 <= $r && $r <= 6 && ($c === 0 || $c === 6))
  456. || (0 <= $c && $c <= 6 && ($r === 0 || $r === 6))
  457. || (2 <= $c && $c <= 4 && 2 <= $r && $r <= 4);
  458. }
  459. }
  460. }
  461. // setupPositionAdjustPattern
  462. $PATTERN_POSITION = QRConst::PATTERN_POSITION; // PHP5 compat
  463. $pos = $PATTERN_POSITION[$this->typeNumber - 1];
  464. $range = range(-2, 2);
  465. foreach($pos as $i => $posI){
  466. foreach($pos as $j => $posJ){
  467. if($this->matrix[$posI][$posJ] !== null){
  468. continue;
  469. }
  470. foreach($range as $row){
  471. foreach($range as $col){
  472. $this->matrix[$posI + $row][$posJ + $col] =
  473. $row === -2 || $row === 2
  474. || $col === -2 || $col === 2
  475. ||($row === 0 && $col === 0);
  476. }
  477. }
  478. }
  479. }
  480. // setupTimingPattern
  481. foreach(range(8, $this->pixelCount - 8) as $i){
  482. if($this->matrix[$i][6] !== null){
  483. continue; // @codeCoverageIgnore
  484. }
  485. $this->matrix[$i][6] = $this->matrix[6][$i] = $i % 2 === 0;
  486. }
  487. }
  488. /**
  489. * @param bool $test
  490. * @param int $maskPattern
  491. *
  492. * @throws \chillerlan\QRCode\QRCodeException
  493. */
  494. protected function getMatrix($test, $maskPattern){
  495. $this->pixelCount = $this->typeNumber * 4 + 17;
  496. $this->matrix = array_fill(0, $this->pixelCount, array_fill(0, $this->pixelCount, null));
  497. $this->setTypeInfo($test, $maskPattern);
  498. if($this->typeNumber >= 7){
  499. $this->setTypeNumber($test);
  500. }
  501. $this->mapData($maskPattern);
  502. }
  503. }