commit message
This commit is contained in:
34
vendor/chillerlan/php-qrcode/src/Detector/AlignmentPattern.php
vendored
Normal file
34
vendor/chillerlan/php-qrcode/src/Detector/AlignmentPattern.php
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
/**
|
||||
* Class AlignmentPattern
|
||||
*
|
||||
* @created 17.01.2021
|
||||
* @author ZXing Authors
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @copyright 2021 Smiley
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
namespace chillerlan\QRCode\Detector;
|
||||
|
||||
/**
|
||||
* Encapsulates an alignment pattern, which are the smaller square patterns found in
|
||||
* all but the simplest QR Codes.
|
||||
*
|
||||
* @author Sean Owen
|
||||
*/
|
||||
final class AlignmentPattern extends ResultPoint{
|
||||
|
||||
/**
|
||||
* Combines this object's current estimate of a finder pattern position and module size
|
||||
* with a new estimate. It returns a new FinderPattern containing an average of the two.
|
||||
*/
|
||||
public function combineEstimate(float $i, float $j, float $newModuleSize):self{
|
||||
return new self(
|
||||
(($this->x + $j) / 2.0),
|
||||
(($this->y + $i) / 2.0),
|
||||
(($this->estimatedModuleSize + $newModuleSize) / 2.0)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
283
vendor/chillerlan/php-qrcode/src/Detector/AlignmentPatternFinder.php
vendored
Normal file
283
vendor/chillerlan/php-qrcode/src/Detector/AlignmentPatternFinder.php
vendored
Normal file
@@ -0,0 +1,283 @@
|
||||
<?php
|
||||
/**
|
||||
* Class AlignmentPatternFinder
|
||||
*
|
||||
* @created 17.01.2021
|
||||
* @author ZXing Authors
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @copyright 2021 Smiley
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
namespace chillerlan\QRCode\Detector;
|
||||
|
||||
use chillerlan\QRCode\Decoder\BitMatrix;
|
||||
use function abs, count;
|
||||
|
||||
/**
|
||||
* This class attempts to find alignment patterns in a QR Code. Alignment patterns look like finder
|
||||
* patterns but are smaller and appear at regular intervals throughout the image.
|
||||
*
|
||||
* At the moment this only looks for the bottom-right alignment pattern.
|
||||
*
|
||||
* This is mostly a simplified copy of FinderPatternFinder. It is copied,
|
||||
* pasted and stripped down here for maximum performance but does unfortunately duplicate
|
||||
* some code.
|
||||
*
|
||||
* This class is thread-safe but not reentrant. Each thread must allocate its own object.
|
||||
*
|
||||
* @author Sean Owen
|
||||
*/
|
||||
final class AlignmentPatternFinder{
|
||||
|
||||
private BitMatrix $matrix;
|
||||
private float $moduleSize;
|
||||
/** @var \chillerlan\QRCode\Detector\AlignmentPattern[] */
|
||||
private array $possibleCenters;
|
||||
|
||||
/**
|
||||
* Creates a finder that will look in a portion of the whole image.
|
||||
*
|
||||
* @param \chillerlan\QRCode\Decoder\BitMatrix $matrix image to search
|
||||
* @param float $moduleSize estimated module size so far
|
||||
*/
|
||||
public function __construct(BitMatrix $matrix, float $moduleSize){
|
||||
$this->matrix = $matrix;
|
||||
$this->moduleSize = $moduleSize;
|
||||
$this->possibleCenters = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* This method attempts to find the bottom-right alignment pattern in the image. It is a bit messy since
|
||||
* it's pretty performance-critical and so is written to be fast foremost.
|
||||
*
|
||||
* @param int $startX left column from which to start searching
|
||||
* @param int $startY top row from which to start searching
|
||||
* @param int $width width of region to search
|
||||
* @param int $height height of region to search
|
||||
*
|
||||
* @return \chillerlan\QRCode\Detector\AlignmentPattern|null
|
||||
*/
|
||||
public function find(int $startX, int $startY, int $width, int $height):?AlignmentPattern{
|
||||
$maxJ = ($startX + $width);
|
||||
$middleI = ($startY + ($height / 2));
|
||||
$stateCount = [];
|
||||
|
||||
// We are looking for black/white/black modules in 1:1:1 ratio;
|
||||
// this tracks the number of black/white/black modules seen so far
|
||||
for($iGen = 0; $iGen < $height; $iGen++){
|
||||
// Search from middle outwards
|
||||
$i = (int)($middleI + ((($iGen & 0x01) === 0) ? ($iGen + 1) / 2 : -(($iGen + 1) / 2)));
|
||||
$stateCount[0] = 0;
|
||||
$stateCount[1] = 0;
|
||||
$stateCount[2] = 0;
|
||||
$j = $startX;
|
||||
// Burn off leading white pixels before anything else; if we start in the middle of
|
||||
// a white run, it doesn't make sense to count its length, since we don't know if the
|
||||
// white run continued to the left of the start point
|
||||
while($j < $maxJ && !$this->matrix->check($j, $i)){
|
||||
$j++;
|
||||
}
|
||||
|
||||
$currentState = 0;
|
||||
|
||||
while($j < $maxJ){
|
||||
|
||||
if($this->matrix->check($j, $i)){
|
||||
// Black pixel
|
||||
if($currentState === 1){ // Counting black pixels
|
||||
$stateCount[$currentState]++;
|
||||
}
|
||||
// Counting white pixels
|
||||
else{
|
||||
// A winner?
|
||||
if($currentState === 2){
|
||||
// Yes
|
||||
if($this->foundPatternCross($stateCount)){
|
||||
$confirmed = $this->handlePossibleCenter($stateCount, $i, $j);
|
||||
|
||||
if($confirmed !== null){
|
||||
return $confirmed;
|
||||
}
|
||||
}
|
||||
|
||||
$stateCount[0] = $stateCount[2];
|
||||
$stateCount[1] = 1;
|
||||
$stateCount[2] = 0;
|
||||
$currentState = 1;
|
||||
}
|
||||
else{
|
||||
$stateCount[++$currentState]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
// White pixel
|
||||
else{
|
||||
// Counting black pixels
|
||||
if($currentState === 1){
|
||||
$currentState++;
|
||||
}
|
||||
|
||||
$stateCount[$currentState]++;
|
||||
}
|
||||
|
||||
$j++;
|
||||
}
|
||||
|
||||
if($this->foundPatternCross($stateCount)){
|
||||
$confirmed = $this->handlePossibleCenter($stateCount, $i, $maxJ);
|
||||
|
||||
if($confirmed !== null){
|
||||
return $confirmed;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Hmm, nothing we saw was observed and confirmed twice. If we had
|
||||
// any guess at all, return it.
|
||||
if(count($this->possibleCenters)){
|
||||
return $this->possibleCenters[0];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int[] $stateCount count of black/white/black pixels just read
|
||||
*
|
||||
* @return bool true if the proportions of the counts is close enough to the 1/1/1 ratios
|
||||
* used by alignment patterns to be considered a match
|
||||
*/
|
||||
private function foundPatternCross(array $stateCount):bool{
|
||||
$maxVariance = ($this->moduleSize / 2.0);
|
||||
|
||||
for($i = 0; $i < 3; $i++){
|
||||
if(abs($this->moduleSize - $stateCount[$i]) >= $maxVariance){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is called when a horizontal scan finds a possible alignment pattern. It will
|
||||
* cross-check with a vertical scan, and if successful, will see if this pattern had been
|
||||
* found on a previous horizontal scan. If so, we consider it confirmed and conclude we have
|
||||
* found the alignment pattern.
|
||||
*
|
||||
* @param int[] $stateCount reading state module counts from horizontal scan
|
||||
* @param int $i row where alignment pattern may be found
|
||||
* @param int $j end of possible alignment pattern in row
|
||||
*
|
||||
* @return \chillerlan\QRCode\Detector\AlignmentPattern|null if we have found the same pattern twice, or null if not
|
||||
*/
|
||||
private function handlePossibleCenter(array $stateCount, int $i, int $j):?AlignmentPattern{
|
||||
$stateCountTotal = ($stateCount[0] + $stateCount[1] + $stateCount[2]);
|
||||
$centerJ = $this->centerFromEnd($stateCount, $j);
|
||||
$centerI = $this->crossCheckVertical($i, (int)$centerJ, (2 * $stateCount[1]), $stateCountTotal);
|
||||
|
||||
if($centerI !== null){
|
||||
$estimatedModuleSize = (($stateCount[0] + $stateCount[1] + $stateCount[2]) / 3.0);
|
||||
|
||||
foreach($this->possibleCenters as $center){
|
||||
// Look for about the same center and module size:
|
||||
if($center->aboutEquals($estimatedModuleSize, $centerI, $centerJ)){
|
||||
return $center->combineEstimate($centerI, $centerJ, $estimatedModuleSize);
|
||||
}
|
||||
}
|
||||
|
||||
// Hadn't found this before; save it
|
||||
$point = new AlignmentPattern($centerJ, $centerI, $estimatedModuleSize);
|
||||
$this->possibleCenters[] = $point;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a count of black/white/black pixels just seen and an end position,
|
||||
* figures the location of the center of this black/white/black run.
|
||||
*
|
||||
* @param int[] $stateCount
|
||||
* @param int $end
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
private function centerFromEnd(array $stateCount, int $end):float{
|
||||
return (float)(($end - $stateCount[2]) - $stateCount[1] / 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* After a horizontal scan finds a potential alignment pattern, this method
|
||||
* "cross-checks" by scanning down vertically through the center of the possible
|
||||
* alignment pattern to see if the same proportion is detected.
|
||||
*
|
||||
* @param int $startI row where an alignment pattern was detected
|
||||
* @param int $centerJ center of the section that appears to cross an alignment pattern
|
||||
* @param int $maxCount maximum reasonable number of modules that should be
|
||||
* observed in any reading state, based on the results of the horizontal scan
|
||||
* @param int $originalStateCountTotal
|
||||
*
|
||||
* @return float|null vertical center of alignment pattern, or null if not found
|
||||
*/
|
||||
private function crossCheckVertical(int $startI, int $centerJ, int $maxCount, int $originalStateCountTotal):?float{
|
||||
$maxI = $this->matrix->getSize();
|
||||
$stateCount = [];
|
||||
$stateCount[0] = 0;
|
||||
$stateCount[1] = 0;
|
||||
$stateCount[2] = 0;
|
||||
|
||||
// Start counting up from center
|
||||
$i = $startI;
|
||||
while($i >= 0 && $this->matrix->check($centerJ, $i) && $stateCount[1] <= $maxCount){
|
||||
$stateCount[1]++;
|
||||
$i--;
|
||||
}
|
||||
// If already too many modules in this state or ran off the edge:
|
||||
if($i < 0 || $stateCount[1] > $maxCount){
|
||||
return null;
|
||||
}
|
||||
|
||||
while($i >= 0 && !$this->matrix->check($centerJ, $i) && $stateCount[0] <= $maxCount){
|
||||
$stateCount[0]++;
|
||||
$i--;
|
||||
}
|
||||
|
||||
if($stateCount[0] > $maxCount){
|
||||
return null;
|
||||
}
|
||||
|
||||
// Now also count down from center
|
||||
$i = ($startI + 1);
|
||||
while($i < $maxI && $this->matrix->check($centerJ, $i) && $stateCount[1] <= $maxCount){
|
||||
$stateCount[1]++;
|
||||
$i++;
|
||||
}
|
||||
|
||||
if($i == $maxI || $stateCount[1] > $maxCount){
|
||||
return null;
|
||||
}
|
||||
|
||||
while($i < $maxI && !$this->matrix->check($centerJ, $i) && $stateCount[2] <= $maxCount){
|
||||
$stateCount[2]++;
|
||||
$i++;
|
||||
}
|
||||
|
||||
if($stateCount[2] > $maxCount){
|
||||
return null;
|
||||
}
|
||||
|
||||
if((5 * abs(($stateCount[0] + $stateCount[1] + $stateCount[2]) - $originalStateCountTotal)) >= (2 * $originalStateCountTotal)){
|
||||
return null;
|
||||
}
|
||||
|
||||
if(!$this->foundPatternCross($stateCount)){
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->centerFromEnd($stateCount, $i);
|
||||
}
|
||||
|
||||
}
|
||||
350
vendor/chillerlan/php-qrcode/src/Detector/Detector.php
vendored
Normal file
350
vendor/chillerlan/php-qrcode/src/Detector/Detector.php
vendored
Normal file
@@ -0,0 +1,350 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Detector
|
||||
*
|
||||
* @created 17.01.2021
|
||||
* @author ZXing Authors
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @copyright 2021 Smiley
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
namespace chillerlan\QRCode\Detector;
|
||||
|
||||
use chillerlan\QRCode\Common\{LuminanceSourceInterface, Version};
|
||||
use chillerlan\QRCode\Decoder\{Binarizer, BitMatrix};
|
||||
use function abs, intdiv, is_nan, max, min, round;
|
||||
use const NAN;
|
||||
|
||||
/**
|
||||
* Encapsulates logic that can detect a QR Code in an image, even if the QR Code
|
||||
* is rotated or skewed, or partially obscured.
|
||||
*
|
||||
* @author Sean Owen
|
||||
*/
|
||||
final class Detector{
|
||||
|
||||
private BitMatrix $matrix;
|
||||
|
||||
/**
|
||||
* Detector constructor.
|
||||
*/
|
||||
public function __construct(LuminanceSourceInterface $source){
|
||||
$this->matrix = (new Binarizer($source))->getBlackMatrix();
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects a QR Code in an image.
|
||||
*/
|
||||
public function detect():BitMatrix{
|
||||
[$bottomLeft, $topLeft, $topRight] = (new FinderPatternFinder($this->matrix))->find();
|
||||
|
||||
$moduleSize = $this->calculateModuleSize($topLeft, $topRight, $bottomLeft);
|
||||
$dimension = $this->computeDimension($topLeft, $topRight, $bottomLeft, $moduleSize);
|
||||
$provisionalVersion = new Version(intdiv(($dimension - 17), 4));
|
||||
$alignmentPattern = null;
|
||||
|
||||
// Anything above version 1 has an alignment pattern
|
||||
if(!empty($provisionalVersion->getAlignmentPattern())){
|
||||
// Guess where a "bottom right" finder pattern would have been
|
||||
$bottomRightX = ($topRight->getX() - $topLeft->getX() + $bottomLeft->getX());
|
||||
$bottomRightY = ($topRight->getY() - $topLeft->getY() + $bottomLeft->getY());
|
||||
|
||||
// Estimate that alignment pattern is closer by 3 modules
|
||||
// from "bottom right" to known top left location
|
||||
$correctionToTopLeft = (1.0 - 3.0 / (float)($provisionalVersion->getDimension() - 7));
|
||||
$estAlignmentX = (int)($topLeft->getX() + $correctionToTopLeft * ($bottomRightX - $topLeft->getX()));
|
||||
$estAlignmentY = (int)($topLeft->getY() + $correctionToTopLeft * ($bottomRightY - $topLeft->getY()));
|
||||
|
||||
// Kind of arbitrary -- expand search radius before giving up
|
||||
for($i = 4; $i <= 16; $i <<= 1){//??????????
|
||||
$alignmentPattern = $this->findAlignmentInRegion($moduleSize, $estAlignmentX, $estAlignmentY, (float)$i);
|
||||
|
||||
if($alignmentPattern !== null){
|
||||
break;
|
||||
}
|
||||
}
|
||||
// If we didn't find alignment pattern... well try anyway without it
|
||||
}
|
||||
|
||||
$transform = $this->createTransform($topLeft, $topRight, $bottomLeft, $dimension, $alignmentPattern);
|
||||
|
||||
return (new GridSampler)->sampleGrid($this->matrix, $dimension, $transform);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes an average estimated module size based on estimated derived from the positions
|
||||
* of the three finder patterns.
|
||||
*
|
||||
* @throws \chillerlan\QRCode\Detector\QRCodeDetectorException
|
||||
*/
|
||||
private function calculateModuleSize(FinderPattern $topLeft, FinderPattern $topRight, FinderPattern $bottomLeft):float{
|
||||
// Take the average
|
||||
$moduleSize = ((
|
||||
$this->calculateModuleSizeOneWay($topLeft, $topRight) +
|
||||
$this->calculateModuleSizeOneWay($topLeft, $bottomLeft)
|
||||
) / 2.0);
|
||||
|
||||
if($moduleSize < 1.0){
|
||||
throw new QRCodeDetectorException('module size < 1.0');
|
||||
}
|
||||
|
||||
return $moduleSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Estimates module size based on two finder patterns -- it uses
|
||||
* #sizeOfBlackWhiteBlackRunBothWays(int, int, int, int) to figure the
|
||||
* width of each, measuring along the axis between their centers.
|
||||
*/
|
||||
private function calculateModuleSizeOneWay(FinderPattern $a, FinderPattern $b):float{
|
||||
|
||||
$moduleSizeEst1 = $this->sizeOfBlackWhiteBlackRunBothWays($a->getX(), $a->getY(), $b->getX(), $b->getY());
|
||||
$moduleSizeEst2 = $this->sizeOfBlackWhiteBlackRunBothWays($b->getX(), $b->getY(), $a->getX(), $a->getY());
|
||||
|
||||
if(is_nan($moduleSizeEst1)){
|
||||
return ($moduleSizeEst2 / 7.0);
|
||||
}
|
||||
|
||||
if(is_nan($moduleSizeEst2)){
|
||||
return ($moduleSizeEst1 / 7.0);
|
||||
}
|
||||
// Average them, and divide by 7 since we've counted the width of 3 black modules,
|
||||
// and 1 white and 1 black module on either side. Ergo, divide sum by 14.
|
||||
return (($moduleSizeEst1 + $moduleSizeEst2) / 14.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* See #sizeOfBlackWhiteBlackRun(int, int, int, int); computes the total width of
|
||||
* a finder pattern by looking for a black-white-black run from the center in the direction
|
||||
* of another po$(another finder pattern center), and in the opposite direction too.
|
||||
*
|
||||
* @noinspection DuplicatedCode
|
||||
*/
|
||||
private function sizeOfBlackWhiteBlackRunBothWays(float $fromX, float $fromY, float $toX, float $toY):float{
|
||||
$result = $this->sizeOfBlackWhiteBlackRun((int)$fromX, (int)$fromY, (int)$toX, (int)$toY);
|
||||
$dimension = $this->matrix->getSize();
|
||||
// Now count other way -- don't run off image though of course
|
||||
$scale = 1.0;
|
||||
$otherToX = ($fromX - ($toX - $fromX));
|
||||
|
||||
if($otherToX < 0){
|
||||
$scale = ($fromX / ($fromX - $otherToX));
|
||||
$otherToX = 0;
|
||||
}
|
||||
elseif($otherToX >= $dimension){
|
||||
$scale = (($dimension - 1 - $fromX) / ($otherToX - $fromX));
|
||||
$otherToX = ($dimension - 1);
|
||||
}
|
||||
|
||||
$otherToY = (int)($fromY - ($toY - $fromY) * $scale);
|
||||
$scale = 1.0;
|
||||
|
||||
if($otherToY < 0){
|
||||
$scale = ($fromY / ($fromY - $otherToY));
|
||||
$otherToY = 0;
|
||||
}
|
||||
elseif($otherToY >= $dimension){
|
||||
$scale = (($dimension - 1 - $fromY) / ($otherToY - $fromY));
|
||||
$otherToY = ($dimension - 1);
|
||||
}
|
||||
|
||||
$otherToX = (int)($fromX + ($otherToX - $fromX) * $scale);
|
||||
$result += $this->sizeOfBlackWhiteBlackRun((int)$fromX, (int)$fromY, $otherToX, $otherToY);
|
||||
|
||||
// Middle pixel is double-counted this way; subtract 1
|
||||
return ($result - 1.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method traces a line from a po$in the image, in the direction towards another point.
|
||||
* It begins in a black region, and keeps going until it finds white, then black, then white again.
|
||||
* It reports the distance from the start to this point.
|
||||
*
|
||||
* This is used when figuring out how wide a finder pattern is, when the finder pattern
|
||||
* may be skewed or rotated.
|
||||
*/
|
||||
private function sizeOfBlackWhiteBlackRun(int $fromX, int $fromY, int $toX, int $toY):float{
|
||||
// Mild variant of Bresenham's algorithm;
|
||||
// @see https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm
|
||||
$steep = abs($toY - $fromY) > abs($toX - $fromX);
|
||||
|
||||
if($steep){
|
||||
$temp = $fromX;
|
||||
$fromX = $fromY;
|
||||
$fromY = $temp;
|
||||
$temp = $toX;
|
||||
$toX = $toY;
|
||||
$toY = $temp;
|
||||
}
|
||||
|
||||
$dx = abs($toX - $fromX);
|
||||
$dy = abs($toY - $fromY);
|
||||
$error = (-$dx / 2);
|
||||
$xstep = (($fromX < $toX) ? 1 : -1);
|
||||
$ystep = (($fromY < $toY) ? 1 : -1);
|
||||
|
||||
// In black pixels, looking for white, first or second time.
|
||||
$state = 0;
|
||||
// Loop up until x == toX, but not beyond
|
||||
$xLimit = ($toX + $xstep);
|
||||
|
||||
for($x = $fromX, $y = $fromY; $x !== $xLimit; $x += $xstep){
|
||||
$realX = ($steep) ? $y : $x;
|
||||
$realY = ($steep) ? $x : $y;
|
||||
|
||||
// Does current pixel mean we have moved white to black or vice versa?
|
||||
// Scanning black in state 0,2 and white in state 1, so if we find the wrong
|
||||
// color, advance to next state or end if we are in state 2 already
|
||||
if(($state === 1) === $this->matrix->check($realX, $realY)){
|
||||
|
||||
if($state === 2){
|
||||
return FinderPattern::distance($x, $y, $fromX, $fromY);
|
||||
}
|
||||
|
||||
$state++;
|
||||
}
|
||||
|
||||
$error += $dy;
|
||||
|
||||
if($error > 0){
|
||||
|
||||
if($y === $toY){
|
||||
break;
|
||||
}
|
||||
|
||||
$y += $ystep;
|
||||
$error -= $dx;
|
||||
}
|
||||
}
|
||||
|
||||
// Found black-white-black; give the benefit of the doubt that the next pixel outside the image
|
||||
// is "white" so this last po$at (toX+xStep,toY) is the right ending. This is really a
|
||||
// small approximation; (toX+xStep,toY+yStep) might be really correct. Ignore this.
|
||||
if($state === 2){
|
||||
return FinderPattern::distance(($toX + $xstep), $toY, $fromX, $fromY);
|
||||
}
|
||||
|
||||
// else we didn't find even black-white-black; no estimate is really possible
|
||||
return NAN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the dimension (number of modules on a size) of the QR Code based on the position
|
||||
* of the finder patterns and estimated module size.
|
||||
*
|
||||
* @throws \chillerlan\QRCode\Detector\QRCodeDetectorException
|
||||
*/
|
||||
private function computeDimension(FinderPattern $nw, FinderPattern $ne, FinderPattern $sw, float $size):int{
|
||||
$tltrCentersDimension = (int)round($nw->getDistance($ne) / $size);
|
||||
$tlblCentersDimension = (int)round($nw->getDistance($sw) / $size);
|
||||
$dimension = (int)((($tltrCentersDimension + $tlblCentersDimension) / 2) + 7);
|
||||
|
||||
switch($dimension % 4){
|
||||
case 0:
|
||||
$dimension++;
|
||||
break;
|
||||
// 1? do nothing
|
||||
case 2:
|
||||
$dimension--;
|
||||
break;
|
||||
case 3:
|
||||
throw new QRCodeDetectorException('estimated dimension: '.$dimension);
|
||||
}
|
||||
|
||||
if(($dimension % 4) !== 1){
|
||||
throw new QRCodeDetectorException('dimension mod 4 is not 1');
|
||||
}
|
||||
|
||||
return $dimension;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to locate an alignment pattern in a limited region of the image, which is
|
||||
* guessed to contain it.
|
||||
*
|
||||
* @param float $overallEstModuleSize estimated module size so far
|
||||
* @param int $estAlignmentX x coordinate of center of area probably containing alignment pattern
|
||||
* @param int $estAlignmentY y coordinate of above
|
||||
* @param float $allowanceFactor number of pixels in all directions to search from the center
|
||||
*
|
||||
* @return \chillerlan\QRCode\Detector\AlignmentPattern|null if found, or null otherwise
|
||||
*/
|
||||
private function findAlignmentInRegion(
|
||||
float $overallEstModuleSize,
|
||||
int $estAlignmentX,
|
||||
int $estAlignmentY,
|
||||
float $allowanceFactor
|
||||
):?AlignmentPattern{
|
||||
// Look for an alignment pattern (3 modules in size) around where it should be
|
||||
$dimension = $this->matrix->getSize();
|
||||
$allowance = (int)($allowanceFactor * $overallEstModuleSize);
|
||||
$alignmentAreaLeftX = max(0, ($estAlignmentX - $allowance));
|
||||
$alignmentAreaRightX = min(($dimension - 1), ($estAlignmentX + $allowance));
|
||||
|
||||
if(($alignmentAreaRightX - $alignmentAreaLeftX) < ($overallEstModuleSize * 3)){
|
||||
return null;
|
||||
}
|
||||
|
||||
$alignmentAreaTopY = max(0, ($estAlignmentY - $allowance));
|
||||
$alignmentAreaBottomY = min(($dimension - 1), ($estAlignmentY + $allowance));
|
||||
|
||||
if(($alignmentAreaBottomY - $alignmentAreaTopY) < ($overallEstModuleSize * 3)){
|
||||
return null;
|
||||
}
|
||||
|
||||
return (new AlignmentPatternFinder($this->matrix, $overallEstModuleSize))->find(
|
||||
$alignmentAreaLeftX,
|
||||
$alignmentAreaTopY,
|
||||
($alignmentAreaRightX - $alignmentAreaLeftX),
|
||||
($alignmentAreaBottomY - $alignmentAreaTopY),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private function createTransform(
|
||||
FinderPattern $nw,
|
||||
FinderPattern $ne,
|
||||
FinderPattern $sw,
|
||||
int $size,
|
||||
AlignmentPattern $ap = null
|
||||
):PerspectiveTransform{
|
||||
$dimMinusThree = ($size - 3.5);
|
||||
|
||||
if($ap instanceof AlignmentPattern){
|
||||
$bottomRightX = $ap->getX();
|
||||
$bottomRightY = $ap->getY();
|
||||
$sourceBottomRightX = ($dimMinusThree - 3.0);
|
||||
$sourceBottomRightY = $sourceBottomRightX;
|
||||
}
|
||||
else{
|
||||
// Don't have an alignment pattern, just make up the bottom-right point
|
||||
$bottomRightX = ($ne->getX() - $nw->getX() + $sw->getX());
|
||||
$bottomRightY = ($ne->getY() - $nw->getY() + $sw->getY());
|
||||
$sourceBottomRightX = $dimMinusThree;
|
||||
$sourceBottomRightY = $dimMinusThree;
|
||||
}
|
||||
|
||||
return (new PerspectiveTransform)->quadrilateralToQuadrilateral(
|
||||
3.5,
|
||||
3.5,
|
||||
$dimMinusThree,
|
||||
3.5,
|
||||
$sourceBottomRightX,
|
||||
$sourceBottomRightY,
|
||||
3.5,
|
||||
$dimMinusThree,
|
||||
$nw->getX(),
|
||||
$nw->getY(),
|
||||
$ne->getX(),
|
||||
$ne->getY(),
|
||||
$bottomRightX,
|
||||
$bottomRightY,
|
||||
$sw->getX(),
|
||||
$sw->getY()
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
92
vendor/chillerlan/php-qrcode/src/Detector/FinderPattern.php
vendored
Normal file
92
vendor/chillerlan/php-qrcode/src/Detector/FinderPattern.php
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
/**
|
||||
* Class FinderPattern
|
||||
*
|
||||
* @created 17.01.2021
|
||||
* @author ZXing Authors
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @copyright 2021 Smiley
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
namespace chillerlan\QRCode\Detector;
|
||||
|
||||
use function sqrt;
|
||||
|
||||
/**
|
||||
* Encapsulates a finder pattern, which are the three square patterns found in
|
||||
* the corners of QR Codes. It also encapsulates a count of similar finder patterns,
|
||||
* as a convenience to the finder's bookkeeping.
|
||||
*
|
||||
* @author Sean Owen
|
||||
*/
|
||||
final class FinderPattern extends ResultPoint{
|
||||
|
||||
private int $count;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function __construct(float $posX, float $posY, float $estimatedModuleSize, int $count = null){
|
||||
parent::__construct($posX, $posY, $estimatedModuleSize);
|
||||
|
||||
$this->count = ($count ?? 1);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function getCount():int{
|
||||
return $this->count;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \chillerlan\QRCode\Detector\FinderPattern $b second pattern
|
||||
*
|
||||
* @return float distance between two points
|
||||
*/
|
||||
public function getDistance(FinderPattern $b):float{
|
||||
return self::distance($this->x, $this->y, $b->x, $b->y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get square of distance between a and b.
|
||||
*/
|
||||
public function getSquaredDistance(FinderPattern $b):float{
|
||||
return self::squaredDistance($this->x, $this->y, $b->x, $b->y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines this object's current estimate of a finder pattern position and module size
|
||||
* with a new estimate. It returns a new FinderPattern containing a weighted average
|
||||
* based on count.
|
||||
*/
|
||||
public function combineEstimate(float $i, float $j, float $newModuleSize):self{
|
||||
$combinedCount = ($this->count + 1);
|
||||
|
||||
return new self(
|
||||
($this->count * $this->x + $j) / $combinedCount,
|
||||
($this->count * $this->y + $i) / $combinedCount,
|
||||
($this->count * $this->estimatedModuleSize + $newModuleSize) / $combinedCount,
|
||||
$combinedCount
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static function squaredDistance(float $aX, float $aY, float $bX, float $bY):float{
|
||||
$xDiff = ($aX - $bX);
|
||||
$yDiff = ($aY - $bY);
|
||||
|
||||
return ($xDiff * $xDiff + $yDiff * $yDiff);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public static function distance(float $aX, float $aY, float $bX, float $bY):float{
|
||||
return sqrt(self::squaredDistance($aX, $aY, $bX, $bY));
|
||||
}
|
||||
|
||||
}
|
||||
770
vendor/chillerlan/php-qrcode/src/Detector/FinderPatternFinder.php
vendored
Normal file
770
vendor/chillerlan/php-qrcode/src/Detector/FinderPatternFinder.php
vendored
Normal file
@@ -0,0 +1,770 @@
|
||||
<?php
|
||||
/**
|
||||
* Class FinderPatternFinder
|
||||
*
|
||||
* @created 17.01.2021
|
||||
* @author ZXing Authors
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @copyright 2021 Smiley
|
||||
* @license Apache-2.0
|
||||
*
|
||||
* @phan-file-suppress PhanTypePossiblyInvalidDimOffset
|
||||
*/
|
||||
|
||||
namespace chillerlan\QRCode\Detector;
|
||||
|
||||
use chillerlan\QRCode\Decoder\BitMatrix;
|
||||
use function abs, count, intdiv, usort;
|
||||
use const PHP_FLOAT_MAX;
|
||||
|
||||
/**
|
||||
* This class attempts to find finder patterns in a QR Code. Finder patterns are the square
|
||||
* markers at three corners of a QR Code.
|
||||
*
|
||||
* This class is thread-safe but not reentrant. Each thread must allocate its own object.
|
||||
*
|
||||
* @author Sean Owen
|
||||
*/
|
||||
final class FinderPatternFinder{
|
||||
|
||||
private const MIN_SKIP = 2;
|
||||
private const MAX_MODULES = 177; // 1 pixel/module times 3 modules/center
|
||||
private const CENTER_QUORUM = 2; // support up to version 10 for mobile clients
|
||||
private BitMatrix $matrix;
|
||||
/** @var \chillerlan\QRCode\Detector\FinderPattern[] */
|
||||
private array $possibleCenters;
|
||||
private bool $hasSkipped = false;
|
||||
|
||||
/**
|
||||
* Creates a finder that will search the image for three finder patterns.
|
||||
*
|
||||
* @param BitMatrix $matrix image to search
|
||||
*/
|
||||
public function __construct(BitMatrix $matrix){
|
||||
$this->matrix = $matrix;
|
||||
$this->possibleCenters = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \chillerlan\QRCode\Detector\FinderPattern[]
|
||||
*/
|
||||
public function find():array{
|
||||
$dimension = $this->matrix->getSize();
|
||||
|
||||
// We are looking for black/white/black/white/black modules in
|
||||
// 1:1:3:1:1 ratio; this tracks the number of such modules seen so far
|
||||
// Let's assume that the maximum version QR Code we support takes up 1/4 the height of the
|
||||
// image, and then account for the center being 3 modules in size. This gives the smallest
|
||||
// number of pixels the center could be, so skip this often.
|
||||
$iSkip = intdiv((3 * $dimension), (4 * self::MAX_MODULES));
|
||||
|
||||
if($iSkip < self::MIN_SKIP){
|
||||
$iSkip = self::MIN_SKIP;
|
||||
}
|
||||
|
||||
$done = false;
|
||||
|
||||
for($i = ($iSkip - 1); ($i < $dimension) && !$done; $i += $iSkip){
|
||||
// Get a row of black/white values
|
||||
$stateCount = $this->getCrossCheckStateCount();
|
||||
$currentState = 0;
|
||||
|
||||
for($j = 0; $j < $dimension; $j++){
|
||||
|
||||
// Black pixel
|
||||
if($this->matrix->check($j, $i)){
|
||||
// Counting white pixels
|
||||
if(($currentState & 1) === 1){
|
||||
$currentState++;
|
||||
}
|
||||
|
||||
$stateCount[$currentState]++;
|
||||
}
|
||||
// White pixel
|
||||
else{
|
||||
// Counting black pixels
|
||||
if(($currentState & 1) === 0){
|
||||
// A winner?
|
||||
if($currentState === 4){
|
||||
// Yes
|
||||
if($this->foundPatternCross($stateCount)){
|
||||
$confirmed = $this->handlePossibleCenter($stateCount, $i, $j);
|
||||
|
||||
if($confirmed){
|
||||
// Start examining every other line. Checking each line turned out to be too
|
||||
// expensive and didn't improve performance.
|
||||
$iSkip = 3;
|
||||
|
||||
if($this->hasSkipped){
|
||||
$done = $this->haveMultiplyConfirmedCenters();
|
||||
}
|
||||
else{
|
||||
$rowSkip = $this->findRowSkip();
|
||||
|
||||
if($rowSkip > $stateCount[2]){
|
||||
// Skip rows between row of lower confirmed center
|
||||
// and top of presumed third confirmed center
|
||||
// but back up a bit to get a full chance of detecting
|
||||
// it, entire width of center of finder pattern
|
||||
|
||||
// Skip by rowSkip, but back off by $stateCount[2] (size of last center
|
||||
// of pattern we saw) to be conservative, and also back off by iSkip which
|
||||
// is about to be re-added
|
||||
$i += ($rowSkip - $stateCount[2] - $iSkip);
|
||||
$j = ($dimension - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
else{
|
||||
$stateCount = $this->doShiftCounts2($stateCount);
|
||||
$currentState = 3;
|
||||
|
||||
continue;
|
||||
}
|
||||
// Clear state to start looking again
|
||||
$currentState = 0;
|
||||
$stateCount = $this->getCrossCheckStateCount();
|
||||
}
|
||||
// No, shift counts back by two
|
||||
else{
|
||||
$stateCount = $this->doShiftCounts2($stateCount);
|
||||
$currentState = 3;
|
||||
}
|
||||
}
|
||||
else{
|
||||
$stateCount[++$currentState]++;
|
||||
}
|
||||
}
|
||||
// Counting white pixels
|
||||
else{
|
||||
$stateCount[$currentState]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if($this->foundPatternCross($stateCount)){
|
||||
$confirmed = $this->handlePossibleCenter($stateCount, $i, $dimension);
|
||||
|
||||
if($confirmed){
|
||||
$iSkip = $stateCount[0];
|
||||
|
||||
if($this->hasSkipped){
|
||||
// Found a third one
|
||||
$done = $this->haveMultiplyConfirmedCenters();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->orderBestPatterns($this->selectBestPatterns());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int[]
|
||||
*/
|
||||
private function getCrossCheckStateCount():array{
|
||||
return [0, 0, 0, 0, 0];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int[] $stateCount
|
||||
*
|
||||
* @return int[]
|
||||
*/
|
||||
private function doShiftCounts2(array $stateCount):array{
|
||||
$stateCount[0] = $stateCount[2];
|
||||
$stateCount[1] = $stateCount[3];
|
||||
$stateCount[2] = $stateCount[4];
|
||||
$stateCount[3] = 1;
|
||||
$stateCount[4] = 0;
|
||||
|
||||
return $stateCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a count of black/white/black/white/black pixels just seen and an end position,
|
||||
* figures the location of the center of this run.
|
||||
*
|
||||
* @param int[] $stateCount
|
||||
*/
|
||||
private function centerFromEnd(array $stateCount, int $end):float{
|
||||
return (float)(($end - $stateCount[4] - $stateCount[3]) - $stateCount[2] / 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int[] $stateCount
|
||||
*/
|
||||
private function foundPatternCross(array $stateCount):bool{
|
||||
// Allow less than 50% variance from 1-1-3-1-1 proportions
|
||||
return $this->foundPatternVariance($stateCount, 2.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int[] $stateCount
|
||||
*/
|
||||
private function foundPatternDiagonal(array $stateCount):bool{
|
||||
// Allow less than 75% variance from 1-1-3-1-1 proportions
|
||||
return $this->foundPatternVariance($stateCount, 1.333);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int[] $stateCount count of black/white/black/white/black pixels just read
|
||||
*
|
||||
* @return bool true if the proportions of the counts is close enough to the 1/1/3/1/1 ratios
|
||||
* used by finder patterns to be considered a match
|
||||
*/
|
||||
private function foundPatternVariance(array $stateCount, float $variance):bool{
|
||||
$totalModuleSize = 0;
|
||||
|
||||
for($i = 0; $i < 5; $i++){
|
||||
$count = $stateCount[$i];
|
||||
|
||||
if($count === 0){
|
||||
return false;
|
||||
}
|
||||
|
||||
$totalModuleSize += $count;
|
||||
}
|
||||
|
||||
if($totalModuleSize < 7){
|
||||
return false;
|
||||
}
|
||||
|
||||
$moduleSize = ($totalModuleSize / 7.0);
|
||||
$maxVariance = ($moduleSize / $variance);
|
||||
|
||||
return
|
||||
abs($moduleSize - $stateCount[0]) < $maxVariance
|
||||
&& abs($moduleSize - $stateCount[1]) < $maxVariance
|
||||
&& abs(3.0 * $moduleSize - $stateCount[2]) < (3 * $maxVariance)
|
||||
&& abs($moduleSize - $stateCount[3]) < $maxVariance
|
||||
&& abs($moduleSize - $stateCount[4]) < $maxVariance;
|
||||
}
|
||||
|
||||
/**
|
||||
* After a vertical and horizontal scan finds a potential finder pattern, this method
|
||||
* "cross-cross-cross-checks" by scanning down diagonally through the center of the possible
|
||||
* finder pattern to see if the same proportion is detected.
|
||||
*
|
||||
* @param int $centerI row where a finder pattern was detected
|
||||
* @param int $centerJ center of the section that appears to cross a finder pattern
|
||||
*
|
||||
* @return bool true if proportions are withing expected limits
|
||||
*/
|
||||
private function crossCheckDiagonal(int $centerI, int $centerJ):bool{
|
||||
$stateCount = $this->getCrossCheckStateCount();
|
||||
|
||||
// Start counting up, left from center finding black center mass
|
||||
$i = 0;
|
||||
|
||||
while($centerI >= $i && $centerJ >= $i && $this->matrix->check(($centerJ - $i), ($centerI - $i))){
|
||||
$stateCount[2]++;
|
||||
$i++;
|
||||
}
|
||||
|
||||
if($stateCount[2] === 0){
|
||||
return false;
|
||||
}
|
||||
|
||||
// Continue up, left finding white space
|
||||
while($centerI >= $i && $centerJ >= $i && !$this->matrix->check(($centerJ - $i), ($centerI - $i))){
|
||||
$stateCount[1]++;
|
||||
$i++;
|
||||
}
|
||||
|
||||
if($stateCount[1] === 0){
|
||||
return false;
|
||||
}
|
||||
|
||||
// Continue up, left finding black border
|
||||
while($centerI >= $i && $centerJ >= $i && $this->matrix->check(($centerJ - $i), ($centerI - $i))){
|
||||
$stateCount[0]++;
|
||||
$i++;
|
||||
}
|
||||
|
||||
if($stateCount[0] === 0){
|
||||
return false;
|
||||
}
|
||||
|
||||
$dimension = $this->matrix->getSize();
|
||||
|
||||
// Now also count down, right from center
|
||||
$i = 1;
|
||||
while(($centerI + $i) < $dimension && ($centerJ + $i) < $dimension && $this->matrix->check(($centerJ + $i), ($centerI + $i))){
|
||||
$stateCount[2]++;
|
||||
$i++;
|
||||
}
|
||||
|
||||
while(($centerI + $i) < $dimension && ($centerJ + $i) < $dimension && !$this->matrix->check(($centerJ + $i), ($centerI + $i))){
|
||||
$stateCount[3]++;
|
||||
$i++;
|
||||
}
|
||||
|
||||
if($stateCount[3] === 0){
|
||||
return false;
|
||||
}
|
||||
|
||||
while(($centerI + $i) < $dimension && ($centerJ + $i) < $dimension && $this->matrix->check(($centerJ + $i), ($centerI + $i))){
|
||||
$stateCount[4]++;
|
||||
$i++;
|
||||
}
|
||||
|
||||
if($stateCount[4] === 0){
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->foundPatternDiagonal($stateCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* After a horizontal scan finds a potential finder pattern, this method
|
||||
* "cross-checks" by scanning down vertically through the center of the possible
|
||||
* finder pattern to see if the same proportion is detected.
|
||||
*
|
||||
* @param int $startI row where a finder pattern was detected
|
||||
* @param int $centerJ center of the section that appears to cross a finder pattern
|
||||
* @param int $maxCount maximum reasonable number of modules that should be
|
||||
* observed in any reading state, based on the results of the horizontal scan
|
||||
* @param int $originalStateCountTotal
|
||||
*
|
||||
* @return float|null vertical center of finder pattern, or null if not found
|
||||
* @noinspection DuplicatedCode
|
||||
*/
|
||||
private function crossCheckVertical(int $startI, int $centerJ, int $maxCount, int $originalStateCountTotal):?float{
|
||||
$maxI = $this->matrix->getSize();
|
||||
$stateCount = $this->getCrossCheckStateCount();
|
||||
|
||||
// Start counting up from center
|
||||
$i = $startI;
|
||||
while($i >= 0 && $this->matrix->check($centerJ, $i)){
|
||||
$stateCount[2]++;
|
||||
$i--;
|
||||
}
|
||||
|
||||
if($i < 0){
|
||||
return null;
|
||||
}
|
||||
|
||||
while($i >= 0 && !$this->matrix->check($centerJ, $i) && $stateCount[1] <= $maxCount){
|
||||
$stateCount[1]++;
|
||||
$i--;
|
||||
}
|
||||
|
||||
// If already too many modules in this state or ran off the edge:
|
||||
if($i < 0 || $stateCount[1] > $maxCount){
|
||||
return null;
|
||||
}
|
||||
|
||||
while($i >= 0 && $this->matrix->check($centerJ, $i) && $stateCount[0] <= $maxCount){
|
||||
$stateCount[0]++;
|
||||
$i--;
|
||||
}
|
||||
|
||||
if($stateCount[0] > $maxCount){
|
||||
return null;
|
||||
}
|
||||
|
||||
// Now also count down from center
|
||||
$i = ($startI + 1);
|
||||
while($i < $maxI && $this->matrix->check($centerJ, $i)){
|
||||
$stateCount[2]++;
|
||||
$i++;
|
||||
}
|
||||
|
||||
if($i === $maxI){
|
||||
return null;
|
||||
}
|
||||
|
||||
while($i < $maxI && !$this->matrix->check($centerJ, $i) && $stateCount[3] < $maxCount){
|
||||
$stateCount[3]++;
|
||||
$i++;
|
||||
}
|
||||
|
||||
if($i === $maxI || $stateCount[3] >= $maxCount){
|
||||
return null;
|
||||
}
|
||||
|
||||
while($i < $maxI && $this->matrix->check($centerJ, $i) && $stateCount[4] < $maxCount){
|
||||
$stateCount[4]++;
|
||||
$i++;
|
||||
}
|
||||
|
||||
if($stateCount[4] >= $maxCount){
|
||||
return null;
|
||||
}
|
||||
|
||||
// If we found a finder-pattern-like section, but its size is more than 40% different from
|
||||
// the original, assume it's a false positive
|
||||
$stateCountTotal = ($stateCount[0] + $stateCount[1] + $stateCount[2] + $stateCount[3] + $stateCount[4]);
|
||||
|
||||
if((5 * abs($stateCountTotal - $originalStateCountTotal)) >= (2 * $originalStateCountTotal)){
|
||||
return null;
|
||||
}
|
||||
|
||||
if(!$this->foundPatternCross($stateCount)){
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->centerFromEnd($stateCount, $i);
|
||||
}
|
||||
|
||||
/**
|
||||
* Like #crossCheckVertical(int, int, int, int), and in fact is basically identical,
|
||||
* except it reads horizontally instead of vertically. This is used to cross-cross
|
||||
* check a vertical cross-check and locate the real center of the alignment pattern.
|
||||
* @noinspection DuplicatedCode
|
||||
*/
|
||||
private function crossCheckHorizontal(int $startJ, int $centerI, int $maxCount, int $originalStateCountTotal):?float{
|
||||
$maxJ = $this->matrix->getSize();
|
||||
$stateCount = $this->getCrossCheckStateCount();
|
||||
|
||||
$j = $startJ;
|
||||
while($j >= 0 && $this->matrix->check($j, $centerI)){
|
||||
$stateCount[2]++;
|
||||
$j--;
|
||||
}
|
||||
|
||||
if($j < 0){
|
||||
return null;
|
||||
}
|
||||
|
||||
while($j >= 0 && !$this->matrix->check($j, $centerI) && $stateCount[1] <= $maxCount){
|
||||
$stateCount[1]++;
|
||||
$j--;
|
||||
}
|
||||
|
||||
if($j < 0 || $stateCount[1] > $maxCount){
|
||||
return null;
|
||||
}
|
||||
|
||||
while($j >= 0 && $this->matrix->check($j, $centerI) && $stateCount[0] <= $maxCount){
|
||||
$stateCount[0]++;
|
||||
$j--;
|
||||
}
|
||||
|
||||
if($stateCount[0] > $maxCount){
|
||||
return null;
|
||||
}
|
||||
|
||||
$j = ($startJ + 1);
|
||||
while($j < $maxJ && $this->matrix->check($j, $centerI)){
|
||||
$stateCount[2]++;
|
||||
$j++;
|
||||
}
|
||||
|
||||
if($j === $maxJ){
|
||||
return null;
|
||||
}
|
||||
|
||||
while($j < $maxJ && !$this->matrix->check($j, $centerI) && $stateCount[3] < $maxCount){
|
||||
$stateCount[3]++;
|
||||
$j++;
|
||||
}
|
||||
|
||||
if($j === $maxJ || $stateCount[3] >= $maxCount){
|
||||
return null;
|
||||
}
|
||||
|
||||
while($j < $maxJ && $this->matrix->check($j, $centerI) && $stateCount[4] < $maxCount){
|
||||
$stateCount[4]++;
|
||||
$j++;
|
||||
}
|
||||
|
||||
if($stateCount[4] >= $maxCount){
|
||||
return null;
|
||||
}
|
||||
|
||||
// If we found a finder-pattern-like section, but its size is significantly different from
|
||||
// the original, assume it's a false positive
|
||||
$stateCountTotal = ($stateCount[0] + $stateCount[1] + $stateCount[2] + $stateCount[3] + $stateCount[4]);
|
||||
|
||||
if((5 * abs($stateCountTotal - $originalStateCountTotal)) >= $originalStateCountTotal){
|
||||
return null;
|
||||
}
|
||||
|
||||
if(!$this->foundPatternCross($stateCount)){
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->centerFromEnd($stateCount, $j);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is called when a horizontal scan finds a possible alignment pattern. It will
|
||||
* cross-check with a vertical scan, and if successful, will, ah, cross-cross-check
|
||||
* with another horizontal scan. This is needed primarily to locate the real horizontal
|
||||
* center of the pattern in cases of extreme skew.
|
||||
* And then we cross-cross-cross check with another diagonal scan.
|
||||
*
|
||||
* If that succeeds the finder pattern location is added to a list that tracks
|
||||
* the number of times each location has been nearly-matched as a finder pattern.
|
||||
* Each additional find is more evidence that the location is in fact a finder
|
||||
* pattern center
|
||||
*
|
||||
* @param int[] $stateCount reading state module counts from horizontal scan
|
||||
* @param int $i row where finder pattern may be found
|
||||
* @param int $j end of possible finder pattern in row
|
||||
*
|
||||
* @return bool if a finder pattern candidate was found this time
|
||||
*/
|
||||
private function handlePossibleCenter(array $stateCount, int $i, int $j):bool{
|
||||
$stateCountTotal = ($stateCount[0] + $stateCount[1] + $stateCount[2] + $stateCount[3] + $stateCount[4]);
|
||||
$centerJ = $this->centerFromEnd($stateCount, $j);
|
||||
$centerI = $this->crossCheckVertical($i, (int)$centerJ, $stateCount[2], $stateCountTotal);
|
||||
|
||||
if($centerI !== null){
|
||||
// Re-cross check
|
||||
$centerJ = $this->crossCheckHorizontal((int)$centerJ, (int)$centerI, $stateCount[2], $stateCountTotal);
|
||||
if($centerJ !== null && ($this->crossCheckDiagonal((int)$centerI, (int)$centerJ))){
|
||||
$estimatedModuleSize = ($stateCountTotal / 7.0);
|
||||
$found = false;
|
||||
|
||||
// cautious (was in for fool in which $this->possibleCenters is updated)
|
||||
$count = count($this->possibleCenters);
|
||||
|
||||
for($index = 0; $index < $count; $index++){
|
||||
$center = $this->possibleCenters[$index];
|
||||
// Look for about the same center and module size:
|
||||
if($center->aboutEquals($estimatedModuleSize, $centerI, $centerJ)){
|
||||
$this->possibleCenters[$index] = $center->combineEstimate($centerI, $centerJ, $estimatedModuleSize);
|
||||
$found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!$found){
|
||||
$point = new FinderPattern($centerJ, $centerI, $estimatedModuleSize);
|
||||
$this->possibleCenters[] = $point;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int number of rows we could safely skip during scanning, based on the first
|
||||
* two finder patterns that have been located. In some cases their position will
|
||||
* allow us to infer that the third pattern must lie below a certain point farther
|
||||
* down in the image.
|
||||
*/
|
||||
private function findRowSkip():int{
|
||||
$max = count($this->possibleCenters);
|
||||
|
||||
if($max <= 1){
|
||||
return 0;
|
||||
}
|
||||
|
||||
$firstConfirmedCenter = null;
|
||||
|
||||
foreach($this->possibleCenters as $center){
|
||||
|
||||
if($center->getCount() >= self::CENTER_QUORUM){
|
||||
|
||||
if($firstConfirmedCenter === null){
|
||||
$firstConfirmedCenter = $center;
|
||||
}
|
||||
else{
|
||||
// We have two confirmed centers
|
||||
// How far down can we skip before resuming looking for the next
|
||||
// pattern? In the worst case, only the difference between the
|
||||
// difference in the x / y coordinates of the two centers.
|
||||
// This is the case where you find top left last.
|
||||
$this->hasSkipped = true;
|
||||
|
||||
return (int)((abs($firstConfirmedCenter->getX() - $center->getX()) -
|
||||
abs($firstConfirmedCenter->getY() - $center->getY())) / 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool true if we have found at least 3 finder patterns that have been detected
|
||||
* at least #CENTER_QUORUM times each, and, the estimated module size of the
|
||||
* candidates is "pretty similar"
|
||||
*/
|
||||
private function haveMultiplyConfirmedCenters():bool{
|
||||
$confirmedCount = 0;
|
||||
$totalModuleSize = 0.0;
|
||||
$max = count($this->possibleCenters);
|
||||
|
||||
foreach($this->possibleCenters as $pattern){
|
||||
if($pattern->getCount() >= self::CENTER_QUORUM){
|
||||
$confirmedCount++;
|
||||
$totalModuleSize += $pattern->getEstimatedModuleSize();
|
||||
}
|
||||
}
|
||||
|
||||
if($confirmedCount < 3){
|
||||
return false;
|
||||
}
|
||||
// OK, we have at least 3 confirmed centers, but, it's possible that one is a "false positive"
|
||||
// and that we need to keep looking. We detect this by asking if the estimated module sizes
|
||||
// vary too much. We arbitrarily say that when the total deviation from average exceeds
|
||||
// 5% of the total module size estimates, it's too much.
|
||||
$average = ($totalModuleSize / (float)$max);
|
||||
$totalDeviation = 0.0;
|
||||
|
||||
foreach($this->possibleCenters as $pattern){
|
||||
$totalDeviation += abs($pattern->getEstimatedModuleSize() - $average);
|
||||
}
|
||||
|
||||
return $totalDeviation <= (0.05 * $totalModuleSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \chillerlan\QRCode\Detector\FinderPattern[] the 3 best FinderPatterns from our list of candidates. The "best" are
|
||||
* those that have been detected at least #CENTER_QUORUM times, and whose module
|
||||
* size differs from the average among those patterns the least
|
||||
* @throws \chillerlan\QRCode\Detector\QRCodeDetectorException if 3 such finder patterns do not exist
|
||||
*/
|
||||
private function selectBestPatterns():array{
|
||||
$startSize = count($this->possibleCenters);
|
||||
|
||||
if($startSize < 3){
|
||||
throw new QRCodeDetectorException('could not find enough finder patterns');
|
||||
}
|
||||
|
||||
usort(
|
||||
$this->possibleCenters,
|
||||
fn(FinderPattern $a, FinderPattern $b) => ($a->getEstimatedModuleSize() <=> $b->getEstimatedModuleSize())
|
||||
);
|
||||
|
||||
$distortion = PHP_FLOAT_MAX;
|
||||
$bestPatterns = [];
|
||||
|
||||
for($i = 0; $i < ($startSize - 2); $i++){
|
||||
$fpi = $this->possibleCenters[$i];
|
||||
$minModuleSize = $fpi->getEstimatedModuleSize();
|
||||
|
||||
for($j = ($i + 1); $j < ($startSize - 1); $j++){
|
||||
$fpj = $this->possibleCenters[$j];
|
||||
$squares0 = $fpi->getSquaredDistance($fpj);
|
||||
|
||||
for($k = ($j + 1); $k < $startSize; $k++){
|
||||
$fpk = $this->possibleCenters[$k];
|
||||
$maxModuleSize = $fpk->getEstimatedModuleSize();
|
||||
|
||||
// module size is not similar
|
||||
if($maxModuleSize > ($minModuleSize * 1.4)){
|
||||
continue;
|
||||
}
|
||||
|
||||
$a = $squares0;
|
||||
$b = $fpj->getSquaredDistance($fpk);
|
||||
$c = $fpi->getSquaredDistance($fpk);
|
||||
|
||||
// sorts ascending - inlined
|
||||
if($a < $b){
|
||||
if($b > $c){
|
||||
if($a < $c){
|
||||
$temp = $b;
|
||||
$b = $c;
|
||||
$c = $temp;
|
||||
}
|
||||
else{
|
||||
$temp = $a;
|
||||
$a = $c;
|
||||
$c = $b;
|
||||
$b = $temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
else{
|
||||
if($b < $c){
|
||||
if($a < $c){
|
||||
$temp = $a;
|
||||
$a = $b;
|
||||
$b = $temp;
|
||||
}
|
||||
else{
|
||||
$temp = $a;
|
||||
$a = $b;
|
||||
$b = $c;
|
||||
$c = $temp;
|
||||
}
|
||||
}
|
||||
else{
|
||||
$temp = $a;
|
||||
$a = $c;
|
||||
$c = $temp;
|
||||
}
|
||||
}
|
||||
|
||||
// a^2 + b^2 = c^2 (Pythagorean theorem), and a = b (isosceles triangle).
|
||||
// Since any right triangle satisfies the formula c^2 - b^2 - a^2 = 0,
|
||||
// we need to check both two equal sides separately.
|
||||
// The value of |c^2 - 2 * b^2| + |c^2 - 2 * a^2| increases as dissimilarity
|
||||
// from isosceles right triangle.
|
||||
$d = (abs($c - 2 * $b) + abs($c - 2 * $a));
|
||||
|
||||
if($d < $distortion){
|
||||
$distortion = $d;
|
||||
$bestPatterns = [$fpi, $fpj, $fpk];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if($distortion === PHP_FLOAT_MAX){
|
||||
throw new QRCodeDetectorException('finder patterns may be too distorted');
|
||||
}
|
||||
|
||||
return $bestPatterns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Orders an array of three ResultPoints in an order [A,B,C] such that AB is less than AC
|
||||
* and BC is less than AC, and the angle between BC and BA is less than 180 degrees.
|
||||
*
|
||||
* @param \chillerlan\QRCode\Detector\FinderPattern[] $patterns array of three FinderPattern to order
|
||||
*
|
||||
* @return \chillerlan\QRCode\Detector\FinderPattern[]
|
||||
*/
|
||||
private function orderBestPatterns(array $patterns):array{
|
||||
|
||||
// Find distances between pattern centers
|
||||
$zeroOneDistance = $patterns[0]->getDistance($patterns[1]);
|
||||
$oneTwoDistance = $patterns[1]->getDistance($patterns[2]);
|
||||
$zeroTwoDistance = $patterns[0]->getDistance($patterns[2]);
|
||||
|
||||
// Assume one closest to other two is B; A and C will just be guesses at first
|
||||
if($oneTwoDistance >= $zeroOneDistance && $oneTwoDistance >= $zeroTwoDistance){
|
||||
[$pointB, $pointA, $pointC] = $patterns;
|
||||
}
|
||||
elseif($zeroTwoDistance >= $oneTwoDistance && $zeroTwoDistance >= $zeroOneDistance){
|
||||
[$pointA, $pointB, $pointC] = $patterns;
|
||||
}
|
||||
else{
|
||||
[$pointA, $pointC, $pointB] = $patterns;
|
||||
}
|
||||
|
||||
// Use cross product to figure out whether A and C are correct or flipped.
|
||||
// This asks whether BC x BA has a positive z component, which is the arrangement
|
||||
// we want for A, B, C. If it's negative, then we've got it flipped around and
|
||||
// should swap A and C.
|
||||
if($this->crossProductZ($pointA, $pointB, $pointC) < 0.0){
|
||||
$temp = $pointA;
|
||||
$pointA = $pointC;
|
||||
$pointC = $temp;
|
||||
}
|
||||
|
||||
return [$pointA, $pointB, $pointC];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the z component of the cross product between vectors BC and BA.
|
||||
*/
|
||||
private function crossProductZ(FinderPattern $pointA, FinderPattern $pointB, FinderPattern $pointC):float{
|
||||
$bX = $pointB->getX();
|
||||
$bY = $pointB->getY();
|
||||
|
||||
return ((($pointC->getX() - $bX) * ($pointA->getY() - $bY)) - (($pointC->getY() - $bY) * ($pointA->getX() - $bX)));
|
||||
}
|
||||
|
||||
}
|
||||
181
vendor/chillerlan/php-qrcode/src/Detector/GridSampler.php
vendored
Normal file
181
vendor/chillerlan/php-qrcode/src/Detector/GridSampler.php
vendored
Normal file
@@ -0,0 +1,181 @@
|
||||
<?php
|
||||
/**
|
||||
* Class GridSampler
|
||||
*
|
||||
* @created 17.01.2021
|
||||
* @author ZXing Authors
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @copyright 2021 Smiley
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
namespace chillerlan\QRCode\Detector;
|
||||
|
||||
use chillerlan\QRCode\Data\QRMatrix;
|
||||
use chillerlan\QRCode\Decoder\BitMatrix;
|
||||
use function array_fill, count, intdiv, sprintf;
|
||||
|
||||
/**
|
||||
* 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 #setGridSampler(GridSampler)
|
||||
* with an instance of a class which implements this interface.
|
||||
*
|
||||
* @author Sean Owen
|
||||
*/
|
||||
final class GridSampler{
|
||||
|
||||
private array $points;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @param int $dimension matrix width/height
|
||||
*
|
||||
* @throws \chillerlan\QRCode\Detector\QRCodeDetectorException if an endpoint is lies outside the image boundaries
|
||||
*/
|
||||
private function checkAndNudgePoints(int $dimension):void{
|
||||
$nudged = true;
|
||||
$max = count($this->points);
|
||||
|
||||
// Check and nudge points from start until we see some that are OK:
|
||||
for($offset = 0; $offset < $max && $nudged; $offset += 2){
|
||||
$x = (int)$this->points[$offset];
|
||||
$y = (int)$this->points[($offset + 1)];
|
||||
|
||||
if($x < -1 || $x > $dimension || $y < -1 || $y > $dimension){
|
||||
throw new QRCodeDetectorException(sprintf('checkAndNudgePoints 1, x: %s, y: %s, d: %s', $x, $y, $dimension));
|
||||
}
|
||||
|
||||
$nudged = false;
|
||||
|
||||
if($x === -1){
|
||||
$this->points[$offset] = 0.0;
|
||||
$nudged = true;
|
||||
}
|
||||
elseif($x === $dimension){
|
||||
$this->points[$offset] = ($dimension - 1);
|
||||
$nudged = true;
|
||||
}
|
||||
|
||||
if($y === -1){
|
||||
$this->points[($offset + 1)] = 0.0;
|
||||
$nudged = true;
|
||||
}
|
||||
elseif($y === $dimension){
|
||||
$this->points[($offset + 1)] = ($dimension - 1);
|
||||
$nudged = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Check and nudge points from end:
|
||||
$nudged = true;
|
||||
|
||||
for($offset = ($max - 2); $offset >= 0 && $nudged; $offset -= 2){
|
||||
$x = (int)$this->points[$offset];
|
||||
$y = (int)$this->points[($offset + 1)];
|
||||
|
||||
if($x < -1 || $x > $dimension || $y < -1 || $y > $dimension){
|
||||
throw new QRCodeDetectorException(sprintf('checkAndNudgePoints 2, x: %s, y: %s, d: %s', $x, $y, $dimension));
|
||||
}
|
||||
|
||||
$nudged = false;
|
||||
|
||||
if($x === -1){
|
||||
$this->points[$offset] = 0.0;
|
||||
$nudged = true;
|
||||
}
|
||||
elseif($x === $dimension){
|
||||
$this->points[$offset] = ($dimension - 1);
|
||||
$nudged = true;
|
||||
}
|
||||
|
||||
if($y === -1){
|
||||
$this->points[($offset + 1)] = 0.0;
|
||||
$nudged = true;
|
||||
}
|
||||
elseif($y === $dimension){
|
||||
$this->points[($offset + 1)] = ($dimension - 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.
|
||||
*
|
||||
* @return \chillerlan\QRCode\Decoder\BitMatrix representing a grid of points sampled from the image within a region
|
||||
* defined by the "from" parameters
|
||||
* @throws \chillerlan\QRCode\Detector\QRCodeDetectorException 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
|
||||
*/
|
||||
public function sampleGrid(BitMatrix $matrix, int $dimension, PerspectiveTransform $transform):BitMatrix{
|
||||
|
||||
if($dimension <= 0){
|
||||
throw new QRCodeDetectorException('invalid matrix size');
|
||||
}
|
||||
|
||||
$bits = new BitMatrix($dimension);
|
||||
$this->points = array_fill(0, (2 * $dimension), 0.0);
|
||||
|
||||
for($y = 0; $y < $dimension; $y++){
|
||||
$max = count($this->points);
|
||||
$iValue = ($y + 0.5);
|
||||
|
||||
for($x = 0; $x < $max; $x += 2){
|
||||
$this->points[$x] = (($x / 2) + 0.5);
|
||||
$this->points[($x + 1)] = $iValue;
|
||||
}
|
||||
// phpcs:ignore
|
||||
[$this->points, ] = $transform->transformPoints($this->points);
|
||||
// Quick check to see if points transformed to something inside the image;
|
||||
// sufficient to check the endpoints
|
||||
$this->checkAndNudgePoints($matrix->getSize());
|
||||
|
||||
// no need to try/catch as QRMatrix::set() will silently discard out of bounds values
|
||||
# try{
|
||||
for($x = 0; $x < $max; $x += 2){
|
||||
// Black(-ish) pixel
|
||||
$bits->set(
|
||||
intdiv($x, 2),
|
||||
$y,
|
||||
$matrix->check((int)$this->points[$x], (int)$this->points[($x + 1)]),
|
||||
QRMatrix::M_DATA
|
||||
);
|
||||
}
|
||||
# }
|
||||
# catch(\Throwable $aioobe){//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 new QRCodeDetectorException('ArrayIndexOutOfBoundsException');
|
||||
# }
|
||||
|
||||
}
|
||||
|
||||
return $bits;
|
||||
}
|
||||
|
||||
}
|
||||
182
vendor/chillerlan/php-qrcode/src/Detector/PerspectiveTransform.php
vendored
Normal file
182
vendor/chillerlan/php-qrcode/src/Detector/PerspectiveTransform.php
vendored
Normal file
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
/**
|
||||
* Class PerspectiveTransform
|
||||
*
|
||||
* @created 17.01.2021
|
||||
* @author ZXing Authors
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @copyright 2021 Smiley
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
namespace chillerlan\QRCode\Detector;
|
||||
|
||||
use function count;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @author Sean Owen
|
||||
*/
|
||||
final class PerspectiveTransform{
|
||||
|
||||
private float $a11;
|
||||
private float $a12;
|
||||
private float $a13;
|
||||
private float $a21;
|
||||
private float $a22;
|
||||
private float $a23;
|
||||
private float $a31;
|
||||
private float $a32;
|
||||
private float $a33;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private function set(
|
||||
float $a11, float $a21, float $a31,
|
||||
float $a12, float $a22, float $a32,
|
||||
float $a13, float $a23, float $a33
|
||||
):self{
|
||||
$this->a11 = $a11;
|
||||
$this->a12 = $a12;
|
||||
$this->a13 = $a13;
|
||||
$this->a21 = $a21;
|
||||
$this->a22 = $a22;
|
||||
$this->a23 = $a23;
|
||||
$this->a31 = $a31;
|
||||
$this->a32 = $a32;
|
||||
$this->a33 = $a33;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
|
||||
*/
|
||||
public function quadrilateralToQuadrilateral(
|
||||
float $x0, float $y0, float $x1, float $y1, float $x2, float $y2, float $x3, float $y3,
|
||||
float $x0p, float $y0p, float $x1p, float $y1p, float $x2p, float $y2p, float $x3p, float $y3p
|
||||
):self{
|
||||
return (new self)
|
||||
->squareToQuadrilateral($x0p, $y0p, $x1p, $y1p, $x2p, $y2p, $x3p, $y3p)
|
||||
->times($this->quadrilateralToSquare($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private function quadrilateralToSquare(
|
||||
float $x0, float $y0, float $x1, float $y1,
|
||||
float $x2, float $y2, float $x3, float $y3
|
||||
):self{
|
||||
// Here, the adjoint serves as the inverse:
|
||||
return $this
|
||||
->squareToQuadrilateral($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3)
|
||||
->buildAdjoint();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private function buildAdjoint():self{
|
||||
// Adjoint is the transpose of the cofactor matrix:
|
||||
return $this->set(
|
||||
($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)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private function squareToQuadrilateral(
|
||||
float $x0, float $y0, float $x1, float $y1,
|
||||
float $x2, float $y2, float $x3, float $y3
|
||||
):self{
|
||||
$dx3 = ($x0 - $x1 + $x2 - $x3);
|
||||
$dy3 = ($y0 - $y1 + $y2 - $y3);
|
||||
|
||||
if($dx3 === 0.0 && $dy3 === 0.0){
|
||||
// Affine
|
||||
return $this->set(($x1 - $x0), ($x2 - $x1), $x0, ($y1 - $y0), ($y2 - $y1), $y0, 0.0, 0.0, 1.0);
|
||||
}
|
||||
|
||||
$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 $this->set(
|
||||
($x1 - $x0 + $a13 * $x1),
|
||||
($x3 - $x0 + $a23 * $x3),
|
||||
$x0,
|
||||
($y1 - $y0 + $a13 * $y1),
|
||||
($y3 - $y0 + $a23 * $y3),
|
||||
$y0,
|
||||
$a13,
|
||||
$a23,
|
||||
1.0
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private function times(PerspectiveTransform $other):self{
|
||||
return $this->set(
|
||||
($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)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array[] [$xValues, $yValues]
|
||||
*/
|
||||
public function transformPoints(array $xValues, array $yValues = null):array{
|
||||
$max = count($xValues);
|
||||
|
||||
if($yValues !== null){ // unused
|
||||
|
||||
for($i = 0; $i < $max; $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);
|
||||
}
|
||||
|
||||
return [$xValues, $yValues];
|
||||
}
|
||||
|
||||
for($i = 0; $i < $max; $i += 2){
|
||||
$x = $xValues[$i];
|
||||
$y = $xValues[($i + 1)];
|
||||
$denominator = ($this->a13 * $x + $this->a23 * $y + $this->a33);
|
||||
$xValues[$i] = (($this->a11 * $x + $this->a21 * $y + $this->a31) / $denominator);
|
||||
$xValues[($i + 1)] = (($this->a12 * $x + $this->a22 * $y + $this->a32) / $denominator);
|
||||
}
|
||||
|
||||
return [$xValues, []];
|
||||
}
|
||||
|
||||
}
|
||||
20
vendor/chillerlan/php-qrcode/src/Detector/QRCodeDetectorException.php
vendored
Normal file
20
vendor/chillerlan/php-qrcode/src/Detector/QRCodeDetectorException.php
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
/**
|
||||
* Class QRCodeDetectorException
|
||||
*
|
||||
* @created 01.12.2021
|
||||
* @author smiley <smiley@chillerlan.net>
|
||||
* @copyright 2021 smiley
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace chillerlan\QRCode\Detector;
|
||||
|
||||
use chillerlan\QRCode\QRCodeException;
|
||||
|
||||
/**
|
||||
* An exception container
|
||||
*/
|
||||
final class QRCodeDetectorException extends QRCodeException{
|
||||
|
||||
}
|
||||
73
vendor/chillerlan/php-qrcode/src/Detector/ResultPoint.php
vendored
Normal file
73
vendor/chillerlan/php-qrcode/src/Detector/ResultPoint.php
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
/**
|
||||
* Class ResultPoint
|
||||
*
|
||||
* @created 17.01.2021
|
||||
* @author ZXing Authors
|
||||
* @author Smiley <smiley@chillerlan.net>
|
||||
* @copyright 2021 Smiley
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
namespace chillerlan\QRCode\Detector;
|
||||
|
||||
use function abs;
|
||||
|
||||
/**
|
||||
* Encapsulates a point of interest in an image containing a barcode. Typically, this
|
||||
* would be the location of a finder pattern or the corner of the barcode, for example.
|
||||
*
|
||||
* @author Sean Owen
|
||||
*/
|
||||
abstract class ResultPoint{
|
||||
|
||||
protected float $x;
|
||||
protected float $y;
|
||||
protected float $estimatedModuleSize;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function __construct(float $x, float $y, float $estimatedModuleSize){
|
||||
$this->x = $x;
|
||||
$this->y = $y;
|
||||
$this->estimatedModuleSize = $estimatedModuleSize;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function getX():float{
|
||||
return $this->x;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function getY():float{
|
||||
return $this->y;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function getEstimatedModuleSize():float{
|
||||
return $this->estimatedModuleSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if this finder pattern "about equals" a finder pattern at the stated
|
||||
* position and size -- meaning, it is at nearly the same center with nearly the same size.
|
||||
*/
|
||||
public function aboutEquals(float $moduleSize, float $i, float $j):bool{
|
||||
|
||||
if(abs($i - $this->y) <= $moduleSize && abs($j - $this->x) <= $moduleSize){
|
||||
$moduleSizeDiff = abs($moduleSize - $this->estimatedModuleSize);
|
||||
|
||||
return $moduleSizeDiff <= 1.0 || $moduleSizeDiff <= $this->estimatedModuleSize;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user