Prettify scan result screen, I think
[zxing.git] / csharp / oned / ITFReader.cs
index 264e234..2a57009 100755 (executable)
@@ -1,4 +1,6 @@
-/*\r
+/*\r
+* Copyright 2008 ZXing authors\r
+*\r
 * Licensed under the Apache License, Version 2.0 (the "License");\r
 * you may not use this file except in compliance with the License.\r
 * You may obtain a copy of the License at\r
 * See the License for the specific language governing permissions and\r
 * limitations under the License.\r
 */\r
+using System;\r
+using BarcodeFormat = com.google.zxing.BarcodeFormat;\r
+using DecodeHintType = com.google.zxing.DecodeHintType;\r
+using ReaderException = com.google.zxing.ReaderException;\r
+using Result = com.google.zxing.Result;\r
+using ResultPoint = com.google.zxing.ResultPoint;\r
+using BitArray = com.google.zxing.common.BitArray;\r
 namespace com.google.zxing.oned\r
 {\r
-    /**\r
-     * <p>Implements decoding of the EAN-13 format.</p>\r
-     *\r
-     * @author dswitkin@google.com (Daniel Switkin)\r
-     * @author Sean Owen\r
-     * @author alasdair@google.com (Alasdair Mackintosh)\r
-     */\r
-\r
-    using System.Text;\r
-    using com.google.zxing.common;\r
-\r
-    public sealed class ITFReader : AbstractOneDReader\r
-    { \r
-          private static  int MAX_AVG_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.42f);\r
-          private static  int MAX_INDIVIDUAL_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.8f);\r
-\r
-          private static  int W = 3; // Pixel width of a wide line\r
-          private static  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  int[] START_PATTERN = {N, N, N, N};\r
-          private static  int[] END_PATTERN_REVERSED = {N, N, W};\r
-\r
-          /**\r
-           * Patterns of Wide / Narrow lines to indicate each digit\r
-           */\r
-          private static  int[][] PATTERNS = new int[][]{\r
-              new int[]{N, N, W, W, N}, // 0\r
-              new int[]{W, N, N, N, W}, // 1\r
-              new int[]{N, W, N, N, W}, // 2\r
-              new int[]{W, W, N, N, N}, // 3\r
-              new int[]{N, N, W, N, W}, // 4\r
-              new int[]{W, N, W, N, N}, // 5\r
-              new int[]{N, W, W, N, N}, // 6\r
-              new int[]{N, N, N, W, W}, // 7\r
-              new int[]{W, N, N, W, N}, // 8\r
-              new int[]{N, W, N, W, N}  // 9\r
-          };\r
-\r
-          public override Result decodeRow(int rowNumber, BitArray row, System.Collections.Hashtable hints) {\r
-\r
-            StringBuilder result = new StringBuilder(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 new ReaderException();\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 StringBuilder} to Append decoded chars to\r
-           * @throws ReaderException if decoding could not complete successfully\r
-           */\r
-          static void decodeMiddle(BitArray row, int payloadStart, int payloadEnd, StringBuilder resultString) {\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));\r
-              bestMatch = decodeDigit(counterWhite);\r
-              resultString.Append((char) ('0' + bestMatch));\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) {\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) {\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 new ReaderException();\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) {\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 new ReaderException();\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) {\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) {\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
-            bool isWhite = false;\r
-\r
-            int counterPosition = 0;\r
-            int patternStart = rowOffset;\r
-            for (int x = rowOffset; x < width; x++) {\r
-              bool 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 new ReaderException();\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
-          private static int decodeDigit(int[] counters) {\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 new ReaderException();\r
-                       }\r
-               }\r
-    \r
-    }\r
+       \r
+       /// <summary> <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\r
+       /// digits. The checksum is optional and is not applied by this Reader. The consumer of the decoded\r
+       /// value 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
+       /// </summary>\r
+       /// <author>  kevin.osullivan@sita.aero, SITA Lab.\r
+       /// </author>\r
+       /// <author>www.Redivivus.in (suraj.supekar@redivivus.in) - Ported from ZXING Java Source \r
+       /// </author>\r
+       public sealed class ITFReader:OneDReader\r
+       {\r
+               \r
+               //UPGRADE_NOTE: Final was removed from the declaration of 'MAX_AVG_VARIANCE '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"\r
+               //UPGRADE_WARNING: Data types in Visual C# might be different.  Verify the accuracy of narrowing conversions. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1042'"\r
+               private static readonly int MAX_AVG_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.42f);\r
+               //UPGRADE_NOTE: Final was removed from the declaration of 'MAX_INDIVIDUAL_VARIANCE '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"\r
+               //UPGRADE_WARNING: Data types in Visual C# might be different.  Verify the accuracy of narrowing conversions. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1042'"\r
+               private static readonly int MAX_INDIVIDUAL_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.8f);\r
+               \r
+               private const int W = 3; // Pixel width of a wide line\r
+               private const int N = 1; // Pixed width of a narrow line\r
+               \r
+               //UPGRADE_NOTE: Final was removed from the declaration of 'DEFAULT_ALLOWED_LENGTHS'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"\r
+               private static readonly int[] DEFAULT_ALLOWED_LENGTHS = new int[]{6, 10, 14, 44};\r
+               \r
+               // Stores the actual narrow line width of the image being decoded.\r
+               private int narrowLineWidth = - 1;\r
+               \r
+               /// <summary> 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
+               /// </summary>\r
+               //UPGRADE_NOTE: Final was removed from the declaration of 'START_PATTERN '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"\r
+               private static readonly int[] START_PATTERN = new int[]{N, N, N, N};\r
+               //UPGRADE_NOTE: Final was removed from the declaration of 'END_PATTERN_REVERSED '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"\r
+               private static readonly int[] END_PATTERN_REVERSED = new int[]{N, N, W};\r
+               \r
+               /// <summary> Patterns of Wide / Narrow lines to indicate each digit</summary>\r
+               //UPGRADE_NOTE: Final was removed from the declaration of 'PATTERNS '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"\r
+               private static readonly int[][] PATTERNS = new int[][]{new int[]{N, N, W, W, N}, new int[]{W, N, N, N, W}, new int[]{N, W, N, N, W}, new int[]{W, W, N, N, N}, new int[]{N, N, W, N, W}, new int[]{W, N, W, N, N}, new int[]{N, W, W, N, N}, new int[]{N, N, N, W, W}, new int[]{W, N, N, W, N}, new int[]{N, W, N, W, N}};\r
+               \r
+               public override Result decodeRow(int rowNumber, BitArray row, System.Collections.Hashtable hints)\r
+               {\r
+                       \r
+                       // Find out where the Middle section (payload) starts & ends\r
+                       int[] startRange = decodeStart(row);\r
+                       int[] endRange = decodeEnd(row);\r
+                       \r
+                       System.Text.StringBuilder result = new System.Text.StringBuilder(20);\r
+                       decodeMiddle(row, startRange[1], endRange[0], result);\r
+                       System.String resultString = result.ToString();\r
+                       \r
+                       int[] allowedLengths = null;\r
+                       if (hints != null)\r
+                       {\r
+                               allowedLengths = (int[]) hints[DecodeHintType.ALLOWED_LENGTHS];\r
+                       }\r
+                       if (allowedLengths == null)\r
+                       {\r
+                               allowedLengths = DEFAULT_ALLOWED_LENGTHS;\r
+                       }\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
+                       bool lengthOK = false;\r
+                       for (int i = 0; i < allowedLengths.Length; i++)\r
+                       {\r
+                               if (length == allowedLengths[i])\r
+                               {\r
+                                       lengthOK = true;\r
+                                       break;\r
+                               }\r
+                       }\r
+                       if (!lengthOK)\r
+                       {\r
+                               throw ReaderException.Instance;\r
+                       }\r
+                       \r
+                       //UPGRADE_WARNING: Data types in Visual C# might be different.  Verify the accuracy of narrowing conversions. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1042'"\r
+                       return new Result(resultString, null, new ResultPoint[]{new ResultPoint(startRange[1], (float) rowNumber), new ResultPoint(endRange[0], (float) rowNumber)}, BarcodeFormat.ITF);\r
+               }\r
+               \r
+               /// <param name="row">         row of black/white values to search\r
+               /// </param>\r
+               /// <param name="payloadStart">offset of start pattern\r
+               /// </param>\r
+               /// <param name="resultString">{@link StringBuffer} to append decoded chars to\r
+               /// </param>\r
+               /// <throws>  ReaderException if decoding could not complete successfully </throws>\r
+               private static void  decodeMiddle(BitArray row, int payloadStart, int payloadEnd, System.Text.StringBuilder resultString)\r
+               {\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
+                               \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
+                               {\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));\r
+                               bestMatch = decodeDigit(counterWhite);\r
+                               resultString.Append((char) ('0' + bestMatch));\r
+                               \r
+                               for (int i = 0; i < counterDigitPair.Length; i++)\r
+                               {\r
+                                       payloadStart += counterDigitPair[i];\r
+                               }\r
+                       }\r
+               }\r
+               \r
+               /// <summary> Identify where the start of the middle / payload section starts.\r
+               /// \r
+               /// </summary>\r
+               /// <param name="row">row of black/white values to search\r
+               /// </param>\r
+               /// <returns> Array, containing index of start of 'start block' and end of\r
+               /// 'start block'\r
+               /// </returns>\r
+               /// <throws>  ReaderException </throws>\r
+               internal int[] decodeStart(BitArray row)\r
+               {\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
+               /// <summary> 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
+               /// </summary>\r
+               /// <param name="row">bit array representing the scanned barcode.\r
+               /// </param>\r
+               /// <param name="startPattern">index into row of the start or end pattern.\r
+               /// </param>\r
+               /// <throws>  ReaderException if the quiet zone cannot be found, a ReaderException is thrown. </throws>\r
+               private void  validateQuietZone(BitArray row, int startPattern)\r
+               {\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
+                       {\r
+                               if (row.get_Renamed(i))\r
+                               {\r
+                                       break;\r
+                               }\r
+                               quietCount--;\r
+                       }\r
+                       if (quietCount != 0)\r
+                       {\r
+                               // Unable to find the necessary number of quiet zone pixels.\r
+                               throw ReaderException.Instance;\r
+                       }\r
+               }\r
+               \r
+               /// <summary> Skip all whitespace until we get to the first black line.\r
+               /// \r
+               /// </summary>\r
+               /// <param name="row">row of black/white values to search\r
+               /// </param>\r
+               /// <returns> index of the first black line.\r
+               /// </returns>\r
+               /// <throws>  ReaderException Throws exception if no black lines are found in the row </throws>\r
+               private static int skipWhiteSpace(BitArray row)\r
+               {\r
+                       int width = row.Size;\r
+                       int endStart = 0;\r
+                       while (endStart < width)\r
+                       {\r
+                               if (row.get_Renamed(endStart))\r
+                               {\r
+                                       break;\r
+                               }\r
+                               endStart++;\r
+                       }\r
+                       if (endStart == width)\r
+                       {\r
+                               throw ReaderException.Instance;\r
+                       }\r
+                       \r
+                       return endStart;\r
+               }\r
+               \r
+               /// <summary> Identify where the end of the middle / payload section ends.\r
+               /// \r
+               /// </summary>\r
+               /// <param name="row">row of black/white values to search\r
+               /// </param>\r
+               /// <returns> Array, containing index of start of 'end block' and end of 'end\r
+               /// block'\r
+               /// </returns>\r
+               /// <throws>  ReaderException </throws>\r
+               \r
+               internal int[] decodeEnd(BitArray row)\r
+               {\r
+                       \r
+                       // For convenience, reverse the row and then\r
+                       // search from 'the start' for the end block\r
+                       row.reverse();\r
+                       try\r
+                       {\r
+                               int endStart = skipWhiteSpace(row);\r
+                               int[] endPattern = findGuardPattern(row, endStart, END_PATTERN_REVERSED);\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 recalculate the indices of where the 'endblock' starts & stops to\r
+                               // accommodate\r
+                               // the reversed nature of the search\r
+                               int temp = endPattern[0];\r
+                               endPattern[0] = row.Size - endPattern[1];\r
+                               endPattern[1] = row.Size - temp;\r
+                               \r
+                               return endPattern;\r
+                       }\r
+                       finally\r
+                       {\r
+                               // Put the row back the right way.\r
+                               row.reverse();\r
+                       }\r
+               }\r
+               \r
+               /// <param name="row">      row of black/white values to search\r
+               /// </param>\r
+               /// <param name="rowOffset">position to start search\r
+               /// </param>\r
+               /// <param name="pattern">  pattern of counts of number of black and white pixels that are\r
+               /// being searched for as a pattern\r
+               /// </param>\r
+               /// <returns> start/end horizontal offset of guard pattern, as an array of two\r
+               /// ints\r
+               /// </returns>\r
+               /// <throws>  ReaderException if pattern is not found </throws>\r
+               private static int[] findGuardPattern(BitArray row, int rowOffset, int[] pattern)\r
+               {\r
+                       \r
+                       // TODO: This is very similar to implementation in UPCEANReader. Consider if they can be\r
+                       // merged to a single method.\r
+                       int patternLength = pattern.Length;\r
+                       int[] counters = new int[patternLength];\r
+                       int width = row.Size;\r
+                       bool isWhite = false;\r
+                       \r
+                       int counterPosition = 0;\r
+                       int patternStart = rowOffset;\r
+                       for (int x = rowOffset; x < width; x++)\r
+                       {\r
+                               bool pixel = row.get_Renamed(x);\r
+                               if (pixel ^ isWhite)\r
+                               {\r
+                                       counters[counterPosition]++;\r
+                               }\r
+                               else\r
+                               {\r
+                                       if (counterPosition == patternLength - 1)\r
+                                       {\r
+                                               if (patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE) < MAX_AVG_VARIANCE)\r
+                                               {\r
+                                                       return new int[]{patternStart, x};\r
+                                               }\r
+                                               patternStart += counters[0] + counters[1];\r
+                                               for (int y = 2; y < patternLength; y++)\r
+                                               {\r
+                                                       counters[y - 2] = counters[y];\r
+                                               }\r
+                                               counters[patternLength - 2] = 0;\r
+                                               counters[patternLength - 1] = 0;\r
+                                               counterPosition--;\r
+                                       }\r
+                                       else\r
+                                       {\r
+                                               counterPosition++;\r
+                                       }\r
+                                       counters[counterPosition] = 1;\r
+                                       isWhite = !isWhite;\r
+                               }\r
+                       }\r
+                       throw ReaderException.Instance;\r
+               }\r
+               \r
+               /// <summary> Attempts to decode a sequence of ITF black/white lines into single\r
+               /// digit.\r
+               /// \r
+               /// </summary>\r
+               /// <param name="counters">the counts of runs of observed black/white/black/... values\r
+               /// </param>\r
+               /// <returns> The decoded digit\r
+               /// </returns>\r
+               /// <throws>  ReaderException if digit cannot be decoded </throws>\r
+               private static int decodeDigit(int[] counters)\r
+               {\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
+                       {\r
+                               int[] pattern = PATTERNS[i];\r
+                               int variance = patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE);\r
+                               if (variance < bestVariance)\r
+                               {\r
+                                       bestVariance = variance;\r
+                                       bestMatch = i;\r
+                               }\r
+                       }\r
+                       if (bestMatch >= 0)\r
+                       {\r
+                               return bestMatch;\r
+                       }\r
+                       else\r
+                       {\r
+                               throw ReaderException.Instance;\r
+                       }\r
+               }\r
+       }\r
 }
\ No newline at end of file