| <?php |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| namespace Zxing\Qrcode\Detector; |
|
|
| use Zxing\BinaryBitmap; |
| use Zxing\Common\BitMatrix; |
| use Zxing\NotFoundException; |
| use Zxing\ResultPoint; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| class FinderPatternFinder |
| { |
| protected static $MIN_SKIP = 3; |
| protected static $MAX_MODULES = 57; |
| private static $CENTER_QUORUM = 2; |
| private $image; |
| private $average; |
| private $possibleCenters; |
| private $hasSkipped = false; |
| private $crossCheckStateCount; |
| private $resultPointCallback; |
|
|
| |
| |
| |
| |
| |
| public function __construct($image, $resultPointCallback = null) |
| { |
| $this->image = $image; |
|
|
|
|
| $this->possibleCenters = []; |
| $this->crossCheckStateCount = fill_array(0, 5, 0); |
| $this->resultPointCallback = $resultPointCallback; |
| } |
|
|
| final public function find($hints) |
| { |
| $tryHarder = true; |
| $pureBarcode = $hints != null && $hints['PURE_BARCODE']; |
| $maxI = $this->image->getHeight(); |
| $maxJ = $this->image->getWidth(); |
| |
| |
|
|
| |
| |
| |
| |
| $iSkip = (int)((3 * $maxI) / (4 * self::$MAX_MODULES)); |
| if ($iSkip < self::$MIN_SKIP || $tryHarder) { |
| $iSkip = self::$MIN_SKIP; |
| } |
|
|
| $done = false; |
| $stateCount = []; |
| for ($i = $iSkip - 1; $i < $maxI && !$done; $i += $iSkip) { |
| |
| $stateCount[0] = 0; |
| $stateCount[1] = 0; |
| $stateCount[2] = 0; |
| $stateCount[3] = 0; |
| $stateCount[4] = 0; |
| $currentState = 0; |
| for ($j = 0; $j < $maxJ; $j++) { |
| if ($this->image->get($j, $i)) { |
| |
| if (($currentState & 1) == 1) { |
| $currentState++; |
| } |
| $stateCount[$currentState]++; |
| } else { |
| if (($currentState & 1) == 0) { |
| if ($currentState == 4) { |
| if (self::foundPatternCross($stateCount)) { |
| $confirmed = $this->handlePossibleCenter($stateCount, $i, $j, $pureBarcode); |
| if ($confirmed) { |
| |
| |
| $iSkip = 3; |
| if ($this->hasSkipped) { |
| $done = $this->haveMultiplyConfirmedCenters(); |
| } else { |
| $rowSkip = $this->findRowSkip(); |
| if ($rowSkip > $stateCount[2]) { |
| |
| |
| |
| |
|
|
| |
| |
| |
| $i += $rowSkip - $stateCount[2] - $iSkip; |
| $j = $maxJ - 1; |
| } |
| } |
| } else { |
| $stateCount[0] = $stateCount[2]; |
| $stateCount[1] = $stateCount[3]; |
| $stateCount[2] = $stateCount[4]; |
| $stateCount[3] = 1; |
| $stateCount[4] = 0; |
| $currentState = 3; |
| continue; |
| } |
| |
| $currentState = 0; |
| $stateCount[0] = 0; |
| $stateCount[1] = 0; |
| $stateCount[2] = 0; |
| $stateCount[3] = 0; |
| $stateCount[4] = 0; |
| } else { |
| $stateCount[0] = $stateCount[2]; |
| $stateCount[1] = $stateCount[3]; |
| $stateCount[2] = $stateCount[4]; |
| $stateCount[3] = 1; |
| $stateCount[4] = 0; |
| $currentState = 3; |
| } |
| } else { |
| $stateCount[++$currentState]++; |
| } |
| } else { |
| $stateCount[$currentState]++; |
| } |
| } |
| } |
| if (self::foundPatternCross($stateCount)) { |
| $confirmed = $this->handlePossibleCenter($stateCount, $i, $maxJ, $pureBarcode); |
| if ($confirmed) { |
| $iSkip = $stateCount[0]; |
| if ($this->hasSkipped) { |
| |
| $done = $this->haveMultiplyConfirmedCenters(); |
| } |
| } |
| } |
| } |
|
|
| $patternInfo = $this->selectBestPatterns(); |
| $patternInfo = ResultPoint::orderBestPatterns($patternInfo); |
|
|
| return new FinderPatternInfo($patternInfo); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| protected static function foundPatternCross($stateCount) |
| { |
| $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 / 2.0; |
|
|
| |
| 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; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| protected final function handlePossibleCenter($stateCount, $i, $j, $pureBarcode) |
| { |
| $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 (!is_nan($centerI)) { |
| |
| $centerJ = $this->crossCheckHorizontal((int)($centerJ), (int)($centerI), $stateCount[2], $stateCountTotal); |
| if (!is_nan($centerJ) && |
| (!$pureBarcode || $this->crossCheckDiagonal((int)($centerI), (int)($centerJ), $stateCount[2], $stateCountTotal)) |
| ) { |
| $estimatedModuleSize = (float)$stateCountTotal / 7.0; |
| $found = false; |
| for ($index = 0; $index < count($this->possibleCenters); $index++) { |
| $center = $this->possibleCenters[$index]; |
| |
| 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; |
| if ($this->resultPointCallback != null) { |
| $this->resultPointCallback->foundPossibleResultPoint($point); |
| } |
| } |
|
|
| return true; |
| } |
| } |
|
|
| return false; |
| } |
|
|
| |
| |
| |
| |
| private static function centerFromEnd($stateCount, $end) |
| { |
| return (float)($end - $stateCount[4] - $stateCount[3]) - $stateCount[2] / 2.0; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| private function crossCheckVertical($startI, $centerJ, $maxCount, |
| $originalStateCountTotal) |
| { |
| $image = $this->image; |
|
|
| $maxI = $image->getHeight(); |
| $stateCount = $this->getCrossCheckStateCount(); |
|
|
| |
| $i = $startI; |
| while ($i >= 0 && $image->get($centerJ, $i)) { |
| $stateCount[2]++; |
| $i--; |
| } |
| if ($i < 0) { |
| return NAN; |
| } |
| 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[2]++; |
| $i++; |
| } |
| if ($i == $maxI) { |
| return NAN; |
| } |
| while ($i < $maxI && !$image->get($centerJ, $i) && $stateCount[3] < $maxCount) { |
| $stateCount[3]++; |
| $i++; |
| } |
| if ($i == $maxI || $stateCount[3] >= $maxCount) { |
| return NAN; |
| } |
| while ($i < $maxI && $image->get($centerJ, $i) && $stateCount[4] < $maxCount) { |
| $stateCount[4]++; |
| $i++; |
| } |
| if ($stateCount[4] >= $maxCount) { |
| return NAN; |
| } |
|
|
| |
| |
| $stateCountTotal = $stateCount[0] + $stateCount[1] + $stateCount[2] + $stateCount[3] + |
| $stateCount[4]; |
| if (5 * abs($stateCountTotal - $originalStateCountTotal) >= 2 * $originalStateCountTotal) { |
| return NAN; |
| } |
|
|
| return self::foundPatternCross($stateCount) ? $this->centerFromEnd($stateCount, $i) : NAN; |
| } |
|
|
| private function getCrossCheckStateCount() |
| { |
| $this->crossCheckStateCount[0] = 0; |
| $this->crossCheckStateCount[1] = 0; |
| $this->crossCheckStateCount[2] = 0; |
| $this->crossCheckStateCount[3] = 0; |
| $this->crossCheckStateCount[4] = 0; |
|
|
| return $this->crossCheckStateCount; |
| } |
|
|
| |
| |
| |
| |
| |
| private function crossCheckHorizontal($startJ, $centerI, $maxCount, |
| $originalStateCountTotal) |
| { |
| $image = $this->image; |
|
|
| $maxJ = $this->image->getWidth(); |
| $stateCount = $this->getCrossCheckStateCount(); |
|
|
| $j = $startJ; |
| while ($j >= 0 && $image->get($j, $centerI)) { |
| $stateCount[2]++; |
| $j--; |
| } |
| if ($j < 0) { |
| return NAN; |
| } |
| while ($j >= 0 && !$image->get($j, $centerI) && $stateCount[1] <= $maxCount) { |
| $stateCount[1]++; |
| $j--; |
| } |
| if ($j < 0 || $stateCount[1] > $maxCount) { |
| return NAN; |
| } |
| while ($j >= 0 && $image->get($j, $centerI) && $stateCount[0] <= $maxCount) { |
| $stateCount[0]++; |
| $j--; |
| } |
| if ($stateCount[0] > $maxCount) { |
| return NAN; |
| } |
|
|
| $j = $startJ + 1; |
| while ($j < $maxJ && $image->get($j, $centerI)) { |
| $stateCount[2]++; |
| $j++; |
| } |
| if ($j == $maxJ) { |
| return NAN; |
| } |
| while ($j < $maxJ && !$image->get($j, $centerI) && $stateCount[3] < $maxCount) { |
| $stateCount[3]++; |
| $j++; |
| } |
| if ($j == $maxJ || $stateCount[3] >= $maxCount) { |
| return NAN; |
| } |
| while ($j < $maxJ && $this->image->get($j, $centerI) && $stateCount[4] < $maxCount) { |
| $stateCount[4]++; |
| $j++; |
| } |
| if ($stateCount[4] >= $maxCount) { |
| return NAN; |
| } |
|
|
| |
| |
| $stateCountTotal = $stateCount[0] + $stateCount[1] + $stateCount[2] + $stateCount[3] + |
| $stateCount[4]; |
| if (5 * abs($stateCountTotal - $originalStateCountTotal) >= $originalStateCountTotal) { |
| return NAN; |
| } |
|
|
| return $this->foundPatternCross($stateCount) ? $this->centerFromEnd($stateCount, $j) : NAN; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| private function crossCheckDiagonal($startI, $centerJ, $maxCount, $originalStateCountTotal) |
| { |
| $stateCount = $this->getCrossCheckStateCount(); |
|
|
| |
| $i = 0; |
| $startI = (int)($startI); |
| $centerJ = (int)($centerJ); |
| while ($startI >= $i && $centerJ >= $i && $this->image->get($centerJ - $i, $startI - $i)) { |
| $stateCount[2]++; |
| $i++; |
| } |
|
|
| if ($startI < $i || $centerJ < $i) { |
| return false; |
| } |
|
|
| |
| while ($startI >= $i && $centerJ >= $i && !$this->image->get($centerJ - $i, $startI - $i) && |
| $stateCount[1] <= $maxCount) { |
| $stateCount[1]++; |
| $i++; |
| } |
|
|
| |
| if ($startI < $i || $centerJ < $i || $stateCount[1] > $maxCount) { |
| return false; |
| } |
|
|
| |
| while ($startI >= $i && $centerJ >= $i && $this->image->get($centerJ - $i, $startI - $i) && |
| $stateCount[0] <= $maxCount) { |
| $stateCount[0]++; |
| $i++; |
| } |
| if ($stateCount[0] > $maxCount) { |
| return false; |
| } |
|
|
| $maxI = $this->image->getHeight(); |
| $maxJ = $this->image->getWidth(); |
|
|
| |
| $i = 1; |
| while ($startI + $i < $maxI && $centerJ + $i < $maxJ && $this->image->get($centerJ + $i, $startI + $i)) { |
| $stateCount[2]++; |
| $i++; |
| } |
|
|
| |
| if ($startI + $i >= $maxI || $centerJ + $i >= $maxJ) { |
| return false; |
| } |
|
|
| while ($startI + $i < $maxI && $centerJ + $i < $maxJ && !$this->image->get($centerJ + $i, $startI + $i) && |
| $stateCount[3] < $maxCount) { |
| $stateCount[3]++; |
| $i++; |
| } |
|
|
| if ($startI + $i >= $maxI || $centerJ + $i >= $maxJ || $stateCount[3] >= $maxCount) { |
| return false; |
| } |
|
|
| while ($startI + $i < $maxI && $centerJ + $i < $maxJ && $this->image->get($centerJ + $i, $startI + $i) && |
| $stateCount[4] < $maxCount) { |
| $stateCount[4]++; |
| $i++; |
| } |
|
|
| if ($stateCount[4] >= $maxCount) { |
| return false; |
| } |
|
|
| |
| |
| $stateCountTotal = $stateCount[0] + $stateCount[1] + $stateCount[2] + $stateCount[3] + $stateCount[4]; |
|
|
| return |
| abs($stateCountTotal - $originalStateCountTotal) < 2 * $originalStateCountTotal && |
| self::foundPatternCross($stateCount); |
| } |
|
|
| |
| |
| |
| |
| |
| private function haveMultiplyConfirmedCenters() |
| { |
| $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; |
| } |
| |
| |
| |
| |
| $average = $totalModuleSize / (float)$max; |
| $totalDeviation = 0.0; |
| foreach ($this->possibleCenters as $pattern) { |
| $totalDeviation += abs($pattern->getEstimatedModuleSize() - $average); |
| } |
|
|
| return $totalDeviation <= 0.05 * $totalModuleSize; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| private function findRowSkip() |
| { |
| $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 { |
| |
| |
| |
| |
| |
| $this->hasSkipped = true; |
|
|
| return (int)((abs($firstConfirmedCenter->getX() - $center->getX()) - |
| abs($firstConfirmedCenter->getY() - $center->getY())) / 2); |
| } |
| } |
| } |
|
|
| return 0; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| private function selectBestPatterns() |
| { |
| $startSize = count($this->possibleCenters); |
| if ($startSize < 3) { |
| |
| throw new NotFoundException; |
| } |
|
|
| |
| if ($startSize > 3) { |
| |
| $totalModuleSize = 0.0; |
| $square = 0.0; |
| foreach ($this->possibleCenters as $center) { |
| $size = $center->getEstimatedModuleSize(); |
| $totalModuleSize += $size; |
| $square += $size * $size; |
| } |
| $this->average = $totalModuleSize / (float)$startSize; |
| $stdDev = (float)sqrt($square / $startSize - $this->average * $this->average); |
|
|
| usort($this->possibleCenters, [$this, 'FurthestFromAverageComparator']); |
|
|
| $limit = max(0.2 * $this->average, $stdDev); |
|
|
| for ($i = 0; $i < count($this->possibleCenters) && count($this->possibleCenters) > 3; $i++) { |
| $pattern = $this->possibleCenters[$i]; |
| if (abs($pattern->getEstimatedModuleSize() - $this->average) > $limit) { |
| unset($this->possibleCenters[$i]); |
| $this->possibleCenters = array_values($this->possibleCenters); |
| $i--; |
| } |
| } |
| } |
|
|
| if (count($this->possibleCenters) > 3) { |
| |
|
|
| $totalModuleSize = 0.0; |
| foreach ($this->possibleCenters as $possibleCenter) { |
| $totalModuleSize += $possibleCenter->getEstimatedModuleSize(); |
| } |
|
|
| $this->average = $totalModuleSize / (float)count($this->possibleCenters); |
|
|
| usort($this->possibleCenters, [$this, 'CenterComparator']); |
|
|
| array_slice($this->possibleCenters, 3, count($this->possibleCenters) - 3); |
| } |
|
|
| return [$this->possibleCenters[0], $this->possibleCenters[1], $this->possibleCenters[2]]; |
| } |
|
|
| |
| |
| |
| public function FurthestFromAverageComparator($center1, $center2) |
| { |
|
|
| $dA = abs($center2->getEstimatedModuleSize() - $this->average); |
| $dB = abs($center1->getEstimatedModuleSize() - $this->average); |
| if ($dA < $dB) { |
| return -1; |
| } elseif ($dA == $dB) { |
| return 0; |
| } else { |
| return 1; |
| } |
| } |
|
|
| public function CenterComparator($center1, $center2) |
| { |
| if ($center2->getCount() == $center1->getCount()) { |
| $dA = abs($center2->getEstimatedModuleSize() - $this->average); |
| $dB = abs($center1->getEstimatedModuleSize() - $this->average); |
| if ($dA < $dB) { |
| return 1; |
| } elseif ($dA == $dB) { |
| return 0; |
| } else { |
| return -1; |
| } |
| } else { |
| return $center2->getCount() - $center1->getCount(); |
| } |
| } |
|
|
| protected final function getImage() |
| { |
| return $this->image; |
| } |
| |
| |
| |
|
|
| |
| protected final function getPossibleCenters() |
| { |
| return $this->possibleCenters; |
| } |
| } |
|
|