/*
|
* 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 "ZXAztecDetector.h"
|
#import "ZXAztecDetectorResult.h"
|
#import "ZXErrors.h"
|
#import "ZXGenericGF.h"
|
#import "ZXGridSampler.h"
|
#import "ZXIntArray.h"
|
#import "ZXMathUtils.h"
|
#import "ZXReedSolomonDecoder.h"
|
#import "ZXResultPoint.h"
|
#import "ZXWhiteRectangleDetector.h"
|
|
@implementation ZXAztecPoint
|
|
- (id)initWithX:(int)x y:(int)y {
|
if (self = [super init]) {
|
_x = x;
|
_y = y;
|
}
|
return self;
|
}
|
|
- (ZXResultPoint *)toResultPoint {
|
return [[ZXResultPoint alloc] initWithX:self.x y:self.y];
|
}
|
|
- (NSString *)description {
|
return [NSString stringWithFormat:@"<%d %d>", self.x, self.y];
|
}
|
|
@end
|
|
@interface ZXAztecDetector ()
|
|
@property (nonatomic, assign, getter = isCompact) BOOL compact;
|
@property (nonatomic, strong) ZXBitMatrix *image;
|
@property (nonatomic, assign) int nbCenterLayers;
|
@property (nonatomic, assign) int nbDataBlocks;
|
@property (nonatomic, assign) int nbLayers;
|
@property (nonatomic, assign) int shift;
|
|
@end
|
|
@implementation ZXAztecDetector
|
|
- (id)initWithImage:(ZXBitMatrix *)image {
|
if (self = [super init]) {
|
_image = image;
|
}
|
return self;
|
}
|
|
- (ZXAztecDetectorResult *)detectWithError:(NSError **)error {
|
return [self detectWithMirror:NO error:error];
|
}
|
|
- (ZXAztecDetectorResult *)detectWithMirror:(BOOL)isMirror error:(NSError **)error {
|
// 1. Get the center of the aztec matrix
|
ZXAztecPoint *pCenter = [self matrixCenter];
|
if (!pCenter) {
|
if (error) *error = ZXNotFoundErrorInstance();
|
return nil;
|
}
|
|
// 2. Get the center points of the four diagonal points just outside the bull's eye
|
// [topRight, bottomRight, bottomLeft, topLeft]
|
NSMutableArray *bullsEyeCorners = [self bullsEyeCorners:pCenter];
|
if (!bullsEyeCorners) {
|
if (error) *error = ZXNotFoundErrorInstance();
|
return nil;
|
}
|
|
if (isMirror) {
|
ZXResultPoint *temp = bullsEyeCorners[0];
|
bullsEyeCorners[0] = bullsEyeCorners[2];
|
bullsEyeCorners[2] = temp;
|
}
|
|
// 3. Get the size of the matrix and other parameters from the bull's eye
|
if (![self extractParameters:bullsEyeCorners]) {
|
if (error) *error = ZXNotFoundErrorInstance();
|
return nil;
|
}
|
|
// 4. Sample the grid
|
ZXBitMatrix *bits = [self sampleGrid:self.image
|
topLeft:bullsEyeCorners[self.shift % 4]
|
topRight:bullsEyeCorners[(self.shift + 1) % 4]
|
bottomRight:bullsEyeCorners[(self.shift + 2) % 4]
|
bottomLeft:bullsEyeCorners[(self.shift + 3) % 4]];
|
if (!bits) {
|
if (error) *error = ZXNotFoundErrorInstance();
|
return nil;
|
}
|
|
// 5. Get the corners of the matrix.
|
NSArray *corners = [self matrixCornerPoints:bullsEyeCorners];
|
if (!corners) {
|
if (error) *error = ZXNotFoundErrorInstance();
|
return nil;
|
}
|
|
return [[ZXAztecDetectorResult alloc] initWithBits:bits
|
points:corners
|
compact:self.compact
|
nbDatablocks:self.nbDataBlocks
|
nbLayers:self.nbLayers];
|
}
|
|
|
/**
|
* Extracts the number of data layers and data blocks from the layer around the bull's eye
|
*/
|
- (BOOL)extractParameters:(NSArray *)bullsEyeCorners {
|
ZXResultPoint *p0 = bullsEyeCorners[0];
|
ZXResultPoint *p1 = bullsEyeCorners[1];
|
ZXResultPoint *p2 = bullsEyeCorners[2];
|
ZXResultPoint *p3 = bullsEyeCorners[3];
|
|
if (![self isValid:p0] || ![self isValid:p1] ||
|
![self isValid:p2] || ![self isValid:p3]) {
|
return NO;
|
}
|
int length = 2 * self.nbCenterLayers;
|
// Get the bits around the bull's eye
|
int sides[] = {
|
[self sampleLine:p0 p2:p1 size:length], // Right side
|
[self sampleLine:p1 p2:p2 size:length], // Bottom
|
[self sampleLine:p2 p2:p3 size:length], // Left side
|
[self sampleLine:p3 p2:p0 size:length] // Top
|
};
|
|
// bullsEyeCorners[shift] is the corner of the bulls'eye that has three
|
// orientation marks.
|
// sides[shift] is the row/column that goes from the corner with three
|
// orientation marks to the corner with two.
|
int shift = [self rotationForSides:sides length:length];
|
if (shift == -1) {
|
return NO;
|
}
|
self.shift = shift;
|
|
// Flatten the parameter bits into a single 28- or 40-bit long
|
long parameterData = 0;
|
for (int i = 0; i < 4; i++) {
|
int side = sides[(self.shift + i) % 4];
|
if (self.isCompact) {
|
// Each side of the form ..XXXXXXX. where Xs are parameter data
|
parameterData <<= 7;
|
parameterData += (side >> 1) & 0x7F;
|
} else {
|
// Each side of the form ..XXXXX.XXXXX. where Xs are parameter data
|
parameterData <<= 10;
|
parameterData += ((side >> 2) & (0x1f << 5)) + ((side >> 1) & 0x1F);
|
}
|
}
|
|
// Corrects parameter data using RS. Returns just the data portion
|
// without the error correction.
|
int correctedData = [self correctedParameterData:parameterData compact:self.isCompact];
|
if (correctedData == -1) {
|
return NO;
|
}
|
|
if (self.isCompact) {
|
// 8 bits: 2 bits layers and 6 bits data blocks
|
self.nbLayers = (correctedData >> 6) + 1;
|
self.nbDataBlocks = (correctedData & 0x3F) + 1;
|
} else {
|
// 16 bits: 5 bits layers and 11 bits data blocks
|
self.nbLayers = (correctedData >> 11) + 1;
|
self.nbDataBlocks = (correctedData & 0x7FF) + 1;
|
}
|
|
return YES;
|
}
|
|
static int expectedCornerBits[] = {
|
0xee0, // 07340 XXX .XX X.. ...
|
0x1dc, // 00734 ... XXX .XX X..
|
0x83b, // 04073 X.. ... XXX .XX
|
0x707, // 03407 .XX X.. ... XXX
|
};
|
|
static int bitCount(uint32_t i) {
|
i = i - ((i >> 1) & 0x55555555);
|
i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
|
return (((i + (i >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;
|
}
|
|
- (int)rotationForSides:(const int[])sides length:(int)length {
|
// In a normal pattern, we expect to See
|
// ** .* D A
|
// * *
|
//
|
// . *
|
// .. .. C B
|
//
|
// Grab the 3 bits from each of the sides the form the locator pattern and concatenate
|
// into a 12-bit integer. Start with the bit at A
|
int cornerBits = 0;
|
for (int i = 0; i < 4; i++) {
|
int side = sides[i];
|
// XX......X where X's are orientation marks
|
int t = ((side >> (length - 2)) << 1) + (side & 1);
|
cornerBits = (cornerBits << 3) + t;
|
}
|
// Mov the bottom bit to the top, so that the three bits of the locator pattern at A are
|
// together. cornerBits is now:
|
// 3 orientation bits at A || 3 orientation bits at B || ... || 3 orientation bits at D
|
cornerBits = ((cornerBits & 1) << 11) + (cornerBits >> 1);
|
// The result shift indicates which element of BullsEyeCorners[] goes into the top-left
|
// corner. Since the four rotation values have a Hamming distance of 8, we
|
// can easily tolerate two errors.
|
for (int shift = 0; shift < 4; shift++) {
|
if (bitCount(cornerBits ^ expectedCornerBits[shift]) <= 2) {
|
return shift;
|
}
|
}
|
return -1;
|
}
|
|
/**
|
* Corrects the parameter bits using Reed-Solomon algorithm.
|
*
|
* @param parameterData parameter bits
|
* @param compact true if this is a compact Aztec code
|
* @return -1 if the array contains too many errors
|
*/
|
- (int)correctedParameterData:(long)parameterData compact:(BOOL)compact {
|
int numCodewords;
|
int numDataCodewords;
|
|
if (compact) {
|
numCodewords = 7;
|
numDataCodewords = 2;
|
} else {
|
numCodewords = 10;
|
numDataCodewords = 4;
|
}
|
|
int numECCodewords = numCodewords - numDataCodewords;
|
ZXIntArray *parameterWords = [[ZXIntArray alloc] initWithLength:numCodewords];
|
for (int i = numCodewords - 1; i >= 0; --i) {
|
parameterWords.array[i] = (int32_t) parameterData & 0xF;
|
parameterData >>= 4;
|
}
|
|
ZXReedSolomonDecoder *rsDecoder = [[ZXReedSolomonDecoder alloc] initWithField:[ZXGenericGF AztecParam]];
|
if (![rsDecoder decode:parameterWords twoS:numECCodewords error:nil]) {
|
return NO;
|
}
|
// Toss the error correction. Just return the data as an integer
|
int result = 0;
|
for (int i = 0; i < numDataCodewords; i++) {
|
result = (result << 4) + parameterWords.array[i];
|
}
|
return result;
|
}
|
|
/**
|
* Finds the corners of a bull-eye centered on the passed point.
|
* This returns the centers of the diagonal points just outside the bull's eye
|
* Returns [topRight, bottomRight, bottomLeft, topLeft]
|
*
|
* @param pCenter Center point
|
* @return The corners of the bull-eye, or nil if no valid bull-eye can be found
|
*/
|
- (NSMutableArray *)bullsEyeCorners:(ZXAztecPoint *)pCenter {
|
ZXAztecPoint *pina = pCenter;
|
ZXAztecPoint *pinb = pCenter;
|
ZXAztecPoint *pinc = pCenter;
|
ZXAztecPoint *pind = pCenter;
|
|
BOOL color = YES;
|
|
for (self.nbCenterLayers = 1; self.nbCenterLayers < 9; self.nbCenterLayers++) {
|
ZXAztecPoint *pouta = [self firstDifferent:pina color:color dx:1 dy:-1];
|
ZXAztecPoint *poutb = [self firstDifferent:pinb color:color dx:1 dy:1];
|
ZXAztecPoint *poutc = [self firstDifferent:pinc color:color dx:-1 dy:1];
|
ZXAztecPoint *poutd = [self firstDifferent:pind color:color dx:-1 dy:-1];
|
|
//d a
|
//
|
//c b
|
|
if (self.nbCenterLayers > 2) {
|
float q = [self distance:poutd b:pouta] * self.nbCenterLayers / ([self distance:pind b:pina] * (self.nbCenterLayers + 2));
|
if (q < 0.75 || q > 1.25 || ![self isWhiteOrBlackRectangle:pouta p2:poutb p3:poutc p4:poutd]) {
|
break;
|
}
|
}
|
|
pina = pouta;
|
pinb = poutb;
|
pinc = poutc;
|
pind = poutd;
|
|
color = !color;
|
}
|
|
if (self.nbCenterLayers != 5 && self.nbCenterLayers != 7) {
|
return nil;
|
}
|
|
self.compact = self.nbCenterLayers == 5;
|
|
// Expand the square by .5 pixel in each direction so that we're on the border
|
// between the white square and the black square
|
ZXResultPoint *pinax = [[ZXResultPoint alloc] initWithX:pina.x + 0.5f y:pina.y - 0.5f];
|
ZXResultPoint *pinbx = [[ZXResultPoint alloc] initWithX:pinb.x + 0.5f y:pinb.y + 0.5f];
|
ZXResultPoint *pincx = [[ZXResultPoint alloc] initWithX:pinc.x - 0.5f y:pinc.y + 0.5f];
|
ZXResultPoint *pindx = [[ZXResultPoint alloc] initWithX:pind.x - 0.5f y:pind.y - 0.5f];
|
|
// Expand the square so that its corners are the centers of the points
|
// just outside the bull's eye.
|
return [[self expandSquare:@[pinax, pinbx, pincx, pindx]
|
oldSide:2 * self.nbCenterLayers - 3
|
newSide:2 * self.nbCenterLayers] mutableCopy];
|
}
|
|
/**
|
* Finds a candidate center point of an Aztec code from an image
|
*/
|
- (ZXAztecPoint *)matrixCenter {
|
ZXResultPoint *pointA;
|
ZXResultPoint *pointB;
|
ZXResultPoint *pointC;
|
ZXResultPoint *pointD;
|
|
ZXWhiteRectangleDetector *detector = [[ZXWhiteRectangleDetector alloc] initWithImage:self.image error:nil];
|
NSArray *cornerPoints = [detector detectWithError:nil];
|
|
if (cornerPoints) {
|
pointA = cornerPoints[0];
|
pointB = cornerPoints[1];
|
pointC = cornerPoints[2];
|
pointD = cornerPoints[3];
|
} else {
|
// This exception can be in case the initial rectangle is white
|
// In that case, surely in the bull's eye, we try to expand the rectangle.
|
int cx = self.image.width / 2;
|
int cy = self.image.height / 2;
|
pointA = [[self firstDifferent:[[ZXAztecPoint alloc] initWithX:cx + 7 y:cy - 7] color:NO dx:1 dy:-1] toResultPoint];
|
pointB = [[self firstDifferent:[[ZXAztecPoint alloc] initWithX:cx + 7 y:cy + 7] color:NO dx:1 dy:1] toResultPoint];
|
pointC = [[self firstDifferent:[[ZXAztecPoint alloc] initWithX:cx - 7 y:cy + 7] color:NO dx:-1 dy:1] toResultPoint];
|
pointD = [[self firstDifferent:[[ZXAztecPoint alloc] initWithX:cx - 7 y:cy - 7] color:NO dx:-1 dy:-1] toResultPoint];
|
}
|
|
//Compute the center of the rectangle
|
int cx = [ZXMathUtils round:([pointA x] + [pointD x] + [pointB x] + [pointC x]) / 4.0f];
|
int cy = [ZXMathUtils round:([pointA y] + [pointD y] + [pointB y] + [pointC y]) / 4.0f];
|
|
// Redetermine the white rectangle starting from previously computed center.
|
// This will ensure that we end up with a white rectangle in center bull's eye
|
// in order to compute a more accurate center.
|
detector = [[ZXWhiteRectangleDetector alloc] initWithImage:self.image initSize:15 x:cx y:cy error:nil];
|
cornerPoints = [detector detectWithError:nil];
|
|
if (cornerPoints) {
|
pointA = cornerPoints[0];
|
pointB = cornerPoints[1];
|
pointC = cornerPoints[2];
|
pointD = cornerPoints[3];
|
} else {
|
// This exception can be in case the initial rectangle is white
|
// In that case we try to expand the rectangle.
|
pointA = [[self firstDifferent:[[ZXAztecPoint alloc] initWithX:cx + 7 y:cy - 7] color:NO dx:1 dy:-1] toResultPoint];
|
pointB = [[self firstDifferent:[[ZXAztecPoint alloc] initWithX:cx + 7 y:cy + 7] color:NO dx:1 dy:1] toResultPoint];
|
pointC = [[self firstDifferent:[[ZXAztecPoint alloc] initWithX:cx - 7 y:cy + 7] color:NO dx:-1 dy:1] toResultPoint];
|
pointD = [[self firstDifferent:[[ZXAztecPoint alloc] initWithX:cx - 7 y:cy - 7] color:NO dx:-1 dy:-1] toResultPoint];
|
}
|
|
cx = [ZXMathUtils round:([pointA x] + [pointD x] + [pointB x] + [pointC x]) / 4];
|
cy = [ZXMathUtils round:([pointA y] + [pointD y] + [pointB y] + [pointC y]) / 4];
|
|
// Recompute the center of the rectangle
|
return [[ZXAztecPoint alloc] initWithX:cx y:cy];
|
}
|
|
/**
|
* Gets the Aztec code corners from the bull's eye corners and the parameters.
|
*
|
* @param bullsEyeCorners the array of bull's eye corners
|
* @return the array of aztec code corners, or nil if the corner points do not fit in the image
|
*/
|
- (NSArray *)matrixCornerPoints:(NSArray *)bullsEyeCorners {
|
return [self expandSquare:bullsEyeCorners oldSide:2 * self.nbCenterLayers newSide:[self dimension]];
|
}
|
|
/**
|
* Creates a BitMatrix by sampling the provided image.
|
* topLeft, topRight, bottomRight, and bottomLeft are the centers of the squares on the
|
* diagonal just outside the bull's eye.
|
*/
|
- (ZXBitMatrix *)sampleGrid:(ZXBitMatrix *)anImage
|
topLeft:(ZXResultPoint *)topLeft
|
topRight:(ZXResultPoint *)topRight
|
bottomRight:(ZXResultPoint *)bottomRight
|
bottomLeft:(ZXResultPoint *)bottomLeft {
|
ZXGridSampler *sampler = [ZXGridSampler instance];
|
int dimension = [self dimension];
|
|
float low = dimension / 2.0f - self.nbCenterLayers;
|
float high = dimension / 2.0f + self.nbCenterLayers;
|
|
return [sampler sampleGrid:anImage
|
dimensionX:dimension
|
dimensionY:dimension
|
p1ToX:low p1ToY:low // topleft
|
p2ToX:high p2ToY:low // topright
|
p3ToX:high p3ToY:high // bottomright
|
p4ToX:low p4ToY:high // bottomleft
|
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:nil];
|
}
|
|
/**
|
* Samples a line.
|
*
|
* @param p1 start point (inclusive)
|
* @param p2 end point (exclusive)
|
* @param size number of bits
|
* @return the array of bits as an int (first bit is high-order bit of result)
|
*/
|
- (int)sampleLine:(ZXResultPoint *)p1 p2:(ZXResultPoint *)p2 size:(int)size {
|
int result = 0;
|
|
float d = [self resultDistance:p1 b:p2];
|
float moduleSize = d / size;
|
float px = p1.x;
|
float py = p1.y;
|
float dx = moduleSize * (p2.x - p1.x) / d;
|
float dy = moduleSize * (p2.y - p1.y) / d;
|
for (int i = 0; i < size; i++) {
|
if ([self.image getX:[ZXMathUtils round:px + i * dx] y:[ZXMathUtils round:py + i * dy]]) {
|
result |= 1 << (size - i - 1);
|
}
|
}
|
|
return result;
|
}
|
|
/**
|
* @return true if the border of the rectangle passed in parameter is compound of white points only
|
* or black points only
|
*/
|
- (BOOL)isWhiteOrBlackRectangle:(ZXAztecPoint *)p1 p2:(ZXAztecPoint *)p2 p3:(ZXAztecPoint *)p3 p4:(ZXAztecPoint *)p4 {
|
int corr = 3;
|
|
p1 = [[ZXAztecPoint alloc] initWithX:p1.x - corr y:p1.y + corr];
|
p2 = [[ZXAztecPoint alloc] initWithX:p2.x - corr y:p2.y - corr];
|
p3 = [[ZXAztecPoint alloc] initWithX:p3.x + corr y:p3.y - corr];
|
p4 = [[ZXAztecPoint alloc] initWithX:p4.x + corr y:p4.y + corr];
|
|
int cInit = [self color:p4 p2:p1];
|
|
if (cInit == 0) {
|
return NO;
|
}
|
|
int c = [self color:p1 p2:p2];
|
|
if (c != cInit) {
|
return NO;
|
}
|
|
c = [self color:p2 p2:p3];
|
|
if (c != cInit) {
|
return NO;
|
}
|
|
c = [self color:p3 p2:p4];
|
|
return c == cInit;
|
}
|
|
/**
|
* Gets the color of a segment
|
*
|
* @return 1 if segment more than 90% black, -1 if segment is more than 90% white, 0 else
|
*/
|
- (int)color:(ZXAztecPoint *)p1 p2:(ZXAztecPoint *)p2 {
|
float d = [self distance:p1 b:p2];
|
float dx = (p2.x - p1.x) / d;
|
float dy = (p2.y - p1.y) / d;
|
int error = 0;
|
|
float px = p1.x;
|
float py = p1.y;
|
|
BOOL colorModel = [self.image getX:p1.x y:p1.y];
|
|
for (int i = 0; i < d; i++) {
|
px += dx;
|
py += dy;
|
if ([self.image getX:[ZXMathUtils round:px] y:[ZXMathUtils round:py]] != colorModel) {
|
error++;
|
}
|
}
|
|
float errRatio = (float)error / d;
|
|
if (errRatio > 0.1f && errRatio < 0.9f) {
|
return 0;
|
}
|
|
return (errRatio <= 0.1f) == colorModel ? 1 : -1;
|
}
|
|
/**
|
* Gets the coordinate of the first point with a different color in the given direction
|
*/
|
- (ZXAztecPoint *)firstDifferent:(ZXAztecPoint *)init color:(BOOL)color dx:(int)dx dy:(int)dy {
|
int x = init.x + dx;
|
int y = init.y + dy;
|
|
while ([self isValidX:x y:y] && [self.image getX:x y:y] == color) {
|
x += dx;
|
y += dy;
|
}
|
|
x -= dx;
|
y -= dy;
|
|
while ([self isValidX:x y:y] && [self.image getX:x y:y] == color) {
|
x += dx;
|
}
|
x -= dx;
|
|
while ([self isValidX:x y:y] && [self.image getX:x y:y] == color) {
|
y += dy;
|
}
|
y -= dy;
|
|
return [[ZXAztecPoint alloc] initWithX:x y:y];
|
}
|
|
/**
|
* Expand the square represented by the corner points by pushing out equally in all directions
|
*
|
* @param cornerPoints the corners of the square, which has the bull's eye at its center
|
* @param oldSide the original length of the side of the square in the target bit matrix
|
* @param newSide the new length of the size of the square in the target bit matrix
|
* @return the corners of the expanded square
|
*/
|
- (NSArray *)expandSquare:(NSArray *)cornerPoints oldSide:(float)oldSide newSide:(float)newSide {
|
ZXResultPoint *cornerPoints0 = (ZXResultPoint *)cornerPoints[0];
|
ZXResultPoint *cornerPoints1 = (ZXResultPoint *)cornerPoints[1];
|
ZXResultPoint *cornerPoints2 = (ZXResultPoint *)cornerPoints[2];
|
ZXResultPoint *cornerPoints3 = (ZXResultPoint *)cornerPoints[3];
|
|
float ratio = newSide / (2 * oldSide);
|
float dx = cornerPoints0.x - cornerPoints2.x;
|
float dy = cornerPoints0.y - cornerPoints2.y;
|
float centerx = (cornerPoints0.x + cornerPoints2.x) / 2.0f;
|
float centery = (cornerPoints0.y + cornerPoints2.y) / 2.0f;
|
|
ZXResultPoint *result0 = [[ZXResultPoint alloc] initWithX:centerx + ratio * dx y:centery + ratio * dy];
|
ZXResultPoint *result2 = [[ZXResultPoint alloc] initWithX:centerx - ratio * dx y:centery - ratio * dy];
|
|
dx = cornerPoints1.x - cornerPoints3.x;
|
dy = cornerPoints1.y - cornerPoints3.y;
|
centerx = (cornerPoints1.x + cornerPoints3.x) / 2.0f;
|
centery = (cornerPoints1.y + cornerPoints3.y) / 2.0f;
|
ZXResultPoint *result1 = [[ZXResultPoint alloc] initWithX:centerx + ratio * dx y:centery + ratio * dy];
|
ZXResultPoint *result3 = [[ZXResultPoint alloc] initWithX:centerx - ratio * dx y:centery - ratio * dy];
|
|
return @[result0, result1, result2, result3];
|
}
|
|
- (BOOL)isValidX:(int)x y:(int)y {
|
return x >= 0 && x < self.image.width && y > 0 && y < self.image.height;
|
}
|
|
- (BOOL)isValid:(ZXResultPoint *)point {
|
int x = [ZXMathUtils round:point.x];
|
int y = [ZXMathUtils round:point.y];
|
return [self isValidX:x y:y];
|
}
|
|
- (float)distance:(ZXAztecPoint *)a b:(ZXAztecPoint *)b {
|
return [ZXMathUtils distance:a.x aY:a.y bX:b.x bY:b.y];
|
}
|
|
- (float)resultDistance:(ZXResultPoint *)a b:(ZXResultPoint *)b {
|
return [ZXMathUtils distance:a.x aY:a.y bX:b.x bY:b.y];
|
}
|
|
- (int)dimension {
|
if (self.compact) {
|
return 4 * self.nbLayers + 11;
|
}
|
if (self.nbLayers <= 4) {
|
return 4 * self.nbLayers + 15;
|
}
|
return 4 * self.nbLayers + 2 * ((self.nbLayers-4)/8 + 1) + 15;
|
}
|
|
@end
|