| <?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; |
| } |
| } |
|
|