/*
|
* 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 "ZXDataMatrixDetector.h"
|
#import "ZXDetectorResult.h"
|
#import "ZXErrors.h"
|
#import "ZXGridSampler.h"
|
#import "ZXMathUtils.h"
|
#import "ZXResultPoint.h"
|
#import "ZXWhiteRectangleDetector.h"
|
|
/**
|
* Simply encapsulates two points and a number of transitions between them.
|
*/
|
@interface ZXResultPointsAndTransitions : NSObject
|
|
@property (nonatomic, strong, readonly) ZXResultPoint *from;
|
@property (nonatomic, strong, readonly) ZXResultPoint *to;
|
@property (nonatomic, assign, readonly) int transitions;
|
|
@end
|
|
@implementation ZXResultPointsAndTransitions
|
|
- (id)initWithFrom:(ZXResultPoint *)from to:(ZXResultPoint *)to transitions:(int)transitions {
|
if (self = [super init]) {
|
_from = from;
|
_to = to;
|
_transitions = transitions;
|
}
|
|
return self;
|
}
|
|
- (NSString *)description {
|
return [NSString stringWithFormat:@"%@/%@/%d", self.from, self.to, self.transitions];
|
}
|
|
- (NSComparisonResult)compare:(ZXResultPointsAndTransitions *)otherObject {
|
return [@(self.transitions) compare:@(otherObject.transitions)];
|
}
|
|
@end
|
|
|
@interface ZXDataMatrixDetector ()
|
|
@property (nonatomic, strong, readonly) ZXBitMatrix *image;
|
@property (nonatomic, strong, readonly) ZXWhiteRectangleDetector *rectangleDetector;
|
|
@end
|
|
@implementation ZXDataMatrixDetector
|
|
- (id)initWithImage:(ZXBitMatrix *)image error:(NSError **)error {
|
if (self = [super init]) {
|
_image = image;
|
_rectangleDetector = [[ZXWhiteRectangleDetector alloc] initWithImage:_image error:error];
|
if (!_rectangleDetector) {
|
return nil;
|
}
|
}
|
|
return self;
|
}
|
|
- (ZXDetectorResult *)detectWithError:(NSError **)error {
|
NSArray *cornerPoints = [self.rectangleDetector detectWithError:error];
|
if (!cornerPoints) {
|
return nil;
|
}
|
ZXResultPoint *pointA = cornerPoints[0];
|
ZXResultPoint *pointB = cornerPoints[1];
|
ZXResultPoint *pointC = cornerPoints[2];
|
ZXResultPoint *pointD = cornerPoints[3];
|
|
NSMutableArray *transitions = [NSMutableArray arrayWithCapacity:4];
|
[transitions addObject:[self transitionsBetween:pointA to:pointB]];
|
[transitions addObject:[self transitionsBetween:pointA to:pointC]];
|
[transitions addObject:[self transitionsBetween:pointB to:pointD]];
|
[transitions addObject:[self transitionsBetween:pointC to:pointD]];
|
[transitions sortUsingSelector:@selector(compare:)];
|
|
ZXResultPointsAndTransitions *lSideOne = (ZXResultPointsAndTransitions *)transitions[0];
|
ZXResultPointsAndTransitions *lSideTwo = (ZXResultPointsAndTransitions *)transitions[1];
|
|
NSMutableDictionary *pointCount = [NSMutableDictionary dictionary];
|
[self increment:pointCount key:[lSideOne from]];
|
[self increment:pointCount key:[lSideOne to]];
|
[self increment:pointCount key:[lSideTwo from]];
|
[self increment:pointCount key:[lSideTwo to]];
|
|
ZXResultPoint *maybeTopLeft = nil;
|
ZXResultPoint *bottomLeft = nil;
|
ZXResultPoint *maybeBottomRight = nil;
|
for (ZXResultPoint *point in [pointCount allKeys]) {
|
NSNumber *value = pointCount[point];
|
if ([value intValue] == 2) {
|
bottomLeft = point;
|
} else {
|
if (maybeTopLeft == nil) {
|
maybeTopLeft = point;
|
} else {
|
maybeBottomRight = point;
|
}
|
}
|
}
|
|
if (maybeTopLeft == nil || bottomLeft == nil || maybeBottomRight == nil) {
|
if (error) *error = ZXNotFoundErrorInstance();
|
return nil;
|
}
|
|
NSMutableArray *corners = [NSMutableArray arrayWithObjects:maybeTopLeft, bottomLeft, maybeBottomRight, nil];
|
[ZXResultPoint orderBestPatterns:corners];
|
|
ZXResultPoint *bottomRight = corners[0];
|
bottomLeft = corners[1];
|
ZXResultPoint *topLeft = corners[2];
|
|
ZXResultPoint *topRight;
|
if (!pointCount[pointA]) {
|
topRight = pointA;
|
} else if (!pointCount[pointB]) {
|
topRight = pointB;
|
} else if (!pointCount[pointC]) {
|
topRight = pointC;
|
} else {
|
topRight = pointD;
|
}
|
|
int dimensionTop = [[self transitionsBetween:topLeft to:topRight] transitions];
|
int dimensionRight = [[self transitionsBetween:bottomRight to:topRight] transitions];
|
|
if ((dimensionTop & 0x01) == 1) {
|
dimensionTop++;
|
}
|
dimensionTop += 2;
|
|
if ((dimensionRight & 0x01) == 1) {
|
dimensionRight++;
|
}
|
dimensionRight += 2;
|
|
ZXBitMatrix *bits;
|
ZXResultPoint *correctedTopRight;
|
|
if (4 * dimensionTop >= 7 * dimensionRight || 4 * dimensionRight >= 7 * dimensionTop) {
|
correctedTopRight = [self correctTopRightRectangular:bottomLeft bottomRight:bottomRight topLeft:topLeft topRight:topRight dimensionTop:dimensionTop dimensionRight:dimensionRight];
|
if (correctedTopRight == nil) {
|
correctedTopRight = topRight;
|
}
|
|
dimensionTop = [[self transitionsBetween:topLeft to:correctedTopRight] transitions];
|
dimensionRight = [[self transitionsBetween:bottomRight to:correctedTopRight] transitions];
|
|
if ((dimensionTop & 0x01) == 1) {
|
dimensionTop++;
|
}
|
|
if ((dimensionRight & 0x01) == 1) {
|
dimensionRight++;
|
}
|
|
bits = [self sampleGrid:self.image topLeft:topLeft bottomLeft:bottomLeft bottomRight:bottomRight topRight:correctedTopRight dimensionX:dimensionTop dimensionY:dimensionRight error:error];
|
if (!bits) {
|
return nil;
|
}
|
} else {
|
int dimension = MIN(dimensionRight, dimensionTop);
|
correctedTopRight = [self correctTopRight:bottomLeft bottomRight:bottomRight topLeft:topLeft topRight:topRight dimension:dimension];
|
if (correctedTopRight == nil) {
|
correctedTopRight = topRight;
|
}
|
|
int dimensionCorrected = MAX([[self transitionsBetween:topLeft to:correctedTopRight] transitions], [[self transitionsBetween:bottomRight to:correctedTopRight] transitions]);
|
dimensionCorrected++;
|
if ((dimensionCorrected & 0x01) == 1) {
|
dimensionCorrected++;
|
}
|
|
bits = [self sampleGrid:self.image topLeft:topLeft bottomLeft:bottomLeft bottomRight:bottomRight topRight:correctedTopRight dimensionX:dimensionCorrected dimensionY:dimensionCorrected error:error];
|
if (!bits) {
|
return nil;
|
}
|
}
|
return [[ZXDetectorResult alloc] initWithBits:bits points:@[topLeft, bottomLeft, bottomRight, correctedTopRight]];
|
}
|
|
/**
|
* Calculates the position of the white top right module using the output of the rectangle detector
|
* for a rectangular matrix
|
*/
|
- (ZXResultPoint *)correctTopRightRectangular:(ZXResultPoint *)bottomLeft bottomRight:(ZXResultPoint *)bottomRight
|
topLeft:(ZXResultPoint *)topLeft topRight:(ZXResultPoint *)topRight
|
dimensionTop:(int)dimensionTop dimensionRight:(int)dimensionRight {
|
float corr = [self distance:bottomLeft b:bottomRight] / (float)dimensionTop;
|
int norm = [self distance:topLeft b:topRight];
|
float cos = ([topRight x] - [topLeft x]) / norm;
|
float sin = ([topRight y] - [topLeft y]) / norm;
|
|
ZXResultPoint *c1 = [[ZXResultPoint alloc] initWithX:[topRight x] + corr * cos y:[topRight y] + corr * sin];
|
|
corr = [self distance:bottomLeft b:topLeft] / (float)dimensionRight;
|
norm = [self distance:bottomRight b:topRight];
|
cos = ([topRight x] - [bottomRight x]) / norm;
|
sin = ([topRight y] - [bottomRight y]) / norm;
|
|
ZXResultPoint *c2 = [[ZXResultPoint alloc] initWithX:[topRight x] + corr * cos y:[topRight y] + corr * sin];
|
|
if (![self isValid:c1]) {
|
if ([self isValid:c2]) {
|
return c2;
|
}
|
return nil;
|
} else if (![self isValid:c2]) {
|
return c1;
|
}
|
|
int l1 = abs(dimensionTop - [[self transitionsBetween:topLeft to:c1] transitions]) + abs(dimensionRight - [[self transitionsBetween:bottomRight to:c1] transitions]);
|
int l2 = abs(dimensionTop - [[self transitionsBetween:topLeft to:c2] transitions]) + abs(dimensionRight - [[self transitionsBetween:bottomRight to:c2] transitions]);
|
|
if (l1 <= l2) {
|
return c1;
|
}
|
|
return c2;
|
}
|
|
/**
|
* Calculates the position of the white top right module using the output of the rectangle detector
|
* for a square matrix
|
*/
|
- (ZXResultPoint *)correctTopRight:(ZXResultPoint *)bottomLeft bottomRight:(ZXResultPoint *)bottomRight
|
topLeft:(ZXResultPoint *)topLeft topRight:(ZXResultPoint *)topRight dimension:(int)dimension {
|
float corr = [self distance:bottomLeft b:bottomRight] / (float)dimension;
|
int norm = [self distance:topLeft b:topRight];
|
float cos = ([topRight x] - [topLeft x]) / norm;
|
float sin = ([topRight y] - [topLeft y]) / norm;
|
|
ZXResultPoint *c1 = [[ZXResultPoint alloc] initWithX:[topRight x] + corr * cos y:[topRight y] + corr * sin];
|
|
corr = [self distance:bottomLeft b:topLeft] / (float)dimension;
|
norm = [self distance:bottomRight b:topRight];
|
cos = ([topRight x] - [bottomRight x]) / norm;
|
sin = ([topRight y] - [bottomRight y]) / norm;
|
|
ZXResultPoint *c2 = [[ZXResultPoint alloc] initWithX:[topRight x] + corr * cos y:[topRight y] + corr * sin];
|
|
if (![self isValid:c1]) {
|
if ([self isValid:c2]) {
|
return c2;
|
}
|
return nil;
|
} else if (![self isValid:c2]) {
|
return c1;
|
}
|
|
int l1 = abs([[self transitionsBetween:topLeft to:c1] transitions] - [[self transitionsBetween:bottomRight to:c1] transitions]);
|
int l2 = abs([[self transitionsBetween:topLeft to:c2] transitions] - [[self transitionsBetween:bottomRight to:c2] transitions]);
|
|
return l1 <= l2 ? c1 : c2;
|
}
|
|
- (BOOL) isValid:(ZXResultPoint *)p {
|
return [p x] >= 0 && [p x] < self.image.width && [p y] > 0 && [p y] < self.image.height;
|
}
|
|
- (int)distance:(ZXResultPoint *)a b:(ZXResultPoint *)b {
|
return [ZXMathUtils round:[ZXResultPoint distance:a pattern2:b]];
|
}
|
|
/**
|
* Increments the Integer associated with a key by one.
|
*/
|
- (void)increment:(NSMutableDictionary *)table key:(ZXResultPoint *)key {
|
NSNumber *value = table[key];
|
table[key] = value == nil ? @1 : @([value intValue] + 1);
|
}
|
|
- (ZXBitMatrix *)sampleGrid:(ZXBitMatrix *)image
|
topLeft:(ZXResultPoint *)topLeft
|
bottomLeft:(ZXResultPoint *)bottomLeft
|
bottomRight:(ZXResultPoint *)bottomRight
|
topRight:(ZXResultPoint *)topRight
|
dimensionX:(int)dimensionX
|
dimensionY:(int)dimensionY
|
error:(NSError **)error {
|
ZXGridSampler *sampler = [ZXGridSampler instance];
|
return [sampler sampleGrid:image
|
dimensionX:dimensionX dimensionY:dimensionY
|
p1ToX:0.5f p1ToY:0.5f
|
p2ToX:dimensionX - 0.5f p2ToY:0.5f
|
p3ToX:dimensionX - 0.5f p3ToY:dimensionY - 0.5f
|
p4ToX:0.5f p4ToY:dimensionY - 0.5f
|
p1FromX:[topLeft x] p1FromY:[topLeft y]
|
p2FromX:[topRight x] p2FromY:[topRight y]
|
p3FromX:[bottomRight x] p3FromY:[bottomRight y]
|
p4FromX:[bottomLeft x] p4FromY:[bottomLeft y]
|
error:error];
|
}
|
|
/**
|
* Counts the number of black/white transitions between two points, using something like Bresenham's algorithm.
|
*/
|
- (ZXResultPointsAndTransitions *)transitionsBetween:(ZXResultPoint *)from to:(ZXResultPoint *)to {
|
int fromX = (int)[from x];
|
int fromY = (int)[from y];
|
int toX = (int)[to x];
|
int toY = (int)[to y];
|
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 ystep = fromY < toY ? 1 : -1;
|
int xstep = fromX < toX ? 1 : -1;
|
int transitions = 0;
|
BOOL inBlack = [self.image getX:steep ? fromY : fromX y:steep ? fromX : fromY];
|
for (int x = fromX, y = fromY; x != toX; x += xstep) {
|
BOOL isBlack = [self.image getX:steep ? y : x y:steep ? x : y];
|
if (isBlack != inBlack) {
|
transitions++;
|
inBlack = isBlack;
|
}
|
error += dy;
|
if (error > 0) {
|
if (y == toY) {
|
break;
|
}
|
y += ystep;
|
error -= dx;
|
}
|
}
|
return [[ZXResultPointsAndTransitions alloc] initWithFrom:from to:to transitions:transitions];
|
}
|
|
@end
|