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.common.BitArray;
20 import com.google.zxing.MonochromeBitmapSource;
22 import java.util.Arrays;
25 * This class takes a bitmap, and attempts to return a String which is the contents of the UPC
26 * barcode in the image. It should be scale-invariant, but does not make any corrections for
29 * @author dswitkin@google.com (Daniel Switkin)
31 public class UPCDecoder {
32 UPCDecoder(MonochromeBitmapSource bitmap) {
35 mWidth = bitmap.getWidth();
36 mHeight = bitmap.getHeight();
40 // To decode the image, we follow a search pattern defined in kBitmapSearchPattern. It is a
41 // list of percentages which translate to row numbers to scan across. For each row, we scan
42 // left to right, and if that fails, we reverse the row in place and try again to see if the
43 // bar code was upside down.
44 public String decode() {
45 if (mBitmap == null) return "";
47 BitArray rowData = new BitArray(mWidth);
48 String longestResult = "";
50 for (int x = 0; x < kBitmapSearchPattern.length; x++) {
51 int row = mHeight * kBitmapSearchPattern[x] / 100;
52 mBitmap.getBlackRow(row, rowData, 0, mWidth);
54 if (decodeRow(rowData)) {
58 //Log("decode: row " + row + " normal result: " + mResult);
59 if (mResult.length() > longestResult.length()) {
60 longestResult = mResult;
64 if (decodeRow(rowData)) {
68 //Log("decode: row " + row + " inverted result: " + mResult);
69 if (mResult.length() > longestResult.length()) {
70 longestResult = mResult;
74 if (found >= 0) return mResult;
78 // UPC-A bar codes are made up of a left marker, six digits, a middle marker, six more digits,
79 // and an end marker, reading from left to right. For more information, see:
81 // http://en.wikipedia.org/wiki/Universal_Product_Code
83 // TODO: Add support for UPC-E Zero Compressed bar codes.
84 // TODO: Add support for EAN-13 (European Article Number) bar codes.
85 // FIXME: Don't trust the first result from findPattern() for the start sequence - resume from
86 // that spot and try to start again if finding digits fails.
87 private boolean decodeRow(BitArray rowData) {
89 int rowOffset = findPattern(rowData, 0, kStartEndPattern, false);
90 if (rowOffset < 0) return false;
91 //Log("Start pattern ends at column " + rowOffset);
93 rowOffset = decodeOneSide(rowData, rowOffset);
94 if (rowOffset < 0) return false;
96 rowOffset = findPattern(rowData, rowOffset, kMiddlePattern, true);
97 if (rowOffset < 0) return false;
98 //Log("Middle pattern ends at column " + rowOffset);
100 rowOffset = decodeOneSide(rowData, rowOffset);
101 if (rowOffset < 0) return false;
103 // We could attempt to read the end pattern for sanity, but there's not much point.
104 // UPC-A codes have 12 digits, so any other result is garbage.
105 return (mResult.length() == 12);
108 private int decodeOneSide(BitArray rowData, int rowOffset) {
109 int[] counters = new int[4];
110 for (int x = 0; x < 6 && rowOffset < mWidth; x++) {
111 recordPattern(rowData, rowOffset, counters, 4);
112 for (int y = 0; y < 4; y++) {
113 rowOffset += counters[y];
115 char c = findDigit(counters);
125 // Returns the horizontal position just after the pattern was found if successful, otherwise
126 // returns -1 if the pattern was not found. Searches are always left to right, and patterns
127 // begin on white or black based on the flag.
128 private int findPattern(BitArray rowData, int rowOffset, byte[] pattern, boolean whiteFirst) {
129 int[] counters = new int[pattern.length];
131 boolean isWhite = false;
132 for (; rowOffset < width; rowOffset++) {
133 isWhite = !rowData.get(rowOffset);
134 if (whiteFirst == isWhite) {
139 int counterPosition = 0;
140 for (int x = rowOffset; x < width; x++) {
141 boolean pixel = rowData.get(x);
142 if ((!pixel && isWhite) || (pixel && !isWhite)) {
143 counters[counterPosition]++;
145 if (counterPosition == pattern.length - 1) {
146 if (doesPatternMatch(counters, pattern)) {
149 for (int y = 2; y < pattern.length; y++) {
150 counters[y - 2] = counters[y];
156 counters[counterPosition] = 1;
163 // Records a pattern of alternating white and black pixels, returning an array of how many
164 // pixels of each color were seen. The pattern begins immediately based on the color of the
165 // first pixel encountered, so a patternSize of 3 could result in WBW or BWB.
166 private void recordPattern(BitArray rowData, int rowOffset, int[] counters, int patternSize) {
167 Arrays.fill(counters, 0);
168 boolean isWhite = !rowData.get(rowOffset);
170 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]++;
178 if (counterPosition == patternSize) {
181 counters[counterPosition] = 1;
188 // This is an optimized version of doesPatternMatch() which is specific to recognizing digits.
189 // The average is divided by 7 because there are 7 bits per digit, even though the color only
190 // alternates four times. kDigitPatterns has been premultiplied by 10 for efficiency. Notice
191 // that the contents of the counters array are modified to save an extra allocation, so don't
192 // use these values after returning from this call.
193 // TODO: add EAN even parity support
194 private char findDigit(int[] counters) {
195 int total = counters[0] + counters[1] + counters[2] + counters[3];
196 int average = total * 10 / 7;
197 for (int x = 0; x < 4; x++) {
198 counters[x] = counters[x] * 100 / average;
201 for (int x = 0; x < 10; x++) {
202 boolean match = true;
203 for (int y = 0; y < 4; y++) {
204 int diff = counters[y] - kDigitPatterns[x][y];
205 if (diff > kTolerance || diff < -kTolerance) {
210 if (match) return kDigits[x];
215 // Finds whether the given set of pixel counters matches the requested pattern. Taking an
216 // average based on the number of counters offers some robustness when antialiased edges get
217 // interpreted as the wrong color.
218 // TODO: Remove the divide for performance.
219 private boolean doesPatternMatch(int[] counters, byte[] pattern) {
221 for (int x = 0; x < counters.length; x++) {
222 total += counters[x];
224 int average = total * 10 / counters.length;
226 for (int x = 0; x < counters.length; x++) {
227 int scaledCounter = counters[x] * 100 / average;
228 int scaledPattern = pattern[x] * 10;
229 if (scaledCounter < scaledPattern - kTolerance || scaledCounter > scaledPattern + kTolerance) {
236 private static final byte[] kBitmapSearchPattern = { 50, 49, 51, 48, 52, 46, 54, 43, 57, 40, 60 };
237 private static final byte[] kStartEndPattern = { 1, 1, 1 };
238 private static final byte[] kMiddlePattern = { 1, 1, 1, 1, 1 };
240 private static final byte[][] kDigitPatterns = {
241 { 30, 20, 10, 10 }, // 0
242 { 20, 20, 20, 10 }, // 1
243 { 20, 10, 20, 20 }, // 2
244 { 10, 40, 10, 10 }, // 3
245 { 10, 10, 30, 20 }, // 4
246 { 10, 20, 30, 10 }, // 5
247 { 10, 10, 10, 40 }, // 6
248 { 10, 30, 10, 20 }, // 7
249 { 10, 20, 10, 30 }, // 8
250 { 30, 10, 10, 20 } // 9
253 private static final char[] kDigits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
254 private static final int kTolerance = 5;
256 private MonochromeBitmapSource mBitmap;
259 private String mResult;