/* * 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 "ZXBitMatrix.h" #import "ZXDecoderResult.h" #import "ZXErrors.h" #import "ZXIntArray.h" #import "ZXPDF417BarcodeMetadata.h" #import "ZXPDF417BarcodeValue.h" #import "ZXPDF417BoundingBox.h" #import "ZXPDF417Codeword.h" #import "ZXPDF417CodewordDecoder.h" #import "ZXPDF417Common.h" #import "ZXPDF417DecodedBitStreamParser.h" #import "ZXPDF417DetectionResult.h" #import "ZXPDF417DetectionResultRowIndicatorColumn.h" #import "ZXPDF417ECErrorCorrection.h" #import "ZXPDF417ScanningDecoder.h" #import "ZXResultPoint.h" const int ZX_PDF417_CODEWORD_SKEW_SIZE = 2; const int ZX_PDF417_MAX_ERRORS = 3; const int ZX_PDF417_MAX_EC_CODEWORDS = 512; static ZXPDF417ECErrorCorrection *errorCorrection; @implementation ZXPDF417ScanningDecoder + (void)initialize { if ([self class] != [ZXPDF417ScanningDecoder class]) return; errorCorrection = [[ZXPDF417ECErrorCorrection alloc] init]; } // TODO don't pass in minCodewordWidth and maxCodewordWidth, pass in barcode columns for start and stop pattern // columns. That way width can be deducted from the pattern column. // This approach also allows to detect more details about the barcode, e.g. if a bar type (white or black) is wider // than it should be. This can happen if the scanner used a bad blackpoint. + (ZXDecoderResult *)decode:(ZXBitMatrix *)image imageTopLeft:(ZXResultPoint *)imageTopLeft imageBottomLeft:(ZXResultPoint *)imageBottomLeft imageTopRight:(ZXResultPoint *)imageTopRight imageBottomRight:(ZXResultPoint *)imageBottomRight minCodewordWidth:(int)minCodewordWidth maxCodewordWidth:(int)maxCodewordWidth error:(NSError **)error { ZXPDF417BoundingBox *boundingBox = [[ZXPDF417BoundingBox alloc] initWithImage:image topLeft:imageTopLeft bottomLeft:imageBottomLeft topRight:imageTopRight bottomRight:imageBottomRight]; ZXPDF417DetectionResultRowIndicatorColumn *leftRowIndicatorColumn; ZXPDF417DetectionResultRowIndicatorColumn *rightRowIndicatorColumn; ZXPDF417DetectionResult *detectionResult; for (int i = 0; i < 2; i++) { if (imageTopLeft) { leftRowIndicatorColumn = [self rowIndicatorColumn:image boundingBox:boundingBox startPoint:imageTopLeft leftToRight:YES minCodewordWidth:minCodewordWidth maxCodewordWidth:maxCodewordWidth]; } if (imageTopRight) { rightRowIndicatorColumn = [self rowIndicatorColumn:image boundingBox:boundingBox startPoint:imageTopRight leftToRight:NO minCodewordWidth:minCodewordWidth maxCodewordWidth:maxCodewordWidth]; } detectionResult = [self merge:leftRowIndicatorColumn rightRowIndicatorColumn:rightRowIndicatorColumn error:error]; if (!detectionResult) { return nil; } if (i == 0 && detectionResult.boundingBox && (detectionResult.boundingBox.minY < boundingBox.minY || detectionResult.boundingBox.maxY > boundingBox.maxY)) { boundingBox = [detectionResult boundingBox]; } else { detectionResult.boundingBox = boundingBox; break; } } int maxBarcodeColumn = detectionResult.barcodeColumnCount + 1; [detectionResult setDetectionResultColumn:0 detectionResultColumn:leftRowIndicatorColumn]; [detectionResult setDetectionResultColumn:maxBarcodeColumn detectionResultColumn:rightRowIndicatorColumn]; BOOL leftToRight = leftRowIndicatorColumn != nil; for (int barcodeColumnCount = 1; barcodeColumnCount <= maxBarcodeColumn; barcodeColumnCount++) { int barcodeColumn = leftToRight ? barcodeColumnCount : maxBarcodeColumn - barcodeColumnCount; if ([detectionResult detectionResultColumn:barcodeColumn]) { // This will be the case for the opposite row indicator column, which doesn't need to be decoded again. continue; } ZXPDF417DetectionResultColumn *detectionResultColumn; if (barcodeColumn == 0 || barcodeColumn == maxBarcodeColumn) { detectionResultColumn = [[ZXPDF417DetectionResultRowIndicatorColumn alloc] initWithBoundingBox:boundingBox isLeft:barcodeColumn == 0]; } else { detectionResultColumn = [[ZXPDF417DetectionResultColumn alloc] initWithBoundingBox:boundingBox]; } [detectionResult setDetectionResultColumn:barcodeColumn detectionResultColumn:detectionResultColumn]; int startColumn = -1; int previousStartColumn = startColumn; // TODO start at a row for which we know the start position, then detect upwards and downwards from there. for (int imageRow = boundingBox.minY; imageRow <= boundingBox.maxY; imageRow++) { startColumn = [self startColumn:detectionResult barcodeColumn:barcodeColumn imageRow:imageRow leftToRight:leftToRight]; if (startColumn < 0 || startColumn > boundingBox.maxX) { if (previousStartColumn == -1) { continue; } startColumn = previousStartColumn; } ZXPDF417Codeword *codeword = [self detectCodeword:image minColumn:boundingBox.minX maxColumn:boundingBox.maxX leftToRight:leftToRight startColumn:startColumn imageRow:imageRow minCodewordWidth:minCodewordWidth maxCodewordWidth:maxCodewordWidth]; if (codeword) { [detectionResultColumn setCodeword:imageRow codeword:codeword]; previousStartColumn = startColumn; minCodewordWidth = MIN(minCodewordWidth, codeword.width); maxCodewordWidth = MAX(maxCodewordWidth, codeword.width); } } } return [self createDecoderResult:detectionResult error:error]; } + (ZXPDF417DetectionResult *)merge:(ZXPDF417DetectionResultRowIndicatorColumn *)leftRowIndicatorColumn rightRowIndicatorColumn:(ZXPDF417DetectionResultRowIndicatorColumn *)rightRowIndicatorColumn error:(NSError **)error { if (!leftRowIndicatorColumn && !rightRowIndicatorColumn) { return nil; } ZXPDF417BarcodeMetadata *barcodeMetadata = [self barcodeMetadata:leftRowIndicatorColumn rightRowIndicatorColumn:rightRowIndicatorColumn]; if (!barcodeMetadata) { return nil; } ZXPDF417BoundingBox *leftBoundingBox, *rightBoundingBox; if (![self adjustBoundingBox:&leftBoundingBox rowIndicatorColumn:leftRowIndicatorColumn error:error]) { return nil; } if (![self adjustBoundingBox:&rightBoundingBox rowIndicatorColumn:rightRowIndicatorColumn error:error]) { return nil; } ZXPDF417BoundingBox *boundingBox = [ZXPDF417BoundingBox mergeLeftBox:leftBoundingBox rightBox:rightBoundingBox]; if (!boundingBox) { if (error) *error = ZXNotFoundErrorInstance(); return nil; } return [[ZXPDF417DetectionResult alloc] initWithBarcodeMetadata:barcodeMetadata boundingBox:boundingBox]; } + (BOOL)adjustBoundingBox:(ZXPDF417BoundingBox **)boundingBox rowIndicatorColumn:(ZXPDF417DetectionResultRowIndicatorColumn *)rowIndicatorColumn error:(NSError **)error { if (!rowIndicatorColumn) { *boundingBox = nil; return YES; } ZXIntArray *rowHeights; if (![rowIndicatorColumn getRowHeights:&rowHeights]) { if (error) *error = ZXFormatErrorInstance(); *boundingBox = nil; return NO; } if (!rowHeights) { *boundingBox = nil; return YES; } int maxRowHeight = [self max:rowHeights]; int missingStartRows = 0; for (int i = 0; i < rowHeights.length; i++) { int rowHeight = rowHeights.array[i]; missingStartRows += maxRowHeight - rowHeight; if (rowHeight > 0) { break; } } NSArray *codewords = rowIndicatorColumn.codewords; for (int row = 0; missingStartRows > 0 && codewords[row] == [NSNull null]; row++) { missingStartRows--; } int missingEndRows = 0; for (int row = rowHeights.length - 1; row >= 0; row--) { missingEndRows += maxRowHeight - rowHeights.array[row]; if (rowHeights.array[row] > 0) { break; } } for (int row = (int)[codewords count] - 1; missingEndRows > 0 && codewords[row] == [NSNull null]; row--) { missingEndRows--; } *boundingBox = [rowIndicatorColumn.boundingBox addMissingRows:missingStartRows missingEndRows:missingEndRows isLeft:rowIndicatorColumn.isLeft]; return *boundingBox != nil; } + (int)max:(ZXIntArray *)values { int maxValue = -1; for (int i = 0; i < values.length; i++) { int value = values.array[i]; maxValue = MAX(maxValue, value); } return maxValue; } + (ZXPDF417BarcodeMetadata *)barcodeMetadata:(ZXPDF417DetectionResultRowIndicatorColumn *)leftRowIndicatorColumn rightRowIndicatorColumn:(ZXPDF417DetectionResultRowIndicatorColumn *)rightRowIndicatorColumn { ZXPDF417BarcodeMetadata *leftBarcodeMetadata; if (!leftRowIndicatorColumn || !(leftBarcodeMetadata = leftRowIndicatorColumn.barcodeMetadata)) { return rightRowIndicatorColumn ? rightRowIndicatorColumn.barcodeMetadata : nil; } ZXPDF417BarcodeMetadata *rightBarcodeMetadata; if (!rightRowIndicatorColumn || !(rightBarcodeMetadata = rightRowIndicatorColumn.barcodeMetadata)) { return leftRowIndicatorColumn.barcodeMetadata; } if (leftBarcodeMetadata.columnCount != rightBarcodeMetadata.columnCount && leftBarcodeMetadata.errorCorrectionLevel != rightBarcodeMetadata.errorCorrectionLevel && leftBarcodeMetadata.rowCount != rightBarcodeMetadata.rowCount) { return nil; } return leftBarcodeMetadata; } + (ZXPDF417DetectionResultRowIndicatorColumn *)rowIndicatorColumn:(ZXBitMatrix *)image boundingBox:(ZXPDF417BoundingBox *)boundingBox startPoint:(ZXResultPoint *)startPoint leftToRight:(BOOL)leftToRight minCodewordWidth:(int)minCodewordWidth maxCodewordWidth:(int)maxCodewordWidth { ZXPDF417DetectionResultRowIndicatorColumn *rowIndicatorColumn = [[ZXPDF417DetectionResultRowIndicatorColumn alloc] initWithBoundingBox:boundingBox isLeft:leftToRight]; for (int i = 0; i < 2; i++) { int increment = i == 0 ? 1 : -1; int startColumn = (int) startPoint.x; for (int imageRow = (int) startPoint.y; imageRow <= boundingBox.maxY && imageRow >= boundingBox.minY; imageRow += increment) { ZXPDF417Codeword *codeword = [self detectCodeword:image minColumn:0 maxColumn:image.width leftToRight:leftToRight startColumn:startColumn imageRow:imageRow minCodewordWidth:minCodewordWidth maxCodewordWidth:maxCodewordWidth]; if (codeword) { [rowIndicatorColumn setCodeword:imageRow codeword:codeword]; if (leftToRight) { startColumn = codeword.startX; } else { startColumn = codeword.endX; } } } } return rowIndicatorColumn; } + (BOOL)adjustCodewordCount:(ZXPDF417DetectionResult *)detectionResult barcodeMatrix:(NSArray *)barcodeMatrix { ZXIntArray *numberOfCodewords = [(ZXPDF417BarcodeValue *)barcodeMatrix[0][1] value]; int calculatedNumberOfCodewords = [detectionResult barcodeColumnCount] * [detectionResult barcodeRowCount]; [self numberOfECCodeWords:detectionResult.barcodeECLevel]; if (numberOfCodewords.length == 0) { if (calculatedNumberOfCodewords < 1 || calculatedNumberOfCodewords > ZX_PDF417_MAX_CODEWORDS_IN_BARCODE) { return NO; } [(ZXPDF417BarcodeValue *)barcodeMatrix[0][1] setValue:calculatedNumberOfCodewords]; } else if (numberOfCodewords.array[0] != calculatedNumberOfCodewords) { // The calculated one is more reliable as it is derived from the row indicator columns [(ZXPDF417BarcodeValue *)barcodeMatrix[0][1] setValue:calculatedNumberOfCodewords]; } return YES; } + (ZXDecoderResult *)createDecoderResult:(ZXPDF417DetectionResult *)detectionResult error:(NSError **)error { NSArray *barcodeMatrix = [self createBarcodeMatrix:detectionResult]; if (!barcodeMatrix) { if (error) *error = ZXFormatErrorInstance(); return nil; } if (![self adjustCodewordCount:detectionResult barcodeMatrix:barcodeMatrix]) { if (error) *error = ZXNotFoundErrorInstance(); return nil; } NSMutableArray *erasures = [NSMutableArray array]; ZXIntArray *codewords = [[ZXIntArray alloc] initWithLength:detectionResult.barcodeRowCount * detectionResult.barcodeColumnCount]; NSMutableArray *ambiguousIndexValuesList = [NSMutableArray array]; NSMutableArray *ambiguousIndexesList = [NSMutableArray array]; for (int row = 0; row < detectionResult.barcodeRowCount; row++) { for (int column = 0; column < detectionResult.barcodeColumnCount; column++) { ZXIntArray *values = [(ZXPDF417BarcodeValue *)barcodeMatrix[row][column + 1] value]; int codewordIndex = row * detectionResult.barcodeColumnCount + column; if (values.length == 0) { [erasures addObject:@(codewordIndex)]; } else if (values.length == 1) { codewords.array[codewordIndex] = values.array[0]; } else { [ambiguousIndexesList addObject:@(codewordIndex)]; [ambiguousIndexValuesList addObject:values]; } } } return [self createDecoderResultFromAmbiguousValues:detectionResult.barcodeECLevel codewords:codewords erasureArray:[ZXPDF417Common toIntArray:erasures] ambiguousIndexes:[ZXPDF417Common toIntArray:ambiguousIndexesList] ambiguousIndexValues:ambiguousIndexValuesList error:error]; } /** * This method deals with the fact, that the decoding process doesn't always yield a single most likely value. The * current error correction implementation doesn't deal with erasures very well, so it's better to provide a value * for these ambiguous codewords instead of treating it as an erasure. The problem is that we don't know which of * the ambiguous values to choose. We try decode using the first value, and if that fails, we use another of the * ambiguous values and try to decode again. This usually only happens on very hard to read and decode barcodes, * so decoding the normal barcodes is not affected by this. * * @param erasureArray contains the indexes of erasures * @param ambiguousIndexes array with the indexes that have more than one most likely value * @param ambiguousIndexValues two dimensional array that contains the ambiguous values. The first dimension must * be the same length as the ambiguousIndexes array */ + (ZXDecoderResult *)createDecoderResultFromAmbiguousValues:(int)ecLevel codewords:(ZXIntArray *)codewords erasureArray:(ZXIntArray *)erasureArray ambiguousIndexes:(ZXIntArray *)ambiguousIndexes ambiguousIndexValues:(NSArray *)ambiguousIndexValues error:(NSError **)error { ZXIntArray *ambiguousIndexCount = [[ZXIntArray alloc] initWithLength:ambiguousIndexes.length]; int tries = 100; while (tries-- > 0) { for (int i = 0; i < ambiguousIndexCount.length; i++) { ZXIntArray *a = ambiguousIndexValues[i]; codewords.array[ambiguousIndexes.array[i]] = a.array[(ambiguousIndexCount.array[i] + 1) % [(ZXIntArray *)ambiguousIndexValues[i] length]]; } NSError *e; ZXDecoderResult *result = [self decodeCodewords:codewords ecLevel:ecLevel erasures:erasureArray error:&e]; if (result) { return result; } else if (e.code != ZXChecksumError) { if (error) *error = e; return nil; } if (ambiguousIndexCount.length == 0) { if (error) *error = ZXChecksumErrorInstance(); return nil; } for (int i = 0; i < ambiguousIndexCount.length; i++) { if (ambiguousIndexCount.array[i] < [(ZXIntArray *)ambiguousIndexValues[i] length] - 1) { ambiguousIndexCount.array[i]++; break; } else { ambiguousIndexCount.array[i] = 0; if (i == ambiguousIndexes.length - 1) { if (error) *error = ZXChecksumErrorInstance(); return nil; } } } } if (error) *error = ZXChecksumErrorInstance(); return nil; } + (NSArray *)createBarcodeMatrix:(ZXPDF417DetectionResult *)detectionResult { NSMutableArray *barcodeMatrix = [NSMutableArray array]; for (int row = 0; row < detectionResult.barcodeRowCount; row++) { [barcodeMatrix addObject:[NSMutableArray array]]; for (int column = 0; column < detectionResult.barcodeColumnCount + 2; column++) { barcodeMatrix[row][column] = [[ZXPDF417BarcodeValue alloc] init]; } } int column = 0; for (ZXPDF417DetectionResultColumn *detectionResultColumn in [detectionResult detectionResultColumns]) { if ((id)detectionResultColumn != [NSNull null]) { for (ZXPDF417Codeword *codeword in detectionResultColumn.codewords) { if ((id)codeword != [NSNull null]) { int rowNumber = codeword.rowNumber; if (rowNumber >= 0) { if (rowNumber >= barcodeMatrix.count) { return nil; } [(ZXPDF417BarcodeValue *)barcodeMatrix[rowNumber][column] setValue:codeword.value]; } } } } column++; } return barcodeMatrix; } + (BOOL)isValidBarcodeColumn:(ZXPDF417DetectionResult *)detectionResult barcodeColumn:(int)barcodeColumn { return barcodeColumn >= 0 && barcodeColumn <= detectionResult.barcodeColumnCount + 1; } + (int)startColumn:(ZXPDF417DetectionResult *)detectionResult barcodeColumn:(int)barcodeColumn imageRow:(int)imageRow leftToRight:(BOOL)leftToRight { int offset = leftToRight ? 1 : -1; ZXPDF417Codeword *codeword; if ([self isValidBarcodeColumn:detectionResult barcodeColumn:barcodeColumn - offset]) { codeword = [[detectionResult detectionResultColumn:barcodeColumn - offset] codeword:imageRow]; } if (codeword) { return leftToRight ? codeword.endX : codeword.startX; } codeword = [[detectionResult detectionResultColumn:barcodeColumn] codewordNearby:imageRow]; if (codeword) { return leftToRight ? codeword.startX : codeword.endX; } if ([self isValidBarcodeColumn:detectionResult barcodeColumn:barcodeColumn - offset]) { codeword = [[detectionResult detectionResultColumn:barcodeColumn - offset] codewordNearby:imageRow]; } if (codeword) { return leftToRight ? codeword.endX : codeword.startX; } int skippedColumns = 0; while ([self isValidBarcodeColumn:detectionResult barcodeColumn:barcodeColumn - offset]) { barcodeColumn -= offset; for (ZXPDF417Codeword *previousRowCodeword in [detectionResult detectionResultColumn:barcodeColumn].codewords) { if ((id)previousRowCodeword != [NSNull null]) { return (leftToRight ? previousRowCodeword.endX : previousRowCodeword.startX) + offset * skippedColumns * (previousRowCodeword.endX - previousRowCodeword.startX); } } skippedColumns++; } return leftToRight ? detectionResult.boundingBox.minX : detectionResult.boundingBox.maxX; } + (ZXPDF417Codeword *)detectCodeword:(ZXBitMatrix *)image minColumn:(int)minColumn maxColumn:(int)maxColumn leftToRight:(BOOL)leftToRight startColumn:(int)startColumn imageRow:(int)imageRow minCodewordWidth:(int)minCodewordWidth maxCodewordWidth:(int)maxCodewordWidth { startColumn = [self adjustCodewordStartColumn:image minColumn:minColumn maxColumn:maxColumn leftToRight:leftToRight codewordStartColumn:startColumn imageRow:imageRow]; // we usually know fairly exact now how long a codeword is. We should provide minimum and maximum expected length // and try to adjust the read pixels, e.g. remove single pixel errors or try to cut off exceeding pixels. // min and maxCodewordWidth should not be used as they are calculated for the whole barcode an can be inaccurate // for the current position NSMutableArray *moduleBitCount = [self moduleBitCount:image minColumn:minColumn maxColumn:maxColumn leftToRight:leftToRight startColumn:startColumn imageRow:imageRow]; if (!moduleBitCount) { return nil; } int endColumn; int codewordBitCount = [ZXPDF417Common bitCountSum:moduleBitCount]; if (leftToRight) { endColumn = startColumn + codewordBitCount; } else { for (int i = 0; i < [moduleBitCount count] / 2; i++) { int tmpCount = [moduleBitCount[i] intValue]; moduleBitCount[i] = moduleBitCount[[moduleBitCount count] - 1 - i]; moduleBitCount[[moduleBitCount count] - 1 - i] = @(tmpCount); } endColumn = startColumn; startColumn = endColumn - codewordBitCount; } // TODO implement check for width and correction of black and white bars // use start (and maybe stop pattern) to determine if blackbars are wider than white bars. If so, adjust. // should probably done only for codewords with a lot more than 17 bits. // The following fixes 10-1.png, which has wide black bars and small white bars // for (int i = 0; i < moduleBitCount.length; i++) { // if (i % 2 == 0) { // moduleBitCount[i]--; // } else { // moduleBitCount[i]++; // } // } // We could also use the width of surrounding codewords for more accurate results, but this seems // sufficient for now if (![self checkCodewordSkew:codewordBitCount minCodewordWidth:minCodewordWidth maxCodewordWidth:maxCodewordWidth]) { // We could try to use the startX and endX position of the codeword in the same column in the previous row, // create the bit count from it and normalize it to 8. This would help with single pixel errors. return nil; } int decodedValue = [ZXPDF417CodewordDecoder decodedValue:moduleBitCount]; int codeword = [ZXPDF417Common codeword:decodedValue]; if (codeword == -1) { return nil; } return [[ZXPDF417Codeword alloc] initWithStartX:startColumn endX:endColumn bucket:[self codewordBucketNumber:decodedValue] value:codeword]; } + (NSMutableArray *)moduleBitCount:(ZXBitMatrix *)image minColumn:(int)minColumn maxColumn:(int)maxColumn leftToRight:(BOOL)leftToRight startColumn:(int)startColumn imageRow:(int)imageRow { int imageColumn = startColumn; NSMutableArray *moduleBitCount = [NSMutableArray arrayWithCapacity:8]; for (int i = 0; i < 8; i++) { [moduleBitCount addObject:@0]; } int moduleNumber = 0; int increment = leftToRight ? 1 : -1; BOOL previousPixelValue = leftToRight; while (((leftToRight && imageColumn < maxColumn) || (!leftToRight && imageColumn >= minColumn)) && moduleNumber < [moduleBitCount count]) { if ([image getX:imageColumn y:imageRow] == previousPixelValue) { moduleBitCount[moduleNumber] = @([moduleBitCount[moduleNumber] intValue] + 1); imageColumn += increment; } else { moduleNumber++; previousPixelValue = !previousPixelValue; } } if (moduleNumber == [moduleBitCount count] || (((leftToRight && imageColumn == maxColumn) || (!leftToRight && imageColumn == minColumn)) && moduleNumber == [moduleBitCount count] - 1)) { return moduleBitCount; } return nil; } + (int)numberOfECCodeWords:(int)barcodeECLevel { return 2 << barcodeECLevel; } + (int)adjustCodewordStartColumn:(ZXBitMatrix *)image minColumn:(int)minColumn maxColumn:(int)maxColumn leftToRight:(BOOL)leftToRight codewordStartColumn:(int)codewordStartColumn imageRow:(int)imageRow { int correctedStartColumn = codewordStartColumn; int increment = leftToRight ? -1 : 1; // there should be no black pixels before the start column. If there are, then we need to start earlier. for (int i = 0; i < 2; i++) { while (((leftToRight && correctedStartColumn >= minColumn) || (!leftToRight && correctedStartColumn < maxColumn)) && leftToRight == [image getX:correctedStartColumn y:imageRow]) { if (abs(codewordStartColumn - correctedStartColumn) > ZX_PDF417_CODEWORD_SKEW_SIZE) { return codewordStartColumn; } correctedStartColumn += increment; } increment = -increment; leftToRight = !leftToRight; } return correctedStartColumn; } + (BOOL)checkCodewordSkew:(int)codewordSize minCodewordWidth:(int)minCodewordWidth maxCodewordWidth:(int)maxCodewordWidth { return minCodewordWidth - ZX_PDF417_CODEWORD_SKEW_SIZE <= codewordSize && codewordSize <= maxCodewordWidth + ZX_PDF417_CODEWORD_SKEW_SIZE; } + (ZXDecoderResult *)decodeCodewords:(ZXIntArray *)codewords ecLevel:(int)ecLevel erasures:(ZXIntArray *)erasures error:(NSError **)error { if (codewords.length == 0) { if (error) *error = ZXFormatErrorInstance(); return nil; } int numECCodewords = 1 << (ecLevel + 1); int correctedErrorsCount = [self correctErrors:codewords erasures:erasures numECCodewords:numECCodewords]; if (correctedErrorsCount == -1) { if (error) *error = ZXChecksumErrorInstance(); return nil; } if (![self verifyCodewordCount:codewords numECCodewords:numECCodewords]) { if (error) *error = ZXFormatErrorInstance(); return nil; } // Decode the codewords ZXDecoderResult *decoderResult = [ZXPDF417DecodedBitStreamParser decode:codewords ecLevel:[@(ecLevel) stringValue] error:error]; if (!decoderResult) { return nil; } decoderResult.errorsCorrected = @(correctedErrorsCount); decoderResult.erasures = @(erasures.length); return decoderResult; } /** * Given data and error-correction codewords received, possibly corrupted by errors, attempts to * correct the errors in-place. * * @param codewords data and error correction codewords * @param erasures positions of any known erasures * @param numECCodewords number of error correction codewords that are available in codewords * @throws ChecksumException if error correction fails */ + (int)correctErrors:(ZXIntArray *)codewords erasures:(ZXIntArray *)erasures numECCodewords:(int)numECCodewords { if (erasures && (erasures.length > numECCodewords / 2 + ZX_PDF417_MAX_ERRORS || numECCodewords < 0 || numECCodewords > ZX_PDF417_MAX_EC_CODEWORDS)) { // Too many errors or EC Codewords is corrupted return -1; } return [errorCorrection decode:codewords numECCodewords:numECCodewords erasures:erasures]; } /** * Verify that all is OK with the codeword array. */ + (BOOL)verifyCodewordCount:(ZXIntArray *)codewords numECCodewords:(int)numECCodewords { if (codewords.length < 4) { // Codeword array size should be at least 4 allowing for // Count CW, At least one Data CW, Error Correction CW, Error Correction CW return NO; } // The first codeword, the Symbol Length Descriptor, shall always encode the total number of data // codewords in the symbol, including the Symbol Length Descriptor itself, data codewords and pad // codewords, but excluding the number of error correction codewords. int numberOfCodewords = codewords.array[0]; if (numberOfCodewords > codewords.length) { return NO; } if (numberOfCodewords == 0) { // Reset to the length of the array - 8 (Allow for at least level 3 Error Correction (8 Error Codewords) if (numECCodewords < codewords.length) { codewords.array[0] = codewords.length - numECCodewords; } else { return NO; } } return YES; } + (NSArray *)bitCountForCodeword:(int)codeword { NSMutableArray *result = [NSMutableArray array]; for (int i = 0; i < 8; i++) { [result addObject:@0]; } int previousValue = 0; int i = (int)[result count] - 1; while (YES) { if ((codeword & 0x1) != previousValue) { previousValue = codeword & 0x1; i--; if (i < 0) { break; } } result[i] = @([result[i] intValue] + 1); codeword >>= 1; } return result; } + (int)codewordBucketNumber:(int)codeword { return [self codewordBucketNumberWithModuleBitCount:[self bitCountForCodeword:codeword]]; } + (int)codewordBucketNumberWithModuleBitCount:(NSArray *)moduleBitCount { return ([moduleBitCount[0] intValue] - [moduleBitCount[2] intValue] + [moduleBitCount[4] intValue] - [moduleBitCount[6] intValue] + 9) % 9; } - (NSString *)description:(NSArray *)barcodeMatrix { NSMutableString *result = [NSMutableString string]; for (int row = 0; row < [barcodeMatrix count]; row++) { [result appendFormat:@"Row %2d: ", row]; for (int column = 0; column < [(NSArray *)barcodeMatrix[row] count]; column++) { ZXPDF417BarcodeValue *barcodeValue = barcodeMatrix[row][column]; if ([barcodeValue value].length == 0) { [result appendString:@" "]; } else { [result appendFormat:@"%4d(%2d)", [barcodeValue value].array[0], [[barcodeValue confidence:[barcodeValue value].array[0]] intValue]]; } } [result appendString:@"\n"]; } return [NSString stringWithString:result]; } @end