/* * Copyright 2012 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import "ZXBitMatrix.h" #import "ZXDecodeHints.h" #import "ZXErrors.h" #import "ZXQRCodeFinderPattern.h" #import "ZXQRCodeFinderPatternInfo.h" #import "ZXQRCodeFinderPatternFinder.h" #import "ZXResultPoint.h" #import "ZXResultPointCallback.h" const int ZX_CENTER_QUORUM = 2; const int ZX_FINDER_PATTERN_MIN_SKIP = 3; const int ZX_FINDER_PATTERN_MAX_MODULES = 57; @interface ZXQRCodeFinderPatternFinder () NSInteger centerCompare(id center1, id center2, void *context); NSInteger furthestFromAverageCompare(id center1, id center2, void *context); @property (nonatomic, assign) BOOL hasSkipped; @property (nonatomic, weak, readonly) id resultPointCallback; @property (nonatomic, strong) NSMutableArray *possibleCenters; @end @implementation ZXQRCodeFinderPatternFinder - (id)initWithImage:(ZXBitMatrix *)image { return [self initWithImage:image resultPointCallback:nil]; } - (id)initWithImage:(ZXBitMatrix *)image resultPointCallback:(id)resultPointCallback { if (self = [super init]) { _image = image; _possibleCenters = [NSMutableArray array]; _resultPointCallback = resultPointCallback; } return self; } - (ZXQRCodeFinderPatternInfo *)find:(ZXDecodeHints *)hints error:(NSError **)error { BOOL tryHarder = hints != nil && hints.tryHarder; BOOL pureBarcode = hints != nil && hints.pureBarcode; int maxI = self.image.height; int maxJ = self.image.width; int iSkip = (3 * maxI) / (4 * ZX_FINDER_PATTERN_MAX_MODULES); if (iSkip < ZX_FINDER_PATTERN_MIN_SKIP || tryHarder) { iSkip = ZX_FINDER_PATTERN_MIN_SKIP; } BOOL done = NO; int stateCount[5]; for (int i = iSkip - 1; i < maxI && !done; i += iSkip) { stateCount[0] = 0; stateCount[1] = 0; stateCount[2] = 0; stateCount[3] = 0; stateCount[4] = 0; int currentState = 0; for (int j = 0; j < maxJ; j++) { if ([self.image getX:j y:i]) { if ((currentState & 1) == 1) { currentState++; } stateCount[currentState]++; } else { if ((currentState & 1) == 0) { if (currentState == 4) { if ([ZXQRCodeFinderPatternFinder foundPatternCross:stateCount]) { BOOL confirmed = [self handlePossibleCenter:stateCount i:i j:j pureBarcode:pureBarcode]; if (confirmed) { iSkip = 2; if (self.hasSkipped) { done = [self haveMultiplyConfirmedCenters]; } else { int rowSkip = [self 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 ([ZXQRCodeFinderPatternFinder foundPatternCross:stateCount]) { BOOL confirmed = [self handlePossibleCenter:stateCount i:i j:maxJ pureBarcode:pureBarcode]; if (confirmed) { iSkip = stateCount[0]; if (self.hasSkipped) { done = [self haveMultiplyConfirmedCenters]; } } } } NSMutableArray *patternInfo = [self selectBestPatterns]; if (!patternInfo) { if (error) *error = ZXNotFoundErrorInstance(); return nil; } [ZXResultPoint orderBestPatterns:patternInfo]; return [[ZXQRCodeFinderPatternInfo alloc] initWithPatternCenters:patternInfo]; } /** * 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. */ - (float)centerFromEnd:(const int[])stateCount end:(int)end { return (float)(end - stateCount[4] - stateCount[3]) - stateCount[2] / 2.0f; } + (BOOL)foundPatternCross:(const int[])stateCount { int totalModuleSize = 0; for (int i = 0; i < 5; i++) { int count = stateCount[i]; if (count == 0) { return NO; } totalModuleSize += count; } if (totalModuleSize < 7) { return NO; } float moduleSize = totalModuleSize / 7.0f; float maxVariance = moduleSize / 2.0f; // Allow less than 50% variance from 1-1-3-1-1 proportions return ABS(moduleSize - stateCount[0]) < maxVariance && ABS(moduleSize - stateCount[1]) < maxVariance && ABS(3.0f * 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 startI row where a finder pattern was detected * @param centerJ center of the section that appears to cross a finder pattern * @param maxCount maximum reasonable number of modules that should be * observed in any reading state, based on the results of the horizontal scan * @param originalStateCountTotal The original state count total. * @return true if proportions are withing expected limits */ - (BOOL)crossCheckDiagonal:(int)startI centerJ:(int)centerJ maxCount:(int)maxCount originalStateCountTotal:(int)originalStateCountTotal { int stateCount[5] = {0, 0, 0, 0, 0}; // Start counting up, left from center finding black center mass int i = 0; while (startI >= i && centerJ >= i && [self.image getX:centerJ - i y:startI - i]) { stateCount[2]++; i++; } if (startI < i || centerJ < i) { return NO; } // Continue up, left finding white space while (startI >= i && centerJ >= i && ![self.image getX:centerJ - i y:startI - i] && stateCount[1] <= maxCount) { stateCount[1]++; i++; } // If already too many modules in this state or ran off the edge: if (startI < i || centerJ < i || stateCount[1] > maxCount) { return NO; } // Continue up, left finding black border while (startI >= i && centerJ >= i && [self.image getX:centerJ - i y:startI - i] && stateCount[0] <= maxCount) { stateCount[0]++; i++; } if (stateCount[0] > maxCount) { return NO; } int maxI = self.image.height; int maxJ = self.image.width; // Now also count down, right from center i = 1; while (startI + i < maxI && centerJ + i < maxJ && [self.image getX:centerJ + i y:startI + i]) { stateCount[2]++; i++; } // Ran off the edge? if (startI + i >= maxI || centerJ + i >= maxJ) { return NO; } while (startI + i < maxI && centerJ + i < maxJ && ![self.image getX:centerJ + i y:startI + i] && stateCount[3] < maxCount) { stateCount[3]++; i++; } if (startI + i >= maxI || centerJ + i >= maxJ || stateCount[3] >= maxCount) { return NO; } while (startI + i < maxI && centerJ + i < maxJ && [self.image getX:centerJ + i y:startI + i] && stateCount[4] < maxCount) { stateCount[4]++; i++; } if (stateCount[4] >= maxCount) { return NO; } // If we found a finder-pattern-like section, but its size is more than 100% different than // the original, assume it's a false positive int stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] + stateCount[4]; return abs(stateCountTotal - originalStateCountTotal) < 2 * originalStateCountTotal && [ZXQRCodeFinderPatternFinder foundPatternCross: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 startI row where a finder pattern was detected * @param centerJ center of the section that appears to cross a finder pattern * @param maxCount maximum reasonable number of modules that should be * observed in any reading state, based on the results of the horizontal scan * @return vertical center of finder pattern, or {@link Float#NaN} if not found */ - (float)crossCheckVertical:(int)startI centerJ:(int)centerJ maxCount:(int)maxCount originalStateCountTotal:(int)originalStateCountTotal { int maxI = self.image.height; int stateCount[5] = {0, 0, 0, 0, 0}; int i = startI; while (i >= 0 && [self.image getX:centerJ y:i]) { stateCount[2]++; i--; } if (i < 0) { return NAN; } while (i >= 0 && ![self.image getX:centerJ y:i] && stateCount[1] <= maxCount) { stateCount[1]++; i--; } if (i < 0 || stateCount[1] > maxCount) { return NAN; } while (i >= 0 && [self.image getX:centerJ y:i] && stateCount[0] <= maxCount) { stateCount[0]++; i--; } if (stateCount[0] > maxCount) { return NAN; } i = startI + 1; while (i < maxI && [self.image getX:centerJ y:i]) { stateCount[2]++; i++; } if (i == maxI) { return NAN; } while (i < maxI && ![self.image getX:centerJ y:i] && stateCount[3] < maxCount) { stateCount[3]++; i++; } if (i == maxI || stateCount[3] >= maxCount) { return NAN; } while (i < maxI && [self.image getX:centerJ y:i] && stateCount[4] < maxCount) { stateCount[4]++; i++; } if (stateCount[4] >= maxCount) { return NAN; } int stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] + stateCount[4]; if (5 * abs(stateCountTotal - originalStateCountTotal) >= 2 * originalStateCountTotal) { return NAN; } return [ZXQRCodeFinderPatternFinder foundPatternCross:stateCount] ? [self centerFromEnd:stateCount end:i] : NAN; } /** * Like crossCheckVertical, 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. */ - (float)crossCheckHorizontal:(int)startJ centerI:(int)centerI maxCount:(int)maxCount originalStateCountTotal:(int)originalStateCountTotal { int maxJ = self.image.width; int stateCount[5] = {0, 0, 0, 0, 0}; int j = startJ; while (j >= 0 && [self.image getX:j y:centerI]) { stateCount[2]++; j--; } if (j < 0) { return NAN; } while (j >= 0 && ![self.image getX:j y:centerI] && stateCount[1] <= maxCount) { stateCount[1]++; j--; } if (j < 0 || stateCount[1] > maxCount) { return NAN; } while (j >= 0 && [self.image getX:j y:centerI] && stateCount[0] <= maxCount) { stateCount[0]++; j--; } if (stateCount[0] > maxCount) { return NAN; } j = startJ + 1; while (j < maxJ && [self.image getX:j y:centerI]) { stateCount[2]++; j++; } if (j == maxJ) { return NAN; } while (j < maxJ && ![self.image getX:j y:centerI] && stateCount[3] < maxCount) { stateCount[3]++; j++; } if (j == maxJ || stateCount[3] >= maxCount) { return NAN; } while (j < maxJ && [self.image getX:j y:centerI] && stateCount[4] < maxCount) { stateCount[4]++; j++; } if (stateCount[4] >= maxCount) { return NAN; } int stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] + stateCount[4]; if (5 * abs(stateCountTotal - originalStateCountTotal) >= originalStateCountTotal) { return NAN; } return [ZXQRCodeFinderPatternFinder foundPatternCross:stateCount] ? [self centerFromEnd:stateCount end:j] : NAN; } - (BOOL)handlePossibleCenter:(const int[])stateCount i:(int)i j:(int)j pureBarcode:(BOOL)pureBarcode { int stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] + stateCount[4]; float centerJ = [self centerFromEnd:stateCount end:j]; float centerI = [self crossCheckVertical:i centerJ:(int)centerJ maxCount:stateCount[2] originalStateCountTotal:stateCountTotal]; if (!isnan(centerI)) { centerJ = [self crossCheckHorizontal:(int)centerJ centerI:(int)centerI maxCount:stateCount[2] originalStateCountTotal:stateCountTotal]; if (!isnan(centerJ) && (!pureBarcode || [self crossCheckDiagonal:(int)centerI centerJ:(int) centerJ maxCount:stateCount[2] originalStateCountTotal:stateCountTotal])) { float estimatedModuleSize = (float)stateCountTotal / 7.0f; BOOL found = NO; int max = (int)[self.possibleCenters count]; for (int index = 0; index < max; index++) { ZXQRCodeFinderPattern *center = self.possibleCenters[index]; if ([center aboutEquals:estimatedModuleSize i:centerI j:centerJ]) { self.possibleCenters[index] = [center combineEstimateI:centerI j:centerJ newModuleSize:estimatedModuleSize]; found = YES; break; } } if (!found) { ZXResultPoint *point = [[ZXQRCodeFinderPattern alloc] initWithPosX:centerJ posY:centerI estimatedModuleSize:estimatedModuleSize]; [self.possibleCenters addObject:point]; if (self.resultPointCallback != nil) { [self.resultPointCallback foundPossibleResultPoint:point]; } } return YES; } } return NO; } /** * @return 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. */ - (int)findRowSkip { int max = (int)[self.possibleCenters count]; if (max <= 1) { return 0; } ZXResultPoint *firstConfirmedCenter = nil; for (int i = 0; i < max; i++) { ZXQRCodeFinderPattern *center = self.possibleCenters[i]; if ([center count] >= ZX_CENTER_QUORUM) { if (firstConfirmedCenter == nil) { firstConfirmedCenter = center; } else { self.hasSkipped = YES; return (int)(fabsf([firstConfirmedCenter x] - [center x]) - fabsf([firstConfirmedCenter y] - [center y])) / 2; } } } return 0; } /** * @return true iff we have found at least 3 finder patterns that have been detected * at least ZX_CENTER_QUORUM times each, and, the estimated module size of the * candidates is "pretty similar" */ - (BOOL)haveMultiplyConfirmedCenters { int confirmedCount = 0; float totalModuleSize = 0.0f; int max = (int)[self.possibleCenters count]; for (int i = 0; i < max; i++) { ZXQRCodeFinderPattern *pattern = self.possibleCenters[i]; if ([pattern count] >= ZX_CENTER_QUORUM) { confirmedCount++; totalModuleSize += [pattern estimatedModuleSize]; } } if (confirmedCount < 3) { return NO; } float average = totalModuleSize / (float)max; float totalDeviation = 0.0f; for (int i = 0; i < max; i++) { ZXQRCodeFinderPattern *pattern = self.possibleCenters[i]; totalDeviation += fabsf([pattern estimatedModuleSize] - average); } return totalDeviation <= 0.05f * totalModuleSize; } /** * Orders by ZXFinderPattern count, descending. */ NSInteger centerCompare(id center1, id center2, void *context) { float average = [(__bridge NSNumber *)context floatValue]; if ([((ZXQRCodeFinderPattern *)center2) count] == [((ZXQRCodeFinderPattern *)center1) count]) { float dA = fabsf([((ZXQRCodeFinderPattern *)center2) estimatedModuleSize] - average); float dB = fabsf([((ZXQRCodeFinderPattern *)center1) estimatedModuleSize] - average); return dA < dB ? 1 : dA == dB ? 0 : -1; } else { return [((ZXQRCodeFinderPattern *)center2) count] - [((ZXQRCodeFinderPattern *)center1) count]; } } /** * Orders by furthest from average */ NSInteger furthestFromAverageCompare(id center1, id center2, void *context) { float average = [(__bridge NSNumber *)context floatValue]; float dA = fabsf([((ZXQRCodeFinderPattern *)center2) estimatedModuleSize] - average); float dB = fabsf([((ZXQRCodeFinderPattern *)center1) estimatedModuleSize] - average); return dA < dB ? -1 : dA == dB ? 0 : 1; } /** * @return the 3 best ZXFinderPatterns from our list of candidates. The "best" are * those that have been detected at least ZXCENTER_QUORUM times, and whose module * size differs from the average among those patterns the least * @return nil if 3 such finder patterns do not exist */ - (NSMutableArray *)selectBestPatterns { int startSize = (int)[self.possibleCenters count]; if (startSize < 3) { return nil; } if (startSize > 3) { float totalModuleSize = 0.0f; float square = 0.0f; for (int i = 0; i < startSize; i++) { float size = [self.possibleCenters[i] estimatedModuleSize]; totalModuleSize += size; square += size * size; } float average = totalModuleSize / (float)startSize; float stdDev = (float)sqrt(square / startSize - average * average); [self.possibleCenters sortUsingFunction: furthestFromAverageCompare context: (__bridge void *)@(average)]; float limit = MAX(0.2f * average, stdDev); for (int i = 0; i < [self.possibleCenters count] && [self.possibleCenters count] > 3; i++) { ZXQRCodeFinderPattern *pattern = self.possibleCenters[i]; if (fabsf([pattern estimatedModuleSize] - average) > limit) { [self.possibleCenters removeObjectAtIndex:i]; i--; } } } if ([self.possibleCenters count] > 3) { float totalModuleSize = 0.0f; for (int i = 0; i < [self.possibleCenters count]; i++) { totalModuleSize += [self.possibleCenters[i] estimatedModuleSize]; } float average = totalModuleSize / (float)[self.possibleCenters count]; [self.possibleCenters sortUsingFunction:centerCompare context:(__bridge void *)(@(average))]; self.possibleCenters = [[NSMutableArray alloc] initWithArray:[self.possibleCenters subarrayWithRange:NSMakeRange(0, 3)]]; } return [@[self.possibleCenters[0], self.possibleCenters[1], self.possibleCenters[2]] mutableCopy]; } @end