Prettify scan result screen, I think
[zxing.git] / csharp / oned / Code128Reader.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 ReaderException = com.google.zxing.ReaderException;\r
19 using Result = com.google.zxing.Result;\r
20 using ResultPoint = com.google.zxing.ResultPoint;\r
21 using BitArray = com.google.zxing.common.BitArray;\r
22 namespace com.google.zxing.oned\r
23 {\r
24         \r
25         /// <summary> <p>Decodes Code 128 barcodes.</p>\r
26         /// \r
27         /// </summary>\r
28         /// <author>  Sean Owen\r
29         /// </author>\r
30         /// <author>www.Redivivus.in (suraj.supekar@redivivus.in) - Ported from ZXING Java Source \r
31         /// </author>\r
32         public sealed class Code128Reader:OneDReader\r
33         {\r
34                 \r
35                 //UPGRADE_NOTE: Final was removed from the declaration of 'CODE_PATTERNS'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"\r
36                 private static readonly int[][] CODE_PATTERNS = new int[][]{new int[]{2, 1, 2, 2, 2, 2}, new int[]{2, 2, 2, 1, 2, 2}, new int[]{2, 2, 2, 2, 2, 1}, new int[]{1, 2, 1, 2, 2, 3}, new int[]{1, 2, 1, 3, 2, 2}, new int[]{1, 3, 1, 2, 2, 2}, new int[]{1, 2, 2, 2, 1, 3}, new int[]{1, 2, 2, 3, 1, 2}, new int[]{1, 3, 2, 2, 1, 2}, new int[]{2, 2, 1, 2, 1, 3}, new int[]{2, 2, 1, 3, 1, 2}, new int[]{2, 3, 1, 2, 1, 2}, new int[]{1, 1, 2, 2, 3, 2}, new int[]{1, 2, 2, 1, 3, 2}, new int[]{1, 2, 2, 2, 3, 1}, new int[]{1, 1, 3, 2, 2, 2}, new int[]{1, 2, 3, 1, 2, 2}, new int[]{1, 2, 3, 2, 2, 1}, new int[]{2, 2, 3, 2, 1, 1}, new int[]{2, 2, 1, 1, 3, 2}, new int[]{2, 2, 1, 2, 3, 1}, new int[]{2, 1, 3, 2, 1, 2}, new int[]{2, 2, 3, 1, 1, 2}, new int[]{3, 1, 2, 1, 3, 1}, new int[]{3, 1, 1, 2, 2, 2}, new int[]{3, 2, 1, 1, 2, 2}, new int[]{3, 2, 1, 2, 2, 1}, new int[]{3, 1, 2, 2, 1, 2}, new int[]{3, 2, 2, 1, 1, 2}, new int[]{3, 2, 2, 2, 1, 1}, new int[]{2, 1, 2, 1, 2, 3}, new int[]{2, 1, 2, 3, 2, 1}, new int[]{2, 3, 2, 1, 2, 1}, new int[]{1, 1, 1, 3, 2, 3}, new int[]{1, 3, 1, 1, 2, 3}, new int[]{1, 3, 1, 3, 2, 1}, new int[]{1, 1, 2, 3, 1, 3}, new int[]{1, 3, 2, 1, 1, 3}, new int[]{1, 3, 2, 3, 1, 1}, new int[]{2, 1, 1, 3, 1, 3}, new int[]{2, 3, 1, 1, 1, 3}, new int[]{2, 3, 1, 3, 1, 1}, new int[]{1, 1, 2, 1, 3, 3}, new int[]{1, 1, 2, 3, 3, 1}, new int[]{1, 3, 2, 1, 3, 1}, new int[]{1, 1, 3, 1, 2, 3}, new int[]{1, 1, 3, 3, 2, 1}, new int[]{1, 3, 3, 1, 2, 1}, new int[]{3, 1, 3, 1, 2, 1}, new int[]{2, 1, 1, 3, 3, 1}, new int[]{2, 3, 1, 1, 3, 1}, new int[]{2, 1, 3, 1, 1, 3}, new int[]{2, 1, 3, 3, 1, 1}, new int[]{2, 1, 3, 1, 3, 1}, new int[]{3, 1, 1, 1, 2, 3}, new int[]{3, 1, 1, 3, 2, 1}, new int[]{3, 3, 1, 1, 2, 1}, new int[]{3, 1, 2, 1, 1, 3}, new int[]{3, 1, 2, 3, 1, 1}, new int[]{3, 3, 2, 1, 1, 1}, new int[]{3, 1, 4, 1, 1, 1}, new int[]{2, 2, 1, 4, 1, 1}, new int[]{4, 3, 1, 1, 1, 1}, new int[]{1, 1, 1, 2, 2, 4}, new int[]{1, 1, 1, 4, 2, 2}, new int[]{1, 2, 1, 1, 2, 4}, new int[]{1, 2, 1, 4, 2, 1}, new int[]{1, 4, 1, 1, 2, 2}, new \r
37                         int[]{1, 4, 1, 2, 2, 1}, new int[]{1, 1, 2, 2, 1, 4}, new int[]{1, 1, 2, 4, 1, 2}, new int[]{1, 2, 2, 1, 1, 4}, new int[]{1, 2, 2, 4, 1, 1}, new int[]{1, 4, 2, 1, 1, 2}, new int[]{1, 4, 2, 2, 1, 1}, new int[]{2, 4, 1, 2, 1, 1}, new int[]{2, 2, 1, 1, 1, 4}, new int[]{4, 1, 3, 1, 1, 1}, new int[]{2, 4, 1, 1, 1, 2}, new int[]{1, 3, 4, 1, 1, 1}, new int[]{1, 1, 1, 2, 4, 2}, new int[]{1, 2, 1, 1, 4, 2}, new int[]{1, 2, 1, 2, 4, 1}, new int[]{1, 1, 4, 2, 1, 2}, new int[]{1, 2, 4, 1, 1, 2}, new int[]{1, 2, 4, 2, 1, 1}, new int[]{4, 1, 1, 2, 1, 2}, new int[]{4, 2, 1, 1, 1, 2}, new int[]{4, 2, 1, 2, 1, 1}, new int[]{2, 1, 2, 1, 4, 1}, new int[]{2, 1, 4, 1, 2, 1}, new int[]{4, 1, 2, 1, 2, 1}, new int[]{1, 1, 1, 1, 4, 3}, new int[]{1, 1, 1, 3, 4, 1}, new int[]{1, 3, 1, 1, 4, 1}, new int[]{1, 1, 4, 1, 1, 3}, new int[]{1, 1, 4, 3, 1, 1}, new int[]{4, 1, 1, 1, 1, 3}, new int[]{4, 1, 1, 3, 1, 1}, new int[]{1, 1, 3, 1, 4, 1}, new int[]{1, 1, 4, 1, 3, 1}, new int[]{3, 1, 1, 1, 4, 1}, new int[]{4, 1, 1, 1, 3, 1}, new int[]{2, 1, 1, 4, 1, 2}, new int[]{2, 1, 1, 2, 1, 4}, new int[]{2, 1, 1, 2, 3, 2}, new int[]{2, 3, 3, 1, 1, 1, 2}};\r
38                 \r
39                 //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
40                 //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
41                 private static readonly int MAX_AVG_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.25f);\r
42                 //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
43                 //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
44                 private static readonly int MAX_INDIVIDUAL_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.7f);\r
45                 \r
46                 private const int CODE_SHIFT = 98;\r
47                 \r
48                 private const int CODE_CODE_C = 99;\r
49                 private const int CODE_CODE_B = 100;\r
50                 private const int CODE_CODE_A = 101;\r
51                 \r
52                 private const int CODE_FNC_1 = 102;\r
53                 private const int CODE_FNC_2 = 97;\r
54                 private const int CODE_FNC_3 = 96;\r
55                 private const int CODE_FNC_4_A = 101;\r
56                 private const int CODE_FNC_4_B = 100;\r
57                 \r
58                 private const int CODE_START_A = 103;\r
59                 private const int CODE_START_B = 104;\r
60                 private const int CODE_START_C = 105;\r
61                 private const int CODE_STOP = 106;\r
62                 \r
63                 private static int[] findStartPattern(BitArray row)\r
64                 {\r
65                         int width = row.Size;\r
66                         int rowOffset = 0;\r
67                         while (rowOffset < width)\r
68                         {\r
69                                 if (row.get_Renamed(rowOffset))\r
70                                 {\r
71                                         break;\r
72                                 }\r
73                                 rowOffset++;\r
74                         }\r
75                         \r
76                         int counterPosition = 0;\r
77                         int[] counters = new int[6];\r
78                         int patternStart = rowOffset;\r
79                         bool isWhite = false;\r
80                         int patternLength = counters.Length;\r
81                         \r
82                         for (int i = rowOffset; i < width; i++)\r
83                         {\r
84                                 bool pixel = row.get_Renamed(i);\r
85                                 if (pixel ^ isWhite)\r
86                                 {\r
87                                         counters[counterPosition]++;\r
88                                 }\r
89                                 else\r
90                                 {\r
91                                         if (counterPosition == patternLength - 1)\r
92                                         {\r
93                                                 int bestVariance = MAX_AVG_VARIANCE;\r
94                                                 int bestMatch = - 1;\r
95                                                 for (int startCode = CODE_START_A; startCode <= CODE_START_C; startCode++)\r
96                                                 {\r
97                                                         int variance = patternMatchVariance(counters, CODE_PATTERNS[startCode], MAX_INDIVIDUAL_VARIANCE);\r
98                                                         if (variance < bestVariance)\r
99                                                         {\r
100                                                                 bestVariance = variance;\r
101                                                                 bestMatch = startCode;\r
102                                                         }\r
103                                                 }\r
104                                                 if (bestMatch >= 0)\r
105                                                 {\r
106                                                         // Look for whitespace before start pattern, >= 50% of width of start pattern\r
107                                                         if (row.isRange(System.Math.Max(0, patternStart - (i - patternStart) / 2), patternStart, false))\r
108                                                         {\r
109                                                                 return new int[]{patternStart, i, bestMatch};\r
110                                                         }\r
111                                                 }\r
112                                                 patternStart += counters[0] + counters[1];\r
113                                                 for (int y = 2; y < patternLength; y++)\r
114                                                 {\r
115                                                         counters[y - 2] = counters[y];\r
116                                                 }\r
117                                                 counters[patternLength - 2] = 0;\r
118                                                 counters[patternLength - 1] = 0;\r
119                                                 counterPosition--;\r
120                                         }\r
121                                         else\r
122                                         {\r
123                                                 counterPosition++;\r
124                                         }\r
125                                         counters[counterPosition] = 1;\r
126                                         isWhite = !isWhite;\r
127                                 }\r
128                         }\r
129                         throw ReaderException.Instance;\r
130                 }\r
131                 \r
132                 private static int decodeCode(BitArray row, int[] counters, int rowOffset)\r
133                 {\r
134                         recordPattern(row, rowOffset, counters);\r
135                         int bestVariance = MAX_AVG_VARIANCE; // worst variance we'll accept\r
136                         int bestMatch = - 1;\r
137                         for (int d = 0; d < CODE_PATTERNS.Length; d++)\r
138                         {\r
139                                 int[] pattern = CODE_PATTERNS[d];\r
140                                 int variance = patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE);\r
141                                 if (variance < bestVariance)\r
142                                 {\r
143                                         bestVariance = variance;\r
144                                         bestMatch = d;\r
145                                 }\r
146                         }\r
147                         // TODO We're overlooking the fact that the STOP pattern has 7 values, not 6.\r
148                         if (bestMatch >= 0)\r
149                         {\r
150                                 return bestMatch;\r
151                         }\r
152                         else\r
153                         {\r
154                                 throw ReaderException.Instance;\r
155                         }\r
156                 }\r
157                 \r
158                 public override Result decodeRow(int rowNumber, BitArray row, System.Collections.Hashtable hints)\r
159                 {\r
160                         \r
161                         int[] startPatternInfo = findStartPattern(row);\r
162                         int startCode = startPatternInfo[2];\r
163                         int codeSet;\r
164                         switch (startCode)\r
165                         {\r
166                                 \r
167                                 case CODE_START_A: \r
168                                         codeSet = CODE_CODE_A;\r
169                                         break;\r
170                                 \r
171                                 case CODE_START_B: \r
172                                         codeSet = CODE_CODE_B;\r
173                                         break;\r
174                                 \r
175                                 case CODE_START_C: \r
176                                         codeSet = CODE_CODE_C;\r
177                                         break;\r
178                                 \r
179                                 default: \r
180                                         throw ReaderException.Instance;\r
181                                 \r
182                         }\r
183                         \r
184                         bool done = false;\r
185                         bool isNextShifted = false;\r
186                         \r
187                         System.Text.StringBuilder result = new System.Text.StringBuilder(20);\r
188                         int lastStart = startPatternInfo[0];\r
189                         int nextStart = startPatternInfo[1];\r
190                         int[] counters = new int[6];\r
191                         \r
192                         int lastCode = 0;\r
193                         int code = 0;\r
194                         int checksumTotal = startCode;\r
195                         int multiplier = 0;\r
196                         bool lastCharacterWasPrintable = true;\r
197                         \r
198                         while (!done)\r
199                         {\r
200                                 \r
201                                 bool unshift = isNextShifted;\r
202                                 isNextShifted = false;\r
203                                 \r
204                                 // Save off last code\r
205                                 lastCode = code;\r
206                                 \r
207                                 // Decode another code from image\r
208                                 code = decodeCode(row, counters, nextStart);\r
209                                 \r
210                                 // Remember whether the last code was printable or not (excluding CODE_STOP)\r
211                                 if (code != CODE_STOP)\r
212                                 {\r
213                                         lastCharacterWasPrintable = true;\r
214                                 }\r
215                                 \r
216                                 // Add to checksum computation (if not CODE_STOP of course)\r
217                                 if (code != CODE_STOP)\r
218                                 {\r
219                                         multiplier++;\r
220                                         checksumTotal += multiplier * code;\r
221                                 }\r
222                                 \r
223                                 // Advance to where the next code will to start\r
224                                 lastStart = nextStart;\r
225                                 for (int i = 0; i < counters.Length; i++)\r
226                                 {\r
227                                         nextStart += counters[i];\r
228                                 }\r
229                                 \r
230                                 // Take care of illegal start codes\r
231                                 switch (code)\r
232                                 {\r
233                                         \r
234                                         case CODE_START_A: \r
235                                         case CODE_START_B: \r
236                                         case CODE_START_C: \r
237                                                 throw ReaderException.Instance;\r
238                                         }\r
239                                 \r
240                                 switch (codeSet)\r
241                                 {\r
242                                         \r
243                                         \r
244                                         case CODE_CODE_A: \r
245                                                 if (code < 64)\r
246                                                 {\r
247                                                         result.Append((char) (' ' + code));\r
248                                                 }\r
249                                                 else if (code < 96)\r
250                                                 {\r
251                                                         result.Append((char) (code - 64));\r
252                                                 }\r
253                                                 else\r
254                                                 {\r
255                                                         // Don't let CODE_STOP, which always appears, affect whether whether we think the last\r
256                                                         // code was printable or not.\r
257                                                         if (code != CODE_STOP)\r
258                                                         {\r
259                                                                 lastCharacterWasPrintable = false;\r
260                                                         }\r
261                                                         switch (code)\r
262                                                         {\r
263                                                                 \r
264                                                                 case CODE_FNC_1: \r
265                                                                 case CODE_FNC_2: \r
266                                                                 case CODE_FNC_3: \r
267                                                                 case CODE_FNC_4_A: \r
268                                                                         // do nothing?\r
269                                                                         break;\r
270                                                                 \r
271                                                                 case CODE_SHIFT: \r
272                                                                         isNextShifted = true;\r
273                                                                         codeSet = CODE_CODE_B;\r
274                                                                         break;\r
275                                                                 \r
276                                                                 case CODE_CODE_B: \r
277                                                                         codeSet = CODE_CODE_B;\r
278                                                                         break;\r
279                                                                 \r
280                                                                 case CODE_CODE_C: \r
281                                                                         codeSet = CODE_CODE_C;\r
282                                                                         break;\r
283                                                                 \r
284                                                                 case CODE_STOP: \r
285                                                                         done = true;\r
286                                                                         break;\r
287                                                                 }\r
288                                                 }\r
289                                                 break;\r
290                                         \r
291                                         case CODE_CODE_B: \r
292                                                 if (code < 96)\r
293                                                 {\r
294                                                         result.Append((char) (' ' + code));\r
295                                                 }\r
296                                                 else\r
297                                                 {\r
298                                                         if (code != CODE_STOP)\r
299                                                         {\r
300                                                                 lastCharacterWasPrintable = false;\r
301                                                         }\r
302                                                         switch (code)\r
303                                                         {\r
304                                                                 \r
305                                                                 case CODE_FNC_1: \r
306                                                                 case CODE_FNC_2: \r
307                                                                 case CODE_FNC_3: \r
308                                                                 case CODE_FNC_4_B: \r
309                                                                         // do nothing?\r
310                                                                         break;\r
311                                                                 \r
312                                                                 case CODE_SHIFT: \r
313                                                                         isNextShifted = true;\r
314                                                                         codeSet = CODE_CODE_C;\r
315                                                                         break;\r
316                                                                 \r
317                                                                 case CODE_CODE_A: \r
318                                                                         codeSet = CODE_CODE_A;\r
319                                                                         break;\r
320                                                                 \r
321                                                                 case CODE_CODE_C: \r
322                                                                         codeSet = CODE_CODE_C;\r
323                                                                         break;\r
324                                                                 \r
325                                                                 case CODE_STOP: \r
326                                                                         done = true;\r
327                                                                         break;\r
328                                                                 }\r
329                                                 }\r
330                                                 break;\r
331                                         \r
332                                         case CODE_CODE_C: \r
333                                                 if (code < 100)\r
334                                                 {\r
335                                                         if (code < 10)\r
336                                                         {\r
337                                                                 result.Append('0');\r
338                                                         }\r
339                                                         result.Append(code);\r
340                                                 }\r
341                                                 else\r
342                                                 {\r
343                                                         if (code != CODE_STOP)\r
344                                                         {\r
345                                                                 lastCharacterWasPrintable = false;\r
346                                                         }\r
347                                                         switch (code)\r
348                                                         {\r
349                                                                 \r
350                                                                 case CODE_FNC_1: \r
351                                                                         // do nothing?\r
352                                                                         break;\r
353                                                                 \r
354                                                                 case CODE_CODE_A: \r
355                                                                         codeSet = CODE_CODE_A;\r
356                                                                         break;\r
357                                                                 \r
358                                                                 case CODE_CODE_B: \r
359                                                                         codeSet = CODE_CODE_B;\r
360                                                                         break;\r
361                                                                 \r
362                                                                 case CODE_STOP: \r
363                                                                         done = true;\r
364                                                                         break;\r
365                                                                 }\r
366                                                 }\r
367                                                 break;\r
368                                         }\r
369                                 \r
370                                 // Unshift back to another code set if we were shifted\r
371                                 if (unshift)\r
372                                 {\r
373                                         switch (codeSet)\r
374                                         {\r
375                                                 \r
376                                                 case CODE_CODE_A: \r
377                                                         codeSet = CODE_CODE_C;\r
378                                                         break;\r
379                                                 \r
380                                                 case CODE_CODE_B: \r
381                                                         codeSet = CODE_CODE_A;\r
382                                                         break;\r
383                                                 \r
384                                                 case CODE_CODE_C: \r
385                                                         codeSet = CODE_CODE_B;\r
386                                                         break;\r
387                                                 }\r
388                                 }\r
389                         }\r
390                         \r
391                         // Check for ample whitespace following pattern, but, to do this we first need to remember that\r
392                         // we fudged decoding CODE_STOP since it actually has 7 bars, not 6. There is a black bar left\r
393                         // to read off. Would be slightly better to properly read. Here we just skip it:\r
394                         int width = row.Size;\r
395                         while (nextStart < width && row.get_Renamed(nextStart))\r
396                         {\r
397                                 nextStart++;\r
398                         }\r
399                         if (!row.isRange(nextStart, System.Math.Min(width, nextStart + (nextStart - lastStart) / 2), false))\r
400                         {\r
401                                 throw ReaderException.Instance;\r
402                         }\r
403                         \r
404                         // Pull out from sum the value of the penultimate check code\r
405                         checksumTotal -= multiplier * lastCode;\r
406                         // lastCode is the checksum then:\r
407                         if (checksumTotal % 103 != lastCode)\r
408                         {\r
409                                 throw ReaderException.Instance;\r
410                         }\r
411                         \r
412                         // Need to pull out the check digits from string\r
413                         int resultLength = result.Length;\r
414                         // Only bother if the result had at least one character, and if the checksum digit happened to\r
415                         // be a printable character. If it was just interpreted as a control code, nothing to remove.\r
416                         if (resultLength > 0 && lastCharacterWasPrintable)\r
417                         {\r
418                                 if (codeSet == CODE_CODE_C)\r
419                                 {\r
420                                         result.Remove(resultLength - 2, resultLength - (resultLength - 2));\r
421                                 }\r
422                                 else\r
423                                 {\r
424                                         result.Remove(resultLength - 1, resultLength - (resultLength - 1));\r
425                                 }\r
426                         }\r
427                         \r
428                         System.String resultString = result.ToString();\r
429                         \r
430                         if (resultString.Length == 0)\r
431                         {\r
432                                 // Almost surely a false positive\r
433                                 throw ReaderException.Instance;\r
434                         }\r
435                         \r
436                         //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
437                         float left = (float) (startPatternInfo[1] + startPatternInfo[0]) / 2.0f;\r
438                         //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
439                         float right = (float) (nextStart + lastStart) / 2.0f;\r
440                         //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
441                         return new Result(resultString, null, new ResultPoint[]{new ResultPoint(left, (float) rowNumber), new ResultPoint(right, (float) rowNumber)}, BarcodeFormat.CODE_128);\r
442                 }\r
443         }\r
444 }