Fixed some code which was ignoring the result of MonochromeBitmapSource calls, which...
[zxing.git] / core / src / com / google / zxing / oned / AbstractUPCEANReader.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.ReaderException;
21 import com.google.zxing.Result;
22 import com.google.zxing.ResultPoint;
23 import com.google.zxing.common.BitArray;
24
25 import java.util.Hashtable;
26
27 /**
28  * <p>Encapsulates functionality and implementation that is common to UPC and EAN families
29  * of one-dimensional barcodes.</p>
30  *
31  * @author dswitkin@google.com (Daniel Switkin)
32  * @author Sean Owen
33  * @author alasdair@google.com (Alasdair Mackintosh)
34  */
35 public abstract class AbstractUPCEANReader extends AbstractOneDReader implements UPCEANReader {
36
37   private static final int MAX_AVG_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.42f);
38   private static final int MAX_INDIVIDUAL_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.7f);
39
40   /**
41    * Start/end guard pattern.
42    */
43   static final int[] START_END_PATTERN = {1, 1, 1,};
44
45   /**
46    * Pattern marking the middle of a UPC/EAN pattern, separating the two halves.
47    */
48   static final int[] MIDDLE_PATTERN = {1, 1, 1, 1, 1};
49
50   /**
51    * "Odd", or "L" patterns used to encode UPC/EAN digits.
52    */
53   static final int[][] L_PATTERNS = {
54       {3, 2, 1, 1}, // 0
55       {2, 2, 2, 1}, // 1
56       {2, 1, 2, 2}, // 2
57       {1, 4, 1, 1}, // 3
58       {1, 1, 3, 2}, // 4
59       {1, 2, 3, 1}, // 5
60       {1, 1, 1, 4}, // 6
61       {1, 3, 1, 2}, // 7
62       {1, 2, 1, 3}, // 8
63       {3, 1, 1, 2}  // 9
64   };
65
66   /**
67    * As above but also including the "even", or "G" patterns used to encode UPC/EAN digits.
68    */
69   static final int[][] L_AND_G_PATTERNS;
70
71   static {
72     L_AND_G_PATTERNS = new int[20][];
73     for (int i = 0; i < 10; i++) {
74       L_AND_G_PATTERNS[i] = L_PATTERNS[i];
75     }
76     for (int i = 10; i < 20; i++) {
77       int[] widths = L_PATTERNS[i - 10];
78       int[] reversedWidths = new int[widths.length];
79       for (int j = 0; j < widths.length; j++) {
80         reversedWidths[j] = widths[widths.length - j - 1];
81       }
82       L_AND_G_PATTERNS[i] = reversedWidths;
83     }
84   }
85
86   private final StringBuffer decodeRowStringBuffer;
87
88   protected AbstractUPCEANReader() {
89     decodeRowStringBuffer = new StringBuffer(20);
90   }
91
92   static int[] findStartGuardPattern(BitArray row) throws ReaderException {
93     boolean foundStart = false;
94     int[] startRange = null;
95     int nextStart = 0;
96     while (!foundStart) {
97       startRange = findGuardPattern(row, nextStart, false, START_END_PATTERN);
98       int start = startRange[0];
99       nextStart = startRange[1];
100       // Make sure there is a quiet zone at least as big as the start pattern before the barcode. If
101       // this check would run off the left edge of the image, do not accept this barcode, as it is
102       // very likely to be a false positive.
103       int quietStart = start - (nextStart - start);
104       if (quietStart >= 0) {
105         foundStart = row.isRange(quietStart, start, false);
106       }
107     }
108     return startRange;
109   }
110
111   public final Result decodeRow(int rowNumber, BitArray row, Hashtable hints) throws ReaderException {
112     return decodeRow(rowNumber, row, findStartGuardPattern(row));
113   }
114
115   public final Result decodeRow(int rowNumber, BitArray row, int[] startGuardRange) throws ReaderException {
116     StringBuffer result = decodeRowStringBuffer;
117     result.setLength(0);
118     int endStart = decodeMiddle(row, startGuardRange, result);
119     int[] endRange = decodeEnd(row, endStart);
120
121     // Make sure there is a quiet zone at least as big as the end pattern after the barcode. The
122     // spec might want more whitespace, but in practice this is the maximum we can count on.
123     int end = endRange[1];
124     int quietEnd = end + (end - endRange[0]);
125     if (quietEnd >= row.getSize() || !row.isRange(end, quietEnd, false)) {
126       throw ReaderException.getInstance();
127     }
128
129     String resultString = result.toString();
130     if (!checkChecksum(resultString)) {
131       throw ReaderException.getInstance();
132     }
133
134     float left = (float) (startGuardRange[1] + startGuardRange[0]) / 2.0f;
135     float right = (float) (endRange[1] + endRange[0]) / 2.0f;
136     return new Result(resultString,
137         null, // no natural byte representation for these barcodes
138         new ResultPoint[]{
139             new ResultPoint(left, (float) rowNumber),
140             new ResultPoint(right, (float) rowNumber)},
141         getBarcodeFormat());
142   }
143
144   abstract BarcodeFormat getBarcodeFormat();
145
146   /**
147    * @return {@link #checkStandardUPCEANChecksum(String)} 
148    */
149   boolean checkChecksum(String s) throws ReaderException {
150     return checkStandardUPCEANChecksum(s);
151   }
152
153   /**
154    * Computes the UPC/EAN checksum on a string of digits, and reports
155    * whether the checksum is correct or not.
156    *
157    * @param s string of digits to check
158    * @return true iff string of digits passes the UPC/EAN checksum algorithm
159    * @throws ReaderException if the string does not contain only digits
160    */
161   public static boolean checkStandardUPCEANChecksum(String s) throws ReaderException {
162     int length = s.length();
163     if (length == 0) {
164       return false;
165     }
166
167     int sum = 0;
168     for (int i = length - 2; i >= 0; i -= 2) {
169       int digit = (int) s.charAt(i) - (int) '0';
170       if (digit < 0 || digit > 9) {
171         throw ReaderException.getInstance();
172       }
173       sum += digit;
174     }
175     sum *= 3;
176     for (int i = length - 1; i >= 0; i -= 2) {
177       int digit = (int) s.charAt(i) - (int) '0';
178       if (digit < 0 || digit > 9) {
179         throw ReaderException.getInstance();
180       }
181       sum += digit;
182     }
183     return sum % 10 == 0;
184   }
185
186   /**
187    * Subclasses override this to decode the portion of a barcode between the start and end guard patterns.
188    *
189    * @param row row of black/white values to search
190    * @param startRange start/end offset of start guard pattern
191    * @param resultString {@link StringBuffer} to append decoded chars to
192    * @return horizontal offset of first pixel after the "middle" that was decoded
193    * @throws ReaderException if decoding could not complete successfully
194    */
195   protected abstract int decodeMiddle(BitArray row, int[] startRange, StringBuffer resultString)
196       throws ReaderException;
197
198   int[] decodeEnd(BitArray row, int endStart) throws ReaderException {
199     return findGuardPattern(row, endStart, false, START_END_PATTERN);
200   }
201
202   /**
203    * @param row row of black/white values to search
204    * @param rowOffset position to start search
205    * @param whiteFirst if true, indicates that the pattern specifies white/black/white/...
206    * pixel counts, otherwise, it is interpreted as black/white/black/...
207    * @param pattern pattern of counts of number of black and white pixels that are being
208    * searched for as a pattern
209    * @return start/end horizontal offset of guard pattern, as an array of two ints
210    * @throws ReaderException if pattern is not found
211    */
212   static int[] findGuardPattern(BitArray row, int rowOffset, boolean whiteFirst, int[] pattern)
213       throws ReaderException {
214     int patternLength = pattern.length;
215     int[] counters = new int[patternLength];
216     int width = row.getSize();
217     boolean isWhite = false;
218     while (rowOffset < width) {
219       isWhite = !row.get(rowOffset);
220       if (whiteFirst == isWhite) {
221         break;
222       }
223       rowOffset++;
224     }
225
226     int counterPosition = 0;
227     int patternStart = rowOffset;
228     for (int x = rowOffset; x < width; x++) {
229       boolean pixel = row.get(x);
230       if (pixel ^ isWhite) {
231         counters[counterPosition]++;
232       } else {
233         if (counterPosition == patternLength - 1) {
234           if (patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE) < MAX_AVG_VARIANCE) {
235             return new int[]{patternStart, x};
236           }
237           patternStart += counters[0] + counters[1];
238           for (int y = 2; y < patternLength; y++) {
239             counters[y - 2] = counters[y];
240           }
241           counters[patternLength - 2] = 0;
242           counters[patternLength - 1] = 0;
243           counterPosition--;
244         } else {
245           counterPosition++;
246         }
247         counters[counterPosition] = 1;
248         isWhite ^= true; // isWhite = !isWhite;
249       }
250     }
251     throw ReaderException.getInstance();
252   }
253
254   /**
255    * Attempts to decode a single UPC/EAN-encoded digit.
256    *
257    * @param row row of black/white values to decode
258    * @param counters the counts of runs of observed black/white/black/... values
259    * @param rowOffset horizontal offset to start decoding from
260    * @param patterns the set of patterns to use to decode -- sometimes different encodings
261    * for the digits 0-9 are used, and this indicates the encodings for 0 to 9 that should
262    * be used
263    * @return horizontal offset of first pixel beyond the decoded digit
264    * @throws ReaderException if digit cannot be decoded
265    */
266   static int decodeDigit(BitArray row, int[] counters, int rowOffset, int[][] patterns)
267       throws ReaderException {
268     recordPattern(row, rowOffset, counters);
269     int bestVariance = MAX_AVG_VARIANCE; // worst variance we'll accept
270     int bestMatch = -1;
271     int max = patterns.length;
272     for (int i = 0; i < max; i++) {
273       int[] pattern = patterns[i];
274       int variance = patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE);
275       if (variance < bestVariance) {
276         bestVariance = variance;
277         bestMatch = i;
278       }
279     }
280     if (bestMatch >= 0) {
281       return bestMatch;
282     } else {
283       throw ReaderException.getInstance();
284     }
285   }
286
287 }