6036157f2fd553e303f4438cec64f37017c02309
[zxing.git] / core / src / com / google / zxing / oned / UPCEANReader.java
1 /*
2  * Copyright 2008 ZXing authors
3  *
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
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 package com.google.zxing.oned;
18
19 import com.google.zxing.BarcodeFormat;
20 import com.google.zxing.ChecksumException;
21 import com.google.zxing.DecodeHintType;
22 import com.google.zxing.FormatException;
23 import com.google.zxing.NotFoundException;
24 import com.google.zxing.ReaderException;
25 import com.google.zxing.Result;
26 import com.google.zxing.ResultPoint;
27 import com.google.zxing.ResultPointCallback;
28 import com.google.zxing.common.BitArray;
29
30 import java.util.Hashtable;
31
32 /**
33  * <p>Encapsulates functionality and implementation that is common to UPC and EAN families
34  * of one-dimensional barcodes.</p>
35  *
36  * @author dswitkin@google.com (Daniel Switkin)
37  * @author Sean Owen
38  * @author alasdair@google.com (Alasdair Mackintosh)
39  */
40 public abstract class UPCEANReader extends OneDReader {
41
42   // These two values are critical for determining how permissive the decoding will be.
43   // We've arrived at these values through a lot of trial and error. Setting them any higher
44   // lets false positives creep in quickly.
45   private static final int MAX_AVG_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.42f);
46   private static final int MAX_INDIVIDUAL_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.7f);
47
48   /**
49    * Start/end guard pattern.
50    */
51   static final int[] START_END_PATTERN = {1, 1, 1,};
52
53   /**
54    * Pattern marking the middle of a UPC/EAN pattern, separating the two halves.
55    */
56   static final int[] MIDDLE_PATTERN = {1, 1, 1, 1, 1};
57
58   /**
59    * "Odd", or "L" patterns used to encode UPC/EAN digits.
60    */
61   static final int[][] L_PATTERNS = {
62       {3, 2, 1, 1}, // 0
63       {2, 2, 2, 1}, // 1
64       {2, 1, 2, 2}, // 2
65       {1, 4, 1, 1}, // 3
66       {1, 1, 3, 2}, // 4
67       {1, 2, 3, 1}, // 5
68       {1, 1, 1, 4}, // 6
69       {1, 3, 1, 2}, // 7
70       {1, 2, 1, 3}, // 8
71       {3, 1, 1, 2}  // 9
72   };
73
74   /**
75    * As above but also including the "even", or "G" patterns used to encode UPC/EAN digits.
76    */
77   static final int[][] L_AND_G_PATTERNS;
78
79   static {
80     L_AND_G_PATTERNS = new int[20][];
81     for (int i = 0; i < 10; i++) {
82       L_AND_G_PATTERNS[i] = L_PATTERNS[i];
83     }
84     for (int i = 10; i < 20; i++) {
85       int[] widths = L_PATTERNS[i - 10];
86       int[] reversedWidths = new int[widths.length];
87       for (int j = 0; j < widths.length; j++) {
88         reversedWidths[j] = widths[widths.length - j - 1];
89       }
90       L_AND_G_PATTERNS[i] = reversedWidths;
91     }
92   }
93
94   private final StringBuffer decodeRowStringBuffer;
95   private final UPCEANExtensionSupport extensionReader;
96
97   protected UPCEANReader() {
98     decodeRowStringBuffer = new StringBuffer(20);
99     extensionReader = new UPCEANExtensionSupport();
100   }
101
102   static int[] findStartGuardPattern(BitArray row) throws NotFoundException {
103     boolean foundStart = false;
104     int[] startRange = null;
105     int nextStart = 0;
106     while (!foundStart) {
107       startRange = findGuardPattern(row, nextStart, false, START_END_PATTERN);
108       int start = startRange[0];
109       nextStart = startRange[1];
110       // Make sure there is a quiet zone at least as big as the start pattern before the barcode.
111       // If this check would run off the left edge of the image, do not accept this barcode,
112       // as it is very likely to be a false positive.
113       int quietStart = start - (nextStart - start);
114       if (quietStart >= 0) {
115         foundStart = row.isRange(quietStart, start, false);
116       }
117     }
118     return startRange;
119   }
120
121   public Result decodeRow(int rowNumber, BitArray row, Hashtable hints)
122       throws NotFoundException, ChecksumException, FormatException {
123     return decodeRow(rowNumber, row, findStartGuardPattern(row), hints);
124   }
125
126   /**
127    * <p>Like {@link #decodeRow(int, BitArray, java.util.Hashtable)}, but
128    * allows caller to inform method about where the UPC/EAN start pattern is
129    * found. This allows this to be computed once and reused across many implementations.</p>
130    */
131   public Result decodeRow(int rowNumber, BitArray row, int[] startGuardRange, Hashtable hints)
132       throws NotFoundException, ChecksumException, FormatException {
133
134     ResultPointCallback resultPointCallback = hints == null ? null :
135         (ResultPointCallback) hints.get(DecodeHintType.NEED_RESULT_POINT_CALLBACK);
136
137     if (resultPointCallback != null) {
138       resultPointCallback.foundPossibleResultPoint(new ResultPoint(
139           (startGuardRange[0] + startGuardRange[1]) / 2.0f, rowNumber
140       ));
141     }
142
143     StringBuffer result = decodeRowStringBuffer;
144     result.setLength(0);
145     int endStart = decodeMiddle(row, startGuardRange, result);
146
147     if (resultPointCallback != null) {
148       resultPointCallback.foundPossibleResultPoint(new ResultPoint(
149           endStart, rowNumber
150       ));
151     }
152
153     int[] endRange = decodeEnd(row, endStart);
154
155     if (resultPointCallback != null) {
156       resultPointCallback.foundPossibleResultPoint(new ResultPoint(
157           (endRange[0] + endRange[1]) / 2.0f, rowNumber
158       ));
159     }
160
161
162     // Make sure there is a quiet zone at least as big as the end pattern after the barcode. The
163     // spec might want more whitespace, but in practice this is the maximum we can count on.
164     int end = endRange[1];
165     int quietEnd = end + (end - endRange[0]);
166     if (quietEnd >= row.getSize() || !row.isRange(end, quietEnd, false)) {
167       throw NotFoundException.getNotFoundInstance();
168     }
169
170     String resultString = result.toString();
171     if (!checkChecksum(resultString)) {
172       throw ChecksumException.getChecksumInstance();
173     }
174
175     float left = (float) (startGuardRange[1] + startGuardRange[0]) / 2.0f;
176     float right = (float) (endRange[1] + endRange[0]) / 2.0f;
177     Result decodeResult = new Result(resultString,
178         null, // no natural byte representation for these barcodes
179         new ResultPoint[]{
180             new ResultPoint(left, (float) rowNumber),
181             new ResultPoint(right, (float) rowNumber)},
182         getBarcodeFormat());
183
184     try {
185       Result extensionResult = extensionReader.decodeRow(row, endRange[1]);
186       decodeResult.putAllMetadata(extensionResult.getResultMetadata());
187     } catch (ReaderException re) {
188       // continue
189     }
190     return decodeResult;
191   }
192
193   /**
194    * @return {@link #checkStandardUPCEANChecksum(String)}
195    */
196   boolean checkChecksum(String s) throws ChecksumException, FormatException {
197     return checkStandardUPCEANChecksum(s);
198   }
199
200   /**
201    * Computes the UPC/EAN checksum on a string of digits, and reports
202    * whether the checksum is correct or not.
203    *
204    * @param s string of digits to check
205    * @return true iff string of digits passes the UPC/EAN checksum algorithm
206    * @throws FormatException if the string does not contain only digits
207    */
208   private static boolean checkStandardUPCEANChecksum(String s) throws FormatException {
209     int length = s.length();
210     if (length == 0) {
211       return false;
212     }
213
214     int sum = 0;
215     for (int i = length - 2; i >= 0; i -= 2) {
216       int digit = (int) s.charAt(i) - (int) '0';
217       if (digit < 0 || digit > 9) {
218         throw FormatException.getFormatInstance();
219       }
220       sum += digit;
221     }
222     sum *= 3;
223     for (int i = length - 1; i >= 0; i -= 2) {
224       int digit = (int) s.charAt(i) - (int) '0';
225       if (digit < 0 || digit > 9) {
226         throw FormatException.getFormatInstance();
227       }
228       sum += digit;
229     }
230     return sum % 10 == 0;
231   }
232
233   int[] decodeEnd(BitArray row, int endStart) throws NotFoundException {
234     return findGuardPattern(row, endStart, false, START_END_PATTERN);
235   }
236
237   /**
238    * @param row row of black/white values to search
239    * @param rowOffset position to start search
240    * @param whiteFirst if true, indicates that the pattern specifies white/black/white/...
241    * pixel counts, otherwise, it is interpreted as black/white/black/...
242    * @param pattern pattern of counts of number of black and white pixels that are being
243    * searched for as a pattern
244    * @return start/end horizontal offset of guard pattern, as an array of two ints
245    * @throws NotFoundException if pattern is not found
246    */
247   static int[] findGuardPattern(BitArray row, int rowOffset, boolean whiteFirst, int[] pattern)
248       throws NotFoundException {
249     int patternLength = pattern.length;
250     int[] counters = new int[patternLength];
251     int width = row.getSize();
252     boolean isWhite = false;
253     while (rowOffset < width) {
254       isWhite = !row.get(rowOffset);
255       if (whiteFirst == isWhite) {
256         break;
257       }
258       rowOffset++;
259     }
260
261     int counterPosition = 0;
262     int patternStart = rowOffset;
263     for (int x = rowOffset; x < width; x++) {
264       boolean pixel = row.get(x);
265       if (pixel ^ isWhite) {
266         counters[counterPosition]++;
267       } else {
268         if (counterPosition == patternLength - 1) {
269           if (patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE) < MAX_AVG_VARIANCE) {
270             return new int[]{patternStart, x};
271           }
272           patternStart += counters[0] + counters[1];
273           for (int y = 2; y < patternLength; y++) {
274             counters[y - 2] = counters[y];
275           }
276           counters[patternLength - 2] = 0;
277           counters[patternLength - 1] = 0;
278           counterPosition--;
279         } else {
280           counterPosition++;
281         }
282         counters[counterPosition] = 1;
283         isWhite = !isWhite;
284       }
285     }
286     throw NotFoundException.getNotFoundInstance();
287   }
288
289   /**
290    * Attempts to decode a single UPC/EAN-encoded digit.
291    *
292    * @param row row of black/white values to decode
293    * @param counters the counts of runs of observed black/white/black/... values
294    * @param rowOffset horizontal offset to start decoding from
295    * @param patterns the set of patterns to use to decode -- sometimes different encodings
296    * for the digits 0-9 are used, and this indicates the encodings for 0 to 9 that should
297    * be used
298    * @return horizontal offset of first pixel beyond the decoded digit
299    * @throws NotFoundException if digit cannot be decoded
300    */
301   static int decodeDigit(BitArray row, int[] counters, int rowOffset, int[][] patterns)
302       throws NotFoundException {
303     recordPattern(row, rowOffset, counters);
304     int bestVariance = MAX_AVG_VARIANCE; // worst variance we'll accept
305     int bestMatch = -1;
306     int max = patterns.length;
307     for (int i = 0; i < max; i++) {
308       int[] pattern = patterns[i];
309       int variance = patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE);
310       if (variance < bestVariance) {
311         bestVariance = variance;
312         bestMatch = i;
313       }
314     }
315     if (bestMatch >= 0) {
316       return bestMatch;
317     } else {
318       throw NotFoundException.getNotFoundInstance();
319     }
320   }
321
322   /**
323    * Get the format of this decoder.
324    *
325    * @return The 1D format.
326    */
327   abstract BarcodeFormat getBarcodeFormat();
328
329   /**
330    * Subclasses override this to decode the portion of a barcode between the start
331    * and end guard patterns.
332    *
333    * @param row row of black/white values to search
334    * @param startRange start/end offset of start guard pattern
335    * @param resultString {@link StringBuffer} to append decoded chars to
336    * @return horizontal offset of first pixel after the "middle" that was decoded
337    * @throws NotFoundException if decoding could not complete successfully
338    */
339   protected abstract int decodeMiddle(BitArray row, int[] startRange, StringBuffer resultString)
340       throws NotFoundException;
341
342 }