blob: 101ee9300362b024b7f9676cef5c0f1ddd8cfb34 [file] [log] [blame] [edit]
/*
* 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 "ZXPDF417BarcodeMetadata.h"
#import "ZXPDF417BoundingBox.h"
#import "ZXPDF417Codeword.h"
#import "ZXPDF417Common.h"
#import "ZXPDF417DetectionResult.h"
#import "ZXPDF417DetectionResultColumn.h"
#import "ZXPDF417DetectionResultRowIndicatorColumn.h"
int const ADJUST_ROW_NUMBER_SKIP = 2;
@interface ZXPDF417DetectionResult ()
@property (nonatomic, strong) ZXPDF417BarcodeMetadata *barcodeMetadata;
@property (nonatomic, strong) NSMutableArray *detectionResultColumnsInternal;
@property (nonatomic, assign) int barcodeColumnCount;
@end
@implementation ZXPDF417DetectionResult
- (id)initWithBarcodeMetadata:(ZXPDF417BarcodeMetadata *)barcodeMetadata boundingBox:(ZXPDF417BoundingBox *)boundingBox {
self = [super init];
if (self) {
_barcodeMetadata = barcodeMetadata;
_barcodeColumnCount = barcodeMetadata.columnCount;
_boundingBox = boundingBox;
_detectionResultColumnsInternal = [NSMutableArray arrayWithCapacity:_barcodeColumnCount + 2];
for (int i = 0; i < _barcodeColumnCount + 2; i++) {
[_detectionResultColumnsInternal addObject:[NSNull null]];
}
}
return self;
}
- (NSArray *)detectionResultColumns {
[self adjustIndicatorColumnRowNumbers:self.detectionResultColumnsInternal[0]];
[self adjustIndicatorColumnRowNumbers:self.detectionResultColumnsInternal[self.barcodeColumnCount + 1]];
int unadjustedCodewordCount = ZXPDF417_MAX_CODEWORDS_IN_BARCODE;
int previousUnadjustedCount;
do {
previousUnadjustedCount = unadjustedCodewordCount;
unadjustedCodewordCount = [self adjustRowNumbers];
} while (unadjustedCodewordCount > 0 && unadjustedCodewordCount < previousUnadjustedCount);
return self.detectionResultColumnsInternal;
}
- (void)adjustIndicatorColumnRowNumbers:(ZXPDF417DetectionResultColumn *)detectionResultColumn {
if (detectionResultColumn && (id)detectionResultColumn != [NSNull null]) {
[(ZXPDF417DetectionResultRowIndicatorColumn *)detectionResultColumn adjustCompleteIndicatorColumnRowNumbers:self.barcodeMetadata];
}
}
// TODO ensure that no detected codewords with unknown row number are left
// we should be able to estimate the row height and use it as a hint for the row number
// we should also fill the rows top to bottom and bottom to top
/**
* Returns the number of codewords which don't have a valid row number. Note that the count is not accurate as codewords
* will be counted several times. It just serves as an indicator to see when we can stop adjusting row numbers
*/
- (int)adjustRowNumbers {
int unadjustedCount = [self adjustRowNumbersByRow];
if (unadjustedCount == 0) {
return 0;
}
for (int barcodeColumn = 1; barcodeColumn < self.barcodeColumnCount + 1; barcodeColumn++) {
NSArray *codewords = [self.detectionResultColumnsInternal[barcodeColumn] codewords];
for (int codewordsRow = 0; codewordsRow < [codewords count]; codewordsRow++) {
if ((id)codewords[codewordsRow] == [NSNull null]) {
continue;
}
if (![codewords[codewordsRow] hasValidRowNumber]) {
[self adjustRowNumbers:barcodeColumn codewordsRow:codewordsRow codewords:codewords];
}
}
}
return unadjustedCount;
}
- (int)adjustRowNumbersByRow {
[self adjustRowNumbersFromBothRI];
// TODO we should only do full row adjustments if row numbers of left and right row indicator column match.
// Maybe it's even better to calculated the height (in codeword rows) and divide it by the number of barcode
// rows. This, together with the LRI and RRI row numbers should allow us to get a good estimate where a row
// number starts and ends.
int unadjustedCount = [self adjustRowNumbersFromLRI];
return unadjustedCount + [self adjustRowNumbersFromRRI];
}
- (int)adjustRowNumbersFromBothRI {
if (self.detectionResultColumnsInternal[0] == [NSNull null] || self.detectionResultColumnsInternal[self.barcodeColumnCount + 1] == [NSNull null]) {
return 0;
}
NSArray *LRIcodewords = [(ZXPDF417DetectionResultColumn *)self.detectionResultColumnsInternal[0] codewords];
NSArray *RRIcodewords = [(ZXPDF417DetectionResultColumn *)self.detectionResultColumnsInternal[self.barcodeColumnCount + 1] codewords];
for (int codewordsRow = 0; codewordsRow < [LRIcodewords count]; codewordsRow++) {
if (LRIcodewords[codewordsRow] != [NSNull null] &&
RRIcodewords[codewordsRow] != [NSNull null] &&
[(ZXPDF417Codeword *)LRIcodewords[codewordsRow] rowNumber] == [(ZXPDF417Codeword *)RRIcodewords[codewordsRow] rowNumber]) {
for (int barcodeColumn = 1; barcodeColumn <= self.barcodeColumnCount; barcodeColumn++) {
ZXPDF417Codeword *codeword = [(ZXPDF417DetectionResultColumn *)self.detectionResultColumnsInternal[barcodeColumn] codewords][codewordsRow];
if ((id)codeword == [NSNull null]) {
continue;
}
codeword.rowNumber = [(ZXPDF417Codeword *)LRIcodewords[codewordsRow] rowNumber];
if (![codeword hasValidRowNumber]) {
[(ZXPDF417DetectionResultColumn *)self.detectionResultColumnsInternal[barcodeColumn] codewords][codewordsRow] = [NSNull null];
}
}
}
}
return 0;
}
- (int)adjustRowNumbersFromRRI {
if (self.detectionResultColumnsInternal[self.barcodeColumnCount + 1] == [NSNull null]) {
return 0;
}
int unadjustedCount = 0;
NSArray *codewords = [self.detectionResultColumnsInternal[self.barcodeColumnCount + 1] codewords];
for (int codewordsRow = 0; codewordsRow < [codewords count]; codewordsRow++) {
if ((id)codewords[codewordsRow] == [NSNull null]) {
continue;
}
int rowIndicatorRowNumber = [codewords[codewordsRow] rowNumber];
int invalidRowCounts = 0;
for (int barcodeColumn = self.barcodeColumnCount + 1; barcodeColumn > 0 && invalidRowCounts < ADJUST_ROW_NUMBER_SKIP; barcodeColumn--) {
if (self.detectionResultColumnsInternal[barcodeColumn] != [NSNull null]) {
ZXPDF417Codeword *codeword = [self.detectionResultColumnsInternal[barcodeColumn] codewords][codewordsRow];
if ((id)codeword != [NSNull null]) {
invalidRowCounts = [self adjustRowNumberIfValid:rowIndicatorRowNumber invalidRowCounts:invalidRowCounts codeword:codeword];
if (![codeword hasValidRowNumber]) {
unadjustedCount++;
}
}
}
}
}
return unadjustedCount;
}
- (int)adjustRowNumbersFromLRI {
if (self.detectionResultColumnsInternal[0] == [NSNull null]) {
return 0;
}
int unadjustedCount = 0;
NSArray *codewords = [self.detectionResultColumnsInternal[0] codewords];
for (int codewordsRow = 0; codewordsRow < [codewords count]; codewordsRow++) {
if ((id)codewords[codewordsRow] == [NSNull null]) {
continue;
}
int rowIndicatorRowNumber = [codewords[codewordsRow] rowNumber];
int invalidRowCounts = 0;
for (int barcodeColumn = 1; barcodeColumn < self.barcodeColumnCount + 1 && invalidRowCounts < ADJUST_ROW_NUMBER_SKIP; barcodeColumn++) {
if (self.detectionResultColumnsInternal[barcodeColumn] != [NSNull null]) {
ZXPDF417Codeword *codeword = [self.detectionResultColumnsInternal[barcodeColumn] codewords][codewordsRow];
if ((id)codeword != [NSNull null]) {
invalidRowCounts = [self adjustRowNumberIfValid:rowIndicatorRowNumber invalidRowCounts:invalidRowCounts codeword:codeword];
if (![codeword hasValidRowNumber]) {
unadjustedCount++;
}
}
}
}
}
return unadjustedCount;
}
- (int)adjustRowNumberIfValid:(int)rowIndicatorRowNumber invalidRowCounts:(int)invalidRowCounts codeword:(ZXPDF417Codeword *)codeword {
if (!codeword) {
return invalidRowCounts;
}
if (![codeword hasValidRowNumber]) {
if ([codeword isValidRowNumber:rowIndicatorRowNumber]) {
[codeword setRowNumber:rowIndicatorRowNumber];
invalidRowCounts = 0;
} else {
++invalidRowCounts;
}
}
return invalidRowCounts;
}
- (void)adjustRowNumbers:(int)barcodeColumn codewordsRow:(int)codewordsRow codewords:(NSArray *)codewords {
ZXPDF417Codeword *codeword = codewords[codewordsRow];
NSArray *previousColumnCodewords = [self.detectionResultColumnsInternal[barcodeColumn - 1] codewords];
NSArray *nextColumnCodewords = previousColumnCodewords;
if (self.detectionResultColumnsInternal[barcodeColumn + 1] != [NSNull null]) {
nextColumnCodewords = [self.detectionResultColumnsInternal[barcodeColumn + 1] codewords];
}
NSMutableArray *otherCodewords = [NSMutableArray arrayWithCapacity:14];
for (int i = 0; i < 14; i++) {
[otherCodewords addObject:[NSNull null]];
}
otherCodewords[2] = previousColumnCodewords[codewordsRow];
otherCodewords[3] = nextColumnCodewords[codewordsRow];
if (codewordsRow > 0) {
otherCodewords[0] = codewords[codewordsRow - 1];
otherCodewords[4] = previousColumnCodewords[codewordsRow - 1];
otherCodewords[5] = nextColumnCodewords[codewordsRow - 1];
}
if (codewordsRow > 1) {
otherCodewords[8] = codewords[codewordsRow - 2];
otherCodewords[10] = previousColumnCodewords[codewordsRow - 2];
otherCodewords[11] = nextColumnCodewords[codewordsRow - 2];
}
if (codewordsRow < [codewords count] - 1) {
otherCodewords[1] = codewords[codewordsRow + 1];
otherCodewords[6] = previousColumnCodewords[codewordsRow + 1];
otherCodewords[7] = nextColumnCodewords[codewordsRow + 1];
}
if (codewordsRow < [codewords count] - 2) {
otherCodewords[9] = codewords[codewordsRow + 2];
otherCodewords[12] = previousColumnCodewords[codewordsRow + 2];
otherCodewords[13] = nextColumnCodewords[codewordsRow + 2];
}
for (ZXPDF417Codeword *otherCodeword in otherCodewords) {
if ([self adjustRowNumber:codeword otherCodeword:otherCodeword]) {
return;
}
}
}
/**
* Return true if row number was adjusted, false otherwise
*/
- (BOOL)adjustRowNumber:(ZXPDF417Codeword *)codeword otherCodeword:(ZXPDF417Codeword *)otherCodeword {
if ((id)otherCodeword == [NSNull null]) {
return NO;
}
if ([otherCodeword hasValidRowNumber] && otherCodeword.bucket == codeword.bucket) {
[codeword setRowNumber:otherCodeword.rowNumber];
return YES;
}
return NO;
}
- (int)barcodeColumnCount {
return _barcodeColumnCount;
}
- (int)barcodeRowCount {
return self.barcodeMetadata.rowCount;
}
- (int)barcodeECLevel {
return self.barcodeMetadata.errorCorrectionLevel;
}
- (void)setDetectionResultColumn:(int)barcodeColumn detectionResultColumn:(ZXPDF417DetectionResultColumn *)detectionResultColumn {
if (!detectionResultColumn) {
self.detectionResultColumnsInternal[barcodeColumn] = [NSNull null];
} else {
self.detectionResultColumnsInternal[barcodeColumn] = detectionResultColumn;
}
}
- (ZXPDF417DetectionResultColumn *)detectionResultColumn:(int)barcodeColumn {
ZXPDF417DetectionResultColumn *result = self.detectionResultColumnsInternal[barcodeColumn];
return (id)result == [NSNull null] ? nil : result;
}
- (NSString *)description {
ZXPDF417DetectionResultColumn *rowIndicatorColumn = self.detectionResultColumnsInternal[0];
if ((id)rowIndicatorColumn == [NSNull null]) {
rowIndicatorColumn = self.detectionResultColumnsInternal[self.barcodeColumnCount + 1];
}
NSMutableString *result = [NSMutableString string];
for (int codewordsRow = 0; codewordsRow < [rowIndicatorColumn.codewords count]; codewordsRow++) {
[result appendFormat:@"CW %3d:", codewordsRow];
for (int barcodeColumn = 0; barcodeColumn < self.barcodeColumnCount + 2; barcodeColumn++) {
if (self.detectionResultColumnsInternal[barcodeColumn] == [NSNull null]) {
[result appendString:@" | "];
continue;
}
ZXPDF417Codeword *codeword = [(ZXPDF417DetectionResultColumn *)self.detectionResultColumnsInternal[barcodeColumn] codewords][codewordsRow];
if ((id)codeword == [NSNull null]) {
[result appendString:@" | "];
continue;
}
[result appendFormat:@" %3d|%3d", codeword.rowNumber, codeword.value];
}
[result appendString:@"\n"];
}
return [NSString stringWithString:result];
}
@end