blob: d182acab06d1c8872e060054902925c9bc09c862 [file] [log] [blame]
/*
* 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 "ZXFinderPatternFinder.h"
#import "ZXFinderPatternInfo.h"
#import "ZXOneDReader.h"
#import "ZXQRCodeFinderPattern.h"
#import "ZXResultPoint.h"
#import "ZXResultPointCallback.h"
int const CENTER_QUORUM = 2;
int const FINDER_PATTERN_MIN_SKIP = 3;
int const FINDER_PATTERN_MAX_MODULES = 57;
@interface ZXFinderPatternFinder ()
NSInteger centerCompare(id center1, id center2, void *context);
NSInteger furthestFromAverageCompare(id center1, id center2, void *context);
@property (nonatomic, assign) BOOL hasSkipped;
@property (nonatomic, weak) id <ZXResultPointCallback> resultPointCallback;
@property (nonatomic, strong) NSMutableArray *possibleCenters;
@end
@implementation ZXFinderPatternFinder
/**
* Creates a finder that will search the image for three finder patterns.
*/
- (id)initWithImage:(ZXBitMatrix *)image {
return [self initWithImage:image resultPointCallback:nil];
}
- (id)initWithImage:(ZXBitMatrix *)image resultPointCallback:(id<ZXResultPointCallback>)resultPointCallback {
if (self = [super init]) {
_image = image;
_possibleCenters = [NSMutableArray array];
_resultPointCallback = resultPointCallback;
}
return self;
}
- (ZXFinderPatternInfo *)find:(ZXDecodeHints *)hints error:(NSError **)error {
BOOL tryHarder = hints != nil && hints.tryHarder;
int maxI = self.image.height;
int maxJ = self.image.width;
int iSkip = (3 * maxI) / (4 * FINDER_PATTERN_MAX_MODULES);
if (iSkip < FINDER_PATTERN_MIN_SKIP || tryHarder) {
iSkip = 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 ([ZXFinderPatternFinder foundPatternCross:stateCount]) {
BOOL confirmed = [self handlePossibleCenter:stateCount i:i j:j];
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 ([ZXFinderPatternFinder foundPatternCross:stateCount]) {
BOOL confirmed = [self handlePossibleCenter:stateCount i:i j:maxJ];
if (confirmed) {
iSkip = stateCount[0];
if (self.hasSkipped) {
done = [self haveMultiplyConfirmedCenters];
}
}
}
}
NSMutableArray *patternInfo = [self selectBestPatterns];
if (!patternInfo) {
if (error) *error = NotFoundErrorInstance();
return nil;
}
[ZXResultPoint orderBestPatterns:patternInfo];
return [[ZXFinderPatternInfo 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:(int *)stateCount end:(int)end {
return (float)(end - stateCount[4] - stateCount[3]) - stateCount[2] / 2.0f;
}
+ (BOOL)foundPatternCross:(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;
}
int moduleSize = (totalModuleSize << INTEGER_MATH_SHIFT) / 7;
int maxVariance = moduleSize / 2;
return abs(moduleSize - (stateCount[0] << INTEGER_MATH_SHIFT)) < maxVariance &&
abs(moduleSize - (stateCount[1] << INTEGER_MATH_SHIFT)) < maxVariance &&
abs(3 * moduleSize - (stateCount[2] << INTEGER_MATH_SHIFT)) < 3 * maxVariance &&
abs(moduleSize - (stateCount[3] << INTEGER_MATH_SHIFT)) < maxVariance &&
abs(moduleSize - (stateCount[4] << INTEGER_MATH_SHIFT)) < maxVariance;
}
/**
* 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.
*/
- (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 [ZXFinderPatternFinder 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 [ZXFinderPatternFinder foundPatternCross:stateCount] ? [self centerFromEnd:stateCount end:j] : NAN;
}
/**
* This is called when a horizontal scan finds a possible alignment pattern. It will
* cross check with a vertical scan, and if successful, will, ah, cross-cross-check
* with another horizontal scan. This is needed primarily to locate the real horizontal
* center of the pattern in cases of extreme skew.
*
* If that succeeds the finder pattern location is added to a list that tracks
* the number of times each location has been nearly-matched as a finder pattern.
* Each additional find is more evidence that the location is in fact a finder
* pattern center
*/
- (BOOL)handlePossibleCenter:(int *)stateCount i:(int)i j:(int)j {
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)) {
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;
}
ZXQRCodeFinderPattern *firstConfirmedCenter = nil;
for (int i = 0; i < max; i++) {
ZXQRCodeFinderPattern *center = self.possibleCenters[i];
if ([center count] >= 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;
}
- (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] >= 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;
}
/**
* Returns the 3 best ZXFinderPatterns from our list of candidates. The "best" are
* those that have been detected at least CENTER_QUORUM times, and whose module
* size differs from the average among those patterns the least
*/
- (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