123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570 |
- /*
- * 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<ZXResultPointCallback> 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<ZXResultPointCallback>)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
|