Issue 1737: Add hint to not strip Codabar start/end

git-svn-id: https://zxing.googlecode.com/svn/trunk@2847 59b500cc-1b3d-0410-9834-0bbf25fbcc57
This commit is contained in:
srowen@gmail.com 2013-07-24 08:08:37 +00:00
parent ef96893525
commit 20193fda16
2 changed files with 352 additions and 343 deletions

View file

@ -75,6 +75,13 @@ public enum DecodeHintType {
*/ */
ASSUME_GS1(Void.class), ASSUME_GS1(Void.class),
/**
* If true, return the start and end digits in a Codabar barcode instead of stripping them. They
* are alpha, whereas the rest are numeric. By default, they are stripped, but this causes them
* to not be. Doesn't matter what it maps to; use {@link Boolean#TRUE}.
*/
RETURN_CODABAR_START_END(Void.class),
/** /**
* The caller needs to be notified via callback when a possible {@link ResultPoint} * The caller needs to be notified via callback when a possible {@link ResultPoint}
* is found. Maps to a {@link ResultPointCallback}. * is found. Maps to a {@link ResultPointCallback}.

View file

@ -1,343 +1,345 @@
/* /*
* Copyright 2008 ZXing authors * Copyright 2008 ZXing authors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.google.zxing.oned; package com.google.zxing.oned;
import com.google.zxing.BarcodeFormat; import com.google.zxing.BarcodeFormat;
import com.google.zxing.DecodeHintType; import com.google.zxing.DecodeHintType;
import com.google.zxing.NotFoundException; import com.google.zxing.NotFoundException;
import com.google.zxing.Result; import com.google.zxing.Result;
import com.google.zxing.ResultPoint; import com.google.zxing.ResultPoint;
import com.google.zxing.common.BitArray; import com.google.zxing.common.BitArray;
import java.util.Arrays; import java.util.Arrays;
import java.util.Map; import java.util.Map;
/** /**
* <p>Decodes Codabar barcodes.</p> * <p>Decodes Codabar barcodes.</p>
* *
* @author Bas Vijfwinkel * @author Bas Vijfwinkel
* @author David Walker * @author David Walker
*/ */
public final class CodaBarReader extends OneDReader { public final class CodaBarReader extends OneDReader {
// These values are critical for determining how permissive the decoding // These values are critical for determining how permissive the decoding
// will be. All stripe sizes must be within the window these define, as // will be. All stripe sizes must be within the window these define, as
// compared to the average stripe size. // compared to the average stripe size.
private static final int MAX_ACCEPTABLE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 2.0f); private static final int MAX_ACCEPTABLE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 2.0f);
private static final int PADDING = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 1.5f); private static final int PADDING = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 1.5f);
private static final String ALPHABET_STRING = "0123456789-$:/.+ABCD"; private static final String ALPHABET_STRING = "0123456789-$:/.+ABCD";
static final char[] ALPHABET = ALPHABET_STRING.toCharArray(); static final char[] ALPHABET = ALPHABET_STRING.toCharArray();
/** /**
* These represent the encodings of characters, as patterns of wide and narrow bars. The 7 least-significant bits of * 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. * each int correspond to the pattern of wide and narrow, with 1s representing "wide" and 0s representing narrow.
*/ */
static final int[] CHARACTER_ENCODINGS = { static final int[] CHARACTER_ENCODINGS = {
0x003, 0x006, 0x009, 0x060, 0x012, 0x042, 0x021, 0x024, 0x030, 0x048, // 0-9 0x003, 0x006, 0x009, 0x060, 0x012, 0x042, 0x021, 0x024, 0x030, 0x048, // 0-9
0x00c, 0x018, 0x045, 0x051, 0x054, 0x015, 0x01A, 0x029, 0x00B, 0x00E, // -$:/.+ABCD 0x00c, 0x018, 0x045, 0x051, 0x054, 0x015, 0x01A, 0x029, 0x00B, 0x00E, // -$:/.+ABCD
}; };
// minimal number of characters that should be present (inclusing start and stop characters) // 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 // under normal circumstances this should be set to 3, but can be set higher
// as a last-ditch attempt to reduce false positives. // as a last-ditch attempt to reduce false positives.
private static final int MIN_CHARACTER_LENGTH = 3; private static final int MIN_CHARACTER_LENGTH = 3;
// official start and end patterns // official start and end patterns
private static final char[] STARTEND_ENCODING = {'A', 'B', 'C', 'D'}; private static final char[] STARTEND_ENCODING = {'A', 'B', 'C', 'D'};
// some codabar generator allow the codabar string to be closed by every // some codabar generator allow the codabar string to be closed by every
// character. This will cause lots of false positives! // character. This will cause lots of false positives!
// some industries use a checksum standard but this is not part of the original codabar standard // 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 // for more information see : http://www.mecsw.com/specs/codabar.html
// Keep some instance variables to avoid reallocations // Keep some instance variables to avoid reallocations
private final StringBuilder decodeRowResult; private final StringBuilder decodeRowResult;
private int[] counters; private int[] counters;
private int counterLength; private int counterLength;
public CodaBarReader() { public CodaBarReader() {
decodeRowResult = new StringBuilder(20); decodeRowResult = new StringBuilder(20);
counters = new int[80]; counters = new int[80];
counterLength = 0; counterLength = 0;
} }
@Override @Override
public Result decodeRow(int rowNumber, BitArray row, Map<DecodeHintType,?> hints) throws NotFoundException { public Result decodeRow(int rowNumber, BitArray row, Map<DecodeHintType,?> hints) throws NotFoundException {
Arrays.fill(counters, 0); Arrays.fill(counters, 0);
setCounters(row); setCounters(row);
int startOffset = findStartPattern(); int startOffset = findStartPattern();
int nextStart = startOffset; int nextStart = startOffset;
decodeRowResult.setLength(0); decodeRowResult.setLength(0);
do { do {
int charOffset = toNarrowWidePattern(nextStart); int charOffset = toNarrowWidePattern(nextStart);
if (charOffset == -1) { if (charOffset == -1) {
throw NotFoundException.getNotFoundInstance(); throw NotFoundException.getNotFoundInstance();
} }
// Hack: We store the position in the alphabet table into a // Hack: We store the position in the alphabet table into a
// StringBuilder, so that we can access the decoded patterns in // StringBuilder, so that we can access the decoded patterns in
// validatePattern. We'll translate to the actual characters later. // validatePattern. We'll translate to the actual characters later.
decodeRowResult.append((char)charOffset); decodeRowResult.append((char)charOffset);
nextStart += 8; nextStart += 8;
// Stop as soon as we see the end character. // Stop as soon as we see the end character.
if (decodeRowResult.length() > 1 && if (decodeRowResult.length() > 1 &&
arrayContains(STARTEND_ENCODING, ALPHABET[charOffset])) { arrayContains(STARTEND_ENCODING, ALPHABET[charOffset])) {
break; break;
} }
} while (nextStart < counterLength); // no fixed end pattern so keep on reading while data is available } while (nextStart < counterLength); // no fixed end pattern so keep on reading while data is available
// Look for whitespace after pattern: // Look for whitespace after pattern:
int trailingWhitespace = counters[nextStart - 1]; int trailingWhitespace = counters[nextStart - 1];
int lastPatternSize = 0; int lastPatternSize = 0;
for (int i = -8; i < -1; i++) { for (int i = -8; i < -1; i++) {
lastPatternSize += counters[nextStart + i]; lastPatternSize += counters[nextStart + i];
} }
// We need to see whitespace equal to 50% of the last pattern size, // 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 // 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.) // at the end of the row. (I.e. the barcode barely fits.)
if (nextStart < counterLength && trailingWhitespace < lastPatternSize / 2) { if (nextStart < counterLength && trailingWhitespace < lastPatternSize / 2) {
throw NotFoundException.getNotFoundInstance(); throw NotFoundException.getNotFoundInstance();
} }
validatePattern(startOffset); validatePattern(startOffset);
// Translate character table offsets to actual characters. // Translate character table offsets to actual characters.
for (int i = 0; i < decodeRowResult.length(); i++) { for (int i = 0; i < decodeRowResult.length(); i++) {
decodeRowResult.setCharAt(i, ALPHABET[decodeRowResult.charAt(i)]); decodeRowResult.setCharAt(i, ALPHABET[decodeRowResult.charAt(i)]);
} }
// Ensure a valid start and end character // Ensure a valid start and end character
char startchar = decodeRowResult.charAt(0); char startchar = decodeRowResult.charAt(0);
if (!arrayContains(STARTEND_ENCODING, startchar)) { if (!arrayContains(STARTEND_ENCODING, startchar)) {
throw NotFoundException.getNotFoundInstance(); throw NotFoundException.getNotFoundInstance();
} }
char endchar = decodeRowResult.charAt(decodeRowResult.length() - 1); char endchar = decodeRowResult.charAt(decodeRowResult.length() - 1);
if (!arrayContains(STARTEND_ENCODING, endchar)) { if (!arrayContains(STARTEND_ENCODING, endchar)) {
throw NotFoundException.getNotFoundInstance(); throw NotFoundException.getNotFoundInstance();
} }
// remove stop/start characters character and check if a long enough string is contained // remove stop/start characters character and check if a long enough string is contained
if (decodeRowResult.length() <= MIN_CHARACTER_LENGTH) { if (decodeRowResult.length() <= MIN_CHARACTER_LENGTH) {
// Almost surely a false positive ( start + stop + at least 1 character) // Almost surely a false positive ( start + stop + at least 1 character)
throw NotFoundException.getNotFoundInstance(); throw NotFoundException.getNotFoundInstance();
} }
decodeRowResult.deleteCharAt(decodeRowResult.length() - 1); if (hints == null || !hints.containsKey(DecodeHintType.RETURN_CODABAR_START_END)) {
decodeRowResult.deleteCharAt(0); decodeRowResult.deleteCharAt(decodeRowResult.length() - 1);
decodeRowResult.deleteCharAt(0);
int runningCount = 0; }
for (int i = 0; i < startOffset; i++) {
runningCount += counters[i]; int runningCount = 0;
} for (int i = 0; i < startOffset; i++) {
float left = (float) runningCount; runningCount += counters[i];
for (int i = startOffset; i < nextStart - 1; i++) { }
runningCount += counters[i]; float left = (float) runningCount;
} for (int i = startOffset; i < nextStart - 1; i++) {
float right = (float) runningCount; runningCount += counters[i];
return new Result( }
decodeRowResult.toString(), float right = (float) runningCount;
null, return new Result(
new ResultPoint[]{ decodeRowResult.toString(),
new ResultPoint(left, (float) rowNumber), null,
new ResultPoint(right, (float) rowNumber)}, new ResultPoint[]{
BarcodeFormat.CODABAR); new ResultPoint(left, (float) rowNumber),
} new ResultPoint(right, (float) rowNumber)},
BarcodeFormat.CODABAR);
void validatePattern(int start) throws NotFoundException { }
// First, sum up the total size of our four categories of stripe sizes;
int[] sizes = {0, 0, 0, 0}; void validatePattern(int start) throws NotFoundException {
int[] counts = {0, 0, 0, 0}; // First, sum up the total size of our four categories of stripe sizes;
int end = decodeRowResult.length() - 1; int[] sizes = {0, 0, 0, 0};
int[] counts = {0, 0, 0, 0};
// We break out of this loop in the middle, in order to handle int end = decodeRowResult.length() - 1;
// inter-character spaces properly.
int pos = start; // We break out of this loop in the middle, in order to handle
for (int i = 0; true; i++) { // inter-character spaces properly.
int pattern = CHARACTER_ENCODINGS[decodeRowResult.charAt(i)]; int pos = start;
for (int j = 6; j >= 0; j--) { for (int i = 0; true; i++) {
// Even j = bars, while odd j = spaces. Categories 2 and 3 are for int pattern = CHARACTER_ENCODINGS[decodeRowResult.charAt(i)];
// long stripes, while 0 and 1 are for short stripes. for (int j = 6; j >= 0; j--) {
int category = (j & 1) + (pattern & 1) * 2; // Even j = bars, while odd j = spaces. Categories 2 and 3 are for
sizes[category] += counters[pos + j]; // long stripes, while 0 and 1 are for short stripes.
counts[category]++; int category = (j & 1) + (pattern & 1) * 2;
pattern >>= 1; sizes[category] += counters[pos + j];
} counts[category]++;
if (i >= end) { pattern >>= 1;
break; }
} if (i >= end) {
// We ignore the inter-character space - it could be of any size. break;
pos += 8; }
} // We ignore the inter-character space - it could be of any size.
pos += 8;
// Calculate our allowable size thresholds using fixed-point math. }
int[] maxes = new int[4];
int[] mins = new int[4]; // Calculate our allowable size thresholds using fixed-point math.
// Define the threshold of acceptability to be the midpoint between the int[] maxes = new int[4];
// average small stripe and the average large stripe. No stripe lengths int[] mins = new int[4];
// should be on the "wrong" side of that line. // Define the threshold of acceptability to be the midpoint between the
for (int i = 0; i < 2; i++) { // average small stripe and the average large stripe. No stripe lengths
mins[i] = 0; // Accept arbitrarily small "short" stripes. // should be on the "wrong" side of that line.
mins[i + 2] = ((sizes[i] << INTEGER_MATH_SHIFT) / counts[i] + for (int i = 0; i < 2; i++) {
(sizes[i + 2] << INTEGER_MATH_SHIFT) / counts[i + 2]) >> 1; mins[i] = 0; // Accept arbitrarily small "short" stripes.
maxes[i] = mins[i + 2]; mins[i + 2] = ((sizes[i] << INTEGER_MATH_SHIFT) / counts[i] +
maxes[i + 2] = (sizes[i + 2] * MAX_ACCEPTABLE + PADDING) / counts[i + 2]; (sizes[i + 2] << INTEGER_MATH_SHIFT) / counts[i + 2]) >> 1;
} maxes[i] = mins[i + 2];
maxes[i + 2] = (sizes[i + 2] * MAX_ACCEPTABLE + PADDING) / counts[i + 2];
// Now verify that all of the stripes are within the thresholds. }
pos = start;
for (int i = 0; true; i++) { // Now verify that all of the stripes are within the thresholds.
int pattern = CHARACTER_ENCODINGS[decodeRowResult.charAt(i)]; pos = start;
for (int j = 6; j >= 0; j--) { for (int i = 0; true; i++) {
// Even j = bars, while odd j = spaces. Categories 2 and 3 are for int pattern = CHARACTER_ENCODINGS[decodeRowResult.charAt(i)];
// long stripes, while 0 and 1 are for short stripes. for (int j = 6; j >= 0; j--) {
int category = (j & 1) + (pattern & 1) * 2; // Even j = bars, while odd j = spaces. Categories 2 and 3 are for
int size = counters[pos + j] << INTEGER_MATH_SHIFT; // long stripes, while 0 and 1 are for short stripes.
if (size < mins[category] || size > maxes[category]) { int category = (j & 1) + (pattern & 1) * 2;
throw NotFoundException.getNotFoundInstance(); int size = counters[pos + j] << INTEGER_MATH_SHIFT;
} if (size < mins[category] || size > maxes[category]) {
pattern >>= 1; throw NotFoundException.getNotFoundInstance();
} }
if (i >= end) { pattern >>= 1;
break; }
} if (i >= end) {
pos += 8; break;
} }
} pos += 8;
}
/** }
* 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. * Records the size of all runs of white and black pixels, starting with white.
* @param row row to count from * This is just like recordPattern, except it records all the counters, and
*/ * uses our builtin "counters" member for storage.
private void setCounters(BitArray row) throws NotFoundException { * @param row row to count from
counterLength = 0; */
// Start from the first white bit. private void setCounters(BitArray row) throws NotFoundException {
int i = row.getNextUnset(0); counterLength = 0;
int end = row.getSize(); // Start from the first white bit.
if (i >= end) { int i = row.getNextUnset(0);
throw NotFoundException.getNotFoundInstance(); int end = row.getSize();
} if (i >= end) {
boolean isWhite = true; throw NotFoundException.getNotFoundInstance();
int count = 0; }
for (; i < end; i++) { boolean isWhite = true;
if (row.get(i) ^ isWhite) { // that is, exactly one is true int count = 0;
count++; for (; i < end; i++) {
} else { if (row.get(i) ^ isWhite) { // that is, exactly one is true
counterAppend(count); count++;
count = 1; } else {
isWhite = !isWhite; counterAppend(count);
} count = 1;
} isWhite = !isWhite;
counterAppend(count); }
} }
counterAppend(count);
private void counterAppend(int e) { }
counters[counterLength] = e;
counterLength++; private void counterAppend(int e) {
if (counterLength >= counters.length) { counters[counterLength] = e;
int[] temp = new int[counterLength * 2]; counterLength++;
System.arraycopy(counters, 0, temp, 0, counterLength); if (counterLength >= counters.length) {
counters = temp; int[] temp = new int[counterLength * 2];
} System.arraycopy(counters, 0, temp, 0, counterLength);
} counters = temp;
}
private int findStartPattern() throws NotFoundException { }
for (int i = 1; i < counterLength; i += 2) {
int charOffset = toNarrowWidePattern(i); private int findStartPattern() throws NotFoundException {
if (charOffset != -1 && arrayContains(STARTEND_ENCODING, ALPHABET[charOffset])) { for (int i = 1; i < counterLength; i += 2) {
// Look for whitespace before start pattern, >= 50% of width of start pattern int charOffset = toNarrowWidePattern(i);
// We make an exception if the whitespace is the first element. if (charOffset != -1 && arrayContains(STARTEND_ENCODING, ALPHABET[charOffset])) {
int patternSize = 0; // Look for whitespace before start pattern, >= 50% of width of start pattern
for (int j = i; j < i + 7; j++) { // We make an exception if the whitespace is the first element.
patternSize += counters[j]; int patternSize = 0;
} for (int j = i; j < i + 7; j++) {
if (i == 1 || counters[i-1] >= patternSize / 2) { patternSize += counters[j];
return i; }
} if (i == 1 || counters[i-1] >= patternSize / 2) {
} return i;
} }
throw NotFoundException.getNotFoundInstance(); }
} }
throw NotFoundException.getNotFoundInstance();
static boolean arrayContains(char[] array, char key) { }
if (array != null) {
for (char c : array) { static boolean arrayContains(char[] array, char key) {
if (c == key) { if (array != null) {
return true; for (char c : array) {
} if (c == key) {
} return true;
} }
return false; }
} }
return false;
// Assumes that counters[position] is a bar. }
private int toNarrowWidePattern(int position) {
int end = position + 7; // Assumes that counters[position] is a bar.
if (end >= counterLength) { private int toNarrowWidePattern(int position) {
return -1; int end = position + 7;
} if (end >= counterLength) {
return -1;
int[] theCounters = counters; }
int maxBar = 0; int[] theCounters = counters;
int minBar = Integer.MAX_VALUE;
for (int j = position; j < end; j += 2) { int maxBar = 0;
int currentCounter = theCounters[j]; int minBar = Integer.MAX_VALUE;
if (currentCounter < minBar) { for (int j = position; j < end; j += 2) {
minBar = currentCounter; int currentCounter = theCounters[j];
} if (currentCounter < minBar) {
if (currentCounter > maxBar) { minBar = currentCounter;
maxBar = currentCounter; }
} if (currentCounter > maxBar) {
} maxBar = currentCounter;
int thresholdBar = (minBar + maxBar) / 2; }
}
int maxSpace = 0; int thresholdBar = (minBar + maxBar) / 2;
int minSpace = Integer.MAX_VALUE;
for (int j = position + 1; j < end; j += 2) { int maxSpace = 0;
int currentCounter = theCounters[j]; int minSpace = Integer.MAX_VALUE;
if (currentCounter < minSpace) { for (int j = position + 1; j < end; j += 2) {
minSpace = currentCounter; int currentCounter = theCounters[j];
} if (currentCounter < minSpace) {
if (currentCounter > maxSpace) { minSpace = currentCounter;
maxSpace = currentCounter; }
} if (currentCounter > maxSpace) {
} maxSpace = currentCounter;
int thresholdSpace = (minSpace + maxSpace) / 2; }
}
int bitmask = 1 << 7; int thresholdSpace = (minSpace + maxSpace) / 2;
int pattern = 0;
for (int i = 0; i < 7; i++) { int bitmask = 1 << 7;
int threshold = (i & 1) == 0 ? thresholdBar : thresholdSpace; int pattern = 0;
bitmask >>= 1; for (int i = 0; i < 7; i++) {
if (theCounters[position + i] > threshold) { int threshold = (i & 1) == 0 ? thresholdBar : thresholdSpace;
pattern |= bitmask; bitmask >>= 1;
} if (theCounters[position + i] > threshold) {
} pattern |= bitmask;
}
for (int i = 0; i < CHARACTER_ENCODINGS.length; i++) { }
if (CHARACTER_ENCODINGS[i] == pattern) {
return i; for (int i = 0; i < CHARACTER_ENCODINGS.length; i++) {
} if (CHARACTER_ENCODINGS[i] == pattern) {
} return i;
return -1; }
} }
return -1;
} }
}