/* * 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