Improved notion of pattern variance in 1D barcode elements, improving decode accuracy...
[zxing.git] / core / src / com / google / zxing / oned / Code128Reader.java
1 /*
2  * Copyright 2008 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.google.zxing.oned;
18
19 import com.google.zxing.ReaderException;
20 import com.google.zxing.Result;
21 import com.google.zxing.ResultPoint;
22 import com.google.zxing.common.BitArray;
23 import com.google.zxing.common.GenericResultPoint;
24
25 /**
26  * <p>Decodes Code 128 barcodes.</p>
27  *
28  * @author srowen@google.com (Sean Owen)
29  */
30 public final class Code128Reader extends AbstractOneDReader {
31
32   private static final int[][] CODE_PATTERNS = {
33       {2, 1, 2, 2, 2, 2}, // 0
34       {2, 2, 2, 1, 2, 2},
35       {2, 2, 2, 2, 2, 1},
36       {1, 2, 1, 2, 2, 3},
37       {1, 2, 1, 3, 2, 2},
38       {1, 3, 1, 2, 2, 2}, // 5
39       {1, 2, 2, 2, 1, 3},
40       {1, 2, 2, 3, 1, 2},
41       {1, 3, 2, 2, 1, 2},
42       {2, 2, 1, 2, 1, 3},
43       {2, 2, 1, 3, 1, 2}, // 10
44       {2, 3, 1, 2, 1, 2},
45       {1, 1, 2, 2, 3, 2},
46       {1, 2, 2, 1, 3, 2},
47       {1, 2, 2, 2, 3, 1},
48       {1, 1, 3, 2, 2, 2}, // 15
49       {1, 2, 3, 1, 2, 2},
50       {1, 2, 3, 2, 2, 1},
51       {2, 2, 3, 2, 1, 1},
52       {2, 2, 1, 1, 3, 2},
53       {2, 2, 1, 2, 3, 1}, // 20
54       {2, 1, 3, 2, 1, 2},
55       {2, 2, 3, 1, 1, 2},
56       {3, 1, 2, 1, 3, 1},
57       {3, 1, 1, 2, 2, 2},
58       {3, 2, 1, 1, 2, 2}, // 25
59       {3, 2, 1, 2, 2, 1},
60       {3, 1, 2, 2, 1, 2},
61       {3, 2, 2, 1, 1, 2},
62       {3, 2, 2, 2, 1, 1},
63       {2, 1, 2, 1, 2, 3}, // 30
64       {2, 1, 2, 3, 2, 1},
65       {2, 3, 2, 1, 2, 1},
66       {1, 1, 1, 3, 2, 3},
67       {1, 3, 1, 1, 2, 3},
68       {1, 3, 1, 3, 2, 1}, // 35
69       {1, 1, 2, 3, 1, 3},
70       {1, 3, 2, 1, 1, 3},
71       {1, 3, 2, 3, 1, 1},
72       {2, 1, 1, 3, 1, 3},
73       {2, 3, 1, 1, 1, 3}, // 40
74       {2, 3, 1, 3, 1, 1},
75       {1, 1, 2, 1, 3, 3},
76       {1, 1, 2, 3, 3, 1},
77       {1, 3, 2, 1, 3, 1},
78       {1, 1, 3, 1, 2, 3}, // 45
79       {1, 1, 3, 3, 2, 1},
80       {1, 3, 3, 1, 2, 1},
81       {3, 1, 3, 1, 2, 1},
82       {2, 1, 1, 3, 3, 1},
83       {2, 3, 1, 1, 3, 1}, // 50
84       {2, 1, 3, 1, 1, 3},
85       {2, 1, 3, 3, 1, 1},
86       {2, 1, 3, 1, 3, 1},
87       {3, 1, 1, 1, 2, 3},
88       {3, 1, 1, 3, 2, 1}, // 55
89       {3, 3, 1, 1, 2, 1},
90       {3, 1, 2, 1, 1, 3},
91       {3, 1, 2, 3, 1, 1},
92       {3, 3, 2, 1, 1, 1},
93       {3, 1, 4, 1, 1, 1}, // 60
94       {2, 2, 1, 4, 1, 1},
95       {4, 3, 1, 1, 1, 1},
96       {1, 1, 1, 2, 2, 4},
97       {1, 1, 1, 4, 2, 2},
98       {1, 2, 1, 1, 2, 4}, // 65
99       {1, 2, 1, 4, 2, 1},
100       {1, 4, 1, 1, 2, 2},
101       {1, 4, 1, 2, 2, 1},
102       {1, 1, 2, 2, 1, 4},
103       {1, 1, 2, 4, 1, 2}, // 70
104       {1, 2, 2, 1, 1, 4},
105       {1, 2, 2, 4, 1, 1},
106       {1, 4, 2, 1, 1, 2},
107       {1, 4, 2, 2, 1, 1},
108       {2, 4, 1, 2, 1, 1}, // 75
109       {2, 2, 1, 1, 1, 4},
110       {4, 1, 3, 1, 1, 1},
111       {2, 4, 1, 1, 1, 2},
112       {1, 3, 4, 1, 1, 1},
113       {1, 1, 1, 2, 4, 2}, // 80
114       {1, 2, 1, 1, 4, 2},
115       {1, 2, 1, 2, 4, 1},
116       {1, 1, 4, 2, 1, 2},
117       {1, 2, 4, 1, 1, 2},
118       {1, 2, 4, 2, 1, 1}, // 85
119       {4, 1, 1, 2, 1, 2},
120       {4, 2, 1, 1, 1, 2},
121       {4, 2, 1, 2, 1, 1},
122       {2, 1, 2, 1, 4, 1},
123       {2, 1, 4, 1, 2, 1}, // 90
124       {4, 1, 2, 1, 2, 1},
125       {1, 1, 1, 1, 4, 3},
126       {1, 1, 1, 3, 4, 1},
127       {1, 3, 1, 1, 4, 1},
128       {1, 1, 4, 1, 1, 3}, // 95
129       {1, 1, 4, 3, 1, 1},
130       {4, 1, 1, 1, 1, 3},
131       {4, 1, 1, 3, 1, 1},
132       {1, 1, 3, 1, 4, 1},
133       {1, 1, 4, 1, 3, 1}, // 100
134       {3, 1, 1, 1, 4, 1},
135       {4, 1, 1, 1, 3, 1},
136       {2, 1, 1, 4, 1, 2},
137       {2, 1, 1, 2, 1, 4},
138       {2, 1, 1, 2, 3, 2}, // 105
139       {2, 3, 3, 1, 1, 1, 2}
140   };
141
142   private static final float MAX_VARIANCE = 0.3f;
143
144   private static final int CODE_SHIFT = 98;
145
146   private static final int CODE_CODE_C = 99;
147   private static final int CODE_CODE_B = 100;
148   private static final int CODE_CODE_A = 101;
149
150   private static final int CODE_FNC_1 = 102;
151   private static final int CODE_FNC_2 = 97;
152   private static final int CODE_FNC_3 = 96;
153   private static final int CODE_FNC_4_A = 101;
154   private static final int CODE_FNC_4_B = 100;
155
156   private static final int CODE_START_A = 103;
157   private static final int CODE_START_B = 104;
158   private static final int CODE_START_C = 105;
159   private static final int CODE_STOP = 106;
160
161   private static int[] findStartPattern(BitArray row) throws ReaderException {
162     int width = row.getSize();
163     int rowOffset = 0;
164     while (rowOffset < width) {
165       if (row.get(rowOffset)) {
166         break;
167       }
168       rowOffset++;
169     }
170
171     int counterPosition = 0;
172     int[] counters = new int[6];
173     int patternStart = rowOffset;
174     boolean isWhite = false;
175     int patternLength = counters.length;
176
177     for (int i = rowOffset; i < width; i++) {
178       boolean pixel = row.get(i);
179       if ((!pixel && isWhite) || (pixel && !isWhite)) {
180         counters[counterPosition]++;
181       } else {
182         if (counterPosition == patternLength - 1) {
183           float bestVariance = MAX_VARIANCE;
184           int bestMatch = -1;
185           for (int startCode = CODE_START_A; startCode <= CODE_START_C; startCode++) {
186             float variance = patternMatchVariance(counters, CODE_PATTERNS[startCode]);
187             if (variance < bestVariance) {
188               bestVariance = variance;
189               bestMatch = startCode;
190             }
191           }
192           if (bestMatch >= 0) {
193             return new int[]{patternStart, i, bestMatch};
194           }
195           patternStart += counters[0] + counters[1];
196           for (int y = 2; y < patternLength; y++) {
197             counters[y - 2] = counters[y];
198           }
199           counterPosition--;
200         } else {
201           counterPosition++;
202         }
203         counters[counterPosition] = 1;
204         isWhite = !isWhite;
205       }
206     }
207     throw new ReaderException("Can't find pattern");
208   }
209
210   private static int decodeCode(BitArray row, int[] counters, int rowOffset) throws ReaderException {
211     recordPattern(row, rowOffset, counters);
212     float bestVariance = MAX_VARIANCE; // worst variance we'll accept
213     int bestMatch = -1;
214     for (int d = 0; d < CODE_PATTERNS.length; d++) {
215       int[] pattern = CODE_PATTERNS[d];
216       float variance = patternMatchVariance(counters, pattern);
217       if (variance < bestVariance) {
218         bestVariance = variance;
219         bestMatch = d;
220       }
221     }
222     // TODO We're overlooking the fact that the STOP pattern has 7 values, not 6
223     if (bestMatch >= 0) {
224       return bestMatch;
225     } else {
226       throw new ReaderException("Could not match any code pattern");
227     }
228   }
229
230   public Result decodeRow(final int rowNumber, final BitArray row) throws ReaderException {
231
232     int[] startPatternInfo = findStartPattern(row);
233     int startCode = startPatternInfo[2];
234     int codeSet;
235     switch (startCode) {
236       case CODE_START_A:
237         codeSet = CODE_CODE_A;
238         break;
239       case CODE_START_B:
240         codeSet = CODE_CODE_B;
241         break;
242       case CODE_START_C:
243         codeSet = CODE_CODE_C;
244         break;
245       default:
246         throw new ReaderException("Illegal start code");
247     }
248
249     boolean done = false;
250     boolean isNextShifted = false;
251
252     StringBuffer result = new StringBuffer();
253     int lastStart = startPatternInfo[0];
254     int nextStart = startPatternInfo[1];
255     int[] counters = new int[6];
256
257     int lastCode = 0;
258     int code = 0;
259     int checksumTotal = startCode;
260     int multiplier = 0;
261
262     while (!done) {
263
264       boolean unshift = isNextShifted;
265       isNextShifted = false;
266
267       lastCode = code;
268       code = decodeCode(row, counters, nextStart);
269       if (code != CODE_STOP) {
270         multiplier++;
271         checksumTotal += multiplier * code;
272       }
273
274       lastStart = nextStart;
275       for (int i = 0; i < counters.length; i++) {
276         nextStart += counters[i];
277       }
278
279       // Take care of illegal start codes
280       switch (code) {
281         case CODE_START_A:
282         case CODE_START_B:
283         case CODE_START_C:
284           throw new ReaderException("Unexpected start code");
285       }
286
287       switch (codeSet) {
288
289         case CODE_CODE_A:
290           if (code < 64) {
291             result.append((char) (' ' + code));
292           } else if (code < 96) {
293             result.append((char) (code - 64));
294           } else {
295             switch (code) {
296               case CODE_FNC_1:
297               case CODE_FNC_2:
298               case CODE_FNC_3:
299               case CODE_FNC_4_A:
300                 // do nothing?
301                 break;
302               case CODE_SHIFT:
303                 isNextShifted = true;
304                 codeSet = CODE_CODE_B;
305                 break;
306               case CODE_CODE_B:
307                 codeSet = CODE_CODE_B;
308                 break;
309               case CODE_CODE_C:
310                 codeSet = CODE_CODE_C;
311                 break;
312               case CODE_STOP:
313                 done = true;
314                 break;
315             }
316           }
317           break;
318         case CODE_CODE_B:
319           if (code < 96) {
320             result.append((char) (' ' + code));
321           } else {
322             switch (code) {
323               case CODE_FNC_1:
324               case CODE_FNC_2:
325               case CODE_FNC_3:
326               case CODE_FNC_4_B:
327                 // do nothing?
328                 break;
329               case CODE_SHIFT:
330                 isNextShifted = true;
331                 codeSet = CODE_CODE_C;
332                 break;
333               case CODE_CODE_A:
334                 codeSet = CODE_CODE_A;
335                 break;
336               case CODE_CODE_C:
337                 codeSet = CODE_CODE_C;
338                 break;
339               case CODE_STOP:
340                 done = true;
341                 break;
342             }
343           }
344           break;
345         case CODE_CODE_C:
346           if (code < 100) {
347             if (code < 10) {
348               result.append('0');
349             }
350             result.append(code);
351           } else {
352             switch (code) {
353               case CODE_FNC_1:
354                 // do nothing?
355                 break;
356               case CODE_CODE_A:
357                 codeSet = CODE_CODE_A;
358                 break;
359               case CODE_CODE_B:
360                 codeSet = CODE_CODE_B;
361                 break;
362               case CODE_STOP:
363                 done = true;
364                 break;
365             }
366           }
367           break;
368       }
369
370       if (unshift) {
371         switch (codeSet) {
372           case CODE_CODE_A:
373             codeSet = CODE_CODE_C;
374             break;
375           case CODE_CODE_B:
376             codeSet = CODE_CODE_A;
377             break;
378           case CODE_CODE_C:
379             codeSet = CODE_CODE_B;
380             break;
381         }
382       }
383
384     }
385
386     // Pull out from sum the value of the penultimate check code
387     checksumTotal -= multiplier * lastCode;
388     if (checksumTotal % 103 != lastCode) {
389       throw new ReaderException("Checksum failed");
390     }
391
392     // Need to pull out the check digits from string
393     int resultLength = result.length();
394     if (resultLength > 0) {
395       if (codeSet == CODE_CODE_C) {
396         result.delete(resultLength - 2, resultLength);
397       } else {
398         result.delete(resultLength - 1, resultLength);
399       }
400     }
401
402     String resultString = result.toString();
403     return new Result(resultString,
404         new ResultPoint[]{new GenericResultPoint((float) (startPatternInfo[1] - startPatternInfo[0]) / 2.0f,
405             (float) rowNumber),
406             new GenericResultPoint((float) (nextStart - lastStart) / 2.0f,
407                 (float) rowNumber)});
408
409   }
410
411 }