Removed three interfaces which weren't doing any good and were making the 1D class hierarchy even more complicated than it already is.

git-svn-id: https://zxing.googlecode.com/svn/trunk@1138 59b500cc-1b3d-0410-9834-0bbf25fbcc57
This commit is contained in:
dswitkin 2009-12-08 02:15:35 +00:00
parent fe87c689bc
commit bdc25cf5bc
17 changed files with 664 additions and 752 deletions

View file

@ -1,253 +0,0 @@
/*
* 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.BinaryBitmap;
import com.google.zxing.DecodeHintType;
import com.google.zxing.ReaderException;
import com.google.zxing.Result;
import com.google.zxing.ResultMetadataType;
import com.google.zxing.ResultPoint;
import com.google.zxing.common.BitArray;
import java.util.Hashtable;
/**
* <p>Encapsulates functionality and implementation that is common to all families
* of one-dimensional barcodes.</p>
*
* @author dswitkin@google.com (Daniel Switkin)
* @author Sean Owen
*/
public abstract class AbstractOneDReader implements OneDReader {
private static final int INTEGER_MATH_SHIFT = 8;
static final int PATTERN_MATCH_RESULT_SCALE_FACTOR = 1 << INTEGER_MATH_SHIFT;
public final Result decode(BinaryBitmap image) throws ReaderException {
return decode(image, null);
}
// Note that we don't try rotation without the try harder flag, even if rotation was supported.
public final Result decode(BinaryBitmap image, Hashtable hints) throws ReaderException {
try {
return doDecode(image, hints);
} catch (ReaderException re) {
boolean tryHarder = hints != null && hints.containsKey(DecodeHintType.TRY_HARDER);
if (tryHarder && image.isRotateSupported()) {
BinaryBitmap rotatedImage = image.rotateCounterClockwise();
Result result = doDecode(rotatedImage, hints);
// Record that we found it rotated 90 degrees CCW / 270 degrees CW
Hashtable metadata = result.getResultMetadata();
int orientation = 270;
if (metadata != null && metadata.containsKey(ResultMetadataType.ORIENTATION)) {
// But if we found it reversed in doDecode(), add in that result here:
orientation = (orientation +
((Integer) metadata.get(ResultMetadataType.ORIENTATION)).intValue()) % 360;
}
result.putMetadata(ResultMetadataType.ORIENTATION, new Integer(orientation));
return result;
} else {
throw re;
}
}
}
/**
* We're going to examine rows from the middle outward, searching alternately above and below the
* middle, and farther out each time. rowStep is the number of rows between each successive
* attempt above and below the middle. So we'd scan row middle, then middle - rowStep, then
* middle + rowStep, then middle - (2 * rowStep), etc.
* rowStep is bigger as the image is taller, but is always at least 1. We've somewhat arbitrarily
* decided that moving up and down by about 1/16 of the image is pretty good; we try more of the
* image if "trying harder".
*
* @param image The image to decode
* @param hints Any hints that were requested
* @return The contents of the decoded barcode
* @throws ReaderException Any spontaneous errors which occur
*/
private Result doDecode(BinaryBitmap image, Hashtable hints) throws ReaderException {
int width = image.getWidth();
int height = image.getHeight();
BitArray row = new BitArray(width);
int middle = height >> 1;
boolean tryHarder = hints != null && hints.containsKey(DecodeHintType.TRY_HARDER);
int rowStep = Math.max(1, height >> (tryHarder ? 7 : 4));
int maxLines;
if (tryHarder) {
maxLines = height; // Look at the whole image, not just the center
} else {
maxLines = 9; // Nine rows spaced 1/16 apart is roughly the middle half of the image
}
for (int x = 0; x < maxLines; x++) {
// Scanning from the middle out. Determine which row we're looking at next:
int rowStepsAboveOrBelow = (x + 1) >> 1;
boolean isAbove = (x & 0x01) == 0; // i.e. is x even?
int rowNumber = middle + rowStep * (isAbove ? rowStepsAboveOrBelow : -rowStepsAboveOrBelow);
if (rowNumber < 0 || rowNumber >= height) {
// Oops, if we run off the top or bottom, stop
break;
}
// Estimate black point for this row and load it:
try {
row = image.getBlackRow(rowNumber, row);
} catch (ReaderException re) {
continue;
}
// While we have the image data in a BitArray, it's fairly cheap to reverse it in place to
// handle decoding upside down barcodes.
for (int attempt = 0; attempt < 2; attempt++) {
if (attempt == 1) { // trying again?
row.reverse(); // reverse the row and continue
// This means we will only ever draw result points *once* in the life of this method
// since we want to avoid drawing the wrong points after flipping the row, and,
// don't want to clutter with noise from every single row scan -- just the scans
// that start on the center line.
if (hints != null && hints.containsKey(DecodeHintType.NEED_RESULT_POINT_CALLBACK)) {
hints = (Hashtable) hints.clone();
hints.remove(DecodeHintType.NEED_RESULT_POINT_CALLBACK);
}
}
try {
// Look for a barcode
Result result = decodeRow(rowNumber, row, hints);
// We found our barcode
if (attempt == 1) {
// But it was upside down, so note that
result.putMetadata(ResultMetadataType.ORIENTATION, new Integer(180));
// And remember to flip the result points horizontally.
ResultPoint[] points = result.getResultPoints();
points[0] = new ResultPoint(width - points[0].getX() - 1, points[0].getY());
points[1] = new ResultPoint(width - points[1].getX() - 1, points[1].getY());
}
return result;
} catch (ReaderException re) {
// continue -- just couldn't decode this row
}
}
}
throw ReaderException.getInstance();
}
/**
* Records the size of successive runs of white and black pixels in a row, starting at a given point.
* The values are recorded in the given array, and the number of runs recorded is equal to the size
* of the array. If the row starts on a white pixel at the given start point, then the first count
* recorded is the run of white pixels starting from that point; likewise it is the count of a run
* of black pixels if the row begin on a black pixels at that point.
*
* @param row row to count from
* @param start offset into row to start at
* @param counters array into which to record counts
* @throws ReaderException if counters cannot be filled entirely from row before running out
* of pixels
*/
static void recordPattern(BitArray row, int start, int[] counters) throws ReaderException {
int numCounters = counters.length;
for (int i = 0; i < numCounters; i++) {
counters[i] = 0;
}
int end = row.getSize();
if (start >= end) {
throw ReaderException.getInstance();
}
boolean isWhite = !row.get(start);
int counterPosition = 0;
int i = start;
while (i < end) {
boolean pixel = row.get(i);
if (pixel ^ isWhite) { // that is, exactly one is true
counters[counterPosition]++;
} else {
counterPosition++;
if (counterPosition == numCounters) {
break;
} else {
counters[counterPosition] = 1;
isWhite = !isWhite;
}
}
i++;
}
// If we read fully the last section of pixels and filled up our counters -- or filled
// the last counter but ran off the side of the image, OK. Otherwise, a problem.
if (!(counterPosition == numCounters || (counterPosition == numCounters - 1 && i == end))) {
throw ReaderException.getInstance();
}
}
/**
* Determines how closely a set of observed counts of runs of black/white values matches a given
* target pattern. This is reported as the ratio of the total variance from the expected pattern
* proportions across all pattern elements, to the length of the pattern.
*
* @param counters observed counters
* @param pattern expected pattern
* @param maxIndividualVariance The most any counter can differ before we give up
* @return ratio of total variance between counters and pattern compared to total pattern size,
* where the ratio has been multiplied by 256. So, 0 means no variance (perfect match); 256 means
* the total variance between counters and patterns equals the pattern length, higher values mean
* even more variance
*/
static int patternMatchVariance(int[] counters, int[] pattern, int maxIndividualVariance) {
int numCounters = counters.length;
int total = 0;
int patternLength = 0;
for (int i = 0; i < numCounters; i++) {
total += counters[i];
patternLength += pattern[i];
}
if (total < patternLength) {
// If we don't even have one pixel per unit of bar width, assume this is too small
// to reliably match, so fail:
return Integer.MAX_VALUE;
}
// We're going to fake floating-point math in integers. We just need to use more bits.
// Scale up patternLength so that intermediate values below like scaledCounter will have
// more "significant digits"
int unitBarWidth = (total << INTEGER_MATH_SHIFT) / patternLength;
maxIndividualVariance = (maxIndividualVariance * unitBarWidth) >> INTEGER_MATH_SHIFT;
int totalVariance = 0;
for (int x = 0; x < numCounters; x++) {
int counter = counters[x] << INTEGER_MATH_SHIFT;
int scaledPattern = pattern[x] * unitBarWidth;
int variance = counter > scaledPattern ? counter - scaledPattern : scaledPattern - counter;
if (variance > maxIndividualVariance) {
return Integer.MAX_VALUE;
}
totalVariance += variance;
}
return totalVariance / total;
}
// This declaration should not be necessary, since this class is
// abstract and so does not have to provide an implementation for every
// method of an interface it implements, but it is causing NoSuchMethodError
// issues on some Nokia JVMs. So we add this superfluous declaration:
public abstract Result decodeRow(int rowNumber, BitArray row, Hashtable hints)
throws ReaderException;
}

View file

@ -1,319 +0,0 @@
/*
* 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.ReaderException;
import com.google.zxing.Result;
import com.google.zxing.ResultPoint;
import com.google.zxing.ResultPointCallback;
import com.google.zxing.common.BitArray;
import java.util.Hashtable;
/**
* <p>Encapsulates functionality and implementation that is common to UPC and EAN families
* of one-dimensional barcodes.</p>
*
* @author dswitkin@google.com (Daniel Switkin)
* @author Sean Owen
* @author alasdair@google.com (Alasdair Mackintosh)
*/
public abstract class AbstractUPCEANReader extends AbstractOneDReader implements UPCEANReader {
// These two values are critical for determining how permissive the decoding will be.
// We've arrived at these values through a lot of trial and error. Setting them any higher
// lets false positives creep in quickly.
private static final int MAX_AVG_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.42f);
private static final int MAX_INDIVIDUAL_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.7f);
/**
* Start/end guard pattern.
*/
static final int[] START_END_PATTERN = {1, 1, 1,};
/**
* Pattern marking the middle of a UPC/EAN pattern, separating the two halves.
*/
static final int[] MIDDLE_PATTERN = {1, 1, 1, 1, 1};
/**
* "Odd", or "L" patterns used to encode UPC/EAN digits.
*/
static final int[][] L_PATTERNS = {
{3, 2, 1, 1}, // 0
{2, 2, 2, 1}, // 1
{2, 1, 2, 2}, // 2
{1, 4, 1, 1}, // 3
{1, 1, 3, 2}, // 4
{1, 2, 3, 1}, // 5
{1, 1, 1, 4}, // 6
{1, 3, 1, 2}, // 7
{1, 2, 1, 3}, // 8
{3, 1, 1, 2} // 9
};
/**
* As above but also including the "even", or "G" patterns used to encode UPC/EAN digits.
*/
static final int[][] L_AND_G_PATTERNS;
static {
L_AND_G_PATTERNS = new int[20][];
for (int i = 0; i < 10; i++) {
L_AND_G_PATTERNS[i] = L_PATTERNS[i];
}
for (int i = 10; i < 20; i++) {
int[] widths = L_PATTERNS[i - 10];
int[] reversedWidths = new int[widths.length];
for (int j = 0; j < widths.length; j++) {
reversedWidths[j] = widths[widths.length - j - 1];
}
L_AND_G_PATTERNS[i] = reversedWidths;
}
}
private final StringBuffer decodeRowStringBuffer;
protected AbstractUPCEANReader() {
decodeRowStringBuffer = new StringBuffer(20);
}
static int[] findStartGuardPattern(BitArray row) throws ReaderException {
boolean foundStart = false;
int[] startRange = null;
int nextStart = 0;
while (!foundStart) {
startRange = findGuardPattern(row, nextStart, false, START_END_PATTERN);
int start = startRange[0];
nextStart = startRange[1];
// Make sure there is a quiet zone at least as big as the start pattern before the barcode.
// If this check would run off the left edge of the image, do not accept this barcode,
// as it is very likely to be a false positive.
int quietStart = start - (nextStart - start);
if (quietStart >= 0) {
foundStart = row.isRange(quietStart, start, false);
}
}
return startRange;
}
public final Result decodeRow(int rowNumber, BitArray row, Hashtable hints)
throws ReaderException {
return decodeRow(rowNumber, row, findStartGuardPattern(row), hints);
}
public final Result decodeRow(int rowNumber, BitArray row, int[] startGuardRange, Hashtable hints)
throws ReaderException {
ResultPointCallback resultPointCallback = hints == null ? null :
(ResultPointCallback) hints.get(DecodeHintType.NEED_RESULT_POINT_CALLBACK);
if (resultPointCallback != null) {
resultPointCallback.foundPossibleResultPoint(new ResultPoint(
(startGuardRange[0] + startGuardRange[1]) / 2.0f, rowNumber
));
}
StringBuffer result = decodeRowStringBuffer;
result.setLength(0);
int endStart = decodeMiddle(row, startGuardRange, result);
if (resultPointCallback != null) {
resultPointCallback.foundPossibleResultPoint(new ResultPoint(
endStart, rowNumber
));
}
int[] endRange = decodeEnd(row, endStart);
if (resultPointCallback != null) {
resultPointCallback.foundPossibleResultPoint(new ResultPoint(
(endRange[0] + endRange[1]) / 2.0f, rowNumber
));
}
// Make sure there is a quiet zone at least as big as the end pattern after the barcode. The
// spec might want more whitespace, but in practice this is the maximum we can count on.
int end = endRange[1];
int quietEnd = end + (end - endRange[0]);
if (quietEnd >= row.getSize() || !row.isRange(end, quietEnd, false)) {
throw ReaderException.getInstance();
}
String resultString = result.toString();
if (!checkChecksum(resultString)) {
throw ReaderException.getInstance();
}
float left = (float) (startGuardRange[1] + startGuardRange[0]) / 2.0f;
float right = (float) (endRange[1] + endRange[0]) / 2.0f;
return new Result(resultString,
null, // no natural byte representation for these barcodes
new ResultPoint[]{
new ResultPoint(left, (float) rowNumber),
new ResultPoint(right, (float) rowNumber)},
getBarcodeFormat());
}
abstract BarcodeFormat getBarcodeFormat();
/**
* @return {@link #checkStandardUPCEANChecksum(String)}
*/
boolean checkChecksum(String s) throws ReaderException {
return checkStandardUPCEANChecksum(s);
}
/**
* Computes the UPC/EAN checksum on a string of digits, and reports
* whether the checksum is correct or not.
*
* @param s string of digits to check
* @return true iff string of digits passes the UPC/EAN checksum algorithm
* @throws ReaderException if the string does not contain only digits
*/
private static boolean checkStandardUPCEANChecksum(String s) throws ReaderException {
int length = s.length();
if (length == 0) {
return false;
}
int sum = 0;
for (int i = length - 2; i >= 0; i -= 2) {
int digit = (int) s.charAt(i) - (int) '0';
if (digit < 0 || digit > 9) {
throw ReaderException.getInstance();
}
sum += digit;
}
sum *= 3;
for (int i = length - 1; i >= 0; i -= 2) {
int digit = (int) s.charAt(i) - (int) '0';
if (digit < 0 || digit > 9) {
throw ReaderException.getInstance();
}
sum += digit;
}
return sum % 10 == 0;
}
/**
* Subclasses override this to decode the portion of a barcode between the start
* and end guard patterns.
*
* @param row row of black/white values to search
* @param startRange start/end offset of start guard pattern
* @param resultString {@link StringBuffer} to append decoded chars to
* @return horizontal offset of first pixel after the "middle" that was decoded
* @throws ReaderException if decoding could not complete successfully
*/
protected abstract int decodeMiddle(BitArray row, int[] startRange, StringBuffer resultString)
throws ReaderException;
int[] decodeEnd(BitArray row, int endStart) throws ReaderException {
return findGuardPattern(row, endStart, false, START_END_PATTERN);
}
/**
* @param row row of black/white values to search
* @param rowOffset position to start search
* @param whiteFirst if true, indicates that the pattern specifies white/black/white/...
* pixel counts, otherwise, it is interpreted as black/white/black/...
* @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
* @throws ReaderException if pattern is not found
*/
static int[] findGuardPattern(BitArray row, int rowOffset, boolean whiteFirst, int[] pattern)
throws ReaderException {
int patternLength = pattern.length;
int[] counters = new int[patternLength];
int width = row.getSize();
boolean isWhite = false;
while (rowOffset < width) {
isWhite = !row.get(rowOffset);
if (whiteFirst == isWhite) {
break;
}
rowOffset++;
}
int counterPosition = 0;
int patternStart = rowOffset;
for (int x = rowOffset; x < width; x++) {
boolean pixel = row.get(x);
if (pixel ^ isWhite) {
counters[counterPosition]++;
} else {
if (counterPosition == patternLength - 1) {
if (patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE) < MAX_AVG_VARIANCE) {
return new int[]{patternStart, x};
}
patternStart += counters[0] + counters[1];
for (int y = 2; y < patternLength; y++) {
counters[y - 2] = counters[y];
}
counters[patternLength - 2] = 0;
counters[patternLength - 1] = 0;
counterPosition--;
} else {
counterPosition++;
}
counters[counterPosition] = 1;
isWhite = !isWhite;
}
}
throw ReaderException.getInstance();
}
/**
* Attempts to decode a single UPC/EAN-encoded digit.
*
* @param row row of black/white values to decode
* @param counters the counts of runs of observed black/white/black/... values
* @param rowOffset horizontal offset to start decoding from
* @param patterns the set of patterns to use to decode -- sometimes different encodings
* for the digits 0-9 are used, and this indicates the encodings for 0 to 9 that should
* be used
* @return horizontal offset of first pixel beyond the decoded digit
* @throws ReaderException if digit cannot be decoded
*/
static int decodeDigit(BitArray row, int[] counters, int rowOffset, int[][] patterns)
throws ReaderException {
recordPattern(row, rowOffset, counters);
int bestVariance = MAX_AVG_VARIANCE; // worst variance we'll accept
int bestMatch = -1;
int max = patterns.length;
for (int i = 0; i < max; i++) {
int[] pattern = patterns[i];
int variance = patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE);
if (variance < bestVariance) {
bestVariance = variance;
bestMatch = i;
}
}
if (bestMatch >= 0) {
return bestMatch;
} else {
throw ReaderException.getInstance();
}
}
}

View file

@ -1,124 +0,0 @@
/*
* Copyright 2009 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 java.util.Hashtable;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.WriterException;
import com.google.zxing.common.ByteMatrix;
/**
* <p>Encapsulates functionality and implementation that is common to UPC and EAN families
* of one-dimensional barcodes.</p>
*
* @author aripollak@gmail.com (Ari Pollak)
*/
public abstract class AbstractUPCEANWriter implements UPCEANWriter {
public ByteMatrix encode(String contents, BarcodeFormat format, int width, int height)
throws WriterException {
return encode(contents, format, width, height, null);
}
public ByteMatrix encode(String contents, BarcodeFormat format, int width, int height,
Hashtable hints) throws WriterException {
if (contents == null || contents.length() == 0) {
throw new IllegalArgumentException("Found empty contents");
}
if (width < 0 || height < 0) {
throw new IllegalArgumentException("Requested dimensions are too small: "
+ width + 'x' + height);
}
byte[] code = encode(contents);
return renderResult(code, width, height);
}
/** @return a byte array of horizontal pixels (0 = white, 1 = black) */
private static ByteMatrix renderResult(byte[] code, int width, int height) {
int inputWidth = code.length;
// Add quiet zone on both sides
int fullWidth = inputWidth + (AbstractUPCEANReader.START_END_PATTERN.length << 1);
int outputWidth = Math.max(width, fullWidth);
int outputHeight = Math.max(1, height);
int multiple = outputWidth / fullWidth;
int leftPadding = (outputWidth - (inputWidth * multiple)) / 2;
ByteMatrix output = new ByteMatrix(outputWidth, outputHeight);
byte[][] outputArray = output.getArray();
byte[] row = new byte[outputWidth];
// a. Write the white pixels at the left of each row
for (int x = 0; x < leftPadding; x++) {
row[x] = (byte) 255;
}
// b. Write the contents of this row of the barcode
int offset = leftPadding;
for (int x = 0; x < inputWidth; x++) {
byte value = (code[x] == 1) ? 0 : (byte) 255;
for (int z = 0; z < multiple; z++) {
row[offset + z] = value;
}
offset += multiple;
}
// c. Write the white pixels at the right of each row
offset = leftPadding + (inputWidth * multiple);
for (int x = offset; x < outputWidth; x++) {
row[x] = (byte) 255;
}
// d. Write the completed row multiple times
for (int z = 0; z < outputHeight; z++) {
System.arraycopy(row, 0, outputArray[z], 0, outputWidth);
}
return output;
}
/**
* Appends the given pattern to the target array starting at pos.
*
* @param startColor
* starting color - 0 for white, 1 for black
* @return the number of elements added to target.
*/
protected static int appendPattern(byte[] target, int pos, int[] pattern, int startColor) {
if (startColor != 0 && startColor != 1) {
throw new IllegalArgumentException(
"startColor must be either 0 or 1, but got: " + startColor);
}
byte color = (byte) startColor;
int numAdded = 0;
for (int i = 0; i < pattern.length; i++) {
for (int j = 0; j < pattern[i]; j++) {
target[pos] = color;
pos += 1;
numAdded += 1;
}
color ^= 1; // flip color after each segment
}
return numAdded;
}
}

View file

@ -29,7 +29,7 @@ import java.util.Hashtable;
*
* @author Sean Owen
*/
public final class Code128Reader extends AbstractOneDReader {
public final class Code128Reader extends OneDReader {
private static final int[][] CODE_PATTERNS = {
{2, 1, 2, 2, 2, 2}, // 0
@ -194,7 +194,7 @@ public final class Code128Reader extends AbstractOneDReader {
}
}
if (bestMatch >= 0) {
// Look for whitespace before start pattern, >= 50% of width of start pattern
// Look for whitespace before start pattern, >= 50% of width of start pattern
if (row.isRange(Math.max(0, patternStart - (i - patternStart) / 2), patternStart,
false)) {
return new int[]{patternStart, i, bestMatch};

View file

@ -29,7 +29,7 @@ import java.util.Hashtable;
*
* @author Sean Owen
*/
public final class Code39Reader extends AbstractOneDReader {
public final class Code39Reader extends OneDReader {
private static final String ALPHABET_STRING = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. *$/+%";
private static final char[] ALPHABET = ALPHABET_STRING.toCharArray();

View file

@ -16,8 +16,8 @@
package com.google.zxing.oned;
import com.google.zxing.ReaderException;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.ReaderException;
import com.google.zxing.common.BitArray;
/**
@ -27,7 +27,7 @@ import com.google.zxing.common.BitArray;
* @author Sean Owen
* @author alasdair@google.com (Alasdair Mackintosh)
*/
public final class EAN13Reader extends AbstractUPCEANReader {
public final class EAN13Reader extends UPCEANReader {
// For an EAN-13 barcode, the first digit is represented by the parities used
// to encode the next six digits, according to the table below. For example,
@ -132,4 +132,4 @@ public final class EAN13Reader extends AbstractUPCEANReader {
throw ReaderException.getInstance();
}
}
}

View file

@ -16,20 +16,20 @@
package com.google.zxing.oned;
import java.util.Hashtable;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.WriterException;
import com.google.zxing.common.ByteMatrix;
import java.util.Hashtable;
/**
* This object renders an EAN13 code as a ByteMatrix 2D array of greyscale
* values.
*
*
* @author aripollak@gmail.com (Ari Pollak)
*/
public final class EAN13Writer extends AbstractUPCEANWriter {
public final class EAN13Writer extends UPCEANWriter {
private static final int codeWidth = 3 + // start guard
(7 * 6) + // left bars
@ -42,7 +42,7 @@ public final class EAN13Writer extends AbstractUPCEANWriter {
if (format != BarcodeFormat.EAN_13) {
throw new IllegalArgumentException("Can only encode EAN_13, but got " + format);
}
return super.encode(contents, format, width, height, hints);
}
@ -57,7 +57,7 @@ public final class EAN13Writer extends AbstractUPCEANWriter {
byte[] result = new byte[codeWidth];
int pos = 0;
pos += appendPattern(result, pos, AbstractUPCEANReader.START_END_PATTERN, 1);
pos += appendPattern(result, pos, UPCEANReader.START_END_PATTERN, 1);
// See {@link #EAN13Reader} for a description of how the first digit & left bars are encoded
for (int i = 1; i <= 6; i++) {
@ -65,16 +65,16 @@ public final class EAN13Writer extends AbstractUPCEANWriter {
if ((parities >> (6 - i) & 1) == 1) {
digit += 10;
}
pos += appendPattern(result, pos, AbstractUPCEANReader.L_AND_G_PATTERNS[digit], 0);
pos += appendPattern(result, pos, UPCEANReader.L_AND_G_PATTERNS[digit], 0);
}
pos += appendPattern(result, pos, AbstractUPCEANReader.MIDDLE_PATTERN, 0);
pos += appendPattern(result, pos, UPCEANReader.MIDDLE_PATTERN, 0);
for (int i = 7; i <= 12; i++) {
int digit = Integer.parseInt(contents.substring(i, i + 1));
pos += appendPattern(result, pos, AbstractUPCEANReader.L_PATTERNS[digit], 1);
pos += appendPattern(result, pos, UPCEANReader.L_PATTERNS[digit], 1);
}
pos += appendPattern(result, pos, AbstractUPCEANReader.START_END_PATTERN, 1);
pos += appendPattern(result, pos, UPCEANReader.START_END_PATTERN, 1);
return result;
}

View file

@ -16,8 +16,8 @@
package com.google.zxing.oned;
import com.google.zxing.ReaderException;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.ReaderException;
import com.google.zxing.common.BitArray;
/**
@ -25,7 +25,7 @@ import com.google.zxing.common.BitArray;
*
* @author Sean Owen
*/
public final class EAN8Reader extends AbstractUPCEANReader {
public final class EAN8Reader extends UPCEANReader {
private final int[] decodeMiddleCounters;
@ -66,7 +66,7 @@ public final class EAN8Reader extends AbstractUPCEANReader {
}
BarcodeFormat getBarcodeFormat() {
return BarcodeFormat.EAN_8;
return BarcodeFormat.EAN_8;
}
}
}

View file

@ -16,20 +16,19 @@
package com.google.zxing.oned;
import java.util.Hashtable;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.WriterException;
import com.google.zxing.common.ByteMatrix;
import java.util.Hashtable;
/**
* This object renders an EAN8 code as a ByteMatrix 2D array of greyscale
* values.
*
*
* @author aripollak@gmail.com (Ari Pollak)
*/
public final class EAN8Writer extends AbstractUPCEANWriter {
public final class EAN8Writer extends UPCEANWriter {
private static final int codeWidth = 3 + // start guard
(7 * 4) + // left bars
@ -43,7 +42,7 @@ public final class EAN8Writer extends AbstractUPCEANWriter {
throw new IllegalArgumentException("Can only encode EAN_8, but got "
+ format);
}
return super.encode(contents, format, width, height, hints);
}
@ -57,20 +56,20 @@ public final class EAN8Writer extends AbstractUPCEANWriter {
byte[] result = new byte[codeWidth];
int pos = 0;
pos += appendPattern(result, pos, AbstractUPCEANReader.START_END_PATTERN, 1);
pos += appendPattern(result, pos, UPCEANReader.START_END_PATTERN, 1);
for (int i = 0; i <= 3; i++) {
int digit = Integer.parseInt(contents.substring(i, i + 1));
pos += appendPattern(result, pos, AbstractUPCEANReader.L_PATTERNS[digit], 0);
pos += appendPattern(result, pos, UPCEANReader.L_PATTERNS[digit], 0);
}
pos += appendPattern(result, pos, AbstractUPCEANReader.MIDDLE_PATTERN, 0);
pos += appendPattern(result, pos, UPCEANReader.MIDDLE_PATTERN, 0);
for (int i = 4; i <= 7; i++) {
int digit = Integer.parseInt(contents.substring(i, i + 1));
pos += appendPattern(result, pos, AbstractUPCEANReader.L_PATTERNS[digit], 1);
pos += appendPattern(result, pos, UPCEANReader.L_PATTERNS[digit], 1);
}
pos += appendPattern(result, pos, AbstractUPCEANReader.START_END_PATTERN, 1);
pos += appendPattern(result, pos, UPCEANReader.START_END_PATTERN, 1);
return result;
}

View file

@ -17,10 +17,10 @@
package com.google.zxing.oned;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.DecodeHintType;
import com.google.zxing.ReaderException;
import com.google.zxing.Result;
import com.google.zxing.ResultPoint;
import com.google.zxing.DecodeHintType;
import com.google.zxing.common.BitArray;
import java.util.Hashtable;
@ -37,7 +37,7 @@ import java.util.Hashtable;
*
* @author kevin.osullivan@sita.aero, SITA Lab.
*/
public final class ITFReader extends AbstractOneDReader {
public final class ITFReader extends OneDReader {
private static final int MAX_AVG_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.42f);
private static final int MAX_INDIVIDUAL_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.8f);
@ -281,7 +281,7 @@ public final class ITFReader extends AbstractOneDReader {
*/
private static int[] findGuardPattern(BitArray row, int rowOffset, int[] pattern) throws ReaderException {
// TODO: This is very similar to implementation in AbstractUPCEANReader. Consider if they can be
// TODO: This is very similar to implementation in UPCEANReader. Consider if they can be
// merged to a single method.
int patternLength = pattern.length;
int[] counters = new int[patternLength];
@ -344,4 +344,4 @@ public final class ITFReader extends AbstractOneDReader {
}
}
}
}

View file

@ -16,10 +16,10 @@
package com.google.zxing.oned;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.DecodeHintType;
import com.google.zxing.ReaderException;
import com.google.zxing.Result;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.common.BitArray;
import java.util.Hashtable;
@ -29,7 +29,7 @@ import java.util.Vector;
* @author dswitkin@google.com (Daniel Switkin)
* @author Sean Owen
*/
public final class MultiFormatOneDReader extends AbstractOneDReader {
public final class MultiFormatOneDReader extends OneDReader {
private final Vector readers;

View file

@ -32,7 +32,7 @@ import java.util.Vector;
*
* @author Sean Owen
*/
public final class MultiFormatUPCEANReader extends AbstractOneDReader {
public final class MultiFormatUPCEANReader extends OneDReader {
private final Vector readers;
@ -63,7 +63,7 @@ public final class MultiFormatUPCEANReader extends AbstractOneDReader {
public Result decodeRow(int rowNumber, BitArray row, Hashtable hints) throws ReaderException {
// Compute this location once and reuse it on multiple implementations
int[] startGuardPattern = AbstractUPCEANReader.findStartGuardPattern(row);
int[] startGuardPattern = UPCEANReader.findStartGuardPattern(row);
int size = readers.size();
for (int i = 0; i < size; i++) {
UPCEANReader reader = (UPCEANReader) readers.elementAt(i);

View file

@ -16,20 +16,232 @@
package com.google.zxing.oned;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.DecodeHintType;
import com.google.zxing.Reader;
import com.google.zxing.ReaderException;
import com.google.zxing.Result;
import com.google.zxing.ResultMetadataType;
import com.google.zxing.ResultPoint;
import com.google.zxing.common.BitArray;
import java.util.Hashtable;
/**
* <p>{@link Reader}s which also implement this interface read one-dimensional barcode
* formats, and expose additional functionality that is specific to this type of barcode.</p>
* Encapsulates functionality and implementation that is common to all families
* of one-dimensional barcodes.
*
* @author dswitkin@google.com (Daniel Switkin)
* @author Sean Owen
*/
public interface OneDReader extends Reader {
public abstract class OneDReader implements Reader {
private static final int INTEGER_MATH_SHIFT = 8;
static final int PATTERN_MATCH_RESULT_SCALE_FACTOR = 1 << INTEGER_MATH_SHIFT;
public Result decode(BinaryBitmap image) throws ReaderException {
return decode(image, null);
}
// Note that we don't try rotation without the try harder flag, even if rotation was supported.
public Result decode(BinaryBitmap image, Hashtable hints) throws ReaderException {
try {
return doDecode(image, hints);
} catch (ReaderException re) {
boolean tryHarder = hints != null && hints.containsKey(DecodeHintType.TRY_HARDER);
if (tryHarder && image.isRotateSupported()) {
BinaryBitmap rotatedImage = image.rotateCounterClockwise();
Result result = doDecode(rotatedImage, hints);
// Record that we found it rotated 90 degrees CCW / 270 degrees CW
Hashtable metadata = result.getResultMetadata();
int orientation = 270;
if (metadata != null && metadata.containsKey(ResultMetadataType.ORIENTATION)) {
// But if we found it reversed in doDecode(), add in that result here:
orientation = (orientation +
((Integer) metadata.get(ResultMetadataType.ORIENTATION)).intValue()) % 360;
}
result.putMetadata(ResultMetadataType.ORIENTATION, new Integer(orientation));
return result;
} else {
throw re;
}
}
}
/**
* We're going to examine rows from the middle outward, searching alternately above and below the
* middle, and farther out each time. rowStep is the number of rows between each successive
* attempt above and below the middle. So we'd scan row middle, then middle - rowStep, then
* middle + rowStep, then middle - (2 * rowStep), etc.
* rowStep is bigger as the image is taller, but is always at least 1. We've somewhat arbitrarily
* decided that moving up and down by about 1/16 of the image is pretty good; we try more of the
* image if "trying harder".
*
* @param image The image to decode
* @param hints Any hints that were requested
* @return The contents of the decoded barcode
* @throws ReaderException Any spontaneous errors which occur
*/
private Result doDecode(BinaryBitmap image, Hashtable hints) throws ReaderException {
int width = image.getWidth();
int height = image.getHeight();
BitArray row = new BitArray(width);
int middle = height >> 1;
boolean tryHarder = hints != null && hints.containsKey(DecodeHintType.TRY_HARDER);
int rowStep = Math.max(1, height >> (tryHarder ? 7 : 4));
int maxLines;
if (tryHarder) {
maxLines = height; // Look at the whole image, not just the center
} else {
maxLines = 9; // Nine rows spaced 1/16 apart is roughly the middle half of the image
}
for (int x = 0; x < maxLines; x++) {
// Scanning from the middle out. Determine which row we're looking at next:
int rowStepsAboveOrBelow = (x + 1) >> 1;
boolean isAbove = (x & 0x01) == 0; // i.e. is x even?
int rowNumber = middle + rowStep * (isAbove ? rowStepsAboveOrBelow : -rowStepsAboveOrBelow);
if (rowNumber < 0 || rowNumber >= height) {
// Oops, if we run off the top or bottom, stop
break;
}
// Estimate black point for this row and load it:
try {
row = image.getBlackRow(rowNumber, row);
} catch (ReaderException re) {
continue;
}
// While we have the image data in a BitArray, it's fairly cheap to reverse it in place to
// handle decoding upside down barcodes.
for (int attempt = 0; attempt < 2; attempt++) {
if (attempt == 1) { // trying again?
row.reverse(); // reverse the row and continue
// This means we will only ever draw result points *once* in the life of this method
// since we want to avoid drawing the wrong points after flipping the row, and,
// don't want to clutter with noise from every single row scan -- just the scans
// that start on the center line.
if (hints != null && hints.containsKey(DecodeHintType.NEED_RESULT_POINT_CALLBACK)) {
hints = (Hashtable) hints.clone();
hints.remove(DecodeHintType.NEED_RESULT_POINT_CALLBACK);
}
}
try {
// Look for a barcode
Result result = decodeRow(rowNumber, row, hints);
// We found our barcode
if (attempt == 1) {
// But it was upside down, so note that
result.putMetadata(ResultMetadataType.ORIENTATION, new Integer(180));
// And remember to flip the result points horizontally.
ResultPoint[] points = result.getResultPoints();
points[0] = new ResultPoint(width - points[0].getX() - 1, points[0].getY());
points[1] = new ResultPoint(width - points[1].getX() - 1, points[1].getY());
}
return result;
} catch (ReaderException re) {
// continue -- just couldn't decode this row
}
}
}
throw ReaderException.getInstance();
}
/**
* Records the size of successive runs of white and black pixels in a row, starting at a given point.
* The values are recorded in the given array, and the number of runs recorded is equal to the size
* of the array. If the row starts on a white pixel at the given start point, then the first count
* recorded is the run of white pixels starting from that point; likewise it is the count of a run
* of black pixels if the row begin on a black pixels at that point.
*
* @param row row to count from
* @param start offset into row to start at
* @param counters array into which to record counts
* @throws ReaderException if counters cannot be filled entirely from row before running out
* of pixels
*/
static void recordPattern(BitArray row, int start, int[] counters) throws ReaderException {
int numCounters = counters.length;
for (int i = 0; i < numCounters; i++) {
counters[i] = 0;
}
int end = row.getSize();
if (start >= end) {
throw ReaderException.getInstance();
}
boolean isWhite = !row.get(start);
int counterPosition = 0;
int i = start;
while (i < end) {
boolean pixel = row.get(i);
if (pixel ^ isWhite) { // that is, exactly one is true
counters[counterPosition]++;
} else {
counterPosition++;
if (counterPosition == numCounters) {
break;
} else {
counters[counterPosition] = 1;
isWhite = !isWhite;
}
}
i++;
}
// If we read fully the last section of pixels and filled up our counters -- or filled
// the last counter but ran off the side of the image, OK. Otherwise, a problem.
if (!(counterPosition == numCounters || (counterPosition == numCounters - 1 && i == end))) {
throw ReaderException.getInstance();
}
}
/**
* Determines how closely a set of observed counts of runs of black/white values matches a given
* target pattern. This is reported as the ratio of the total variance from the expected pattern
* proportions across all pattern elements, to the length of the pattern.
*
* @param counters observed counters
* @param pattern expected pattern
* @param maxIndividualVariance The most any counter can differ before we give up
* @return ratio of total variance between counters and pattern compared to total pattern size,
* where the ratio has been multiplied by 256. So, 0 means no variance (perfect match); 256 means
* the total variance between counters and patterns equals the pattern length, higher values mean
* even more variance
*/
static int patternMatchVariance(int[] counters, int[] pattern, int maxIndividualVariance) {
int numCounters = counters.length;
int total = 0;
int patternLength = 0;
for (int i = 0; i < numCounters; i++) {
total += counters[i];
patternLength += pattern[i];
}
if (total < patternLength) {
// If we don't even have one pixel per unit of bar width, assume this is too small
// to reliably match, so fail:
return Integer.MAX_VALUE;
}
// We're going to fake floating-point math in integers. We just need to use more bits.
// Scale up patternLength so that intermediate values below like scaledCounter will have
// more "significant digits"
int unitBarWidth = (total << INTEGER_MATH_SHIFT) / patternLength;
maxIndividualVariance = (maxIndividualVariance * unitBarWidth) >> INTEGER_MATH_SHIFT;
int totalVariance = 0;
for (int x = 0; x < numCounters; x++) {
int counter = counters[x] << INTEGER_MATH_SHIFT;
int scaledPattern = pattern[x] * unitBarWidth;
int variance = counter > scaledPattern ? counter - scaledPattern : scaledPattern - counter;
if (variance > maxIndividualVariance) {
return Integer.MAX_VALUE;
}
totalVariance += variance;
}
return totalVariance / total;
}
/**
* <p>Attempts to decode a one-dimensional barcode format given a single row of
@ -41,6 +253,7 @@ public interface OneDReader extends Reader {
* @return {@link Result} containing encoded string and start/end of barcode
* @throws ReaderException if an error occurs or barcode cannot be found
*/
Result decodeRow(int rowNumber, BitArray row, Hashtable hints) throws ReaderException;
public abstract Result decodeRow(int rowNumber, BitArray row, Hashtable hints)
throws ReaderException;
}
}

View file

@ -30,7 +30,7 @@ import java.util.Hashtable;
* @author dswitkin@google.com (Daniel Switkin)
* @author Sean Owen
*/
public final class UPCAReader implements UPCEANReader {
public final class UPCAReader extends UPCEANReader {
private final UPCEANReader ean13Reader = new EAN13Reader();
@ -51,6 +51,15 @@ public final class UPCAReader implements UPCEANReader {
return maybeReturnResult(ean13Reader.decode(image, hints));
}
BarcodeFormat getBarcodeFormat() {
return BarcodeFormat.UPC_A;
}
protected int decodeMiddle(BitArray row, int[] startRange, StringBuffer resultString)
throws ReaderException {
return ean13Reader.decodeMiddle(row, startRange, resultString);
}
private static Result maybeReturnResult(Result result) throws ReaderException {
String text = result.getText();
if (text.charAt(0) == '0') {
@ -60,4 +69,4 @@ public final class UPCAReader implements UPCEANReader {
}
}
}
}

View file

@ -18,24 +18,311 @@ package com.google.zxing.oned;
import com.google.zxing.ReaderException;
import com.google.zxing.Result;
import com.google.zxing.ResultPointCallback;
import com.google.zxing.DecodeHintType;
import com.google.zxing.ResultPoint;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.common.BitArray;
import java.util.Hashtable;
/**
* <p>This interfaces captures additional functionality that readers of
* UPC/EAN family of barcodes should expose.</p>
* <p>Encapsulates functionality and implementation that is common to UPC and EAN families
* of one-dimensional barcodes.</p>
*
* @author dswitkin@google.com (Daniel Switkin)
* @author Sean Owen
* @author alasdair@google.com (Alasdair Mackintosh)
*/
public interface UPCEANReader extends OneDReader {
public abstract class UPCEANReader extends OneDReader {
// These two values are critical for determining how permissive the decoding will be.
// We've arrived at these values through a lot of trial and error. Setting them any higher
// lets false positives creep in quickly.
private static final int MAX_AVG_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.42f);
private static final int MAX_INDIVIDUAL_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.7f);
/**
* Start/end guard pattern.
*/
static final int[] START_END_PATTERN = {1, 1, 1,};
/**
* Pattern marking the middle of a UPC/EAN pattern, separating the two halves.
*/
static final int[] MIDDLE_PATTERN = {1, 1, 1, 1, 1};
/**
* "Odd", or "L" patterns used to encode UPC/EAN digits.
*/
static final int[][] L_PATTERNS = {
{3, 2, 1, 1}, // 0
{2, 2, 2, 1}, // 1
{2, 1, 2, 2}, // 2
{1, 4, 1, 1}, // 3
{1, 1, 3, 2}, // 4
{1, 2, 3, 1}, // 5
{1, 1, 1, 4}, // 6
{1, 3, 1, 2}, // 7
{1, 2, 1, 3}, // 8
{3, 1, 1, 2} // 9
};
/**
* As above but also including the "even", or "G" patterns used to encode UPC/EAN digits.
*/
static final int[][] L_AND_G_PATTERNS;
static {
L_AND_G_PATTERNS = new int[20][];
for (int i = 0; i < 10; i++) {
L_AND_G_PATTERNS[i] = L_PATTERNS[i];
}
for (int i = 10; i < 20; i++) {
int[] widths = L_PATTERNS[i - 10];
int[] reversedWidths = new int[widths.length];
for (int j = 0; j < widths.length; j++) {
reversedWidths[j] = widths[widths.length - j - 1];
}
L_AND_G_PATTERNS[i] = reversedWidths;
}
}
private final StringBuffer decodeRowStringBuffer;
protected UPCEANReader() {
decodeRowStringBuffer = new StringBuffer(20);
}
static int[] findStartGuardPattern(BitArray row) throws ReaderException {
boolean foundStart = false;
int[] startRange = null;
int nextStart = 0;
while (!foundStart) {
startRange = findGuardPattern(row, nextStart, false, START_END_PATTERN);
int start = startRange[0];
nextStart = startRange[1];
// Make sure there is a quiet zone at least as big as the start pattern before the barcode.
// If this check would run off the left edge of the image, do not accept this barcode,
// as it is very likely to be a false positive.
int quietStart = start - (nextStart - start);
if (quietStart >= 0) {
foundStart = row.isRange(quietStart, start, false);
}
}
return startRange;
}
public Result decodeRow(int rowNumber, BitArray row, Hashtable hints) throws ReaderException {
return decodeRow(rowNumber, row, findStartGuardPattern(row), hints);
}
/**
* <p>Like {@link #decodeRow(int, BitArray, java.util.Hashtable)}, but
* allows caller to inform method about where the UPC/EAN start pattern is
* found. This allows this to be computed once and reused across many implementations.</p>
*/
Result decodeRow(int rowNumber, BitArray row, int[] startGuardRange, Hashtable hints)
public Result decodeRow(int rowNumber, BitArray row, int[] startGuardRange, Hashtable hints)
throws ReaderException {
ResultPointCallback resultPointCallback = hints == null ? null :
(ResultPointCallback) hints.get(DecodeHintType.NEED_RESULT_POINT_CALLBACK);
if (resultPointCallback != null) {
resultPointCallback.foundPossibleResultPoint(new ResultPoint(
(startGuardRange[0] + startGuardRange[1]) / 2.0f, rowNumber
));
}
StringBuffer result = decodeRowStringBuffer;
result.setLength(0);
int endStart = decodeMiddle(row, startGuardRange, result);
if (resultPointCallback != null) {
resultPointCallback.foundPossibleResultPoint(new ResultPoint(
endStart, rowNumber
));
}
int[] endRange = decodeEnd(row, endStart);
if (resultPointCallback != null) {
resultPointCallback.foundPossibleResultPoint(new ResultPoint(
(endRange[0] + endRange[1]) / 2.0f, rowNumber
));
}
// Make sure there is a quiet zone at least as big as the end pattern after the barcode. The
// spec might want more whitespace, but in practice this is the maximum we can count on.
int end = endRange[1];
int quietEnd = end + (end - endRange[0]);
if (quietEnd >= row.getSize() || !row.isRange(end, quietEnd, false)) {
throw ReaderException.getInstance();
}
String resultString = result.toString();
if (!checkChecksum(resultString)) {
throw ReaderException.getInstance();
}
float left = (float) (startGuardRange[1] + startGuardRange[0]) / 2.0f;
float right = (float) (endRange[1] + endRange[0]) / 2.0f;
return new Result(resultString,
null, // no natural byte representation for these barcodes
new ResultPoint[]{
new ResultPoint(left, (float) rowNumber),
new ResultPoint(right, (float) rowNumber)},
getBarcodeFormat());
}
/**
* @return {@link #checkStandardUPCEANChecksum(String)}
*/
boolean checkChecksum(String s) throws ReaderException {
return checkStandardUPCEANChecksum(s);
}
/**
* Computes the UPC/EAN checksum on a string of digits, and reports
* whether the checksum is correct or not.
*
* @param s string of digits to check
* @return true iff string of digits passes the UPC/EAN checksum algorithm
* @throws ReaderException if the string does not contain only digits
*/
private static boolean checkStandardUPCEANChecksum(String s) throws ReaderException {
int length = s.length();
if (length == 0) {
return false;
}
int sum = 0;
for (int i = length - 2; i >= 0; i -= 2) {
int digit = (int) s.charAt(i) - (int) '0';
if (digit < 0 || digit > 9) {
throw ReaderException.getInstance();
}
sum += digit;
}
sum *= 3;
for (int i = length - 1; i >= 0; i -= 2) {
int digit = (int) s.charAt(i) - (int) '0';
if (digit < 0 || digit > 9) {
throw ReaderException.getInstance();
}
sum += digit;
}
return sum % 10 == 0;
}
int[] decodeEnd(BitArray row, int endStart) throws ReaderException {
return findGuardPattern(row, endStart, false, START_END_PATTERN);
}
/**
* @param row row of black/white values to search
* @param rowOffset position to start search
* @param whiteFirst if true, indicates that the pattern specifies white/black/white/...
* pixel counts, otherwise, it is interpreted as black/white/black/...
* @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
* @throws ReaderException if pattern is not found
*/
static int[] findGuardPattern(BitArray row, int rowOffset, boolean whiteFirst, int[] pattern)
throws ReaderException {
int patternLength = pattern.length;
int[] counters = new int[patternLength];
int width = row.getSize();
boolean isWhite = false;
while (rowOffset < width) {
isWhite = !row.get(rowOffset);
if (whiteFirst == isWhite) {
break;
}
rowOffset++;
}
int counterPosition = 0;
int patternStart = rowOffset;
for (int x = rowOffset; x < width; x++) {
boolean pixel = row.get(x);
if (pixel ^ isWhite) {
counters[counterPosition]++;
} else {
if (counterPosition == patternLength - 1) {
if (patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE) < MAX_AVG_VARIANCE) {
return new int[]{patternStart, x};
}
patternStart += counters[0] + counters[1];
for (int y = 2; y < patternLength; y++) {
counters[y - 2] = counters[y];
}
counters[patternLength - 2] = 0;
counters[patternLength - 1] = 0;
counterPosition--;
} else {
counterPosition++;
}
counters[counterPosition] = 1;
isWhite = !isWhite;
}
}
throw ReaderException.getInstance();
}
/**
* Attempts to decode a single UPC/EAN-encoded digit.
*
* @param row row of black/white values to decode
* @param counters the counts of runs of observed black/white/black/... values
* @param rowOffset horizontal offset to start decoding from
* @param patterns the set of patterns to use to decode -- sometimes different encodings
* for the digits 0-9 are used, and this indicates the encodings for 0 to 9 that should
* be used
* @return horizontal offset of first pixel beyond the decoded digit
* @throws ReaderException if digit cannot be decoded
*/
static int decodeDigit(BitArray row, int[] counters, int rowOffset, int[][] patterns)
throws ReaderException {
recordPattern(row, rowOffset, counters);
int bestVariance = MAX_AVG_VARIANCE; // worst variance we'll accept
int bestMatch = -1;
int max = patterns.length;
for (int i = 0; i < max; i++) {
int[] pattern = patterns[i];
int variance = patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE);
if (variance < bestVariance) {
bestVariance = variance;
bestMatch = i;
}
}
if (bestMatch >= 0) {
return bestMatch;
} else {
throw ReaderException.getInstance();
}
}
/**
* Get the format of this decoder.
*
* @return The 1D format.
*/
abstract BarcodeFormat getBarcodeFormat();
/**
* Subclasses override this to decode the portion of a barcode between the start
* and end guard patterns.
*
* @param row row of black/white values to search
* @param startRange start/end offset of start guard pattern
* @param resultString {@link StringBuffer} to append decoded chars to
* @return horizontal offset of first pixel after the "middle" that was decoded
* @throws ReaderException if decoding could not complete successfully
*/
protected abstract int decodeMiddle(BitArray row, int[] startRange, StringBuffer resultString)
throws ReaderException;
}
}

View file

@ -16,14 +16,114 @@
package com.google.zxing.oned;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.Writer;
import com.google.zxing.WriterException;
import com.google.zxing.common.ByteMatrix;
import java.util.Hashtable;
/**
* @author Ari Pollak
* <p>Encapsulates functionality and implementation that is common to UPC and EAN families
* of one-dimensional barcodes.</p>
*
* @author aripollak@gmail.com (Ari Pollak)
*/
public interface UPCEANWriter extends Writer {
public abstract class UPCEANWriter implements Writer {
public ByteMatrix encode(String contents, BarcodeFormat format, int width, int height)
throws WriterException {
return encode(contents, format, width, height, null);
}
public ByteMatrix encode(String contents, BarcodeFormat format, int width, int height,
Hashtable hints) throws WriterException {
if (contents == null || contents.length() == 0) {
throw new IllegalArgumentException("Found empty contents");
}
if (width < 0 || height < 0) {
throw new IllegalArgumentException("Requested dimensions are too small: "
+ width + 'x' + height);
}
byte[] code = encode(contents);
return renderResult(code, width, height);
}
/** @return a byte array of horizontal pixels (0 = white, 1 = black) */
byte[] encode(String contents);
private static ByteMatrix renderResult(byte[] code, int width, int height) {
int inputWidth = code.length;
// Add quiet zone on both sides
int fullWidth = inputWidth + (UPCEANReader.START_END_PATTERN.length << 1);
int outputWidth = Math.max(width, fullWidth);
int outputHeight = Math.max(1, height);
int multiple = outputWidth / fullWidth;
int leftPadding = (outputWidth - (inputWidth * multiple)) / 2;
ByteMatrix output = new ByteMatrix(outputWidth, outputHeight);
byte[][] outputArray = output.getArray();
byte[] row = new byte[outputWidth];
// a. Write the white pixels at the left of each row
for (int x = 0; x < leftPadding; x++) {
row[x] = (byte) 255;
}
// b. Write the contents of this row of the barcode
int offset = leftPadding;
for (int x = 0; x < inputWidth; x++) {
byte value = (code[x] == 1) ? 0 : (byte) 255;
for (int z = 0; z < multiple; z++) {
row[offset + z] = value;
}
offset += multiple;
}
// c. Write the white pixels at the right of each row
offset = leftPadding + (inputWidth * multiple);
for (int x = offset; x < outputWidth; x++) {
row[x] = (byte) 255;
}
// d. Write the completed row multiple times
for (int z = 0; z < outputHeight; z++) {
System.arraycopy(row, 0, outputArray[z], 0, outputWidth);
}
return output;
}
/**
* Appends the given pattern to the target array starting at pos.
*
* @param startColor
* starting color - 0 for white, 1 for black
* @return the number of elements added to target.
*/
protected static int appendPattern(byte[] target, int pos, int[] pattern, int startColor) {
if (startColor != 0 && startColor != 1) {
throw new IllegalArgumentException(
"startColor must be either 0 or 1, but got: " + startColor);
}
byte color = (byte) startColor;
int numAdded = 0;
for (int i = 0; i < pattern.length; i++) {
for (int j = 0; j < pattern[i]; j++) {
target[pos] = color;
pos += 1;
numAdded += 1;
}
color ^= 1; // flip color after each segment
}
return numAdded;
}
/** @return a byte array of horizontal pixels (0 = white, 1 = black) */
public abstract byte[] encode(String contents);
}

View file

@ -16,8 +16,8 @@
package com.google.zxing.oned;
import com.google.zxing.ReaderException;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.ReaderException;
import com.google.zxing.common.BitArray;
/**
@ -28,7 +28,7 @@ import com.google.zxing.common.BitArray;
*
* @author Sean Owen
*/
public final class UPCEReader extends AbstractUPCEANReader {
public final class UPCEReader extends UPCEANReader {
/**
* The pattern that marks the middle, and end, of a UPC-E pattern.
@ -148,4 +148,4 @@ public final class UPCEReader extends AbstractUPCEANReader {
return result.toString();
}
}
}