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