|
|
<?php |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
namespace Zxing\Qrcode\Detector; |
|
|
|
|
|
use Zxing\NotFoundException; |
|
|
use Zxing\ResultPointCallback; |
|
|
use Zxing\Common\BitMatrix; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
final class AlignmentPatternFinder |
|
|
{ |
|
|
private $image; |
|
|
private $possibleCenters; |
|
|
private $startX; |
|
|
private $startY; |
|
|
private $width; |
|
|
private $height; |
|
|
private $moduleSize; |
|
|
private $crossCheckStateCount; |
|
|
private $resultPointCallback; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public function __construct($image, |
|
|
$startX, |
|
|
$startY, |
|
|
$width, |
|
|
$height, |
|
|
$moduleSize, |
|
|
$resultPointCallback) |
|
|
{ |
|
|
$this->image = $image; |
|
|
$this->possibleCenters = []; |
|
|
$this->startX = $startX; |
|
|
$this->startY = $startY; |
|
|
$this->width = $width; |
|
|
$this->height = $height; |
|
|
$this->moduleSize = $moduleSize; |
|
|
$this->crossCheckStateCount = []; |
|
|
$this->resultPointCallback = $resultPointCallback; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public function find() |
|
|
{ |
|
|
$startX = $this->startX; |
|
|
$height = $this->height; |
|
|
$maxJ = $startX + $this->width; |
|
|
$middleI = $this->startY + ($height / 2); |
|
|
|
|
|
|
|
|
$stateCount = []; |
|
|
for ($iGen = 0; $iGen < $height; $iGen++) { |
|
|
|
|
|
$i = $middleI + (($iGen & 0x01) == 0 ? ($iGen + 1) / 2 : -(($iGen + 1) / 2)); |
|
|
$i = (int)($i); |
|
|
$stateCount[0] = 0; |
|
|
$stateCount[1] = 0; |
|
|
$stateCount[2] = 0; |
|
|
$j = $startX; |
|
|
|
|
|
|
|
|
|
|
|
while ($j < $maxJ && !$this->image->get($j, $i)) { |
|
|
$j++; |
|
|
} |
|
|
$currentState = 0; |
|
|
while ($j < $maxJ) { |
|
|
if ($this->image->get($j, $i)) { |
|
|
|
|
|
if ($currentState == 1) { |
|
|
$stateCount[$currentState]++; |
|
|
} else { |
|
|
if ($currentState == 2) { |
|
|
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]++; |
|
|
} |
|
|
} |
|
|
} else { |
|
|
if ($currentState == 1) { |
|
|
$currentState++; |
|
|
} |
|
|
$stateCount[$currentState]++; |
|
|
} |
|
|
$j++; |
|
|
} |
|
|
if ($this->foundPatternCross($stateCount)) { |
|
|
$confirmed = $this->handlePossibleCenter($stateCount, $i, $maxJ); |
|
|
if ($confirmed != null) { |
|
|
return $confirmed; |
|
|
} |
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (count($this->possibleCenters)) { |
|
|
return $this->possibleCenters[0]; |
|
|
} |
|
|
|
|
|
throw NotFoundException::getNotFoundInstance(); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private function foundPatternCross($stateCount) |
|
|
{ |
|
|
$moduleSize = $this->moduleSize; |
|
|
$maxVariance = $moduleSize / 2.0; |
|
|
for ($i = 0; $i < 3; $i++) { |
|
|
if (abs($moduleSize - $stateCount[$i]) >= $maxVariance) { |
|
|
return false; |
|
|
} |
|
|
} |
|
|
|
|
|
return true; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private function handlePossibleCenter($stateCount, $i, $j) |
|
|
{ |
|
|
$stateCountTotal = $stateCount[0] + $stateCount[1] + $stateCount[2]; |
|
|
$centerJ = $this->centerFromEnd($stateCount, $j); |
|
|
$centerI = $this->crossCheckVertical($i, (int)$centerJ, 2 * $stateCount[1], $stateCountTotal); |
|
|
if (!is_nan($centerI)) { |
|
|
$estimatedModuleSize = (float)($stateCount[0] + $stateCount[1] + $stateCount[2]) / 3.0; |
|
|
foreach ($this->possibleCenters as $center) { |
|
|
|
|
|
if ($center->aboutEquals($estimatedModuleSize, $centerI, $centerJ)) { |
|
|
return $center->combineEstimate($centerI, $centerJ, $estimatedModuleSize); |
|
|
} |
|
|
} |
|
|
|
|
|
$point = new AlignmentPattern($centerJ, $centerI, $estimatedModuleSize); |
|
|
$this->possibleCenters[] = $point; |
|
|
if ($this->resultPointCallback != null) { |
|
|
$this->resultPointCallback->foundPossibleResultPoint($point); |
|
|
} |
|
|
} |
|
|
|
|
|
return null; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static function centerFromEnd($stateCount, $end) |
|
|
{ |
|
|
return (float)($end - $stateCount[2]) - $stateCount[1] / 2.0; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private function crossCheckVertical($startI, $centerJ, $maxCount, |
|
|
$originalStateCountTotal) |
|
|
{ |
|
|
$image = $this->image; |
|
|
|
|
|
$maxI = $image->getHeight(); |
|
|
$stateCount = $this->crossCheckStateCount; |
|
|
$stateCount[0] = 0; |
|
|
$stateCount[1] = 0; |
|
|
$stateCount[2] = 0; |
|
|
|
|
|
|
|
|
$i = $startI; |
|
|
while ($i >= 0 && $image->get($centerJ, $i) && $stateCount[1] <= $maxCount) { |
|
|
$stateCount[1]++; |
|
|
$i--; |
|
|
} |
|
|
|
|
|
if ($i < 0 || $stateCount[1] > $maxCount) { |
|
|
return NAN; |
|
|
} |
|
|
while ($i >= 0 && !$image->get($centerJ, $i) && $stateCount[0] <= $maxCount) { |
|
|
$stateCount[0]++; |
|
|
$i--; |
|
|
} |
|
|
if ($stateCount[0] > $maxCount) { |
|
|
return NAN; |
|
|
} |
|
|
|
|
|
|
|
|
$i = $startI + 1; |
|
|
while ($i < $maxI && $image->get($centerJ, $i) && $stateCount[1] <= $maxCount) { |
|
|
$stateCount[1]++; |
|
|
$i++; |
|
|
} |
|
|
if ($i == $maxI || $stateCount[1] > $maxCount) { |
|
|
return NAN; |
|
|
} |
|
|
while ($i < $maxI && !$image->get($centerJ, $i) && $stateCount[2] <= $maxCount) { |
|
|
$stateCount[2]++; |
|
|
$i++; |
|
|
} |
|
|
if ($stateCount[2] > $maxCount) { |
|
|
return NAN; |
|
|
} |
|
|
|
|
|
$stateCountTotal = $stateCount[0] + $stateCount[1] + $stateCount[2]; |
|
|
if (5 * abs($stateCountTotal - $originalStateCountTotal) >= 2 * $originalStateCountTotal) { |
|
|
return NAN; |
|
|
} |
|
|
|
|
|
return $this->foundPatternCross($stateCount) ? $this->centerFromEnd($stateCount, $i) : NAN; |
|
|
} |
|
|
} |
|
|
|