2 * Copyright 2007 Google Inc.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.google.zxing.upc;
19 import com.google.zxing.BlackPointEstimationMethod;
20 import com.google.zxing.common.BitArray;
21 import com.google.zxing.MonochromeBitmapSource;
24 * This class takes a bitmap, and attempts to return a String which is the contents of the UPC
25 * barcode in the image. It should be scale-invariant, but does not make any corrections for
28 * @author dswitkin@google.com (Daniel Switkin)
30 final class UPCDecoder {
32 private static final byte[] BITMAP_SEARCH_PATTERN = { 50, 49, 51, 48, 52, 46, 54, 43, 57, 40, 60 };
33 private static final byte[] START_END_PATTERN = { 1, 1, 1 };
34 private static final byte[] MIDDLE_PATTERN = { 1, 1, 1, 1, 1 };
35 private static final byte[][] DIGIT_PATTERNS = {
36 { 30, 20, 10, 10 }, // 0
37 { 20, 20, 20, 10 }, // 1
38 { 20, 10, 20, 20 }, // 2
39 { 10, 40, 10, 10 }, // 3
40 { 10, 10, 30, 20 }, // 4
41 { 10, 20, 30, 10 }, // 5
42 { 10, 10, 10, 40 }, // 6
43 { 10, 30, 10, 20 }, // 7
44 { 10, 20, 10, 30 }, // 8
45 { 30, 10, 10, 20 } // 9
47 private static final int TOLERANCE = 5;
49 private MonochromeBitmapSource bitmap;
52 private StringBuffer result;
54 UPCDecoder(MonochromeBitmapSource bitmap) {
56 width = bitmap.getWidth();
57 height = bitmap.getHeight();
60 // To decode the image, we follow a search pattern defined in kBitmapSearchPattern. It is a
61 // list of percentages which translate to row numbers to scan across. For each row, we scan
62 // left to right, and if that fails, we reverse the row in place and try again to see if the
63 // bar code was upside down.
65 BitArray rowData = new BitArray(width);
66 String longestResult = "";
68 for (int x = 0; x < BITMAP_SEARCH_PATTERN.length; x++) {
69 int row = height * BITMAP_SEARCH_PATTERN[x] / 100;
70 bitmap.estimateBlackPoint(BlackPointEstimationMethod.ROW_SAMPLING, row);
71 bitmap.getBlackRow(row, rowData, 0, width);
73 if (decodeRow(rowData)) {
77 //Log("decode: row " + row + " normal result: " + mResult);
78 if (result.length() > longestResult.length()) {
79 longestResult = result.toString();
83 if (decodeRow(rowData)) {
87 //Log("decode: row " + row + " inverted result: " + mResult);
88 if (result.length() > longestResult.length()) {
89 longestResult = result.toString();
94 return result.toString();
101 * UPC-A bar codes are made up of a left marker, six digits, a middle marker, six more digits,
102 * and an end marker, reading from left to right. For more information, see:
103 * <a href="http://en.wikipedia.org/wiki/Universal_Product_Code">
104 * http://en.wikipedia.org/wiki/Universal_Product_Code</a>
106 private boolean decodeRow(BitArray rowData) {
107 // TODO: Add support for UPC-E Zero Compressed bar codes.
108 // TODO: Add support for EAN-13 (European Article Number) bar codes.
109 // FIXME: Don't trust the first result from findPattern() for the start sequence - resume from
110 // that spot and try to start again if finding digits fails.
111 result = new StringBuffer();
112 int rowOffset = findPattern(rowData, 0, START_END_PATTERN, false);
116 //Log("Start pattern ends at column " + rowOffset);
118 rowOffset = decodeOneSide(rowData, rowOffset);
123 rowOffset = findPattern(rowData, rowOffset, MIDDLE_PATTERN, true);
127 //Log("Middle pattern ends at column " + rowOffset);
129 rowOffset = decodeOneSide(rowData, rowOffset);
134 // We could attempt to read the end pattern for sanity, but there's not much point.
135 // UPC-A codes have 12 digits, so any other result is garbage.
136 return result.length() == 12;
139 private int decodeOneSide(BitArray rowData, int rowOffset) {
140 int[] counters = new int[4];
141 for (int x = 0; x < 6 && rowOffset < width; x++) {
142 recordPattern(rowData, rowOffset, counters, 4);
143 for (int y = 0; y < 4; y++) {
144 rowOffset += counters[y];
146 char c = findDigit(counters);
156 // Returns the horizontal position just after the pattern was found if successful, otherwise
157 // returns -1 if the pattern was not found. Searches are always left to right, and patterns
158 // begin on white or black based on the flag.
159 private int findPattern(BitArray rowData, int rowOffset, byte[] pattern, boolean whiteFirst) {
160 int[] counters = new int[pattern.length];
161 int width = this.width;
162 boolean isWhite = false;
163 while (rowOffset < width) {
164 isWhite = !rowData.get(rowOffset);
165 if (whiteFirst == isWhite) {
171 int counterPosition = 0;
172 for (int x = rowOffset; x < width; x++) {
173 boolean pixel = rowData.get(x);
174 if ((!pixel && isWhite) || (pixel && !isWhite)) {
175 counters[counterPosition]++;
177 if (counterPosition == pattern.length - 1) {
178 if (doesPatternMatch(counters, pattern)) {
181 for (int y = 2; y < pattern.length; y++) {
182 counters[y - 2] = counters[y];
188 counters[counterPosition] = 1;
196 * Records a pattern of alternating white and black pixels, returning an array of how many
197 * pixels of each color were seen. The pattern begins immediately based on the color of the
198 * first pixel encountered, so a patternSize of 3 could result in WBW or BWB.
200 private void recordPattern(BitArray rowData, int rowOffset, int[] counters, int patternSize) {
201 for (int i = 0; i < counters.length; i++) {
204 boolean isWhite = !rowData.get(rowOffset);
206 int counterPosition = 0;
207 int width = this.width;
208 for (int x = rowOffset; x < width; x++) {
209 boolean pixel = rowData.get(x);
210 if ((!pixel && isWhite) || (pixel && !isWhite)) {
211 counters[counterPosition]++;
214 if (counterPosition == patternSize) {
217 counters[counterPosition] = 1;
225 * This is an optimized version of doesPatternMatch() which is specific to recognizing digits.
226 * The average is divided by 7 because there are 7 bits per digit, even though the color only
227 * alternates four times. kDigitPatterns has been premultiplied by 10 for efficiency. Notice
228 * that the contents of the counters array are modified to save an extra allocation, so don't
229 * use these values after returning from this call.
231 private static char findDigit(int[] counters) {
232 // TODO: add EAN even parity support
233 int total = counters[0] + counters[1] + counters[2] + counters[3];
234 int average = total * 10 / 7;
235 for (int x = 0; x < 4; x++) {
236 counters[x] = counters[x] * 100 / average;
239 for (int x = 0; x < 10; x++) {
240 boolean match = true;
241 for (int y = 0; y < 4; y++) {
242 int diff = counters[y] - DIGIT_PATTERNS[x][y];
243 if (diff > TOLERANCE || diff < -TOLERANCE) {
249 return (char) ((int) '0' + x);
256 * Finds whether the given set of pixel counters matches the requested pattern. Taking an
257 * average based on the number of counters offers some robustness when antialiased edges get
258 * interpreted as the wrong color.
260 private static boolean doesPatternMatch(int[] counters, byte[] pattern) {
261 // TODO: Remove the divide for performance.
263 int numCounters = counters.length;
264 for (int x = 0; x < numCounters; x++) {
265 total += counters[x];
267 int average = total * 10 / counters.length;
269 for (int x = 0; x < numCounters; x++) {
270 int scaledCounter = counters[x] * 100 / average;
271 int scaledPattern = pattern[x] * 10;
272 if (scaledCounter < scaledPattern - TOLERANCE || scaledCounter > scaledPattern + TOLERANCE) {