QRCode.php 14 KB

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