Tiny optimizations to boolean logic to avoid extra byte code and branches in semi...
[zxing.git] / core / src / com / google / zxing / oned / ITFReader.java
1 /*\r
2  * Copyright 2008 ZXing authors\r
3  *\r
4  * Licensed under the Apache License, Version 2.0 (the "License");\r
5  * you may not use this file except in compliance with the License.\r
6  * You may obtain a copy of the License at\r
7  *\r
8  *      http://www.apache.org/licenses/LICENSE-2.0\r
9  *\r
10  * Unless required by applicable law or agreed to in writing, software\r
11  * distributed under the License is distributed on an "AS IS" BASIS,\r
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
13  * See the License for the specific language governing permissions and\r
14  * limitations under the License.\r
15  */\r
16 \r
17 package com.google.zxing.oned;\r
18 \r
19 import com.google.zxing.BarcodeFormat;\r
20 import com.google.zxing.ReaderException;\r
21 import com.google.zxing.Result;\r
22 import com.google.zxing.ResultPoint;\r
23 import com.google.zxing.DecodeHintType;\r
24 import com.google.zxing.common.BitArray;\r
25 import com.google.zxing.common.GenericResultPoint;\r
26 \r
27 import java.util.Hashtable;\r
28 \r
29 /**\r
30  * <p>Implements decoding of the ITF format.</p>\r
31  *\r
32  * <p>"ITF" stands for Interleaved Two of Five. This Reader will scan ITF barcode with 6, 10 or 14 digits.\r
33  * The checksum is optional and is not applied by this Reader. The consumer of the decoded value\r
34  * will have to apply a checksum if required.</p>\r
35  *\r
36  * <p><a href="http://en.wikipedia.org/wiki/Interleaved_2_of_5">http://en.wikipedia.org/wiki/Interleaved_2_of_5</a>\r
37  * is a great reference for Interleaved 2 of 5 information.</p>\r
38  *\r
39  * @author kevin.osullivan@sita.aero, SITA Lab.\r
40  */\r
41 public final class ITFReader extends AbstractOneDReader {\r
42 \r
43   private static final int MAX_AVG_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.42f);\r
44   private static final int MAX_INDIVIDUAL_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.8f);\r
45 \r
46   private static final int W = 3; // Pixel width of a wide line\r
47   private static final int N = 1; // Pixed width of a narrow line\r
48 \r
49   private static final int[] DEFAULT_ALLOWED_LENGTHS = { 6, 10, 14 };\r
50 \r
51   // Stores the actual narrow line width of the image being decoded.\r
52   private int narrowLineWidth = -1;\r
53 \r
54   /**\r
55    * Start/end guard pattern.\r
56    *\r
57    * Note: The end pattern is reversed because the row is reversed before\r
58    * searching for the END_PATTERN\r
59    */\r
60   private static final int[] START_PATTERN = {N, N, N, N};\r
61   private static final int[] END_PATTERN_REVERSED = {N, N, W};\r
62 \r
63   /**\r
64    * Patterns of Wide / Narrow lines to indicate each digit\r
65    */\r
66   private static final int[][] PATTERNS = {\r
67       {N, N, W, W, N}, // 0\r
68       {W, N, N, N, W}, // 1\r
69       {N, W, N, N, W}, // 2\r
70       {W, W, N, N, N}, // 3\r
71       {N, N, W, N, W}, // 4\r
72       {W, N, W, N, N}, // 5\r
73       {N, W, W, N, N}, // 6\r
74       {N, N, N, W, W}, // 7\r
75       {W, N, N, W, N}, // 8\r
76       {N, W, N, W, N}  // 9\r
77   };\r
78 \r
79   public Result decodeRow(int rowNumber, BitArray row, Hashtable hints) throws ReaderException {\r
80 \r
81     StringBuffer result = new StringBuffer(20);\r
82 \r
83     // Find out where the Middle section (payload) starts & ends\r
84     int[] startRange = decodeStart(row);\r
85     int[] endRange = decodeEnd(row);\r
86 \r
87     decodeMiddle(row, startRange[1], endRange[0], result);\r
88 \r
89     String resultString = result.toString();\r
90 \r
91     int[] allowedLengths = (int[]) hints.get(DecodeHintType.ALLOWED_LENGTHS);\r
92     if (allowedLengths == null) {\r
93       allowedLengths = DEFAULT_ALLOWED_LENGTHS;\r
94     }\r
95 \r
96     // To avoid false positives with 2D barcodes (and other patterns), make\r
97     // an assumption that the decoded string must be 6, 10 or 14 digits.\r
98     int length = resultString.length();\r
99     boolean lengthOK = false;\r
100     for (int i = 0; i < allowedLengths.length; i++) {\r
101       if (length == allowedLengths[i]) {\r
102         lengthOK = true;\r
103         break;\r
104       }\r
105 \r
106     }\r
107     if (!lengthOK) {\r
108       throw ReaderException.getInstance();\r
109     }\r
110 \r
111     return new Result(\r
112         resultString,\r
113         null, // no natural byte representation for these barcodes\r
114         new ResultPoint[] { new GenericResultPoint(startRange[1], (float) rowNumber),\r
115                             new GenericResultPoint(startRange[0], (float) rowNumber)},\r
116         BarcodeFormat.ITF);\r
117   }\r
118 \r
119   /**\r
120    * @param row          row of black/white values to search\r
121    * @param payloadStart offset of start pattern\r
122    * @param resultString {@link StringBuffer} to append decoded chars to\r
123    * @throws ReaderException if decoding could not complete successfully\r
124    */\r
125   static void decodeMiddle(BitArray row, int payloadStart, int payloadEnd, StringBuffer resultString) throws ReaderException {\r
126 \r
127     // Digits are interleaved in pairs - 5 black lines for one digit, and the\r
128     // 5\r
129     // interleaved white lines for the second digit.\r
130     // Therefore, need to scan 10 lines and then\r
131     // split these into two arrays\r
132     int[] counterDigitPair = new int[10];\r
133     int[] counterBlack = new int[5];\r
134     int[] counterWhite = new int[5];\r
135 \r
136     while (payloadStart < payloadEnd) {\r
137 \r
138       // Get 10 runs of black/white.\r
139       recordPattern(row, payloadStart, counterDigitPair);\r
140       // Split them into each array\r
141       for (int k = 0; k < 5; k++) {\r
142         int twoK = k << 1;\r
143         counterBlack[k] = counterDigitPair[twoK];\r
144         counterWhite[k] = counterDigitPair[twoK + 1];\r
145       }\r
146 \r
147       int bestMatch = decodeDigit(counterBlack);\r
148       resultString.append((char) ('0' + bestMatch));\r
149       bestMatch = decodeDigit(counterWhite);\r
150       resultString.append((char) ('0' + bestMatch));\r
151 \r
152       for (int i = 0; i < counterDigitPair.length; i++) {\r
153         payloadStart += counterDigitPair[i];\r
154       }\r
155     }\r
156   }\r
157 \r
158   /**\r
159    * Identify where the start of the middle / payload section starts.\r
160    *\r
161    * @param row row of black/white values to search\r
162    * @return Array, containing index of start of 'start block' and end of\r
163    *         'start block'\r
164    * @throws ReaderException\r
165    */\r
166   int[] decodeStart(BitArray row) throws ReaderException {\r
167     int endStart = skipWhiteSpace(row);\r
168     int[] startPattern = findGuardPattern(row, endStart, START_PATTERN);\r
169 \r
170     // Determine the width of a narrow line in pixels. We can do this by\r
171     // getting the width of the start pattern and dividing by 4 because its\r
172     // made up of 4 narrow lines.\r
173     this.narrowLineWidth = (startPattern[1] - startPattern[0]) >> 2;\r
174 \r
175     validateQuietZone(row, startPattern[0]);\r
176 \r
177     return startPattern;\r
178   }\r
179 \r
180   /**\r
181    * The start & end patterns must be pre/post fixed by a quiet zone. This\r
182    * zone must be at least 10 times the width of a narrow line.  Scan back until\r
183    * we either get to the start of the barcode or match the necessary number of\r
184    * quiet zone pixels.\r
185    *\r
186    * Note: Its assumed the row is reversed when using this method to find\r
187    * quiet zone after the end pattern.\r
188    *\r
189    * ref: http://www.barcode-1.net/i25code.html\r
190    *\r
191    * @param row bit array representing the scanned barcode.\r
192    * @param startPattern index into row of the start or end pattern.\r
193    * @throws ReaderException if the quiet zone cannot be found, a ReaderException is thrown.\r
194    */\r
195   private void validateQuietZone(BitArray row, int startPattern) throws ReaderException {\r
196 \r
197     int quietCount = this.narrowLineWidth * 10;  // expect to find this many pixels of quiet zone\r
198 \r
199     for (int i = startPattern - 1; quietCount > 0 && i >= 0; i--) {\r
200       if (row.get(i)) {\r
201         break;\r
202       }\r
203       quietCount--;\r
204     }\r
205     if (quietCount != 0) {\r
206       // Unable to find the necessary number of quiet zone pixels.\r
207       throw ReaderException.getInstance();\r
208     }\r
209   }\r
210 \r
211   /**\r
212    * Skip all whitespace until we get to the first black line.\r
213    *\r
214    * @param row row of black/white values to search\r
215    * @return index of the first black line.\r
216    * @throws ReaderException Throws exception if no black lines are found in the row\r
217    */\r
218   private static int skipWhiteSpace(BitArray row) throws ReaderException {\r
219     int width = row.getSize();\r
220     int endStart = 0;\r
221     while (endStart < width) {\r
222       if (row.get(endStart)) {\r
223         break;\r
224       }\r
225       endStart++;\r
226     }\r
227     if (endStart == width) {\r
228       throw ReaderException.getInstance();\r
229     }\r
230 \r
231     return endStart;\r
232   }\r
233 \r
234   /**\r
235    * Identify where the end of the middle / payload section ends.\r
236    *\r
237    * @param row row of black/white values to search\r
238    * @return Array, containing index of start of 'end block' and end of 'end\r
239    *         block'\r
240    * @throws ReaderException\r
241    */\r
242 \r
243   int[] decodeEnd(BitArray row) throws ReaderException {\r
244 \r
245     // For convenience, reverse the row and then\r
246     // search from 'the start' for the end block\r
247     row.reverse();\r
248 \r
249     int endStart = skipWhiteSpace(row);\r
250     int[] endPattern;\r
251     try {\r
252       endPattern = findGuardPattern(row, endStart, END_PATTERN_REVERSED);\r
253     } catch (ReaderException e) {\r
254       // Put our row of data back the right way before throwing\r
255       row.reverse();\r
256       throw e;\r
257     }\r
258 \r
259     // The start & end patterns must be pre/post fixed by a quiet zone. This\r
260     // zone must be at least 10 times the width of a narrow line.\r
261     // ref: http://www.barcode-1.net/i25code.html\r
262     validateQuietZone(row, endPattern[0]);\r
263 \r
264     // Now recalc the indicies of where the 'endblock' starts & stops to\r
265     // accomodate\r
266     // the reversed nature of the search\r
267     int temp = endPattern[0];\r
268     endPattern[0] = row.getSize() - endPattern[1];\r
269     endPattern[1] = row.getSize() - temp;\r
270 \r
271     // Put the row back the righ way.\r
272     row.reverse();\r
273     return endPattern;\r
274   }\r
275 \r
276   /**\r
277    * @param row       row of black/white values to search\r
278    * @param rowOffset position to start search\r
279    * @param pattern   pattern of counts of number of black and white pixels that are\r
280    *                  being searched for as a pattern\r
281    * @return start/end horizontal offset of guard pattern, as an array of two\r
282    *         ints\r
283    * @throws ReaderException if pattern is not found\r
284    */\r
285   static int[] findGuardPattern(BitArray row, int rowOffset, int[] pattern) throws ReaderException {\r
286 \r
287     // TODO: This is very similar to implementation in AbstractUPCEANReader. Consider if they can be merged to\r
288     // a single method.\r
289 \r
290     int patternLength = pattern.length;\r
291     int[] counters = new int[patternLength];\r
292     int width = row.getSize();\r
293     boolean isWhite = false;\r
294 \r
295     int counterPosition = 0;\r
296     int patternStart = rowOffset;\r
297     for (int x = rowOffset; x < width; x++) {\r
298       boolean pixel = row.get(x);\r
299       if (pixel ^ isWhite) {\r
300         counters[counterPosition]++;\r
301       } else {\r
302         if (counterPosition == patternLength - 1) {\r
303           if (patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE) < MAX_AVG_VARIANCE) {\r
304             return new int[]{patternStart, x};\r
305           }\r
306           patternStart += counters[0] + counters[1];\r
307           for (int y = 2; y < patternLength; y++) {\r
308             counters[y - 2] = counters[y];\r
309           }\r
310           counters[patternLength - 2] = 0;\r
311           counters[patternLength - 1] = 0;\r
312           counterPosition--;\r
313         } else {\r
314           counterPosition++;\r
315         }\r
316         counters[counterPosition] = 1;\r
317         isWhite ^= true; // isWhite = !isWhite;\r
318       }\r
319     }\r
320     throw ReaderException.getInstance();\r
321   }\r
322 \r
323   /**\r
324    * Attempts to decode a sequence of ITF black/white lines into single\r
325    * digit.\r
326    *\r
327    * @param counters the counts of runs of observed black/white/black/... values\r
328    * @return The decoded digit\r
329    * @throws ReaderException if digit cannot be decoded\r
330    */\r
331   private static int decodeDigit(int[] counters) throws ReaderException {\r
332 \r
333     int bestVariance = MAX_AVG_VARIANCE; // worst variance we'll accept\r
334     int bestMatch = -1;\r
335     int max = PATTERNS.length;\r
336     for (int i = 0; i < max; i++) {\r
337       int[] pattern = PATTERNS[i];\r
338       int variance = patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE);\r
339       if (variance < bestVariance) {\r
340         bestVariance = variance;\r
341         bestMatch = i;\r
342       }\r
343     }\r
344     if (bestMatch >= 0) {\r
345       return bestMatch;\r
346                 } else {\r
347                         throw ReaderException.getInstance();\r
348                 }\r
349         }\r
350 \r
351 }