MRBS 1.12.2 等保2.0二级整改完整提交
包含:登录失败锁定、90天密码有效期、30分钟会话超时、 强制改密、登录审计日志、屏幕水印、企业背景图、 备案信息固定底部、favicon、JS空集合保护、 会话过期体验优化(403 JSON)、display_errors 关闭、 固定 key 根治 Integrity check failed 等全部改动 注意:config.inc.php/.htaccess/.user.ini 含敏感信息, 通过 .gitignore 排除,勿推送到公开仓库。
This commit is contained in:
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
/**
|
||||
* Class BitBuffer
|
||||
*
|
||||
* @created 25.11.2015
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @copyright 2015 Smiley
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace chillerlan\QRCode\Common;
|
||||
|
||||
use chillerlan\QRCode\QRCodeException;
|
||||
use function count;
|
||||
use function floor;
|
||||
use function min;
|
||||
|
||||
/**
|
||||
* Holds the raw binary data
|
||||
*/
|
||||
final class BitBuffer{
|
||||
|
||||
/**
|
||||
* The buffer content
|
||||
*
|
||||
* @var int[]
|
||||
*/
|
||||
private array $buffer;
|
||||
|
||||
/**
|
||||
* Length of the content (bits)
|
||||
*/
|
||||
private int $length;
|
||||
|
||||
/**
|
||||
* Read count (bytes)
|
||||
*/
|
||||
private int $bytesRead = 0;
|
||||
|
||||
/**
|
||||
* Read count (bits)
|
||||
*/
|
||||
private int $bitsRead = 0;
|
||||
|
||||
/**
|
||||
* BitBuffer constructor.
|
||||
*
|
||||
* @param int[] $bytes
|
||||
*/
|
||||
public function __construct(array $bytes = []){
|
||||
$this->buffer = $bytes;
|
||||
$this->length = count($this->buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* appends a sequence of bits
|
||||
*/
|
||||
public function put(int $bits, int $length):self{
|
||||
|
||||
for($i = 0; $i < $length; $i++){
|
||||
$this->putBit((($bits >> ($length - $i - 1)) & 1) === 1);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* appends a single bit
|
||||
*/
|
||||
public function putBit(bool $bit):self{
|
||||
$bufIndex = (int)floor($this->length / 8);
|
||||
|
||||
if(count($this->buffer) <= $bufIndex){
|
||||
$this->buffer[] = 0;
|
||||
}
|
||||
|
||||
if($bit === true){
|
||||
$this->buffer[$bufIndex] |= (0x80 >> ($this->length % 8));
|
||||
}
|
||||
|
||||
$this->length++;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the current buffer length
|
||||
*/
|
||||
public function getLength():int{
|
||||
return $this->length;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the buffer content
|
||||
*
|
||||
* to debug: array_map(fn($v) => sprintf('%08b', $v), $bitBuffer->getBuffer())
|
||||
*/
|
||||
public function getBuffer():array{
|
||||
return $this->buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int number of bits that can be read successfully
|
||||
*/
|
||||
public function available():int{
|
||||
return ((8 * ($this->length - $this->bytesRead)) - $this->bitsRead);
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Sean Owen, ZXing
|
||||
*
|
||||
* @param int $numBits number of bits to read
|
||||
*
|
||||
* @return int representing the bits read. The bits will appear as the least-significant bits of the int
|
||||
* @throws \chillerlan\QRCode\QRCodeException if numBits isn't in [1,32] or more than is available
|
||||
*/
|
||||
public function read(int $numBits):int{
|
||||
|
||||
if($numBits < 1 || $numBits > $this->available()){
|
||||
throw new QRCodeException('invalid $numBits: '.$numBits);
|
||||
}
|
||||
|
||||
$result = 0;
|
||||
|
||||
// First, read remainder from current byte
|
||||
if($this->bitsRead > 0){
|
||||
$bitsLeft = (8 - $this->bitsRead);
|
||||
$toRead = min($numBits, $bitsLeft);
|
||||
$bitsToNotRead = ($bitsLeft - $toRead);
|
||||
$mask = ((0xff >> (8 - $toRead)) << $bitsToNotRead);
|
||||
$result = (($this->buffer[$this->bytesRead] & $mask) >> $bitsToNotRead);
|
||||
$numBits -= $toRead;
|
||||
$this->bitsRead += $toRead;
|
||||
|
||||
if($this->bitsRead === 8){
|
||||
$this->bitsRead = 0;
|
||||
$this->bytesRead++;
|
||||
}
|
||||
}
|
||||
|
||||
// Next read whole bytes
|
||||
if($numBits > 0){
|
||||
|
||||
while($numBits >= 8){
|
||||
$result = (($result << 8) | ($this->buffer[$this->bytesRead] & 0xff));
|
||||
$this->bytesRead++;
|
||||
$numBits -= 8;
|
||||
}
|
||||
|
||||
// Finally read a partial byte
|
||||
if($numBits > 0){
|
||||
$bitsToNotRead = (8 - $numBits);
|
||||
$mask = ((0xff >> $bitsToNotRead) << $bitsToNotRead);
|
||||
$result = (($result << $numBits) | (($this->buffer[$this->bytesRead] & $mask) >> $bitsToNotRead));
|
||||
$this->bitsRead += $numBits;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the buffer and resets the stats
|
||||
*/
|
||||
public function clear():self{
|
||||
$this->buffer = [];
|
||||
$this->length = 0;
|
||||
|
||||
return $this->rewind();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the read-counters
|
||||
*/
|
||||
public function rewind():self{
|
||||
$this->bytesRead = 0;
|
||||
$this->bitsRead = 0;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
/**
|
||||
* Class ECICharset
|
||||
*
|
||||
* @created 21.01.2021
|
||||
* @author ZXing Authors
|
||||
* @author smiley <smiley@chillerlan.net>
|
||||
* @copyright 2021 smiley
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
namespace chillerlan\QRCode\Common;
|
||||
|
||||
use chillerlan\QRCode\QRCodeException;
|
||||
use function sprintf;
|
||||
|
||||
/**
|
||||
* ISO/IEC 18004:2000 - 8.4.1 Extended Channel Interpretation (ECI) Mode
|
||||
*/
|
||||
final class ECICharset{
|
||||
|
||||
public const CP437 = 0; // Code page 437, DOS Latin US
|
||||
public const ISO_IEC_8859_1_GLI = 1; // GLI encoding with characters 0 to 127 identical to ISO/IEC 646 and characters 128 to 255 identical to ISO 8859-1
|
||||
public const CP437_WO_GLI = 2; // An equivalent code table to CP437, without the return-to-GLI 0 logic
|
||||
public const ISO_IEC_8859_1 = 3; // Latin-1 (Default)
|
||||
public const ISO_IEC_8859_2 = 4; // Latin-2
|
||||
public const ISO_IEC_8859_3 = 5; // Latin-3
|
||||
public const ISO_IEC_8859_4 = 6; // Latin-4
|
||||
public const ISO_IEC_8859_5 = 7; // Latin/Cyrillic
|
||||
public const ISO_IEC_8859_6 = 8; // Latin/Arabic
|
||||
public const ISO_IEC_8859_7 = 9; // Latin/Greek
|
||||
public const ISO_IEC_8859_8 = 10; // Latin/Hebrew
|
||||
public const ISO_IEC_8859_9 = 11; // Latin-5
|
||||
public const ISO_IEC_8859_10 = 12; // Latin-6
|
||||
public const ISO_IEC_8859_11 = 13; // Latin/Thai
|
||||
// 14 reserved
|
||||
public const ISO_IEC_8859_13 = 15; // Latin-7 (Baltic Rim)
|
||||
public const ISO_IEC_8859_14 = 16; // Latin-8 (Celtic)
|
||||
public const ISO_IEC_8859_15 = 17; // Latin-9
|
||||
public const ISO_IEC_8859_16 = 18; // Latin-10
|
||||
// 19 reserved
|
||||
public const SHIFT_JIS = 20; // JIS X 0208 Annex 1 + JIS X 0201
|
||||
public const WINDOWS_1250_LATIN_2 = 21; // Superset of Latin-2, Central Europe
|
||||
public const WINDOWS_1251_CYRILLIC = 22; // Latin/Cyrillic
|
||||
public const WINDOWS_1252_LATIN_1 = 23; // Superset of Latin-1
|
||||
public const WINDOWS_1256_ARABIC = 24;
|
||||
public const ISO_IEC_10646_UCS_2 = 25; // High order byte first (UTF-16BE)
|
||||
public const ISO_IEC_10646_UTF_8 = 26; // UTF-8
|
||||
public const ISO_IEC_646_1991 = 27; // International Reference Version of ISO 7-bit coded character set (US-ASCII)
|
||||
public const BIG5 = 28; // Big 5 (Taiwan) Chinese Character Set
|
||||
public const GB18030 = 29; // GB (PRC) Chinese Character Set
|
||||
public const EUC_KR = 30; // Korean Character Set
|
||||
|
||||
/**
|
||||
* map of charset id -> name
|
||||
*
|
||||
* @see \mb_list_encodings()
|
||||
*/
|
||||
public const MB_ENCODINGS = [
|
||||
self::CP437 => null,
|
||||
self::ISO_IEC_8859_1_GLI => null,
|
||||
self::CP437_WO_GLI => null,
|
||||
self::ISO_IEC_8859_1 => 'ISO-8859-1',
|
||||
self::ISO_IEC_8859_2 => 'ISO-8859-2',
|
||||
self::ISO_IEC_8859_3 => 'ISO-8859-3',
|
||||
self::ISO_IEC_8859_4 => 'ISO-8859-4',
|
||||
self::ISO_IEC_8859_5 => 'ISO-8859-5',
|
||||
self::ISO_IEC_8859_6 => 'ISO-8859-6',
|
||||
self::ISO_IEC_8859_7 => 'ISO-8859-7',
|
||||
self::ISO_IEC_8859_8 => 'ISO-8859-8',
|
||||
self::ISO_IEC_8859_9 => 'ISO-8859-9',
|
||||
self::ISO_IEC_8859_10 => 'ISO-8859-10',
|
||||
self::ISO_IEC_8859_11 => null,
|
||||
self::ISO_IEC_8859_13 => 'ISO-8859-13',
|
||||
self::ISO_IEC_8859_14 => 'ISO-8859-14',
|
||||
self::ISO_IEC_8859_15 => 'ISO-8859-15',
|
||||
self::ISO_IEC_8859_16 => 'ISO-8859-16',
|
||||
self::SHIFT_JIS => 'SJIS',
|
||||
self::WINDOWS_1250_LATIN_2 => null, // @see https://www.php.net/manual/en/function.mb-convert-encoding.php#112547
|
||||
self::WINDOWS_1251_CYRILLIC => 'Windows-1251',
|
||||
self::WINDOWS_1252_LATIN_1 => 'Windows-1252',
|
||||
self::WINDOWS_1256_ARABIC => null, // @see https://stackoverflow.com/a/8592995
|
||||
self::ISO_IEC_10646_UCS_2 => 'UTF-16BE',
|
||||
self::ISO_IEC_10646_UTF_8 => 'UTF-8',
|
||||
self::ISO_IEC_646_1991 => 'ASCII',
|
||||
self::BIG5 => 'BIG-5',
|
||||
self::GB18030 => 'GB18030',
|
||||
self::EUC_KR => 'EUC-KR',
|
||||
];
|
||||
|
||||
/**
|
||||
* The current ECI character set ID
|
||||
*/
|
||||
private int $charsetID;
|
||||
|
||||
/**
|
||||
* @throws \chillerlan\QRCode\QRCodeException
|
||||
*/
|
||||
public function __construct(int $charsetID){
|
||||
|
||||
if($charsetID < 0 || $charsetID > 999999){
|
||||
throw new QRCodeException(sprintf('invalid charset id: "%s"', $charsetID));
|
||||
}
|
||||
|
||||
$this->charsetID = $charsetID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current character set ID
|
||||
*/
|
||||
public function getID():int{
|
||||
return $this->charsetID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the current character set or null if no name is available
|
||||
*
|
||||
* @see \mb_convert_encoding()
|
||||
* @see \iconv()
|
||||
*/
|
||||
public function getName():?string{
|
||||
return (self::MB_ENCODINGS[$this->charsetID] ?? null);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
<?php
|
||||
/**
|
||||
* Class EccLevel
|
||||
*
|
||||
* @created 19.11.2020
|
||||
* @author smiley <smiley@chillerlan.net>
|
||||
* @copyright 2020 smiley
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace chillerlan\QRCode\Common;
|
||||
|
||||
use chillerlan\QRCode\QRCodeException;
|
||||
use function array_column;
|
||||
|
||||
/**
|
||||
* This class encapsulates the four error correction levels defined by the QR code standard.
|
||||
*/
|
||||
final class EccLevel{
|
||||
|
||||
// ISO/IEC 18004:2000 Tables 12, 25
|
||||
|
||||
/** @var int */
|
||||
public const L = 0b01; // 7%.
|
||||
/** @var int */
|
||||
public const M = 0b00; // 15%.
|
||||
/** @var int */
|
||||
public const Q = 0b11; // 25%.
|
||||
/** @var int */
|
||||
public const H = 0b10; // 30%.
|
||||
|
||||
/**
|
||||
* ISO/IEC 18004:2000 Tables 7-11 - Number of symbol characters and input data capacity for versions 1 to 40
|
||||
*
|
||||
* @var int[][]
|
||||
*/
|
||||
private const MAX_BITS = [
|
||||
// [ L, M, Q, H] // v => modules
|
||||
[ 0, 0, 0, 0], // 0 => will be ignored, index starts at 1
|
||||
[ 152, 128, 104, 72], // 1 => 21
|
||||
[ 272, 224, 176, 128], // 2 => 25
|
||||
[ 440, 352, 272, 208], // 3 => 29
|
||||
[ 640, 512, 384, 288], // 4 => 33
|
||||
[ 864, 688, 496, 368], // 5 => 37
|
||||
[ 1088, 864, 608, 480], // 6 => 41
|
||||
[ 1248, 992, 704, 528], // 7 => 45
|
||||
[ 1552, 1232, 880, 688], // 8 => 49
|
||||
[ 1856, 1456, 1056, 800], // 9 => 53
|
||||
[ 2192, 1728, 1232, 976], // 10 => 57
|
||||
[ 2592, 2032, 1440, 1120], // 11 => 61
|
||||
[ 2960, 2320, 1648, 1264], // 12 => 65
|
||||
[ 3424, 2672, 1952, 1440], // 13 => 69 NICE!
|
||||
[ 3688, 2920, 2088, 1576], // 14 => 73
|
||||
[ 4184, 3320, 2360, 1784], // 15 => 77
|
||||
[ 4712, 3624, 2600, 2024], // 16 => 81
|
||||
[ 5176, 4056, 2936, 2264], // 17 => 85
|
||||
[ 5768, 4504, 3176, 2504], // 18 => 89
|
||||
[ 6360, 5016, 3560, 2728], // 19 => 93
|
||||
[ 6888, 5352, 3880, 3080], // 20 => 97
|
||||
[ 7456, 5712, 4096, 3248], // 21 => 101
|
||||
[ 8048, 6256, 4544, 3536], // 22 => 105
|
||||
[ 8752, 6880, 4912, 3712], // 23 => 109
|
||||
[ 9392, 7312, 5312, 4112], // 24 => 113
|
||||
[10208, 8000, 5744, 4304], // 25 => 117
|
||||
[10960, 8496, 6032, 4768], // 26 => 121
|
||||
[11744, 9024, 6464, 5024], // 27 => 125
|
||||
[12248, 9544, 6968, 5288], // 28 => 129
|
||||
[13048, 10136, 7288, 5608], // 29 => 133
|
||||
[13880, 10984, 7880, 5960], // 30 => 137
|
||||
[14744, 11640, 8264, 6344], // 31 => 141
|
||||
[15640, 12328, 8920, 6760], // 32 => 145
|
||||
[16568, 13048, 9368, 7208], // 33 => 149
|
||||
[17528, 13800, 9848, 7688], // 34 => 153
|
||||
[18448, 14496, 10288, 7888], // 35 => 157
|
||||
[19472, 15312, 10832, 8432], // 36 => 161
|
||||
[20528, 15936, 11408, 8768], // 37 => 165
|
||||
[21616, 16816, 12016, 9136], // 38 => 169
|
||||
[22496, 17728, 12656, 9776], // 39 => 173
|
||||
[23648, 18672, 13328, 10208], // 40 => 177
|
||||
];
|
||||
|
||||
/**
|
||||
* ISO/IEC 18004:2000 Section 8.9 - Format Information
|
||||
*
|
||||
* ECC level -> mask pattern
|
||||
*
|
||||
* @var int[][]
|
||||
*/
|
||||
private const FORMAT_PATTERN = [
|
||||
[ // L
|
||||
0b111011111000100,
|
||||
0b111001011110011,
|
||||
0b111110110101010,
|
||||
0b111100010011101,
|
||||
0b110011000101111,
|
||||
0b110001100011000,
|
||||
0b110110001000001,
|
||||
0b110100101110110,
|
||||
],
|
||||
[ // M
|
||||
0b101010000010010,
|
||||
0b101000100100101,
|
||||
0b101111001111100,
|
||||
0b101101101001011,
|
||||
0b100010111111001,
|
||||
0b100000011001110,
|
||||
0b100111110010111,
|
||||
0b100101010100000,
|
||||
],
|
||||
[ // Q
|
||||
0b011010101011111,
|
||||
0b011000001101000,
|
||||
0b011111100110001,
|
||||
0b011101000000110,
|
||||
0b010010010110100,
|
||||
0b010000110000011,
|
||||
0b010111011011010,
|
||||
0b010101111101101,
|
||||
],
|
||||
[ // H
|
||||
0b001011010001001,
|
||||
0b001001110111110,
|
||||
0b001110011100111,
|
||||
0b001100111010000,
|
||||
0b000011101100010,
|
||||
0b000001001010101,
|
||||
0b000110100001100,
|
||||
0b000100000111011,
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* The current ECC level value
|
||||
*
|
||||
* L: 0b01
|
||||
* M: 0b00
|
||||
* Q: 0b11
|
||||
* H: 0b10
|
||||
*/
|
||||
private int $eccLevel;
|
||||
|
||||
/**
|
||||
* @param int $eccLevel containing the two bits encoding a QR Code's error correction level
|
||||
*
|
||||
* @todo: accept string values (PHP8+)
|
||||
* @see https://github.com/chillerlan/php-qrcode/discussions/160
|
||||
*
|
||||
* @throws \chillerlan\QRCode\QRCodeException
|
||||
*/
|
||||
public function __construct(int $eccLevel){
|
||||
|
||||
if((0b11 & $eccLevel) !== $eccLevel){
|
||||
throw new QRCodeException('invalid ECC level');
|
||||
}
|
||||
|
||||
$this->eccLevel = $eccLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the string representation of the current ECC level
|
||||
*/
|
||||
public function __toString():string{
|
||||
return [
|
||||
self::L => 'L',
|
||||
self::M => 'M',
|
||||
self::Q => 'Q',
|
||||
self::H => 'H',
|
||||
][$this->eccLevel];
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the current ECC level
|
||||
*/
|
||||
public function getLevel():int{
|
||||
return $this->eccLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the ordinal value of the current ECC level
|
||||
*
|
||||
* references to the keys of the following tables:
|
||||
*
|
||||
* @see \chillerlan\QRCode\Common\EccLevel::MAX_BITS
|
||||
* @see \chillerlan\QRCode\Common\EccLevel::FORMAT_PATTERN
|
||||
* @see \chillerlan\QRCode\Common\Version::RSBLOCKS
|
||||
*/
|
||||
public function getOrdinal():int{
|
||||
return [
|
||||
self::L => 0,
|
||||
self::M => 1,
|
||||
self::Q => 2,
|
||||
self::H => 3,
|
||||
][$this->eccLevel];
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the format pattern for the given $eccLevel and $maskPattern
|
||||
*/
|
||||
public function getformatPattern(MaskPattern $maskPattern):int{
|
||||
return self::FORMAT_PATTERN[$this->getOrdinal()][$maskPattern->getPattern()];
|
||||
}
|
||||
|
||||
/**
|
||||
* returns an array with the max bit lengths for version 1-40 and the current ECC level
|
||||
*
|
||||
* @return int[]
|
||||
*/
|
||||
public function getMaxBits():array{
|
||||
$col = array_column(self::MAX_BITS, $this->getOrdinal());
|
||||
|
||||
unset($col[0]); // remove the inavlid index 0
|
||||
|
||||
return $col;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum bit length for the given version and current ECC level
|
||||
*/
|
||||
public function getMaxBitsForVersion(Version $version):int{
|
||||
return self::MAX_BITS[$version->getVersionNumber()][$this->getOrdinal()];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
/**
|
||||
* Class GDLuminanceSource
|
||||
*
|
||||
* @created 17.01.2021
|
||||
* @author Ashot Khanamiryan
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @copyright 2021 Smiley
|
||||
* @license MIT
|
||||
*
|
||||
* @noinspection PhpComposerExtensionStubsInspection
|
||||
*/
|
||||
|
||||
namespace chillerlan\QRCode\Common;
|
||||
|
||||
use chillerlan\QRCode\Decoder\QRCodeDecoderException;
|
||||
use chillerlan\Settings\SettingsContainerInterface;
|
||||
use function file_get_contents;
|
||||
use function get_resource_type;
|
||||
use function imagecolorat;
|
||||
use function imagecolorsforindex;
|
||||
use function imagecreatefromstring;
|
||||
use function imagefilter;
|
||||
use function imagesx;
|
||||
use function imagesy;
|
||||
use function is_resource;
|
||||
use const IMG_FILTER_BRIGHTNESS;
|
||||
use const IMG_FILTER_CONTRAST;
|
||||
use const IMG_FILTER_GRAYSCALE;
|
||||
use const IMG_FILTER_NEGATE;
|
||||
use const PHP_MAJOR_VERSION;
|
||||
|
||||
/**
|
||||
* This class is used to help decode images from files which arrive as GD Resource
|
||||
* It does not support rotation.
|
||||
*/
|
||||
class GDLuminanceSource extends LuminanceSourceAbstract{
|
||||
|
||||
/**
|
||||
* @var resource|\GdImage
|
||||
*/
|
||||
protected $gdImage;
|
||||
|
||||
/**
|
||||
* GDLuminanceSource constructor.
|
||||
*
|
||||
* @param resource|\GdImage $gdImage
|
||||
* @param \chillerlan\Settings\SettingsContainerInterface|null $options
|
||||
*
|
||||
* @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException
|
||||
*/
|
||||
public function __construct($gdImage, ?SettingsContainerInterface $options = null){
|
||||
|
||||
/** @noinspection PhpFullyQualifiedNameUsageInspection */
|
||||
if(
|
||||
(PHP_MAJOR_VERSION >= 8 && !$gdImage instanceof \GdImage) // @todo: remove version check in v6
|
||||
|| (PHP_MAJOR_VERSION < 8 && (!is_resource($gdImage) || get_resource_type($gdImage) !== 'gd'))
|
||||
){
|
||||
throw new QRCodeDecoderException('Invalid GD image source.'); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
parent::__construct(imagesx($gdImage), imagesy($gdImage), $options);
|
||||
|
||||
$this->gdImage = $gdImage;
|
||||
|
||||
if($this->options->readerGrayscale){
|
||||
imagefilter($this->gdImage, IMG_FILTER_GRAYSCALE);
|
||||
}
|
||||
|
||||
if($this->options->readerInvertColors){
|
||||
imagefilter($this->gdImage, IMG_FILTER_NEGATE);
|
||||
}
|
||||
|
||||
if($this->options->readerIncreaseContrast){
|
||||
imagefilter($this->gdImage, IMG_FILTER_BRIGHTNESS, -100);
|
||||
imagefilter($this->gdImage, IMG_FILTER_CONTRAST, -100);
|
||||
}
|
||||
|
||||
$this->setLuminancePixels();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
protected function setLuminancePixels():void{
|
||||
|
||||
for($j = 0; $j < $this->height; $j++){
|
||||
for($i = 0; $i < $this->width; $i++){
|
||||
$argb = imagecolorat($this->gdImage, $i, $j);
|
||||
$pixel = imagecolorsforindex($this->gdImage, $argb);
|
||||
|
||||
$this->setLuminancePixel($pixel['red'], $pixel['green'], $pixel['blue']);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public static function fromFile(string $path, ?SettingsContainerInterface $options = null):self{
|
||||
return new self(imagecreatefromstring(file_get_contents(self::checkFile($path))), $options);
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public static function fromBlob(string $blob, ?SettingsContainerInterface $options = null):self{
|
||||
return new self(imagecreatefromstring($blob), $options);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
/**
|
||||
* Class GF256
|
||||
*
|
||||
* @created 16.01.2021
|
||||
* @author ZXing Authors
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @copyright 2021 Smiley
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
namespace chillerlan\QRCode\Common;
|
||||
|
||||
use chillerlan\QRCode\QRCodeException;
|
||||
use function array_fill;
|
||||
|
||||
/**
|
||||
* This class contains utility methods for performing mathematical operations over
|
||||
* the Galois Fields. Operations use a given primitive polynomial in calculations.
|
||||
*
|
||||
* Throughout this package, elements of the GF are represented as an int
|
||||
* for convenience and speed (but at the cost of memory).
|
||||
*
|
||||
*
|
||||
* @author Sean Owen
|
||||
* @author David Olivier
|
||||
*/
|
||||
final class GF256{
|
||||
|
||||
/**
|
||||
* irreducible polynomial whose coefficients are represented by the bits of an int,
|
||||
* where the least-significant bit represents the constant coefficient
|
||||
*/
|
||||
# private int $primitive = 0x011D;
|
||||
|
||||
private const logTable = [
|
||||
0, // the first value is never returned, index starts at 1
|
||||
0, 1, 25, 2, 50, 26, 198, 3, 223, 51, 238, 27, 104, 199, 75,
|
||||
4, 100, 224, 14, 52, 141, 239, 129, 28, 193, 105, 248, 200, 8, 76, 113,
|
||||
5, 138, 101, 47, 225, 36, 15, 33, 53, 147, 142, 218, 240, 18, 130, 69,
|
||||
29, 181, 194, 125, 106, 39, 249, 185, 201, 154, 9, 120, 77, 228, 114, 166,
|
||||
6, 191, 139, 98, 102, 221, 48, 253, 226, 152, 37, 179, 16, 145, 34, 136,
|
||||
54, 208, 148, 206, 143, 150, 219, 189, 241, 210, 19, 92, 131, 56, 70, 64,
|
||||
30, 66, 182, 163, 195, 72, 126, 110, 107, 58, 40, 84, 250, 133, 186, 61,
|
||||
202, 94, 155, 159, 10, 21, 121, 43, 78, 212, 229, 172, 115, 243, 167, 87,
|
||||
7, 112, 192, 247, 140, 128, 99, 13, 103, 74, 222, 237, 49, 197, 254, 24,
|
||||
227, 165, 153, 119, 38, 184, 180, 124, 17, 68, 146, 217, 35, 32, 137, 46,
|
||||
55, 63, 209, 91, 149, 188, 207, 205, 144, 135, 151, 178, 220, 252, 190, 97,
|
||||
242, 86, 211, 171, 20, 42, 93, 158, 132, 60, 57, 83, 71, 109, 65, 162,
|
||||
31, 45, 67, 216, 183, 123, 164, 118, 196, 23, 73, 236, 127, 12, 111, 246,
|
||||
108, 161, 59, 82, 41, 157, 85, 170, 251, 96, 134, 177, 187, 204, 62, 90,
|
||||
203, 89, 95, 176, 156, 169, 160, 81, 11, 245, 22, 235, 122, 117, 44, 215,
|
||||
79, 174, 213, 233, 230, 231, 173, 232, 116, 214, 244, 234, 168, 80, 88, 175,
|
||||
];
|
||||
|
||||
private const expTable = [
|
||||
1, 2, 4, 8, 16, 32, 64, 128, 29, 58, 116, 232, 205, 135, 19, 38,
|
||||
76, 152, 45, 90, 180, 117, 234, 201, 143, 3, 6, 12, 24, 48, 96, 192,
|
||||
157, 39, 78, 156, 37, 74, 148, 53, 106, 212, 181, 119, 238, 193, 159, 35,
|
||||
70, 140, 5, 10, 20, 40, 80, 160, 93, 186, 105, 210, 185, 111, 222, 161,
|
||||
95, 190, 97, 194, 153, 47, 94, 188, 101, 202, 137, 15, 30, 60, 120, 240,
|
||||
253, 231, 211, 187, 107, 214, 177, 127, 254, 225, 223, 163, 91, 182, 113, 226,
|
||||
217, 175, 67, 134, 17, 34, 68, 136, 13, 26, 52, 104, 208, 189, 103, 206,
|
||||
129, 31, 62, 124, 248, 237, 199, 147, 59, 118, 236, 197, 151, 51, 102, 204,
|
||||
133, 23, 46, 92, 184, 109, 218, 169, 79, 158, 33, 66, 132, 21, 42, 84,
|
||||
168, 77, 154, 41, 82, 164, 85, 170, 73, 146, 57, 114, 228, 213, 183, 115,
|
||||
230, 209, 191, 99, 198, 145, 63, 126, 252, 229, 215, 179, 123, 246, 241, 255,
|
||||
227, 219, 171, 75, 150, 49, 98, 196, 149, 55, 110, 220, 165, 87, 174, 65,
|
||||
130, 25, 50, 100, 200, 141, 7, 14, 28, 56, 112, 224, 221, 167, 83, 166,
|
||||
81, 162, 89, 178, 121, 242, 249, 239, 195, 155, 43, 86, 172, 69, 138, 9,
|
||||
18, 36, 72, 144, 61, 122, 244, 245, 247, 243, 251, 235, 203, 139, 11, 22,
|
||||
44, 88, 176, 125, 250, 233, 207, 131, 27, 54, 108, 216, 173, 71, 142, 1,
|
||||
];
|
||||
|
||||
/**
|
||||
* Implements both addition and subtraction -- they are the same in GF(size).
|
||||
*
|
||||
* @return int sum/difference of a and b
|
||||
*/
|
||||
public static function addOrSubtract(int $a, int $b):int{
|
||||
return ($a ^ $b);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return GenericGFPoly the monomial representing coefficient * x^degree
|
||||
* @throws \chillerlan\QRCode\QRCodeException
|
||||
*/
|
||||
public static function buildMonomial(int $degree, int $coefficient):GenericGFPoly{
|
||||
|
||||
if($degree < 0){
|
||||
throw new QRCodeException('degree < 0');
|
||||
}
|
||||
|
||||
$coefficients = array_fill(0, ($degree + 1), 0);
|
||||
$coefficients[0] = $coefficient;
|
||||
|
||||
return new GenericGFPoly($coefficients);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int 2 to the power of $a in GF(size)
|
||||
*/
|
||||
public static function exp(int $a):int{
|
||||
|
||||
if($a < 0){
|
||||
$a += 255;
|
||||
}
|
||||
elseif($a >= 256){
|
||||
$a -= 255;
|
||||
}
|
||||
|
||||
return self::expTable[$a];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int base 2 log of $a in GF(size)
|
||||
* @throws \chillerlan\QRCode\QRCodeException
|
||||
*/
|
||||
public static function log(int $a):int{
|
||||
|
||||
if($a < 1){
|
||||
throw new QRCodeException('$a < 1');
|
||||
}
|
||||
|
||||
return self::logTable[$a];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int multiplicative inverse of a
|
||||
* @throws \chillerlan\QRCode\QRCodeException
|
||||
*/
|
||||
public static function inverse(int $a):int{
|
||||
|
||||
if($a === 0){
|
||||
throw new QRCodeException('$a === 0');
|
||||
}
|
||||
|
||||
return self::expTable[(256 - self::logTable[$a] - 1)];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int product of a and b in GF(size)
|
||||
*/
|
||||
public static function multiply(int $a, int $b):int{
|
||||
|
||||
if($a === 0 || $b === 0){
|
||||
return 0;
|
||||
}
|
||||
|
||||
return self::expTable[((self::logTable[$a] + self::logTable[$b]) % 255)];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,266 @@
|
||||
<?php
|
||||
/**
|
||||
* Class GenericGFPoly
|
||||
*
|
||||
* @created 16.01.2021
|
||||
* @author ZXing Authors
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @copyright 2021 Smiley
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
namespace chillerlan\QRCode\Common;
|
||||
|
||||
use chillerlan\QRCode\QRCodeException;
|
||||
use function array_fill;
|
||||
use function array_slice;
|
||||
use function array_splice;
|
||||
use function count;
|
||||
|
||||
/**
|
||||
* Represents a polynomial whose coefficients are elements of a GF.
|
||||
* Instances of this class are immutable.
|
||||
*
|
||||
* Much credit is due to William Rucklidge since portions of this code are an indirect
|
||||
* port of his C++ Reed-Solomon implementation.
|
||||
*
|
||||
* @author Sean Owen
|
||||
*/
|
||||
final class GenericGFPoly{
|
||||
|
||||
private array $coefficients;
|
||||
|
||||
/**
|
||||
* @param array $coefficients array coefficients as ints representing elements of GF(size), arranged
|
||||
* from most significant (highest-power term) coefficient to the least significant
|
||||
* @param int|null $degree
|
||||
*
|
||||
* @throws \chillerlan\QRCode\QRCodeException if argument is null or empty, or if leading coefficient is 0 and this
|
||||
* is not a constant polynomial (that is, it is not the monomial "0")
|
||||
*/
|
||||
public function __construct(array $coefficients, ?int $degree = null){
|
||||
$degree ??= 0;
|
||||
|
||||
if(empty($coefficients)){
|
||||
throw new QRCodeException('arg $coefficients is empty');
|
||||
}
|
||||
|
||||
if($degree < 0){
|
||||
throw new QRCodeException('negative degree');
|
||||
}
|
||||
|
||||
$coefficientsLength = count($coefficients);
|
||||
|
||||
// Leading term must be non-zero for anything except the constant polynomial "0"
|
||||
$firstNonZero = 0;
|
||||
|
||||
while($firstNonZero < $coefficientsLength && $coefficients[$firstNonZero] === 0){
|
||||
$firstNonZero++;
|
||||
}
|
||||
|
||||
$this->coefficients = [0];
|
||||
|
||||
if($firstNonZero !== $coefficientsLength){
|
||||
$this->coefficients = array_fill(0, ($coefficientsLength - $firstNonZero + $degree), 0);
|
||||
|
||||
for($i = 0; $i < ($coefficientsLength - $firstNonZero); $i++){
|
||||
$this->coefficients[$i] = $coefficients[($i + $firstNonZero)];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int $coefficient of x^degree term in this polynomial
|
||||
*/
|
||||
public function getCoefficient(int $degree):int{
|
||||
return $this->coefficients[(count($this->coefficients) - 1 - $degree)];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int[]
|
||||
*/
|
||||
public function getCoefficients():array{
|
||||
return $this->coefficients;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int $degree of this polynomial
|
||||
*/
|
||||
public function getDegree():int{
|
||||
return (count($this->coefficients) - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool true if this polynomial is the monomial "0"
|
||||
*/
|
||||
public function isZero():bool{
|
||||
return $this->coefficients[0] === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int evaluation of this polynomial at a given point
|
||||
*/
|
||||
public function evaluateAt(int $a):int{
|
||||
|
||||
if($a === 0){
|
||||
// Just return the x^0 coefficient
|
||||
return $this->getCoefficient(0);
|
||||
}
|
||||
|
||||
$result = 0;
|
||||
|
||||
foreach($this->coefficients as $c){
|
||||
// if $a === 1 just the sum of the coefficients
|
||||
$result = GF256::addOrSubtract((($a === 1) ? $result : GF256::multiply($a, $result)), $c);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function multiply(GenericGFPoly $other):self{
|
||||
|
||||
if($this->isZero() || $other->isZero()){
|
||||
return new self([0]);
|
||||
}
|
||||
|
||||
$product = array_fill(0, (count($this->coefficients) + count($other->coefficients) - 1), 0);
|
||||
|
||||
foreach($this->coefficients as $i => $aCoeff){
|
||||
foreach($other->coefficients as $j => $bCoeff){
|
||||
$product[($i + $j)] ^= GF256::multiply($aCoeff, $bCoeff);
|
||||
}
|
||||
}
|
||||
|
||||
return new self($product);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \chillerlan\QRCode\Common\GenericGFPoly[] [quotient, remainder]
|
||||
* @throws \chillerlan\QRCode\QRCodeException
|
||||
*/
|
||||
public function divide(GenericGFPoly $other):array{
|
||||
|
||||
if($other->isZero()){
|
||||
throw new QRCodeException('Division by 0');
|
||||
}
|
||||
|
||||
$quotient = new self([0]);
|
||||
$remainder = clone $this;
|
||||
|
||||
$denominatorLeadingTerm = $other->getCoefficient($other->getDegree());
|
||||
$inverseDenominatorLeadingTerm = GF256::inverse($denominatorLeadingTerm);
|
||||
|
||||
while($remainder->getDegree() >= $other->getDegree() && !$remainder->isZero()){
|
||||
$scale = GF256::multiply($remainder->getCoefficient($remainder->getDegree()), $inverseDenominatorLeadingTerm);
|
||||
$diff = ($remainder->getDegree() - $other->getDegree());
|
||||
$quotient = $quotient->addOrSubtract(GF256::buildMonomial($diff, $scale));
|
||||
$remainder = $remainder->addOrSubtract($other->multiplyByMonomial($diff, $scale));
|
||||
}
|
||||
|
||||
return [$quotient, $remainder];
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function multiplyInt(int $scalar):self{
|
||||
|
||||
if($scalar === 0){
|
||||
return new self([0]);
|
||||
}
|
||||
|
||||
if($scalar === 1){
|
||||
return $this;
|
||||
}
|
||||
|
||||
$product = array_fill(0, count($this->coefficients), 0);
|
||||
|
||||
foreach($this->coefficients as $i => $c){
|
||||
$product[$i] = GF256::multiply($c, $scalar);
|
||||
}
|
||||
|
||||
return new self($product);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \chillerlan\QRCode\QRCodeException
|
||||
*/
|
||||
public function multiplyByMonomial(int $degree, int $coefficient):self{
|
||||
|
||||
if($degree < 0){
|
||||
throw new QRCodeException('degree < 0');
|
||||
}
|
||||
|
||||
if($coefficient === 0){
|
||||
return new self([0]);
|
||||
}
|
||||
|
||||
$product = array_fill(0, (count($this->coefficients) + $degree), 0);
|
||||
|
||||
foreach($this->coefficients as $i => $c){
|
||||
$product[$i] = GF256::multiply($c, $coefficient);
|
||||
}
|
||||
|
||||
return new self($product);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function mod(GenericGFPoly $other):self{
|
||||
|
||||
if((count($this->coefficients) - count($other->coefficients)) < 0){
|
||||
return $this;
|
||||
}
|
||||
|
||||
$ratio = (GF256::log($this->coefficients[0]) - GF256::log($other->coefficients[0]));
|
||||
|
||||
foreach($other->coefficients as $i => $c){
|
||||
$this->coefficients[$i] ^= GF256::exp(GF256::log($c) + $ratio);
|
||||
}
|
||||
|
||||
return (new self($this->coefficients))->mod($other);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function addOrSubtract(GenericGFPoly $other):self{
|
||||
|
||||
if($this->isZero()){
|
||||
return $other;
|
||||
}
|
||||
|
||||
if($other->isZero()){
|
||||
return $this;
|
||||
}
|
||||
|
||||
$smallerCoefficients = $this->coefficients;
|
||||
$largerCoefficients = $other->coefficients;
|
||||
|
||||
if(count($smallerCoefficients) > count($largerCoefficients)){
|
||||
$temp = $smallerCoefficients;
|
||||
$smallerCoefficients = $largerCoefficients;
|
||||
$largerCoefficients = $temp;
|
||||
}
|
||||
|
||||
$sumDiff = array_fill(0, count($largerCoefficients), 0);
|
||||
$lengthDiff = (count($largerCoefficients) - count($smallerCoefficients));
|
||||
// Copy high-order terms only found in higher-degree polynomial's coefficients
|
||||
array_splice($sumDiff, 0, $lengthDiff, array_slice($largerCoefficients, 0, $lengthDiff));
|
||||
|
||||
$countLargerCoefficients = count($largerCoefficients);
|
||||
|
||||
for($i = $lengthDiff; $i < $countLargerCoefficients; $i++){
|
||||
$sumDiff[$i] = GF256::addOrSubtract($smallerCoefficients[($i - $lengthDiff)], $largerCoefficients[$i]);
|
||||
}
|
||||
|
||||
return new self($sumDiff);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
/**
|
||||
* Class IMagickLuminanceSource
|
||||
*
|
||||
* @created 17.01.2021
|
||||
* @author Ashot Khanamiryan
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @copyright 2021 Smiley
|
||||
* @license MIT
|
||||
*
|
||||
* @noinspection PhpComposerExtensionStubsInspection
|
||||
*/
|
||||
|
||||
namespace chillerlan\QRCode\Common;
|
||||
|
||||
use chillerlan\Settings\SettingsContainerInterface;
|
||||
use Imagick;
|
||||
use function count;
|
||||
|
||||
/**
|
||||
* This class is used to help decode images from files which arrive as Imagick Resource
|
||||
* It does not support rotation.
|
||||
*/
|
||||
class IMagickLuminanceSource extends LuminanceSourceAbstract{
|
||||
|
||||
protected Imagick $imagick;
|
||||
|
||||
/**
|
||||
* IMagickLuminanceSource constructor.
|
||||
*/
|
||||
public function __construct(Imagick $imagick, ?SettingsContainerInterface $options = null){
|
||||
parent::__construct($imagick->getImageWidth(), $imagick->getImageHeight(), $options);
|
||||
|
||||
$this->imagick = $imagick;
|
||||
|
||||
if($this->options->readerGrayscale){
|
||||
$this->imagick->setImageColorspace(Imagick::COLORSPACE_GRAY);
|
||||
}
|
||||
|
||||
if($this->options->readerInvertColors){
|
||||
$this->imagick->negateImage($this->options->readerGrayscale);
|
||||
}
|
||||
|
||||
if($this->options->readerIncreaseContrast){
|
||||
for($i = 0; $i < 10; $i++){
|
||||
$this->imagick->contrastImage(false); // misleading docs
|
||||
}
|
||||
}
|
||||
|
||||
$this->setLuminancePixels();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
protected function setLuminancePixels():void{
|
||||
$pixels = $this->imagick->exportImagePixels(1, 1, $this->width, $this->height, 'RGB', Imagick::PIXEL_CHAR);
|
||||
$count = count($pixels);
|
||||
|
||||
for($i = 0; $i < $count; $i += 3){
|
||||
$this->setLuminancePixel(($pixels[$i] & 0xff), ($pixels[($i + 1)] & 0xff), ($pixels[($i + 2)] & 0xff));
|
||||
}
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public static function fromFile(string $path, ?SettingsContainerInterface $options = null):self{
|
||||
return new self(new Imagick(self::checkFile($path)), $options);
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public static function fromBlob(string $blob, ?SettingsContainerInterface $options = null):self{
|
||||
$im = new Imagick;
|
||||
$im->readImageBlob($blob);
|
||||
|
||||
return new self($im, $options);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
/**
|
||||
* Class LuminanceSourceAbstract
|
||||
*
|
||||
* @created 24.01.2021
|
||||
* @author ZXing Authors
|
||||
* @author Ashot Khanamiryan
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @copyright 2021 Smiley
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
namespace chillerlan\QRCode\Common;
|
||||
|
||||
use chillerlan\QRCode\Decoder\QRCodeDecoderException;
|
||||
use chillerlan\QRCode\QROptions;
|
||||
use chillerlan\Settings\SettingsContainerInterface;
|
||||
use function array_slice;
|
||||
use function array_splice;
|
||||
use function file_exists;
|
||||
use function is_file;
|
||||
use function is_readable;
|
||||
use function realpath;
|
||||
|
||||
/**
|
||||
* The purpose of this class hierarchy is to abstract different bitmap implementations across
|
||||
* platforms into a standard interface for requesting greyscale luminance values.
|
||||
*
|
||||
* @author dswitkin@google.com (Daniel Switkin)
|
||||
*/
|
||||
abstract class LuminanceSourceAbstract implements LuminanceSourceInterface{
|
||||
|
||||
/** @var \chillerlan\QRCode\QROptions|\chillerlan\Settings\SettingsContainerInterface */
|
||||
protected SettingsContainerInterface $options;
|
||||
protected array $luminances;
|
||||
protected int $width;
|
||||
protected int $height;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function __construct(int $width, int $height, ?SettingsContainerInterface $options = null){
|
||||
$this->width = $width;
|
||||
$this->height = $height;
|
||||
$this->options = ($options ?? new QROptions);
|
||||
|
||||
$this->luminances = [];
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function getLuminances():array{
|
||||
return $this->luminances;
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function getWidth():int{
|
||||
return $this->width;
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
public function getHeight():int{
|
||||
return $this->height;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException
|
||||
*/
|
||||
public function getRow(int $y):array{
|
||||
|
||||
if($y < 0 || $y >= $this->getHeight()){
|
||||
throw new QRCodeDecoderException('Requested row is outside the image: '.$y);
|
||||
}
|
||||
|
||||
$arr = [];
|
||||
|
||||
array_splice($arr, 0, $this->width, array_slice($this->luminances, ($y * $this->width), $this->width));
|
||||
|
||||
return $arr;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
protected function setLuminancePixel(int $r, int $g, int $b):void{
|
||||
$this->luminances[] = ($r === $g && $g === $b)
|
||||
// Image is already greyscale, so pick any channel.
|
||||
? $r // (($r + 128) % 256) - 128;
|
||||
// Calculate luminance cheaply, favoring green.
|
||||
: (($r + 2 * $g + $b) / 4); // (((($r + 2 * $g + $b) / 4) + 128) % 256) - 128;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException
|
||||
*/
|
||||
protected static function checkFile(string $path):string{
|
||||
$path = trim($path);
|
||||
|
||||
if(!file_exists($path) || !is_file($path) || !is_readable($path)){
|
||||
throw new QRCodeDecoderException('invalid file: '.$path);
|
||||
}
|
||||
|
||||
$realpath = realpath($path);
|
||||
|
||||
if($realpath === false){
|
||||
throw new QRCodeDecoderException('unable to resolve path: '.$path);
|
||||
}
|
||||
|
||||
return $realpath;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
/**
|
||||
* Interface LuminanceSourceInterface
|
||||
*
|
||||
* @created 18.11.2021
|
||||
* @author smiley <smiley@chillerlan.net>
|
||||
* @copyright 2021 smiley
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace chillerlan\QRCode\Common;
|
||||
|
||||
/**
|
||||
*/
|
||||
interface LuminanceSourceInterface{
|
||||
|
||||
/**
|
||||
* Fetches luminance data for the underlying bitmap. Values should be fetched using:
|
||||
* `int luminance = array[y * width + x] & 0xff`
|
||||
*
|
||||
* @return array A row-major 2D array of luminance values. Do not use result $length as it may be
|
||||
* larger than $width * $height bytes on some platforms. Do not modify the contents
|
||||
* of the result.
|
||||
*/
|
||||
public function getLuminances():array;
|
||||
|
||||
/**
|
||||
* @return int The width of the bitmap.
|
||||
*/
|
||||
public function getWidth():int;
|
||||
|
||||
/**
|
||||
* @return int The height of the bitmap.
|
||||
*/
|
||||
public function getHeight():int;
|
||||
|
||||
/**
|
||||
* Fetches one row of luminance data from the underlying platform's bitmap. Values range from
|
||||
* 0 (black) to 255 (white). Because Java does not have an unsigned byte type, callers will have
|
||||
* to bitwise and with 0xff for each value. It is preferable for implementations of this method
|
||||
* to only fetch this row rather than the whole image, since no 2D Readers may be installed and
|
||||
* getLuminances() may never be called.
|
||||
*
|
||||
* @param int $y The row to fetch, which must be in [0,getHeight())
|
||||
*
|
||||
* @return array An array containing the luminance data.
|
||||
* @throws \chillerlan\QRCode\Decoder\QRCodeDecoderException
|
||||
*/
|
||||
public function getRow(int $y):array;
|
||||
|
||||
/**
|
||||
* Creates a LuminanceSource instance from the given file
|
||||
*/
|
||||
public static function fromFile(string $path):self;
|
||||
|
||||
/**
|
||||
* Creates a LuminanceSource instance from the given data blob
|
||||
*/
|
||||
public static function fromBlob(string $blob):self;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,333 @@
|
||||
<?php
|
||||
/**
|
||||
* Class MaskPattern
|
||||
*
|
||||
* @created 19.01.2021
|
||||
* @author ZXing Authors
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @copyright 2021 Smiley
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
namespace chillerlan\QRCode\Common;
|
||||
|
||||
use chillerlan\QRCode\Data\QRMatrix;
|
||||
use chillerlan\QRCode\QRCodeException;
|
||||
use Closure;
|
||||
use function abs;
|
||||
use function array_column;
|
||||
use function array_search;
|
||||
use function intdiv;
|
||||
use function min;
|
||||
|
||||
/**
|
||||
* ISO/IEC 18004:2000 Section 8.8.1
|
||||
* ISO/IEC 18004:2000 Section 8.8.2 - Evaluation of masking results
|
||||
*
|
||||
* @see http://www.thonky.com/qr-code-tutorial/data-masking
|
||||
* @see https://github.com/zxing/zxing/blob/e9e2bd280bcaeabd59d0f955798384fe6c018a6c/core/src/main/java/com/google/zxing/qrcode/encoder/MaskUtil.java
|
||||
*/
|
||||
final class MaskPattern{
|
||||
|
||||
/**
|
||||
* @see \chillerlan\QRCode\QROptionsTrait::$maskPattern
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const AUTO = -1;
|
||||
|
||||
public const PATTERN_000 = 0b000;
|
||||
public const PATTERN_001 = 0b001;
|
||||
public const PATTERN_010 = 0b010;
|
||||
public const PATTERN_011 = 0b011;
|
||||
public const PATTERN_100 = 0b100;
|
||||
public const PATTERN_101 = 0b101;
|
||||
public const PATTERN_110 = 0b110;
|
||||
public const PATTERN_111 = 0b111;
|
||||
|
||||
/**
|
||||
* @var int[]
|
||||
*/
|
||||
public const PATTERNS = [
|
||||
self::PATTERN_000,
|
||||
self::PATTERN_001,
|
||||
self::PATTERN_010,
|
||||
self::PATTERN_011,
|
||||
self::PATTERN_100,
|
||||
self::PATTERN_101,
|
||||
self::PATTERN_110,
|
||||
self::PATTERN_111,
|
||||
];
|
||||
|
||||
/*
|
||||
* Penalty scores
|
||||
*
|
||||
* ISO/IEC 18004:2000 Section 8.8.1 - Table 24
|
||||
*/
|
||||
private const PENALTY_N1 = 3;
|
||||
private const PENALTY_N2 = 3;
|
||||
private const PENALTY_N3 = 40;
|
||||
private const PENALTY_N4 = 10;
|
||||
|
||||
/**
|
||||
* The current mask pattern value (0-7)
|
||||
*/
|
||||
private int $maskPattern;
|
||||
|
||||
/**
|
||||
* MaskPattern constructor.
|
||||
*
|
||||
* @throws \chillerlan\QRCode\QRCodeException
|
||||
*/
|
||||
public function __construct(int $maskPattern){
|
||||
|
||||
if(($maskPattern & 0b111) !== $maskPattern){
|
||||
throw new QRCodeException('invalid mask pattern');
|
||||
}
|
||||
|
||||
$this->maskPattern = $maskPattern;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current mask pattern
|
||||
*/
|
||||
public function getPattern():int{
|
||||
return $this->maskPattern;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a closure that applies the mask for the chosen mask pattern.
|
||||
*
|
||||
* Note that the diagram in section 6.8.1 is misleading since it indicates that $i is column position
|
||||
* and $j is row position. In fact, as the text says, $i is row position and $j is column position.
|
||||
*
|
||||
* @see https://www.thonky.com/qr-code-tutorial/mask-patterns
|
||||
* @see https://github.com/zxing/zxing/blob/e9e2bd280bcaeabd59d0f955798384fe6c018a6c/core/src/main/java/com/google/zxing/qrcode/decoder/DataMask.java#L32-L117
|
||||
*/
|
||||
public function getMask():Closure{
|
||||
// $x = column (width), $y = row (height)
|
||||
return [
|
||||
self::PATTERN_000 => fn(int $x, int $y):bool => (($x + $y) % 2) === 0,
|
||||
self::PATTERN_001 => fn(int $x, int $y):bool => ($y % 2) === 0,
|
||||
self::PATTERN_010 => fn(int $x, int $y):bool => ($x % 3) === 0,
|
||||
self::PATTERN_011 => fn(int $x, int $y):bool => (($x + $y) % 3) === 0,
|
||||
self::PATTERN_100 => fn(int $x, int $y):bool => ((intdiv($y, 2) + intdiv($x, 3)) % 2) === 0,
|
||||
self::PATTERN_101 => fn(int $x, int $y):bool => (($x * $y) % 6) === 0,
|
||||
self::PATTERN_110 => fn(int $x, int $y):bool => (($x * $y) % 6) < 3,
|
||||
self::PATTERN_111 => fn(int $x, int $y):bool => (($x + $y + (($x * $y) % 3)) % 2) === 0,
|
||||
][$this->maskPattern];
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates the matrix of the given data interface and returns a new mask pattern instance for the best result
|
||||
*/
|
||||
public static function getBestPattern(QRMatrix $QRMatrix):self{
|
||||
$penalties = [];
|
||||
$size = $QRMatrix->getSize();
|
||||
|
||||
foreach(self::PATTERNS as $pattern){
|
||||
$mp = new self($pattern);
|
||||
$matrix = (clone $QRMatrix)->setFormatInfo($mp)->mask($mp)->getMatrix(true);
|
||||
$penalty = 0;
|
||||
|
||||
for($level = 1; $level <= 4; $level++){
|
||||
$penalty += self::{'testRule'.$level}($matrix, $size, $size);
|
||||
}
|
||||
|
||||
$penalties[$pattern] = (int)$penalty;
|
||||
}
|
||||
|
||||
return new self(array_search(min($penalties), $penalties, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply mask penalty rule 1 and return the penalty. Find repetitive cells with the same color and
|
||||
* give penalty to them. Example: 00000 or 11111.
|
||||
*/
|
||||
public static function testRule1(array $matrix, int $height, int $width):int{
|
||||
$penalty = 0;
|
||||
|
||||
// horizontal
|
||||
foreach($matrix as $row){
|
||||
$penalty += self::applyRule1($row);
|
||||
}
|
||||
|
||||
// vertical
|
||||
for($x = 0; $x < $width; $x++){
|
||||
$penalty += self::applyRule1(array_column($matrix, $x));
|
||||
}
|
||||
|
||||
return $penalty;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static function applyRule1(array $rc):int{
|
||||
$penalty = 0;
|
||||
$numSameBitCells = 0;
|
||||
$prevBit = null;
|
||||
|
||||
foreach($rc as $val){
|
||||
|
||||
if($val === $prevBit){
|
||||
$numSameBitCells++;
|
||||
}
|
||||
else{
|
||||
|
||||
if($numSameBitCells >= 5){
|
||||
$penalty += (self::PENALTY_N1 + $numSameBitCells - 5);
|
||||
}
|
||||
|
||||
$numSameBitCells = 1; // Include the cell itself.
|
||||
$prevBit = $val;
|
||||
}
|
||||
}
|
||||
|
||||
if($numSameBitCells >= 5){
|
||||
$penalty += (self::PENALTY_N1 + $numSameBitCells - 5);
|
||||
}
|
||||
|
||||
return $penalty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply mask penalty rule 2 and return the penalty. Find 2x2 blocks with the same color and give
|
||||
* penalty to them. This is actually equivalent to the spec's rule, which is to find MxN blocks and give a
|
||||
* penalty proportional to (M-1)x(N-1), because this is the number of 2x2 blocks inside such a block.
|
||||
*/
|
||||
public static function testRule2(array $matrix, int $height, int $width):int{
|
||||
$penalty = 0;
|
||||
|
||||
foreach($matrix as $y => $row){
|
||||
|
||||
if($y > ($height - 2)){
|
||||
break;
|
||||
}
|
||||
|
||||
foreach($row as $x => $val){
|
||||
|
||||
if($x > ($width - 2)){
|
||||
break;
|
||||
}
|
||||
|
||||
if(
|
||||
$val === $row[($x + 1)]
|
||||
&& $val === $matrix[($y + 1)][$x]
|
||||
&& $val === $matrix[($y + 1)][($x + 1)]
|
||||
){
|
||||
$penalty++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (self::PENALTY_N2 * $penalty);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply mask penalty rule 3 and return the penalty. Find consecutive runs of 1:1:3:1:1:4
|
||||
* starting with black, or 4:1:1:3:1:1 starting with white, and give penalty to them. If we
|
||||
* find patterns like 000010111010000, we give penalty once.
|
||||
*/
|
||||
public static function testRule3(array $matrix, int $height, int $width):int{
|
||||
$penalties = 0;
|
||||
|
||||
foreach($matrix as $y => $row){
|
||||
foreach($row as $x => $val){
|
||||
|
||||
if(
|
||||
($x + 6) < $width
|
||||
&& $val
|
||||
&& !$row[($x + 1)]
|
||||
&& $row[($x + 2)]
|
||||
&& $row[($x + 3)]
|
||||
&& $row[($x + 4)]
|
||||
&& !$row[($x + 5)]
|
||||
&& $row[($x + 6)]
|
||||
&& (
|
||||
self::isWhiteHorizontal($row, $width, ($x - 4), $x)
|
||||
|| self::isWhiteHorizontal($row, $width, ($x + 7), ($x + 11))
|
||||
)
|
||||
){
|
||||
$penalties++;
|
||||
}
|
||||
|
||||
if(
|
||||
($y + 6) < $height
|
||||
&& $val
|
||||
&& !$matrix[($y + 1)][$x]
|
||||
&& $matrix[($y + 2)][$x]
|
||||
&& $matrix[($y + 3)][$x]
|
||||
&& $matrix[($y + 4)][$x]
|
||||
&& !$matrix[($y + 5)][$x]
|
||||
&& $matrix[($y + 6)][$x]
|
||||
&& (
|
||||
self::isWhiteVertical($matrix, $height, $x, ($y - 4), $y)
|
||||
|| self::isWhiteVertical($matrix, $height, $x, ($y + 7), ($y + 11))
|
||||
)
|
||||
){
|
||||
$penalties++;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return ($penalties * self::PENALTY_N3);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static function isWhiteHorizontal(array $row, int $width, int $from, int $to):bool{
|
||||
|
||||
if($from < 0 || $width < $to){
|
||||
return false;
|
||||
}
|
||||
|
||||
for($x = $from; $x < $to; $x++){
|
||||
if($row[$x]){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static function isWhiteVertical(array $matrix, int $height, int $x, int $from, int $to):bool{
|
||||
|
||||
if($from < 0 || $height < $to){
|
||||
return false;
|
||||
}
|
||||
|
||||
for($y = $from; $y < $to; $y++){
|
||||
if($matrix[$y][$x] === true){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply mask penalty rule 4 and return the penalty. Calculate the ratio of dark cells and give
|
||||
* penalty if the ratio is far from 50%. It gives 10 penalty for 5% distance.
|
||||
*/
|
||||
public static function testRule4(array $matrix, int $height, int $width):int{
|
||||
$darkCells = 0;
|
||||
$totalCells = ($height * $width);
|
||||
|
||||
foreach($matrix as $row){
|
||||
foreach($row as $val){
|
||||
if($val === true){
|
||||
$darkCells++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (intdiv((abs($darkCells * 2 - $totalCells) * 10), $totalCells) * self::PENALTY_N4);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Mode
|
||||
*
|
||||
* @created 19.11.2020
|
||||
* @author smiley <smiley@chillerlan.net>
|
||||
* @copyright 2020 smiley
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace chillerlan\QRCode\Common;
|
||||
|
||||
use chillerlan\QRCode\Data\{AlphaNum, Byte, Hanzi, Kanji, Number};
|
||||
use chillerlan\QRCode\QRCodeException;
|
||||
|
||||
/**
|
||||
* Data mode information - ISO 18004:2006, 6.4.1, Tables 2 and 3
|
||||
*/
|
||||
final class Mode{
|
||||
|
||||
// ISO/IEC 18004:2000 Table 2
|
||||
|
||||
/** @var int */
|
||||
public const TERMINATOR = 0b0000;
|
||||
/** @var int */
|
||||
public const NUMBER = 0b0001;
|
||||
/** @var int */
|
||||
public const ALPHANUM = 0b0010;
|
||||
/** @var int */
|
||||
public const BYTE = 0b0100;
|
||||
/** @var int */
|
||||
public const KANJI = 0b1000;
|
||||
/** @var int */
|
||||
public const HANZI = 0b1101;
|
||||
/** @var int */
|
||||
public const STRCTURED_APPEND = 0b0011;
|
||||
/** @var int */
|
||||
public const FNC1_FIRST = 0b0101;
|
||||
/** @var int */
|
||||
public const FNC1_SECOND = 0b1001;
|
||||
/** @var int */
|
||||
public const ECI = 0b0111;
|
||||
|
||||
/**
|
||||
* mode length bits for the version breakpoints 1-9, 10-26 and 27-40
|
||||
*
|
||||
* ISO/IEC 18004:2000 Table 3 - Number of bits in Character Count Indicator
|
||||
*/
|
||||
public const LENGTH_BITS = [
|
||||
self::NUMBER => [10, 12, 14],
|
||||
self::ALPHANUM => [ 9, 11, 13],
|
||||
self::BYTE => [ 8, 16, 16],
|
||||
self::KANJI => [ 8, 10, 12],
|
||||
self::HANZI => [ 8, 10, 12],
|
||||
self::ECI => [ 0, 0, 0],
|
||||
];
|
||||
|
||||
/**
|
||||
* Map of data mode => interface (detection order)
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
public const INTERFACES = [
|
||||
self::NUMBER => Number::class,
|
||||
self::ALPHANUM => AlphaNum::class,
|
||||
self::KANJI => Kanji::class,
|
||||
self::HANZI => Hanzi::class,
|
||||
self::BYTE => Byte::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* returns the length bits for the version breakpoints 1-9, 10-26 and 27-40
|
||||
*
|
||||
* @throws \chillerlan\QRCode\QRCodeException
|
||||
*/
|
||||
public static function getLengthBitsForVersion(int $mode, int $version):int{
|
||||
|
||||
if(!isset(self::LENGTH_BITS[$mode])){
|
||||
throw new QRCodeException('invalid mode given');
|
||||
}
|
||||
|
||||
$minVersion = 0;
|
||||
|
||||
foreach([9, 26, 40] as $key => $breakpoint){
|
||||
|
||||
if($version > $minVersion && $version <= $breakpoint){
|
||||
return self::LENGTH_BITS[$mode][$key];
|
||||
}
|
||||
|
||||
$minVersion = $breakpoint;
|
||||
}
|
||||
|
||||
throw new QRCodeException(sprintf('invalid version number: %d', $version));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Version
|
||||
*
|
||||
* @created 19.11.2020
|
||||
* @author smiley <smiley@chillerlan.net>
|
||||
* @copyright 2020 smiley
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace chillerlan\QRCode\Common;
|
||||
|
||||
use chillerlan\QRCode\QRCodeException;
|
||||
|
||||
/**
|
||||
* Version related tables and methods
|
||||
*/
|
||||
final class Version{
|
||||
|
||||
/**
|
||||
* Enable version auto detection
|
||||
*
|
||||
* @see \chillerlan\QRCode\QROptionsTrait::$version
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const AUTO = -1;
|
||||
|
||||
/**
|
||||
* ISO/IEC 18004:2000 Annex E, Table E.1 - Row/column coordinates of center module of Alignment Patterns
|
||||
*
|
||||
* version -> pattern
|
||||
*
|
||||
* @var int[][]
|
||||
*/
|
||||
private const ALIGNMENT_PATTERN = [
|
||||
1 => [],
|
||||
2 => [6, 18],
|
||||
3 => [6, 22],
|
||||
4 => [6, 26],
|
||||
5 => [6, 30],
|
||||
6 => [6, 34],
|
||||
7 => [6, 22, 38],
|
||||
8 => [6, 24, 42],
|
||||
9 => [6, 26, 46],
|
||||
10 => [6, 28, 50],
|
||||
11 => [6, 30, 54],
|
||||
12 => [6, 32, 58],
|
||||
13 => [6, 34, 62],
|
||||
14 => [6, 26, 46, 66],
|
||||
15 => [6, 26, 48, 70],
|
||||
16 => [6, 26, 50, 74],
|
||||
17 => [6, 30, 54, 78],
|
||||
18 => [6, 30, 56, 82],
|
||||
19 => [6, 30, 58, 86],
|
||||
20 => [6, 34, 62, 90],
|
||||
21 => [6, 28, 50, 72, 94],
|
||||
22 => [6, 26, 50, 74, 98],
|
||||
23 => [6, 30, 54, 78, 102],
|
||||
24 => [6, 28, 54, 80, 106],
|
||||
25 => [6, 32, 58, 84, 110],
|
||||
26 => [6, 30, 58, 86, 114],
|
||||
27 => [6, 34, 62, 90, 118],
|
||||
28 => [6, 26, 50, 74, 98, 122],
|
||||
29 => [6, 30, 54, 78, 102, 126],
|
||||
30 => [6, 26, 52, 78, 104, 130],
|
||||
31 => [6, 30, 56, 82, 108, 134],
|
||||
32 => [6, 34, 60, 86, 112, 138],
|
||||
33 => [6, 30, 58, 86, 114, 142],
|
||||
34 => [6, 34, 62, 90, 118, 146],
|
||||
35 => [6, 30, 54, 78, 102, 126, 150],
|
||||
36 => [6, 24, 50, 76, 102, 128, 154],
|
||||
37 => [6, 28, 54, 80, 106, 132, 158],
|
||||
38 => [6, 32, 58, 84, 110, 136, 162],
|
||||
39 => [6, 26, 54, 82, 110, 138, 166],
|
||||
40 => [6, 30, 58, 86, 114, 142, 170],
|
||||
];
|
||||
|
||||
/**
|
||||
* ISO/IEC 18004:2000 Annex D, Table D.1 - Version information bit stream for each version
|
||||
*
|
||||
* no version pattern for QR Codes < 7
|
||||
*
|
||||
* @var int[]
|
||||
*/
|
||||
private const VERSION_PATTERN = [
|
||||
7 => 0b000111110010010100,
|
||||
8 => 0b001000010110111100,
|
||||
9 => 0b001001101010011001,
|
||||
10 => 0b001010010011010011,
|
||||
11 => 0b001011101111110110,
|
||||
12 => 0b001100011101100010,
|
||||
13 => 0b001101100001000111,
|
||||
14 => 0b001110011000001101,
|
||||
15 => 0b001111100100101000,
|
||||
16 => 0b010000101101111000,
|
||||
17 => 0b010001010001011101,
|
||||
18 => 0b010010101000010111,
|
||||
19 => 0b010011010100110010,
|
||||
20 => 0b010100100110100110,
|
||||
21 => 0b010101011010000011,
|
||||
22 => 0b010110100011001001,
|
||||
23 => 0b010111011111101100,
|
||||
24 => 0b011000111011000100,
|
||||
25 => 0b011001000111100001,
|
||||
26 => 0b011010111110101011,
|
||||
27 => 0b011011000010001110,
|
||||
28 => 0b011100110000011010,
|
||||
29 => 0b011101001100111111,
|
||||
30 => 0b011110110101110101,
|
||||
31 => 0b011111001001010000,
|
||||
32 => 0b100000100111010101,
|
||||
33 => 0b100001011011110000,
|
||||
34 => 0b100010100010111010,
|
||||
35 => 0b100011011110011111,
|
||||
36 => 0b100100101100001011,
|
||||
37 => 0b100101010000101110,
|
||||
38 => 0b100110101001100100,
|
||||
39 => 0b100111010101000001,
|
||||
40 => 0b101000110001101001,
|
||||
];
|
||||
|
||||
/**
|
||||
* ISO/IEC 18004:2000 Tables 13-22 - Error correction characteristics
|
||||
*
|
||||
* @see http://www.thonky.com/qr-code-tutorial/error-correction-table
|
||||
*/
|
||||
private const RSBLOCKS = [
|
||||
1 => [[ 7, [[ 1, 19], [ 0, 0]]], [10, [[ 1, 16], [ 0, 0]]], [13, [[ 1, 13], [ 0, 0]]], [17, [[ 1, 9], [ 0, 0]]]],
|
||||
2 => [[10, [[ 1, 34], [ 0, 0]]], [16, [[ 1, 28], [ 0, 0]]], [22, [[ 1, 22], [ 0, 0]]], [28, [[ 1, 16], [ 0, 0]]]],
|
||||
3 => [[15, [[ 1, 55], [ 0, 0]]], [26, [[ 1, 44], [ 0, 0]]], [18, [[ 2, 17], [ 0, 0]]], [22, [[ 2, 13], [ 0, 0]]]],
|
||||
4 => [[20, [[ 1, 80], [ 0, 0]]], [18, [[ 2, 32], [ 0, 0]]], [26, [[ 2, 24], [ 0, 0]]], [16, [[ 4, 9], [ 0, 0]]]],
|
||||
5 => [[26, [[ 1, 108], [ 0, 0]]], [24, [[ 2, 43], [ 0, 0]]], [18, [[ 2, 15], [ 2, 16]]], [22, [[ 2, 11], [ 2, 12]]]],
|
||||
6 => [[18, [[ 2, 68], [ 0, 0]]], [16, [[ 4, 27], [ 0, 0]]], [24, [[ 4, 19], [ 0, 0]]], [28, [[ 4, 15], [ 0, 0]]]],
|
||||
7 => [[20, [[ 2, 78], [ 0, 0]]], [18, [[ 4, 31], [ 0, 0]]], [18, [[ 2, 14], [ 4, 15]]], [26, [[ 4, 13], [ 1, 14]]]],
|
||||
8 => [[24, [[ 2, 97], [ 0, 0]]], [22, [[ 2, 38], [ 2, 39]]], [22, [[ 4, 18], [ 2, 19]]], [26, [[ 4, 14], [ 2, 15]]]],
|
||||
9 => [[30, [[ 2, 116], [ 0, 0]]], [22, [[ 3, 36], [ 2, 37]]], [20, [[ 4, 16], [ 4, 17]]], [24, [[ 4, 12], [ 4, 13]]]],
|
||||
10 => [[18, [[ 2, 68], [ 2, 69]]], [26, [[ 4, 43], [ 1, 44]]], [24, [[ 6, 19], [ 2, 20]]], [28, [[ 6, 15], [ 2, 16]]]],
|
||||
11 => [[20, [[ 4, 81], [ 0, 0]]], [30, [[ 1, 50], [ 4, 51]]], [28, [[ 4, 22], [ 4, 23]]], [24, [[ 3, 12], [ 8, 13]]]],
|
||||
12 => [[24, [[ 2, 92], [ 2, 93]]], [22, [[ 6, 36], [ 2, 37]]], [26, [[ 4, 20], [ 6, 21]]], [28, [[ 7, 14], [ 4, 15]]]],
|
||||
13 => [[26, [[ 4, 107], [ 0, 0]]], [22, [[ 8, 37], [ 1, 38]]], [24, [[ 8, 20], [ 4, 21]]], [22, [[12, 11], [ 4, 12]]]],
|
||||
14 => [[30, [[ 3, 115], [ 1, 116]]], [24, [[ 4, 40], [ 5, 41]]], [20, [[11, 16], [ 5, 17]]], [24, [[11, 12], [ 5, 13]]]],
|
||||
15 => [[22, [[ 5, 87], [ 1, 88]]], [24, [[ 5, 41], [ 5, 42]]], [30, [[ 5, 24], [ 7, 25]]], [24, [[11, 12], [ 7, 13]]]],
|
||||
16 => [[24, [[ 5, 98], [ 1, 99]]], [28, [[ 7, 45], [ 3, 46]]], [24, [[15, 19], [ 2, 20]]], [30, [[ 3, 15], [13, 16]]]],
|
||||
17 => [[28, [[ 1, 107], [ 5, 108]]], [28, [[10, 46], [ 1, 47]]], [28, [[ 1, 22], [15, 23]]], [28, [[ 2, 14], [17, 15]]]],
|
||||
18 => [[30, [[ 5, 120], [ 1, 121]]], [26, [[ 9, 43], [ 4, 44]]], [28, [[17, 22], [ 1, 23]]], [28, [[ 2, 14], [19, 15]]]],
|
||||
19 => [[28, [[ 3, 113], [ 4, 114]]], [26, [[ 3, 44], [11, 45]]], [26, [[17, 21], [ 4, 22]]], [26, [[ 9, 13], [16, 14]]]],
|
||||
20 => [[28, [[ 3, 107], [ 5, 108]]], [26, [[ 3, 41], [13, 42]]], [30, [[15, 24], [ 5, 25]]], [28, [[15, 15], [10, 16]]]],
|
||||
21 => [[28, [[ 4, 116], [ 4, 117]]], [26, [[17, 42], [ 0, 0]]], [28, [[17, 22], [ 6, 23]]], [30, [[19, 16], [ 6, 17]]]],
|
||||
22 => [[28, [[ 2, 111], [ 7, 112]]], [28, [[17, 46], [ 0, 0]]], [30, [[ 7, 24], [16, 25]]], [24, [[34, 13], [ 0, 0]]]],
|
||||
23 => [[30, [[ 4, 121], [ 5, 122]]], [28, [[ 4, 47], [14, 48]]], [30, [[11, 24], [14, 25]]], [30, [[16, 15], [14, 16]]]],
|
||||
24 => [[30, [[ 6, 117], [ 4, 118]]], [28, [[ 6, 45], [14, 46]]], [30, [[11, 24], [16, 25]]], [30, [[30, 16], [ 2, 17]]]],
|
||||
25 => [[26, [[ 8, 106], [ 4, 107]]], [28, [[ 8, 47], [13, 48]]], [30, [[ 7, 24], [22, 25]]], [30, [[22, 15], [13, 16]]]],
|
||||
26 => [[28, [[10, 114], [ 2, 115]]], [28, [[19, 46], [ 4, 47]]], [28, [[28, 22], [ 6, 23]]], [30, [[33, 16], [ 4, 17]]]],
|
||||
27 => [[30, [[ 8, 122], [ 4, 123]]], [28, [[22, 45], [ 3, 46]]], [30, [[ 8, 23], [26, 24]]], [30, [[12, 15], [28, 16]]]],
|
||||
28 => [[30, [[ 3, 117], [10, 118]]], [28, [[ 3, 45], [23, 46]]], [30, [[ 4, 24], [31, 25]]], [30, [[11, 15], [31, 16]]]],
|
||||
29 => [[30, [[ 7, 116], [ 7, 117]]], [28, [[21, 45], [ 7, 46]]], [30, [[ 1, 23], [37, 24]]], [30, [[19, 15], [26, 16]]]],
|
||||
30 => [[30, [[ 5, 115], [10, 116]]], [28, [[19, 47], [10, 48]]], [30, [[15, 24], [25, 25]]], [30, [[23, 15], [25, 16]]]],
|
||||
31 => [[30, [[13, 115], [ 3, 116]]], [28, [[ 2, 46], [29, 47]]], [30, [[42, 24], [ 1, 25]]], [30, [[23, 15], [28, 16]]]],
|
||||
32 => [[30, [[17, 115], [ 0, 0]]], [28, [[10, 46], [23, 47]]], [30, [[10, 24], [35, 25]]], [30, [[19, 15], [35, 16]]]],
|
||||
33 => [[30, [[17, 115], [ 1, 116]]], [28, [[14, 46], [21, 47]]], [30, [[29, 24], [19, 25]]], [30, [[11, 15], [46, 16]]]],
|
||||
34 => [[30, [[13, 115], [ 6, 116]]], [28, [[14, 46], [23, 47]]], [30, [[44, 24], [ 7, 25]]], [30, [[59, 16], [ 1, 17]]]],
|
||||
35 => [[30, [[12, 121], [ 7, 122]]], [28, [[12, 47], [26, 48]]], [30, [[39, 24], [14, 25]]], [30, [[22, 15], [41, 16]]]],
|
||||
36 => [[30, [[ 6, 121], [14, 122]]], [28, [[ 6, 47], [34, 48]]], [30, [[46, 24], [10, 25]]], [30, [[ 2, 15], [64, 16]]]],
|
||||
37 => [[30, [[17, 122], [ 4, 123]]], [28, [[29, 46], [14, 47]]], [30, [[49, 24], [10, 25]]], [30, [[24, 15], [46, 16]]]],
|
||||
38 => [[30, [[ 4, 122], [18, 123]]], [28, [[13, 46], [32, 47]]], [30, [[48, 24], [14, 25]]], [30, [[42, 15], [32, 16]]]],
|
||||
39 => [[30, [[20, 117], [ 4, 118]]], [28, [[40, 47], [ 7, 48]]], [30, [[43, 24], [22, 25]]], [30, [[10, 15], [67, 16]]]],
|
||||
40 => [[30, [[19, 118], [ 6, 119]]], [28, [[18, 47], [31, 48]]], [30, [[34, 24], [34, 25]]], [30, [[20, 15], [61, 16]]]],
|
||||
];
|
||||
|
||||
/**
|
||||
* ISO/IEC 18004:2000 Table 1 - Data capacity of all versions of QR Code
|
||||
*/
|
||||
private const TOTAL_CODEWORDS = [
|
||||
1 => 26,
|
||||
2 => 44,
|
||||
3 => 70,
|
||||
4 => 100,
|
||||
5 => 134,
|
||||
6 => 172,
|
||||
7 => 196,
|
||||
8 => 242,
|
||||
9 => 292,
|
||||
10 => 346,
|
||||
11 => 404,
|
||||
12 => 466,
|
||||
13 => 532,
|
||||
14 => 581,
|
||||
15 => 655,
|
||||
16 => 733,
|
||||
17 => 815,
|
||||
18 => 901,
|
||||
19 => 991,
|
||||
20 => 1085,
|
||||
21 => 1156,
|
||||
22 => 1258,
|
||||
23 => 1364,
|
||||
24 => 1474,
|
||||
25 => 1588,
|
||||
26 => 1706,
|
||||
27 => 1828,
|
||||
28 => 1921,
|
||||
29 => 2051,
|
||||
30 => 2185,
|
||||
31 => 2323,
|
||||
32 => 2465,
|
||||
33 => 2611,
|
||||
34 => 2761,
|
||||
35 => 2876,
|
||||
36 => 3034,
|
||||
37 => 3196,
|
||||
38 => 3362,
|
||||
39 => 3532,
|
||||
40 => 3706,
|
||||
];
|
||||
|
||||
/**
|
||||
* QR Code version number
|
||||
*/
|
||||
private int $version;
|
||||
|
||||
/**
|
||||
* Version constructor.
|
||||
*
|
||||
* @throws \chillerlan\QRCode\QRCodeException
|
||||
*/
|
||||
public function __construct(int $version){
|
||||
|
||||
if($version < 1 || $version > 40){
|
||||
throw new QRCodeException('invalid version given');
|
||||
}
|
||||
|
||||
$this->version = $version;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the current version number as string
|
||||
*/
|
||||
public function __toString():string{
|
||||
return (string)$this->version;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the current version number
|
||||
*/
|
||||
public function getVersionNumber():int{
|
||||
return $this->version;
|
||||
}
|
||||
|
||||
/**
|
||||
* the matrix size for the given version
|
||||
*/
|
||||
public function getDimension():int{
|
||||
return (($this->version * 4) + 17);
|
||||
}
|
||||
|
||||
/**
|
||||
* the version pattern for the given version
|
||||
*/
|
||||
public function getVersionPattern():?int{
|
||||
return (self::VERSION_PATTERN[$this->version] ?? null);
|
||||
}
|
||||
|
||||
/**
|
||||
* the alignment patterns for the current version
|
||||
*
|
||||
* @return int[]
|
||||
*/
|
||||
public function getAlignmentPattern():array{
|
||||
return self::ALIGNMENT_PATTERN[$this->version];
|
||||
}
|
||||
|
||||
/**
|
||||
* returns ECC block information for the given $version and $eccLevel
|
||||
*/
|
||||
public function getRSBlocks(EccLevel $eccLevel):array{
|
||||
return self::RSBLOCKS[$this->version][$eccLevel->getOrdinal()];
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the maximum codewords for the current version
|
||||
*/
|
||||
public function getTotalCodewords():int{
|
||||
return self::TOTAL_CODEWORDS[$this->version];
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user