diff --git a/core/src/com/google/zxing/oned/AbstractOneDReader.java b/core/src/com/google/zxing/oned/AbstractOneDReader.java deleted file mode 100644 index bab29e344..000000000 --- a/core/src/com/google/zxing/oned/AbstractOneDReader.java +++ /dev/null @@ -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; - -/** - *
Encapsulates functionality and implementation that is common to all families - * of one-dimensional barcodes.
- * - * @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; - -} diff --git a/core/src/com/google/zxing/oned/AbstractUPCEANReader.java b/core/src/com/google/zxing/oned/AbstractUPCEANReader.java deleted file mode 100644 index ee39d08a1..000000000 --- a/core/src/com/google/zxing/oned/AbstractUPCEANReader.java +++ /dev/null @@ -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; - -/** - *Encapsulates functionality and implementation that is common to UPC and EAN families - * of one-dimensional barcodes.
- * - * @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(); - } - } - -} \ No newline at end of file diff --git a/core/src/com/google/zxing/oned/AbstractUPCEANWriter.java b/core/src/com/google/zxing/oned/AbstractUPCEANWriter.java deleted file mode 100644 index eb33ec2ef..000000000 --- a/core/src/com/google/zxing/oned/AbstractUPCEANWriter.java +++ /dev/null @@ -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; - -/** - *Encapsulates functionality and implementation that is common to UPC and EAN families - * of one-dimensional barcodes.
- * - * @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; - } -} diff --git a/core/src/com/google/zxing/oned/Code128Reader.java b/core/src/com/google/zxing/oned/Code128Reader.java index 66b9e62ec..f39d91f85 100644 --- a/core/src/com/google/zxing/oned/Code128Reader.java +++ b/core/src/com/google/zxing/oned/Code128Reader.java @@ -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}; diff --git a/core/src/com/google/zxing/oned/Code39Reader.java b/core/src/com/google/zxing/oned/Code39Reader.java index b696c3f0f..5cf07ee47 100644 --- a/core/src/com/google/zxing/oned/Code39Reader.java +++ b/core/src/com/google/zxing/oned/Code39Reader.java @@ -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(); diff --git a/core/src/com/google/zxing/oned/EAN13Reader.java b/core/src/com/google/zxing/oned/EAN13Reader.java index d8f8775b7..67d82a85c 100644 --- a/core/src/com/google/zxing/oned/EAN13Reader.java +++ b/core/src/com/google/zxing/oned/EAN13Reader.java @@ -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(); } -} \ No newline at end of file +} diff --git a/core/src/com/google/zxing/oned/EAN13Writer.java b/core/src/com/google/zxing/oned/EAN13Writer.java index 65f58d41b..ff9371d1e 100644 --- a/core/src/com/google/zxing/oned/EAN13Writer.java +++ b/core/src/com/google/zxing/oned/EAN13Writer.java @@ -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; } diff --git a/core/src/com/google/zxing/oned/EAN8Reader.java b/core/src/com/google/zxing/oned/EAN8Reader.java index bb801121e..3e64ea6dc 100644 --- a/core/src/com/google/zxing/oned/EAN8Reader.java +++ b/core/src/com/google/zxing/oned/EAN8Reader.java @@ -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; } -} \ No newline at end of file +} diff --git a/core/src/com/google/zxing/oned/EAN8Writer.java b/core/src/com/google/zxing/oned/EAN8Writer.java index 0aae4a296..b665f5f56 100644 --- a/core/src/com/google/zxing/oned/EAN8Writer.java +++ b/core/src/com/google/zxing/oned/EAN8Writer.java @@ -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; } diff --git a/core/src/com/google/zxing/oned/ITFReader.java b/core/src/com/google/zxing/oned/ITFReader.java index 11a2dda6d..ca0fabf35 100644 --- a/core/src/com/google/zxing/oned/ITFReader.java +++ b/core/src/com/google/zxing/oned/ITFReader.java @@ -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 { } } -} \ No newline at end of file +} diff --git a/core/src/com/google/zxing/oned/MultiFormatOneDReader.java b/core/src/com/google/zxing/oned/MultiFormatOneDReader.java index 14ddb2e7d..33b6edc83 100644 --- a/core/src/com/google/zxing/oned/MultiFormatOneDReader.java +++ b/core/src/com/google/zxing/oned/MultiFormatOneDReader.java @@ -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; diff --git a/core/src/com/google/zxing/oned/MultiFormatUPCEANReader.java b/core/src/com/google/zxing/oned/MultiFormatUPCEANReader.java index 502b086f1..f33c9cdda 100644 --- a/core/src/com/google/zxing/oned/MultiFormatUPCEANReader.java +++ b/core/src/com/google/zxing/oned/MultiFormatUPCEANReader.java @@ -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); diff --git a/core/src/com/google/zxing/oned/OneDReader.java b/core/src/com/google/zxing/oned/OneDReader.java index ae1295725..fba71fd03 100644 --- a/core/src/com/google/zxing/oned/OneDReader.java +++ b/core/src/com/google/zxing/oned/OneDReader.java @@ -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; /** - *{@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.
+ * 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; + } /** *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; -} \ No newline at end of file +} diff --git a/core/src/com/google/zxing/oned/UPCAReader.java b/core/src/com/google/zxing/oned/UPCAReader.java index 5299b033f..9b5e9cbe9 100644 --- a/core/src/com/google/zxing/oned/UPCAReader.java +++ b/core/src/com/google/zxing/oned/UPCAReader.java @@ -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 { } } -} \ No newline at end of file +} diff --git a/core/src/com/google/zxing/oned/UPCEANReader.java b/core/src/com/google/zxing/oned/UPCEANReader.java index db1dcb1c9..be4902778 100644 --- a/core/src/com/google/zxing/oned/UPCEANReader.java +++ b/core/src/com/google/zxing/oned/UPCEANReader.java @@ -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; /** - *
This interfaces captures additional functionality that readers of - * UPC/EAN family of barcodes should expose.
+ *Encapsulates functionality and implementation that is common to UPC and EAN families + * of one-dimensional barcodes.
* + * @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); + } /** *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.
*/ - 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; -} \ No newline at end of file +} diff --git a/core/src/com/google/zxing/oned/UPCEANWriter.java b/core/src/com/google/zxing/oned/UPCEANWriter.java index 4e0759da5..b65c2070f 100644 --- a/core/src/com/google/zxing/oned/UPCEANWriter.java +++ b/core/src/com/google/zxing/oned/UPCEANWriter.java @@ -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 + *Encapsulates functionality and implementation that is common to UPC and EAN families + * of one-dimensional barcodes.
+ * + * @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); } diff --git a/core/src/com/google/zxing/oned/UPCEReader.java b/core/src/com/google/zxing/oned/UPCEReader.java index 1ba1fce25..3dd95a4bd 100644 --- a/core/src/com/google/zxing/oned/UPCEReader.java +++ b/core/src/com/google/zxing/oned/UPCEReader.java @@ -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(); } -} \ No newline at end of file +}