Rest of cosmetic changes -- simpler, black theme with easier-to-touch buttons and...
[zxing.git] / csharp / oned / ITFReader.cs
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 using System;\r
17 using BarcodeFormat = com.google.zxing.BarcodeFormat;\r
18 using DecodeHintType = com.google.zxing.DecodeHintType;\r
19 using ReaderException = com.google.zxing.ReaderException;\r
20 using Result = com.google.zxing.Result;\r
21 using ResultPoint = com.google.zxing.ResultPoint;\r
22 using BitArray = com.google.zxing.common.BitArray;\r
23 namespace com.google.zxing.oned\r
24 {\r
25         \r
26         /// <summary> <p>Implements decoding of the ITF format.</p>\r
27         /// \r
28         /// <p>"ITF" stands for Interleaved Two of Five. This Reader will scan ITF barcode with 6, 10 or 14\r
29         /// digits. The checksum is optional and is not applied by this Reader. The consumer of the decoded\r
30         /// value will have to apply a checksum if required.</p>\r
31         /// \r
32         /// <p><a href="http://en.wikipedia.org/wiki/Interleaved_2_of_5">http://en.wikipedia.org/wiki/Interleaved_2_of_5</a>\r
33         /// is a great reference for Interleaved 2 of 5 information.</p>\r
34         /// \r
35         /// </summary>\r
36         /// <author>  kevin.osullivan@sita.aero, SITA Lab.\r
37         /// </author>\r
38         /// <author>www.Redivivus.in (suraj.supekar@redivivus.in) - Ported from ZXING Java Source \r
39         /// </author>\r
40         public sealed class ITFReader:OneDReader\r
41         {\r
42                 \r
43                 //UPGRADE_NOTE: Final was removed from the declaration of 'MAX_AVG_VARIANCE '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"\r
44                 //UPGRADE_WARNING: Data types in Visual C# might be different.  Verify the accuracy of narrowing conversions. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1042'"\r
45                 private static readonly int MAX_AVG_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.42f);\r
46                 //UPGRADE_NOTE: Final was removed from the declaration of 'MAX_INDIVIDUAL_VARIANCE '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"\r
47                 //UPGRADE_WARNING: Data types in Visual C# might be different.  Verify the accuracy of narrowing conversions. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1042'"\r
48                 private static readonly int MAX_INDIVIDUAL_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.8f);\r
49                 \r
50                 private const int W = 3; // Pixel width of a wide line\r
51                 private const int N = 1; // Pixed width of a narrow line\r
52                 \r
53                 //UPGRADE_NOTE: Final was removed from the declaration of 'DEFAULT_ALLOWED_LENGTHS'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"\r
54                 private static readonly int[] DEFAULT_ALLOWED_LENGTHS = new int[]{6, 10, 14, 44};\r
55                 \r
56                 // Stores the actual narrow line width of the image being decoded.\r
57                 private int narrowLineWidth = - 1;\r
58                 \r
59                 /// <summary> Start/end guard pattern.\r
60                 /// \r
61                 /// Note: The end pattern is reversed because the row is reversed before\r
62                 /// searching for the END_PATTERN\r
63                 /// </summary>\r
64                 //UPGRADE_NOTE: Final was removed from the declaration of 'START_PATTERN '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"\r
65                 private static readonly int[] START_PATTERN = new int[]{N, N, N, N};\r
66                 //UPGRADE_NOTE: Final was removed from the declaration of 'END_PATTERN_REVERSED '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"\r
67                 private static readonly int[] END_PATTERN_REVERSED = new int[]{N, N, W};\r
68                 \r
69                 /// <summary> Patterns of Wide / Narrow lines to indicate each digit</summary>\r
70                 //UPGRADE_NOTE: Final was removed from the declaration of 'PATTERNS '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"\r
71                 private static readonly int[][] PATTERNS = new int[][]{new int[]{N, N, W, W, N}, new int[]{W, N, N, N, W}, new int[]{N, W, N, N, W}, new int[]{W, W, N, N, N}, new int[]{N, N, W, N, W}, new int[]{W, N, W, N, N}, new int[]{N, W, W, N, N}, new int[]{N, N, N, W, W}, new int[]{W, N, N, W, N}, new int[]{N, W, N, W, N}};\r
72                 \r
73                 public override Result decodeRow(int rowNumber, BitArray row, System.Collections.Hashtable hints)\r
74                 {\r
75                         \r
76                         // Find out where the Middle section (payload) starts & ends\r
77                         int[] startRange = decodeStart(row);\r
78                         int[] endRange = decodeEnd(row);\r
79                         \r
80                         System.Text.StringBuilder result = new System.Text.StringBuilder(20);\r
81                         decodeMiddle(row, startRange[1], endRange[0], result);\r
82                         System.String resultString = result.ToString();\r
83                         \r
84                         int[] allowedLengths = null;\r
85                         if (hints != null)\r
86                         {\r
87                                 allowedLengths = (int[]) hints[DecodeHintType.ALLOWED_LENGTHS];\r
88                         }\r
89                         if (allowedLengths == null)\r
90                         {\r
91                                 allowedLengths = DEFAULT_ALLOWED_LENGTHS;\r
92                         }\r
93                         \r
94                         // To avoid false positives with 2D barcodes (and other patterns), make\r
95                         // an assumption that the decoded string must be 6, 10 or 14 digits.\r
96                         int length = resultString.Length;\r
97                         bool lengthOK = false;\r
98                         for (int i = 0; i < allowedLengths.Length; i++)\r
99                         {\r
100                                 if (length == allowedLengths[i])\r
101                                 {\r
102                                         lengthOK = true;\r
103                                         break;\r
104                                 }\r
105                         }\r
106                         if (!lengthOK)\r
107                         {\r
108                                 throw ReaderException.Instance;\r
109                         }\r
110                         \r
111                         //UPGRADE_WARNING: Data types in Visual C# might be different.  Verify the accuracy of narrowing conversions. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1042'"\r
112                         return new Result(resultString, null, new ResultPoint[]{new ResultPoint(startRange[1], (float) rowNumber), new ResultPoint(endRange[0], (float) rowNumber)}, BarcodeFormat.ITF);\r
113                 }\r
114                 \r
115                 /// <param name="row">         row of black/white values to search\r
116                 /// </param>\r
117                 /// <param name="payloadStart">offset of start pattern\r
118                 /// </param>\r
119                 /// <param name="resultString">{@link StringBuffer} to append decoded chars to\r
120                 /// </param>\r
121                 /// <throws>  ReaderException if decoding could not complete successfully </throws>\r
122                 private static void  decodeMiddle(BitArray row, int payloadStart, int payloadEnd, System.Text.StringBuilder resultString)\r
123                 {\r
124                         \r
125                         // Digits are interleaved in pairs - 5 black lines for one digit, and the\r
126                         // 5\r
127                         // interleaved white lines for the second digit.\r
128                         // Therefore, need to scan 10 lines and then\r
129                         // split these into two arrays\r
130                         int[] counterDigitPair = new int[10];\r
131                         int[] counterBlack = new int[5];\r
132                         int[] counterWhite = new int[5];\r
133                         \r
134                         while (payloadStart < payloadEnd)\r
135                         {\r
136                                 \r
137                                 // Get 10 runs of black/white.\r
138                                 recordPattern(row, payloadStart, counterDigitPair);\r
139                                 // Split them into each array\r
140                                 for (int k = 0; k < 5; k++)\r
141                                 {\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                                 {\r
154                                         payloadStart += counterDigitPair[i];\r
155                                 }\r
156                         }\r
157                 }\r
158                 \r
159                 /// <summary> Identify where the start of the middle / payload section starts.\r
160                 /// \r
161                 /// </summary>\r
162                 /// <param name="row">row of black/white values to search\r
163                 /// </param>\r
164                 /// <returns> Array, containing index of start of 'start block' and end of\r
165                 /// 'start block'\r
166                 /// </returns>\r
167                 /// <throws>  ReaderException </throws>\r
168                 internal int[] decodeStart(BitArray row)\r
169                 {\r
170                         int endStart = skipWhiteSpace(row);\r
171                         int[] startPattern = findGuardPattern(row, endStart, START_PATTERN);\r
172                         \r
173                         // Determine the width of a narrow line in pixels. We can do this by\r
174                         // getting the width of the start pattern and dividing by 4 because its\r
175                         // made up of 4 narrow lines.\r
176                         this.narrowLineWidth = (startPattern[1] - startPattern[0]) >> 2;\r
177                         \r
178                         validateQuietZone(row, startPattern[0]);\r
179                         \r
180                         return startPattern;\r
181                 }\r
182                 \r
183                 /// <summary> The start & end patterns must be pre/post fixed by a quiet zone. This\r
184                 /// zone must be at least 10 times the width of a narrow line.  Scan back until\r
185                 /// we either get to the start of the barcode or match the necessary number of\r
186                 /// quiet zone pixels.\r
187                 /// \r
188                 /// Note: Its assumed the row is reversed when using this method to find\r
189                 /// quiet zone after the end pattern.\r
190                 /// \r
191                 /// ref: http://www.barcode-1.net/i25code.html\r
192                 /// \r
193                 /// </summary>\r
194                 /// <param name="row">bit array representing the scanned barcode.\r
195                 /// </param>\r
196                 /// <param name="startPattern">index into row of the start or end pattern.\r
197                 /// </param>\r
198                 /// <throws>  ReaderException if the quiet zone cannot be found, a ReaderException is thrown. </throws>\r
199                 private void  validateQuietZone(BitArray row, int startPattern)\r
200                 {\r
201                         \r
202                         int quietCount = this.narrowLineWidth * 10; // expect to find this many pixels of quiet zone\r
203                         \r
204                         for (int i = startPattern - 1; quietCount > 0 && i >= 0; i--)\r
205                         {\r
206                                 if (row.get_Renamed(i))\r
207                                 {\r
208                                         break;\r
209                                 }\r
210                                 quietCount--;\r
211                         }\r
212                         if (quietCount != 0)\r
213                         {\r
214                                 // Unable to find the necessary number of quiet zone pixels.\r
215                                 throw ReaderException.Instance;\r
216                         }\r
217                 }\r
218                 \r
219                 /// <summary> Skip all whitespace until we get to the first black line.\r
220                 /// \r
221                 /// </summary>\r
222                 /// <param name="row">row of black/white values to search\r
223                 /// </param>\r
224                 /// <returns> index of the first black line.\r
225                 /// </returns>\r
226                 /// <throws>  ReaderException Throws exception if no black lines are found in the row </throws>\r
227                 private static int skipWhiteSpace(BitArray row)\r
228                 {\r
229                         int width = row.Size;\r
230                         int endStart = 0;\r
231                         while (endStart < width)\r
232                         {\r
233                                 if (row.get_Renamed(endStart))\r
234                                 {\r
235                                         break;\r
236                                 }\r
237                                 endStart++;\r
238                         }\r
239                         if (endStart == width)\r
240                         {\r
241                                 throw ReaderException.Instance;\r
242                         }\r
243                         \r
244                         return endStart;\r
245                 }\r
246                 \r
247                 /// <summary> Identify where the end of the middle / payload section ends.\r
248                 /// \r
249                 /// </summary>\r
250                 /// <param name="row">row of black/white values to search\r
251                 /// </param>\r
252                 /// <returns> Array, containing index of start of 'end block' and end of 'end\r
253                 /// block'\r
254                 /// </returns>\r
255                 /// <throws>  ReaderException </throws>\r
256                 \r
257                 internal int[] decodeEnd(BitArray row)\r
258                 {\r
259                         \r
260                         // For convenience, reverse the row and then\r
261                         // search from 'the start' for the end block\r
262                         row.reverse();\r
263                         try\r
264                         {\r
265                                 int endStart = skipWhiteSpace(row);\r
266                                 int[] endPattern = findGuardPattern(row, endStart, END_PATTERN_REVERSED);\r
267                                 \r
268                                 // The start & end patterns must be pre/post fixed by a quiet zone. This\r
269                                 // zone must be at least 10 times the width of a narrow line.\r
270                                 // ref: http://www.barcode-1.net/i25code.html\r
271                                 validateQuietZone(row, endPattern[0]);\r
272                                 \r
273                                 // Now recalculate the indices of where the 'endblock' starts & stops to\r
274                                 // accommodate\r
275                                 // the reversed nature of the search\r
276                                 int temp = endPattern[0];\r
277                                 endPattern[0] = row.Size - endPattern[1];\r
278                                 endPattern[1] = row.Size - temp;\r
279                                 \r
280                                 return endPattern;\r
281                         }\r
282                         finally\r
283                         {\r
284                                 // Put the row back the right way.\r
285                                 row.reverse();\r
286                         }\r
287                 }\r
288                 \r
289                 /// <param name="row">      row of black/white values to search\r
290                 /// </param>\r
291                 /// <param name="rowOffset">position to start search\r
292                 /// </param>\r
293                 /// <param name="pattern">  pattern of counts of number of black and white pixels that are\r
294                 /// being searched for as a pattern\r
295                 /// </param>\r
296                 /// <returns> start/end horizontal offset of guard pattern, as an array of two\r
297                 /// ints\r
298                 /// </returns>\r
299                 /// <throws>  ReaderException if pattern is not found </throws>\r
300                 private static int[] findGuardPattern(BitArray row, int rowOffset, int[] pattern)\r
301                 {\r
302                         \r
303                         // TODO: This is very similar to implementation in UPCEANReader. Consider if they can be\r
304                         // merged to a single method.\r
305                         int patternLength = pattern.Length;\r
306                         int[] counters = new int[patternLength];\r
307                         int width = row.Size;\r
308                         bool isWhite = false;\r
309                         \r
310                         int counterPosition = 0;\r
311                         int patternStart = rowOffset;\r
312                         for (int x = rowOffset; x < width; x++)\r
313                         {\r
314                                 bool pixel = row.get_Renamed(x);\r
315                                 if (pixel ^ isWhite)\r
316                                 {\r
317                                         counters[counterPosition]++;\r
318                                 }\r
319                                 else\r
320                                 {\r
321                                         if (counterPosition == patternLength - 1)\r
322                                         {\r
323                                                 if (patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE) < MAX_AVG_VARIANCE)\r
324                                                 {\r
325                                                         return new int[]{patternStart, x};\r
326                                                 }\r
327                                                 patternStart += counters[0] + counters[1];\r
328                                                 for (int y = 2; y < patternLength; y++)\r
329                                                 {\r
330                                                         counters[y - 2] = counters[y];\r
331                                                 }\r
332                                                 counters[patternLength - 2] = 0;\r
333                                                 counters[patternLength - 1] = 0;\r
334                                                 counterPosition--;\r
335                                         }\r
336                                         else\r
337                                         {\r
338                                                 counterPosition++;\r
339                                         }\r
340                                         counters[counterPosition] = 1;\r
341                                         isWhite = !isWhite;\r
342                                 }\r
343                         }\r
344                         throw ReaderException.Instance;\r
345                 }\r
346                 \r
347                 /// <summary> Attempts to decode a sequence of ITF black/white lines into single\r
348                 /// digit.\r
349                 /// \r
350                 /// </summary>\r
351                 /// <param name="counters">the counts of runs of observed black/white/black/... values\r
352                 /// </param>\r
353                 /// <returns> The decoded digit\r
354                 /// </returns>\r
355                 /// <throws>  ReaderException if digit cannot be decoded </throws>\r
356                 private static int decodeDigit(int[] counters)\r
357                 {\r
358                         \r
359                         int bestVariance = MAX_AVG_VARIANCE; // worst variance we'll accept\r
360                         int bestMatch = - 1;\r
361                         int max = PATTERNS.Length;\r
362                         for (int i = 0; i < max; i++)\r
363                         {\r
364                                 int[] pattern = PATTERNS[i];\r
365                                 int variance = patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE);\r
366                                 if (variance < bestVariance)\r
367                                 {\r
368                                         bestVariance = variance;\r
369                                         bestMatch = i;\r
370                                 }\r
371                         }\r
372                         if (bestMatch >= 0)\r
373                         {\r
374                                 return bestMatch;\r
375                         }\r
376                         else\r
377                         {\r
378                                 throw ReaderException.Instance;\r
379                         }\r
380                 }\r
381         }\r
382 }