mirror of
https://github.com/zxing/zxing.git
synced 2025-03-05 20:48:51 -08:00
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:
parent
fe87c689bc
commit
bdc25cf5bc
|
@ -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;
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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};
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 {
|
|||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
|||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue