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