/*
|
* 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 "ZXErrors.h"
|
#import "ZXMathUtils.h"
|
#import "ZXWhiteRectangleDetector.h"
|
|
@interface ZXWhiteRectangleDetector ()
|
|
@property (nonatomic, strong, readonly) ZXBitMatrix *image;
|
@property (nonatomic, assign, readonly) int height;
|
@property (nonatomic, assign, readonly) int width;
|
@property (nonatomic, assign, readonly) int leftInit;
|
@property (nonatomic, assign, readonly) int rightInit;
|
@property (nonatomic, assign, readonly) int downInit;
|
@property (nonatomic, assign, readonly) int upInit;
|
|
@end
|
|
const int ZX_INIT_SIZE = 10;
|
const int ZX_CORR = 1;
|
|
@implementation ZXWhiteRectangleDetector
|
|
- (id)initWithImage:(ZXBitMatrix *)image error:(NSError **)error {
|
return [self initWithImage:image initSize:ZX_INIT_SIZE x:image.width / 2 y:image.height / 2 error:error];
|
}
|
|
- (id)initWithImage:(ZXBitMatrix *)image initSize:(int)initSize x:(int)x y:(int)y error:(NSError **)error {
|
if (self = [super init]) {
|
_image = image;
|
_height = image.height;
|
_width = image.width;
|
int halfsize = initSize / 2;
|
_leftInit = x - halfsize;
|
_rightInit = x + halfsize;
|
_upInit = y - halfsize;
|
_downInit = y + halfsize;
|
if (_upInit < 0 || _leftInit < 0 || _downInit >= _height || _rightInit >= _width) {
|
if (error) *error = ZXNotFoundErrorInstance();
|
return nil;
|
}
|
}
|
|
return self;
|
}
|
|
- (NSArray *)detectWithError:(NSError **)error {
|
int left = self.leftInit;
|
int right = self.rightInit;
|
int up = self.upInit;
|
int down = self.downInit;
|
BOOL sizeExceeded = NO;
|
BOOL aBlackPointFoundOnBorder = YES;
|
BOOL atLeastOneBlackPointFoundOnBorder = NO;
|
|
BOOL atLeastOneBlackPointFoundOnRight = NO;
|
BOOL atLeastOneBlackPointFoundOnBottom = NO;
|
BOOL atLeastOneBlackPointFoundOnLeft = NO;
|
BOOL atLeastOneBlackPointFoundOnTop = NO;
|
|
while (aBlackPointFoundOnBorder) {
|
aBlackPointFoundOnBorder = NO;
|
|
// .....
|
// . |
|
// .....
|
BOOL rightBorderNotWhite = YES;
|
while ((rightBorderNotWhite || !atLeastOneBlackPointFoundOnRight) && right < self.width) {
|
rightBorderNotWhite = [self containsBlackPoint:up b:down fixed:right horizontal:NO];
|
if (rightBorderNotWhite) {
|
right++;
|
aBlackPointFoundOnBorder = YES;
|
atLeastOneBlackPointFoundOnRight = YES;
|
} else if (!atLeastOneBlackPointFoundOnRight) {
|
right++;
|
}
|
}
|
|
if (right >= self.width) {
|
sizeExceeded = YES;
|
break;
|
}
|
|
// .....
|
// . .
|
// .___.
|
BOOL bottomBorderNotWhite = YES;
|
while ((bottomBorderNotWhite || !atLeastOneBlackPointFoundOnBottom) && down < self.height) {
|
bottomBorderNotWhite = [self containsBlackPoint:left b:right fixed:down horizontal:YES];
|
if (bottomBorderNotWhite) {
|
down++;
|
aBlackPointFoundOnBorder = YES;
|
atLeastOneBlackPointFoundOnBottom = YES;
|
} else if (!atLeastOneBlackPointFoundOnBottom) {
|
down++;
|
}
|
}
|
|
if (down >= self.height) {
|
sizeExceeded = YES;
|
break;
|
}
|
|
// .....
|
// | .
|
// .....
|
BOOL leftBorderNotWhite = YES;
|
while ((leftBorderNotWhite || !atLeastOneBlackPointFoundOnLeft) && left >= 0) {
|
leftBorderNotWhite = [self containsBlackPoint:up b:down fixed:left horizontal:NO];
|
if (leftBorderNotWhite) {
|
left--;
|
aBlackPointFoundOnBorder = YES;
|
atLeastOneBlackPointFoundOnLeft = YES;
|
} else if (!atLeastOneBlackPointFoundOnLeft) {
|
left--;
|
}
|
}
|
|
if (left < 0) {
|
sizeExceeded = YES;
|
break;
|
}
|
|
// .___.
|
// . .
|
// .....
|
BOOL topBorderNotWhite = YES;
|
while ((topBorderNotWhite || !atLeastOneBlackPointFoundOnTop) && up >= 0) {
|
topBorderNotWhite = [self containsBlackPoint:left b:right fixed:up horizontal:YES];
|
if (topBorderNotWhite) {
|
up--;
|
aBlackPointFoundOnBorder = YES;
|
atLeastOneBlackPointFoundOnTop = YES;
|
} else if (!atLeastOneBlackPointFoundOnTop) {
|
up--;
|
}
|
}
|
|
if (up < 0) {
|
sizeExceeded = YES;
|
break;
|
}
|
|
if (aBlackPointFoundOnBorder) {
|
atLeastOneBlackPointFoundOnBorder = YES;
|
}
|
}
|
|
if (!sizeExceeded && atLeastOneBlackPointFoundOnBorder) {
|
int maxSize = right - left;
|
|
ZXResultPoint *z = nil;
|
for (int i = 1; i < maxSize; i++) {
|
z = [self blackPointOnSegment:left aY:down - i bX:left + i bY:down];
|
if (z != nil) {
|
break;
|
}
|
}
|
|
if (z == nil) {
|
if (error) *error = ZXNotFoundErrorInstance();
|
return nil;
|
}
|
|
ZXResultPoint *t = nil;
|
for (int i = 1; i < maxSize; i++) {
|
t = [self blackPointOnSegment:left aY:up + i bX:left + i bY:up];
|
if (t != nil) {
|
break;
|
}
|
}
|
|
if (t == nil) {
|
if (error) *error = ZXNotFoundErrorInstance();
|
return nil;
|
}
|
|
ZXResultPoint *x = nil;
|
for (int i = 1; i < maxSize; i++) {
|
x = [self blackPointOnSegment:right aY:up + i bX:right - i bY:up];
|
if (x != nil) {
|
break;
|
}
|
}
|
|
if (x == nil) {
|
if (error) *error = ZXNotFoundErrorInstance();
|
return nil;
|
}
|
|
ZXResultPoint *y = nil;
|
for (int i = 1; i < maxSize; i++) {
|
y = [self blackPointOnSegment:right aY:down - i bX:right - i bY:down];
|
if (y != nil) {
|
break;
|
}
|
}
|
|
if (y == nil) {
|
if (error) *error = ZXNotFoundErrorInstance();
|
return nil;
|
}
|
return [self centerEdges:y z:z x:x t:t];
|
} else {
|
if (error) *error = ZXNotFoundErrorInstance();
|
return nil;
|
}
|
}
|
|
|
- (ZXResultPoint *)blackPointOnSegment:(float)aX aY:(float)aY bX:(float)bX bY:(float)bY {
|
int dist = [ZXMathUtils round:[ZXMathUtils distance:aX aY:aY bX:bX bY:bY]];
|
float xStep = (bX - aX) / dist;
|
float yStep = (bY - aY) / dist;
|
|
for (int i = 0; i < dist; i++) {
|
int x = [ZXMathUtils round:aX + i * xStep];
|
int y = [ZXMathUtils round:aY + i * yStep];
|
if ([self.image getX:x y:y]) {
|
return [[ZXResultPoint alloc] initWithX:x y:y];
|
}
|
}
|
|
return nil;
|
}
|
|
/**
|
* recenters the points of a constant distance towards the center
|
*
|
* @param y bottom most point
|
* @param z left most point
|
* @param x right most point
|
* @param t top most point
|
* @return ZXResultPoint array describing the corners of the rectangular
|
* region. The first and last points are opposed on the diagonal, as
|
* are the second and third. The first point will be the topmost
|
* point and the last, the bottommost. The second point will be
|
* leftmost and the third, the rightmost
|
*/
|
- (NSArray *)centerEdges:(ZXResultPoint *)y z:(ZXResultPoint *)z x:(ZXResultPoint *)x t:(ZXResultPoint *)t {
|
//
|
// t t
|
// z x
|
// x OR z
|
// y y
|
//
|
|
float yi = y.x;
|
float yj = y.y;
|
float zi = z.x;
|
float zj = z.y;
|
float xi = x.x;
|
float xj = x.y;
|
float ti = t.x;
|
float tj = t.y;
|
|
if (yi < self.width / 2.0f) {
|
return @[[[ZXResultPoint alloc] initWithX:ti - ZX_CORR y:tj + ZX_CORR],
|
[[ZXResultPoint alloc] initWithX:zi + ZX_CORR y:zj + ZX_CORR],
|
[[ZXResultPoint alloc] initWithX:xi - ZX_CORR y:xj - ZX_CORR],
|
[[ZXResultPoint alloc] initWithX:yi + ZX_CORR y:yj - ZX_CORR]];
|
} else {
|
return @[[[ZXResultPoint alloc] initWithX:ti + ZX_CORR y:tj + ZX_CORR],
|
[[ZXResultPoint alloc] initWithX:zi + ZX_CORR y:zj - ZX_CORR],
|
[[ZXResultPoint alloc] initWithX:xi - ZX_CORR y:xj + ZX_CORR],
|
[[ZXResultPoint alloc] initWithX:yi - ZX_CORR y:yj - ZX_CORR]];
|
}
|
}
|
|
/**
|
* Determines whether a segment contains a black point
|
*
|
* @param a min value of the scanned coordinate
|
* @param b max value of the scanned coordinate
|
* @param fixed value of fixed coordinate
|
* @param horizontal set to true if scan must be horizontal, false if vertical
|
* @return true if a black point has been found, else false.
|
*/
|
- (BOOL)containsBlackPoint:(int)a b:(int)b fixed:(int)fixed horizontal:(BOOL)horizontal {
|
if (horizontal) {
|
for (int x = a; x <= b; x++) {
|
if ([self.image getX:x y:fixed]) {
|
return YES;
|
}
|
}
|
} else {
|
for (int y = a; y <= b; y++) {
|
if ([self.image getX:fixed y:y]) {
|
return YES;
|
}
|
}
|
}
|
|
return NO;
|
}
|
|
@end
|