123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348 |
- /*
- * Copyright 2012 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 "ZXBitArray.h"
- #import "ZXBitMatrix.h"
- #import "ZXBinaryBitmap.h"
- #import "ZXDecodeHints.h"
- #import "ZXErrors.h"
- #import "ZXGridSampler.h"
- #import "ZXMathUtils.h"
- #import "ZXPDF417Detector.h"
- #import "ZXPDF417DetectorResult.h"
- #import "ZXPerspectiveTransform.h"
- #import "ZXResultPoint.h"
-
- const int ZX_PDF417_INDEXES_START_PATTERN[] = {0, 4, 1, 5};
- const int ZX_PDF417_INDEXES_STOP_PATTERN[] = {6, 2, 7, 3};
- const float ZX_PDF417_MAX_AVG_VARIANCE = 0.42f;
- const float ZX_PDF417_MAX_INDIVIDUAL_VARIANCE = 0.8f;
-
- // B S B S B S B S Bar/Space pattern
- // 11111111 0 1 0 1 0 1 000
- const int ZX_PDF417_DETECTOR_START_PATTERN[] = {8, 1, 1, 1, 1, 1, 1, 3};
-
- // 1111111 0 1 000 1 0 1 00 1
- const int ZX_PDF417_DETECTOR_STOP_PATTERN[] = {7, 1, 1, 3, 1, 1, 1, 2, 1};
- const int ZX_PDF417_MAX_PIXEL_DRIFT = 3;
- const int ZX_PDF417_MAX_PATTERN_DRIFT = 5;
- // if we set the value too low, then we don't detect the correct height of the bar if the start patterns are damaged.
- // if we set the value too high, then we might detect the start pattern from a neighbor barcode.
- const int ZX_PDF417_SKIPPED_ROW_COUNT_MAX = 25;
- // A PDF471 barcode should have at least 3 rows, with each row being >= 3 times the module width. Therefore it should be at least
- // 9 pixels tall. To be conservative, we use about half the size to ensure we don't miss it.
- const int ZX_PDF417_ROW_STEP = 5;
- const int ZX_PDF417_BARCODE_MIN_HEIGHT = 10;
-
- @implementation ZXPDF417Detector
-
- + (ZXPDF417DetectorResult *)detect:(ZXBinaryBitmap *)image hints:(ZXDecodeHints *)hints multiple:(BOOL)multiple error:(NSError **)error {
- // TODO detection improvement, tryHarder could try several different luminance thresholds/blackpoints or even
- // different binarizers
- //boolean tryHarder = hints != null && hints.containsKey(DecodeHintType.TRY_HARDER);
-
- ZXBitMatrix *bitMatrix = [image blackMatrixWithError:error];
-
- NSArray *barcodeCoordinates = [self detect:multiple bitMatrix:bitMatrix error:error];
- if (!barcodeCoordinates) {
- return nil;
- }
- if ([barcodeCoordinates count] == 0) {
- bitMatrix = [bitMatrix copy];
- [bitMatrix rotate180];
- barcodeCoordinates = [self detect:multiple bitMatrix:bitMatrix error:error];
- if (!barcodeCoordinates) {
- return nil;
- }
- }
- return [[ZXPDF417DetectorResult alloc] initWithBits:bitMatrix points:barcodeCoordinates];
- }
-
- /**
- * Detects PDF417 codes in an image. Only checks 0 degree rotation
- * @param multiple if true, then the image is searched for multiple codes. If false, then at most one code will
- * be found and returned
- * @param bitMatrix bit matrix to detect barcodes in
- * @return List of ResultPoint arrays containing the coordinates of found barcodes
- */
- + (NSArray *)detect:(BOOL)multiple bitMatrix:(ZXBitMatrix *)bitMatrix error:(NSError **)error {
- NSMutableArray *barcodeCoordinates = [NSMutableArray array];
- int row = 0;
- int column = 0;
- BOOL foundBarcodeInRow = NO;
- while (row < bitMatrix.height) {
- NSArray *vertices = [self findVertices:bitMatrix startRow:row startColumn:column];
-
- if (vertices[0] == [NSNull null] && vertices[3] == [NSNull null]) {
- if (!foundBarcodeInRow) {
- // we didn't find any barcode so that's the end of searching
- break;
- }
- // we didn't find a barcode starting at the given column and row. Try again from the first column and slightly
- // below the lowest barcode we found so far.
- foundBarcodeInRow = NO;
- column = 0;
- for (NSArray *barcodeCoordinate in barcodeCoordinates) {
- if (barcodeCoordinate[1] != [NSNull null]) {
- row = MAX(row, (int) [(ZXResultPoint *)barcodeCoordinate[1] y]);
- }
- if (barcodeCoordinate[3] != [NSNull null]) {
- row = MAX(row, (int) [(ZXResultPoint *)barcodeCoordinate[3] y]);
- }
- }
- row += ZX_PDF417_ROW_STEP;
- continue;
- }
- foundBarcodeInRow = YES;
- [barcodeCoordinates addObject:vertices];
- if (!multiple) {
- break;
- }
- // if we didn't find a right row indicator column, then continue the search for the next barcode after the
- // start pattern of the barcode just found.
- if (vertices[2] != [NSNull null]) {
- column = (int) [(ZXResultPoint *)vertices[2] x];
- row = (int) [(ZXResultPoint *)vertices[2] y];
- } else {
- column = (int) [(ZXResultPoint *)vertices[4] x];
- row = (int) [(ZXResultPoint *)vertices[4] y];
- }
- }
- return barcodeCoordinates;
- }
-
- /**
- * Locate the vertices and the codewords area of a black blob using the Start
- * and Stop patterns as locators.
- *
- * @param matrix the scanned barcode image.
- * @return an array containing the vertices:
- * vertices[0] x, y top left barcode
- * vertices[1] x, y bottom left barcode
- * vertices[2] x, y top right barcode
- * vertices[3] x, y bottom right barcode
- * vertices[4] x, y top left codeword area
- * vertices[5] x, y bottom left codeword area
- * vertices[6] x, y top right codeword area
- * vertices[7] x, y bottom right codeword area
- */
- + (NSMutableArray *)findVertices:(ZXBitMatrix *)matrix startRow:(int)startRow startColumn:(int)startColumn {
- int height = matrix.height;
- int width = matrix.width;
-
- NSMutableArray *result = [NSMutableArray arrayWithCapacity:8];
- for (int i = 0; i < 8; i++) {
- [result addObject:[NSNull null]];
- }
- [self copyToResult:result
- tmpResult:[self findRowsWithPattern:matrix
- height:height
- width:width
- startRow:startRow
- startColumn:startColumn
- pattern:ZX_PDF417_DETECTOR_START_PATTERN
- patternLen:sizeof(ZX_PDF417_DETECTOR_START_PATTERN) / sizeof(int)]
- destinationIndexes:ZX_PDF417_INDEXES_START_PATTERN
- length:sizeof(ZX_PDF417_INDEXES_START_PATTERN) / sizeof(int)];
-
- if (result[4] != [NSNull null]) {
- startColumn = (int) [(ZXResultPoint *)result[4] x];
- startRow = (int) [(ZXResultPoint *)result[4] y];
- }
- [self copyToResult:result
- tmpResult:[self findRowsWithPattern:matrix
- height:height
- width:width
- startRow:startRow
- startColumn:startColumn
- pattern:ZX_PDF417_DETECTOR_STOP_PATTERN
- patternLen:sizeof(ZX_PDF417_DETECTOR_STOP_PATTERN) / sizeof(int)]
- destinationIndexes:ZX_PDF417_INDEXES_STOP_PATTERN
- length:sizeof(ZX_PDF417_INDEXES_STOP_PATTERN) / sizeof(int)];
- return result;
- }
-
- + (void)copyToResult:(NSMutableArray *)result tmpResult:(NSMutableArray *)tmpResult destinationIndexes:(const int[])destinationIndexes length:(int)length {
- for (int i = 0; i < length; i++) {
- result[destinationIndexes[i]] = tmpResult[i];
- }
- }
-
- + (NSMutableArray *)findRowsWithPattern:(ZXBitMatrix *)matrix height:(int)height width:(int)width startRow:(int)startRow
- startColumn:(int)startColumn pattern:(const int[])pattern patternLen:(int)patternLen {
- NSMutableArray *result = [NSMutableArray array];
- for (int i = 0; i < 4; i++) {
- [result addObject:[NSNull null]];
- }
- BOOL found = NO;
- int counters[patternLen];
- memset(counters, 0, patternLen * sizeof(int));
- for (; startRow < height; startRow += ZX_PDF417_ROW_STEP) {
- NSRange loc = [self findGuardPattern:matrix column:startColumn row:startRow width:width whiteFirst:false pattern:pattern patternLen:patternLen counters:counters];
- if (loc.location != NSNotFound) {
- while (startRow > 0) {
- NSRange previousRowLoc = [self findGuardPattern:matrix column:startColumn row:--startRow width:width whiteFirst:false pattern:pattern patternLen:patternLen counters:counters];
- if (previousRowLoc.location != NSNotFound) {
- loc = previousRowLoc;
- } else {
- startRow++;
- break;
- }
- }
- result[0] = [[ZXResultPoint alloc] initWithX:loc.location y:startRow];
- result[1] = [[ZXResultPoint alloc] initWithX:NSMaxRange(loc) y:startRow];
- found = YES;
- break;
- }
- }
- int stopRow = startRow + 1;
- // Last row of the current symbol that contains pattern
- if (found) {
- int skippedRowCount = 0;
- NSRange previousRowLoc = NSMakeRange((NSUInteger) [(ZXResultPoint *)result[0] x], ((NSUInteger)[(ZXResultPoint *)result[1] x]) - ((NSUInteger)[(ZXResultPoint *)result[0] x]));
- for (; stopRow < height; stopRow++) {
- NSRange loc = [self findGuardPattern:matrix column:(int)previousRowLoc.location row:stopRow width:width whiteFirst:NO pattern:pattern patternLen:patternLen counters:counters];
- // a found pattern is only considered to belong to the same barcode if the start and end positions
- // don't differ too much. Pattern drift should be not bigger than two for consecutive rows. With
- // a higher number of skipped rows drift could be larger. To keep it simple for now, we allow a slightly
- // larger drift and don't check for skipped rows.
- if (loc.location != NSNotFound &&
- ABS((int)previousRowLoc.location - (int)loc.location) < ZX_PDF417_MAX_PATTERN_DRIFT &&
- ABS((int)NSMaxRange(previousRowLoc) - (int)NSMaxRange(loc)) < ZX_PDF417_MAX_PATTERN_DRIFT) {
- previousRowLoc = loc;
- skippedRowCount = 0;
- } else {
- if (skippedRowCount > ZX_PDF417_SKIPPED_ROW_COUNT_MAX) {
- break;
- } else {
- skippedRowCount++;
- }
- }
- }
- stopRow -= skippedRowCount + 1;
- result[2] = [[ZXResultPoint alloc] initWithX:previousRowLoc.location y:stopRow];
- result[3] = [[ZXResultPoint alloc] initWithX:NSMaxRange(previousRowLoc) y:stopRow];
- }
- if (stopRow - startRow < ZX_PDF417_BARCODE_MIN_HEIGHT) {
- for (int i = 0; i < 4; i++) {
- result[i] = [NSNull null];
- }
- }
- return result;
- }
-
- /**
- * @param matrix row of black/white values to search
- * @param column x position to start search
- * @param row y position to start search
- * @param width the number of pixels to search on this row
- * @param pattern pattern of counts of number of black and white pixels that are
- * being searched for as a pattern
- * @param counters array of counters, as long as pattern, to re-use
- * @return start/end horizontal offset of guard pattern, as an array of two ints.
- */
- + (NSRange)findGuardPattern:(ZXBitMatrix *)matrix
- column:(int)column
- row:(int)row
- width:(int)width
- whiteFirst:(BOOL)whiteFirst
- pattern:(const int[])pattern
- patternLen:(int)patternLen
- counters:(int *)counters {
- int patternLength = patternLen;
- memset(counters, 0, patternLength * sizeof(int));
- BOOL isWhite = whiteFirst;
- int patternStart = column;
- int pixelDrift = 0;
-
- // if there are black pixels left of the current pixel shift to the left, but only for ZX_PDF417_MAX_PIXEL_DRIFT pixels
- while ([matrix getX:patternStart y:row] && patternStart > 0 && pixelDrift++ < ZX_PDF417_MAX_PIXEL_DRIFT) {
- patternStart--;
- }
- int x = patternStart;
- int counterPosition = 0;
- for (;x < width; x++) {
- BOOL pixel = [matrix getX:x y:row];
- if (pixel ^ isWhite) {
- counters[counterPosition] = counters[counterPosition] + 1;
- } else {
- if (counterPosition == patternLength - 1) {
- if ([self patternMatchVariance:counters countersSize:patternLength pattern:pattern maxIndividualVariance:ZX_PDF417_MAX_INDIVIDUAL_VARIANCE] < ZX_PDF417_MAX_AVG_VARIANCE) {
- return NSMakeRange(patternStart, x - patternStart);
- }
- patternStart += counters[0] + counters[1];
- for (int y = 2; y < patternLength; y++) {
- counters[y - 2] = counters[y];
- }
- counters[patternLength - 2] = 0;
- counters[patternLength - 1] = 0;
- counterPosition--;
- } else {
- counterPosition++;
- }
- counters[counterPosition] = 1;
- isWhite = !isWhite;
- }
- }
- if (counterPosition == patternLength - 1) {
- if ([self patternMatchVariance:counters countersSize:patternLen pattern:pattern maxIndividualVariance:ZX_PDF417_MAX_INDIVIDUAL_VARIANCE] < ZX_PDF417_MAX_AVG_VARIANCE) {
- return NSMakeRange(patternStart, x - patternStart - 1);
- }
- }
- return NSMakeRange(NSNotFound, 0);
- }
-
- /**
- * Determines how closely a set of observed counts of runs of black/white
- * values matches a given target pattern. This is reported as the ratio of
- * the total variance from the expected pattern proportions across all
- * pattern elements, to the length of the pattern.
- *
- * @param counters observed counters
- * @param pattern expected pattern
- * @param maxIndividualVariance The most any counter can differ before we give up
- * @return ratio of total variance between counters and pattern compared to total pattern size
- */
- + (float)patternMatchVariance:(int *)counters countersSize:(int)countersSize pattern:(const int[])pattern maxIndividualVariance:(float)maxIndividualVariance {
- int numCounters = countersSize;
- int total = 0;
- int patternLength = 0;
- for (int i = 0; i < numCounters; i++) {
- total += counters[i];
- patternLength += pattern[i];
- }
-
- if (total < patternLength || patternLength == 0) {
- return FLT_MAX;
- }
- float unitBarWidth = (float) total / patternLength;
- maxIndividualVariance *= unitBarWidth;
-
- float totalVariance = 0.0f;
- for (int x = 0; x < numCounters; x++) {
- int counter = counters[x];
- float scaledPattern = pattern[x] * unitBarWidth;
- float variance = counter > scaledPattern ? counter - scaledPattern : scaledPattern - counter;
- if (variance > maxIndividualVariance) {
- return FLT_MAX;
- }
- totalVariance += variance;
- }
-
- return totalVariance / total;
- }
-
- @end
|