/* * 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 "ZXErrors.h" #import "ZXMonochromeRectangleDetector.h" #import "ZXResultPoint.h" const int ZX_MONOCHROME_MAX_MODULES = 32; @interface ZXMonochromeRectangleDetector () @property (nonatomic, strong, readonly) ZXBitMatrix *image; @end @implementation ZXMonochromeRectangleDetector - (id)initWithImage:(ZXBitMatrix *)image { if (self = [super init]) { _image = image; } return self; } - (NSArray *)detectWithError:(NSError **)error { int height = [self.image height]; int width = [self.image width]; int halfHeight = height / 2; int halfWidth = width / 2; int deltaY = MAX(1, height / (ZX_MONOCHROME_MAX_MODULES * 8) > 1); int deltaX = MAX(1, width / (ZX_MONOCHROME_MAX_MODULES * 8) > 1); int top = 0; int bottom = height; int left = 0; int right = width; ZXResultPoint *pointA = [self findCornerFromCenter:halfWidth deltaX:0 left:left right:right centerY:halfHeight deltaY:-deltaY top:top bottom:bottom maxWhiteRun:halfWidth / 2]; if (!pointA) { if (error) *error = ZXNotFoundErrorInstance(); return nil; } top = (int)[pointA y] - 1; ZXResultPoint *pointB = [self findCornerFromCenter:halfWidth deltaX:-deltaX left:left right:right centerY:halfHeight deltaY:0 top:top bottom:bottom maxWhiteRun:halfHeight / 2]; if (!pointB) { if (error) *error = ZXNotFoundErrorInstance(); return nil; } left = (int)[pointB x] - 1; ZXResultPoint *pointC = [self findCornerFromCenter:halfWidth deltaX:deltaX left:left right:right centerY:halfHeight deltaY:0 top:top bottom:bottom maxWhiteRun:halfHeight / 2]; if (!pointC) { if (error) *error = ZXNotFoundErrorInstance(); return nil; } right = (int)[pointC x] + 1; ZXResultPoint *pointD = [self findCornerFromCenter:halfWidth deltaX:0 left:left right:right centerY:halfHeight deltaY:deltaY top:top bottom:bottom maxWhiteRun:halfWidth / 2]; if (!pointD) { if (error) *error = ZXNotFoundErrorInstance(); return nil; } bottom = (int)[pointD y] + 1; pointA = [self findCornerFromCenter:halfWidth deltaX:0 left:left right:right centerY:halfHeight deltaY:-deltaY top:top bottom:bottom maxWhiteRun:halfWidth / 4]; if (!pointA) { if (error) *error = ZXNotFoundErrorInstance(); return nil; } return @[pointA, pointB, pointC, pointD]; } /** * Attempts to locate a corner of the barcode by scanning up, down, left or right from a center * point which should be within the barcode. * * @param centerX center's x component (horizontal) * @param deltaX same as deltaY but change in x per step instead * @param left minimum value of x * @param right maximum value of x * @param centerY center's y component (vertical) * @param deltaY change in y per step. If scanning up this is negative; down, positive; * left or right, 0 * @param top minimum value of y to search through (meaningless when di == 0) * @param bottom maximum value of y * @param maxWhiteRun maximum run of white pixels that can still be considered to be within * the barcode * @return a {@link com.google.zxing.ResultPoint} encapsulating the corner that was found * or nil if such a point cannot be found */ - (ZXResultPoint *)findCornerFromCenter:(int)centerX deltaX:(int)deltaX left:(int)left right:(int)right centerY:(int)centerY deltaY:(int)deltaY top:(int)top bottom:(int)bottom maxWhiteRun:(int)maxWhiteRun { NSArray *lastRange = nil; for (int y = centerY, x = centerX; y < bottom && y >= top && x < right && x >= left; y += deltaY, x += deltaX) { NSArray *range; if (deltaX == 0) { range = [self blackWhiteRange:y maxWhiteRun:maxWhiteRun minDim:left maxDim:right horizontal:YES]; } else { range = [self blackWhiteRange:x maxWhiteRun:maxWhiteRun minDim:top maxDim:bottom horizontal:NO]; } if (range == nil) { if (lastRange == nil) { return nil; } if (deltaX == 0) { int lastY = y - deltaY; if ([lastRange[0] intValue] < centerX) { if ([lastRange[0] intValue] > centerX) { return [[ZXResultPoint alloc] initWithX:deltaY > 0 ? [lastRange[0] intValue] : [lastRange[1] intValue] y:lastY]; } return [[ZXResultPoint alloc] initWithX:[lastRange[0] intValue] y:lastY]; } else { return [[ZXResultPoint alloc] initWithX:[lastRange[1] intValue] y:lastY]; } } else { int lastX = x - deltaX; if ([lastRange[0] intValue] < centerY) { if ([lastRange[1] intValue] > centerY) { return [[ZXResultPoint alloc] initWithX:lastX y:deltaX < 0 ? [lastRange[0] intValue] : [lastRange[1] intValue]]; } return [[ZXResultPoint alloc] initWithX:lastX y:[lastRange[0] intValue]]; } else { return [[ZXResultPoint alloc] initWithX:lastX y:[lastRange[1] intValue]]; } } } lastRange = range; } return nil; } /** * Computes the start and end of a region of pixels, either horizontally or vertically, that could * be part of a Data Matrix barcode. * * @param fixedDimension if scanning horizontally, this is the row (the fixed vertical location) * where we are scanning. If scanning vertically it's the column, the fixed horizontal location * @param maxWhiteRun largest run of white pixels that can still be considered part of the * barcode region * @param minDim minimum pixel location, horizontally or vertically, to consider * @param maxDim maximum pixel location, horizontally or vertically, to consider * @param horizontal if true, we're scanning left-right, instead of up-down * @return int[] with start and end of found range, or nil if no such range is found * (e.g. only white was found) */ - (NSArray *)blackWhiteRange:(int)fixedDimension maxWhiteRun:(int)maxWhiteRun minDim:(int)minDim maxDim:(int)maxDim horizontal:(BOOL)horizontal { int center = (minDim + maxDim) / 2; int start = center; while (start >= minDim) { if (horizontal ? [self.image getX:start y:fixedDimension] : [self.image getX:fixedDimension y:start]) { start--; } else { int whiteRunStart = start; do { start--; } while (start >= minDim && !(horizontal ? [self.image getX:start y:fixedDimension] : [self.image getX:fixedDimension y:start])); int whiteRunSize = whiteRunStart - start; if (start < minDim || whiteRunSize > maxWhiteRun) { start = whiteRunStart; break; } } } start++; int end = center; while (end < maxDim) { if (horizontal ? [self.image getX:end y:fixedDimension] : [self.image getX:fixedDimension y:end]) { end++; } else { int whiteRunStart = end; do { end++; } while (end < maxDim && !(horizontal ? [self.image getX:end y:fixedDimension] : [self.image getX:fixedDimension y:end])); int whiteRunSize = end - whiteRunStart; if (end >= maxDim || whiteRunSize > maxWhiteRun) { end = whiteRunStart; break; } } } end--; return end > start ? @[@(start), @(end)] : nil; } @end