From 8671a6936a84787fc3fba4361ee4d6f6f6517164 Mon Sep 17 00:00:00 2001 From: smiley Date: Fri, 4 Dec 2015 04:34:00 +0100 Subject: [PATCH] :rage4: --- examples/custom.php | 6 +- examples/html.php | 2 +- examples/image.php | 2 +- src/BitBuffer.php | 13 +- src/Data/AlphaNum.php | 41 ++- src/Data/Byte.php | 5 +- src/Data/Kanji.php | 4 +- src/Data/Number.php | 4 +- src/Data/QRDataBase.php | 31 +- src/Data/QRDataInterface.php | 1 + src/Polynomial.php | 53 +--- src/QRCode.php | 585 +++++++++++++++++------------------ src/RSBlock.php | 22 +- src/Util.php | 73 +++-- 14 files changed, 408 insertions(+), 434 deletions(-) diff --git a/examples/custom.php b/examples/custom.php index 4f552b5c1..3732219d8 100644 --- a/examples/custom.php +++ b/examples/custom.php @@ -10,13 +10,11 @@ $starttime = microtime(true); // google authenticator // https://chart.googleapis.com/chart?chs=200x200&chld=M%7C0&cht=qr&chl=otpauth%3A%2F%2Ftotp%2Ftest%3Fsecret%3DB3JX4VCVJDVNXNZ5%26issuer%3Dchillerlan.net -$qrcode = (new QRCode(5, QRConst::ERROR_CORRECT_LEVEL_M)) - ->addData('otpauth://totp/test?secret=B3JX4VCVJDVNXNZ5&issuer=chillerlan.net') - ->make(); +$qrcode = new QRCode('otpauth://totp/test?secret=B3JX4VCVJDVNXNZ5&issuer=chillerlan.net', QRConst::ERROR_CORRECT_LEVEL_M, 5); for($row = 0; $row < $qrcode->moduleCount; $row++){ for($col = 0; $col < $qrcode->moduleCount; $col++){ - echo $qrcode->isDark($row, $col) ? 'X' : ' '; + echo (bool)$qrcode->modules[$row][$col] ? '#' : ' '; } echo PHP_EOL; } diff --git a/examples/html.php b/examples/html.php index 196c87d1f..2a6dfa631 100644 --- a/examples/html.php +++ b/examples/html.php @@ -31,5 +31,5 @@ echo ''; // google authenticator -$qr = (new QRCode)->getMinimumQRCode('otpauth://totp/test?secret=B3JX4VCVJDVNXNZ5&issuer=chillerlan.net', QRConst::ERROR_CORRECT_LEVEL_M); +$qr = new QRCode('otpauth://totp/test?secret=B3JX4VCVJDVNXNZ5&issuer=chillerlan.net', QRConst::ERROR_CORRECT_LEVEL_M); echo $qr->printHTML(); diff --git a/examples/image.php b/examples/image.php index 91a814c8b..97184fa8f 100644 --- a/examples/image.php +++ b/examples/image.php @@ -6,7 +6,7 @@ use codemasher\QRCode\QRCode; use codemasher\QRCode\QRConst; // google authenticator -$qr = (new QRCode)->getMinimumQRCode('otpauth://totp/test?secret=B3JX4VCVJDVNXNZ5&issuer=chillerlan.net', QRConst::ERROR_CORRECT_LEVEL_L); +$qr = new QRCode('otpauth://totp/test?secret=B3JX4VCVJDVNXNZ5&issuer=chillerlan.net', QRConst::ERROR_CORRECT_LEVEL_L); header('Content-type: image/png'); diff --git a/src/BitBuffer.php b/src/BitBuffer.php index ffc559509..a62d3a421 100644 --- a/src/BitBuffer.php +++ b/src/BitBuffer.php @@ -29,7 +29,7 @@ class BitBuffer{ /** * @return string */ - public function __toString(){ +/* public function __toString(){ $buffer = ''; for($i = 0; $i < $this->length; $i++){ @@ -38,19 +38,26 @@ class BitBuffer{ return $buffer; } - +*/ /** * @param $num * @param $length + * + * @return $this */ public function put($num, $length){ + for($i = 0; $i < $length; $i++){ $this->putBit((($num >> ($length - $i - 1))&1) === 1); } + + return $this; } /** * @param $bit + * + * @return $this */ public function putBit($bit){ $bufIndex = floor($this->length / 8); @@ -64,6 +71,8 @@ class BitBuffer{ } $this->length++; + + return $this; } } diff --git a/src/Data/AlphaNum.php b/src/Data/AlphaNum.php index 657119c7d..39677c5b5 100644 --- a/src/Data/AlphaNum.php +++ b/src/Data/AlphaNum.php @@ -4,7 +4,7 @@ * * @filesource AlphaNum.php * @created 25.11.2015 - * @package codemasher\QRCode + * @package codemasher\QRCode\Data * @author Smiley * @copyright 2015 Smiley * @license MIT @@ -13,8 +13,6 @@ namespace codemasher\QRCode\Data; use codemasher\QRCode\BitBuffer; -use codemasher\QRCode\Data\QRDataBase; -use codemasher\QRCode\Data\QRDataInterface; use codemasher\QRCode\QRCodeException; use codemasher\QRCode\QRConst; @@ -58,26 +56,23 @@ class AlphaNum extends QRDataBase implements QRDataInterface{ */ protected function getCode($c){ - if(ord('0') <= $c && $c <= ord('9')){ - return $c - ord('0'); - } - else if(ord('A') <= $c && $c <= ord('Z')){ - return $c - ord('A') + 10; - } - else{ - switch($c){ - case ord(' '): return 36; - case ord('$'): return 37; - case ord('%'): return 38; - case ord('*'): return 39; - case ord('+'): return 40; - case ord('-'): return 41; - case ord('.'): return 42; - case ord('/'): return 43; - case ord(':'): return 44; - default : - throw new QRCodeException('illegal char: '.$c); - } + switch(true){ + case ord('0') <= $c && $c <= ord('9'): return $c - ord('0'); + case ord('A') <= $c && $c <= ord('Z'): return $c - ord('A') + 10; + default: + switch($c){ + case ord(' '): return 36; + case ord('$'): return 37; + case ord('%'): return 38; + case ord('*'): return 39; + case ord('+'): return 40; + case ord('-'): return 41; + case ord('.'): return 42; + case ord('/'): return 43; + case ord(':'): return 44; + default: + throw new QRCodeException('illegal char: '.$c); + } } } diff --git a/src/Data/Byte.php b/src/Data/Byte.php index a8fab7d98..7bec67979 100644 --- a/src/Data/Byte.php +++ b/src/Data/Byte.php @@ -4,7 +4,7 @@ * * @filesource Byte.php * @created 25.11.2015 - * @package codemasher\QRCode + * @package codemasher\QRCode\Data * @author Smiley * @copyright 2015 Smiley * @license MIT @@ -14,9 +14,6 @@ namespace codemasher\QRCode\Data; use codemasher\QRCode\BitBuffer; use codemasher\QRCode\QRConst; -use codemasher\QRCode\Data\QRDataBase; -use codemasher\QRCode\Data\QRDataInterface; - /** * diff --git a/src/Data/Kanji.php b/src/Data/Kanji.php index d9a1d335c..d921271e5 100644 --- a/src/Data/Kanji.php +++ b/src/Data/Kanji.php @@ -4,7 +4,7 @@ * * @filesource Kanji.php * @created 25.11.2015 - * @package codemasher\QRCode + * @package codemasher\QRCode\Data * @author Smiley * @copyright 2015 Smiley * @license MIT @@ -13,8 +13,6 @@ namespace codemasher\QRCode\Data; use codemasher\QRCode\BitBuffer; -use codemasher\QRCode\Data\QRDataBase; -use codemasher\QRCode\Data\QRDataInterface; use codemasher\QRCode\QRCodeException; use codemasher\QRCode\QRConst; diff --git a/src/Data/Number.php b/src/Data/Number.php index c528985d7..a547e74e1 100644 --- a/src/Data/Number.php +++ b/src/Data/Number.php @@ -4,7 +4,7 @@ * * @filesource Number.php * @created 26.11.2015 - * @package codemasher\QRCode + * @package QRCode * @author Smiley * @copyright 2015 Smiley * @license MIT @@ -13,8 +13,6 @@ namespace codemasher\QRCode\Data; use codemasher\QRCode\BitBuffer; -use codemasher\QRCode\Data\QRDataBase; -use codemasher\QRCode\Data\QRDataInterface; use codemasher\QRCode\QRCodeException; use codemasher\QRCode\QRConst; diff --git a/src/Data/QRDataBase.php b/src/Data/QRDataBase.php index 03dfb311c..453a30fca 100644 --- a/src/Data/QRDataBase.php +++ b/src/Data/QRDataBase.php @@ -4,7 +4,7 @@ * * @filesource QRDataBase.php * @created 25.11.2015 - * @package codemasher\QRCode + * @package codemasher\QRCode\Data * @author Smiley * @copyright 2015 Smiley * @license MIT @@ -13,14 +13,12 @@ namespace codemasher\QRCode\Data; use codemasher\QRCode\BitBuffer; -use codemasher\QRCode\QRConst; use codemasher\QRCode\QRCodeException; -use codemasher\QRCode\Util; /** * */ -class QRDataBase{ +class QRDataBase implements QRDataInterface{ /** * @var @@ -42,11 +40,6 @@ class QRDataBase{ */ protected $lengthBits = [0, 0, 0]; - /** - * @var \codemasher\QRCode\Util - */ - protected $util; - /** * QRDataBase constructor. * @@ -55,7 +48,6 @@ class QRDataBase{ public function __construct($data){ $this->data = $data; $this->dataLength = strlen($data); - $this->util = new Util; } /** @@ -67,13 +59,24 @@ class QRDataBase{ public function getLengthInBits($type){ switch(true){ - case 1 <= $type && $type < 10: return $this->lengthBits[0]; break; // 1 - 9 - case $type < 27: return $this->lengthBits[1]; break; // 10 - 26 - case $type < 41: return $this->lengthBits[2]; break; // 27 - 40 + case $type >= 1 && $type <= 9: return $this->lengthBits[0]; // 1 - 9 + case $type <= 26 : return $this->lengthBits[1]; // 10 - 26 + case $type <= 40 : return $this->lengthBits[2]; // 27 - 40 default: - throw new QRCodeException('mode: '.$this->mode); + throw new QRCodeException('$type: '.$type); } } + /** + * @todo Implement write() method. + * + * @param \codemasher\QRCode\BitBuffer $buffer + * + * @throws \codemasher\QRCode\QRCodeException + */ + public function write(BitBuffer &$buffer){ + throw new QRCodeException('Method should be implemented in child classes'); + } + } diff --git a/src/Data/QRDataInterface.php b/src/Data/QRDataInterface.php index 61847f595..66f13514c 100644 --- a/src/Data/QRDataInterface.php +++ b/src/Data/QRDataInterface.php @@ -4,6 +4,7 @@ * * @filesource QRDataInterface.php * @created 01.12.2015 + * @package codemasher\QRCode\Data * @author Smiley * @copyright 2015 Smiley * @license MIT diff --git a/src/Polynomial.php b/src/Polynomial.php index c32440488..01cd40698 100644 --- a/src/Polynomial.php +++ b/src/Polynomial.php @@ -38,8 +38,7 @@ class Polynomial{ * @param int $shift */ public function __construct(array $num = [1], $shift = 0){ - $this->setNum($num, $shift) - ->setTables(); + $this->setNum($num, $shift)->setTables(); } /** @@ -56,10 +55,7 @@ class Polynomial{ $offset++; } - $this->num = []; - for($i = 0; $i < $numCount - $offset + $shift; $i++){ - $this->num[] = 0; - } + $this->num = array_fill(0, $numCount - $offset + $shift, 0); for($i = 0; $i < $numCount - $offset; $i++){ $this->num[$i] = $num[$i + $offset]; @@ -72,12 +68,8 @@ class Polynomial{ * @return $this */ protected function setTables(){ - $numArr = []; - for($i = 0; $i < 256; $i++){ - $numArr[] = 0; - } - $this->EXP_TABLE = $this->LOG_TABLE = $numArr; + $this->EXP_TABLE = $this->LOG_TABLE = array_fill(0, 256, 0); for($i = 0; $i < 8; $i++){ $this->EXP_TABLE[$i] = 1 << $i; @@ -97,7 +89,7 @@ class Polynomial{ /** * @return string */ - public function __toString(){ +/* public function __toString(){ $buffer = ''; foreach($this->num as $i => $value){ @@ -109,11 +101,11 @@ class Polynomial{ return $buffer; } - +*/ /** * @return string */ - public function toLogString(){ +/* public function toLogString(){ $buffer = ''; foreach($this->num as $i => $value){ @@ -125,7 +117,7 @@ class Polynomial{ return $buffer; } - +*/ /** * @param array $e * @@ -133,12 +125,7 @@ class Polynomial{ */ public function multiply(array $e){ - $num = []; - $len = count($this->num) + count($e) - 1; - for($i = 0; $i < $len; $i++){ - $num[] = 0; - } - + $num = array_fill(0, count($this->num) + count($e) - 1, 0); foreach($this->num as $i => $vi){ foreach($e as $j => $vj){ $num[$i + $j] ^= $this->gexp($this->glog($vi) + $this->glog($vj)); @@ -156,28 +143,19 @@ class Polynomial{ * @return $this */ public function mod($e){ -# $starttime = microtime(true); if(count($this->num) - count($e) < 0){ return $this; } $ratio = $this->glog($this->num[0]) - $this->glog($e[0]); - - $num = []; - foreach($this->num as $i => $value){ - $num[$i] = $value; - } - foreach($e as $i => $value){ - $num[$i] ^= $this->gexp($this->glog($e[$i]) + $ratio); + $this->num[$i] ^= $this->gexp($this->glog($e[$i]) + $ratio); } - $this->setNum($num)->mod($e); + $this->setNum($this->num)->mod($e); -# echo 'Polynomial::mod '.round((microtime(true)-$starttime), 5).PHP_EOL; - - return $num; + return $this->num; } /** @@ -202,12 +180,9 @@ class Polynomial{ */ public function gexp($n){ - while($n < 0){ - $n += 255; - } - - while($n >= 256){ - $n -= 255; + switch(true){ + case $n < 0 : $n += 255; break; + case $n >= 256: $n -= 255; break; } return $this->EXP_TABLE[$n]; diff --git a/src/QRCode.php b/src/QRCode.php index 2013f0df7..55feba592 100644 --- a/src/QRCode.php +++ b/src/QRCode.php @@ -12,13 +12,7 @@ namespace codemasher\QRCode; -use codemasher\QRCode\Polynomial; -use codemasher\QRCode\QRConst; -use codemasher\QRCode\Data\AlphaNum; -use codemasher\QRCode\Data\Byte; -use codemasher\QRCode\Data\Kanji; -use codemasher\QRCode\Data\Number; -use codemasher\QRCode\Data\QRDataInterface; +use codemasher\QRCode\Data\QRDataBase; /** * @link https://github.com/kazuhikoarase/qrcode-generator/tree/master/php @@ -28,28 +22,28 @@ class QRCode{ const QR_PAD0 = 0xEC; const QR_PAD1 = 0x11; - /** - * @var int - */ - public $typeNumber; - - /** - * @var array - */ - protected $modules; - /** * @var int */ public $moduleCount; /** - * @var int + * @var array */ - public $errorCorrectLevel; + public $modules; /** - * @var array + * @var int + */ + protected $typeNumber; + + /** + * @var int + */ + protected $errorCorrectLevel; + + /** + * @var array -> \codemasher\QRCode\Data\QRDataInterface */ protected $qrDataList = []; @@ -63,6 +57,31 @@ class QRCode{ */ protected $data; + /** + * @var int + */ + protected $mode; + + /** + * @var array + */ + protected $ERROR_CORRECT_LEVEL = [ + QRConst::ERROR_CORRECT_LEVEL_L, + QRConst::ERROR_CORRECT_LEVEL_M, + QRConst::ERROR_CORRECT_LEVEL_Q, + QRConst::ERROR_CORRECT_LEVEL_H, + ]; + + /** + * @var array + */ + protected $qrDataInterface = [ + QRConst::MODE_ALPHANUM => '\\codemasher\\QRCode\\Data\\AlphaNum', + QRConst::MODE_BYTE => '\\codemasher\\QRCode\\Data\\Byte', + QRConst::MODE_KANJI => '\\codemasher\\QRCode\\Data\\Kanji', + QRConst::MODE_NUMBER => '\\codemasher\\QRCode\\Data\\Number', + ]; + /** * @var \codemasher\QRCode\Util */ @@ -79,79 +98,40 @@ class QRCode{ protected $bitBuffer; /** + * @todo WIP + * * QRCode constructor. * - * @param int $typeNumber - * @param int $errorCorrectLevel + * @param string $data + * @param int $errorCorrectLevel + * @param int $typeNumber + * + * @throws \codemasher\QRCode\QRCodeException */ - public function __construct($typeNumber = 1, $errorCorrectLevel = QRConst::ERROR_CORRECT_LEVEL_H){ + public function __construct($data = '', $errorCorrectLevel = QRConst::ERROR_CORRECT_LEVEL_M, $typeNumber = null){ $this->util = new Util; $this->rsBlock = new RSBlock; $this->bitBuffer = new BitBuffer; - $this->typeNumber = $typeNumber; - $this->errorCorrectLevel = $errorCorrectLevel; + $this->setErrorCorrectLevel($errorCorrectLevel)->setTypeNumber($typeNumber); + + if(!empty($data)){ + $this->getMinimumQRCode($data); + } + } /** - * @param string $data - * @param int $mode + * @todo WIP * - * @return $this - * @throws \codemasher\QRCode\QRCodeException - */ - public function addData($data, $mode = null){ - if($mode === null){ - $mode = $this->util->getMode($data); - } - - switch($mode){ - case QRConst::MODE_NUMBER : $this->qrDataList[] = new Number($data); break; - case QRConst::MODE_ALPHANUM: $this->qrDataList[] = new AlphaNum($data); break; - case QRConst::MODE_BYTE : $this->qrDataList[] = new Byte($data); break; - case QRConst::MODE_KANJI : $this->qrDataList[] = new Kanji($data); break; - default: - throw new QRCodeException('mode: '.$mode); - } - - return $this; - } - - /** - * @param int $row - * @param int $col - * - * @return bool - */ - public function isDark($row, $col){ - if($this->modules[$row][$col] !== null){ - return (bool)$this->modules[$row][$col]; - } - else{ - return false; - } - } - - /** - * - */ - public function make(){ - $this->makeImpl(false, $this->getBestMaskPattern()); - - return $this; - } - - /** * @param $data - * @param $errorCorrectLevel * * @return \codemasher\QRCode\QRCode * @throws \codemasher\QRCode\QRCodeException */ - public function getMinimumQRCode($data, $errorCorrectLevel = QRConst::ERROR_CORRECT_LEVEL_H){ + public function getMinimumQRCode($data){ $mode = $this->util->getMode($data); $this->addData($data, $mode); - $this->errorCorrectLevel = $errorCorrectLevel; /** @var \codemasher\QRCode\Data\QRDataBase $qrData */ $qrData = $this->qrDataList[0]; @@ -164,18 +144,194 @@ class QRCode{ } } - $this->makeImpl(false, $this->getBestMaskPattern()); + $this->make(); + + return $this; + } + + /** + * @todo WIP + * + * @param string $data + * + * @param null $mode + * + * @return $this + * @throws \codemasher\QRCode\QRCodeException + */ + public function addData($data, $mode = null){ + if($mode === null){ + $mode = $this->util->getMode($data); + } + + if(!isset($this->qrDataInterface[$mode])){ + throw new QRCodeException('mode: '.$mode); + } + + $this->qrDataList[] = new $this->qrDataInterface[$mode]($data); + + return $this; + } + + + /** + * @param $errorCorrectLevel + * + * @return $this + * @throws \codemasher\QRCode\QRCodeException + */ + public function setErrorCorrectLevel($errorCorrectLevel){ + + if(!in_array($errorCorrectLevel, $this->ERROR_CORRECT_LEVEL)){ + throw new QRCodeException('Invalid error correct level: '.$errorCorrectLevel); + } + + $this->errorCorrectLevel = $errorCorrectLevel; + + return $this; + } + + /** + * @param int $typeNumber + * + * @return $this + * @throws \codemasher\QRCode\QRCodeException + */ + public function setTypeNumber($typeNumber){ + $typeNumber = intval($typeNumber); + + if($typeNumber < 1 || $typeNumber > 10){ + throw new QRCodeException('Invalid type number: '.$typeNumber); + } + + $this->typeNumber = $typeNumber; + + return $this; + } + + /** + * + */ + public function make(){ + $minLostPoint = 0; + $pattern = 0; + + for($i = 0; $i < 8; $i++){ + $this->makeImpl(true, $i); + $lostPoint = 0; + + // LEVEL1 + for($row = 0; $row < $this->moduleCount; $row++){ + for($col = 0; $col < $this->moduleCount; $col++){ + $sameCount = 0; + $dark = $this->modules[$row][$col]; + + for($r = -1; $r <= 1; $r++){ + if($row + $r < 0 || $this->moduleCount <= $row + $r){ + continue; + } + + for($c = -1; $c <= 1; $c++){ + + if(($r === 0 && $c === 0) || ($col + $c < 0 || $this->moduleCount <= $col + $c)){ + continue; + } + + if($this->modules[$row + $r][$col + $c] === $dark){ + $sameCount++; + } + + } + } + + if($sameCount > 5){ + $lostPoint += (3 + $sameCount - 5); + } + } + } + + // LEVEL2 + for($row = 0; $row < $this->moduleCount - 1; $row++){ + for($col = 0; $col < $this->moduleCount - 1; $col++){ + $count = 0; + + if($this->modules[$row][$col] || $this->modules[$row][$col + 1] + || $this->modules[$row + 1][$col] || $this->modules[$row + 1][$col + 1] + ){ + $count++; + } + + if($count === 0 || $count === 4){ + $lostPoint += 3; + } + + } + } + + // LEVEL3 + for($row = 0; $row < $this->moduleCount; $row++){ + for($col = 0; $col < $this->moduleCount - 6; $col++){ + if($this->modules[$row][$col] + && !$this->modules[$row][$col + 1] + && $this->modules[$row][$col + 2] + && $this->modules[$row][$col + 3] + && $this->modules[$row][$col + 4] + && !$this->modules[$row][$col + 5] + && $this->modules[$row][$col + 6] + ){ + $lostPoint += 40; + } + } + } + + for($col = 0; $col < $this->moduleCount; $col++){ + for($row = 0; $row < $this->moduleCount - 6; $row++){ + if($this->modules[$row][$col] + && !$this->modules[$row + 1][$col] + && $this->modules[$row + 2][$col] + && $this->modules[$row + 3][$col] + && $this->modules[$row + 4][$col] + && !$this->modules[$row + 5][$col] + && $this->modules[$row + 6][$col] + ){ + $lostPoint += 40; + } + } + } + + // LEVEL4 + $darkCount = 0; + for($col = 0; $col < $this->moduleCount; $col++){ + for($row = 0; $row < $this->moduleCount; $row++){ + if($this->modules[$row][$col]){ + $darkCount++; + } + } + } + + $ratio = abs(100 * $darkCount / $this->moduleCount / $this->moduleCount - 50) / 5; + $lostPoint += $ratio * 10; + + if($i === 0 || $minLostPoint > $lostPoint){ + $minLostPoint = $lostPoint; + $pattern = $i; + } + + } + + $this->makeImpl(false, $pattern); return $this; } /** * @param bool $test - * @param int $maskPattern + * @param int $pattern * * @return $this + * @throws \codemasher\QRCode\QRCodeException */ - protected function makeImpl($test, $maskPattern){ + protected function makeImpl($test, $pattern){ $this->moduleCount = $this->typeNumber * 4 + 17; $this->modules = []; @@ -186,15 +342,41 @@ class QRCode{ $this->setupPositionProbePattern(0, 0) ->setupPositionProbePattern($this->moduleCount - 7, 0) - ->setupPositionProbePattern(0, $this->moduleCount - 7) - ->setupPositionAdjustPattern() - ->setupTimingPattern(); + ->setupPositionProbePattern(0, $this->moduleCount - 7); - $data = ($this->errorCorrectLevel << 3) | $maskPattern; + // setupPositionAdjustPattern + $pos = $this->util->PATTERN_POSITION[$this->typeNumber - 1]; + foreach($pos as $i => $posI){ + foreach($pos as $j => $posJ){ + if($this->modules[$posI][$posJ] !== null){ + continue; + } + + for($row = -2; $row <= 2; $row++){ + for($col = -2; $col <= 2; $col++){ + $this->modules[$posI + $row][$posJ + $col] = + (bool)$row === -2 || $row === 2 || $col === -2 || $col === 2 || ($row === 0 && $col === 0); + } + } + + } + } + + // setupTimingPattern + for($i = 8; $i < $this->moduleCount - 8; $i++){ + if($this->modules[$i][6] !== null){ + continue; + } + + $this->modules[$i][6] = $this->modules[6][$i] = (bool)$i % 2 === 0; + } + + + $data = ($this->errorCorrectLevel << 3) | $pattern; $bits = $this->util->getBCHTypeInfo($data); for($i = 0; $i < 15; $i++){ - $mod = !$test && (($bits >> $i) & 1) === 1; + $mod = (bool)!$test && (($bits >> $i) & 1) === 1; switch(true){ case $i < 6: $this->modules[$i][8] = $mod; break; @@ -221,23 +403,12 @@ class QRCode{ $a = (int)floor($i / 3); $b = $i % 3 + $this->moduleCount - 8 - 3; - $this->modules[$a][$b] = $this->modules[$b][$a] = !$test && (($bits >> $i) & 1) === 1; + $this->modules[$a][$b] = $this->modules[$b][$a] = (bool)!$test && (($bits >> $i) & 1) === 1; } } $this->data = $this->createData($this->typeNumber, $this->errorCorrectLevel); - $this->mapData($maskPattern); - return $this; - } - - /** - * @param $maskPattern - * - * @return $this - * @throws \codemasher\QRCode\QRCodeException - */ - protected function mapData($maskPattern){ $inc = -1; $row = $this->moduleCount - 1; $bitIndex = 7; @@ -261,24 +432,27 @@ class QRCode{ } $_col = $col - $c; - switch($maskPattern){ - case QRConst::MASK_PATTERN000: $mask = ($row + $_col) % 2 === 0; break; - case QRConst::MASK_PATTERN001: $mask = $row % 2 === 0; break; - case QRConst::MASK_PATTERN010: $mask = $_col % 3 === 0; break; - case QRConst::MASK_PATTERN011: $mask = ($row + $_col) % 3 === 0; break; - case QRConst::MASK_PATTERN100: $mask = (floor($row / 2) + floor($_col / 3)) % 2 === 0; break; - case QRConst::MASK_PATTERN101: $mask = ($row * $_col) % 2 + ($row * $_col) % 3 === 0; break; - case QRConst::MASK_PATTERN110: $mask = (($row * $_col) % 2 + ($row * $_col) % 3) % 2 === 0; break; - case QRConst::MASK_PATTERN111: $mask = (($row * $_col) % 3 + ($row + $_col) % 2) % 2 === 0; break; - default : - throw new QRCodeException('mask: '.$maskPattern); + + $MASK_PATTERN = [ + QRConst::MASK_PATTERN000 => ($row + $_col) % 2 === 0, + QRConst::MASK_PATTERN001 => $row % 2 === 0, + QRConst::MASK_PATTERN010 => $_col % 3 === 0, + QRConst::MASK_PATTERN011 => ($row + $_col) % 3 === 0, + QRConst::MASK_PATTERN100 => (floor($row / 2) + floor($_col / 3)) % 2 === 0, + QRConst::MASK_PATTERN101 => ($row * $_col) % 2 + ($row * $_col) % 3 === 0, + QRConst::MASK_PATTERN110 => (($row * $_col) % 2 + ($row * $_col) % 3) % 2 === 0, + QRConst::MASK_PATTERN111 => (($row * $_col) % 3 + ($row + $_col) % 2) % 2 === 0, + ]; + + if(!isset($MASK_PATTERN[$pattern])){ + throw new QRCodeException('mask: '.$pattern); } - if($mask){ + if($MASK_PATTERN[$pattern]){ $dark = !$dark; } - $this->modules[$row][$col - $c] = $dark; + $this->modules[$row][$col - $c] = (bool)$dark; $bitIndex--; if($bitIndex === -1){ @@ -328,198 +502,29 @@ class QRCode{ return $this; } - /** - * @return $this - */ - protected function setupPositionAdjustPattern(){ - $pos = $this->util->PATTERN_POSITION[$this->typeNumber - 1]; - - foreach($pos as $i => $posI){ - foreach($pos as $j => $posJ){ - if($this->modules[$posI][$posJ] !== null){ - continue; - } - - for($row = -2; $row <= 2; $row++){ - for($col = -2; $col <= 2; $col++){ - $this->modules[$posI + $row][$posJ + $col] = - (bool)$row === -2 || $row === 2 || $col === -2 || $col === 2 || ($row === 0 && $col === 0); - } - } - - } - } - - return $this; - } - - /** - * @return $this - */ - protected function setupTimingPattern(){ - - for($i = 8; $i < $this->moduleCount - 8; $i++){ - if($this->modules[$i][6] !== null){ - continue; - } - - $this->modules[$i][6] = $this->modules[6][$i] = $i % 2 === 0; - } - - return $this; - } - - /** - * @return int - */ - protected function getBestMaskPattern(){ - $minLostPoint = 0; - $pattern = 0; - - for($i = 0; $i < 8; $i++){ - $this->makeImpl(true, $i); - $lostPoint = 0; - - // LEVEL1 - - for($row = 0; $row < $this->moduleCount; $row++){ - for($col = 0; $col < $this->moduleCount; $col++){ - $sameCount = 0; - $dark = $this->isDark($row, $col); - - for($r = -1; $r <= 1; $r++){ - if($row + $r < 0 || $this->moduleCount <= $row + $r){ - continue; - } - - for($c = -1; $c <= 1; $c++){ - - if($col + $c < 0 || $this->moduleCount <= $col + $c){ - continue; - } - - if($r == 0 && $c == 0){ - continue; - } - - if($dark === $this->isDark($row + $r, $col + $c)){ - $sameCount++; - } - } - } - - if($sameCount > 5){ - $lostPoint += (3 + $sameCount - 5); - } - } - } - - // LEVEL2 - - for($row = 0; $row < $this->moduleCount - 1; $row++){ - for($col = 0; $col < $this->moduleCount - 1; $col++){ - $count = 0; - - if($this->isDark($row, $col)){ - $count++; - } - - if($this->isDark($row + 1, $col)){ - $count++; - } - - if($this->isDark($row, $col + 1)){ - $count++; - } - - if($this->isDark($row + 1, $col + 1)){ - $count++; - } - - if($count === 0 || $count === 4){ - $lostPoint += 3; - } - } - } - - // LEVEL3 - - for($row = 0; $row < $this->moduleCount; $row++){ - for($col = 0; $col < $this->moduleCount - 6; $col++){ - if($this->isDark($row, $col) - && !$this->isDark($row, $col + 1) - && $this->isDark($row, $col + 2) - && $this->isDark($row, $col + 3) - && $this->isDark($row, $col + 4) - && !$this->isDark($row, $col + 5) - && $this->isDark($row, $col + 6) - ){ - $lostPoint += 40; - } - } - } - - for($col = 0; $col < $this->moduleCount; $col++){ - for($row = 0; $row < $this->moduleCount - 6; $row++){ - if($this->isDark($row, $col) - && !$this->isDark($row + 1, $col) - && $this->isDark($row + 2, $col) - && $this->isDark($row + 3, $col) - && $this->isDark($row + 4, $col) - && !$this->isDark($row + 5, $col) - && $this->isDark($row + 6, $col) - ){ - $lostPoint += 40; - } - } - } - - // LEVEL4 - - $darkCount = 0; - for($col = 0; $col < $this->moduleCount; $col++){ - for($row = 0; $row < $this->moduleCount; $row++){ - if($this->isDark($row, $col)){ - $darkCount++; - } - } - } - - $ratio = abs(100 * $darkCount / $this->moduleCount / $this->moduleCount - 50) / 5; - $lostPoint += $ratio * 10; - - if($i === 0 || $minLostPoint > $lostPoint){ - $minLostPoint = $lostPoint; - $pattern = $i; - } - } - - return $pattern; - } - /** * @param $typeNumber * @param $errorCorrectLevel * * @return array * @throws \codemasher\QRCode\QRCodeException - * @todo: slooooow in PHP5 - * */ protected function createData($typeNumber, $errorCorrectLevel){ $this->bitBuffer->buffer = []; $this->bitBuffer->length = 0; - $this->rsBlockList = $this->rsBlock->getRSBlocks($typeNumber, $errorCorrectLevel); + $rsBlockCount = count($this->rsBlockList); - $totalDataCount = $totalCodeCount = $offset = $maxDcCount = $maxEcCount = 0; + $totalDataCount = $totalCodeCount = $offset = $maxDcCount = $maxEcCount = $index = 0; + $dcdata = $ecdata = array_fill(0, $rsBlockCount, null); - /** @var \codemasher\QRCode\Data\QRDataInterface $data */ + + /** @var \codemasher\QRCode\Data\QRDataBase $data */ foreach($this->qrDataList as &$data){ - $len = $data->mode === QRConst::MODE_KANJI ? floor($data->dataLength / 2) : $data->dataLength; + $this->bitBuffer + ->put($data->mode, 4) + ->put($data->mode === QRConst::MODE_KANJI ? floor($data->dataLength / 2) : $data->dataLength, $data->getLengthInBits($typeNumber)); - $this->bitBuffer->put($data->mode, 4); - $this->bitBuffer->put($len, $data->getLengthInBits($typeNumber)); $data->write($this->bitBuffer); } @@ -560,9 +565,6 @@ class QRCode{ $this->bitBuffer->put(self::QR_PAD1, 8); } - $rsBlockCount = count($this->rsBlockList); - $dcdata = $ecdata = array_fill(0, $rsBlockCount, null); - foreach($this->rsBlockList as $r => &$_rsData){ $this->rsBlock->totalCount = $_rsData[0]; $this->rsBlock->dataCount = $_rsData[1]; @@ -582,13 +584,11 @@ class QRCode{ $offset += $this->rsBlock->dataCount; - // PHP5: 0.09s for($i = 0; $i < $this->rsBlock->totalCount - $this->rsBlock->dataCount; $i++){ $modPoly->setNum([1, $modPoly->gexp($i)]); $rsPoly->multiply($modPoly->num); } - // PHP5: 0.11s $rsPolyCount = count($rsPoly->num); $modPoly->setNum($dcdata[$r], $rsPolyCount - 1)->mod($rsPoly->num); $ecdata[$r] = array_fill(0, $rsPolyCount - 1, null); @@ -603,7 +603,6 @@ class QRCode{ } $data = array_fill(0, $totalCodeCount, null); - $index = 0; for($i = 0; $i < $maxDcCount; $i++){ for($r = 0; $r < $rsBlockCount; $r++){ @@ -681,7 +680,7 @@ class QRCode{ for($r = 0; $r < $this->moduleCount; $r++){ for($c = 0; $c < $this->moduleCount; $c++){ - if($this->isDark($r, $c)){ + if($this->modules[$r][$c]){ // update $black to $fgc imagefilledrectangle($image, @@ -707,7 +706,7 @@ class QRCode{ $html .= ''; for($col = 0; $col < $this->moduleCount; $col++){ - $html .= ''; + $html .= ''; } $html .= ''; diff --git a/src/RSBlock.php b/src/RSBlock.php index 295cbce60..797c98f3b 100644 --- a/src/RSBlock.php +++ b/src/RSBlock.php @@ -101,6 +101,16 @@ class RSBlock{ ]; + /** + * @var array + */ + protected $RSBLOCK = [ + QRConst::ERROR_CORRECT_LEVEL_L => 0, + QRConst::ERROR_CORRECT_LEVEL_M => 1, + QRConst::ERROR_CORRECT_LEVEL_Q => 2, + QRConst::ERROR_CORRECT_LEVEL_H => 3, + ]; + /** * @param $typeNumber * @param $errorCorrectLevel @@ -110,16 +120,11 @@ class RSBlock{ */ public function getRSBlocks($typeNumber, $errorCorrectLevel){ - switch($errorCorrectLevel){ - case QRConst::ERROR_CORRECT_LEVEL_L: $rsBlock = 0; break; - case QRConst::ERROR_CORRECT_LEVEL_M: $rsBlock = 1; break; - case QRConst::ERROR_CORRECT_LEVEL_Q: $rsBlock = 2; break; - case QRConst::ERROR_CORRECT_LEVEL_H: $rsBlock = 3; break; - default: - throw new QRCodeException('$typeNumber:'.$typeNumber.'/$errorCorrectLevel:'.$errorCorrectLevel); + if(!isset($this->RSBLOCK[$errorCorrectLevel])){ + throw new QRCodeException('$typeNumber: '.$typeNumber.' / $errorCorrectLevel: '.$errorCorrectLevel.PHP_EOL.print_r($this->RSBLOCK, true)); } - $rsBlock = $this->BLOCK_TABLE[($typeNumber - 1) * 4 + $rsBlock]; + $rsBlock = $this->BLOCK_TABLE[($typeNumber - 1) * 4 + $this->RSBLOCK[$errorCorrectLevel]]; $list = []; $length = count($rsBlock) / 3; @@ -136,5 +141,4 @@ class RSBlock{ return $list; } - } diff --git a/src/Util.php b/src/Util.php index 7445fabe7..e919813dc 100644 --- a/src/Util.php +++ b/src/Util.php @@ -11,11 +11,6 @@ namespace codemasher\QRCode; -use codemasher\QRCode\Polynomial; -use codemasher\QRCode\QRCode; -use codemasher\QRCode\QRConst; -use codemasher\QRCode\QRCodeException; - /** * Class Util */ @@ -87,39 +82,47 @@ class Util{ [6, 30, 58, 86, 114, 142, 170], ]; + /** + * @var array + */ + protected $ERROR_CORRECT_LEVEL = [ + QRConst::ERROR_CORRECT_LEVEL_L => 0, + QRConst::ERROR_CORRECT_LEVEL_M => 1, + QRConst::ERROR_CORRECT_LEVEL_Q => 2, + QRConst::ERROR_CORRECT_LEVEL_H => 3, + ]; + + /** + * @var array + */ + protected $MODE = [ + QRConst::MODE_NUMBER => 0, + QRConst::MODE_ALPHANUM => 1, + QRConst::MODE_BYTE => 2, + QRConst::MODE_KANJI => 3, + ]; + /** * @param int $typeNumber * @param int $mode - * @param int $errorCorrectLevel + * @param int $ecLevel * * @return mixed * @throws \codemasher\QRCode\QRCodeException */ - public function getMaxLength($typeNumber, $mode, $errorCorrectLevel){ - $_type = $typeNumber - 1; + public function getMaxLength($typeNumber, $mode, $ecLevel){ - switch($errorCorrectLevel){ - case QRConst::ERROR_CORRECT_LEVEL_L: $_err = 0; break; - case QRConst::ERROR_CORRECT_LEVEL_M: $_err = 1; break; - case QRConst::ERROR_CORRECT_LEVEL_Q: $_err = 2; break; - case QRConst::ERROR_CORRECT_LEVEL_H: $_err = 3; break; - default: - throw new QRCodeException('$_err: '.$errorCorrectLevel); + if(!isset($this->ERROR_CORRECT_LEVEL[$ecLevel])){ + throw new QRCodeException('$_err: '.$ecLevel); } - switch($mode){ - case QRConst::MODE_NUMBER : $_mode = 0; break; - case QRConst::MODE_ALPHANUM: $_mode = 1; break; - case QRConst::MODE_BYTE: $_mode = 2; break; - case QRConst::MODE_KANJI : $_mode = 3; break; - default : - throw new QRCodeException('$_mode: '.$mode); + if(!isset($this->MODE[$mode])){ + throw new QRCodeException('$_mode: '.$mode); } - return $this->MAX_LENGTH[$_type][$_err][$_mode]; + return $this->MAX_LENGTH[$typeNumber - 1][$this->ERROR_CORRECT_LEVEL[$ecLevel]][$this->MODE[$mode]]; } - /** * @param $s * @@ -127,17 +130,11 @@ class Util{ */ public function getMode($s){ - if($this->isAlphaNum($s)){ - if($this->isNumber($s)){ - return QRConst::MODE_NUMBER; - } - return QRConst::MODE_ALPHANUM; - } - else if($this->isKanji($s)){ - return QRConst::MODE_KANJI; - } - else{ - return QRConst::MODE_BYTE; + switch(true){ + case $this->isAlphaNum($s): return $this->isNumber($s) ? QRConst::MODE_NUMBER : QRConst::MODE_ALPHANUM; + case $this->isKanji($s) : return QRConst::MODE_KANJI; + default: + return QRConst::MODE_BYTE; } } @@ -262,9 +259,9 @@ class Util{ */ public function hex2rgb($hex = 0x0){ return [ - 'r' => floor($hex / 65536), - 'g' => floor($hex / 256) % 256, - 'b' => $hex % 256, + 'r' => floor($hex / 65536), + 'g' => floor($hex / 256) % 256, + 'b' => $hex % 256, ]; }