/* * 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 "ZXDetectorResult.h" #import "ZXErrors.h" #import "ZXGridSampler.h" #import "ZXIntArray.h" #import "ZXMathUtils.h" #import "ZXPerspectiveTransform.h" #import "ZXQRCodeAlignmentPattern.h" #import "ZXQRCodeAlignmentPatternFinder.h" #import "ZXQRCodeDetector.h" #import "ZXQRCodeFinderPattern.h" #import "ZXQRCodeFinderPatternFinder.h" #import "ZXQRCodeFinderPatternInfo.h" #import "ZXQRCodeVersion.h" #import "ZXResultPoint.h" #import "ZXResultPointCallback.h" @interface ZXQRCodeDetector () @property (nonatomic, weak) id resultPointCallback; @end @implementation ZXQRCodeDetector - (id)initWithImage:(ZXBitMatrix *)image { if (self = [super init]) { _image = image; } return self; } - (ZXDetectorResult *)detectWithError:(NSError **)error { return [self detect:nil error:error]; } - (ZXDetectorResult *)detect:(ZXDecodeHints *)hints error:(NSError **)error { self.resultPointCallback = hints == nil ? nil : hints.resultPointCallback; ZXQRCodeFinderPatternFinder *finder = [[ZXQRCodeFinderPatternFinder alloc] initWithImage:self.image resultPointCallback:self.resultPointCallback]; ZXQRCodeFinderPatternInfo *info = [finder find:hints error:error]; if (!info) { return nil; } return [self processFinderPatternInfo:info error:error]; } - (ZXDetectorResult *)processFinderPatternInfo:(ZXQRCodeFinderPatternInfo *)info error:(NSError **)error { ZXQRCodeFinderPattern *topLeft = info.topLeft; ZXQRCodeFinderPattern *topRight = info.topRight; ZXQRCodeFinderPattern *bottomLeft = info.bottomLeft; float moduleSize = [self calculateModuleSize:topLeft topRight:topRight bottomLeft:bottomLeft]; if (moduleSize < 1.0f) { if (error) *error = ZXNotFoundErrorInstance(); return nil; } int dimension = [ZXQRCodeDetector computeDimension:topLeft topRight:topRight bottomLeft:bottomLeft moduleSize:moduleSize error:error]; if (dimension == -1) { return nil; } ZXQRCodeVersion *provisionalVersion = [ZXQRCodeVersion provisionalVersionForDimension:dimension]; if (!provisionalVersion) { if (error) *error = ZXFormatErrorInstance(); return nil; } int modulesBetweenFPCenters = [provisionalVersion dimensionForVersion] - 7; ZXQRCodeAlignmentPattern *alignmentPattern = nil; if (provisionalVersion.alignmentPatternCenters.length > 0) { float bottomRightX = [topRight x] - [topLeft x] + [bottomLeft x]; float bottomRightY = [topRight y] - [topLeft y] + [bottomLeft y]; float correctionToTopLeft = 1.0f - 3.0f / (float)modulesBetweenFPCenters; int estAlignmentX = (int)([topLeft x] + correctionToTopLeft * (bottomRightX - [topLeft x])); int estAlignmentY = (int)([topLeft y] + correctionToTopLeft * (bottomRightY - [topLeft y])); for (int i = 4; i <= 16; i <<= 1) { NSError *alignmentError = nil; alignmentPattern = [self findAlignmentInRegion:moduleSize estAlignmentX:estAlignmentX estAlignmentY:estAlignmentY allowanceFactor:(float)i error:&alignmentError]; if (alignmentPattern) { break; } else if (alignmentError.code != ZXNotFoundError) { if (error) *error = alignmentError; return nil; } } } ZXPerspectiveTransform *transform = [ZXQRCodeDetector createTransform:topLeft topRight:topRight bottomLeft:bottomLeft alignmentPattern:alignmentPattern dimension:dimension]; ZXBitMatrix *bits = [self sampleGrid:self.image transform:transform dimension:dimension error:error]; if (!bits) { return nil; } NSArray *points; if (alignmentPattern == nil) { points = @[bottomLeft, topLeft, topRight]; } else { points = @[bottomLeft, topLeft, topRight, alignmentPattern]; } return [[ZXDetectorResult alloc] initWithBits:bits points:points]; } + (ZXPerspectiveTransform *)createTransform:(ZXResultPoint *)topLeft topRight:(ZXResultPoint *)topRight bottomLeft:(ZXResultPoint *)bottomLeft alignmentPattern:(ZXResultPoint *)alignmentPattern dimension:(int)dimension { float dimMinusThree = (float)dimension - 3.5f; float bottomRightX; float bottomRightY; float sourceBottomRightX; float sourceBottomRightY; if (alignmentPattern != nil) { bottomRightX = alignmentPattern.x; bottomRightY = alignmentPattern.y; sourceBottomRightX = dimMinusThree - 3.0f; sourceBottomRightY = sourceBottomRightX; } else { bottomRightX = (topRight.x - topLeft.x) + bottomLeft.x; bottomRightY = (topRight.y - topLeft.y) + bottomLeft.y; sourceBottomRightX = dimMinusThree; sourceBottomRightY = dimMinusThree; } return [ZXPerspectiveTransform quadrilateralToQuadrilateral:3.5f y0:3.5f x1:dimMinusThree y1:3.5f x2:sourceBottomRightX y2:sourceBottomRightY x3:3.5f y3:dimMinusThree x0p:topLeft.x y0p:topLeft.y x1p:topRight.x y1p:topRight.y x2p:bottomRightX y2p:bottomRightY x3p:bottomLeft.x y3p:bottomLeft.y]; } - (ZXBitMatrix *)sampleGrid:(ZXBitMatrix *)anImage transform:(ZXPerspectiveTransform *)transform dimension:(int)dimension error:(NSError **)error { ZXGridSampler *sampler = [ZXGridSampler instance]; return [sampler sampleGrid:anImage dimensionX:dimension dimensionY:dimension transform:transform error:error]; } /** * Computes the dimension (number of modules on a size) of the QR Code based on the position * of the finder patterns and estimated module size. Returns -1 on an error. */ + (int)computeDimension:(ZXResultPoint *)topLeft topRight:(ZXResultPoint *)topRight bottomLeft:(ZXResultPoint *)bottomLeft moduleSize:(float)moduleSize error:(NSError **)error { int tltrCentersDimension = [ZXMathUtils round:[ZXResultPoint distance:topLeft pattern2:topRight] / moduleSize]; int tlblCentersDimension = [ZXMathUtils round:[ZXResultPoint distance:topLeft pattern2:bottomLeft] / moduleSize]; int dimension = ((tltrCentersDimension + tlblCentersDimension) / 2) + 7; switch (dimension & 0x03) { case 0: dimension++; break; case 2: dimension--; break; case 3: if (error) *error = ZXNotFoundErrorInstance(); return -1; } return dimension; } /** * Computes an average estimated module size based on estimated derived from the positions * of the three finder patterns. */ - (float)calculateModuleSize:(ZXResultPoint *)topLeft topRight:(ZXResultPoint *)topRight bottomLeft:(ZXResultPoint *)bottomLeft { return ([self calculateModuleSizeOneWay:topLeft otherPattern:topRight] + [self calculateModuleSizeOneWay:topLeft otherPattern:bottomLeft]) / 2.0f; } /** * Estimates module size based on two finder patterns -- it uses * sizeOfBlackWhiteBlackRunBothWays:fromY:toX:toY: to figure the * width of each, measuring along the axis between their centers. */ - (float)calculateModuleSizeOneWay:(ZXResultPoint *)pattern otherPattern:(ZXResultPoint *)otherPattern { float moduleSizeEst1 = [self sizeOfBlackWhiteBlackRunBothWays:(int)[pattern x] fromY:(int)[pattern y] toX:(int)[otherPattern x] toY:(int)[otherPattern y]]; float moduleSizeEst2 = [self sizeOfBlackWhiteBlackRunBothWays:(int)[otherPattern x] fromY:(int)[otherPattern y] toX:(int)[pattern x] toY:(int)[pattern y]]; if (isnan(moduleSizeEst1)) { return moduleSizeEst2 / 7.0f; } if (isnan(moduleSizeEst2)) { return moduleSizeEst1 / 7.0f; } return (moduleSizeEst1 + moduleSizeEst2) / 14.0f; } /** * See sizeOfBlackWhiteBlackRun:fromY:toX:toY:

computes the total width of * a finder pattern by looking for a black-white-black run from the center in the direction * of another point (another finder pattern center), and in the opposite direction too.

*/ - (float)sizeOfBlackWhiteBlackRunBothWays:(int)fromX fromY:(int)fromY toX:(int)toX toY:(int)toY { float result = [self sizeOfBlackWhiteBlackRun:fromX fromY:fromY toX:toX toY:toY]; // Now count other way -- don't run off image though of course float scale = 1.0f; int otherToX = fromX - (toX - fromX); if (otherToX < 0) { scale = (float)fromX / (float)(fromX - otherToX); otherToX = 0; } else if (otherToX >= self.image.width) { scale = (float)(self.image.width - 1 - fromX) / (float)(otherToX - fromX); otherToX = self.image.width - 1; } int otherToY = (int)(fromY - (toY - fromY) * scale); scale = 1.0f; if (otherToY < 0) { scale = (float)fromY / (float)(fromY - otherToY); otherToY = 0; } else if (otherToY >= self.image.height) { scale = (float)(self.image.height - 1 - fromY) / (float)(otherToY - fromY); otherToY = self.image.height - 1; } otherToX = (int)(fromX + (otherToX - fromX) * scale); result += [self sizeOfBlackWhiteBlackRun:fromX fromY:fromY toX:otherToX toY:otherToY]; // Middle pixel is double-counted this way; subtract 1 return result - 1.0f; } /** * This method traces a line from a point in the image, in the direction towards another point. * It begins in a black region, and keeps going until it finds white, then black, then white again. * It reports the distance from the start to this point. * * This is used when figuring out how wide a finder pattern is, when the finder pattern * may be skewed or rotated. */ - (float)sizeOfBlackWhiteBlackRun:(int)fromX fromY:(int)fromY toX:(int)toX toY:(int)toY { // Mild variant of Bresenham's algorithm; // see http://en.wikipedia.org/wiki/Bresenham's_line_algorithm BOOL steep = abs(toY - fromY) > abs(toX - fromX); if (steep) { int temp = fromX; fromX = fromY; fromY = temp; temp = toX; toX = toY; toY = temp; } int dx = abs(toX - fromX); int dy = abs(toY - fromY); int error = -dx / 2; int xstep = fromX < toX ? 1 : -1; int ystep = fromY < toY ? 1 : -1; // In black pixels, looking for white, first or second time. int state = 0; // Loop up until x == toX, but not beyond int xLimit = toX + xstep; for (int x = fromX, y = fromY; x != xLimit; x += xstep) { int realX = steep ? y : x; int realY = steep ? x : y; // Does current pixel mean we have moved white to black or vice versa? // Scanning black in state 0,2 and white in state 1, so if we find the wrong // color, advance to next state or end if we are in state 2 already if ((state == 1) == [self.image getX:realX y:realY]) { if (state == 2) { return [ZXMathUtils distanceInt:x aY:y bX:fromX bY:fromY]; } state++; } error += dy; if (error > 0) { if (y == toY) { break; } y += ystep; error -= dx; } } // Found black-white-black; give the benefit of the doubt that the next pixel outside the image // is "white" so this last point at (toX+xStep,toY) is the right ending. This is really a // small approximation; (toX+xStep,toY+yStep) might be really correct. Ignore this. if (state == 2) { return [ZXMathUtils distanceInt:toX + xstep aY:toY bX:fromX bY:fromY]; } // else we didn't find even black-white-black; no estimate is really possible return NAN; } - (ZXQRCodeAlignmentPattern *)findAlignmentInRegion:(float)overallEstModuleSize estAlignmentX:(int)estAlignmentX estAlignmentY:(int)estAlignmentY allowanceFactor:(float)allowanceFactor error:(NSError **)error { int allowance = (int)(allowanceFactor * overallEstModuleSize); int alignmentAreaLeftX = MAX(0, estAlignmentX - allowance); int alignmentAreaRightX = MIN(self.image.width - 1, estAlignmentX + allowance); if (alignmentAreaRightX - alignmentAreaLeftX < overallEstModuleSize * 3) { if (error) *error = ZXNotFoundErrorInstance(); return nil; } int alignmentAreaTopY = MAX(0, estAlignmentY - allowance); int alignmentAreaBottomY = MIN(self.image.height - 1, estAlignmentY + allowance); if (alignmentAreaBottomY - alignmentAreaTopY < overallEstModuleSize * 3) { if (error) *error = ZXNotFoundErrorInstance(); return nil; } ZXQRCodeAlignmentPatternFinder *alignmentFinder = [[ZXQRCodeAlignmentPatternFinder alloc] initWithImage:self.image startX:alignmentAreaLeftX startY:alignmentAreaTopY width:alignmentAreaRightX - alignmentAreaLeftX height:alignmentAreaBottomY - alignmentAreaTopY moduleSize:overallEstModuleSize resultPointCallback:self.resultPointCallback]; return [alignmentFinder findWithError:error]; } @end