/*
|
* 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 "ZXCodaBarReader.h"
|
#import "ZXDecodeHints.h"
|
#import "ZXErrors.h"
|
#import "ZXIntArray.h"
|
#import "ZXResult.h"
|
#import "ZXResultPoint.h"
|
|
// These values are critical for determining how permissive the decoding
|
// will be. All stripe sizes must be within the window these define, as
|
// compared to the average stripe size.
|
static float ZX_CODA_MAX_ACCEPTABLE = 2.0f;
|
static float ZX_CODA_PADDING = 1.5f;
|
|
const unichar ZX_CODA_ALPHABET[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
'-', '$', ':', '/', '.', '+', 'A', 'B', 'C', 'D', 'T', 'N'};
|
const int ZX_CODA_ALPHABET_LEN = sizeof(ZX_CODA_ALPHABET) / sizeof(unichar);
|
|
/**
|
* These represent the encodings of characters, as patterns of wide and narrow bars. The 7 least-significant bits of
|
* each int correspond to the pattern of wide and narrow, with 1s representing "wide" and 0s representing narrow.
|
*/
|
const int ZX_CODA_CHARACTER_ENCODINGS[] = {
|
0x003, 0x006, 0x009, 0x060, 0x012, 0x042, 0x021, 0x024, 0x030, 0x048, // 0-9
|
0x00c, 0x018, 0x045, 0x051, 0x054, 0x015, 0x01A, 0x029, 0x00B, 0x00E, // -$:/.+ABCD
|
};
|
|
// minimal number of characters that should be present (inclusing start and stop characters)
|
// under normal circumstances this should be set to 3, but can be set higher
|
// as a last-ditch attempt to reduce false positives.
|
const int ZX_CODA_MIN_CHARACTER_LENGTH = 3;
|
|
// official start and end patterns
|
const unichar ZX_CODA_STARTEND_ENCODING[] = {'A', 'B', 'C', 'D'};
|
|
// some codabar generator allow the codabar string to be closed by every
|
// character. This will cause lots of false positives!
|
|
// some industries use a checksum standard but this is not part of the original codabar standard
|
// for more information see : http://www.mecsw.com/specs/codabar.html
|
|
@interface ZXCodaBarReader ()
|
|
@property (nonatomic, strong) NSMutableString *decodeRowResult;
|
@property (nonatomic, strong) ZXIntArray *counters;
|
@property (nonatomic, assign) int counterLength;
|
|
@end
|
|
@implementation ZXCodaBarReader
|
|
- (id)init {
|
if (self = [super init]) {
|
_decodeRowResult = [NSMutableString stringWithCapacity:20];
|
_counters = [[ZXIntArray alloc] initWithLength:80];
|
_counterLength = 0;
|
}
|
|
return self;
|
}
|
|
- (ZXResult *)decodeRow:(int)rowNumber row:(ZXBitArray *)row hints:(ZXDecodeHints *)hints error:(NSError **)error {
|
[self.counters clear];
|
|
if (![self setCountersWithRow:row]) {
|
if (error) *error = ZXNotFoundErrorInstance();
|
return nil;
|
}
|
|
int startOffset = [self findStartPattern];
|
if (startOffset == -1) {
|
if (error) *error = ZXNotFoundErrorInstance();
|
return nil;
|
}
|
|
int nextStart = startOffset;
|
|
self.decodeRowResult = [NSMutableString string];
|
do {
|
int charOffset = [self toNarrowWidePattern:nextStart];
|
if (charOffset == -1) {
|
if (error) *error = ZXNotFoundErrorInstance();
|
return nil;
|
}
|
// Hack: We store the position in the alphabet table into a
|
// NSMutableString, so that we can access the decoded patterns in
|
// validatePattern. We'll translate to the actual characters later.
|
[self.decodeRowResult appendFormat:@"%C", (unichar)charOffset];
|
nextStart += 8;
|
// Stop as soon as we see the end character.
|
if (self.decodeRowResult.length > 1 &&
|
[ZXCodaBarReader arrayContains:ZX_CODA_STARTEND_ENCODING
|
length:sizeof(ZX_CODA_STARTEND_ENCODING) / sizeof(unichar)
|
key:ZX_CODA_ALPHABET[charOffset]]) {
|
break;
|
}
|
} while (nextStart < self.counterLength); // no fixed end pattern so keep on reading while data is available
|
|
// Look for whitespace after pattern:
|
int trailingWhitespace = self.counters.array[nextStart - 1];
|
int lastPatternSize = 0;
|
for (int i = -8; i < -1; i++) {
|
lastPatternSize += self.counters.array[nextStart + i];
|
}
|
|
// We need to see whitespace equal to 50% of the last pattern size,
|
// otherwise this is probably a false positive. The exception is if we are
|
// at the end of the row. (I.e. the barcode barely fits.)
|
if (nextStart < self.counterLength && trailingWhitespace < lastPatternSize / 2) {
|
if (error) *error = ZXNotFoundErrorInstance();
|
return nil;
|
}
|
|
if (![self validatePattern:startOffset]) {
|
if (error) *error = ZXNotFoundErrorInstance();
|
return nil;
|
}
|
|
// Translate character table offsets to actual characters.
|
for (int i = 0; i < self.decodeRowResult.length; i++) {
|
[self.decodeRowResult replaceCharactersInRange:NSMakeRange(i, 1) withString:[NSString stringWithFormat:@"%c", ZX_CODA_ALPHABET[[self.decodeRowResult characterAtIndex:i]]]];
|
}
|
// Ensure a valid start and end character
|
unichar startchar = [self.decodeRowResult characterAtIndex:0];
|
if (![ZXCodaBarReader arrayContains:ZX_CODA_STARTEND_ENCODING
|
length:sizeof(ZX_CODA_STARTEND_ENCODING) / sizeof(unichar)
|
key:startchar]) {
|
if (error) *error = ZXNotFoundErrorInstance();
|
return nil;
|
}
|
unichar endchar = [self.decodeRowResult characterAtIndex:self.decodeRowResult.length - 1];
|
if (![ZXCodaBarReader arrayContains:ZX_CODA_STARTEND_ENCODING
|
length:sizeof(ZX_CODA_STARTEND_ENCODING) / sizeof(unichar)
|
key:endchar]) {
|
if (error) *error = ZXNotFoundErrorInstance();
|
return nil;
|
}
|
|
// remove stop/start characters character and check if a long enough string is contained
|
if (self.decodeRowResult.length <= ZX_CODA_MIN_CHARACTER_LENGTH) {
|
if (error) *error = ZXNotFoundErrorInstance();
|
return nil;
|
}
|
|
if (!hints.returnCodaBarStartEnd) {
|
[self.decodeRowResult deleteCharactersInRange:NSMakeRange(self.decodeRowResult.length - 1, 1)];
|
[self.decodeRowResult deleteCharactersInRange:NSMakeRange(0, 1)];
|
}
|
|
int runningCount = 0;
|
for (int i = 0; i < startOffset; i++) {
|
runningCount += self.counters.array[i];
|
}
|
float left = (float) runningCount;
|
for (int i = startOffset; i < nextStart - 1; i++) {
|
runningCount += self.counters.array[i];
|
}
|
float right = (float) runningCount;
|
return [ZXResult resultWithText:self.decodeRowResult
|
rawBytes:nil
|
resultPoints:@[[[ZXResultPoint alloc] initWithX:left y:(float)rowNumber],
|
[[ZXResultPoint alloc] initWithX:right y:(float)rowNumber]]
|
format:kBarcodeFormatCodabar];
|
}
|
|
- (BOOL)validatePattern:(int)start {
|
// First, sum up the total size of our four categories of stripe sizes;
|
int sizes[4] = {0, 0, 0, 0};
|
int counts[4] = {0, 0, 0, 0};
|
int end = (int)self.decodeRowResult.length - 1;
|
|
// We break out of this loop in the middle, in order to handle
|
// inter-character spaces properly.
|
int pos = start;
|
for (int i = 0; true; i++) {
|
int pattern = ZX_CODA_CHARACTER_ENCODINGS[[self.decodeRowResult characterAtIndex:i]];
|
for (int j = 6; j >= 0; j--) {
|
// Even j = bars, while odd j = spaces. Categories 2 and 3 are for
|
// long stripes, while 0 and 1 are for short stripes.
|
int category = (j & 1) + (pattern & 1) * 2;
|
sizes[category] += self.counters.array[pos + j];
|
counts[category]++;
|
pattern >>= 1;
|
}
|
if (i >= end) {
|
break;
|
}
|
// We ignore the inter-character space - it could be of any size.
|
pos += 8;
|
}
|
|
// Calculate our allowable size thresholds using fixed-point math.
|
float maxes[4] = {0.0f, 0.0f, 0.0f, 0.0f};
|
float mins[4] = {0.0f, 0.0f, 0.0f, 0.0f};
|
// Define the threshold of acceptability to be the midpoint between the
|
// average small stripe and the average large stripe. No stripe lengths
|
// should be on the "wrong" side of that line.
|
for (int i = 0; i < 2; i++) {
|
mins[i] = 0.0f; // Accept arbitrarily small "short" stripes.
|
mins[i + 2] = ((float) sizes[i] / counts[i] + (float) sizes[i + 2] / counts[i + 2]) / 2.0f;
|
maxes[i] = mins[i + 2];
|
maxes[i + 2] = (sizes[i + 2] * ZX_CODA_MAX_ACCEPTABLE + ZX_CODA_PADDING) / counts[i + 2];
|
}
|
|
// Now verify that all of the stripes are within the thresholds.
|
pos = start;
|
for (int i = 0; true; i++) {
|
int pattern = ZX_CODA_CHARACTER_ENCODINGS[[self.decodeRowResult characterAtIndex:i]];
|
for (int j = 6; j >= 0; j--) {
|
// Even j = bars, while odd j = spaces. Categories 2 and 3 are for
|
// long stripes, while 0 and 1 are for short stripes.
|
int category = (j & 1) + (pattern & 1) * 2;
|
int size = self.counters.array[pos + j];
|
if (size < mins[category] || size > maxes[category]) {
|
return NO;
|
}
|
pattern >>= 1;
|
}
|
if (i >= end) {
|
break;
|
}
|
pos += 8;
|
}
|
|
return YES;
|
}
|
|
/**
|
* Records the size of all runs of white and black pixels, starting with white.
|
* This is just like recordPattern, except it records all the counters, and
|
* uses our builtin "counters" member for storage.
|
*/
|
- (BOOL)setCountersWithRow:(ZXBitArray *)row {
|
self.counterLength = 0;
|
// Start from the first white bit.
|
int i = [row nextUnset:0];
|
int end = row.size;
|
if (i >= end) {
|
return NO;
|
}
|
BOOL isWhite = YES;
|
int count = 0;
|
while (i < end) {
|
if ([row get:i] ^ isWhite) { // that is, exactly one is true
|
count++;
|
} else {
|
[self counterAppend:count];
|
count = 1;
|
isWhite = !isWhite;
|
}
|
i++;
|
}
|
[self counterAppend:count];
|
return YES;
|
}
|
|
- (void)counterAppend:(int)e {
|
self.counters.array[self.counterLength] = e;
|
self.counterLength++;
|
if (self.counterLength >= self.counters.length) {
|
ZXIntArray *temp = [[ZXIntArray alloc] initWithLength:self.counterLength * 2];
|
memcpy(temp.array, self.counters.array, self.counters.length * sizeof(int32_t));
|
self.counters = temp;
|
}
|
}
|
|
- (int)findStartPattern {
|
for (int i = 1; i < self.counterLength; i += 2) {
|
int charOffset = [self toNarrowWidePattern:i];
|
if (charOffset != -1 && [[self class] arrayContains:ZX_CODA_STARTEND_ENCODING
|
length:sizeof(ZX_CODA_STARTEND_ENCODING) / sizeof(unichar)
|
key:ZX_CODA_ALPHABET[charOffset]]) {
|
// Look for whitespace before start pattern, >= 50% of width of start pattern
|
// We make an exception if the whitespace is the first element.
|
int patternSize = 0;
|
for (int j = i; j < i + 7; j++) {
|
patternSize += self.counters.array[j];
|
}
|
if (i == 1 || self.counters.array[i-1] >= patternSize / 2) {
|
return i;
|
}
|
}
|
}
|
|
return -1;
|
}
|
|
+ (BOOL)arrayContains:(const unichar *)array length:(unsigned int)length key:(unichar)key {
|
if (array != nil) {
|
for (int i = 0; i < length; i++) {
|
if (array[i] == key) {
|
return YES;
|
}
|
}
|
}
|
return NO;
|
}
|
|
// Assumes that counters[position] is a bar.
|
- (int)toNarrowWidePattern:(int)position {
|
int32_t *array = self.counters.array;
|
int end = position + 7;
|
if (end >= self.counterLength) {
|
return -1;
|
}
|
|
int maxBar = 0;
|
int minBar = INT_MAX;
|
for (int j = position; j < end; j += 2) {
|
int currentCounter = array[j];
|
if (currentCounter < minBar) {
|
minBar = currentCounter;
|
}
|
if (currentCounter > maxBar) {
|
maxBar = currentCounter;
|
}
|
}
|
int thresholdBar = (minBar + maxBar) / 2;
|
|
int maxSpace = 0;
|
int minSpace = INT_MAX;
|
for (int j = position + 1; j < end; j += 2) {
|
int currentCounter = array[j];
|
if (currentCounter < minSpace) {
|
minSpace = currentCounter;
|
}
|
if (currentCounter > maxSpace) {
|
maxSpace = currentCounter;
|
}
|
}
|
int thresholdSpace = (minSpace + maxSpace) / 2;
|
|
int bitmask = 1 << 7;
|
int pattern = 0;
|
for (int i = 0; i < 7; i++) {
|
int threshold = (i & 1) == 0 ? thresholdBar : thresholdSpace;
|
bitmask >>= 1;
|
if (array[position + i] > threshold) {
|
pattern |= bitmask;
|
}
|
}
|
|
for (int i = 0; i < sizeof(ZX_CODA_CHARACTER_ENCODINGS) / sizeof(int); i++) {
|
if (ZX_CODA_CHARACTER_ENCODINGS[i] == pattern) {
|
return i;
|
}
|
}
|
return -1;
|
}
|
|
@end
|