|
|
<?php |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
namespace Zxing\Qrcode\Detector; |
|
|
|
|
|
use Zxing\DecodeHintType; |
|
|
use Zxing\FormatException; |
|
|
use Zxing\NotFoundException; |
|
|
use Zxing\ResultPoint; |
|
|
use Zxing\ResultPointCallback; |
|
|
use Zxing\Common\BitMatrix; |
|
|
use Zxing\Common\DetectorResult; |
|
|
use Zxing\Common\GridSampler; |
|
|
use Zxing\Common\PerspectiveTransform; |
|
|
use Zxing\Common\Detector\MathUtils; |
|
|
use Zxing\Qrcode\Decoder\Version; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Detector |
|
|
{ |
|
|
|
|
|
private $image; |
|
|
private $resultPointCallback; |
|
|
|
|
|
public function __construct($image) |
|
|
{ |
|
|
$this->image = $image; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public final function detect($hints = null) |
|
|
{ |
|
|
|
|
|
$resultPointCallback = $hints == null ? null : |
|
|
$hints->get('NEED_RESULT_POINT_CALLBACK'); |
|
|
|
|
|
|
|
|
$finder = new FinderPatternFinder($this->image, $resultPointCallback); |
|
|
$info = $finder->find($hints); |
|
|
|
|
|
return $this->processFinderPatternInfo($info); |
|
|
} |
|
|
|
|
|
protected final function processFinderPatternInfo($info) |
|
|
{ |
|
|
|
|
|
$topLeft = $info->getTopLeft(); |
|
|
$topRight = $info->getTopRight(); |
|
|
$bottomLeft = $info->getBottomLeft(); |
|
|
|
|
|
$moduleSize = (float)$this->calculateModuleSize($topLeft, $topRight, $bottomLeft); |
|
|
if ($moduleSize < 1.0) { |
|
|
throw NotFoundException::getNotFoundInstance(); |
|
|
} |
|
|
$dimension = (int)self::computeDimension($topLeft, $topRight, $bottomLeft, $moduleSize); |
|
|
$provisionalVersion = \Zxing\Qrcode\Decoder\Version::getProvisionalVersionForDimension($dimension); |
|
|
$modulesBetweenFPCenters = $provisionalVersion->getDimensionForVersion() - 7; |
|
|
|
|
|
$alignmentPattern = null; |
|
|
|
|
|
if (count($provisionalVersion->getAlignmentPatternCenters()) > 0) { |
|
|
|
|
|
|
|
|
$bottomRightX = $topRight->getX() - $topLeft->getX() + $bottomLeft->getX(); |
|
|
$bottomRightY = $topRight->getY() - $topLeft->getY() + $bottomLeft->getY(); |
|
|
|
|
|
|
|
|
|
|
|
$correctionToTopLeft = 1.0 - 3.0 / (float)$modulesBetweenFPCenters; |
|
|
$estAlignmentX = (int)($topLeft->getX() + $correctionToTopLeft * ($bottomRightX - $topLeft->getX())); |
|
|
$estAlignmentY = (int)($topLeft->getY() + $correctionToTopLeft * ($bottomRightY - $topLeft->getY())); |
|
|
|
|
|
|
|
|
for ($i = 4; $i <= 16; $i <<= 1) { |
|
|
try { |
|
|
$alignmentPattern = $this->findAlignmentInRegion( |
|
|
$moduleSize, |
|
|
$estAlignmentX, |
|
|
$estAlignmentY, |
|
|
(float)$i |
|
|
); |
|
|
break; |
|
|
} catch (NotFoundException $re) { |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
$transform = self::createTransform($topLeft, $topRight, $bottomLeft, $alignmentPattern, $dimension); |
|
|
|
|
|
$bits = self::sampleGrid($this->image, $transform, $dimension); |
|
|
|
|
|
$points = []; |
|
|
if ($alignmentPattern == null) { |
|
|
$points = [$bottomLeft, $topLeft, $topRight]; |
|
|
} else { |
|
|
|
|
|
$points = [$bottomLeft, $topLeft, $topRight, $alignmentPattern]; |
|
|
} |
|
|
|
|
|
return new DetectorResult($bits, $points); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
protected final function calculateModuleSize($topLeft, $topRight, $bottomLeft) |
|
|
{ |
|
|
|
|
|
return ($this->calculateModuleSizeOneWay($topLeft, $topRight) + |
|
|
$this->calculateModuleSizeOneWay($topLeft, $bottomLeft)) / 2.0; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private function calculateModuleSizeOneWay($pattern, $otherPattern) |
|
|
{ |
|
|
$moduleSizeEst1 = $this->sizeOfBlackWhiteBlackRunBothWays($pattern->getX(), |
|
|
(int)$pattern->getY(), |
|
|
(int)$otherPattern->getX(), |
|
|
(int)$otherPattern->getY()); |
|
|
$moduleSizeEst2 = $this->sizeOfBlackWhiteBlackRunBothWays((int)$otherPattern->getX(), |
|
|
(int)$otherPattern->getY(), |
|
|
(int)$pattern->getX(), |
|
|
(int)$pattern->getY()); |
|
|
if (is_nan($moduleSizeEst1)) { |
|
|
return $moduleSizeEst2 / 7.0; |
|
|
} |
|
|
if (is_nan($moduleSizeEst2)) { |
|
|
return $moduleSizeEst1 / 7.0; |
|
|
} |
|
|
|
|
|
|
|
|
return ($moduleSizeEst1 + $moduleSizeEst2) / 14.0; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private function sizeOfBlackWhiteBlackRunBothWays($fromX, $fromY, $toX, $toY) |
|
|
{ |
|
|
|
|
|
$result = $this->sizeOfBlackWhiteBlackRun($fromX, $fromY, $toX, $toY); |
|
|
|
|
|
|
|
|
$scale = 1.0; |
|
|
$otherToX = $fromX - ($toX - $fromX); |
|
|
if ($otherToX < 0) { |
|
|
$scale = (float)$fromX / (float)($fromX - $otherToX); |
|
|
$otherToX = 0; |
|
|
} else if ($otherToX >= $this->image->getWidth()) { |
|
|
$scale = (float)($this->image->getWidth() - 1 - $fromX) / (float)($otherToX - $fromX); |
|
|
$otherToX = $this->image->getWidth() - 1; |
|
|
} |
|
|
$otherToY = (int)($fromY - ($toY - $fromY) * $scale); |
|
|
|
|
|
$scale = 1.0; |
|
|
if ($otherToY < 0) { |
|
|
$scale = (float)$fromY / (float)($fromY - $otherToY); |
|
|
$otherToY = 0; |
|
|
} else if ($otherToY >= $this->image->getHeight()) { |
|
|
$scale = (float)($this->image->getHeight() - 1 - $fromY) / (float)($otherToY - $fromY); |
|
|
$otherToY = $this->image->getHeight() - 1; |
|
|
} |
|
|
$otherToX = (int)($fromX + ($otherToX - $fromX) * $scale); |
|
|
|
|
|
$result += $this->sizeOfBlackWhiteBlackRun($fromX, $fromY, $otherToX, $otherToY); |
|
|
|
|
|
|
|
|
return $result - 1.0; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private function sizeOfBlackWhiteBlackRun($fromX, $fromY, $toX, $toY) |
|
|
{ |
|
|
|
|
|
|
|
|
$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; |
|
|
|
|
|
|
|
|
$state = 0; |
|
|
|
|
|
$xLimit = $toX + $xstep; |
|
|
for ($x = $fromX, $y = $fromY; $x != $xLimit; $x += $xstep) { |
|
|
$realX = $steep ? $y : $x; |
|
|
$realY = $steep ? $x : $y; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (($state == 1) == $this->image->get($realX, $realY)) { |
|
|
if ($state == 2) { |
|
|
return MathUtils::distance($x, $y, $fromX, $fromY); |
|
|
} |
|
|
$state++; |
|
|
} |
|
|
|
|
|
$error += $dy; |
|
|
if ($error > 0) { |
|
|
if ($y == $toY) { |
|
|
break; |
|
|
} |
|
|
$y += $ystep; |
|
|
$error -= $dx; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if ($state == 2) { |
|
|
return MathUtils::distance($toX + $xstep, $toY, $fromX, $fromY); |
|
|
} |
|
|
|
|
|
|
|
|
return NAN; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static function computeDimension($topLeft, |
|
|
$topRight, |
|
|
$bottomLeft, |
|
|
$moduleSize) |
|
|
{ |
|
|
$tltrCentersDimension = MathUtils::round(ResultPoint::distance($topLeft, $topRight) / $moduleSize); |
|
|
$tlblCentersDimension = MathUtils::round(ResultPoint::distance($topLeft, $bottomLeft) / $moduleSize); |
|
|
$dimension = (($tltrCentersDimension + $tlblCentersDimension) / 2) + 7; |
|
|
switch ($dimension & 0x03) { |
|
|
case 0: |
|
|
$dimension++; |
|
|
break; |
|
|
|
|
|
case 2: |
|
|
$dimension--; |
|
|
break; |
|
|
case 3: |
|
|
throw NotFoundException::getNotFoundInstance(); |
|
|
} |
|
|
|
|
|
return $dimension; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
protected final function findAlignmentInRegion($overallEstModuleSize, |
|
|
$estAlignmentX, |
|
|
$estAlignmentY, |
|
|
$allowanceFactor) |
|
|
{ |
|
|
|
|
|
|
|
|
$allowance = (int)($allowanceFactor * $overallEstModuleSize); |
|
|
$alignmentAreaLeftX = max(0, $estAlignmentX - $allowance); |
|
|
$alignmentAreaRightX = min($this->image->getWidth() - 1, $estAlignmentX + $allowance); |
|
|
if ($alignmentAreaRightX - $alignmentAreaLeftX < $overallEstModuleSize * 3) { |
|
|
throw NotFoundException::getNotFoundInstance(); |
|
|
} |
|
|
|
|
|
$alignmentAreaTopY = max(0, $estAlignmentY - $allowance); |
|
|
$alignmentAreaBottomY = min($this->image->getHeight() - 1, $estAlignmentY + $allowance); |
|
|
if ($alignmentAreaBottomY - $alignmentAreaTopY < $overallEstModuleSize * 3) { |
|
|
throw NotFoundException::getNotFoundInstance(); |
|
|
} |
|
|
|
|
|
$alignmentFinder = |
|
|
new AlignmentPatternFinder( |
|
|
$this->image, |
|
|
$alignmentAreaLeftX, |
|
|
$alignmentAreaTopY, |
|
|
$alignmentAreaRightX - $alignmentAreaLeftX, |
|
|
$alignmentAreaBottomY - $alignmentAreaTopY, |
|
|
$overallEstModuleSize, |
|
|
$this->resultPointCallback); |
|
|
|
|
|
return $alignmentFinder->find(); |
|
|
} |
|
|
|
|
|
private static function createTransform($topLeft, |
|
|
$topRight, |
|
|
$bottomLeft, |
|
|
$alignmentPattern, |
|
|
$dimension) |
|
|
{ |
|
|
$dimMinusThree = (float)$dimension - 3.5; |
|
|
$bottomRightX = 0.0; |
|
|
$bottomRightY = 0.0; |
|
|
$sourceBottomRightX = 0.0; |
|
|
$sourceBottomRightY = 0.0; |
|
|
if ($alignmentPattern != null) { |
|
|
$bottomRightX = $alignmentPattern->getX(); |
|
|
$bottomRightY = $alignmentPattern->getY(); |
|
|
$sourceBottomRightX = $dimMinusThree - 3.0; |
|
|
$sourceBottomRightY = $sourceBottomRightX; |
|
|
} else { |
|
|
|
|
|
$bottomRightX = ($topRight->getX() - $topLeft->getX()) + $bottomLeft->getX(); |
|
|
$bottomRightY = ($topRight->getY() - $topLeft->getY()) + $bottomLeft->getY(); |
|
|
$sourceBottomRightX = $dimMinusThree; |
|
|
$sourceBottomRightY = $dimMinusThree; |
|
|
} |
|
|
|
|
|
return PerspectiveTransform::quadrilateralToQuadrilateral( |
|
|
3.5, |
|
|
3.5, |
|
|
$dimMinusThree, |
|
|
3.5, |
|
|
$sourceBottomRightX, |
|
|
$sourceBottomRightY, |
|
|
3.5, |
|
|
$dimMinusThree, |
|
|
$topLeft->getX(), |
|
|
$topLeft->getY(), |
|
|
$topRight->getX(), |
|
|
$topRight->getY(), |
|
|
$bottomRightX, |
|
|
$bottomRightY, |
|
|
$bottomLeft->getX(), |
|
|
$bottomLeft->getY()); |
|
|
} |
|
|
|
|
|
private static function sampleGrid($image, $transform, |
|
|
$dimension) |
|
|
{ |
|
|
$sampler = GridSampler::getInstance(); |
|
|
|
|
|
return $sampler->sampleGrid_($image, $dimension, $dimension, $transform); |
|
|
} |
|
|
|
|
|
protected final function getImage() |
|
|
{ |
|
|
return $this->image; |
|
|
} |
|
|
|
|
|
protected final function getResultPointCallback() |
|
|
{ |
|
|
return $this->resultPointCallback; |
|
|
} |
|
|
} |
|
|
|