• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2008 ZXing authors
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.BarcodeFormat;
20 import com.google.zxing.DecodeHintType;
21 import com.google.zxing.FormatException;
22 import com.google.zxing.NotFoundException;
23 import com.google.zxing.Result;
24 import com.google.zxing.ResultMetadataType;
25 import com.google.zxing.ResultPoint;
26 import com.google.zxing.common.BitArray;
27 
28 import java.util.Map;
29 
30 /**
31  * <p>Implements decoding of the ITF format, or Interleaved Two of Five.</p>
32  *
33  * <p>This Reader will scan ITF barcodes of certain lengths only.
34  * At the moment it reads length 6, 8, 10, 12, 14, 16, 18, 20, 24, and 44 as these have appeared "in the wild". Not all
35  * lengths are scanned, especially shorter ones, to avoid false positives. This in turn is due to a lack of
36  * required checksum function.</p>
37  *
38  * <p>The checksum is optional and is not applied by this Reader. The consumer of the decoded
39  * value will have to apply a checksum if required.</p>
40  *
41  * <p><a href="http://en.wikipedia.org/wiki/Interleaved_2_of_5">http://en.wikipedia.org/wiki/Interleaved_2_of_5</a>
42  * is a great reference for Interleaved 2 of 5 information.</p>
43  *
44  * @author kevin.osullivan@sita.aero, SITA Lab.
45  */
46 public final class ITFReader extends OneDReader {
47 
48   private static final float MAX_AVG_VARIANCE = 0.38f;
49   private static final float MAX_INDIVIDUAL_VARIANCE = 0.5f;
50 
51   private static final int W = 3; // Pixel width of a 3x wide line
52   private static final int w = 2; // Pixel width of a 2x wide line
53   private static final int N = 1; // Pixed width of a narrow line
54 
55   /** Valid ITF lengths. Anything longer than the largest value is also allowed. */
56   private static final int[] DEFAULT_ALLOWED_LENGTHS = {6, 8, 10, 12, 14};
57 
58   // Stores the actual narrow line width of the image being decoded.
59   private int narrowLineWidth = -1;
60 
61   /**
62    * Start/end guard pattern.
63    *
64    * Note: The end pattern is reversed because the row is reversed before
65    * searching for the END_PATTERN
66    */
67   private static final int[] START_PATTERN = {N, N, N, N};
68   private static final int[][] END_PATTERN_REVERSED = {
69       {N, N, w}, // 2x
70       {N, N, W}  // 3x
71   };
72 
73   // See ITFWriter.PATTERNS
74 
75   /**
76    * Patterns of Wide / Narrow lines to indicate each digit
77    */
78   private static final int[][] PATTERNS = {
79       {N, N, w, w, N}, // 0
80       {w, N, N, N, w}, // 1
81       {N, w, N, N, w}, // 2
82       {w, w, N, N, N}, // 3
83       {N, N, w, N, w}, // 4
84       {w, N, w, N, N}, // 5
85       {N, w, w, N, N}, // 6
86       {N, N, N, w, w}, // 7
87       {w, N, N, w, N}, // 8
88       {N, w, N, w, N}, // 9
89       {N, N, W, W, N}, // 0
90       {W, N, N, N, W}, // 1
91       {N, W, N, N, W}, // 2
92       {W, W, N, N, N}, // 3
93       {N, N, W, N, W}, // 4
94       {W, N, W, N, N}, // 5
95       {N, W, W, N, N}, // 6
96       {N, N, N, W, W}, // 7
97       {W, N, N, W, N}, // 8
98       {N, W, N, W, N}  // 9
99   };
100 
101   @Override
decodeRow(int rowNumber, BitArray row, Map<DecodeHintType,?> hints)102   public Result decodeRow(int rowNumber, BitArray row, Map<DecodeHintType,?> hints)
103       throws FormatException, NotFoundException {
104 
105     // Find out where the Middle section (payload) starts & ends
106     int[] startRange = decodeStart(row);
107     int[] endRange = decodeEnd(row);
108 
109     StringBuilder result = new StringBuilder(20);
110     decodeMiddle(row, startRange[1], endRange[0], result);
111     String resultString = result.toString();
112 
113     int[] allowedLengths = null;
114     if (hints != null) {
115       allowedLengths = (int[]) hints.get(DecodeHintType.ALLOWED_LENGTHS);
116 
117     }
118     if (allowedLengths == null) {
119       allowedLengths = DEFAULT_ALLOWED_LENGTHS;
120     }
121 
122     // To avoid false positives with 2D barcodes (and other patterns), make
123     // an assumption that the decoded string must be a 'standard' length if it's short
124     int length = resultString.length();
125     boolean lengthOK = false;
126     int maxAllowedLength = 0;
127     for (int allowedLength : allowedLengths) {
128       if (length == allowedLength) {
129         lengthOK = true;
130         break;
131       }
132       if (allowedLength > maxAllowedLength) {
133         maxAllowedLength = allowedLength;
134       }
135     }
136     if (!lengthOK && length > maxAllowedLength) {
137       lengthOK = true;
138     }
139     if (!lengthOK) {
140       throw FormatException.getFormatInstance();
141     }
142 
143     Result resultObject = new Result(
144         resultString,
145         null, // no natural byte representation for these barcodes
146         new ResultPoint[] {new ResultPoint(startRange[1], rowNumber),
147                            new ResultPoint(endRange[0], rowNumber)},
148         BarcodeFormat.ITF);
149     resultObject.putMetadata(ResultMetadataType.SYMBOLOGY_IDENTIFIER, "]I0");
150     return resultObject;
151   }
152 
153   /**
154    * @param row          row of black/white values to search
155    * @param payloadStart offset of start pattern
156    * @param resultString {@link StringBuilder} to append decoded chars to
157    * @throws NotFoundException if decoding could not complete successfully
158    */
decodeMiddle(BitArray row, int payloadStart, int payloadEnd, StringBuilder resultString)159   private static void decodeMiddle(BitArray row,
160                                    int payloadStart,
161                                    int payloadEnd,
162                                    StringBuilder resultString) throws NotFoundException {
163 
164     // Digits are interleaved in pairs - 5 black lines for one digit, and the
165     // 5
166     // interleaved white lines for the second digit.
167     // Therefore, need to scan 10 lines and then
168     // split these into two arrays
169     int[] counterDigitPair = new int[10];
170     int[] counterBlack = new int[5];
171     int[] counterWhite = new int[5];
172 
173     while (payloadStart < payloadEnd) {
174 
175       // Get 10 runs of black/white.
176       recordPattern(row, payloadStart, counterDigitPair);
177       // Split them into each array
178       for (int k = 0; k < 5; k++) {
179         int twoK = 2 * k;
180         counterBlack[k] = counterDigitPair[twoK];
181         counterWhite[k] = counterDigitPair[twoK + 1];
182       }
183 
184       int bestMatch = decodeDigit(counterBlack);
185       resultString.append((char) ('0' + bestMatch));
186       bestMatch = decodeDigit(counterWhite);
187       resultString.append((char) ('0' + bestMatch));
188 
189       for (int counterDigit : counterDigitPair) {
190         payloadStart += counterDigit;
191       }
192     }
193   }
194 
195   /**
196    * Identify where the start of the middle / payload section starts.
197    *
198    * @param row row of black/white values to search
199    * @return Array, containing index of start of 'start block' and end of
200    *         'start block'
201    */
decodeStart(BitArray row)202   private int[] decodeStart(BitArray row) throws NotFoundException {
203     int endStart = skipWhiteSpace(row);
204     int[] startPattern = findGuardPattern(row, endStart, START_PATTERN);
205 
206     // Determine the width of a narrow line in pixels. We can do this by
207     // getting the width of the start pattern and dividing by 4 because its
208     // made up of 4 narrow lines.
209     this.narrowLineWidth = (startPattern[1] - startPattern[0]) / 4;
210 
211     validateQuietZone(row, startPattern[0]);
212 
213     return startPattern;
214   }
215 
216   /**
217    * The start & end patterns must be pre/post fixed by a quiet zone. This
218    * zone must be at least 10 times the width of a narrow line.  Scan back until
219    * we either get to the start of the barcode or match the necessary number of
220    * quiet zone pixels.
221    *
222    * Note: Its assumed the row is reversed when using this method to find
223    * quiet zone after the end pattern.
224    *
225    * ref: http://www.barcode-1.net/i25code.html
226    *
227    * @param row bit array representing the scanned barcode.
228    * @param startPattern index into row of the start or end pattern.
229    * @throws NotFoundException if the quiet zone cannot be found
230    */
validateQuietZone(BitArray row, int startPattern)231   private void validateQuietZone(BitArray row, int startPattern) throws NotFoundException {
232 
233     int quietCount = this.narrowLineWidth * 10;  // expect to find this many pixels of quiet zone
234 
235     // if there are not so many pixel at all let's try as many as possible
236     quietCount = Math.min(quietCount, startPattern);
237 
238     for (int i = startPattern - 1; quietCount > 0 && i >= 0; i--) {
239       if (row.get(i)) {
240         break;
241       }
242       quietCount--;
243     }
244     if (quietCount != 0) {
245       // Unable to find the necessary number of quiet zone pixels.
246       throw NotFoundException.getNotFoundInstance();
247     }
248   }
249 
250   /**
251    * Skip all whitespace until we get to the first black line.
252    *
253    * @param row row of black/white values to search
254    * @return index of the first black line.
255    * @throws NotFoundException Throws exception if no black lines are found in the row
256    */
skipWhiteSpace(BitArray row)257   private static int skipWhiteSpace(BitArray row) throws NotFoundException {
258     int width = row.getSize();
259     int endStart = row.getNextSet(0);
260     if (endStart == width) {
261       throw NotFoundException.getNotFoundInstance();
262     }
263 
264     return endStart;
265   }
266 
267   /**
268    * Identify where the end of the middle / payload section ends.
269    *
270    * @param row row of black/white values to search
271    * @return Array, containing index of start of 'end block' and end of 'end
272    *         block'
273    */
decodeEnd(BitArray row)274   private int[] decodeEnd(BitArray row) throws NotFoundException {
275 
276     // For convenience, reverse the row and then
277     // search from 'the start' for the end block
278     row.reverse();
279     try {
280       int endStart = skipWhiteSpace(row);
281       int[] endPattern;
282       try {
283         endPattern = findGuardPattern(row, endStart, END_PATTERN_REVERSED[0]);
284       } catch (NotFoundException nfe) {
285         endPattern = findGuardPattern(row, endStart, END_PATTERN_REVERSED[1]);
286       }
287 
288       // The start & end patterns must be pre/post fixed by a quiet zone. This
289       // zone must be at least 10 times the width of a narrow line.
290       // ref: http://www.barcode-1.net/i25code.html
291       validateQuietZone(row, endPattern[0]);
292 
293       // Now recalculate the indices of where the 'endblock' starts & stops to
294       // accommodate
295       // the reversed nature of the search
296       int temp = endPattern[0];
297       endPattern[0] = row.getSize() - endPattern[1];
298       endPattern[1] = row.getSize() - temp;
299 
300       return endPattern;
301     } finally {
302       // Put the row back the right way.
303       row.reverse();
304     }
305   }
306 
307   /**
308    * @param row       row of black/white values to search
309    * @param rowOffset position to start search
310    * @param pattern   pattern of counts of number of black and white pixels that are
311    *                  being searched for as a pattern
312    * @return start/end horizontal offset of guard pattern, as an array of two
313    *         ints
314    * @throws NotFoundException if pattern is not found
315    */
findGuardPattern(BitArray row, int rowOffset, int[] pattern)316   private static int[] findGuardPattern(BitArray row,
317                                         int rowOffset,
318                                         int[] pattern) throws NotFoundException {
319     int patternLength = pattern.length;
320     int[] counters = new int[patternLength];
321     int width = row.getSize();
322     boolean isWhite = false;
323 
324     int counterPosition = 0;
325     int patternStart = rowOffset;
326     for (int x = rowOffset; x < width; x++) {
327       if (row.get(x) != isWhite) {
328         counters[counterPosition]++;
329       } else {
330         if (counterPosition == patternLength - 1) {
331           if (patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE) < MAX_AVG_VARIANCE) {
332             return new int[]{patternStart, x};
333           }
334           patternStart += counters[0] + counters[1];
335           System.arraycopy(counters, 2, counters, 0, counterPosition - 1);
336           counters[counterPosition - 1] = 0;
337           counters[counterPosition] = 0;
338           counterPosition--;
339         } else {
340           counterPosition++;
341         }
342         counters[counterPosition] = 1;
343         isWhite = !isWhite;
344       }
345     }
346     throw NotFoundException.getNotFoundInstance();
347   }
348 
349   /**
350    * Attempts to decode a sequence of ITF black/white lines into single
351    * digit.
352    *
353    * @param counters the counts of runs of observed black/white/black/... values
354    * @return The decoded digit
355    * @throws NotFoundException if digit cannot be decoded
356    */
decodeDigit(int[] counters)357   private static int decodeDigit(int[] counters) throws NotFoundException {
358     float bestVariance = MAX_AVG_VARIANCE; // worst variance we'll accept
359     int bestMatch = -1;
360     int max = PATTERNS.length;
361     for (int i = 0; i < max; i++) {
362       int[] pattern = PATTERNS[i];
363       float variance = patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE);
364       if (variance < bestVariance) {
365         bestVariance = variance;
366         bestMatch = i;
367       } else if (variance == bestVariance) {
368         // if we find a second 'best match' with the same variance, we can not reliably report to have a suitable match
369         bestMatch = -1;
370       }
371     }
372     if (bestMatch >= 0) {
373       return bestMatch % 10;
374     } else {
375       throw NotFoundException.getNotFoundInstance();
376     }
377   }
378 
379 }
380