v1.0 Initial commit of project

This commit is contained in:
2026-01-01 10:54:18 +01:00
commit 768cf78b57
990 changed files with 241213 additions and 0 deletions

View File

@@ -0,0 +1,96 @@
<?php
namespace Zxing\Common;
use ReflectionClass;
/**
* A general enum implementation until we got SplEnum.
*/
final class AbstractEnum implements \Stringable
{
/**
* Default value.
*/
public const __default = null;
/**
* Current value.
*
* @var mixed
*/
private $value;
/**
* Cache of constants.
*
* @var array<string, mixed>|null
*/
private ?array $constants = null;
/**
* Creates a new enum.
*
* @param mixed $initialValue
* @param boolean $strict
*/
public function __construct($initialValue = null, private $strict = false)
{
$this->change($initialValue);
}
/**
* Changes the value of the enum.
*
* @param mixed $value
*
* @return void
*/
public function change($value)
{
if (!in_array($value, $this->getConstList(), $this->strict)) {
throw new \UnexpectedValueException('Value not a const in enum ' . $this::class);
}
$this->value = $value;
}
/**
* Gets all constants (possible values) as an array.
*
* @param boolean $includeDefault
*
* @return array
*/
public function getConstList($includeDefault = true)
{
if ($this->constants === null) {
$reflection = new ReflectionClass($this);
$this->constants = $reflection->getConstants();
}
if ($includeDefault) {
return $this->constants;
}
$constants = $this->constants;
unset($constants['__default']);
return $constants;
}
/**
* Gets current value.
*
* @return mixed
*/
public function get()
{
return $this->value;
}
/**
* Gets the name of the enum.
*
* @return string
*/
public function __toString(): string
{
return (string)array_search($this->value, $this->getConstList());
}
}

View File

@@ -0,0 +1,425 @@
<?php
/**
* Created by PhpStorm.
* User: Ashot
* Date: 3/25/15
* Time: 11:51
*/
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Zxing\Common;
/**
* <p>A simple, fast array of bits, represented compactly by an array of ints internally.</p>
*
* @author Sean Owen
*/
final class BitArray
{
/**
* @var mixed[]|mixed|int[]|null
*/
private $bits;
/**
* @var mixed|null
*/
private $size;
public function __construct($bits = [], $size = 0)
{
if (!$bits && !$size) {
$this->$size = 0;
$this->bits = [];
} elseif ($bits && !$size) {
$this->size = $bits;
$this->bits = self::makeArray($bits);
} else {
$this->bits = $bits;
$this->size = $size;
}
}
private static function makeArray($size)
{
return [];
}
public function getSize()
{
return $this->size;
}
public function getSizeInBytes()
{
return ($this->size + 7) / 8;
}
/**
* Sets bit i.
*
* @param bit $i to set
*/
public function set($i): void
{
$this->bits[(int)($i / 32)] |= 1 << ($i & 0x1F);
$this->bits[(int)($i / 32)] = ($this->bits[(int)($i / 32)]);
}
/**
* Flips bit i.
*
* @param bit $i to set
*/
public function flip($i): void
{
$this->bits[(int)($i / 32)] ^= 1 << ($i & 0x1F);
$this->bits[(int)($i / 32)] = ($this->bits[(int)($i / 32)]);
}
/**
* @param first $from bit to check
*
* @return index of first bit that is set, starting from the given index, or size if none are set
* at or beyond this given index
* @see #getNextUnset(int)
*/
public function getNextSet($from)
{
if ($from >= $this->size) {
return $this->size;
}
$bitsOffset = (int)($from / 32);
$currentBits = (int)$this->bits[$bitsOffset];
// mask off lesser bits first
$currentBits &= ~((1 << ($from & 0x1F)) - 1);
while ($currentBits == 0) {
if (++$bitsOffset == (is_countable($this->bits) ? count($this->bits) : 0)) {
return $this->size;
}
$currentBits = $this->bits[$bitsOffset];
}
$result = ($bitsOffset * 32) + numberOfTrailingZeros($currentBits); //numberOfTrailingZeros
return $result > $this->size ? $this->size : $result;
}
/**
* @param index $from to start looking for unset bit
*
* @return index of next unset bit, or {@code size} if none are unset until the end
* @see #getNextSet(int)
*/
public function getNextUnset($from)
{
if ($from >= $this->size) {
return $this->size;
}
$bitsOffset = (int)($from / 32);
$currentBits = ~$this->bits[$bitsOffset];
// mask off lesser bits first
$currentBits &= ~((1 << ($from & 0x1F)) - 1);
while ($currentBits == 0) {
if (++$bitsOffset == (is_countable($this->bits) ? count($this->bits) : 0)) {
return $this->size;
}
$currentBits = (~$this->bits[$bitsOffset]);
}
$result = ($bitsOffset * 32) + numberOfTrailingZeros($currentBits);
return $result > $this->size ? $this->size : $result;
}
/**
* Sets a block of 32 bits, starting at bit i.
*
* @param first $i bit to set
* @param the $newBits new value of the next 32 bits. Note again that the least-significant bit
* corresponds to bit i, the next-least-significant to i+1, and so on.
*/
public function setBulk($i, $newBits): void
{
$this->bits[(int)($i / 32)] = $newBits;
}
/**
* Sets a range of bits.
*
* @param start $start of range, inclusive.
* @param end $end of range, exclusive
*/
public function setRange($start, $end)
{
if ($end < $start) {
throw new \InvalidArgumentException();
}
if ($end == $start) {
return;
}
$end--; // will be easier to treat this as the last actually set bit -- inclusive
$firstInt = (int)($start / 32);
$lastInt = (int)($end / 32);
for ($i = $firstInt; $i <= $lastInt; $i++) {
$firstBit = $i > $firstInt ? 0 : $start & 0x1F;
$lastBit = $i < $lastInt ? 31 : $end & 0x1F;
$mask = 0;
if ($firstBit == 0 && $lastBit == 31) {
$mask = -1;
} else {
$mask = 0;
for ($j = $firstBit; $j <= $lastBit; $j++) {
$mask |= 1 << $j;
}
}
$this->bits[$i] = ($this->bits[$i] | $mask);
}
}
/**
* Clears all bits (sets to false).
*/
public function clear(): void
{
$max = is_countable($this->bits) ? count($this->bits) : 0;
for ($i = 0; $i < $max; $i++) {
$this->bits[$i] = 0;
}
}
/**
* Efficient method to check if a range of bits is set, or not set.
*
* @param start $start of range, inclusive.
* @param end $end of range, exclusive
* @param if $value true, checks that bits in range are set, otherwise checks that they are not set
*
* @return true iff all bits are set or not set in range, according to value argument
* @throws InvalidArgumentException if end is less than or equal to start
*/
public function isRange($start, $end, $value)
{
if ($end < $start) {
throw new \InvalidArgumentException();
}
if ($end == $start) {
return true; // empty range matches
}
$end--; // will be easier to treat this as the last actually set bit -- inclusive
$firstInt = (int)($start / 32);
$lastInt = (int)($end / 32);
for ($i = $firstInt; $i <= $lastInt; $i++) {
$firstBit = $i > $firstInt ? 0 : $start & 0x1F;
$lastBit = $i < $lastInt ? 31 : $end & 0x1F;
$mask = 0;
if ($firstBit == 0 && $lastBit == 31) {
$mask = -1;
} else {
$mask = 0;
for ($j = $firstBit; $j <= $lastBit; $j++) {
$mask = ($mask | (1 << $j));
}
}
// Return false if we're looking for 1s and the masked bits[i] isn't all 1s (that is,
// equals the mask, or we're looking for 0s and the masked portion is not all 0s
if (($this->bits[$i] & $mask) != ($value ? $mask : 0)) {
return false;
}
}
return true;
}
/**
* Appends the least-significant bits, from value, in order from most-significant to
* least-significant. For example, appending 6 bits from 0x000001E will append the bits
* 0, 1, 1, 1, 1, 0 in that order.
*
* @param $value {@code int} containing bits to append
* @param bits $numBits from value to append
*/
public function appendBits($value, $numBits)
{
if ($numBits < 0 || $numBits > 32) {
throw new \InvalidArgumentException("Num bits must be between 0 and 32");
}
$this->ensureCapacity($this->size + $numBits);
for ($numBitsLeft = $numBits; $numBitsLeft > 0; $numBitsLeft--) {
$this->appendBit((($value >> ($numBitsLeft - 1)) & 0x01) == 1);
}
}
private function ensureCapacity($size): void
{
if ($size > (is_countable($this->bits) ? count($this->bits) : 0) * 32) {
$newBits = self::makeArray($size);
$newBits = arraycopy($this->bits, 0, $newBits, 0, is_countable($this->bits) ? count($this->bits) : 0);
$this->bits = $newBits;
}
}
public function appendBit($bit): void
{
$this->ensureCapacity($this->size + 1);
if ($bit) {
$this->bits[(int)($this->size / 32)] |= 1 << ($this->size & 0x1F);
}
$this->size++;
}
public function appendBitArray($other): void
{
$otherSize = $other->size;
$this->ensureCapacity($this->size + $otherSize);
for ($i = 0; $i < $otherSize; $i++) {
$this->appendBit($other->get($i));
}
}
public function _xor($other)
{
if ((is_countable($this->bits) ? count($this->bits) : 0) !== (is_countable($other->bits) ? count($other->bits) : 0)) {
throw new \InvalidArgumentException("Sizes don't match");
}
$count = is_countable($this->bits) ? count($this->bits) : 0;
for ($i = 0; $i < $count; $i++) {
// The last byte could be incomplete (i.e. not have 8 bits in
// it) but there is no problem since 0 XOR 0 == 0.
$this->bits[$i] ^= $other->bits[$i];
}
}
/**
*
* @param first $bitOffset bit to start writing
* @param array $array to write into. Bytes are written most-significant byte first. This is the opposite
* of the internal representation, which is exposed by {@link #getBitArray()}
* @param position $offset in array to start writing
* @param how $numBytes many bytes to write
*/
public function toBytes($bitOffset, &$array, $offset, $numBytes): void
{
for ($i = 0; $i < $numBytes; $i++) {
$theByte = 0;
for ($j = 0; $j < 8; $j++) {
if ($this->get($bitOffset)) {
$theByte |= 1 << (7 - $j);
}
$bitOffset++;
}
$array[(int)($offset + $i)] = $theByte;
}
}
/**
* @param $i ; bit to get
*
* @return true iff bit i is set
*/
public function get($i)
{
$key = (int)($i / 32);
return ($this->bits[$key] & (1 << ($i & 0x1F))) != 0;
}
/**
* @return array underlying array of ints. The first element holds the first 32 bits, and the least
* significant bit is bit 0.
*/
public function getBitArray()
{
return $this->bits;
}
/**
* Reverses all bits in the array.
*/
public function reverse(): void
{
$newBits = [];
// reverse all int's first
$len = (($this->size - 1) / 32);
$oldBitsLen = $len + 1;
for ($i = 0; $i < $oldBitsLen; $i++) {
$x = $this->bits[$i];/*
$x = (($x >> 1) & 0x55555555L) | (($x & 0x55555555L) << 1);
$x = (($x >> 2) & 0x33333333L) | (($x & 0x33333333L) << 2);
$x = (($x >> 4) & 0x0f0f0f0fL) | (($x & 0x0f0f0f0fL) << 4);
$x = (($x >> 8) & 0x00ff00ffL) | (($x & 0x00ff00ffL) << 8);
$x = (($x >> 16) & 0x0000ffffL) | (($x & 0x0000ffffL) << 16);*/
$x = (($x >> 1) & 0x55555555) | (($x & 0x55555555) << 1);
$x = (($x >> 2) & 0x33333333) | (($x & 0x33333333) << 2);
$x = (($x >> 4) & 0x0f0f0f0f) | (($x & 0x0f0f0f0f) << 4);
$x = (($x >> 8) & 0x00ff00ff) | (($x & 0x00ff00ff) << 8);
$x = (($x >> 16) & 0x0000ffff) | (($x & 0x0000ffff) << 16);
$newBits[(int)$len - $i] = (int)$x;
}
// now correct the int's if the bit size isn't a multiple of 32
if ($this->size != $oldBitsLen * 32) {
$leftOffset = $oldBitsLen * 32 - $this->size;
$mask = 1;
for ($i = 0; $i < 31 - $leftOffset; $i++) {
$mask = ($mask << 1) | 1;
}
$currentInt = ($newBits[0] >> $leftOffset) & $mask;
for ($i = 1; $i < $oldBitsLen; $i++) {
$nextInt = $newBits[$i];
$currentInt |= $nextInt << (32 - $leftOffset);
$newBits[(int)($i) - 1] = $currentInt;
$currentInt = ($nextInt >> $leftOffset) & $mask;
}
$newBits[(int)($oldBitsLen) - 1] = $currentInt;
}
// $bits = $newBits;
}
public function equals($o)
{
if (!($o instanceof BitArray)) {
return false;
}
$other = $o;
return $this->size == $other->size && $this->bits === $other->bits;
}
public function hashCode()
{
return 31 * $this->size + hashCode($this->bits);
}
public function toString()
{
$result = '';
for ($i = 0; $i < $this->size; $i++) {
if (($i & 0x07) == 0) {
$result .= ' ';
}
$result .= ($this->get($i) ? 'X' : '.');
}
return (string)$result;
}
public function _clone(): \Zxing\Common\BitArray
{
return new BitArray($this->bits, $this->size);
}
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,115 @@
<?php
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Zxing\Common;
/**
* <p>This provides an easy abstraction to read bits at a time from a sequence of bytes, where the
* number of bits read is not often a multiple of 8.</p>
*
* <p>This class is thread-safe but not reentrant -- unless the caller modifies the bytes array
* it passed in, in which case all bets are off.</p>
*
* @author Sean Owen
*/
final class BitSource
{
private int $byteOffset = 0;
private int $bitOffset = 0;
/**
* @param bytes $bytes from which this will read bits. Bits will be read from the first byte first.
* Bits are read within a byte from most-significant to least-significant bit.
*/
public function __construct(private $bytes)
{
}
/**
* @return index of next bit in current byte which would be read by the next call to {@link #readBits(int)}.
*/
public function getBitOffset()
{
return $this->bitOffset;
}
/**
* @return index of next byte in input byte array which would be read by the next call to {@link #readBits(int)}.
*/
public function getByteOffset()
{
return $this->byteOffset;
}
/**
* @param number $numBits of bits to read
*
* @return int representing the bits read. The bits will appear as the least-significant
* bits of the int
* @throws InvalidArgumentException if numBits isn't in [1,32] or more than is available
*/
public function readBits($numBits)
{
if ($numBits < 1 || $numBits > 32 || $numBits > $this->available()) {
throw new \InvalidArgumentException(strval($numBits));
}
$result = 0;
// First, read remainder from current byte
if ($this->bitOffset > 0) {
$bitsLeft = 8 - $this->bitOffset;
$toRead = $numBits < $bitsLeft ? $numBits : $bitsLeft;
$bitsToNotRead = $bitsLeft - $toRead;
$mask = (0xFF >> (8 - $toRead)) << $bitsToNotRead;
$result = ($this->bytes[$this->byteOffset] & $mask) >> $bitsToNotRead;
$numBits -= $toRead;
$this->bitOffset += $toRead;
if ($this->bitOffset == 8) {
$this->bitOffset = 0;
$this->byteOffset++;
}
}
// Next read whole bytes
if ($numBits > 0) {
while ($numBits >= 8) {
$result = ($result << 8) | ($this->bytes[$this->byteOffset] & 0xFF);
$this->byteOffset++;
$numBits -= 8;
}
// Finally read a partial byte
if ($numBits > 0) {
$bitsToNotRead = 8 - $numBits;
$mask = (0xFF >> $bitsToNotRead) << $bitsToNotRead;
$result = ($result << $numBits) | (($this->bytes[$this->byteOffset] & $mask) >> $bitsToNotRead);
$this->bitOffset += $numBits;
}
}
return $result;
}
/**
* @return number of bits that can be read successfully
*/
public function available()
{
return 8 * ((is_countable($this->bytes) ? count($this->bytes) : 0) - $this->byteOffset) - $this->bitOffset;
}
}

View File

@@ -0,0 +1,154 @@
<?php
namespace Zxing\Common;
/**
* Encapsulates a Character Set ECI, according to "Extended Channel
* Interpretations" 5.3.1.1 of ISO 18004.
*/
final class CharacterSetECI
{
/**#@+
* Character set constants.
*/
public const CP437 = 0;
public const ISO8859_1 = 1;
public const ISO8859_2 = 4;
public const ISO8859_3 = 5;
public const ISO8859_4 = 6;
public const ISO8859_5 = 7;
public const ISO8859_6 = 8;
public const ISO8859_7 = 9;
public const ISO8859_8 = 10;
public const ISO8859_9 = 11;
public const ISO8859_10 = 12;
public const ISO8859_11 = 13;
public const ISO8859_12 = 14;
public const ISO8859_13 = 15;
public const ISO8859_14 = 16;
public const ISO8859_15 = 17;
public const ISO8859_16 = 18;
public const SJIS = 20;
public const CP1250 = 21;
public const CP1251 = 22;
public const CP1252 = 23;
public const CP1256 = 24;
public const UNICODE_BIG_UNMARKED = 25;
public const UTF8 = 26;
public const ASCII = 27;
public const BIG5 = 28;
public const GB18030 = 29;
public const EUC_KR = 30;
/**
* Map between character names and their ECI values.
*/
private static array $nameToEci = [
'ISO-8859-1' => self::ISO8859_1,
'ISO-8859-2' => self::ISO8859_2,
'ISO-8859-3' => self::ISO8859_3,
'ISO-8859-4' => self::ISO8859_4,
'ISO-8859-5' => self::ISO8859_5,
'ISO-8859-6' => self::ISO8859_6,
'ISO-8859-7' => self::ISO8859_7,
'ISO-8859-8' => self::ISO8859_8,
'ISO-8859-9' => self::ISO8859_9,
'ISO-8859-10' => self::ISO8859_10,
'ISO-8859-11' => self::ISO8859_11,
'ISO-8859-12' => self::ISO8859_12,
'ISO-8859-13' => self::ISO8859_13,
'ISO-8859-14' => self::ISO8859_14,
'ISO-8859-15' => self::ISO8859_15,
'ISO-8859-16' => self::ISO8859_16,
'SHIFT-JIS' => self::SJIS,
'WINDOWS-1250' => self::CP1250,
'WINDOWS-1251' => self::CP1251,
'WINDOWS-1252' => self::CP1252,
'WINDOWS-1256' => self::CP1256,
'UTF-16BE' => self::UNICODE_BIG_UNMARKED,
'UTF-8' => self::UTF8,
'ASCII' => self::ASCII,
'GBK' => self::GB18030,
'EUC-KR' => self::EUC_KR,
];
/**#@-*/
/**
* Additional possible values for character sets.
*/
private static array $additionalValues = [
self::CP437 => 2,
self::ASCII => 170,
];
private static int|string|null $name = null;
/**
* Gets character set ECI by value.
*
* @param string $value
*
* @return CharacterSetEci|null
*/
public static function getCharacterSetECIByValue($value)
{
if ($value < 0 || $value >= 900) {
throw new \InvalidArgumentException('Value must be between 0 and 900');
}
if (false !== ($key = array_search($value, self::$additionalValues))) {
$value = $key;
}
array_search($value, self::$nameToEci);
try {
self::setName($value);
return new self($value);
} catch (\UnexpectedValueException) {
return null;
}
}
private static function setName($value)
{
foreach (self::$nameToEci as $name => $key) {
if ($key == $value) {
self::$name = $name;
return true;
}
}
if (self::$name == null) {
foreach (self::$additionalValues as $name => $key) {
if ($key == $value) {
self::$name = $name;
return true;
}
}
}
}
/**
* Gets character set ECI name.
*
* @return character set ECI name|null
*/
public static function name()
{
return self::$name;
}
/**
* Gets character set ECI by name.
*
* @param string $name
*
* @return CharacterSetEci|null
*/
public static function getCharacterSetECIByName($name)
{
$name = strtoupper($name);
if (isset(self::$nameToEci[$name])) {
return new self(self::$nameToEci[$name]);
}
return null;
}
}

View File

@@ -0,0 +1,111 @@
<?php
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Zxing\Common;
/**
* <p>Encapsulates the result of decoding a matrix of bits. This typically
* applies to 2D barcode formats. For now it contains the raw bytes obtained,
* as well as a String interpretation of those bytes, if applicable.</p>
*
* @author Sean Owen
*/
final class DecoderResult
{
/**
* @var mixed|null
*/
private $errorsCorrected;
/**
* @var mixed|null
*/
private $erasures;
/**
* @var mixed|null
*/
private $other;
public function __construct(private $rawBytes, private $text, private $byteSegments, private $ecLevel, private $structuredAppendSequenceNumber = -1, private $structuredAppendParity = -1)
{
}
public function getRawBytes()
{
return $this->rawBytes;
}
public function getText()
{
return $this->text;
}
public function getByteSegments()
{
return $this->byteSegments;
}
public function getECLevel()
{
return $this->ecLevel;
}
public function getErrorsCorrected()
{
return $this->errorsCorrected;
}
public function setErrorsCorrected($errorsCorrected): void
{
$this->errorsCorrected = $errorsCorrected;
}
public function getErasures()
{
return $this->erasures;
}
public function setErasures($erasures): void
{
$this->erasures = $erasures;
}
public function getOther()
{
return $this->other;
}
public function setOther($other): void
{
$this->other = $other;
}
public function hasStructuredAppend()
{
return $this->structuredAppendParity >= 0 && $this->structuredAppendSequenceNumber >= 0;
}
public function getStructuredAppendParity()
{
return $this->structuredAppendParity;
}
public function getStructuredAppendSequenceNumber()
{
return $this->structuredAppendSequenceNumber;
}
}

View File

@@ -0,0 +1,115 @@
<?php
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Zxing\Common;
use Zxing\NotFoundException;
/**
* @author Sean Owen
*/
final class DefaultGridSampler extends GridSampler
{
//@Override
public function sampleGrid(
$image,
$dimensionX,
$dimensionY,
$p1ToX,
$p1ToY,
$p2ToX,
$p2ToY,
$p3ToX,
$p3ToY,
$p4ToX,
$p4ToY,
$p1FromX,
$p1FromY,
$p2FromX,
$p2FromY,
$p3FromX,
$p3FromY,
$p4FromX,
$p4FromY
) {
$transform = PerspectiveTransform::quadrilateralToQuadrilateral(
$p1ToX,
$p1ToY,
$p2ToX,
$p2ToY,
$p3ToX,
$p3ToY,
$p4ToX,
$p4ToY,
$p1FromX,
$p1FromY,
$p2FromX,
$p2FromY,
$p3FromX,
$p3FromY,
$p4FromX,
$p4FromY
);
return $this->sampleGrid_($image, $dimensionX, $dimensionY, $transform);
}
//@Override
public function sampleGrid_(
$image,
$dimensionX,
$dimensionY,
$transform
) {
if ($dimensionX <= 0 || $dimensionY <= 0) {
throw NotFoundException::getNotFoundInstance();
}
$bits = new BitMatrix($dimensionX, $dimensionY);
$points = fill_array(0, 2 * $dimensionX, 0.0);
for ($y = 0; $y < $dimensionY; $y++) {
$max = is_countable($points) ? count($points) : 0;
$iValue = (float)$y + 0.5;
for ($x = 0; $x < $max; $x += 2) {
$points[$x] = (float)($x / 2) + 0.5;
$points[$x + 1] = $iValue;
}
$transform->transformPoints($points);
// Quick check to see if points transformed to something inside the image;
// sufficient to check the endpoints
self::checkAndNudgePoints($image, $points);
try {
for ($x = 0; $x < $max; $x += 2) {
if ($image->get((int)$points[$x], (int)$points[$x + 1])) {
// Black(-ish) pixel
$bits->set($x / 2, $y);
}
}
} catch (\Exception) {//ArrayIndexOutOfBoundsException
// This feels wrong, but, sometimes if the finder patterns are misidentified, the resulting
// transform gets "twisted" such that it maps a straight line of points to a set of points
// whose endpoints are in bounds, but others are not. There is probably some mathematical
// way to detect this about the transformation that I don't know yet.
// This results in an ugly runtime exception despite our clever checks above -- can't have
// that. We could check each point's coordinates but that feels duplicative. We settle for
// catching and wrapping ArrayIndexOutOfBoundsException.
throw NotFoundException::getNotFoundInstance();
}
}
return $bits;
}
}

View File

@@ -0,0 +1,48 @@
<?php
/*
* Copyright 2012 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Zxing\Common\Detector;
final class MathUtils
{
private function __construct()
{
}
/**
* Ends up being a bit faster than {@link Math#round(float)}. This merely rounds its
* argument to the nearest int, where x.5 rounds up to x+1. Semantics of this shortcut
* differ slightly from {@link Math#round(float)} in that half rounds down for negative
* values. -2.5 rounds to -3, not -2. For purposes here it makes no difference.
*
* @param float $d real value to round
*
* @return int {@code int}
*/
public static function round($d)
{
return (int)($d + ($d < 0.0 ? -0.5 : 0.5));
}
public static function distance($aX, $aY, $bX, $bY)
{
$xDiff = $aX - $bX;
$yDiff = $aY - $bY;
return (float)sqrt($xDiff * $xDiff + $yDiff * $yDiff);
}
}

View File

@@ -0,0 +1,276 @@
<?php
/**
* Created by PhpStorm.
* User: Ashot
* Date: 3/24/15
* Time: 21:23
*/
namespace Zxing\Common\Detector;
use Zxing\BinaryBitmap;
use Zxing\NotFoundException;
use Zxing\ResultPoint;
/*
*
*
import com.google.zxing.NotFoundException;
import com.google.zxing.ResultPoint;
import com.google.zxing.common.BitMatrix;
*/
//require_once('./lib/NotFoundException.php');
//require_once('./lib/ResultPoint.php');
//require_once('./lib/common/BitMatrix.php');
/**
* <p>A somewhat generic detector that looks for a barcode-like rectangular region within an image.
* It looks within a mostly white region of an image for a region of black and white, but mostly
* black. It returns the four corners of the region, as best it can determine.</p>
*
* @author Sean Owen
* @port Ashot Khanamiryan
*/
class MonochromeRectangleDetector
{
private static int $MAX_MODULES = 32;
public function __construct(private readonly BinaryBitmap $image)
{
}
/**
* <p>Detects a rectangular region of black and white -- mostly black -- with a region of mostly
* white, in an image.</p>
*
* @return {@link ResultPoint}[] describing the corners of the rectangular region. The first and
* last points are opposed on the diagonal, as are the second and third. The first point will be
* the topmost point and the last, the bottommost. The second point will be leftmost and the
* third, the rightmost
* @throws NotFoundException if no Data Matrix Code can be found
*/
public function detect(): \Zxing\ResultPoint
{
$height = $this->image->getHeight();
$width = $this->image->getWidth();
$halfHeight = $height / 2;
$halfWidth = $width / 2;
$deltaY = max(1, $height / (self::$MAX_MODULES * 8));
$deltaX = max(1, $width / (self::$MAX_MODULES * 8));
$top = 0;
$bottom = $height;
$left = 0;
$right = $width;
$pointA = $this->findCornerFromCenter(
$halfWidth,
0,
$left,
$right,
$halfHeight,
-$deltaY,
$top,
$bottom,
$halfWidth / 2
);
$top = (int)$pointA->getY() - 1;
$pointB = $this->findCornerFromCenter(
$halfWidth,
-$deltaX,
$left,
$right,
$halfHeight,
0,
$top,
$bottom,
$halfHeight / 2
);
$left = (int)$pointB->getX() - 1;
$pointC = $this->findCornerFromCenter(
$halfWidth,
$deltaX,
$left,
$right,
$halfHeight,
0,
$top,
$bottom,
$halfHeight / 2
);
$right = (int)$pointC->getX() + 1;
$pointD = $this->findCornerFromCenter(
$halfWidth,
0,
$left,
$right,
$halfHeight,
$deltaY,
$top,
$bottom,
$halfWidth / 2
);
$bottom = (int)$pointD->getY() + 1;
// Go try to find po$A again with better information -- might have been off at first.
$pointA = $this->findCornerFromCenter(
$halfWidth,
0,
$left,
$right,
$halfHeight,
-$deltaY,
$top,
$bottom,
$halfWidth / 4
);
return new ResultPoint($pointA, $pointB, $pointC, $pointD);
}
/**
* Attempts to locate a corner of the barcode by scanning up, down, left or right from a center
* point which should be within the barcode.
*
* @param float $centerX center's x component (horizontal)
* @param float $deltaX same as deltaY but change in x per step instead
* @param float $left minimum value of x
* @param float $right maximum value of x
* @param float $centerY center's y component (vertical)
* @param float $deltaY change in y per step. If scanning up this is negative; down, positive;
* left or right, 0
* @param float $top minimum value of y to search through (meaningless when di == 0)
* @param float $bottom maximum value of y
* @param float $maxWhiteRun maximum run of white pixels that can still be considered to be within
* the barcode
*
* @return ResultPoint {@link com.google.zxing.ResultPoint} encapsulating the corner that was found
* @throws NotFoundException if such a point cannot be found
*/
private function findCornerFromCenter(
$centerX,
$deltaX,
$left,
$right,
$centerY,
$deltaY,
$top,
$bottom,
$maxWhiteRun
): \Zxing\ResultPoint
{
$lastRange = null;
for ($y = $centerY, $x = $centerX;
$y < $bottom && $y >= $top && $x < $right && $x >= $left;
$y += $deltaY, $x += $deltaX) {
$range = 0;
if ($deltaX == 0) {
// horizontal slices, up and down
$range = $this->blackWhiteRange($y, $maxWhiteRun, $left, $right, true);
} else {
// vertical slices, left and right
$range = $this->blackWhiteRange($x, $maxWhiteRun, $top, $bottom, false);
}
if ($range == null) {
if ($lastRange == null) {
throw NotFoundException::getNotFoundInstance();
}
// lastRange was found
if ($deltaX == 0) {
$lastY = $y - $deltaY;
if ($lastRange[0] < $centerX) {
if ($lastRange[1] > $centerX) {
// straddle, choose one or the other based on direction
return new ResultPoint($deltaY > 0 ? $lastRange[0] : $lastRange[1], $lastY);
}
return new ResultPoint($lastRange[0], $lastY);
} else {
return new ResultPoint($lastRange[1], $lastY);
}
} else {
$lastX = $x - $deltaX;
if ($lastRange[0] < $centerY) {
if ($lastRange[1] > $centerY) {
return new ResultPoint($lastX, $deltaX < 0 ? $lastRange[0] : $lastRange[1]);
}
return new ResultPoint($lastX, $lastRange[0]);
} else {
return new ResultPoint($lastX, $lastRange[1]);
}
}
}
$lastRange = $range;
}
throw NotFoundException::getNotFoundInstance();
}
/**
* Computes the start and end of a region of pixels, either horizontally or vertically, that could
* be part of a Data Matrix barcode.
*
* @param if $fixedDimension scanning horizontally, this is the row (the fixed vertical location)
* where we are scanning. If scanning vertically it's the column, the fixed horizontal location
* @param largest $maxWhiteRun run of white pixels that can still be considered part of the
* barcode region
* @param minimum $minDim pixel location, horizontally or vertically, to consider
* @param maximum $maxDim pixel location, horizontally or vertically, to consider
* @param if $horizontal true, we're scanning left-right, instead of up-down
*
* @return int[] with start and end of found range, or null if no such range is found
* (e.g. only white was found)
*/
private function blackWhiteRange($fixedDimension, $maxWhiteRun, $minDim, $maxDim, $horizontal)
{
$center = ($minDim + $maxDim) / 2;
// Scan left/up first
$start = $center;
while ($start >= $minDim) {
if ($horizontal ? $this->image->get($start, $fixedDimension) : $this->image->get($fixedDimension, $start)) {
$start--;
} else {
$whiteRunStart = $start;
do {
$start--;
} while ($start >= $minDim && !($horizontal ? $this->image->get($start, $fixedDimension) :
$this->image->get($fixedDimension, $start)));
$whiteRunSize = $whiteRunStart - $start;
if ($start < $minDim || $whiteRunSize > $maxWhiteRun) {
$start = $whiteRunStart;
break;
}
}
}
$start++;
// Then try right/down
$end = $center;
while ($end < $maxDim) {
if ($horizontal ? $this->image->get($end, $fixedDimension) : $this->image->get($fixedDimension, $end)) {
$end++;
} else {
$whiteRunStart = $end;
do {
$end++;
} while ($end < $maxDim && !($horizontal ? $this->image->get($end, $fixedDimension) :
$this->image->get($fixedDimension, $end)));
$whiteRunSize = $end - $whiteRunStart;
if ($end >= $maxDim || $whiteRunSize > $maxWhiteRun) {
$end = $whiteRunStart;
break;
}
}
}
$end--;
return $end > $start ? [$start, $end] : null;
}
}

View File

@@ -0,0 +1,42 @@
<?php
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Zxing\Common;
/**
* <p>Encapsulates the result of detecting a barcode in an image. This includes the raw
* matrix of black/white pixels corresponding to the barcode, and possibly points of interest
* in the image, like the location of finder patterns or corners of the barcode in the image.</p>
*
* @author Sean Owen
*/
class DetectorResult
{
public function __construct(private $bits, private $points)
{
}
final public function getBits()
{
return $this->bits;
}
final public function getPoints()
{
return $this->points;
}
}

View File

@@ -0,0 +1,209 @@
<?php
/*
* Copyright 2009 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Zxing\Common;
use Zxing\Binarizer;
use Zxing\NotFoundException;
/**
* This Binarizer implementation uses the old ZXing global histogram approach. It is suitable
* for low-end mobile devices which don't have enough CPU or memory to use a local thresholding
* algorithm. However, because it picks a global black point, it cannot handle difficult shadows
* and gradients.
*
* Faster mobile devices and all desktop applications should probably use HybridBinarizer instead.
*
* @author dswitkin@google.com (Daniel Switkin)
* @author Sean Owen
*/
class GlobalHistogramBinarizer extends Binarizer
{
private static int $LUMINANCE_BITS = 5;
private static int $LUMINANCE_SHIFT = 3;
private static int $LUMINANCE_BUCKETS = 32;
private static array $EMPTY = [];
private array $luminances = [];
private array $buckets = [];
/**
* @var mixed|\Zxing\LuminanceSource
*/
private $source = [];
public function __construct($source)
{
self::$LUMINANCE_SHIFT = 8 - self::$LUMINANCE_BITS;
self::$LUMINANCE_BUCKETS = 1 << self::$LUMINANCE_BITS;
parent::__construct($source);
$this->luminances = self::$EMPTY;
$this->buckets = fill_array(0, self::$LUMINANCE_BUCKETS, 0);
$this->source = $source;
}
// Applies simple sharpening to the row data to improve performance of the 1D Readers.
public function getBlackRow($y, $row = null)
{
$this->source = $this->getLuminanceSource();
$width = $this->source->getWidth();
if ($row == null || $row->getSize() < $width) {
$row = new BitArray($width);
} else {
$row->clear();
}
$this->initArrays($width);
$localLuminances = $this->source->getRow($y, $this->luminances);
$localBuckets = $this->buckets;
for ($x = 0; $x < $width; $x++) {
$pixel = $localLuminances[$x] & 0xff;
$localBuckets[$pixel >> self::$LUMINANCE_SHIFT]++;
}
$blackPoint = self::estimateBlackPoint($localBuckets);
$left = $localLuminances[0] & 0xff;
$center = $localLuminances[1] & 0xff;
for ($x = 1; $x < $width - 1; $x++) {
$right = $localLuminances[$x + 1] & 0xff;
// A simple -1 4 -1 box filter with a weight of 2.
$luminance = (($center * 4) - $left - $right) / 2;
if ($luminance < $blackPoint) {
$row->set($x);
}
$left = $center;
$center = $right;
}
return $row;
}
// Does not sharpen the data, as this call is intended to only be used by 2D Readers.
private function initArrays($luminanceSize): void
{
if (count($this->luminances) < $luminanceSize) {
$this->luminances = [];
}
for ($x = 0; $x < self::$LUMINANCE_BUCKETS; $x++) {
$this->buckets[$x] = 0;
}
}
private static function estimateBlackPoint($buckets)
{
// Find the tallest peak in the histogram.
$numBuckets = is_countable($buckets) ? count($buckets) : 0;
$maxBucketCount = 0;
$firstPeak = 0;
$firstPeakSize = 0;
for ($x = 0; $x < $numBuckets; $x++) {
if ($buckets[$x] > $firstPeakSize) {
$firstPeak = $x;
$firstPeakSize = $buckets[$x];
}
if ($buckets[$x] > $maxBucketCount) {
$maxBucketCount = $buckets[$x];
}
}
// Find the second-tallest peak which is somewhat far from the tallest peak.
$secondPeak = 0;
$secondPeakScore = 0;
for ($x = 0; $x < $numBuckets; $x++) {
$distanceToBiggest = $x - $firstPeak;
// Encourage more distant second peaks by multiplying by square of distance.
$score = $buckets[$x] * $distanceToBiggest * $distanceToBiggest;
if ($score > $secondPeakScore) {
$secondPeak = $x;
$secondPeakScore = $score;
}
}
// Make sure firstPeak corresponds to the black peak.
if ($firstPeak > $secondPeak) {
$temp = $firstPeak;
$firstPeak = $secondPeak;
$secondPeak = $temp;
}
// If there is too little contrast in the image to pick a meaningful black point, throw rather
// than waste time trying to decode the image, and risk false positives.
if ($secondPeak - $firstPeak <= $numBuckets / 16) {
throw NotFoundException::getNotFoundInstance();
}
// Find a valley between them that is low and closer to the white peak.
$bestValley = $secondPeak - 1;
$bestValleyScore = -1;
for ($x = $secondPeak - 1; $x > $firstPeak; $x--) {
$fromFirst = $x - $firstPeak;
$score = $fromFirst * $fromFirst * ($secondPeak - $x) * ($maxBucketCount - $buckets[$x]);
if ($score > $bestValleyScore) {
$bestValley = $x;
$bestValleyScore = $score;
}
}
return $bestValley << self::$LUMINANCE_SHIFT;
}
public function getBlackMatrix()
{
$source = $this->getLuminanceSource();
$width = $source->getWidth();
$height = $source->getHeight();
$matrix = new BitMatrix($width, $height);
// Quickly calculates the histogram by sampling four rows from the image. This proved to be
// more robust on the blackbox tests than sampling a diagonal as we used to do.
$this->initArrays($width);
$localBuckets = $this->buckets;
for ($y = 1; $y < 5; $y++) {
$row = (int)($height * $y / 5);
$localLuminances = $source->getRow($row, $this->luminances);
$right = (int)(($width * 4) / 5);
for ($x = (int)($width / 5); $x < $right; $x++) {
$pixel = ($localLuminances[(int)($x)] & 0xff);
$localBuckets[($pixel >> self::$LUMINANCE_SHIFT)]++;
}
}
$blackPoint = self::estimateBlackPoint($localBuckets);
// We delay reading the entire image luminance until the black point estimation succeeds.
// Although we end up reading four rows twice, it is consistent with our motto of
// "fail quickly" which is necessary for continuous scanning.
$localLuminances = $source->getMatrix();
for ($y = 0; $y < $height; $y++) {
$offset = $y * $width;
for ($x = 0; $x < $width; $x++) {
$pixel = (int)($localLuminances[$offset + $x] & 0xff);
if ($pixel < $blackPoint) {
$matrix->set($x, $y);
}
}
}
return $matrix;
}
public function createBinarizer($source): \Zxing\Common\GlobalHistogramBinarizer
{
return new GlobalHistogramBinarizer($source);
}
}

View File

@@ -0,0 +1,198 @@
<?php
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Zxing\Common;
use Zxing\NotFoundException;
/**
* Implementations of this class can, given locations of finder patterns for a QR code in an
* image, sample the right points in the image to reconstruct the QR code, accounting for
* perspective distortion. It is abstracted since it is relatively expensive and should be allowed
* to take advantage of platform-specific optimized implementations, like Sun's Java Advanced
* Imaging library, but which may not be available in other environments such as J2ME, and vice
* versa.
*
* The implementation used can be controlled by calling {@link #setGridSampler(GridSampler)}
* with an instance of a class which implements this interface.
*
* @author Sean Owen
*/
abstract class GridSampler
{
/**
* @var mixed|\Zxing\Common\DefaultGridSampler|null
*/
private static $gridSampler;
/**
* Sets the implementation of GridSampler used by the library. One global
* instance is stored, which may sound problematic. But, the implementation provided
* ought to be appropriate for the entire platform, and all uses of this library
* in the whole lifetime of the JVM. For instance, an Android activity can swap in
* an implementation that takes advantage of native platform libraries.
*
* @param $newGridSampler The platform-specific object to install.
*/
public static function setGridSampler($newGridSampler): void
{
self::$gridSampler = $newGridSampler;
}
/**
* @return GridSampler the current implementation of GridSampler
*/
public static function getInstance()
{
if (!self::$gridSampler) {
self::$gridSampler = new DefaultGridSampler();
}
return self::$gridSampler;
}
/**
* <p>Checks a set of points that have been transformed to sample points on an image against
* the image's dimensions to see if the point are even within the image.</p>
*
* <p>This method will actually "nudge" the endpoints back onto the image if they are found to be
* barely (less than 1 pixel) off the image. This accounts for imperfect detection of finder
* patterns in an image where the QR Code runs all the way to the image border.</p>
*
* <p>For efficiency, the method will check points from either end of the line until one is found
* to be within the image. Because the set of points are assumed to be linear, this is valid.</p>
*
* @param image $image into which the points should map
* @param actual $points points in x1,y1,...,xn,yn form
*
* @throws NotFoundException if an endpoint is lies outside the image boundaries
*/
protected static function checkAndNudgePoints(
$image,
$points
) {
$width = $image->getWidth();
$height = $image->getHeight();
// Check and nudge points from start until we see some that are OK:
$nudged = true;
for ($offset = 0; $offset < (is_countable($points) ? count($points) : 0) && $nudged; $offset += 2) {
$x = (int)$points[$offset];
$y = (int)$points[$offset + 1];
if ($x < -1 || $x > $width || $y < -1 || $y > $height) {
throw NotFoundException::getNotFoundInstance();
}
$nudged = false;
if ($x == -1) {
$points[$offset] = 0.0;
$nudged = true;
} elseif ($x == $width) {
$points[$offset] = $width - 1;
$nudged = true;
}
if ($y == -1) {
$points[$offset + 1] = 0.0;
$nudged = true;
} elseif ($y == $height) {
$points[$offset + 1] = $height - 1;
$nudged = true;
}
}
// Check and nudge points from end:
$nudged = true;
for ($offset = (is_countable($points) ? count($points) : 0) - 2; $offset >= 0 && $nudged; $offset -= 2) {
$x = (int)$points[$offset];
$y = (int)$points[$offset + 1];
if ($x < -1 || $x > $width || $y < -1 || $y > $height) {
throw NotFoundException::getNotFoundInstance();
}
$nudged = false;
if ($x == -1) {
$points[$offset] = 0.0;
$nudged = true;
} elseif ($x == $width) {
$points[$offset] = $width - 1;
$nudged = true;
}
if ($y == -1) {
$points[$offset + 1] = 0.0;
$nudged = true;
} elseif ($y == $height) {
$points[$offset + 1] = $height - 1;
$nudged = true;
}
}
}
/**
* Samples an image for a rectangular matrix of bits of the given dimension. The sampling
* transformation is determined by the coordinates of 4 points, in the original and transformed
* image space.
*
* @param image $image to sample
* @param width $dimensionX of {@link BitMatrix} to sample from image
* @param height $dimensionY of {@link BitMatrix} to sample from image
* @param point $p1ToX 1 preimage X
* @param point $p1ToY 1 preimage Y
* @param point $p2ToX 2 preimage X
* @param point $p2ToY 2 preimage Y
* @param point $p3ToX 3 preimage X
* @param point $p3ToY 3 preimage Y
* @param point $p4ToX 4 preimage X
* @param point $p4ToY 4 preimage Y
* @param point $p1FromX 1 image X
* @param point $p1FromY 1 image Y
* @param point $p2FromX 2 image X
* @param point $p2FromY 2 image Y
* @param point $p3FromX 3 image X
* @param point $p3FromY 3 image Y
* @param point $p4FromX 4 image X
* @param point $p4FromY 4 image Y
*
* @return {@link BitMatrix} representing a grid of points sampled from the image within a region
* defined by the "from" parameters
* @throws NotFoundException if image can't be sampled, for example, if the transformation defined
* by the given points is invalid or results in sampling outside the image boundaries
*/
abstract public function sampleGrid(
$image,
$dimensionX,
$dimensionY,
$p1ToX,
$p1ToY,
$p2ToX,
$p2ToY,
$p3ToX,
$p3ToY,
$p4ToX,
$p4ToY,
$p1FromX,
$p1FromY,
$p2FromX,
$p2FromY,
$p3FromX,
$p3FromY,
$p4FromX,
$p4FromY
);
abstract public function sampleGrid_(
$image,
$dimensionX,
$dimensionY,
$transform
);
}

View File

@@ -0,0 +1,260 @@
<?php
/*
* Copyright 2009 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Zxing\Common;
use Zxing\Binarizer;
/**
* This class implements a local thresholding algorithm, which while slower than the
* GlobalHistogramBinarizer, is fairly efficient for what it does. It is designed for
* high frequency images of barcodes with black data on white backgrounds. For this application,
* it does a much better job than a global blackpoint with severe shadows and gradients.
* However it tends to produce artifacts on lower frequency images and is therefore not
* a good general purpose binarizer for uses outside ZXing.
*
* This class extends GlobalHistogramBinarizer, using the older histogram approach for 1D readers,
* and the newer local approach for 2D readers. 1D decoding using a per-row histogram is already
* inherently local, and only fails for horizontal gradients. We can revisit that problem later,
* but for now it was not a win to use local blocks for 1D.
*
* This Binarizer is the default for the unit tests and the recommended class for library users.
*
* @author dswitkin@google.com (Daniel Switkin)
*/
final class HybridBinarizer extends GlobalHistogramBinarizer
{
// This class uses 5x5 blocks to compute local luminance, where each block is 8x8 pixels.
// So this is the smallest dimension in each axis we can accept.
private static int $BLOCK_SIZE_POWER = 3;
private static int $BLOCK_SIZE = 8; // ...0100...00
private static int $BLOCK_SIZE_MASK = 7; // ...0011...11
private static int $MINIMUM_DIMENSION = 40;
private static int $MIN_DYNAMIC_RANGE = 24;
private ?\Zxing\Common\BitMatrix $matrix = null;
public function __construct($source)
{
parent::__construct($source);
self::$BLOCK_SIZE_POWER = 3;
self::$BLOCK_SIZE = 1 << self::$BLOCK_SIZE_POWER; // ...0100...00
self::$BLOCK_SIZE_MASK = self::$BLOCK_SIZE - 1; // ...0011...11
self::$MINIMUM_DIMENSION = self::$BLOCK_SIZE * 5;
self::$MIN_DYNAMIC_RANGE = 24;
}
/**
* Calculates the final BitMatrix once for all requests. This could be called once from the
* constructor instead, but there are some advantages to doing it lazily, such as making
* profiling easier, and not doing heavy lifting when callers don't expect it.
*/
public function getBlackMatrix()
{
if ($this->matrix !== null) {
return $this->matrix;
}
$source = $this->getLuminanceSource();
$width = $source->getWidth();
$height = $source->getHeight();
if ($width >= self::$MINIMUM_DIMENSION && $height >= self::$MINIMUM_DIMENSION) {
$luminances = $source->getMatrix();
$subWidth = $width >> self::$BLOCK_SIZE_POWER;
if (($width & self::$BLOCK_SIZE_MASK) != 0) {
$subWidth++;
}
$subHeight = $height >> self::$BLOCK_SIZE_POWER;
if (($height & self::$BLOCK_SIZE_MASK) != 0) {
$subHeight++;
}
$blackPoints = self::calculateBlackPoints($luminances, $subWidth, $subHeight, $width, $height);
$newMatrix = new BitMatrix($width, $height);
self::calculateThresholdForBlock($luminances, $subWidth, $subHeight, $width, $height, $blackPoints, $newMatrix);
$this->matrix = $newMatrix;
} else {
// If the image is too small, fall back to the global histogram approach.
$this->matrix = parent::getBlackMatrix();
}
return $this->matrix;
}
/**
* Calculates a single black point for each block of pixels and saves it away.
* See the following thread for a discussion of this algorithm:
* http://groups.google.com/group/zxing/browse_thread/thread/d06efa2c35a7ddc0
*/
private static function calculateBlackPoints(
$luminances,
$subWidth,
$subHeight,
$width,
$height
) {
$blackPoints = fill_array(0, $subHeight, 0);
foreach ($blackPoints as $key => $point) {
$blackPoints[$key] = fill_array(0, $subWidth, 0);
}
for ($y = 0; $y < $subHeight; $y++) {
$yoffset = ($y << self::$BLOCK_SIZE_POWER);
$maxYOffset = $height - self::$BLOCK_SIZE;
if ($yoffset > $maxYOffset) {
$yoffset = $maxYOffset;
}
for ($x = 0; $x < $subWidth; $x++) {
$xoffset = ($x << self::$BLOCK_SIZE_POWER);
$maxXOffset = $width - self::$BLOCK_SIZE;
if ($xoffset > $maxXOffset) {
$xoffset = $maxXOffset;
}
$sum = 0;
$min = 0xFF;
$max = 0;
for ($yy = 0, $offset = $yoffset * $width + $xoffset; $yy < self::$BLOCK_SIZE; $yy++, $offset += $width) {
for ($xx = 0; $xx < self::$BLOCK_SIZE; $xx++) {
$pixel = ((int)($luminances[(int)($offset + $xx)]) & 0xFF);
$sum += $pixel;
// still looking for good contrast
if ($pixel < $min) {
$min = $pixel;
}
if ($pixel > $max) {
$max = $pixel;
}
}
// short-circuit min/max tests once dynamic range is met
if ($max - $min > self::$MIN_DYNAMIC_RANGE) {
// finish the rest of the rows quickly
for ($yy++, $offset += $width; $yy < self::$BLOCK_SIZE; $yy++, $offset += $width) {
for ($xx = 0; $xx < self::$BLOCK_SIZE; $xx++) {
$sum += ($luminances[$offset + $xx] & 0xFF);
}
}
}
}
// The default estimate is the average of the values in the block.
$average = ($sum >> (self::$BLOCK_SIZE_POWER * 2));
if ($max - $min <= self::$MIN_DYNAMIC_RANGE) {
// If variation within the block is low, assume this is a block with only light or only
// dark pixels. In that case we do not want to use the average, as it would divide this
// low contrast area into black and white pixels, essentially creating data out of noise.
//
// The default assumption is that the block is light/background. Since no estimate for
// the level of dark pixels exists locally, use half the min for the block.
$average = (int)($min / 2);
if ($y > 0 && $x > 0) {
// Correct the "white background" assumption for blocks that have neighbors by comparing
// the pixels in this block to the previously calculated black points. This is based on
// the fact that dark barcode symbology is always surrounded by some amount of light
// background for which reasonable black point estimates were made. The bp estimated at
// the boundaries is used for the interior.
// The (min < bp) is arbitrary but works better than other heuristics that were tried.
$averageNeighborBlackPoint =
(int)(($blackPoints[$y - 1][$x] + (2 * $blackPoints[$y][$x - 1]) + $blackPoints[$y - 1][$x - 1]) / 4);
if ($min < $averageNeighborBlackPoint) {
$average = $averageNeighborBlackPoint;
}
}
}
$blackPoints[$y][$x] = (int)($average);
}
}
return $blackPoints;
}
/**
* For each block in the image, calculate the average black point using a 5x5 grid
* of the blocks around it. Also handles the corner cases (fractional blocks are computed based
* on the last pixels in the row/column which are also used in the previous block).
*/
private static function calculateThresholdForBlock(
$luminances,
$subWidth,
$subHeight,
$width,
$height,
$blackPoints,
$matrix
): void {
for ($y = 0; $y < $subHeight; $y++) {
$yoffset = ($y << self::$BLOCK_SIZE_POWER);
$maxYOffset = $height - self::$BLOCK_SIZE;
if ($yoffset > $maxYOffset) {
$yoffset = $maxYOffset;
}
for ($x = 0; $x < $subWidth; $x++) {
$xoffset = ($x << self::$BLOCK_SIZE_POWER);
$maxXOffset = $width - self::$BLOCK_SIZE;
if ($xoffset > $maxXOffset) {
$xoffset = $maxXOffset;
}
$left = self::cap($x, 2, $subWidth - 3);
$top = self::cap($y, 2, $subHeight - 3);
$sum = 0;
for ($z = -2; $z <= 2; $z++) {
$blackRow = $blackPoints[$top + $z];
$sum += $blackRow[$left - 2] + $blackRow[$left - 1] + $blackRow[$left] + $blackRow[$left + 1] + $blackRow[$left + 2];
}
$average = (int)($sum / 25);
self::thresholdBlock($luminances, $xoffset, $yoffset, $average, $width, $matrix);
}
}
}
private static function cap($value, $min, $max)
{
if ($value < $min) {
return $min;
} elseif ($value > $max) {
return $max;
} else {
return $value;
}
}
/**
* Applies a single threshold to a block of pixels.
*/
private static function thresholdBlock(
$luminances,
$xoffset,
$yoffset,
$threshold,
$stride,
$matrix
): void {
for ($y = 0, $offset = $yoffset * $stride + $xoffset; $y < self::$BLOCK_SIZE; $y++, $offset += $stride) {
for ($x = 0; $x < self::$BLOCK_SIZE; $x++) {
// Comparison needs to be <= so that black == 0 pixels are black even if the threshold is 0.
if (($luminances[$offset + $x] & 0xFF) <= $threshold) {
$matrix->set($xoffset + $x, $yoffset + $y);
}
}
}
}
public function createBinarizer($source): \Zxing\Common\HybridBinarizer
{
return new HybridBinarizer($source);
}
}

View File

@@ -0,0 +1,187 @@
<?php
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Zxing\Common;
/**
* <p>This class implements a perspective transform in two dimensions. Given four source and four
* destination points, it will compute the transformation implied between them. The code is based
* directly upon section 3.4.2 of George Wolberg's "Digital Image Warping"; see pages 54-56.</p>
*
* @author Sean Owen
*/
final class PerspectiveTransform
{
private function __construct(private $a11, private $a21, private $a31, private $a12, private $a22, private $a32, private $a13, private $a23, private $a33)
{
}
public static function quadrilateralToQuadrilateral(
$x0,
$y0,
$x1,
$y1,
$x2,
$y2,
$x3,
$y3,
$x0p,
$y0p,
$x1p,
$y1p,
$x2p,
$y2p,
$x3p,
$y3p
) {
$qToS = self::quadrilateralToSquare($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3);
$sToQ = self::squareToQuadrilateral($x0p, $y0p, $x1p, $y1p, $x2p, $y2p, $x3p, $y3p);
return $sToQ->times($qToS);
}
public static function quadrilateralToSquare(
$x0,
$y0,
$x1,
$y1,
$x2,
$y2,
$x3,
$y3
) {
// Here, the adjoint serves as the inverse:
return self::squareToQuadrilateral($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3)->buildAdjoint();
}
public function buildAdjoint(): \Zxing\Common\PerspectiveTransform
{
// Adjoint is the transpose of the cofactor matrix:
return new PerspectiveTransform(
$this->a22 * $this->a33 - $this->a23 * $this->a32,
$this->a23 * $this->a31 - $this->a21 * $this->a33,
$this->a21 * $this->a32 - $this->a22 * $this->a31,
$this->a13 * $this->a32 - $this->a12 * $this->a33,
$this->a11 * $this->a33 - $this->a13 * $this->a31,
$this->a12 * $this->a31 - $this->a11 * $this->a32,
$this->a12 * $this->a23 - $this->a13 * $this->a22,
$this->a13 * $this->a21 - $this->a11 * $this->a23,
$this->a11 * $this->a22 - $this->a12 * $this->a21
);
}
public static function squareToQuadrilateral(
$x0,
$y0,
$x1,
$y1,
$x2,
$y2,
$x3,
$y3
): \Zxing\Common\PerspectiveTransform {
$dx3 = $x0 - $x1 + $x2 - $x3;
$dy3 = $y0 - $y1 + $y2 - $y3;
if ($dx3 == 0.0 && $dy3 == 0.0) {
// Affine
return new PerspectiveTransform(
$x1 - $x0,
$x2 - $x1,
$x0,
$y1 - $y0,
$y2 - $y1,
$y0,
0.0,
0.0,
1.0
);
} else {
$dx1 = $x1 - $x2;
$dx2 = $x3 - $x2;
$dy1 = $y1 - $y2;
$dy2 = $y3 - $y2;
$denominator = $dx1 * $dy2 - $dx2 * $dy1;
$a13 = ($dx3 * $dy2 - $dx2 * $dy3) / $denominator;
$a23 = ($dx1 * $dy3 - $dx3 * $dy1) / $denominator;
return new PerspectiveTransform(
$x1 - $x0 + $a13 * $x1,
$x3 - $x0 + $a23 * $x3,
$x0,
$y1 - $y0 + $a13 * $y1,
$y3 - $y0 + $a23 * $y3,
$y0,
$a13,
$a23,
1.0
);
}
}
public function times($other): \Zxing\Common\PerspectiveTransform
{
return new PerspectiveTransform(
$this->a11 * $other->a11 + $this->a21 * $other->a12 + $this->a31 * $other->a13,
$this->a11 * $other->a21 + $this->a21 * $other->a22 + $this->a31 * $other->a23,
$this->a11 * $other->a31 + $this->a21 * $other->a32 + $this->a31 * $other->a33,
$this->a12 * $other->a11 + $this->a22 * $other->a12 + $this->a32 * $other->a13,
$this->a12 * $other->a21 + $this->a22 * $other->a22 + $this->a32 * $other->a23,
$this->a12 * $other->a31 + $this->a22 * $other->a32 + $this->a32 * $other->a33,
$this->a13 * $other->a11 + $this->a23 * $other->a12 + $this->a33 * $other->a13,
$this->a13 * $other->a21 + $this->a23 * $other->a22 + $this->a33 * $other->a23,
$this->a13 * $other->a31 + $this->a23 * $other->a32 + $this->a33 * $other->a33
);
}
public function transformPoints(&$points, &$yValues = 0): void
{
if ($yValues) {
$this->transformPoints_($points, $yValues);
return;
}
$max = is_countable($points) ? count($points) : 0;
$a11 = $this->a11;
$a12 = $this->a12;
$a13 = $this->a13;
$a21 = $this->a21;
$a22 = $this->a22;
$a23 = $this->a23;
$a31 = $this->a31;
$a32 = $this->a32;
$a33 = $this->a33;
for ($i = 0; $i < $max; $i += 2) {
$x = $points[$i];
$y = $points[$i + 1];
$denominator = $a13 * $x + $a23 * $y + $a33;
$points[$i] = ($a11 * $x + $a21 * $y + $a31) / $denominator;
$points[$i + 1] = ($a12 * $x + $a22 * $y + $a32) / $denominator;
}
}
public function transformPoints_(&$xValues, &$yValues): void
{
$n = is_countable($xValues) ? count($xValues) : 0;
for ($i = 0; $i < $n; $i++) {
$x = $xValues[$i];
$y = $yValues[$i];
$denominator = $this->a13 * $x + $this->a23 * $y + $this->a33;
$xValues[$i] = ($this->a11 * $x + $this->a21 * $y + $this->a31) / $denominator;
$yValues[$i] = ($this->a12 * $x + $this->a22 * $y + $this->a32) / $denominator;
}
}
}

View File

@@ -0,0 +1,187 @@
<?php
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Zxing\Common\Reedsolomon;
/**
* <p>This class contains utility methods for performing mathematical operations over
* the Galois Fields. Operations use a given primitive polynomial in calculations.</p>
*
* <p>Throughout this package, elements of the GF are represented as an {@code int}
* for convenience and speed (but at the cost of memory).
* </p>
*
* @author Sean Owen
* @author David Olivier
*/
final class GenericGF
{
public static $AZTEC_DATA_12;
public static $AZTEC_DATA_10;
public static $AZTEC_DATA_6;
public static $AZTEC_PARAM;
public static $QR_CODE_FIELD_256;
public static $DATA_MATRIX_FIELD_256;
public static $AZTEC_DATA_8;
public static $MAXICODE_FIELD_64;
private array $expTable = [];
private array $logTable = [];
private readonly \Zxing\Common\Reedsolomon\GenericGFPoly $zero;
private readonly \Zxing\Common\Reedsolomon\GenericGFPoly $one;
/**
* Create a representation of GF(size) using the given primitive polynomial.
*
* @param irreducible $primitive polynomial whose coefficients are represented by
* the bits of an int, where the least-significant bit represents the constant
* coefficient
* @param the $size size of the field
* @param the $generatorBase factor b in the generator polynomial can be 0- or 1-based
(g(x) = (x+a^b)(x+a^(b+1))...(x+a^(b+2t-1))).
In most cases it should be 1, but for QR code it is 0.
*/
public function __construct(private $primitive, private $size, private $generatorBase)
{
$x = 1;
for ($i = 0; $i < $size; $i++) {
$this->expTable[$i] = $x;
$x *= 2; // we're assuming the generator alpha is 2
if ($x >= $size) {
$x ^= $primitive;
$x &= $size - 1;
}
}
for ($i = 0; $i < $size - 1; $i++) {
$this->logTable[$this->expTable[$i]] = $i;
}
// logTable[0] == 0 but this should never be used
$this->zero = new GenericGFPoly($this, [0]);
$this->one = new GenericGFPoly($this, [1]);
}
public static function Init(): void
{
self::$AZTEC_DATA_12 = new GenericGF(0x1069, 4096, 1); // x^12 + x^6 + x^5 + x^3 + 1
self::$AZTEC_DATA_10 = new GenericGF(0x409, 1024, 1); // x^10 + x^3 + 1
self::$AZTEC_DATA_6 = new GenericGF(0x43, 64, 1); // x^6 + x + 1
self::$AZTEC_PARAM = new GenericGF(0x13, 16, 1); // x^4 + x + 1
self::$QR_CODE_FIELD_256 = new GenericGF(0x011D, 256, 0); // x^8 + x^4 + x^3 + x^2 + 1
self::$DATA_MATRIX_FIELD_256 = new GenericGF(0x012D, 256, 1); // x^8 + x^5 + x^3 + x^2 + 1
self::$AZTEC_DATA_8 = self::$DATA_MATRIX_FIELD_256;
self::$MAXICODE_FIELD_64 = self::$AZTEC_DATA_6;
}
/**
* Implements both addition and subtraction -- they are the same in GF(size).
*
* @return sum/difference of a and b
*/
public static function addOrSubtract($a, $b)
{
return $a ^ $b;
}
public function getZero()
{
return $this->zero;
}
public function getOne()
{
return $this->one;
}
/**
* @return GenericGFPoly the monomial representing coefficient * x^degree
*/
public function buildMonomial($degree, $coefficient)
{
if ($degree < 0) {
throw new \InvalidArgumentException();
}
if ($coefficient == 0) {
return $this->zero;
}
$coefficients = fill_array(0, $degree + 1, 0);//new int[degree + 1];
$coefficients[0] = $coefficient;
return new GenericGFPoly($this, $coefficients);
}
/**
* @return 2 to the power of a in GF(size)
*/
public function exp($a)
{
return $this->expTable[$a];
}
/**
* @return base 2 log of a in GF(size)
*/
public function log($a)
{
if ($a == 0) {
throw new \InvalidArgumentException();
}
return $this->logTable[$a];
}
/**
* @return multiplicative inverse of a
*/
public function inverse($a)
{
if ($a == 0) {
throw new \Exception();
}
return $this->expTable[$this->size - $this->logTable[$a] - 1];
}
/**
* @return int product of a and b in GF(size)
*/
public function multiply($a, $b)
{
if ($a == 0 || $b == 0) {
return 0;
}
return $this->expTable[($this->logTable[$a] + $this->logTable[$b]) % ($this->size - 1)];
}
public function getSize()
{
return $this->size;
}
public function getGeneratorBase()
{
return $this->generatorBase;
}
// @Override
public function toString()
{
return "GF(0x" . dechex((int)($this->primitive)) . ',' . $this->size . ')';
}
}
GenericGF::Init();

View File

@@ -0,0 +1,303 @@
<?php
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Zxing\Common\Reedsolomon;
/**
* <p>Represents a polynomial whose coefficients are elements of a GF.
* Instances of this class are immutable.</p>
*
* <p>Much credit is due to William Rucklidge since portions of this code are an indirect
* port of his C++ Reed-Solomon implementation.</p>
*
* @author Sean Owen
*/
final class GenericGFPoly
{
/**
* @var int[]|mixed|null
*/
private $coefficients;
/**
* @param the $field {@link GenericGF} instance representing the field to use
* to perform computations
* @param array $coefficients coefficients as ints representing elements of GF(size), arranged
* from most significant (highest-power term) coefficient to least significant
*
* @throws InvalidArgumentException 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(private $field, $coefficients)
{
if (count($coefficients) == 0) {
throw new \InvalidArgumentException();
}
$coefficientsLength = count($coefficients);
if ($coefficientsLength > 1 && $coefficients[0] == 0) {
// Leading term must be non-zero for anything except the constant polynomial "0"
$firstNonZero = 1;
while ($firstNonZero < $coefficientsLength && $coefficients[$firstNonZero] == 0) {
$firstNonZero++;
}
if ($firstNonZero == $coefficientsLength) {
$this->coefficients = [0];
} else {
$this->coefficients = fill_array(0, $coefficientsLength - $firstNonZero, 0);
$this->coefficients = arraycopy(
$coefficients,
$firstNonZero,
$this->coefficients,
0,
is_countable($this->coefficients) ? count($this->coefficients) : 0
);
}
} else {
$this->coefficients = $coefficients;
}
}
public function getCoefficients()
{
return $this->coefficients;
}
/**
* @return evaluation of this polynomial at a given point
*/
public function evaluateAt($a)
{
if ($a == 0) {
// Just return the x^0 coefficient
return $this->getCoefficient(0);
}
$size = is_countable($this->coefficients) ? count($this->coefficients) : 0;
if ($a == 1) {
// Just the sum of the coefficients
$result = 0;
foreach ($this->coefficients as $coefficient) {
$result = GenericGF::addOrSubtract($result, $coefficient);
}
return $result;
}
$result = $this->coefficients[0];
for ($i = 1; $i < $size; $i++) {
$result = GenericGF::addOrSubtract($this->field->multiply($a, $result), $this->coefficients[$i]);
}
return $result;
}
/**
* @return coefficient of x^degree term in this polynomial
*/
public function getCoefficient($degree)
{
return $this->coefficients[(is_countable($this->coefficients) ? count($this->coefficients) : 0) - 1 - $degree];
}
public function multiply($other)
{
$aCoefficients = [];
$bCoefficients = [];
$aLength = null;
$bLength = null;
$product = [];
if (is_int($other)) {
return $this->multiply_($other);
}
if ($this->field !== $other->field) {
throw new \InvalidArgumentException("GenericGFPolys do not have same GenericGF field");
}
if ($this->isZero() || $other->isZero()) {
return $this->field->getZero();
}
$aCoefficients = $this->coefficients;
$aLength = count($aCoefficients);
$bCoefficients = $other->coefficients;
$bLength = count($bCoefficients);
$product = fill_array(0, $aLength + $bLength - 1, 0);
for ($i = 0; $i < $aLength; $i++) {
$aCoeff = $aCoefficients[$i];
for ($j = 0; $j < $bLength; $j++) {
$product[$i + $j] = GenericGF::addOrSubtract(
$product[$i + $j],
$this->field->multiply($aCoeff, $bCoefficients[$j])
);
}
}
return new GenericGFPoly($this->field, $product);
}
public function multiply_($scalar)
{
if ($scalar == 0) {
return $this->field->getZero();
}
if ($scalar == 1) {
return $this;
}
$size = is_countable($this->coefficients) ? count($this->coefficients) : 0;
$product = fill_array(0, $size, 0);
for ($i = 0; $i < $size; $i++) {
$product[$i] = $this->field->multiply($this->coefficients[$i], $scalar);
}
return new GenericGFPoly($this->field, $product);
}
/**
* @return true iff this polynomial is the monomial "0"
*/
public function isZero()
{
return $this->coefficients[0] == 0;
}
public function multiplyByMonomial($degree, $coefficient)
{
if ($degree < 0) {
throw new \InvalidArgumentException();
}
if ($coefficient == 0) {
return $this->field->getZero();
}
$size = is_countable($this->coefficients) ? count($this->coefficients) : 0;
$product = fill_array(0, $size + $degree, 0);
for ($i = 0; $i < $size; $i++) {
$product[$i] = $this->field->multiply($this->coefficients[$i], $coefficient);
}
return new GenericGFPoly($this->field, $product);
}
public function divide($other)
{
if ($this->field !== $other->field) {
throw new \InvalidArgumentException("GenericGFPolys do not have same GenericGF field");
}
if ($other->isZero()) {
throw new \InvalidArgumentException("Divide by 0");
}
$quotient = $this->field->getZero();
$remainder = $this;
$denominatorLeadingTerm = $other->getCoefficient($other->getDegree());
$inverseDenominatorLeadingTerm = $this->field->inverse($denominatorLeadingTerm);
while ($remainder->getDegree() >= $other->getDegree() && !$remainder->isZero()) {
$degreeDifference = $remainder->getDegree() - $other->getDegree();
$scale = $this->field->multiply($remainder->getCoefficient($remainder->getDegree()), $inverseDenominatorLeadingTerm);
$term = $other->multiplyByMonomial($degreeDifference, $scale);
$iterationQuotient = $this->field->buildMonomial($degreeDifference, $scale);
$quotient = $quotient->addOrSubtract($iterationQuotient);
$remainder = $remainder->addOrSubtract($term);
}
return [$quotient, $remainder];
}
/**
* @return degree of this polynomial
*/
public function getDegree()
{
return (is_countable($this->coefficients) ? count($this->coefficients) : 0) - 1;
}
public function addOrSubtract($other)
{
$smallerCoefficients = [];
$largerCoefficients = [];
$sumDiff = [];
$lengthDiff = null;
$countLargerCoefficients = null;
if ($this->field !== $other->field) {
throw new \InvalidArgumentException("GenericGFPolys do not have same GenericGF field");
}
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 = fill_array(0, count($largerCoefficients), 0);
$lengthDiff = count($largerCoefficients) - count($smallerCoefficients);
// Copy high-order terms only found in higher-degree polynomial's coefficients
$sumDiff = arraycopy($largerCoefficients, 0, $sumDiff, 0, $lengthDiff);
$countLargerCoefficients = count($largerCoefficients);
for ($i = $lengthDiff; $i < $countLargerCoefficients; $i++) {
$sumDiff[$i] = GenericGF::addOrSubtract($smallerCoefficients[$i - $lengthDiff], $largerCoefficients[$i]);
}
return new GenericGFPoly($this->field, $sumDiff);
}
//@Override
public function toString()
{
$result = '';
for ($degree = $this->getDegree(); $degree >= 0; $degree--) {
$coefficient = $this->getCoefficient($degree);
if ($coefficient != 0) {
if ($coefficient < 0) {
$result .= " - ";
$coefficient = -$coefficient;
} else {
if (strlen((string) $result) > 0) {
$result .= " + ";
}
}
if ($degree == 0 || $coefficient != 1) {
$alphaPower = $this->field->log($coefficient);
if ($alphaPower == 0) {
$result .= '1';
} elseif ($alphaPower == 1) {
$result .= 'a';
} else {
$result .= "a^";
$result .= ($alphaPower);
}
}
if ($degree != 0) {
if ($degree == 1) {
$result .= 'x';
} else {
$result .= "x^";
$result .= $degree;
}
}
}
}
return $result;
}
}

View File

@@ -0,0 +1,198 @@
<?php
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Zxing\Common\Reedsolomon;
/**
* <p>Implements Reed-Solomon decoding, as the name implies.</p>
*
* <p>The algorithm will not be explained here, but the following references were helpful
* in creating this implementation:</p>
*
* <ul>
* <li>Bruce Maggs.
* <a href="http://www.cs.cmu.edu/afs/cs.cmu.edu/project/pscico-guyb/realworld/www/rs_decode.ps">
* "Decoding Reed-Solomon Codes"</a> (see discussion of Forney's Formula)</li>
* <li>J.I. Hall. <a href="www.mth.msu.edu/~jhall/classes/codenotes/GRS.pdf">
* "Chapter 5. Generalized Reed-Solomon Codes"</a>
* (see discussion of Euclidean algorithm)</li>
* </ul>
*
* <p>Much credit is due to William Rucklidge since portions of this code are an indirect
* port of his C++ Reed-Solomon implementation.</p>
*
* @author Sean Owen
* @author William Rucklidge
* @author sanfordsquires
*/
final class ReedSolomonDecoder
{
public function __construct(private $field)
{
}
/**
* <p>Decodes given set of received codewords, which include both data and error-correction
* codewords. Really, this means it uses Reed-Solomon to detect and correct errors, in-place,
* in the input.</p>
*
* @param data $received and error-correction codewords
* @param number $twoS of error-correction codewords available
*
* @throws ReedSolomonException if decoding fails for any reason
*/
public function decode(&$received, $twoS)
{
$poly = new GenericGFPoly($this->field, $received);
$syndromeCoefficients = fill_array(0, $twoS, 0);
$noError = true;
for ($i = 0; $i < $twoS; $i++) {
$eval = $poly->evaluateAt($this->field->exp($i + $this->field->getGeneratorBase()));
$syndromeCoefficients[(is_countable($syndromeCoefficients) ? count($syndromeCoefficients) : 0) - 1 - $i] = $eval;
if ($eval != 0) {
$noError = false;
}
}
if ($noError) {
return;
}
$syndrome = new GenericGFPoly($this->field, $syndromeCoefficients);
$sigmaOmega =
$this->runEuclideanAlgorithm($this->field->buildMonomial($twoS, 1), $syndrome, $twoS);
$sigma = $sigmaOmega[0];
$omega = $sigmaOmega[1];
$errorLocations = $this->findErrorLocations($sigma);
$errorMagnitudes = $this->findErrorMagnitudes($omega, $errorLocations);
$errorLocationsCount = is_countable($errorLocations) ? count($errorLocations) : 0;
for ($i = 0; $i < $errorLocationsCount; $i++) {
$position = (is_countable($received) ? count($received) : 0) - 1 - $this->field->log($errorLocations[$i]);
if ($position < 0) {
throw new ReedSolomonException("Bad error location");
}
$received[$position] = GenericGF::addOrSubtract($received[$position], $errorMagnitudes[$i]);
}
}
private function runEuclideanAlgorithm($a, $b, $R)
{
// Assume a's degree is >= b's
if ($a->getDegree() < $b->getDegree()) {
$temp = $a;
$a = $b;
$b = $temp;
}
$rLast = $a;
$r = $b;
$tLast = $this->field->getZero();
$t = $this->field->getOne();
// Run Euclidean algorithm until r's degree is less than R/2
while ($r->getDegree() >= $R / 2) {
$rLastLast = $rLast;
$tLastLast = $tLast;
$rLast = $r;
$tLast = $t;
// Divide rLastLast by rLast, with quotient in q and remainder in r
if ($rLast->isZero()) {
// Oops, Euclidean algorithm already terminated?
throw new ReedSolomonException("r_{i-1} was zero");
}
$r = $rLastLast;
$q = $this->field->getZero();
$denominatorLeadingTerm = $rLast->getCoefficient($rLast->getDegree());
$dltInverse = $this->field->inverse($denominatorLeadingTerm);
while ($r->getDegree() >= $rLast->getDegree() && !$r->isZero()) {
$degreeDiff = $r->getDegree() - $rLast->getDegree();
$scale = $this->field->multiply($r->getCoefficient($r->getDegree()), $dltInverse);
$q = $q->addOrSubtract($this->field->buildMonomial($degreeDiff, $scale));
$r = $r->addOrSubtract($rLast->multiplyByMonomial($degreeDiff, $scale));
}
$t = $q->multiply($tLast)->addOrSubtract($tLastLast);
if ($r->getDegree() >= $rLast->getDegree()) {
throw new ReedSolomonException("Division algorithm failed to reduce polynomial?");
}
}
$sigmaTildeAtZero = $t->getCoefficient(0);
if ($sigmaTildeAtZero == 0) {
throw new ReedSolomonException("sigmaTilde(0) was zero");
}
$inverse = $this->field->inverse($sigmaTildeAtZero);
$sigma = $t->multiply($inverse);
$omega = $r->multiply($inverse);
return [$sigma, $omega];
}
private function findErrorLocations($errorLocator)
{
// This is a direct application of Chien's search
$numErrors = $errorLocator->getDegree();
if ($numErrors == 1) { // shortcut
return [$errorLocator->getCoefficient(1)];
}
$result = fill_array(0, $numErrors, 0);
$e = 0;
for ($i = 1; $i < $this->field->getSize() && $e < $numErrors; $i++) {
if ($errorLocator->evaluateAt($i) == 0) {
$result[$e] = $this->field->inverse($i);
$e++;
}
}
if ($e != $numErrors) {
throw new ReedSolomonException("Error locator degree does not match number of roots");
}
return $result;
}
private function findErrorMagnitudes($errorEvaluator, $errorLocations)
{
// This is directly applying Forney's Formula
$s = is_countable($errorLocations) ? count($errorLocations) : 0;
$result = fill_array(0, $s, 0);
for ($i = 0; $i < $s; $i++) {
$xiInverse = $this->field->inverse($errorLocations[$i]);
$denominator = 1;
for ($j = 0; $j < $s; $j++) {
if ($i != $j) {
//denominator = field.multiply(denominator,
// GenericGF.addOrSubtract(1, field.multiply(errorLocations[j], xiInverse)));
// Above should work but fails on some Apple and Linux JDKs due to a Hotspot bug.
// Below is a funny-looking workaround from Steven Parkes
$term = $this->field->multiply($errorLocations[$j], $xiInverse);
$termPlus1 = ($term & 0x1) == 0 ? $term | 1 : $term & ~1;
$denominator = $this->field->multiply($denominator, $termPlus1);
}
}
$result[$i] = $this->field->multiply(
$errorEvaluator->evaluateAt($xiInverse),
$this->field->inverse($denominator)
);
if ($this->field->getGeneratorBase() != 0) {
$result[$i] = $this->field->multiply($result[$i], $xiInverse);
}
}
return $result;
}
}

View File

@@ -0,0 +1,28 @@
<?php
/*
* Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Zxing\Common\Reedsolomon;
/**
* <p>Thrown when an exception occurs during Reed-Solomon decoding, such as when
* there are too many errors to correct.</p>
*
* @author Sean Owen
*/
final class ReedSolomonException extends \Exception
{
}

View File

@@ -0,0 +1,103 @@
<?php
if (!function_exists('arraycopy')) {
function arraycopy($srcArray, $srcPos, $destArray, $destPos, $length)
{
$srcArrayToCopy = array_slice($srcArray, $srcPos, $length);
array_splice($destArray, $destPos, $length, $srcArrayToCopy);
return $destArray;
}
}
if (!function_exists('hashCode')) {
function hashCode($s)
{
$h = 0;
$len = strlen((string) $s);
for ($i = 0; $i < $len; $i++) {
$h = (31 * $h + ord($s[$i]));
}
return $h;
}
}
if (!function_exists('numberOfTrailingZeros')) {
function numberOfTrailingZeros($i)
{
if ($i == 0) {
return 32;
}
$num = 0;
while (($i & 1) == 0) {
$i >>= 1;
$num++;
}
return $num;
}
}
if (!function_exists('uRShift')) {
function uRShift($a, $b)
{
static $mask = (8 * PHP_INT_SIZE - 1);
if ($b === 0) {
return $a;
}
return ($a >> $b) & ~(1 << $mask >> ($b - 1));
}
}
/*
function sdvig3($num,$count=1){//>>> 32 bit
$s = decbin($num);
$sarray = str_split($s,1);
$sarray = array_slice($sarray,-32);//32bit
for($i=0;$i<=1;$i++) {
array_pop($sarray);
array_unshift($sarray, '0');
}
return bindec(implode($sarray));
}
*/
if (!function_exists('sdvig3')) {
function sdvig3($a, $b)
{
if ($a >= 0) {
return bindec(decbin($a >> $b)); //simply right shift for positive number
}
$bin = decbin($a >> $b);
$bin = substr($bin, $b); // zero fill on the left side
return bindec($bin);
}
}
if (!function_exists('floatToIntBits')) {
function floatToIntBits($float_val)
{
$int = unpack('i', pack('f', $float_val));
return $int[1];
}
}
if (!function_exists('fill_array')) {
function fill_array($index, $count, $value)
{
if ($count <= 0) {
return [0];
}
return array_fill($index, $count, $value);
}
}