Major refactoring of 1D barcode code. Moved into com.google.zxing.oned package. Misc...
[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.Result;
20 import com.google.zxing.ReaderException;
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.4f;
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           for (int startCode = CODE_START_A; startCode <= CODE_START_C; startCode++) {
184             if (patternMatchVariance(counters, CODE_PATTERNS[startCode]) < MAX_VARIANCE) {
185               return new int[] {patternStart, i, startCode};
186             }
187           }
188           patternStart += counters[0] + counters[1];
189           for (int y = 2; y < patternLength; y++) {
190             counters[y - 2] = counters[y];
191           }
192           counterPosition--;
193         } else {
194           counterPosition++;
195         }
196         counters[counterPosition] = 1;
197         isWhite = !isWhite;
198       }
199     }
200     throw new ReaderException("Can't find pattern");
201   }
202
203   private static int decodeCode(BitArray row, int[] counters, int rowOffset) throws ReaderException {
204     recordPattern(row, rowOffset, counters);
205     float bestVariance = 0.4f; // worst variance we'll accept
206     int bestMatch = -1;
207     for (int d = 0; d < CODE_PATTERNS.length; d++) {
208       int[] pattern = CODE_PATTERNS[d];
209       float variance = patternMatchVariance(counters, pattern);
210       if (variance < bestVariance) {
211         bestVariance = variance;
212         bestMatch = d;
213       }
214     }
215     // TODO We're overlooking the fact that the STOP pattern has 7 values, not 6
216     if (bestMatch >= 0) {
217       return bestMatch;
218     } else {
219       throw new ReaderException("Could not match any code pattern");
220     }
221   }
222
223   public Result decodeRow(final int rowNumber, final BitArray row) throws ReaderException {
224
225     int[] startPatternInfo = findStartPattern(row);
226     int startCode = startPatternInfo[2];
227     int codeSet;
228     switch (startCode) {
229       case CODE_START_A:
230         codeSet = CODE_CODE_A;
231         break;
232       case CODE_START_B:
233         codeSet = CODE_CODE_B;
234         break;
235       case CODE_START_C:
236         codeSet = CODE_CODE_C;
237         break;
238       default:
239         throw new ReaderException("Illegal start code");
240     }
241
242     boolean done = false;
243     boolean isNextShifted = false;
244
245     StringBuffer result = new StringBuffer();
246     int lastStart = startPatternInfo[0];
247     int nextStart = startPatternInfo[1];
248     int[] counters = new int[6];
249
250     int lastCode = 0;
251     int code = 0;
252     int checksumTotal = startCode;
253     int multiplier = 0;
254
255     while (!done) {
256
257       boolean unshift = isNextShifted;
258       isNextShifted = false;
259
260       lastCode = code;
261       code = decodeCode(row, counters, nextStart);
262       if (code != CODE_STOP) {
263         multiplier++;
264         checksumTotal += multiplier * code;
265       }
266
267       lastStart = nextStart;
268       for (int i = 0; i < counters.length; i++) {
269         nextStart += counters[i];
270       }
271
272       // Take care of illegal start codes
273       switch (code) {
274         case CODE_START_A:
275         case CODE_START_B:
276         case CODE_START_C:
277           throw new ReaderException("Unexpected start code");
278       }
279
280       switch (codeSet) {
281
282         case CODE_CODE_A:
283           if (code < 64) {
284             result.append((char) (' ' + code));
285           } else if (code < 96) {
286             result.append((char) (code - 64));
287           } else {
288             switch (code) {
289               case CODE_FNC_1:
290               case CODE_FNC_2:
291               case CODE_FNC_3:
292               case CODE_FNC_4_A:
293                 // do nothing?
294                 break;
295               case CODE_SHIFT:
296                 isNextShifted = true;
297                 codeSet = CODE_CODE_B;
298                 break;
299               case CODE_CODE_B:
300                 codeSet = CODE_CODE_B;
301                 break;
302               case CODE_CODE_C:
303                 codeSet = CODE_CODE_C;
304                 break;
305               case CODE_STOP:
306                 done = true;
307                 break;
308             }
309           }
310           break;
311         case CODE_CODE_B:
312           if (code < 96) {
313             result.append((char) (' ' + code));
314           } else {
315             switch (code) {
316               case CODE_FNC_1:
317               case CODE_FNC_2:
318               case CODE_FNC_3:
319               case CODE_FNC_4_B:
320                 // do nothing?
321                 break;
322               case CODE_SHIFT:
323                 isNextShifted = true;
324                 codeSet = CODE_CODE_C;
325                 break;
326               case CODE_CODE_A:
327                 codeSet = CODE_CODE_A;
328                 break;
329               case CODE_CODE_C:
330                 codeSet = CODE_CODE_C;
331                 break;
332               case CODE_STOP:
333                 done = true;
334                 break;
335             }
336           }
337           break;
338         case CODE_CODE_C:
339           if (code < 100) {
340             if (code < 10) {
341               result.append('0');
342             }
343             result.append(code);
344           } else {
345             switch (code) {
346               case CODE_FNC_1:
347                 // do nothing?
348                 break;
349               case CODE_CODE_A:
350                 codeSet = CODE_CODE_A;
351                 break;
352               case CODE_CODE_B:
353                 codeSet = CODE_CODE_B;
354                 break;
355               case CODE_STOP:
356                 done = true;
357                 break;
358             }
359           }
360           break;
361       }
362
363       if (unshift) {
364         switch (codeSet) {
365           case CODE_CODE_A:
366             codeSet = CODE_CODE_C;
367             break;
368           case CODE_CODE_B:
369             codeSet = CODE_CODE_A;
370             break;
371           case CODE_CODE_C:
372             codeSet = CODE_CODE_B;
373             break;
374         }
375       }
376
377     }
378
379     // Pull out from sum the value of the penultimate check code
380     checksumTotal -= multiplier * lastCode;
381     if (checksumTotal % 103 != lastCode) {
382       throw new ReaderException("Checksum failed");
383     }
384
385     // Need to pull out the check digits from string
386     int resultLength = result.length();
387     if (resultLength > 0) {
388       if (codeSet == CODE_CODE_C) {
389         result.delete(resultLength - 2, resultLength);
390       } else {
391         result.delete(resultLength - 1, resultLength);
392       }
393     }
394
395     String resultString = result.toString();
396     return new Result(resultString,
397         new ResultPoint[]{new GenericResultPoint((float) (startPatternInfo[1] - startPatternInfo[0]) / 2.0f,
398             (float) rowNumber),
399             new GenericResultPoint((float) (nextStart - lastStart) / 2.0f,
400                 (float) rowNumber)});
401
402   }
403
404 }