Fixed some long lines over 100 columns.
[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.
101       // If this check would run off the left edge of the image, do not accept this barcode,
102       // as it is 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)
112       throws ReaderException {
113     return decodeRow(rowNumber, row, findStartGuardPattern(row));
114   }
115
116   public final Result decodeRow(int rowNumber, BitArray row, int[] startGuardRange)
117       throws ReaderException {
118     StringBuffer result = decodeRowStringBuffer;
119     result.setLength(0);
120     int endStart = decodeMiddle(row, startGuardRange, result);
121     int[] endRange = decodeEnd(row, endStart);
122
123     // Make sure there is a quiet zone at least as big as the end pattern after the barcode. The
124     // spec might want more whitespace, but in practice this is the maximum we can count on.
125     int end = endRange[1];
126     int quietEnd = end + (end - endRange[0]);
127     if (quietEnd >= row.getSize() || !row.isRange(end, quietEnd, false)) {
128       throw ReaderException.getInstance();
129     }
130
131     String resultString = result.toString();
132     if (!checkChecksum(resultString)) {
133       throw ReaderException.getInstance();
134     }
135
136     float left = (float) (startGuardRange[1] + startGuardRange[0]) / 2.0f;
137     float right = (float) (endRange[1] + endRange[0]) / 2.0f;
138     return new Result(resultString,
139         null, // no natural byte representation for these barcodes
140         new ResultPoint[]{
141             new ResultPoint(left, (float) rowNumber),
142             new ResultPoint(right, (float) rowNumber)},
143         getBarcodeFormat());
144   }
145
146   abstract BarcodeFormat getBarcodeFormat();
147
148   /**
149    * @return {@link #checkStandardUPCEANChecksum(String)} 
150    */
151   boolean checkChecksum(String s) throws ReaderException {
152     return checkStandardUPCEANChecksum(s);
153   }
154
155   /**
156    * Computes the UPC/EAN checksum on a string of digits, and reports
157    * whether the checksum is correct or not.
158    *
159    * @param s string of digits to check
160    * @return true iff string of digits passes the UPC/EAN checksum algorithm
161    * @throws ReaderException if the string does not contain only digits
162    */
163   public static boolean checkStandardUPCEANChecksum(String s) throws ReaderException {
164     int length = s.length();
165     if (length == 0) {
166       return false;
167     }
168
169     int sum = 0;
170     for (int i = length - 2; i >= 0; i -= 2) {
171       int digit = (int) s.charAt(i) - (int) '0';
172       if (digit < 0 || digit > 9) {
173         throw ReaderException.getInstance();
174       }
175       sum += digit;
176     }
177     sum *= 3;
178     for (int i = length - 1; i >= 0; i -= 2) {
179       int digit = (int) s.charAt(i) - (int) '0';
180       if (digit < 0 || digit > 9) {
181         throw ReaderException.getInstance();
182       }
183       sum += digit;
184     }
185     return sum % 10 == 0;
186   }
187
188   /**
189    * Subclasses override this to decode the portion of a barcode between the start
190    * and end guard patterns.
191    *
192    * @param row row of black/white values to search
193    * @param startRange start/end offset of start guard pattern
194    * @param resultString {@link StringBuffer} to append decoded chars to
195    * @return horizontal offset of first pixel after the "middle" that was decoded
196    * @throws ReaderException if decoding could not complete successfully
197    */
198   protected abstract int decodeMiddle(BitArray row, int[] startRange, StringBuffer resultString)
199       throws ReaderException;
200
201   int[] decodeEnd(BitArray row, int endStart) throws ReaderException {
202     return findGuardPattern(row, endStart, false, START_END_PATTERN);
203   }
204
205   /**
206    * @param row row of black/white values to search
207    * @param rowOffset position to start search
208    * @param whiteFirst if true, indicates that the pattern specifies white/black/white/...
209    * pixel counts, otherwise, it is interpreted as black/white/black/...
210    * @param pattern pattern of counts of number of black and white pixels that are being
211    * searched for as a pattern
212    * @return start/end horizontal offset of guard pattern, as an array of two ints
213    * @throws ReaderException if pattern is not found
214    */
215   static int[] findGuardPattern(BitArray row, int rowOffset, boolean whiteFirst, int[] pattern)
216       throws ReaderException {
217     int patternLength = pattern.length;
218     int[] counters = new int[patternLength];
219     int width = row.getSize();
220     boolean isWhite = false;
221     while (rowOffset < width) {
222       isWhite = !row.get(rowOffset);
223       if (whiteFirst == isWhite) {
224         break;
225       }
226       rowOffset++;
227     }
228
229     int counterPosition = 0;
230     int patternStart = rowOffset;
231     for (int x = rowOffset; x < width; x++) {
232       boolean pixel = row.get(x);
233       if (pixel ^ isWhite) {
234         counters[counterPosition]++;
235       } else {
236         if (counterPosition == patternLength - 1) {
237           if (patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE) < MAX_AVG_VARIANCE) {
238             return new int[]{patternStart, x};
239           }
240           patternStart += counters[0] + counters[1];
241           for (int y = 2; y < patternLength; y++) {
242             counters[y - 2] = counters[y];
243           }
244           counters[patternLength - 2] = 0;
245           counters[patternLength - 1] = 0;
246           counterPosition--;
247         } else {
248           counterPosition++;
249         }
250         counters[counterPosition] = 1;
251         isWhite ^= true; // isWhite = !isWhite;
252       }
253     }
254     throw ReaderException.getInstance();
255   }
256
257   /**
258    * Attempts to decode a single UPC/EAN-encoded digit.
259    *
260    * @param row row of black/white values to decode
261    * @param counters the counts of runs of observed black/white/black/... values
262    * @param rowOffset horizontal offset to start decoding from
263    * @param patterns the set of patterns to use to decode -- sometimes different encodings
264    * for the digits 0-9 are used, and this indicates the encodings for 0 to 9 that should
265    * be used
266    * @return horizontal offset of first pixel beyond the decoded digit
267    * @throws ReaderException if digit cannot be decoded
268    */
269   static int decodeDigit(BitArray row, int[] counters, int rowOffset, int[][] patterns)
270       throws ReaderException {
271     recordPattern(row, rowOffset, counters);
272     int bestVariance = MAX_AVG_VARIANCE; // worst variance we'll accept
273     int bestMatch = -1;
274     int max = patterns.length;
275     for (int i = 0; i < max; i++) {
276       int[] pattern = patterns[i];
277       int variance = patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE);
278       if (variance < bestVariance) {
279         bestVariance = variance;
280         bestMatch = i;
281       }
282     }
283     if (bestMatch >= 0) {
284       return bestMatch;
285     } else {
286       throw ReaderException.getInstance();
287     }
288   }
289
290 }