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