diff --git a/core/src/com/google/zxing/DecodeHintType.java b/core/src/com/google/zxing/DecodeHintType.java index a70453dfc..54a1c8086 100644 --- a/core/src/com/google/zxing/DecodeHintType.java +++ b/core/src/com/google/zxing/DecodeHintType.java @@ -75,6 +75,13 @@ public enum DecodeHintType { */ 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} * is found. Maps to a {@link ResultPointCallback}. diff --git a/core/src/com/google/zxing/oned/CodaBarReader.java b/core/src/com/google/zxing/oned/CodaBarReader.java index c81d18c8b..f01e4be88 100644 --- a/core/src/com/google/zxing/oned/CodaBarReader.java +++ b/core/src/com/google/zxing/oned/CodaBarReader.java @@ -1,343 +1,345 @@ -/* - * Copyright 2008 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. - */ - -package com.google.zxing.oned; - -import com.google.zxing.BarcodeFormat; -import com.google.zxing.DecodeHintType; -import com.google.zxing.NotFoundException; -import com.google.zxing.Result; -import com.google.zxing.ResultPoint; -import com.google.zxing.common.BitArray; - -import java.util.Arrays; -import java.util.Map; - -/** - *

Decodes Codabar barcodes.

- * - * @author Bas Vijfwinkel - * @author David Walker - */ -public final class CodaBarReader extends OneDReader { - - // 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. - 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 String ALPHABET_STRING = "0123456789-$:/.+ABCD"; - 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 - * each int correspond to the pattern of wide and narrow, with 1s representing "wide" and 0s representing narrow. - */ - static final int[] 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. - private static final int MIN_CHARACTER_LENGTH = 3; - - // official start and end patterns - private static final char[] 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 - - // Keep some instance variables to avoid reallocations - private final StringBuilder decodeRowResult; - private int[] counters; - private int counterLength; - - public CodaBarReader() { - decodeRowResult = new StringBuilder(20); - counters = new int[80]; - counterLength = 0; - } - - @Override - public Result decodeRow(int rowNumber, BitArray row, Map hints) throws NotFoundException { - - Arrays.fill(counters, 0); - setCounters(row); - int startOffset = findStartPattern(); - int nextStart = startOffset; - - decodeRowResult.setLength(0); - do { - int charOffset = toNarrowWidePattern(nextStart); - if (charOffset == -1) { - throw NotFoundException.getNotFoundInstance(); - } - // Hack: We store the position in the alphabet table into a - // StringBuilder, so that we can access the decoded patterns in - // validatePattern. We'll translate to the actual characters later. - decodeRowResult.append((char)charOffset); - nextStart += 8; - // Stop as soon as we see the end character. - if (decodeRowResult.length() > 1 && - arrayContains(STARTEND_ENCODING, ALPHABET[charOffset])) { - break; - } - } while (nextStart < counterLength); // no fixed end pattern so keep on reading while data is available - - // Look for whitespace after pattern: - int trailingWhitespace = counters[nextStart - 1]; - int lastPatternSize = 0; - for (int i = -8; i < -1; i++) { - lastPatternSize += counters[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 < counterLength && trailingWhitespace < lastPatternSize / 2) { - throw NotFoundException.getNotFoundInstance(); - } - - validatePattern(startOffset); - - // Translate character table offsets to actual characters. - for (int i = 0; i < decodeRowResult.length(); i++) { - decodeRowResult.setCharAt(i, ALPHABET[decodeRowResult.charAt(i)]); - } - // Ensure a valid start and end character - char startchar = decodeRowResult.charAt(0); - if (!arrayContains(STARTEND_ENCODING, startchar)) { - throw NotFoundException.getNotFoundInstance(); - } - char endchar = decodeRowResult.charAt(decodeRowResult.length() - 1); - if (!arrayContains(STARTEND_ENCODING, endchar)) { - throw NotFoundException.getNotFoundInstance(); - } - - // remove stop/start characters character and check if a long enough string is contained - if (decodeRowResult.length() <= MIN_CHARACTER_LENGTH) { - // Almost surely a false positive ( start + stop + at least 1 character) - throw NotFoundException.getNotFoundInstance(); - } - - decodeRowResult.deleteCharAt(decodeRowResult.length() - 1); - decodeRowResult.deleteCharAt(0); - - int runningCount = 0; - for (int i = 0; i < startOffset; i++) { - runningCount += counters[i]; - } - float left = (float) runningCount; - for (int i = startOffset; i < nextStart - 1; i++) { - runningCount += counters[i]; - } - float right = (float) runningCount; - return new Result( - decodeRowResult.toString(), - null, - new ResultPoint[]{ - 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}; - int[] counts = {0, 0, 0, 0}; - int end = 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 = CHARACTER_ENCODINGS[decodeRowResult.charAt(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] += counters[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. - int[] maxes = new int[4]; - int[] mins = new int[4]; - // 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; // Accept arbitrarily small "short" stripes. - mins[i + 2] = ((sizes[i] << INTEGER_MATH_SHIFT) / counts[i] + - (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++) { - int pattern = CHARACTER_ENCODINGS[decodeRowResult.charAt(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 = counters[pos + j] << INTEGER_MATH_SHIFT; - if (size < mins[category] || size > maxes[category]) { - throw NotFoundException.getNotFoundInstance(); - } - pattern >>= 1; - } - if (i >= end) { - 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. - * @param row row to count from - */ - private void setCounters(BitArray row) throws NotFoundException { - counterLength = 0; - // Start from the first white bit. - int i = row.getNextUnset(0); - int end = row.getSize(); - if (i >= end) { - throw NotFoundException.getNotFoundInstance(); - } - boolean isWhite = true; - int count = 0; - for (; i < end; i++) { - if (row.get(i) ^ isWhite) { // that is, exactly one is true - count++; - } else { - counterAppend(count); - count = 1; - isWhite = !isWhite; - } - } - counterAppend(count); - } - - private void counterAppend(int e) { - counters[counterLength] = e; - counterLength++; - if (counterLength >= counters.length) { - 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); - if (charOffset != -1 && arrayContains(STARTEND_ENCODING, 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 += counters[j]; - } - if (i == 1 || counters[i-1] >= patternSize / 2) { - return i; - } - } - } - throw NotFoundException.getNotFoundInstance(); - } - - static boolean arrayContains(char[] array, char key) { - if (array != null) { - for (char c : array) { - if (c == key) { - return true; - } - } - } - return false; - } - - // Assumes that counters[position] is a bar. - private int toNarrowWidePattern(int position) { - int end = position + 7; - if (end >= counterLength) { - return -1; - } - - int[] theCounters = counters; - - int maxBar = 0; - int minBar = Integer.MAX_VALUE; - for (int j = position; j < end; j += 2) { - int currentCounter = theCounters[j]; - if (currentCounter < minBar) { - minBar = currentCounter; - } - if (currentCounter > maxBar) { - maxBar = currentCounter; - } - } - int thresholdBar = (minBar + maxBar) / 2; - - int maxSpace = 0; - int minSpace = Integer.MAX_VALUE; - for (int j = position + 1; j < end; j += 2) { - int currentCounter = theCounters[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 (theCounters[position + i] > threshold) { - pattern |= bitmask; - } - } - - for (int i = 0; i < CHARACTER_ENCODINGS.length; i++) { - if (CHARACTER_ENCODINGS[i] == pattern) { - return i; - } - } - return -1; - } - -} +/* + * Copyright 2008 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. + */ + +package com.google.zxing.oned; + +import com.google.zxing.BarcodeFormat; +import com.google.zxing.DecodeHintType; +import com.google.zxing.NotFoundException; +import com.google.zxing.Result; +import com.google.zxing.ResultPoint; +import com.google.zxing.common.BitArray; + +import java.util.Arrays; +import java.util.Map; + +/** + *

Decodes Codabar barcodes.

+ * + * @author Bas Vijfwinkel + * @author David Walker + */ +public final class CodaBarReader extends OneDReader { + + // 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. + 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 String ALPHABET_STRING = "0123456789-$:/.+ABCD"; + 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 + * each int correspond to the pattern of wide and narrow, with 1s representing "wide" and 0s representing narrow. + */ + static final int[] 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. + private static final int MIN_CHARACTER_LENGTH = 3; + + // official start and end patterns + private static final char[] 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 + + // Keep some instance variables to avoid reallocations + private final StringBuilder decodeRowResult; + private int[] counters; + private int counterLength; + + public CodaBarReader() { + decodeRowResult = new StringBuilder(20); + counters = new int[80]; + counterLength = 0; + } + + @Override + public Result decodeRow(int rowNumber, BitArray row, Map hints) throws NotFoundException { + + Arrays.fill(counters, 0); + setCounters(row); + int startOffset = findStartPattern(); + int nextStart = startOffset; + + decodeRowResult.setLength(0); + do { + int charOffset = toNarrowWidePattern(nextStart); + if (charOffset == -1) { + throw NotFoundException.getNotFoundInstance(); + } + // Hack: We store the position in the alphabet table into a + // StringBuilder, so that we can access the decoded patterns in + // validatePattern. We'll translate to the actual characters later. + decodeRowResult.append((char)charOffset); + nextStart += 8; + // Stop as soon as we see the end character. + if (decodeRowResult.length() > 1 && + arrayContains(STARTEND_ENCODING, ALPHABET[charOffset])) { + break; + } + } while (nextStart < counterLength); // no fixed end pattern so keep on reading while data is available + + // Look for whitespace after pattern: + int trailingWhitespace = counters[nextStart - 1]; + int lastPatternSize = 0; + for (int i = -8; i < -1; i++) { + lastPatternSize += counters[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 < counterLength && trailingWhitespace < lastPatternSize / 2) { + throw NotFoundException.getNotFoundInstance(); + } + + validatePattern(startOffset); + + // Translate character table offsets to actual characters. + for (int i = 0; i < decodeRowResult.length(); i++) { + decodeRowResult.setCharAt(i, ALPHABET[decodeRowResult.charAt(i)]); + } + // Ensure a valid start and end character + char startchar = decodeRowResult.charAt(0); + if (!arrayContains(STARTEND_ENCODING, startchar)) { + throw NotFoundException.getNotFoundInstance(); + } + char endchar = decodeRowResult.charAt(decodeRowResult.length() - 1); + if (!arrayContains(STARTEND_ENCODING, endchar)) { + throw NotFoundException.getNotFoundInstance(); + } + + // remove stop/start characters character and check if a long enough string is contained + if (decodeRowResult.length() <= MIN_CHARACTER_LENGTH) { + // Almost surely a false positive ( start + stop + at least 1 character) + throw NotFoundException.getNotFoundInstance(); + } + + if (hints == null || !hints.containsKey(DecodeHintType.RETURN_CODABAR_START_END)) { + decodeRowResult.deleteCharAt(decodeRowResult.length() - 1); + decodeRowResult.deleteCharAt(0); + } + + int runningCount = 0; + for (int i = 0; i < startOffset; i++) { + runningCount += counters[i]; + } + float left = (float) runningCount; + for (int i = startOffset; i < nextStart - 1; i++) { + runningCount += counters[i]; + } + float right = (float) runningCount; + return new Result( + decodeRowResult.toString(), + null, + new ResultPoint[]{ + 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}; + int[] counts = {0, 0, 0, 0}; + int end = 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 = CHARACTER_ENCODINGS[decodeRowResult.charAt(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] += counters[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. + int[] maxes = new int[4]; + int[] mins = new int[4]; + // 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; // Accept arbitrarily small "short" stripes. + mins[i + 2] = ((sizes[i] << INTEGER_MATH_SHIFT) / counts[i] + + (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++) { + int pattern = CHARACTER_ENCODINGS[decodeRowResult.charAt(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 = counters[pos + j] << INTEGER_MATH_SHIFT; + if (size < mins[category] || size > maxes[category]) { + throw NotFoundException.getNotFoundInstance(); + } + pattern >>= 1; + } + if (i >= end) { + 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. + * @param row row to count from + */ + private void setCounters(BitArray row) throws NotFoundException { + counterLength = 0; + // Start from the first white bit. + int i = row.getNextUnset(0); + int end = row.getSize(); + if (i >= end) { + throw NotFoundException.getNotFoundInstance(); + } + boolean isWhite = true; + int count = 0; + for (; i < end; i++) { + if (row.get(i) ^ isWhite) { // that is, exactly one is true + count++; + } else { + counterAppend(count); + count = 1; + isWhite = !isWhite; + } + } + counterAppend(count); + } + + private void counterAppend(int e) { + counters[counterLength] = e; + counterLength++; + if (counterLength >= counters.length) { + 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); + if (charOffset != -1 && arrayContains(STARTEND_ENCODING, 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 += counters[j]; + } + if (i == 1 || counters[i-1] >= patternSize / 2) { + return i; + } + } + } + throw NotFoundException.getNotFoundInstance(); + } + + static boolean arrayContains(char[] array, char key) { + if (array != null) { + for (char c : array) { + if (c == key) { + return true; + } + } + } + return false; + } + + // Assumes that counters[position] is a bar. + private int toNarrowWidePattern(int position) { + int end = position + 7; + if (end >= counterLength) { + return -1; + } + + int[] theCounters = counters; + + int maxBar = 0; + int minBar = Integer.MAX_VALUE; + for (int j = position; j < end; j += 2) { + int currentCounter = theCounters[j]; + if (currentCounter < minBar) { + minBar = currentCounter; + } + if (currentCounter > maxBar) { + maxBar = currentCounter; + } + } + int thresholdBar = (minBar + maxBar) / 2; + + int maxSpace = 0; + int minSpace = Integer.MAX_VALUE; + for (int j = position + 1; j < end; j += 2) { + int currentCounter = theCounters[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 (theCounters[position + i] > threshold) { + pattern |= bitmask; + } + } + + for (int i = 0; i < CHARACTER_ENCODINGS.length; i++) { + if (CHARACTER_ENCODINGS[i] == pattern) { + return i; + } + } + return -1; + } + +}