/*
|
* Copyright 2013 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 "ZXIntArray.h"
|
#import "ZXPDF417BarcodeMetadata.h"
|
#import "ZXPDF417BarcodeValue.h"
|
#import "ZXPDF417BoundingBox.h"
|
#import "ZXPDF417Codeword.h"
|
#import "ZXPDF417Common.h"
|
#import "ZXPDF417DetectionResult.h"
|
#import "ZXPDF417DetectionResultRowIndicatorColumn.h"
|
#import "ZXResultPoint.h"
|
|
@implementation ZXPDF417DetectionResultRowIndicatorColumn
|
|
- (id)initWithBoundingBox:(ZXPDF417BoundingBox *)boundingBox isLeft:(BOOL)isLeft {
|
self = [super initWithBoundingBox:boundingBox];
|
if (self) {
|
_isLeft = isLeft;
|
}
|
|
return self;
|
}
|
|
- (void)setRowNumbers {
|
for (ZXPDF417Codeword *codeword in [self codewords]) {
|
if ((id)codeword != [NSNull null]) {
|
[codeword setRowNumberAsRowIndicatorColumn];
|
}
|
}
|
}
|
|
// TODO implement properly
|
// TODO maybe we should add missing codewords to store the correct row number to make
|
// finding row numbers for other columns easier
|
// use row height count to make detection of invalid row numbers more reliable
|
- (int)adjustCompleteIndicatorColumnRowNumbers:(ZXPDF417BarcodeMetadata *)barcodeMetadata {
|
[self setRowNumbers];
|
[self removeIncorrectCodewords:barcodeMetadata];
|
ZXResultPoint *top = self.isLeft ? self.boundingBox.topLeft : self.boundingBox.topRight;
|
ZXResultPoint *bottom = self.isLeft ? self.boundingBox.bottomLeft : self.boundingBox.bottomRight;
|
int firstRow = [self imageRowToCodewordIndex:(int) top.y];
|
int lastRow = [self imageRowToCodewordIndex:(int) bottom.y];
|
// We need to be careful using the average row height. Barcode could be skewed so that we have smaller and
|
// taller rows
|
float averageRowHeight = (lastRow - firstRow) / (float) barcodeMetadata.rowCount;
|
int barcodeRow = -1;
|
int maxRowHeight = 1;
|
int currentRowHeight = 0;
|
for (int codewordsRow = firstRow; codewordsRow < lastRow; codewordsRow++) {
|
if (self.codewords[codewordsRow] == [NSNull null]) {
|
continue;
|
}
|
ZXPDF417Codeword *codeword = self.codewords[codewordsRow];
|
|
// float expectedRowNumber = (codewordsRow - firstRow) / averageRowHeight;
|
// if (Math.abs(codeword.getRowNumber() - expectedRowNumber) > 2) {
|
// SimpleLog.log(LEVEL.WARNING,
|
// "Removing codeword, rowNumberSkew too high, codeword[" + codewordsRow + "]: Expected Row: " +
|
// expectedRowNumber + ", RealRow: " + codeword.getRowNumber() + ", value: " + codeword.getValue());
|
// codewords[codewordsRow] = null;
|
// }
|
|
int rowDifference = codeword.rowNumber - barcodeRow;
|
|
// TODO improve handling with case where first row indicator doesn't start with 0
|
|
if (rowDifference == 0) {
|
currentRowHeight++;
|
} else if (rowDifference == 1) {
|
maxRowHeight = MAX(maxRowHeight, currentRowHeight);
|
currentRowHeight = 1;
|
barcodeRow = codeword.rowNumber;
|
} else if (rowDifference < 0 ||
|
codeword.rowNumber >= barcodeMetadata.rowCount ||
|
rowDifference > codewordsRow) {
|
self.codewords[codewordsRow] = [NSNull null];
|
} else {
|
int checkedRows;
|
if (maxRowHeight > 2) {
|
checkedRows = (maxRowHeight - 2) * rowDifference;
|
} else {
|
checkedRows = rowDifference;
|
}
|
BOOL closePreviousCodewordFound = checkedRows >= codewordsRow;
|
for (int i = 1; i <= checkedRows && !closePreviousCodewordFound; i++) {
|
// there must be (height * rowDifference) number of codewords missing. For now we assume height = 1.
|
// This should hopefully get rid of most problems already.
|
closePreviousCodewordFound = self.codewords[codewordsRow - i] != [NSNull null];
|
}
|
if (closePreviousCodewordFound) {
|
self.codewords[codewordsRow] = [NSNull null];
|
} else {
|
barcodeRow = codeword.rowNumber;
|
currentRowHeight = 1;
|
}
|
}
|
}
|
return (int) (averageRowHeight + 0.5);
|
}
|
|
- (BOOL)getRowHeights:(ZXIntArray **)rowHeights {
|
ZXPDF417BarcodeMetadata *barcodeMetadata = [self barcodeMetadata];
|
if (!barcodeMetadata) {
|
*rowHeights = nil;
|
return YES;
|
}
|
[self adjustIncompleteIndicatorColumnRowNumbers:barcodeMetadata];
|
ZXIntArray *result = [[ZXIntArray alloc] initWithLength:barcodeMetadata.rowCount];
|
for (ZXPDF417Codeword *codeword in [self codewords]) {
|
if ((id)codeword != [NSNull null]) {
|
int rowNumber = codeword.rowNumber;
|
if (rowNumber >= result.length) {
|
*rowHeights = nil;
|
return NO;
|
}
|
result.array[rowNumber]++;
|
} // else throw exception?
|
}
|
*rowHeights = result;
|
return YES;
|
}
|
|
// TODO maybe we should add missing codewords to store the correct row number to make
|
// finding row numbers for other columns easier
|
// use row height count to make detection of invalid row numbers more reliable
|
- (int)adjustIncompleteIndicatorColumnRowNumbers:(ZXPDF417BarcodeMetadata *)barcodeMetadata {
|
ZXResultPoint *top = self.isLeft ? self.boundingBox.topLeft : self.boundingBox.topRight;
|
ZXResultPoint *bottom = self.isLeft ? self.boundingBox.bottomLeft : self.boundingBox.bottomRight;
|
int firstRow = [self imageRowToCodewordIndex:(int) top.y];
|
int lastRow = [self imageRowToCodewordIndex:(int) bottom.y];
|
float averageRowHeight = (lastRow - firstRow) / (float) barcodeMetadata.rowCount;
|
int barcodeRow = -1;
|
int maxRowHeight = 1;
|
int currentRowHeight = 0;
|
for (int codewordsRow = firstRow; codewordsRow < lastRow; codewordsRow++) {
|
if (self.codewords[codewordsRow] == [NSNull null]) {
|
continue;
|
}
|
ZXPDF417Codeword *codeword = self.codewords[codewordsRow];
|
|
[codeword setRowNumberAsRowIndicatorColumn];
|
|
int rowDifference = codeword.rowNumber - barcodeRow;
|
|
// TODO improve handling with case where first row indicator doesn't start with 0
|
|
if (rowDifference == 0) {
|
currentRowHeight++;
|
} else if (rowDifference == 1) {
|
maxRowHeight = MAX(maxRowHeight, currentRowHeight);
|
currentRowHeight = 1;
|
barcodeRow = codeword.rowNumber;
|
} else if (codeword.rowNumber >= barcodeMetadata.rowCount) {
|
self.codewords[codewordsRow] = [NSNull null];
|
} else {
|
barcodeRow = codeword.rowNumber;
|
currentRowHeight = 1;
|
}
|
}
|
return (int) (averageRowHeight + 0.5);
|
}
|
|
- (ZXPDF417BarcodeMetadata *)barcodeMetadata {
|
ZXPDF417BarcodeValue *barcodeColumnCount = [[ZXPDF417BarcodeValue alloc] init];
|
ZXPDF417BarcodeValue *barcodeRowCountUpperPart = [[ZXPDF417BarcodeValue alloc] init];
|
ZXPDF417BarcodeValue *barcodeRowCountLowerPart = [[ZXPDF417BarcodeValue alloc] init];
|
ZXPDF417BarcodeValue *barcodeECLevel = [[ZXPDF417BarcodeValue alloc] init];
|
for (ZXPDF417Codeword *codeword in self.codewords) {
|
if ((id)codeword == [NSNull null]) {
|
continue;
|
}
|
[codeword setRowNumberAsRowIndicatorColumn];
|
int rowIndicatorValue = codeword.value % 30;
|
int codewordRowNumber = codeword.rowNumber;
|
if (!self.isLeft) {
|
codewordRowNumber += 2;
|
}
|
switch (codewordRowNumber % 3) {
|
case 0:
|
[barcodeRowCountUpperPart setValue:rowIndicatorValue * 3 + 1];
|
break;
|
case 1:
|
[barcodeECLevel setValue:rowIndicatorValue / 3];
|
[barcodeRowCountLowerPart setValue:rowIndicatorValue % 3];
|
break;
|
case 2:
|
[barcodeColumnCount setValue:rowIndicatorValue + 1];
|
break;
|
}
|
}
|
// Maybe we should check if we have ambiguous values?
|
if (([barcodeColumnCount value].length == 0) ||
|
([barcodeRowCountUpperPart value].length == 0) ||
|
([barcodeRowCountLowerPart value].length == 0) ||
|
([barcodeECLevel value].length == 0) ||
|
[barcodeColumnCount value].array[0] < 1 ||
|
[barcodeRowCountUpperPart value].array[0] + [barcodeRowCountLowerPart value].array[0] < ZX_PDF417_MIN_ROWS_IN_BARCODE ||
|
[barcodeRowCountUpperPart value].array[0] + [barcodeRowCountLowerPart value].array[0] > ZX_PDF417_MAX_ROWS_IN_BARCODE) {
|
return nil;
|
}
|
ZXPDF417BarcodeMetadata *barcodeMetadata = [[ZXPDF417BarcodeMetadata alloc] initWithColumnCount:[barcodeColumnCount value].array[0]
|
rowCountUpperPart:[barcodeRowCountUpperPart value].array[0]
|
rowCountLowerPart:[barcodeRowCountLowerPart value].array[0]
|
errorCorrectionLevel:[barcodeECLevel value].array[0]];
|
[self removeIncorrectCodewords:barcodeMetadata];
|
return barcodeMetadata;
|
}
|
|
- (void)removeIncorrectCodewords:(ZXPDF417BarcodeMetadata *)barcodeMetadata {
|
// Remove codewords which do not match the metadata
|
// TODO Maybe we should keep the incorrect codewords for the start and end positions?
|
for (int codewordRow = 0; codewordRow < [self.codewords count]; codewordRow++) {
|
ZXPDF417Codeword *codeword = self.codewords[codewordRow];
|
if (self.codewords[codewordRow] == [NSNull null]) {
|
continue;
|
}
|
int rowIndicatorValue = codeword.value % 30;
|
int codewordRowNumber = codeword.rowNumber;
|
if (codewordRowNumber > barcodeMetadata.rowCount) {
|
self.codewords[codewordRow] = [NSNull null];
|
continue;
|
}
|
if (!self.isLeft) {
|
codewordRowNumber += 2;
|
}
|
switch (codewordRowNumber % 3) {
|
case 0:
|
if (rowIndicatorValue * 3 + 1 != barcodeMetadata.rowCountUpperPart) {
|
self.codewords[codewordRow] = [NSNull null];
|
}
|
break;
|
case 1:
|
if (rowIndicatorValue / 3 != barcodeMetadata.errorCorrectionLevel ||
|
rowIndicatorValue % 3 != barcodeMetadata.rowCountLowerPart) {
|
self.codewords[codewordRow] = [NSNull null];
|
}
|
break;
|
case 2:
|
if (rowIndicatorValue + 1 != barcodeMetadata.columnCount) {
|
self.codewords[codewordRow] = [NSNull null];
|
}
|
break;
|
}
|
}
|
}
|
|
- (NSString *)description {
|
return [NSString stringWithFormat:@"IsLeft: %@\n%@", @(self.isLeft), [super description]];
|
}
|
|
@end
|