123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356 |
- /*
- * 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 "ZXDecodeHints.h"
- #import "ZXErrors.h"
- #import "ZXIntArray.h"
- #import "ZXITFReader.h"
- #import "ZXResult.h"
- #import "ZXResultPoint.h"
-
- static float ZX_ITF_MAX_AVG_VARIANCE = 0.38f;
- static float ZX_ITF_MAX_INDIVIDUAL_VARIANCE = 0.78f;
-
- static const int ZX_ITF_W = 3; // Pixel width of a wide line
- static const int ZX_ITF_N = 1; // Pixel width of a narrow line
-
- /** Valid ITF lengths. Anything longer than the largest value is also allowed. */
- const int ZX_ITF_DEFAULT_ALLOWED_LENGTHS[] = { 6, 8, 10, 12, 14 };
-
- /**
- * Start/end guard pattern.
- *
- * Note: The end pattern is reversed because the row is reversed before
- * searching for the END_PATTERN
- */
- const int ZX_ITF_ITF_START_PATTERN[] = {ZX_ITF_N, ZX_ITF_N, ZX_ITF_N, ZX_ITF_N};
- const int ZX_ITF_END_PATTERN_REVERSED[] = {ZX_ITF_N, ZX_ITF_N, ZX_ITF_W};
-
- /**
- * Patterns of Wide / Narrow lines to indicate each digit
- */
- const int ZX_ITF_PATTERNS_LEN = 10;
- const int ZX_ITF_PATTERNS[ZX_ITF_PATTERNS_LEN][5] = {
- {ZX_ITF_N, ZX_ITF_N, ZX_ITF_W, ZX_ITF_W, ZX_ITF_N}, // 0
- {ZX_ITF_W, ZX_ITF_N, ZX_ITF_N, ZX_ITF_N, ZX_ITF_W}, // 1
- {ZX_ITF_N, ZX_ITF_W, ZX_ITF_N, ZX_ITF_N, ZX_ITF_W}, // 2
- {ZX_ITF_W, ZX_ITF_W, ZX_ITF_N, ZX_ITF_N, ZX_ITF_N}, // 3
- {ZX_ITF_N, ZX_ITF_N, ZX_ITF_W, ZX_ITF_N, ZX_ITF_W}, // 4
- {ZX_ITF_W, ZX_ITF_N, ZX_ITF_W, ZX_ITF_N, ZX_ITF_N}, // 5
- {ZX_ITF_N, ZX_ITF_W, ZX_ITF_W, ZX_ITF_N, ZX_ITF_N}, // 6
- {ZX_ITF_N, ZX_ITF_N, ZX_ITF_N, ZX_ITF_W, ZX_ITF_W}, // 7
- {ZX_ITF_W, ZX_ITF_N, ZX_ITF_N, ZX_ITF_W, ZX_ITF_N}, // 8
- {ZX_ITF_N, ZX_ITF_W, ZX_ITF_N, ZX_ITF_W, ZX_ITF_N} // 9
- };
-
- @interface ZXITFReader ()
-
- @property (nonatomic, assign) int narrowLineWidth;
-
- @end
-
- @implementation ZXITFReader
-
- - (id)init {
- if (self = [super init]) {
- _narrowLineWidth = -1;
- }
-
- return self;
- }
-
- - (ZXResult *)decodeRow:(int)rowNumber row:(ZXBitArray *)row hints:(ZXDecodeHints *)hints error:(NSError **)error {
- // Find out where the Middle section (payload) starts & ends
- ZXIntArray *startRange = [self decodeStart:row];
- ZXIntArray *endRange = [self decodeEnd:row];
- if (!startRange || !endRange) {
- if (error) *error = ZXNotFoundErrorInstance();
- return nil;
- }
-
- NSMutableString *resultString = [NSMutableString stringWithCapacity:20];
- if (![self decodeMiddle:row payloadStart:startRange.array[1] payloadEnd:endRange.array[0] resultString:resultString]) {
- if (error) *error = ZXNotFoundErrorInstance();
- return nil;
- }
-
- NSArray *allowedLengths = nil;
- if (hints != nil) {
- allowedLengths = hints.allowedLengths;
- }
- if (allowedLengths == nil) {
- NSMutableArray *temp = [NSMutableArray array];
- for (int i = 0; i < sizeof(ZX_ITF_DEFAULT_ALLOWED_LENGTHS) / sizeof(int); i++) {
- [temp addObject:@(ZX_ITF_DEFAULT_ALLOWED_LENGTHS[i])];
- }
- allowedLengths = [NSArray arrayWithArray:temp];
- }
-
- // To avoid false positives with 2D barcodes (and other patterns), make
- // an assumption that the decoded string must be a 'standard' length if it's short
- NSUInteger length = [resultString length];
- BOOL lengthOK = NO;
- int maxAllowedLength = 0;
- for (NSNumber *i in allowedLengths) {
- int allowedLength = [i intValue];
- if (length == allowedLength) {
- lengthOK = YES;
- break;
- }
- if (allowedLength > maxAllowedLength) {
- maxAllowedLength = allowedLength;
- }
- }
- if (!lengthOK && length > maxAllowedLength) {
- lengthOK = YES;
- }
- if (!lengthOK) {
- if (error) *error = ZXFormatErrorInstance();
- return nil;
- }
-
- return [ZXResult resultWithText:resultString
- rawBytes:nil
- resultPoints:@[[[ZXResultPoint alloc] initWithX:startRange.array[1] y:(float)rowNumber],
- [[ZXResultPoint alloc] initWithX:endRange.array[0] y:(float)rowNumber]]
- format:kBarcodeFormatITF];
- }
-
- /**
- * @param row row of black/white values to search
- * @param payloadStart offset of start pattern
- * @param resultString NSMutableString to append decoded chars to
- * @return NO if decoding could not complete successfully
- */
- - (BOOL)decodeMiddle:(ZXBitArray *)row payloadStart:(int)payloadStart payloadEnd:(int)payloadEnd resultString:(NSMutableString *)resultString {
- // Digits are interleaved in pairs - 5 black lines for one digit, and the
- // 5
- // interleaved white lines for the second digit.
- // Therefore, need to scan 10 lines and then
- // split these into two arrays
- ZXIntArray *counterDigitPair = [[ZXIntArray alloc] initWithLength:10];
- ZXIntArray *counterBlack = [[ZXIntArray alloc] initWithLength:5];
- ZXIntArray *counterWhite = [[ZXIntArray alloc] initWithLength:5];
-
- while (payloadStart < payloadEnd) {
- // Get 10 runs of black/white.
- if (![ZXOneDReader recordPattern:row start:payloadStart counters:counterDigitPair]) {
- return NO;
- }
- // Split them into each array
- for (int k = 0; k < 5; k++) {
- int twoK = 2 * k;
- counterBlack.array[k] = counterDigitPair.array[twoK];
- counterWhite.array[k] = counterDigitPair.array[twoK + 1];
- }
-
- int bestMatch = [self decodeDigit:counterBlack];
- if (bestMatch == -1) {
- return NO;
- }
- [resultString appendFormat:@"%C", (unichar)('0' + bestMatch)];
- bestMatch = [self decodeDigit:counterWhite];
- if (bestMatch == -1) {
- return NO;
- }
- [resultString appendFormat:@"%C", (unichar)('0' + bestMatch)];
-
- for (int i = 0; i < counterDigitPair.length; i++) {
- payloadStart += counterDigitPair.array[i];
- }
- }
- return YES;
- }
-
- /**
- * Identify where the start of the middle / payload section starts.
- *
- * @param row row of black/white values to search
- * @return Array, containing index of start of 'start block' and end of
- * 'start block'
- */
- - (ZXIntArray *)decodeStart:(ZXBitArray *)row {
- int endStart = [self skipWhiteSpace:row];
- if (endStart == -1) {
- return nil;
- }
- ZXIntArray *startPattern = [self findGuardPattern:row rowOffset:endStart pattern:ZX_ITF_ITF_START_PATTERN patternLen:sizeof(ZX_ITF_ITF_START_PATTERN)/sizeof(int)];
- if (!startPattern) {
- return nil;
- }
-
- self.narrowLineWidth = (startPattern.array[1] - startPattern.array[0]) / 4;
-
- if (![self validateQuietZone:row startPattern:startPattern.array[0]]) {
- return nil;
- }
-
- return startPattern;
- }
-
- /**
- * The start & end patterns must be pre/post fixed by a quiet zone. This
- * zone must be at least 10 times the width of a narrow line. Scan back until
- * we either get to the start of the barcode or match the necessary number of
- * quiet zone pixels.
- *
- * Note: Its assumed the row is reversed when using this method to find
- * quiet zone after the end pattern.
- *
- * ref: http://www.barcode-1.net/i25code.html
- *
- * @param row bit array representing the scanned barcode.
- * @param startPattern index into row of the start or end pattern.
- * @return NO if the quiet zone cannot be found, a ReaderException is thrown.
- */
- - (BOOL)validateQuietZone:(ZXBitArray *)row startPattern:(int)startPattern {
- int quietCount = self.narrowLineWidth * 10;
-
- // if there are not so many pixel at all let's try as many as possible
- quietCount = quietCount < startPattern ? quietCount : startPattern;
-
- for (int i = startPattern - 1; quietCount > 0 && i >= 0; i--) {
- if ([row get:i]) {
- break;
- }
- quietCount--;
- }
- if (quietCount != 0) {
- return NO;
- }
- return YES;
- }
-
- /**
- * Skip all whitespace until we get to the first black line.
- *
- * @param row row of black/white values to search
- * @return index of the first black line or -1 if no black lines are found in the row
- */
- - (int)skipWhiteSpace:(ZXBitArray *)row {
- int width = [row size];
- int endStart = [row nextSet:0];
- if (endStart == width) {
- return -1;
- }
- return endStart;
- }
-
- /**
- * Identify where the end of the middle / payload section ends.
- *
- * @param row row of black/white values to search
- * @return Array, containing index of start of 'end block' and end of 'end
- * block'
- */
- - (ZXIntArray *)decodeEnd:(ZXBitArray *)row {
- [row reverse];
-
- int endStart = [self skipWhiteSpace:row];
- if (endStart == -1) {
- [row reverse];
- return nil;
- }
- ZXIntArray *endPattern = [self findGuardPattern:row rowOffset:endStart pattern:ZX_ITF_END_PATTERN_REVERSED patternLen:sizeof(ZX_ITF_END_PATTERN_REVERSED)/sizeof(int)];
- if (!endPattern) {
- [row reverse];
- return nil;
- }
- if (![self validateQuietZone:row startPattern:endPattern.array[0]]) {
- [row reverse];
- return nil;
- }
- int temp = endPattern.array[0];
- endPattern.array[0] = [row size] - endPattern.array[1];
- endPattern.array[1] = [row size] - temp;
- [row reverse];
- return endPattern;
- }
-
- /**
- * @param row row of black/white values to search
- * @param rowOffset position to start search
- * @param pattern pattern of counts of number of black and white pixels that are
- * being searched for as a pattern
- * @return start/end horizontal offset of guard pattern, as an array of two
- * ints or nil if pattern is not found
- */
- - (ZXIntArray *)findGuardPattern:(ZXBitArray *)row rowOffset:(int)rowOffset pattern:(const int[])pattern patternLen:(int)patternLen {
- int patternLength = patternLen;
- ZXIntArray *counters = [[ZXIntArray alloc] initWithLength:patternLength];
- int32_t *array = counters.array;
- int width = row.size;
- BOOL isWhite = NO;
-
- int counterPosition = 0;
- int patternStart = rowOffset;
- for (int x = rowOffset; x < width; x++) {
- if ([row get:x] ^ isWhite) {
- array[counterPosition]++;
- } else {
- if (counterPosition == patternLength - 1) {
- if ([ZXOneDReader patternMatchVariance:counters pattern:pattern maxIndividualVariance:ZX_ITF_MAX_INDIVIDUAL_VARIANCE] < ZX_ITF_MAX_AVG_VARIANCE) {
- return [[ZXIntArray alloc] initWithInts:patternStart, x, -1];
- }
- patternStart += array[0] + array[1];
- for (int y = 2; y < patternLength; y++) {
- array[y - 2] = array[y];
- }
- array[patternLength - 2] = 0;
- array[patternLength - 1] = 0;
- counterPosition--;
- } else {
- counterPosition++;
- }
- array[counterPosition] = 1;
- isWhite = !isWhite;
- }
- }
-
- return nil;
- }
-
- /**
- * Attempts to decode a sequence of ITF black/white lines into single
- * digit.
- *
- * @param counters the counts of runs of observed black/white/black/... values
- * @return The decoded digit or -1 if digit cannot be decoded
- */
- - (int)decodeDigit:(ZXIntArray *)counters {
- float bestVariance = ZX_ITF_MAX_AVG_VARIANCE; // worst variance we'll accept
- int bestMatch = -1;
- int max = ZX_ITF_PATTERNS_LEN;
- for (int i = 0; i < max; i++) {
- int pattern[counters.length];
- for (int ind = 0; ind < counters.length; ind++){
- pattern[ind] = ZX_ITF_PATTERNS[i][ind];
- }
- float variance = [ZXOneDReader patternMatchVariance:counters pattern:pattern maxIndividualVariance:ZX_ITF_MAX_INDIVIDUAL_VARIANCE];
- if (variance < bestVariance) {
- bestVariance = variance;
- bestMatch = i;
- }
- }
- if (bestMatch >= 0) {
- return bestMatch;
- } else {
- return -1;
- }
- }
-
- @end
|