Major refactoring of 1D barcode code. Moved into com.google.zxing.oned package. Misc. other changes to support this refactoring

git-svn-id: https://zxing.googlecode.com/svn/trunk@159 59b500cc-1b3d-0410-9834-0bbf25fbcc57
This commit is contained in:
srowen 2008-02-04 19:14:29 +00:00
parent e4c014d324
commit 8f7a3346cb
24 changed files with 1610 additions and 531 deletions

View file

@ -25,8 +25,8 @@ public final class BarcodeFormat {
// No, we can't use an enum here. J2ME doesn't support it.
/** UPC 1D barcode format family. */
public static final BarcodeFormat UPC = new BarcodeFormat();
/** 1D barcode format family. */
public static final BarcodeFormat ONED = new BarcodeFormat();
/** QR Code 2D barcode format */
public static final BarcodeFormat QR_CODE = new BarcodeFormat();
/** DataMatrix 2D barcode format */

View file

@ -16,8 +16,8 @@
package com.google.zxing;
import com.google.zxing.oned.MultiFormatOneDReader;
import com.google.zxing.qrcode.QRCodeReader;
import com.google.zxing.upc.UPCReader;
import java.util.Hashtable;
@ -38,28 +38,24 @@ public final class MultiFormatReader implements Reader {
throws ReaderException {
Hashtable possibleFormats = hints == null ? null : (Hashtable) hints.get(DecodeHintType.POSSIBLE_FORMATS);
boolean tryUPC;
boolean tryOneD;
boolean tryQR;
if (possibleFormats == null) {
tryUPC = true;
tryOneD = true;
tryQR = true;
} else {
tryUPC = possibleFormats.contains(BarcodeFormat.UPC);
tryOneD = possibleFormats.contains(BarcodeFormat.ONED);
tryQR = possibleFormats.contains(BarcodeFormat.QR_CODE);
}
if (!(tryUPC || tryQR)) {
if (!(tryOneD || tryQR)) {
throw new ReaderException("POSSIBLE_FORMATS specifies no supported types");
}
// Save the last exception as what we'll report if nothing decodes
ReaderException savedRE = null;
// UPC is much faster to decode, so try it first.
if (tryUPC) {
if (tryOneD) {
try {
return new UPCReader().decode(image, hints);
return new MultiFormatOneDReader().decode(image, hints);
} catch (ReaderException re) {
savedRE = re;
}
}
@ -68,11 +64,10 @@ public final class MultiFormatReader implements Reader {
try {
return new QRCodeReader().decode(image, hints);
} catch (ReaderException re) {
savedRE = re;
}
}
throw savedRE;
throw new ReaderException("No barcode was detected in this image.");
}
}

View file

@ -25,6 +25,7 @@ package com.google.zxing.common;
* </p>
*
* @author srowen@google.com (Sean Owen)
* @author dswitkin@google.com (Daniel Switkin)
*/
public final class BlackPointEstimator {
@ -37,10 +38,17 @@ public final class BlackPointEstimator {
* count of the brightest luminance values that should be considered "black".</p>
*
* @param histogram an array of <em>counts</em> of luminance values
* @param biasTowardsWhite values higher than 1.0 suggest that a higher black point is desirable (e.g.
* more values are considered black); less than 1.0 suggests that lower is desirable. Must be greater
* than 0.0; 1.0 is a good "default"
* @return index within argument of bucket corresponding to brightest values which should be
* considered "black"
*/
public static int estimate(int[] histogram) {
public static int estimate(int[] histogram, float biasTowardsWhite) {
if (Float.isNaN(biasTowardsWhite) || biasTowardsWhite <= 0.0f) {
throw new IllegalArgumentException("Illegal biasTowardsWhite: " + biasTowardsWhite);
}
int numBuckets = histogram.length;
@ -79,7 +87,7 @@ public final class BlackPointEstimator {
int bestValley = secondPeak - 1;
int bestValleyScore = -1;
for (int i = secondPeak - 1; i > firstPeak; i--) {
int fromFirst = i - firstPeak;
int fromFirst = (int) (biasTowardsWhite * (i - firstPeak));
// Favor a "valley" that is not too close to either peak -- especially not the black peak --
// and that has a low value of course
int score = fromFirst * fromFirst * (secondPeak - i) * (256 - histogram[i]);

View file

@ -1,5 +1,5 @@
/*
* Copyright 2007 Google Inc.
* Copyright 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -14,19 +14,22 @@
* limitations under the License.
*/
package com.google.zxing.upc;
package com.google.zxing.common;
import com.google.zxing.ResultPoint;
/**
* <p>Simple implementation of {@link ResultPoint} for applications that don't need
* to use anything more complex.</p>
*
* @author dswitkin@google.com (Daniel Switkin)
*/
public final class UPCPoint implements ResultPoint {
public final class GenericResultPoint implements ResultPoint {
private final float posX;
private final float posY;
UPCPoint(float posX, float posY) {
public GenericResultPoint(float posX, float posY) {
this.posX = posX;
this.posY = posY;
}

View file

@ -0,0 +1,153 @@
/*
* Copyright 2008 Google Inc.
*
* 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.BlackPointEstimationMethod;
import com.google.zxing.MonochromeBitmapSource;
import com.google.zxing.ReaderException;
import com.google.zxing.Result;
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 srowen@google.com (Sean Owen)
*/
public abstract class AbstractOneDReader implements OneDReader {
public final Result decode(MonochromeBitmapSource image) throws ReaderException {
return decode(image, null);
}
public final Result decode(MonochromeBitmapSource image, Hashtable hints) throws ReaderException {
int width = image.getWidth();
int height = image.getHeight();
BitArray row = new BitArray(width);
// 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.
int middle = height >> 1;
int rowStep = Math.max(1, height >> 5);
for (int x = 0; x < 11; x++) {
int rowStepsAboveOrBelow = (x + 1) >> 1;
boolean isAbove = (x & 0x01) == 0; // i.e. is x even?
int rowNumber = middle + rowStep * (isAbove ? rowStepsAboveOrBelow : -rowStepsAboveOrBelow);
image.estimateBlackPoint(BlackPointEstimationMethod.ROW_SAMPLING, rowNumber);
image.getBlackRow(rowNumber, row, 0, width);
try {
return decodeRow(rowNumber, row);
} catch (ReaderException re) {
// TODO re-enable this in a "try harder" mode?
//row.reverse(); // try scanning the row backwards
//try {
// return decodeRow(rowNumber, row);
//} catch (ReaderException re2) {
// continue
//}
}
}
throw new ReaderException("No barcode found");
}
protected static void recordPattern(BitArray row, int start, int[] counters) throws ReaderException {
for (int i = 0; i < counters.length; i++) {
counters[i] = 0;
}
int end = row.getSize();
if (start >= end) {
throw new ReaderException("Couldn't fully read a pattern");
}
boolean isWhite = !row.get(start);
int counterPosition = 0;
int i = start;
while (i < end) {
boolean pixel = row.get(i);
if ((!pixel && isWhite) || (pixel && !isWhite)) {
counters[counterPosition]++;
} else {
counterPosition++;
if (counterPosition == counters.length) {
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 == counters.length || (counterPosition == counters.length - 1 && i == end))) {
throw new ReaderException("Couldn't fully read a pattern");
}
}
/**
* Determines how closely a set of observed counts of runs of black/white values matches a given
* target pattern. For each counter, the ratio of the difference between it and the pattern value
* is compared to the expected pattern value. This ratio is averaged across counters to produce
* the return value. 0.0 means an exact match; higher values mean poorer matches.
*
* @param counters observed counters
* @param pattern expected pattern
* @return average variance between counters and pattern
*/
protected static float patternMatchVariance(int[] counters, int[] pattern) {
int total = 0;
int numCounters = counters.length;
int patternLength = 0;
for (int i = 0; i < numCounters; i++) {
total += counters[i];
patternLength += pattern[i];
}
float unitBarWidth = (float) total / (float) patternLength;
float totalVariance = 0.0f;
for (int x = 0; x < numCounters; x++) {
float scaledCounter = (float) counters[x] / unitBarWidth;
float width = pattern[x];
float abs = scaledCounter > width ? scaledCounter - width : width - scaledCounter;
totalVariance += abs / width;
}
return totalVariance / (float) numCounters;
}
/**
* Fast round method.
*
* @return argument rounded to nearest int
*/
protected static int round(float f) {
return (int) (f + 0.5f);
}
}

View file

@ -0,0 +1,256 @@
/*
* Copyright 2008 Google Inc.
*
* 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.Result;
import com.google.zxing.ResultPoint;
import com.google.zxing.ReaderException;
import com.google.zxing.common.BitArray;
import com.google.zxing.common.GenericResultPoint;
/**
* <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 srowen@google.com (Sean Owen)
* @author alasdair@google.com (Alasdair Mackintosh)
*/
public abstract class AbstractUPCEANReader extends AbstractOneDReader implements UPCEANReader {
private static final float MAX_VARIANCE = 0.4f;
/** Start/end guard pattern. */
protected static final int[] START_END_PATTERN = {1, 1, 1,};
/** Pattern marking the middle of a UPC/EAN pattern, separating the two halves. */
protected static final int[] MIDDLE_PATTERN = {1, 1, 1, 1, 1};
/** "Odd", or "L" patterns used to encode UPC/EAN digits. */
protected 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. */
protected 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;
}
}
static int[] findStartGuardPattern(final 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];
// As a check, we want to see some white in front of this "start pattern",
// maybe as wide as the start pattern itself?
foundStart = isWhiteRange(row, Math.max(0, start - 2 * (startRange[1] - start)), start);
}
return startRange;
}
public final Result decodeRow(int rowNumber, BitArray row) throws ReaderException {
return decodeRow(rowNumber, row, findStartGuardPattern(row));
}
public final Result decodeRow(int rowNumber, BitArray row, int[] startGuardRange) throws ReaderException {
StringBuffer result = new StringBuffer();
int endStart = decodeMiddle(row, startGuardRange, result);
int[] endRange = decodeEnd(row, endStart);
// Check for whitespace after the pattern
int end = endRange[1];
if (!isWhiteRange(row, end, Math.min(row.getSize(), end + 2 * (end - endRange[0])))) {
throw new ReaderException("Pattern not followed by whitespace");
}
String resultString = result.toString();
if (!checkChecksum(resultString)) {
throw new ReaderException("Checksum failed");
}
return new Result(resultString, new ResultPoint[]{
new GenericResultPoint((float) (startGuardRange[1] - startGuardRange[0]) / 2.0f, (float) rowNumber),
new GenericResultPoint((float) (endRange[1] - endRange[0]) / 2.0f, (float) rowNumber)});
}
/**
* @return true iff row consists of white values in the range [start,end)
*/
protected static boolean isWhiteRange(BitArray row, int start, int end) {
for (int i = start; i < end; i++) {
if (row.get(i)) {
return false;
}
}
return true;
}
/**
* Computes the UPC/EAN checksum on a string of digits
* @param s
* @return
*/
protected boolean checkChecksum(String s) throws ReaderException {
int sum = 0;
int length = s.length();
for (int i = length - 2; i >= 0; i -= 2) {
int digit = (int) s.charAt(i) - (int) '0';
if (digit < 0 || digit > 9) {
throw new ReaderException("Illegal character during checksum");
}
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 new ReaderException("Illegal character during checksum");
}
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;
protected 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
*/
protected 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) || (pixel && !isWhite)) {
counters[counterPosition]++;
} else {
if (counterPosition == patternLength - 1) {
if (patternMatchVariance(counters, pattern) < MAX_VARIANCE) {
return new int[] {patternStart, x};
}
patternStart += counters[0] + counters[1];
for (int y = 2; y < patternLength; y++) {
counters[y - 2] = counters[y];
}
counterPosition--;
} else {
counterPosition++;
}
counters[counterPosition] = 1;
isWhite = !isWhite;
}
}
throw new ReaderException("Can't find pattern");
}
/**
* 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
*/
protected static int decodeDigit(BitArray row,
int[] counters,
int rowOffset,
int[][] patterns) throws ReaderException {
recordPattern(row, rowOffset, counters);
float bestVariance = MAX_VARIANCE; // worst variance we'll accept
int bestMatch = -1;
for (int d = 0; d < patterns.length; d++) {
int[] pattern = patterns[d];
float variance = patternMatchVariance(counters, pattern);
if (variance < bestVariance) {
bestVariance = variance;
bestMatch = d;
}
}
if (bestMatch >= 0) {
return bestMatch;
} else {
throw new ReaderException("Could not match any digit in pattern");
}
}
}

View file

@ -0,0 +1,404 @@
/*
* Copyright 2008 Google Inc.
*
* 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.Result;
import com.google.zxing.ReaderException;
import com.google.zxing.ResultPoint;
import com.google.zxing.common.BitArray;
import com.google.zxing.common.GenericResultPoint;
/**
* <p>Decodes Code 128 barcodes.</p>
*
* @author srowen@google.com (Sean Owen)
*/
public final class Code128Reader extends AbstractOneDReader {
private static final int[][] CODE_PATTERNS = {
{2, 1, 2, 2, 2, 2}, // 0
{2, 2, 2, 1, 2, 2},
{2, 2, 2, 2, 2, 1},
{1, 2, 1, 2, 2, 3},
{1, 2, 1, 3, 2, 2},
{1, 3, 1, 2, 2, 2}, // 5
{1, 2, 2, 2, 1, 3},
{1, 2, 2, 3, 1, 2},
{1, 3, 2, 2, 1, 2},
{2, 2, 1, 2, 1, 3},
{2, 2, 1, 3, 1, 2}, // 10
{2, 3, 1, 2, 1, 2},
{1, 1, 2, 2, 3, 2},
{1, 2, 2, 1, 3, 2},
{1, 2, 2, 2, 3, 1},
{1, 1, 3, 2, 2, 2}, // 15
{1, 2, 3, 1, 2, 2},
{1, 2, 3, 2, 2, 1},
{2, 2, 3, 2, 1, 1},
{2, 2, 1, 1, 3, 2},
{2, 2, 1, 2, 3, 1}, // 20
{2, 1, 3, 2, 1, 2},
{2, 2, 3, 1, 1, 2},
{3, 1, 2, 1, 3, 1},
{3, 1, 1, 2, 2, 2},
{3, 2, 1, 1, 2, 2}, // 25
{3, 2, 1, 2, 2, 1},
{3, 1, 2, 2, 1, 2},
{3, 2, 2, 1, 1, 2},
{3, 2, 2, 2, 1, 1},
{2, 1, 2, 1, 2, 3}, // 30
{2, 1, 2, 3, 2, 1},
{2, 3, 2, 1, 2, 1},
{1, 1, 1, 3, 2, 3},
{1, 3, 1, 1, 2, 3},
{1, 3, 1, 3, 2, 1}, // 35
{1, 1, 2, 3, 1, 3},
{1, 3, 2, 1, 1, 3},
{1, 3, 2, 3, 1, 1},
{2, 1, 1, 3, 1, 3},
{2, 3, 1, 1, 1, 3}, // 40
{2, 3, 1, 3, 1, 1},
{1, 1, 2, 1, 3, 3},
{1, 1, 2, 3, 3, 1},
{1, 3, 2, 1, 3, 1},
{1, 1, 3, 1, 2, 3}, // 45
{1, 1, 3, 3, 2, 1},
{1, 3, 3, 1, 2, 1},
{3, 1, 3, 1, 2, 1},
{2, 1, 1, 3, 3, 1},
{2, 3, 1, 1, 3, 1}, // 50
{2, 1, 3, 1, 1, 3},
{2, 1, 3, 3, 1, 1},
{2, 1, 3, 1, 3, 1},
{3, 1, 1, 1, 2, 3},
{3, 1, 1, 3, 2, 1}, // 55
{3, 3, 1, 1, 2, 1},
{3, 1, 2, 1, 1, 3},
{3, 1, 2, 3, 1, 1},
{3, 3, 2, 1, 1, 1},
{3, 1, 4, 1, 1, 1}, // 60
{2, 2, 1, 4, 1, 1},
{4, 3, 1, 1, 1, 1},
{1, 1, 1, 2, 2, 4},
{1, 1, 1, 4, 2, 2},
{1, 2, 1, 1, 2, 4}, // 65
{1, 2, 1, 4, 2, 1},
{1, 4, 1, 1, 2, 2},
{1, 4, 1, 2, 2, 1},
{1, 1, 2, 2, 1, 4},
{1, 1, 2, 4, 1, 2}, // 70
{1, 2, 2, 1, 1, 4},
{1, 2, 2, 4, 1, 1},
{1, 4, 2, 1, 1, 2},
{1, 4, 2, 2, 1, 1},
{2, 4, 1, 2, 1, 1}, // 75
{2, 2, 1, 1, 1, 4},
{4, 1, 3, 1, 1, 1},
{2, 4, 1, 1, 1, 2},
{1, 3, 4, 1, 1, 1},
{1, 1, 1, 2, 4, 2}, // 80
{1, 2, 1, 1, 4, 2},
{1, 2, 1, 2, 4, 1},
{1, 1, 4, 2, 1, 2},
{1, 2, 4, 1, 1, 2},
{1, 2, 4, 2, 1, 1}, // 85
{4, 1, 1, 2, 1, 2},
{4, 2, 1, 1, 1, 2},
{4, 2, 1, 2, 1, 1},
{2, 1, 2, 1, 4, 1},
{2, 1, 4, 1, 2, 1}, // 90
{4, 1, 2, 1, 2, 1},
{1, 1, 1, 1, 4, 3},
{1, 1, 1, 3, 4, 1},
{1, 3, 1, 1, 4, 1},
{1, 1, 4, 1, 1, 3}, // 95
{1, 1, 4, 3, 1, 1},
{4, 1, 1, 1, 1, 3},
{4, 1, 1, 3, 1, 1},
{1, 1, 3, 1, 4, 1},
{1, 1, 4, 1, 3, 1}, // 100
{3, 1, 1, 1, 4, 1},
{4, 1, 1, 1, 3, 1},
{2, 1, 1, 4, 1, 2},
{2, 1, 1, 2, 1, 4},
{2, 1, 1, 2, 3, 2}, // 105
{2, 3, 3, 1, 1, 1, 2}
};
private static final float MAX_VARIANCE = 0.4f;
private static final int CODE_SHIFT = 98;
private static final int CODE_CODE_C = 99;
private static final int CODE_CODE_B = 100;
private static final int CODE_CODE_A = 101;
private static final int CODE_FNC_1 = 102;
private static final int CODE_FNC_2 = 97;
private static final int CODE_FNC_3 = 96;
private static final int CODE_FNC_4_A = 101;
private static final int CODE_FNC_4_B = 100;
private static final int CODE_START_A = 103;
private static final int CODE_START_B = 104;
private static final int CODE_START_C = 105;
private static final int CODE_STOP = 106;
private static int[] findStartPattern(BitArray row) throws ReaderException {
int width = row.getSize();
int rowOffset = 0;
while (rowOffset < width) {
if (row.get(rowOffset)) {
break;
}
rowOffset++;
}
int counterPosition = 0;
int[] counters = new int[6];
int patternStart = rowOffset;
boolean isWhite = false;
int patternLength = counters.length;
for (int i = rowOffset; i < width; i++) {
boolean pixel = row.get(i);
if ((!pixel && isWhite) || (pixel && !isWhite)) {
counters[counterPosition]++;
} else {
if (counterPosition == patternLength - 1) {
for (int startCode = CODE_START_A; startCode <= CODE_START_C; startCode++) {
if (patternMatchVariance(counters, CODE_PATTERNS[startCode]) < MAX_VARIANCE) {
return new int[] {patternStart, i, startCode};
}
}
patternStart += counters[0] + counters[1];
for (int y = 2; y < patternLength; y++) {
counters[y - 2] = counters[y];
}
counterPosition--;
} else {
counterPosition++;
}
counters[counterPosition] = 1;
isWhite = !isWhite;
}
}
throw new ReaderException("Can't find pattern");
}
private static int decodeCode(BitArray row, int[] counters, int rowOffset) throws ReaderException {
recordPattern(row, rowOffset, counters);
float bestVariance = 0.4f; // worst variance we'll accept
int bestMatch = -1;
for (int d = 0; d < CODE_PATTERNS.length; d++) {
int[] pattern = CODE_PATTERNS[d];
float variance = patternMatchVariance(counters, pattern);
if (variance < bestVariance) {
bestVariance = variance;
bestMatch = d;
}
}
// TODO We're overlooking the fact that the STOP pattern has 7 values, not 6
if (bestMatch >= 0) {
return bestMatch;
} else {
throw new ReaderException("Could not match any code pattern");
}
}
public Result decodeRow(final int rowNumber, final BitArray row) throws ReaderException {
int[] startPatternInfo = findStartPattern(row);
int startCode = startPatternInfo[2];
int codeSet;
switch (startCode) {
case CODE_START_A:
codeSet = CODE_CODE_A;
break;
case CODE_START_B:
codeSet = CODE_CODE_B;
break;
case CODE_START_C:
codeSet = CODE_CODE_C;
break;
default:
throw new ReaderException("Illegal start code");
}
boolean done = false;
boolean isNextShifted = false;
StringBuffer result = new StringBuffer();
int lastStart = startPatternInfo[0];
int nextStart = startPatternInfo[1];
int[] counters = new int[6];
int lastCode = 0;
int code = 0;
int checksumTotal = startCode;
int multiplier = 0;
while (!done) {
boolean unshift = isNextShifted;
isNextShifted = false;
lastCode = code;
code = decodeCode(row, counters, nextStart);
if (code != CODE_STOP) {
multiplier++;
checksumTotal += multiplier * code;
}
lastStart = nextStart;
for (int i = 0; i < counters.length; i++) {
nextStart += counters[i];
}
// Take care of illegal start codes
switch (code) {
case CODE_START_A:
case CODE_START_B:
case CODE_START_C:
throw new ReaderException("Unexpected start code");
}
switch (codeSet) {
case CODE_CODE_A:
if (code < 64) {
result.append((char) (' ' + code));
} else if (code < 96) {
result.append((char) (code - 64));
} else {
switch (code) {
case CODE_FNC_1:
case CODE_FNC_2:
case CODE_FNC_3:
case CODE_FNC_4_A:
// do nothing?
break;
case CODE_SHIFT:
isNextShifted = true;
codeSet = CODE_CODE_B;
break;
case CODE_CODE_B:
codeSet = CODE_CODE_B;
break;
case CODE_CODE_C:
codeSet = CODE_CODE_C;
break;
case CODE_STOP:
done = true;
break;
}
}
break;
case CODE_CODE_B:
if (code < 96) {
result.append((char) (' ' + code));
} else {
switch (code) {
case CODE_FNC_1:
case CODE_FNC_2:
case CODE_FNC_3:
case CODE_FNC_4_B:
// do nothing?
break;
case CODE_SHIFT:
isNextShifted = true;
codeSet = CODE_CODE_C;
break;
case CODE_CODE_A:
codeSet = CODE_CODE_A;
break;
case CODE_CODE_C:
codeSet = CODE_CODE_C;
break;
case CODE_STOP:
done = true;
break;
}
}
break;
case CODE_CODE_C:
if (code < 100) {
if (code < 10) {
result.append('0');
}
result.append(code);
} else {
switch (code) {
case CODE_FNC_1:
// do nothing?
break;
case CODE_CODE_A:
codeSet = CODE_CODE_A;
break;
case CODE_CODE_B:
codeSet = CODE_CODE_B;
break;
case CODE_STOP:
done = true;
break;
}
}
break;
}
if (unshift) {
switch (codeSet) {
case CODE_CODE_A:
codeSet = CODE_CODE_C;
break;
case CODE_CODE_B:
codeSet = CODE_CODE_A;
break;
case CODE_CODE_C:
codeSet = CODE_CODE_B;
break;
}
}
}
// Pull out from sum the value of the penultimate check code
checksumTotal -= multiplier * lastCode;
if (checksumTotal % 103 != lastCode) {
throw new ReaderException("Checksum failed");
}
// Need to pull out the check digits from string
int resultLength = result.length();
if (resultLength > 0) {
if (codeSet == CODE_CODE_C) {
result.delete(resultLength - 2, resultLength);
} else {
result.delete(resultLength - 1, resultLength);
}
}
String resultString = result.toString();
return new Result(resultString,
new ResultPoint[]{new GenericResultPoint((float) (startPatternInfo[1] - startPatternInfo[0]) / 2.0f,
(float) rowNumber),
new GenericResultPoint((float) (nextStart - lastStart) / 2.0f,
(float) rowNumber)});
}
}

View file

@ -0,0 +1,192 @@
/*
* Copyright 2008 Google Inc.
*
* 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.ReaderException;
import com.google.zxing.Result;
import com.google.zxing.ResultPoint;
import com.google.zxing.common.BitArray;
import com.google.zxing.common.GenericResultPoint;
/**
* <p>Decodes Code 39 barcodes. This does not supported "Full ASCII Code 39" yet.</p>
*
* @author srowen@google.com (Sean Owen)
*/
public final class Code39Reader extends AbstractOneDReader {
private static final String ALPHABET_STRING = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. *$/+%";
private static final char[] ALPHABET = ALPHABET_STRING.toCharArray();
private static final int[] CHARACTER_ENCODINGS = {
0x038, 0x121, 0x061, 0x160, 0x031, 0x130, 0x070, 0x025, 0x124, 0x064, // 0-9
0x109, 0x049, 0x148, 0x019, 0x118, 0x058, 0x00D, 0x10C, 0x08C, 0x01C, // A-J
0x103, 0x043, 0x142, 0x013, 0x112, 0x052, 0x007, 0x106, 0x046, 0x016, // K-T
0x181, 0x0C1, 0x1C0, 0x091, 0x190, 0x0D0, 0x085, 0x184, 0x0C4, 0x094, // U-*
0x0A8, 0x0A2, 0x08A, 0x02A // $-%
};
private static final int ASTERISK_ENCODING = CHARACTER_ENCODINGS[39];
private final boolean usingCheckDigit;
/**
* Creates a reader that assumes all encoded data is data, and does not treat the final
* character as a check digit.
*/
public Code39Reader() {
usingCheckDigit = false;
}
/**
* Creates a reader that can be configured to check the last character as a check digit.
*
* @param usingCheckDigit if true, treat the last data character as a check digit, not
* data, and verify that the checksum passes
*/
public Code39Reader(boolean usingCheckDigit) {
this.usingCheckDigit = usingCheckDigit;
}
public Result decodeRow(final int rowNumber, final BitArray row) throws ReaderException {
int[] start = findAsteriskPattern(row);
int nextStart = start[1];
int end = row.getSize();
// Read off white space
while (nextStart < end && !row.get(nextStart)) {
nextStart++;
}
StringBuffer result = new StringBuffer();
int[] counters = new int[9];
char decodedChar;
int lastStart;
do {
recordPattern(row, nextStart, counters);
int pattern = toNarrowWidePattern(counters);
decodedChar = patternToChar(pattern);
result.append(decodedChar);
lastStart = nextStart;
for (int i = 0; i < counters.length; i++) {
nextStart += counters[i];
}
// Read off white space
while (nextStart < end && !row.get(nextStart)) {
nextStart++;
}
} while (decodedChar != '*');
result.deleteCharAt(result.length() - 1); // remove asterisk
if (usingCheckDigit) {
int max = result.length() - 1;
int total = 0;
for (int i = 0; i < max; i++) {
total += ALPHABET_STRING.indexOf(result.charAt(i));
}
if (total % 43 != ALPHABET_STRING.indexOf(result.charAt(max))) {
throw new ReaderException("Checksum failed");
}
result.deleteCharAt(max);
}
String resultString = result.toString();
return new Result(resultString,
new ResultPoint[]{new GenericResultPoint((float) (start[1] - start[0]) / 2.0f, (float) rowNumber),
new GenericResultPoint((float) (nextStart - lastStart) / 2.0f, (float) rowNumber)});
}
private static int[] findAsteriskPattern(BitArray row) throws ReaderException {
int width = row.getSize();
int rowOffset = 0;
while (rowOffset < width) {
if (row.get(rowOffset)) {
break;
}
rowOffset++;
}
int counterPosition = 0;
int[] counters = new int[9];
int patternStart = rowOffset;
boolean isWhite = false;
int patternLength = counters.length;
for (int i = rowOffset; i < width; i++) {
boolean pixel = row.get(i);
if ((!pixel && isWhite) || (pixel && !isWhite)) {
counters[counterPosition]++;
} else {
if (counterPosition == patternLength - 1) {
try {
if (toNarrowWidePattern(counters) == ASTERISK_ENCODING) {
return new int[] {patternStart, i};
}
} catch (ReaderException re) {
// no match, continue
}
patternStart += counters[0] + counters[1];
for (int y = 2; y < patternLength; y++) {
counters[y - 2] = counters[y];
}
counterPosition--;
} else {
counterPosition++;
}
counters[counterPosition] = 1;
isWhite = !isWhite;
}
}
throw new ReaderException("Can't find pattern");
}
private static int toNarrowWidePattern(int[] counters) throws ReaderException {
int minCounter = Integer.MAX_VALUE;
for (int i = 0; i < counters.length; i++) {
if (counters[i] < minCounter) {
minCounter = counters[i];
}
}
int maxNarrowCounter = (int) (minCounter * 1.5f);
int wideCounters = 0;
int pattern = 0;
for (int i = 0; i < counters.length; i++) {
if (counters[i] > maxNarrowCounter) {
pattern |= 1 << (counters.length - 1 - i);
wideCounters++;
}
}
if (wideCounters != 3) {
throw new ReaderException("Can't find 3 wide bars/spaces out of 9");
}
return pattern;
}
private static char patternToChar(int pattern) throws ReaderException {
for (int i = 0; i < CHARACTER_ENCODINGS.length; i++) {
if (CHARACTER_ENCODINGS[i] == pattern) {
return ALPHABET[i];
}
}
throw new ReaderException("Pattern did not match character encoding");
}
}

View file

@ -0,0 +1,123 @@
/*
* Copyright 2008 Google Inc.
*
* 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.ReaderException;
import com.google.zxing.common.BitArray;
/**
* <p>Implements decoding of the EAN-13 format.</p>
*
* @author dswitkin@google.com (Daniel Switkin)
* @author srowen@google.com (Sean Owen)
* @author alasdair@google.com (Alasdair Mackintosh)
*/
public final class EAN13Reader extends AbstractUPCEANReader {
// 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,
// if the barcode is 5 123456 789012 then the value of the first digit is
// signified by using odd for '1', even for '2', even for '3', odd for '4',
// odd for '5', and even for '6'. See http://en.wikipedia.org/wiki/EAN-13
//
// Parity of next 6 digits
// Digit 0 1 2 3 4 5
// 0 Odd Odd Odd Odd Odd Odd
// 1 Odd Odd Even Odd Even Even
// 2 Odd Odd Even Even Odd Even
// 3 Odd Odd Even Even Even Odd
// 4 Odd Even Odd Odd Even Even
// 5 Odd Even Even Odd Odd Even
// 6 Odd Even Even Even Odd Odd
// 7 Odd Even Odd Even Odd Even
// 8 Odd Even Odd Even Even Odd
// 9 Odd Even Even Odd Even Odd
//
// Note that the encoding for '0' uses the same parity as a UPC barcode. Hence
// a UPC barcode can be converted to an EAN-13 barcode by prepending a 0.
//
// The encodong is represented by the following array, which is a bit pattern
// using Odd = 0 and Even = 1. For example, 5 is represented by:
//
// Odd Even Even Odd Odd Even
// in binary:
// 0 1 1 0 0 1 == 0x19
//
private static final int[] FIRST_DIGIT_ENCODINGS = {
0x00, 0x0B, 0x0D, 0xE, 0x13, 0x19, 0x1C, 0x15, 0x16, 0x1A
};
protected int decodeMiddle(BitArray row, int[] startRange, StringBuffer resultString) throws ReaderException {
int[] counters = new int[4];
int end = row.getSize();
int rowOffset = startRange[1];
int lgPatternFound = 0;
for (int x = 0; x < 6 && rowOffset < end; x++) {
int bestMatch = decodeDigit(row, counters, rowOffset, L_AND_G_PATTERNS);
resultString.append((char) ('0' + bestMatch % 10));
for (int i = 0; i < counters.length; i++) {
rowOffset += counters[i];
}
if (bestMatch >= 10) {
lgPatternFound |= 1 << (5 - x);
}
}
determineFirstDigit(resultString, lgPatternFound);
int[] middleRange = findGuardPattern(row, rowOffset, true, MIDDLE_PATTERN);
rowOffset = middleRange[1];
for (int x = 0; x < 6 && rowOffset < end; x++) {
int bestMatch = decodeDigit(row, counters, rowOffset, L_PATTERNS);
resultString.append((char) ('0' + bestMatch));
for (int i = 0; i < counters.length; i++) {
rowOffset += counters[i];
}
}
return rowOffset;
}
/**
* Based on pattern of odd-even ('L' and 'G') patterns used to encoded the explicitly-encoded digits
* in a barcode, determines the implicitly encoded first digit and adds it to the result string.
*
* @param resultString string to insert decoded first digit into
* @param lgPatternFound int whose bits indicates the pattern of odd/even L/G patterns used to
* encode digits
* @throws ReaderException if first digit cannot be determined
*/
private static void determineFirstDigit(StringBuffer resultString, int lgPatternFound) throws ReaderException {
for (int d = 0; d < 10; d++) {
if (lgPatternFound == FIRST_DIGIT_ENCODINGS[d]) {
// OK, if the first digit is a 0, then this is effectively also a UPC-A code.
// I think it's best (?) to go ahead and treat it as if it had matched as UPC-A, and return a result
// *without* the leading 0
if (d != 0) {
resultString.insert(0, (char) ('0' + d));
}
return;
}
}
throw new ReaderException("Unable to determine first digit");
}
}

View file

@ -0,0 +1,57 @@
/*
* Copyright 2008 Google Inc.
*
* 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.ReaderException;
import com.google.zxing.common.BitArray;
/**
* <p>Implements decoding of the EAN-8 format.</p>
*
* @author srowen@google.com (Sean Owen)
*/
public final class EAN8Reader extends AbstractUPCEANReader {
protected int decodeMiddle(BitArray row, int[] startRange, StringBuffer result) throws ReaderException {
int[] counters = new int[4];
int end = row.getSize();
int rowOffset = startRange[1];
for (int x = 0; x < 4 && rowOffset < end; x++) {
int bestMatch = decodeDigit(row, counters, rowOffset, L_PATTERNS);
result.append((char) ('0' + bestMatch));
for (int i = 0; i < counters.length; i++) {
rowOffset += counters[i];
}
}
int[] middleRange = findGuardPattern(row, rowOffset, true, MIDDLE_PATTERN);
rowOffset = middleRange[1];
for (int x = 0; x < 4 && rowOffset < end; x++) {
int bestMatch = decodeDigit(row, counters, rowOffset, L_PATTERNS);
result.append((char) ('0' + bestMatch));
for (int i = 0; i < counters.length; i++) {
rowOffset += counters[i];
}
}
return rowOffset;
}
}

View file

@ -0,0 +1,45 @@
/*
* Copyright 2008 Google Inc.
*
* 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.ReaderException;
import com.google.zxing.Result;
import com.google.zxing.common.BitArray;
/**
* @author dswitkin@google.com (Daniel Switkin)
* @author srowen@google.com (Sean Owen)
*/
public final class MultiFormatOneDReader extends AbstractOneDReader {
private final OneDReader[] readers = new OneDReader[]{
new MultiFormatUPCEANReader(), new Code39Reader(), new Code128Reader()
};
public Result decodeRow(int rowNumber, BitArray row) throws ReaderException {
ReaderException saved = null;
for (int i = 0; i < readers.length; i++) {
try {
return readers[i].decodeRow(rowNumber, row);
} catch (ReaderException re) {
saved = re;
}
}
throw saved;
}
}

View file

@ -0,0 +1,65 @@
/*
* Copyright 2008 Google Inc.
*
* 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.ReaderException;
import com.google.zxing.Result;
import com.google.zxing.common.BitArray;
/**
* <p>A reader that can read all available UPC/EAN formats. If a caller wants to try to
* read all such formats, it is most efficent to use this implementation rather than invoke
* individual readers.</p>
*
* @author srowen@google.com (Sean Owen)
*/
public final class MultiFormatUPCEANReader extends AbstractOneDReader {
/**
* Reader implementations to which this implementation delegates, in the order
* they will be attempted. Order is important.
*/
private final UPCEANReader[] readers = new UPCEANReader[] {
new EAN13Reader(), new UPCAReader(), new EAN8Reader(), new UPCEReader()
};
public Result decodeRow(int rowNumber, BitArray row) throws ReaderException {
int[] startGuardPattern = AbstractUPCEANReader.findStartGuardPattern(row);
return decodeRow(rowNumber, row, startGuardPattern);
}
/**
*
* @param rowNumber
* @param row
* @param startGuardRange
* @return
* @throws ReaderException
*/
public Result decodeRow(int rowNumber, BitArray row, int[] startGuardRange) throws ReaderException {
ReaderException saved = null;
for (int i = 0; i < readers.length; i++) {
try {
return readers[i].decodeRow(rowNumber, row, startGuardRange);
} catch (ReaderException re) {
saved = re;
}
}
throw saved;
}
}

View file

@ -0,0 +1,43 @@
/*
* Copyright 2008 Google Inc.
*
* 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.Reader;
import com.google.zxing.ReaderException;
import com.google.zxing.Result;
import com.google.zxing.common.BitArray;
/**
* <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>
*
* @author srowen@google.com (Sean Owen)
*/
public interface OneDReader extends Reader {
/**
* <p>Attempts to decode a one-dimensional barcode format given a single row of
* an image.</p>
*
* @param rowNumber row number from top of the row
* @param row the black/white pixel data of the row
* @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) throws ReaderException;
}

View file

@ -0,0 +1,57 @@
/*
* Copyright 2008 Google Inc.
*
* 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.ReaderException;
import com.google.zxing.common.BitArray;
/**
* <p>Implements decoding of the UPC-A format.</p>
*
* @author dswitkin@google.com (Daniel Switkin)
* @author srowen@google.com (Sean Owen)
*/
public final class UPCAReader extends AbstractUPCEANReader {
protected int decodeMiddle(BitArray row, int[] startRange, StringBuffer resultString) throws ReaderException {
int middleStart = decodeDigits(row, startRange[1], resultString);
int[] middleRange = findGuardPattern(row, middleStart, true, MIDDLE_PATTERN);
return decodeDigits(row, middleRange[1], resultString);
}
/**
* @param row row of black/white values to decode
* @param start horizontal offset from which decoding starts
* @param result {@link StringBuffer} to append decoded digits to
* @return horizontal offset of first pixel after the six decoded digits
* @throws ReaderException if six digits could not be decoded from the row
*/
private static int decodeDigits(BitArray row, int start, StringBuffer result) throws ReaderException {
int[] counters = new int[4];
int end = row.getSize();
int rowOffset = start;
for (int x = 0; x < 6 && rowOffset < end; x++) {
int bestMatch = decodeDigit(row, counters, rowOffset, L_PATTERNS);
result.append((char) ('0' + bestMatch));
for (int i = 0; i < counters.length; i++) {
rowOffset += counters[i];
}
}
return rowOffset;
}
}

View file

@ -0,0 +1,38 @@
/*
* Copyright 2008 Google Inc.
*
* 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.ReaderException;
import com.google.zxing.Result;
import com.google.zxing.common.BitArray;
/**
* <p>This interfaces captures addtional functionality that readers of
* UPC/EAN family of barcodes should expose.</p>
*
* @author srowen@google.com (Sean Owen)
*/
public interface UPCEANReader extends OneDReader {
/**
* <p>Like {@link #decodeRow(int, com.google.zxing.common.BitArray)}, 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) throws ReaderException;
}

View file

@ -0,0 +1,136 @@
/*
* Copyright 2008 Google Inc.
*
* 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.ReaderException;
import com.google.zxing.common.BitArray;
/**
* <p>Implements decoding of the UPC-E format.</p>
* <p/>
* <p><a href="http://www.barcodeisland.com/upce.phtml">This</a> is a great reference for
* UPC-E information.</p>
*
* @author srowen@google.com (Sean Owen)
*/
public final class UPCEReader extends AbstractUPCEANReader {
/**
* The pattern that marks the middle, and end, of a UPC-E pattern.
* There is no "second half" to a UPC-E barcode.
*/
private static final int[] MIDDLE_END_PATTERN = {1, 1, 1, 1, 1, 1};
/**
* See {@link #L_AND_G_PATTERNS}; these values similarly represent patterns of
* even-odd parity encodings of digits that imply both the number system (0 or 1)
* used, and the check digit.
*/
private static final int[][] NUMSYS_AND_CHECK_DIGIT_PATTERNS = {
{0x38, 0x34, 0x32, 0x31, 0x2C, 0x26, 0x23, 0x2A, 0x29, 0x25},
{0x07, 0x0B, 0x0D, 0x0E, 0x13, 0x19, 0x1C, 0x15, 0x16, 0x1A}
};
protected int decodeMiddle(BitArray row, int[] startRange, StringBuffer result) throws ReaderException {
int[] counters = new int[4];
int end = row.getSize();
int rowOffset = startRange[1];
int lgPatternFound = 0;
for (int x = 0; x < 6 && rowOffset < end; x++) {
int bestMatch = decodeDigit(row, counters, rowOffset, L_AND_G_PATTERNS);
result.append((char) ('0' + bestMatch % 10));
for (int i = 0; i < counters.length; i++) {
rowOffset += counters[i];
}
if (bestMatch >= 10) {
lgPatternFound |= 1 << (5 - x);
}
}
determineNumSysAndCheckDigit(result, lgPatternFound);
return rowOffset;
}
protected int[] decodeEnd(BitArray row, int endStart) throws ReaderException {
return findGuardPattern(row, endStart, true, MIDDLE_END_PATTERN);
}
protected boolean checkChecksum(String s) throws ReaderException {
return super.checkChecksum(convertUPCEtoUPCA(s));
}
private static void determineNumSysAndCheckDigit(StringBuffer resultString, int lgPatternFound)
throws ReaderException {
for (int numSys = 0; numSys <= 1; numSys++) {
for (int d = 0; d < 10; d++) {
if (lgPatternFound == NUMSYS_AND_CHECK_DIGIT_PATTERNS[numSys][d]) {
resultString.insert(0, (char) ('0' + numSys));
resultString.append((char) ('0' + d));
return;
}
}
}
throw new ReaderException("Unable to determine number system and check digit");
}
/**
* Expands a UPC-E value back into its full, equivalent UPC-A code value.
*
* @param upce UPC-E code as string of digits
* @return equivalent UPC-A code as string of digits
*/
private static String convertUPCEtoUPCA(String upce) {
char[] upceChars = new char[6];
upce.getChars(1, 7, upceChars, 0);
StringBuffer result = new StringBuffer(12);
result.append(upce.charAt(0));
char lastChar = upceChars[5];
switch (lastChar) {
case '0':
case '1':
case '2':
result.append(upceChars, 0, 2);
result.append(lastChar);
result.append("0000");
result.append(upceChars, 2, 3);
break;
case '3':
result.append(upceChars, 0, 3);
result.append("00000");
result.append(upceChars, 3, 2);
break;
case '4':
result.append(upceChars, 0, 4);
result.append("00000");
result.append(upceChars[4]);
break;
default:
result.append(upceChars, 0, 5);
result.append("0000");
result.append(lastChar);
break;
}
result.append(upce.charAt(7));
return result.toString();
}
}

View file

@ -1,449 +0,0 @@
/*
* Copyright 2007 Google Inc.
*
* 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.upc;
import com.google.zxing.BlackPointEstimationMethod;
import com.google.zxing.MonochromeBitmapSource;
import com.google.zxing.ResultPoint;
import com.google.zxing.common.BitArray;
/**
* This class takes a bitmap, and attempts to return a String which is the contents of the UPC
* barcode in the image. It should be scale-invariant, but does not make any corrections for
* rotation or skew.
*
* @author dswitkin@google.com (Daniel Switkin)
*/
final class UPCDecoder {
private static final byte[] START_END_PATTERN = { 1, 1, 1 };
private static final byte[] MIDDLE_PATTERN = { 1, 1, 1, 1, 1 };
private static final byte[][] DIGIT_PATTERNS = {
{ 30, 20, 10, 10 }, // 0
{ 20, 20, 20, 10 }, // 1
{ 20, 10, 20, 20 }, // 2
{ 10, 40, 10, 10 }, // 3
{ 10, 10, 30, 20 }, // 4
{ 10, 20, 30, 10 }, // 5
{ 10, 10, 10, 40 }, // 6
{ 10, 30, 10, 20 }, // 7
{ 10, 20, 10, 30 }, // 8
{ 30, 10, 10, 20 } // 9
};
/** Alternative even-parity patterns for EAN-13 barcodes. */
private static final byte[][] EVEN_PARITY_PATTERNS = {
{ 10, 10, 20, 30 }, // 0
{ 10, 20, 20, 20 }, // 1
{ 20, 20, 10, 20 }, // 2
{ 10, 10, 40, 10 }, // 3
{ 20, 30, 10, 10 }, // 4
{ 10, 30, 20, 10 }, // 5
{ 40, 10, 10, 10 }, // 6
{ 20, 10, 30, 10 }, // 7
{ 30, 10, 20, 10 }, // 8
{ 20, 10, 10, 30 } // 9
};
// 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,
// if the barcode is 5 123456 789012 then the value of the first digit is
// signified by using odd for '1', even for '2', even for '3', odd for '4',
// odd for '5', and even for '6'. See http://en.wikipedia.org/wiki/EAN-13
//
// Parity of next 6 digits
// Digit 0 1 2 3 4 5
// 0 Odd Odd Odd Odd Odd Odd
// 1 Odd Odd Even Odd Even Even
// 2 Odd Odd Even Even Odd Even
// 3 Odd Odd Even Even Even Odd
// 4 Odd Even Odd Odd Even Even
// 5 Odd Even Even Odd Odd Even
// 6 Odd Even Even Even Odd Odd
// 7 Odd Even Odd Even Odd Even
// 8 Odd Even Odd Even Even Odd
// 9 Odd Even Even Odd Even Odd
//
// Note that the encoding for '0' uses the same parity as a UPC barcode. Hence
// a UPC barcode can be converted to an EAN-13 barcode by prepending a 0.
//
// The encodong is represented by the following array, which is a bit pattern
// using Odd = 0 and Even = 1. For example, 5 is represented by:
//
// Odd Even Even Odd Odd Even
// in binary:
// 0 1 1 0 0 1 == 0x19
//
private static final byte[] FIRST_DIGIT_ENCODINGS = {
0x00, 0x0B, 0x0D, 0xE, 0x13, 0x19, 0x1C, 0x15, 0x16, 0x1A
};
// Parity types indicating how a given digit is encoded
private static final int UNKNOWN_PARITY = 0;
private static final int ODD_PARITY = 1;
private static final int EVEN_PARITY = 2;
/**
* Utility class for returning a matched character. Defines the character
* plus the parity used for encoding it.
*/
private static class CharResult {
public char character; // the encoded character
public int parity; // one of the parity types above
}
private static final int TOLERANCE = 5;
private static final UPCPoint[] NO_POINTS = new UPCPoint[0];
private MonochromeBitmapSource bitmap;
private int width;
private int height;
private StringBuffer result;
private UPCPoint[] points;
UPCDecoder(MonochromeBitmapSource bitmap) {
this.bitmap = bitmap;
width = bitmap.getWidth();
height = bitmap.getHeight();
points = NO_POINTS;
}
/**
* To decode the image, we attempt to decode rows across from the middle outwards.For each row, we scan
* left to right, and if that fails, we reverse the row in place and try again to see if the
* bar code was upside down.
*/
public String decode() {
BitArray rowData = new BitArray(width);
//String longestResult = "";
boolean found = false;
// 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/32 of the image is pretty good.
int middle = height >> 1;
int rowStep = Math.max(1, height >> 5);
for (int x = 0; x < 11; x++) {
int rowStepsAboveOrBelow = (x + 1) >> 1;
boolean isAbove = (x & 0x01) == 0; // i.e. is x even?
int row = middle + rowStep * (isAbove ? rowStepsAboveOrBelow : -rowStepsAboveOrBelow);
bitmap.estimateBlackPoint(BlackPointEstimationMethod.ROW_SAMPLING, row);
bitmap.getBlackRow(row, rowData, 0, width);
if (decodeRow(row, rowData)) {
found = true;
break;
}
//Log("decode: row " + row + " normal result: " + result);
//if (result.length() > longestResult.length()) {
// longestResult = result.toString();
//}
rowData.reverse();
if (decodeRow(row, rowData)) {
found = true;
break;
}
//Log("decode: row " + row + " inverted result: " + result);
//if (result.length() > longestResult.length()) {
// longestResult = result.toString();
//}
}
if (found) {
return result.toString();
} else {
return "";
}
}
public ResultPoint[] getPoints() {
return points;
}
/**
* UPC-A bar codes are made up of a left marker, six digits, a middle marker, six more digits,
* and an end marker, reading from left to right. For more information, see:
* <a href="http://en.wikipedia.org/wiki/Universal_Product_Code">
* http://en.wikipedia.org/wiki/Universal_Product_Code</a>
*/
private boolean decodeRow(int row, BitArray rowData) {
// TODO: Add support for UPC-E Zero Compressed bar codes.
// FIXME: Don't trust the first result from findPattern() for the start sequence - resume from
// that spot and try to start again if finding digits fails.
result = new StringBuffer();
int rowOffset = findPattern(rowData, 0, START_END_PATTERN, false);
if (rowOffset < 0) {
return false;
}
int startOffset = rowOffset;
//Log("Start pattern ends at column " + rowOffset);
rowOffset = decodeOneSide(rowData, rowOffset, true);
if (rowOffset < 0) {
return false;
}
rowOffset = findPattern(rowData, rowOffset, MIDDLE_PATTERN, true);
if (rowOffset < 0) {
return false;
}
//Log("Middle pattern ends at column " + rowOffset);
// Pass in false for checkBothParities(). For an EAN-13 barcode, only the
// left had side will use mixed parities.
rowOffset = decodeOneSide(rowData, rowOffset, false);
if (rowOffset < 0) {
return false;
}
boolean verified = verifyResult();
if (verified) {
points = new UPCPoint[] { new UPCPoint(startOffset, row), new UPCPoint(rowOffset, row) };
}
return verified;
}
/**
* Verifies the checksum. This is computed by adding up digits in the even
* indices (0, 2, 4...) then adding the digits in the odd indices (1, 3, 5..)
* and multiplying by 3. The total, plus the final checksum digit, should be
* divisible by 10.
*
* Note that for a UPC barcode, we add the additional '0' to the front
* (converting it to a EAN-13 code) for purposes of calculating the checksum
*/
private boolean verifyResult() {
// TODO - handle compressed barcodes.
// length is 12 for UPC and 13 for EAN-13
if (result.length() != 12 && result.length() != 13) {
return false;
}
int checksum = 0;
int end = result.length() - 2;
int factor = 3;
// Calculate from penultimate digit down to first. This avoids having to
// account for the optional '0' on the front, which won't actually affect
// the calculation.
for (int i = end; i >= 0; i--) {
int value = (result.charAt(i) - (int)'0') * factor;
checksum += value;
factor = factor == 3 ? 1 : 3;
}
int endValue = (result.charAt(end + 1) - (int)'0');
//Log("checksum + endValue = " + (checksum + endValue));
return (checksum + endValue) % 10 == 0;
}
private int decodeOneSide(BitArray rowData, int rowOffset, boolean checkBothParities) {
int[] counters = new int[4];
byte firstDigitPattern = 0;
CharResult foundChar = new CharResult();
for (int x = 0; x < 6 && rowOffset < width; x++) {
recordPattern(rowData, rowOffset, counters, 4);
for (int y = 0; y < 4; y++) {
rowOffset += counters[y];
}
findDigit(counters, foundChar, checkBothParities);
if (foundChar.parity == UNKNOWN_PARITY) {
return -1;
}
if (foundChar.parity == EVEN_PARITY) {
firstDigitPattern |= 1 << (5 - x);
}
result.append(foundChar.character);
}
// If checkBothParities is true then we're potentially looking at the left
// hand side of an EAN-13 barcode, where the first digit is encoded by the
// parity pattern. In that case, calculate the first digit by checking
// the parity patterns.
if (checkBothParities) {
char firstDigit = '-';
for (int i = 0; i < FIRST_DIGIT_ENCODINGS.length; i++) {
if (firstDigitPattern == FIRST_DIGIT_ENCODINGS[i]) {
firstDigit = (char)((int)'0' + i);
break;
}
}
if (firstDigit == '-') {
return -1;
}
if (firstDigit != '0') {
result.insert(0, firstDigit);
}
}
return rowOffset;
}
/**
* Returns the horizontal position just after the pattern was found if successful, otherwise
* returns -1 if the pattern was not found. Searches are always left to right, and patterns
* begin on white or black based on the flag.
*/
private int findPattern(BitArray rowData, int rowOffset, byte[] pattern, boolean whiteFirst) {
int patternLength = pattern.length;
int[] counters = new int[patternLength];
int width = this.width;
boolean isWhite = false;
while (rowOffset < width) {
isWhite = !rowData.get(rowOffset);
if (whiteFirst == isWhite) {
break;
}
rowOffset++;
}
int counterPosition = 0;
for (int x = rowOffset; x < width; x++) {
boolean pixel = rowData.get(x);
if ((!pixel && isWhite) || (pixel && !isWhite)) {
counters[counterPosition]++;
} else {
if (counterPosition == patternLength - 1) {
if (doesPatternMatch(counters, pattern)) {
return x;
}
for (int y = 2; y < patternLength; y++) {
counters[y - 2] = counters[y];
}
counterPosition--;
} else {
counterPosition++;
}
counters[counterPosition] = 1;
isWhite = !isWhite;
}
}
return -1;
}
/**
* Records a pattern of alternating white and black pixels, returning an array of how many
* pixels of each color were seen. The pattern begins immediately based on the color of the
* first pixel encountered, so a patternSize of 3 could result in WBW or BWB.
*/
private void recordPattern(BitArray rowData, int rowOffset, int[] counters, int patternSize) {
for (int i = 0; i < counters.length; i++) {
counters[i] = 0;
}
boolean isWhite = !rowData.get(rowOffset);
int counterPosition = 0;
int width = this.width;
for (int x = rowOffset; x < width; x++) {
boolean pixel = rowData.get(x);
if ((!pixel && isWhite) || (pixel && !isWhite)) {
counters[counterPosition]++;
} else {
counterPosition++;
if (counterPosition == patternSize) {
return;
} else {
counters[counterPosition] = 1;
isWhite = !isWhite;
}
}
}
}
/**
* This is an optimized version of doesPatternMatch() which is specific to recognizing digits.
* The average is divided by 7 because there are 7 bits per digit, even though the color only
* alternates four times. kDigitPatterns has been premultiplied by 10 for efficiency. Notice
* that the contents of the counters array are modified to save an extra allocation, so don't
* use these values after returning from this call.
*/
private static void findDigit(int[] counters, CharResult result, boolean checkBothParities) {
result.parity = UNKNOWN_PARITY;
int total = counters[0] + counters[1] + counters[2] + counters[3];
int average = total * 10 / 7;
for (int x = 0; x < 4; x++) {
counters[x] = counters[x] * 100 / average;
}
for (int x = 0; x < 10; x++) {
boolean match = true;
byte[] pattern = DIGIT_PATTERNS[x];
for (int y = 0; y < 4; y++) {
int diff = counters[y] - pattern[y];
if (diff > TOLERANCE || diff < -TOLERANCE) {
match = false;
break;
}
}
if (match) {
result.parity = ODD_PARITY;
result.character = (char)((int)'0' + x);
return;
}
}
// If first pattern didn't match, look for even parity patterns, as used in
// EAN-13 barcodes.
if (checkBothParities) {
for (int x = 0; x < 10; x++) {
boolean match = true;
byte[] pattern = EVEN_PARITY_PATTERNS[x];
for (int y = 0; y < 4; y++) {
int diff = counters[y] - pattern[y];
if (diff > TOLERANCE || diff < -TOLERANCE) {
match = false;
break;
}
}
if (match) {
result.parity = EVEN_PARITY;
result.character = (char)((int)'0' + x);
return;
}
}
}
}
/**
* Finds whether the given set of pixel counters matches the requested pattern. Taking an
* average based on the number of counters offers some robustness when antialiased edges get
* interpreted as the wrong color.
*/
private static boolean doesPatternMatch(int[] counters, byte[] pattern) {
// TODO: Remove the divide for performance.
int total = 0;
int numCounters = counters.length;
for (int x = 0; x < numCounters; x++) {
total += counters[x];
}
int average = total * 10 / counters.length;
for (int x = 0; x < numCounters; x++) {
int scaledCounter = counters[x] * 100 / average;
int scaledPattern = pattern[x] * 10;
if (scaledCounter < scaledPattern - TOLERANCE || scaledCounter > scaledPattern + TOLERANCE) {
return false;
}
}
return true;
}
}

View file

@ -1,53 +0,0 @@
/*
* Copyright 2007 Google Inc.
*
* 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.upc;
import com.google.zxing.MonochromeBitmapSource;
import com.google.zxing.Reader;
import com.google.zxing.ReaderException;
import com.google.zxing.Result;
import java.util.Hashtable;
/**
* A reader which decodes UPC-A barcodes.
*
* @author dswitkin@google.com (Daniel Switkin)
*/
public final class UPCReader implements Reader {
/**
* Locates and decodes a UPC barcode in an image.
*
* @return a String representing the digits found
* @throws ReaderException if a barcode cannot be found or decoded
*/
public Result decode(MonochromeBitmapSource image) throws ReaderException {
return decode(image, null);
}
public Result decode(MonochromeBitmapSource image, Hashtable hints)
throws ReaderException {
UPCDecoder decoder = new UPCDecoder(image);
String result = decoder.decode();
if (result == null || result.length() == 0) {
throw new ReaderException("No UPC barcode found");
}
return new Result(result, decoder.getPoints());
}
}

View file

@ -17,7 +17,6 @@
package com.google.zxing;
import com.google.zxing.common.AbstractBlackBoxTestCase;
import com.google.zxing.upc.UPCReader;
import java.io.File;
@ -27,7 +26,7 @@ import java.io.File;
public final class UPCReaderBlackBox2TestCase extends AbstractBlackBoxTestCase {
public UPCReaderBlackBox2TestCase() {
super(new File("test/data/blackbox/upc2"), new UPCReader(), 0.5);
super(new File("test/data/blackbox/upc2"), new MultiFormatReader(), 0.66);
}
}

View file

@ -16,6 +16,7 @@
package com.google.zxing.qrcode;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.common.AbstractBlackBoxTestCase;
import java.io.File;
@ -26,7 +27,7 @@ import java.io.File;
public final class QRCodeReaderBlackBoxTestCase extends AbstractBlackBoxTestCase {
public QRCodeReaderBlackBoxTestCase() {
super(new File("test/data/blackbox/qrcode1"), new QRCodeReader(), 0.5);
super(new File("test/data/blackbox/qrcode1"), new MultiFormatReader(), 0.5);
}
}

View file

@ -16,6 +16,7 @@
package com.google.zxing.qrcode;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.common.AbstractBlackBoxTestCase;
import java.io.File;
@ -26,7 +27,7 @@ import java.io.File;
public final class QRCodeReaderExternalTestCase extends AbstractBlackBoxTestCase {
public QRCodeReaderExternalTestCase() {
super(new File("test/data/blackbox/qrcode2"), new QRCodeReader(), 1.0);
super(new File("test/data/blackbox/qrcode2"), new MultiFormatReader(), 1.0);
}
}

View file

@ -16,6 +16,7 @@
package com.google.zxing.upc;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.common.AbstractBlackBoxTestCase;
import java.io.File;
@ -26,7 +27,7 @@ import java.io.File;
public final class UPCReaderBlackBoxTestCase extends AbstractBlackBoxTestCase {
public UPCReaderBlackBoxTestCase() {
super(new File("test/data/blackbox/upc1"), new UPCReader(), 0.5);
super(new File("test/data/blackbox/upc1"), new MultiFormatReader(), 0.5);
}
}

View file

@ -80,6 +80,7 @@ public final class LCDUIImageMonochromeBitmapSource implements MonochromeBitmapS
public void estimateBlackPoint(BlackPointEstimationMethod method, int argument) {
if (!method.equals(lastMethod) || argument != lastArgument) {
int[] histogram = new int[LUMINANCE_BUCKETS];
float biasTowardsWhite = 1.0f;
if (method.equals(BlackPointEstimationMethod.TWO_D_SAMPLING)) {
int minDimension = width < height ? width : height;
for (int n = 0, offset = 0; n < minDimension; n++, offset += width + 1) {
@ -89,6 +90,7 @@ public final class LCDUIImageMonochromeBitmapSource implements MonochromeBitmapS
if (argument < 0 || argument >= height) {
throw new IllegalArgumentException("Row is not within the image: " + argument);
}
biasTowardsWhite = 2.0f;
int offset = argument * width;
for (int x = 0; x < width; x++) {
histogram[computeRGBLuminance(rgbPixels[offset + x]) >> LUMINANCE_SHIFT]++;
@ -96,7 +98,7 @@ public final class LCDUIImageMonochromeBitmapSource implements MonochromeBitmapS
} else {
throw new IllegalArgumentException("Unknown method: " + method);
}
blackPoint = BlackPointEstimator.estimate(histogram) << LUMINANCE_SHIFT;
blackPoint = BlackPointEstimator.estimate(histogram, biasTowardsWhite) << LUMINANCE_SHIFT;
lastMethod = method;
lastArgument = argument;
}

View file

@ -80,6 +80,7 @@ public final class BufferedImageMonochromeBitmapSource implements MonochromeBitm
int width = image.getWidth();
int height = image.getHeight();
int[] histogram = new int[LUMINANCE_BUCKETS];
float biasTowardsWhite = 1.0f;
if (method.equals(BlackPointEstimationMethod.TWO_D_SAMPLING)) {
int minDimension = width < height ? width : height;
int startI = height == minDimension ? 0 : (height - width) >> 1;
@ -92,6 +93,7 @@ public final class BufferedImageMonochromeBitmapSource implements MonochromeBitm
if (argument < 0 || argument >= height) {
throw new IllegalArgumentException("Row is not within the image: " + argument);
}
biasTowardsWhite = 2.0f;
int[] rgbArray = new int[width];
image.getRGB(0, argument, width, 1, rgbArray, 0, width);
for (int x = 0; x < width; x++) {
@ -100,7 +102,7 @@ public final class BufferedImageMonochromeBitmapSource implements MonochromeBitm
} else {
throw new IllegalArgumentException("Unknown method: " + method);
}
blackPoint = BlackPointEstimator.estimate(histogram) << LUMINANCE_SHIFT;
blackPoint = BlackPointEstimator.estimate(histogram, biasTowardsWhite) << LUMINANCE_SHIFT;
lastMethod = method;
lastArgument = argument;
}