Some formatting changes, and a few tiny optimizations
authorsrowen <srowen@59b500cc-1b3d-0410-9834-0bbf25fbcc57>
Mon, 1 Dec 2008 21:36:58 +0000 (21:36 +0000)
committersrowen <srowen@59b500cc-1b3d-0410-9834-0bbf25fbcc57>
Mon, 1 Dec 2008 21:36:58 +0000 (21:36 +0000)
git-svn-id: http://zxing.googlecode.com/svn/trunk@767 59b500cc-1b3d-0410-9834-0bbf25fbcc57

core/src/com/google/zxing/oned/ITFReader.java

index cdc1d2c..676f9b3 100644 (file)
@@ -26,335 +26,307 @@ import com.google.zxing.common.GenericResultPoint;
 import java.util.Hashtable;\r
 \r
 /**\r
- *<p>\r
- * Implements decoding of the ITF format.\r
- * </p>\r
- *<p>\r
- * "ITF" stands for Interleaved Two of Five. This Reader will scan ITF barcode with 6, 10 or 14 digits. \r
- * The checksum is optional and is not applied by this Reader. The consumer of the decoded value will have to apply a checksum if\r
- * required.\r
- * </p>\r
- * \r
- *<p>\r
- * <a\r
- * href="http://en.wikipedia.org/wiki/Interleaved_2_of_5">http://en.wikipedia.\r
- * org/wiki/Interleaved_2_of_5</a> is a great reference for Interleaved 2 of 5\r
- * information.\r
- * </p>\r
- * \r
+ * <p>Implements decoding of the ITF format.</p>\r
+ *\r
+ * <p>"ITF" stands for Interleaved Two of Five. This Reader will scan ITF barcode with 6, 10 or 14 digits.\r
+ * The checksum is optional and is not applied by this Reader. The consumer of the decoded value\r
+ * will have to apply a checksum if required.</p>\r
+ *\r
+ * <p><a href="http://en.wikipedia.org/wiki/Interleaved_2_of_5">http://en.wikipedia.org/wiki/Interleaved_2_of_5</a>\r
+ * is a great reference for Interleaved 2 of 5 information.</p>\r
+ *\r
  * @author kevin.osullivan@sita.aero, SITA Lab.\r
  */\r
-public class ITFReader extends AbstractOneDReader {\r
-\r
-       private static final int MAX_AVG_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.42f);\r
-       private static final int MAX_INDIVIDUAL_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.8f);\r
-\r
-       private static final int W = 3; // Pixel width of a wide line\r
-       private static final int N = 1; // Pixed width of a narrow line\r
-\r
-       // Stores the actual narrow line width of the image being decoded.\r
-       private int narrowLineWidth = -1;\r
-\r
-       /**\r
-        * Start/end guard pattern.\r
-        * \r
-        * Note: The end pattern is reversed because the row is reversed before\r
-        * searching for the END_PATTERN\r
-        */\r
-       private static final int[] START_PATTERN = { N, N, N, N };\r
-       private static final int[] END_PATTERN_REVERSED = { N, N, W };\r
-\r
-       /**\r
-        * Patterns of Wide / Narrow lines to indicate each digit\r
-        */\r
-       static final int[][] PATTERNS = { { N, N, W, W, N }, // 0\r
-                       { W, N, N, N, W }, // 1\r
-                       { N, W, N, N, W }, // 2\r
-                       { W, W, N, N, N }, // 3\r
-                       { N, N, W, N, W }, // 4\r
-                       { W, N, W, N, N }, // 5\r
-                       { N, W, W, N, N }, // 6\r
-                       { N, N, N, W, W }, // 7\r
-                       { W, N, N, W, N }, // 8\r
-                       { N, W, N, W, N } // 9\r
-       };\r
-\r
-       public final Result decodeRow(int rowNumber, BitArray row, Hashtable hints) throws ReaderException {\r
-\r
-               StringBuffer result = new StringBuffer(20);\r
-\r
-               /**\r
-                * Find out where the Middle section (payload) starts & ends\r
-                */\r
-               int[] startRange = decodeStart(row);\r
-               int[] endRange = decodeEnd(row);\r
-\r
-               decodeMiddle(row, startRange[1], endRange[0], result);\r
-\r
-               String resultString = result.toString();\r
-               /**\r
-                * To avoid false positives with 2D barcodes (and other patterns), make\r
-                * an assumption that the decoded string must be 6, 10 or 14 digits.\r
-                */\r
-               if ((resultString.length() != 6 && resultString.length() != 10 && resultString.length() != 14) || \r
-                    resultString.length() % 2 == 1)\r
-                       throw ReaderException.getInstance();\r
-\r
-               return new Result(resultString,\r
-                               null, // no natural byte representation for these barcodes\r
-                               new ResultPoint[] { new GenericResultPoint(startRange[1], (float) rowNumber), new GenericResultPoint(startRange[0], (float) rowNumber) },\r
-                               BarcodeFormat.ITF);\r
-       }\r
-\r
-       /**\r
-        * @param row\r
-        *           row of black/white values to search\r
-        * @param payloadStart\r
-        *           offset of start pattern\r
-        * @param resultString\r
-        *           {@link StringBuffer} to append decoded chars to\r
-        * @throws ReaderException\r
-        *            if decoding could not complete successfully\r
-        */\r
-       protected void decodeMiddle(BitArray row, int payloadStart, int payloadEnd, StringBuffer resultString) throws ReaderException {\r
-\r
-               // Digits are interleaved in pairs - 5 black lines for one digit, and the\r
-               // 5\r
-               // interleaved white lines for the second digit.\r
-               // Therefore, need to scan 10 lines and then\r
-               // split these into two arrays\r
-               int[] counterDigitPair = new int[10];\r
-               int[] counterBlack = new int[5];\r
-               int[] counterWhite = new int[5];\r
-\r
-               for (int x = 0; payloadStart < payloadEnd; x++) {\r
-\r
-                       // Get 10 runs of black/white.\r
-                       recordPattern(row, payloadStart, counterDigitPair);\r
-                       // Split them into each array\r
-                       for (int k = 0; k < 5; k++) {\r
-                               counterBlack[k] = counterDigitPair[k * 2];\r
-                               counterWhite[k] = counterDigitPair[(k * 2) + 1];\r
-                       }\r
-\r
-                       int bestMatch = decodeDigit(counterBlack);\r
-                       resultString.append((char) ('0' + bestMatch % 10));\r
-                       bestMatch = decodeDigit(counterWhite);\r
-                       resultString.append((char) ('0' + bestMatch % 10));\r
-\r
-                       for (int i = 0; i < counterDigitPair.length; i++) {\r
-                               payloadStart += counterDigitPair[i];\r
-                       }\r
-               }\r
-       }\r
-\r
-       /**\r
-        * Identify where the start of the middle / payload section starts.\r
-        * \r
-        * @param row\r
-        *           row of black/white values to search\r
-        * @return Array, containing index of start of 'start block' and end of\r
-        *         'start block'\r
-        * @throws ReaderException\r
-        */\r
-       int[] decodeStart(BitArray row) throws ReaderException {\r
-               int endStart = skipWhiteSpace(row);\r
-               int startPattern[] = findGuardPattern(row, endStart, START_PATTERN);\r
-\r
-               /**\r
-                * Determine the width of a narrow line in pixels. We can do this by\r
-                * getting the width of the start pattern and dividing by 4 because its\r
-                * made up of 4 narrow lines.\r
-                */\r
-               this.narrowLineWidth = (startPattern[1] - startPattern[0]) / 4;\r
-\r
-               validateQuietZone(row, startPattern[0]);\r
-\r
-               return startPattern;\r
-       }\r
-\r
-       /**\r
-        * \r
-        *      The start & end patterns must be pre/post fixed by a quiet zone. This\r
-        * zone must be at least 10 times the width of a narrow line.  Scan back until\r
-        * we either get to the start of the barcode or match the necessary number of \r
-        * quiet zone pixels.\r
-        * \r
-        * Note: Its assumed the row is reversed when using this method to find\r
-        * quiet zone after the end pattern.\r
-        * \r
-        * ref: http://www.barcode-1.net/i25code.html\r
-        * \r
-        * @param row                                   - The bit array representing the scanned barcode. \r
-        * @param startPattern          - The index into row of the start or end pattern.\r
-        * @throws ReaderException - If the quiet zone cannot be found, a ReaderException is thrown.\r
-        */\r
-       private void validateQuietZone(BitArray row, int startPattern) throws ReaderException {\r
-\r
-               int quietCount=this.narrowLineWidth * 10;       // expect to find this many pixels of quiet zone\r
-               \r
-               int i=0;\r
-               for (i=startPattern-1; quietCount>0 && i>=0; i--)\r
-               {\r
-                       if (row.get(i)==true)\r
-                               break;\r
-                       quietCount--;\r
-               }\r
-               if (quietCount!=0)\r
-               {\r
-                       // Unable to find the necessary number of quiet zone pixels.\r
-                       throw ReaderException.getInstance();\r
-               }\r
-       }\r
-\r
-       /**\r
-        * Skip all whitespace until we get to the first black line.\r
-        * \r
-        * @param row\r
-        *           row of black/white values to search\r
-        * @return index of the first black line.\r
-        * @throws ReaderException\r
-        *            Throws exception if no black lines are found in the row\r
-        */\r
-       private int skipWhiteSpace(BitArray row) throws ReaderException {\r
-               int width = row.getSize();\r
-               int endStart = 0;\r
-               while (endStart < width) {\r
-                       if (row.get(endStart)) {\r
-                               break;\r
-                       }\r
-                       endStart++;\r
-               }\r
-               if (endStart == width)\r
-                       throw ReaderException.getInstance();\r
-\r
-               return endStart;\r
-       }\r
-\r
-       /**\r
-        * Identify where the end of the middle / payload section ends.\r
-        * \r
-        * @param row\r
-        *           row of black/white values to search\r
-        * @return Array, containing index of start of 'end block' and end of 'end\r
-        *         block'\r
-        * @throws ReaderException\r
-        */\r
-\r
-       int[] decodeEnd(BitArray row) throws ReaderException {\r
-\r
-               // For convenience, reverse the row and then\r
-               // search from 'the start' for the end block\r
-               row.reverse();\r
-\r
-               int endStart = skipWhiteSpace(row);\r
-               int endPattern[] = null;\r
-               try {\r
-                       endPattern = findGuardPattern(row, endStart, END_PATTERN_REVERSED);\r
-               } catch (ReaderException e) {\r
-                       // Put our row of data back the right way before throwing\r
-                       row.reverse();\r
-                       throw e;\r
-               }\r
-\r
-               /**\r
-                * The start & end patterns must be pre/post fixed by a quiet zone. This\r
-                * zone must be at least 10 times the width of a narrow line.\r
-                * \r
-                * ref: http://www.barcode-1.net/i25code.html\r
-                */\r
-               \r
-               validateQuietZone(row, endPattern[0]);\r
-\r
-               // Now recalc the indicies of where the 'endblock' starts & stops to\r
-               // accomodate\r
-               // the reversed nature of the search\r
-               int temp = endPattern[0];\r
-               endPattern[0] = row.getSize() - endPattern[1];\r
-               endPattern[1] = row.getSize() - temp;\r
-\r
-               // Put the row back the righ way.\r
-               row.reverse();\r
-               return endPattern;\r
-       }\r
-\r
-       /**\r
-        * @param row\r
-        *           row of black/white values to search\r
-        * @param rowOffset\r
-        *           position to start search\r
-        * @param pattern\r
-        *           pattern of counts of number of black and white pixels that are\r
-        *           being searched for as a pattern\r
-        * @return start/end horizontal offset of guard pattern, as an array of two\r
-        *         ints\r
-        * @throws ReaderException\r
-        *            if pattern is not found\r
-        * \r
-        * TODO: This is very similar to implementation in AbstractUPCEANReader. Consider if they can be merged to\r
-        * a single method.\r
-        */\r
-       int[] findGuardPattern(BitArray row, int rowOffset, int[] pattern) throws ReaderException {\r
-               int patternLength = pattern.length;\r
-               int[] counters = new int[patternLength];\r
-               int width = row.getSize();\r
-               boolean isWhite = false;\r
-\r
-               int counterPosition = 0;\r
-               int patternStart = rowOffset;\r
-               for (int x = rowOffset; x < width; x++) {\r
-                       boolean pixel = row.get(x);\r
-                       if ((!pixel && isWhite) || (pixel && !isWhite)) {\r
-                               counters[counterPosition]++;\r
-                       } else {\r
-                               if (counterPosition == patternLength - 1) {\r
-                                       if (patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE) < MAX_AVG_VARIANCE) {\r
-                                               return new int[] { patternStart, x };\r
-                                       }\r
-                                       patternStart += counters[0] + counters[1];\r
-                                       for (int y = 2; y < patternLength; y++) {\r
-                                               counters[y - 2] = counters[y];\r
-                                       }\r
-                                       counters[patternLength - 2] = 0;\r
-                                       counters[patternLength - 1] = 0;\r
-                                       counterPosition--;\r
-                               } else {\r
-                                       counterPosition++;\r
-                               }\r
-                               counters[counterPosition] = 1;\r
-                               isWhite = !isWhite;\r
-                       }\r
-               }\r
-               throw ReaderException.getInstance();\r
-       }\r
-\r
-       /**\r
-        * Attempts to decode a sequence of ITF black/white lines into single\r
-        * digit.\r
-        * \r
-        * @param counters\r
-        *           the counts of runs of observed black/white/black/... values\r
-        * \r
-        * @return The decoded digit\r
-        * \r
-        * @throws ReaderException\r
-        *            if digit cannot be decoded\r
-        */\r
-       static int decodeDigit(int[] counters) throws ReaderException {\r
-\r
-               int bestVariance = MAX_AVG_VARIANCE; // worst variance we'll accept\r
-               int bestMatch = -1;\r
-               int max = PATTERNS.length;\r
-               for (int i = 0; i < max; i++) {\r
-                       int[] pattern = PATTERNS[i];\r
-                       int variance = patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE);\r
-                       if (variance < bestVariance) {\r
-                               bestVariance = variance;\r
-                               bestMatch = i;\r
-                       }\r
-               }\r
-               if (bestMatch >= 0) {\r
-                       return bestMatch;\r
+public final class ITFReader extends AbstractOneDReader {\r
+\r
+  private static final int MAX_AVG_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.42f);\r
+  private static final int MAX_INDIVIDUAL_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.8f);\r
+\r
+  private static final int W = 3; // Pixel width of a wide line\r
+  private static final int N = 1; // Pixed width of a narrow line\r
+\r
+  // Stores the actual narrow line width of the image being decoded.\r
+  private int narrowLineWidth = -1;\r
+\r
+  /**\r
+   * Start/end guard pattern.\r
+   *\r
+   * Note: The end pattern is reversed because the row is reversed before\r
+   * searching for the END_PATTERN\r
+   */\r
+  private static final int[] START_PATTERN = {N, N, N, N};\r
+  private static final int[] END_PATTERN_REVERSED = {N, N, W};\r
+\r
+  /**\r
+   * Patterns of Wide / Narrow lines to indicate each digit\r
+   */\r
+  static final int[][] PATTERNS = {\r
+      {N, N, W, W, N}, // 0\r
+      {W, N, N, N, W}, // 1\r
+      {N, W, N, N, W}, // 2\r
+      {W, W, N, N, N}, // 3\r
+      {N, N, W, N, W}, // 4\r
+      {W, N, W, N, N}, // 5\r
+      {N, W, W, N, N}, // 6\r
+      {N, N, N, W, W}, // 7\r
+      {W, N, N, W, N}, // 8\r
+      {N, W, N, W, N}  // 9\r
+  };\r
+\r
+  public final Result decodeRow(int rowNumber, BitArray row, Hashtable hints) throws ReaderException {\r
+\r
+    StringBuffer result = new StringBuffer(20);\r
+\r
+    // Find out where the Middle section (payload) starts & ends\r
+    int[] startRange = decodeStart(row);\r
+    int[] endRange = decodeEnd(row);\r
+\r
+    decodeMiddle(row, startRange[1], endRange[0], result);\r
+\r
+    String resultString = result.toString();\r
+\r
+    // To avoid false positives with 2D barcodes (and other patterns), make\r
+    // an assumption that the decoded string must be 6, 10 or 14 digits.\r
+    int length = resultString.length();\r
+    if (length != 6 && length != 10 && length != 14) {\r
+      throw ReaderException.getInstance();\r
+    }\r
+\r
+    return new Result(\r
+        resultString,\r
+        null, // no natural byte representation for these barcodes\r
+        new ResultPoint[] { new GenericResultPoint(startRange[1], (float) rowNumber),\r
+                            new GenericResultPoint(startRange[0], (float) rowNumber)},\r
+        BarcodeFormat.ITF);\r
+  }\r
+\r
+  /**\r
+   * @param row          row of black/white values to search\r
+   * @param payloadStart offset of start pattern\r
+   * @param resultString {@link StringBuffer} to append decoded chars to\r
+   * @throws ReaderException if decoding could not complete successfully\r
+   */\r
+  protected void decodeMiddle(BitArray row, int payloadStart, int payloadEnd, StringBuffer resultString) throws ReaderException {\r
+\r
+    // Digits are interleaved in pairs - 5 black lines for one digit, and the\r
+    // 5\r
+    // interleaved white lines for the second digit.\r
+    // Therefore, need to scan 10 lines and then\r
+    // split these into two arrays\r
+    int[] counterDigitPair = new int[10];\r
+    int[] counterBlack = new int[5];\r
+    int[] counterWhite = new int[5];\r
+\r
+    while (payloadStart < payloadEnd) {\r
+\r
+      // Get 10 runs of black/white.\r
+      recordPattern(row, payloadStart, counterDigitPair);\r
+      // Split them into each array\r
+      for (int k = 0; k < 5; k++) {\r
+        int twoK = k << 1;\r
+        counterBlack[k] = counterDigitPair[twoK];\r
+        counterWhite[k] = counterDigitPair[twoK + 1];\r
+      }\r
+\r
+      int bestMatch = decodeDigit(counterBlack);\r
+      resultString.append((char) ('0' + bestMatch % 10));\r
+      bestMatch = decodeDigit(counterWhite);\r
+      resultString.append((char) ('0' + bestMatch % 10));\r
+\r
+      for (int i = 0; i < counterDigitPair.length; i++) {\r
+        payloadStart += counterDigitPair[i];\r
+      }\r
+    }\r
+  }\r
+\r
+  /**\r
+   * Identify where the start of the middle / payload section starts.\r
+   *\r
+   * @param row row of black/white values to search\r
+   * @return Array, containing index of start of 'start block' and end of\r
+   *         'start block'\r
+   * @throws ReaderException\r
+   */\r
+  int[] decodeStart(BitArray row) throws ReaderException {\r
+    int endStart = skipWhiteSpace(row);\r
+    int startPattern[] = findGuardPattern(row, endStart, START_PATTERN);\r
+\r
+    // Determine the width of a narrow line in pixels. We can do this by\r
+    // getting the width of the start pattern and dividing by 4 because its\r
+    // made up of 4 narrow lines.\r
+    this.narrowLineWidth = (startPattern[1] - startPattern[0]) >> 2;\r
+\r
+    validateQuietZone(row, startPattern[0]);\r
+\r
+    return startPattern;\r
+  }\r
+\r
+  /**\r
+   * The start & end patterns must be pre/post fixed by a quiet zone. This\r
+   * zone must be at least 10 times the width of a narrow line.  Scan back until\r
+   * we either get to the start of the barcode or match the necessary number of\r
+   * quiet zone pixels.\r
+   *\r
+   * Note: Its assumed the row is reversed when using this method to find\r
+   * quiet zone after the end pattern.\r
+   *\r
+   * ref: http://www.barcode-1.net/i25code.html\r
+   *\r
+   * @param row bit array representing the scanned barcode.\r
+   * @param startPattern index into row of the start or end pattern.\r
+   * @throws ReaderException if the quiet zone cannot be found, a ReaderException is thrown.\r
+   */\r
+  private void validateQuietZone(BitArray row, int startPattern) throws ReaderException {\r
+\r
+    int quietCount = this.narrowLineWidth * 10;  // expect to find this many pixels of quiet zone\r
+\r
+    for (int i = startPattern - 1; quietCount > 0 && i >= 0; i--) {\r
+      if (row.get(i)) {\r
+        break;\r
+      }\r
+      quietCount--;\r
+    }\r
+    if (quietCount != 0) {\r
+      // Unable to find the necessary number of quiet zone pixels.\r
+      throw ReaderException.getInstance();\r
+    }\r
+  }\r
+\r
+  /**\r
+   * Skip all whitespace until we get to the first black line.\r
+   *\r
+   * @param row row of black/white values to search\r
+   * @return index of the first black line.\r
+   * @throws ReaderException Throws exception if no black lines are found in the row\r
+   */\r
+  private int skipWhiteSpace(BitArray row) throws ReaderException {\r
+    int width = row.getSize();\r
+    int endStart = 0;\r
+    while (endStart < width) {\r
+      if (row.get(endStart)) {\r
+        break;\r
+      }\r
+      endStart++;\r
+    }\r
+    if (endStart == width) {\r
+      throw ReaderException.getInstance();\r
+    }\r
+\r
+    return endStart;\r
+  }\r
+\r
+  /**\r
+   * Identify where the end of the middle / payload section ends.\r
+   *\r
+   * @param row row of black/white values to search\r
+   * @return Array, containing index of start of 'end block' and end of 'end\r
+   *         block'\r
+   * @throws ReaderException\r
+   */\r
+\r
+  int[] decodeEnd(BitArray row) throws ReaderException {\r
+\r
+    // For convenience, reverse the row and then\r
+    // search from 'the start' for the end block\r
+    row.reverse();\r
+\r
+    int endStart = skipWhiteSpace(row);\r
+    int endPattern[];\r
+    try {\r
+      endPattern = findGuardPattern(row, endStart, END_PATTERN_REVERSED);\r
+    } catch (ReaderException e) {\r
+      // Put our row of data back the right way before throwing\r
+      row.reverse();\r
+      throw e;\r
+    }\r
+\r
+    // The start & end patterns must be pre/post fixed by a quiet zone. This\r
+    // zone must be at least 10 times the width of a narrow line.\r
+    // ref: http://www.barcode-1.net/i25code.html\r
+    validateQuietZone(row, endPattern[0]);\r
+\r
+    // Now recalc the indicies of where the 'endblock' starts & stops to\r
+    // accomodate\r
+    // the reversed nature of the search\r
+    int temp = endPattern[0];\r
+    endPattern[0] = row.getSize() - endPattern[1];\r
+    endPattern[1] = row.getSize() - temp;\r
+\r
+    // Put the row back the righ way.\r
+    row.reverse();\r
+    return endPattern;\r
+  }\r
+\r
+  /**\r
+   * @param row       row of black/white values to search\r
+   * @param rowOffset position to start search\r
+   * @param pattern   pattern of counts of number of black and white pixels that are\r
+   *                  being searched for as a pattern\r
+   * @return start/end horizontal offset of guard pattern, as an array of two\r
+   *         ints\r
+   * @throws ReaderException if pattern is not found\r
+   */\r
+  int[] findGuardPattern(BitArray row, int rowOffset, int[] pattern) throws ReaderException {\r
+\r
+    // TODO: This is very similar to implementation in AbstractUPCEANReader. Consider if they can be merged to\r
+    // a single method.\r
+\r
+    int patternLength = pattern.length;\r
+    int[] counters = new int[patternLength];\r
+    int width = row.getSize();\r
+    boolean isWhite = false;\r
+\r
+    int counterPosition = 0;\r
+    int patternStart = rowOffset;\r
+    for (int x = rowOffset; x < width; x++) {\r
+      boolean pixel = row.get(x);\r
+      if ((!pixel && isWhite) || (pixel && !isWhite)) {\r
+        counters[counterPosition]++;\r
+      } else {\r
+        if (counterPosition == patternLength - 1) {\r
+          if (patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE) < MAX_AVG_VARIANCE) {\r
+            return new int[]{patternStart, x};\r
+          }\r
+          patternStart += counters[0] + counters[1];\r
+          for (int y = 2; y < patternLength; y++) {\r
+            counters[y - 2] = counters[y];\r
+          }\r
+          counters[patternLength - 2] = 0;\r
+          counters[patternLength - 1] = 0;\r
+          counterPosition--;\r
+        } else {\r
+          counterPosition++;\r
+        }\r
+        counters[counterPosition] = 1;\r
+        isWhite = !isWhite;\r
+      }\r
+    }\r
+    throw ReaderException.getInstance();\r
+  }\r
+\r
+  /**\r
+   * Attempts to decode a sequence of ITF black/white lines into single\r
+   * digit.\r
+   *\r
+   * @param counters the counts of runs of observed black/white/black/... values\r
+   * @return The decoded digit\r
+   * @throws ReaderException if digit cannot be decoded\r
+   */\r
+  static int decodeDigit(int[] counters) throws ReaderException {\r
+\r
+    int bestVariance = MAX_AVG_VARIANCE; // worst variance we'll accept\r
+    int bestMatch = -1;\r
+    int max = PATTERNS.length;\r
+    for (int i = 0; i < max; i++) {\r
+      int[] pattern = PATTERNS[i];\r
+      int variance = patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE);\r
+      if (variance < bestVariance) {\r
+        bestVariance = variance;\r
+        bestMatch = i;\r
+      }\r
+    }\r
+    if (bestMatch >= 0) {\r
+      return bestMatch;\r
                } else {\r
                        throw ReaderException.getInstance();\r
                }\r