/*
|
* 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"
|
|
const int ZX_PDF417_ADJUST_ROW_NUMBER_SKIP = 2;
|
|
@interface ZXPDF417DetectionResult ()
|
|
@property (nonatomic, strong, readonly) ZXPDF417BarcodeMetadata *barcodeMetadata;
|
@property (nonatomic, strong, readonly) NSMutableArray *detectionResultColumnsInternal;
|
@property (nonatomic, assign, readonly) 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 = ZX_PDF417_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
|
/**
|
* @return 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];
|
}
|
|
- (void)adjustRowNumbersFromBothRI {
|
if (self.detectionResultColumnsInternal[0] == [NSNull null] || self.detectionResultColumnsInternal[self.barcodeColumnCount + 1] == [NSNull null]) {
|
return;
|
}
|
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];
|
}
|
}
|
}
|
}
|
}
|
|
- (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 < ZX_PDF417_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 < ZX_PDF417_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)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
|