ff677cbc4e2de6572ae8da36a9c4b6cfb08d059c
[zxing.git] / core / src / com / google / zxing / oned / CodaBarReader.java
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 \r
17 package com.google.zxing.oned;\r
18 \r
19 import com.google.zxing.BarcodeFormat;\r
20 import com.google.zxing.FormatException;\r
21 import com.google.zxing.NotFoundException;\r
22 import com.google.zxing.Result;\r
23 import com.google.zxing.ResultPoint;\r
24 import com.google.zxing.common.BitArray;\r
25 import java.util.Hashtable;\r
26 \r
27 /**\r
28  * <p>Decodes Codabar barcodes. </p>\r
29  *\r
30  * @author Bas Vijfwinkel\r
31  */\r
32 public final class CodaBarReader extends OneDReader {\r
33 \r
34         private static  final String ALPHABET_STRING = "0123456789-$:/.+ABCDTN";\r
35         private static final char[] ALPHABET = ALPHABET_STRING.toCharArray();\r
36 \r
37         /**\r
38     * These represent the encodings of characters, as patterns of wide and narrow bars.\r
39     * The 7 least-significant bits of each int correspond to the pattern of wide and narrow,\r
40     * with 1s representing "wide" and 0s representing narrow.\r
41         * NOTE : c is equal to the  * pattern \r
42         * NOTE : d is equal to the e pattern\r
43     */\r
44 \r
45         private static final int[] CHARACTER_ENCODINGS = {\r
46         0x003, 0x006, 0x009, 0x060, 0x012, 0x042, 0x021, 0x024, 0x030, 0x048, // 0-9\r
47         0x00c, 0x018, 0x025, 0x051, 0x054, 0x015, 0x01A, 0x029, 0x00B, 0x00E, // -$:/.+ABCD\r
48         0x01A,0x029 //TN              \r
49         };\r
50         \r
51         // multiple start/end patterns\r
52         // official start and end patterns\r
53     //private static  final char[] STARTEND_ENCODING = {'$','A','B','C','D','T','N','+'}; \r
54         // some codabar generator allow the codabar string to be closed by every character\r
55         private static  final char[] STARTEND_ENCODING = {'0','1','2','3','4','5','6','7','8','9','-','$',':','/','.','+','A','B','C','D','T','N'}; \r
56 \r
57         public CodaBarReader() \r
58         {\r
59         }\r
60 \r
61 \r
62     public Result decodeRow(int rowNumber, BitArray row, Hashtable hints) throws FormatException, NotFoundException\r
63         {\r
64                 int[] start;\r
65                 start = findAsteriskPattern(row);\r
66                 start[1] = 0; // BAS: settings this to 0 improves the recognition rate somehow?\r
67             int nextStart = start[1];\r
68                 int end = row.getSize();\r
69                 \r
70                 // Read off white space\r
71                 while (nextStart < end && !row.get(nextStart)) \r
72                 {\r
73                         nextStart++;\r
74                 }\r
75 \r
76             StringBuffer result = new StringBuffer();\r
77                 //int[] counters = new int[7];\r
78                 int[] counters;\r
79                 char decodedChar;\r
80                 int lastStart;\r
81                 \r
82             do \r
83                 {\r
84                         counters = new int[] {0,0,0,0,0,0,0} ; // reset counters\r
85                         recordPattern(row, nextStart, counters);\r
86                         \r
87                         decodedChar = toNarrowWidePattern(counters);\r
88                         if (decodedChar == '!') \r
89                         {\r
90                                 throw NotFoundException.getNotFoundInstance();\r
91                         }\r
92                         result.append(decodedChar);\r
93                         lastStart = nextStart;\r
94                         for (int i = 0; i < counters.length; i++) \r
95                         {\r
96                                 nextStart += counters[i];\r
97                         }\r
98                         \r
99                         // Read off white space\r
100                         while (nextStart < end && !row.get(nextStart)) \r
101                         {\r
102                                 nextStart++;\r
103                         }\r
104                 } while (nextStart < end); // no fixed end pattern so keep on reading while data is available\r
105 \r
106                 // find last character in STARTEND_ENCODING\r
107                 for (int k = result.length()-1;k >= 0;k--)\r
108                 {\r
109                         if (arrayContains(STARTEND_ENCODING,result.charAt(k)))\r
110                         {\r
111                                 // valid character -> remove and break out of loop\r
112                                 result.deleteCharAt(k);\r
113                                 k=-1;// break out of loop\r
114                         }\r
115                         else\r
116                         {\r
117                                 // not a valid character -> remove anyway\r
118                                 result.deleteCharAt(k);\r
119                         }\r
120                 }\r
121                 \r
122                 \r
123                 // remove first character\r
124                 if (result.length() > 0) {result.deleteCharAt(0);}\r
125 \r
126                 // Look for whitespace after pattern:\r
127         int lastPatternSize = 0;\r
128         for (int i = 0; i < counters.length; i++) \r
129                 {\r
130               lastPatternSize += counters[i];\r
131         }\r
132         int whiteSpaceAfterEnd = nextStart - lastStart - lastPatternSize;\r
133         // If 50% of last pattern size, following last pattern, is not whitespace, fail\r
134          // (but if it's whitespace to the very end of the image, that's OK)\r
135         if ((nextStart) != end && (whiteSpaceAfterEnd / 2 < lastPatternSize)) \r
136                 {\r
137               throw NotFoundException.getNotFoundInstance();\r
138         }\r
139                 \r
140 \r
141                 String resultString = result.toString();\r
142                 if (resultString.length() == 0) \r
143                 {\r
144                   // Almost surely a false positive\r
145                   throw NotFoundException.getNotFoundInstance();\r
146                 }\r
147                 \r
148                 float left = (float) (start[1] + start[0]) / 2.0f;\r
149                 float right = (float) (nextStart + lastStart) / 2.0f;\r
150                 return new Result(\r
151                                                 resultString,\r
152                                                 null,\r
153                                                 new ResultPoint[]{\r
154                                                         new ResultPoint(left, (float) rowNumber),\r
155                                                         new ResultPoint(right, (float) rowNumber)},\r
156                                                         BarcodeFormat.CODABAR);\r
157   }\r
158 \r
159   private static int[] findAsteriskPattern(BitArray row) throws NotFoundException {\r
160     int width = row.getSize();\r
161     int rowOffset = 0;\r
162     while (rowOffset < width) {\r
163       if (row.get(rowOffset)) {\r
164         break;\r
165       }\r
166       rowOffset++;\r
167     }\r
168 \r
169     int counterPosition = 0;\r
170     int[] counters = new int[7];\r
171     int patternStart = rowOffset;\r
172     boolean isWhite = false;\r
173     int patternLength = counters.length;\r
174 \r
175     for (int i = rowOffset; i < width; i++) {\r
176       boolean pixel = row.get(i);\r
177       if (pixel ^ isWhite) {\r
178         counters[counterPosition]++;\r
179       } else {\r
180         if (counterPosition == patternLength - 1) {\r
181           try {\r
182                         if (arrayContains(STARTEND_ENCODING,toNarrowWidePattern(counters)))\r
183                         {\r
184               // Look for whitespace before start pattern, >= 50% of width of start pattern\r
185               if (row.isRange(Math.max(0, patternStart - (i - patternStart) / 2), patternStart, false)) {\r
186                 return new int[]{patternStart, i};\r
187               }\r
188             }\r
189           } catch (IllegalArgumentException re) {\r
190             // no match, continue\r
191           }\r
192           patternStart += counters[0] + counters[1];\r
193           for (int y = 2; y < patternLength; y++) {\r
194             counters[y - 2] = counters[y];\r
195           }\r
196           counters[patternLength - 2] = 0;\r
197           counters[patternLength - 1] = 0;\r
198           counterPosition--;\r
199         } else {\r
200           counterPosition++;\r
201         }\r
202         counters[counterPosition] = 1;\r
203         isWhite ^= true; // isWhite = !isWhite;\r
204       }\r
205     }\r
206     throw NotFoundException.getNotFoundInstance();\r
207   }\r
208 \r
209   private static boolean arrayContains(char[] array, char key)\r
210   {\r
211                 if (array != null)\r
212                 {\r
213                         for (int i=0;i<array.length;i++)\r
214                         {\r
215                                 if (array[i] == key) { return true; }\r
216                         }\r
217                 }\r
218                 return false;\r
219   }\r
220 \r
221     private static char toNarrowWidePattern(int[] counters) {\r
222         // BAS : I have changed the following part because some codabar images would fail with the original routine \r
223         //        I took from the Code39Reader.java file\r
224         // ----------- change start\r
225     int numCounters = counters.length;\r
226     int maxNarrowCounter = 0;\r
227     int wideCounters;\r
228 \r
229         int minCounter = Integer.MAX_VALUE;\r
230         for (int i = 0; i < numCounters; i++) \r
231         { \r
232                 if (counters[i] < minCounter) { minCounter = counters[i]; }\r
233                 if (counters[i] > maxNarrowCounter) { maxNarrowCounter = counters[i]; }\r
234         }\r
235         // ---------- change end        \r
236 \r
237 \r
238     do \r
239         {\r
240           wideCounters = 0;\r
241       int totalWideCountersWidth = 0;\r
242       int pattern = 0;\r
243       for (int i = 0; i < numCounters; i++) {\r
244         int counter = counters[i];\r
245         if (counters[i] > maxNarrowCounter) {\r
246           pattern |= 1 << (numCounters - 1 - i);\r
247           wideCounters++;\r
248           totalWideCountersWidth += counter;\r
249         }\r
250       }\r
251           \r
252       if ((wideCounters == 2) || (wideCounters == 3)) \r
253           {\r
254         for (int i = 0; i < CHARACTER_ENCODINGS.length; i++) \r
255                 {\r
256           if (CHARACTER_ENCODINGS[i] == pattern) \r
257                   {\r
258                         return ALPHABET[i];\r
259               }\r
260             }\r
261       }\r
262           maxNarrowCounter--;\r
263     } while (maxNarrowCounter > minCounter);\r
264     return '!';\r
265   }\r
266 \r
267 }\r