context: /*
   * Copyright 2013 ZXing authors
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  using System;
  using System.Collections.Generic;
  using System.Globalization;
  using System.Text;
  
  using ZXing.Common;
  using ZXing.PDF417.Internal.EC;
  
  namespace ZXing.PDF417.Internal
  {
     /// <summary>
     /// 
     /// </summary>
     /// <author>Guenther Grau</author>
     public static class PDF417ScanningDecoder
     {
        private const int CODEWORD_SKEW_SIZE = 2;
  
        private const int MAX_ERRORS = 3;
        private const int MAX_EC_CODEWORDS = 512;
        private static readonly ErrorCorrection errorCorrection = new ErrorCorrection();
  
        /// <summary>
        /// Decode the specified image, imageTopLeft, imageBottomLeft, imageTopRight, imageBottomRight, minCodewordWidth
        /// and maxCodewordWidth.
        /// TODO: don't pass in minCodewordWidth and maxCodewordWidth, pass in barcode columns for start and stop pattern
        /// columns. That way width can be deducted from the pattern column.
        /// This approach also allows to detect more details about the barcode, e.g. if a bar type (white or black) is wider 
        /// than it should be. This can happen if the scanner used a bad blackpoint.
        /// </summary>
        /// <param name="image">Image.</param>
        /// <param name="imageTopLeft">Image top left.</param>
        /// <param name="imageBottomLeft">Image bottom left.</param>
        /// <param name="imageTopRight">Image top right.</param>
        /// <param name="imageBottomRight">Image bottom right.</param>
        /// <param name="minCodewordWidth">Minimum codeword width.</param>
        /// <param name="maxCodewordWidth">Max codeword width.</param>
        public static DecoderResult decode(BitMatrix image,
                                           ResultPoint imageTopLeft,
                                           ResultPoint imageBottomLeft,
                                           ResultPoint imageTopRight,
                                           ResultPoint imageBottomRight,
                                           int minCodewordWidth,
                                           int maxCodewordWidth)
        {
           BoundingBox boundingBox = BoundingBox.Create(image, imageTopLeft, imageBottomLeft, imageTopRight, imageBottomRight);
           DetectionResultRowIndicatorColumn leftRowIndicatorColumn = null;
           DetectionResultRowIndicatorColumn rightRowIndicatorColumn = null;
           DetectionResult detectionResult = null;
           for (int i = 0; i < 2; i++)
           {
              if (imageTopLeft != null)
              {
                 leftRowIndicatorColumn = getRowIndicatorColumn(image, boundingBox, imageTopLeft, true, minCodewordWidth, maxCodewordWidth);
              }
              if (imageTopRight != null)
              {
                 rightRowIndicatorColumn = getRowIndicatorColumn(image, boundingBox, imageTopRight, false, minCodewordWidth, maxCodewordWidth);
              }
              detectionResult = merge(leftRowIndicatorColumn, rightRowIndicatorColumn);
              if (detectionResult == null)
              {
                 // TODO Based on Owen's Comments in <see cref="ZXing.ReaderException"/>, this method has been modified to continue silently
                 // if a barcode was not decoded where it was detected instead of throwing a new exception object.
                 return null;
              }
              if (i == 0 && detectionResult.Box != null &&
                  (detectionResult.Box.MinY < boundingBox.MinY || detectionResult.Box.MaxY > boundingBox.MaxY))
              {
                 boundingBox = detectionResult.Box;
              }
              else
              {
                 detectionResult.Box = boundingBox;
                 break;
              }
           }
           int maxBarcodeColumn = detectionResult.ColumnCount + 1;
           detectionResult.DetectionResultColumns[0] = leftRowIndicatorColumn;
  
           detectionResult.DetectionResultColumns[maxBarcodeColumn] = rightRowIndicatorColumn;
  
           bool leftToRight = leftRowIndicatorColumn != null;
           for (int barcodeColumnCount = 1; barcodeColumnCount <= maxBarcodeColumn; barcodeColumnCount++)
           {
              int barcodeColumn = leftToRight ? barcodeColumnCount : maxBarcodeColumn - barcodeColumnCount;
              if (detectionResult.DetectionResultColumns[barcodeColumn] != null)
              {
                 // This will be the case for the opposite row indicator column, which doesn't need to be decoded again.
                 continue;
              }
              DetectionResultColumn detectionResultColumn;
              if (barcodeColumn == 0 || barcodeColumn == maxBarcodeColumn)
              {
                 detectionResultColumn = new DetectionResultRowIndicatorColumn(boundingBox, barcodeColumn == 0);
              }
              else
              {
                 detectionResultColumn = new DetectionResultColumn(boundingBox);
              }
              detectionResult.DetectionResultColumns[barcodeColumn] = detectionResultColumn;
              int startColumn = -1;
              int previousStartColumn = startColumn;
              // TODO start at a row for which we know the start position, then detect upwards and downwards from there.
              for (int imageRow = boundingBox.MinY; imageRow <= boundingBox.MaxY; imageRow++)
              {
                 startColumn = getStartColumn(detectionResult, barcodeColumn, imageRow, leftToRight);
                 if (startColumn < 0 || startColumn > boundingBox.MaxX)
                 {
                    if (previousStartColumn == -1)
                    {
                       continue;
                    }
                    startColumn = previousStartColumn;
                 }
                 Codeword codeword = detectCodeword(image, boundingBox.MinX, boundingBox.MaxX, leftToRight,
                                                    startColumn, imageRow, minCodewordWidth, maxCodewordWidth);
                 if (codeword != null)
                 {
                    detectionResultColumn.setCodeword(imageRow, codeword);
                    previousStartColumn = startColumn;
                    minCodewordWidth = Math.Min(minCodewordWidth, codeword.Width);
                    maxCodewordWidth = Math.Max(maxCodewordWidth, codeword.Width);
                 }
              }
           }
           return createDecoderResult(detectionResult);
        }
  
        /// <summary>
        /// Merge the specified leftRowIndicatorColumn and rightRowIndicatorColumn.
        /// </summary>
        /// <param name="leftRowIndicatorColumn">Left row indicator column.</param>
        /// <param name="rightRowIndicatorColumn">Right row indicator column.</param>
        private static DetectionResult merge(DetectionResultRowIndicatorColumn leftRowIndicatorColumn,
                                             DetectionResultRowIndicatorColumn rightRowIndicatorColumn)
        {
           if (leftRowIndicatorColumn == null && rightRowIndicatorColumn == null)
           {
              return null;
           }
           BarcodeMetadata barcodeMetadata = getBarcodeMetadata(leftRowIndicatorColumn, rightRowIndicatorColumn);
           if (barcodeMetadata == null)
           {
              return null;
           }
           BoundingBox boundingBox = BoundingBox.merge(adjustBoundingBox(leftRowIndicatorColumn),
                                                       adjustBoundingBox(rightRowIndicatorColumn));
  
           return new DetectionResult(barcodeMetadata, boundingBox);
        }
  
        /// <summary>
        /// Adjusts the bounding box.
        /// </summary>
        /// <returns>The bounding box.</returns>
        /// <param name="rowIndicatorColumn">Row indicator column.</param>
        private static BoundingBox adjustBoundingBox(DetectionResultRowIndicatorColumn rowIndicatorColumn)
        {
           if (rowIndicatorColumn == null)
           {
              return null;
           }
           int[] rowHeights = rowIndicatorColumn.getRowHeights();
           int maxRowHeight = getMax(rowHeights);
           int missingStartRows = 0;
           foreach (int rowHeight in rowHeights)
           {
              missingStartRows += maxRowHeight - rowHeight;
              if (rowHeight > 0)
              {
                 break;
              }
           }
           Codeword[] codewords = rowIndicatorColumn.Codewords;
           for (int row = 0; missingStartRows > 0 && codewords[row] == null; row++)
           {
              missingStartRows--;
           }
           int missingEndRows = 0;
           for (int row = rowHeights.Length - 1; row >= 0; row--)
           {
              missingEndRows += maxRowHeight - rowHeights[row];
              if (rowHeights[row] > 0)
              {
                 break;
              }
           }
           for (int row = codewords.Length - 1; missingEndRows > 0 && codewords[row] == null; row--)
           {
              missingEndRows--;
           }
           return rowIndicatorColumn.Box.addMissingRows(missingStartRows, missingEndRows, rowIndicatorColumn.IsLeft);
        }
  
        private static int getMax(int[] values)
        {
           int maxValue = -1;
           for (var index = values.Length - 1; index >= 0; index--)
           {
              maxValue = Math.Max(maxValue, values[index]);
           }
           return maxValue;
        }
  
        /// <summary>
        /// Gets the barcode metadata.
        /// </summary>
        /// <returns>The barcode metadata.</returns>
        /// <param name="leftRowIndicatorColumn">Left row indicator column.</param>
        /// <param name="rightRowIndicatorColumn">Right row indicator column.</param>
        private static BarcodeMetadata getBarcodeMetadata(DetectionResultRowIndicatorColumn leftRowIndicatorColumn,
                                                          DetectionResultRowIndicatorColumn rightRowIndicatorColumn)
        {
           if (leftRowIndicatorColumn == null || leftRowIndicatorColumn.getBarcodeMetadata() == null)
           {
              return rightRowIndicatorColumn == null ? null : rightRowIndicatorColumn.getBarcodeMetadata();
           }
           if (rightRowIndicatorColumn == null || rightRowIndicatorColumn.getBarcodeMetadata() == null)
           {
              return leftRowIndicatorColumn == null ? null : leftRowIndicatorColumn.getBarcodeMetadata();
           }
           BarcodeMetadata leftBarcodeMetadata = leftRowIndicatorColumn.getBarcodeMetadata();
           BarcodeMetadata rightBarcodeMetadata = rightRowIndicatorColumn.getBarcodeMetadata();
  
           if (leftBarcodeMetadata.ColumnCount != rightBarcodeMetadata.ColumnCount &&
               leftBarcodeMetadata.ErrorCorrectionLevel != rightBarcodeMetadata.ErrorCorrectionLevel &&
               leftBarcodeMetadata.RowCount != rightBarcodeMetadata.RowCount)
           {
              return null;
           }
           return leftBarcodeMetadata;
        }
  
        /// <summary>
        /// Gets the row indicator column.
        /// </summary>
        /// <returns>The row indicator column.</returns>
        /// <param name="image">Image.</param>
        /// <param name="boundingBox">Bounding box.</param>
        /// <param name="startPoint">Start point.</param>
        /// <param name="leftToRight">If set to <c>true</c> left to right.</param>
        /// <param name="minCodewordWidth">Minimum codeword width.</param>
        /// <param name="maxCodewordWidth">Max codeword width.</param>
        private static DetectionResultRowIndicatorColumn getRowIndicatorColumn(BitMatrix image,
                                                                               BoundingBox boundingBox,
                                                                               ResultPoint startPoint,
                                                                               bool leftToRight,
                                                                               int minCodewordWidth,
                                                                               int maxCodewordWidth)
        {
           DetectionResultRowIndicatorColumn rowIndicatorColumn = new DetectionResultRowIndicatorColumn(boundingBox, leftToRight);
           for (int i = 0; i < 2; i++)
           {
              int increment = i == 0 ? 1 : -1;
              int startColumn = (int) startPoint.X;
              for (int imageRow = (int) startPoint.Y; imageRow <= boundingBox.MaxY &&
                                                      imageRow >= boundingBox.MinY; imageRow += increment)
              {
                 Codeword codeword = detectCodeword(image, 0, image.Width, leftToRight, startColumn, imageRow,
                                                    minCodewordWidth, maxCodewordWidth);
                 if (codeword != null)
                 {
                    rowIndicatorColumn.setCodeword(imageRow, codeword);
                    if (leftToRight)
                    {
                       startColumn = codeword.StartX;
                    }
                    else
                    {
                       startColumn = codeword.EndX;
                    }
                 }
              }
           }
           return rowIndicatorColumn;
        }
  
        /// <summary>
        /// Adjusts the codeword count.
        /// </summary>
        /// <param name="detectionResult">Detection result.</param>
        /// <param name="barcodeMatrix">Barcode matrix.</param>
        private static bool adjustCodewordCount(DetectionResult detectionResult, BarcodeValue[][] barcodeMatrix)
        {
           int[] numberOfCodewords = barcodeMatrix[0][1].getValue();
           int calculatedNumberOfCodewords = detectionResult.ColumnCount*
                                             detectionResult.RowCount -
                                             getNumberOfECCodeWords(detectionResult.ErrorCorrectionLevel);
           if (numberOfCodewords.Length == 0)
           {
              if (calculatedNumberOfCodewords < 1 || calculatedNumberOfCodewords > PDF417Common.MAX_CODEWORDS_IN_BARCODE)
              {
                 return false;
              }
              barcodeMatrix[0][1].setValue(calculatedNumberOfCodewords);
           }
           else if (numberOfCodewords[0] != calculatedNumberOfCodewords)
           {
              // The calculated one is more reliable as it is derived from the row indicator columns
              barcodeMatrix[0][1].setValue(calculatedNumberOfCodewords);
           }
  
           return true;
        }
  
        /// <summary>
        /// Creates the decoder result.
        /// </summary>
        /// <returns>The decoder result.</returns>
        /// <param name="detectionResult">Detection result.</param>
        private static DecoderResult createDecoderResult(DetectionResult detectionResult)
        {
           BarcodeValue[][] barcodeMatrix = createBarcodeMatrix(detectionResult);
           if (!adjustCodewordCount(detectionResult, barcodeMatrix))
           {
              return null;
           }
           List<int> erasures = new List<int>();
           int[] codewords = new int[detectionResult.RowCount*detectionResult.ColumnCount];
           List<int[]> ambiguousIndexValuesList = new List<int[]>();
           List<int> ambiguousIndexesList = new List<int>();
           for (int row = 0; row < detectionResult.RowCount; row++)
           {
              for (int column = 0; column < detectionResult.ColumnCount; column++)
              {
                 int[] values = barcodeMatrix[row][column + 1].getValue();
                 int codewordIndex = row*detectionResult.ColumnCount + column;
                 if (values.Length == 0)
                 {
                    erasures.Add(codewordIndex);
                 }
                 else if (values.Length == 1)
                 {
                    codewords[codewordIndex] = values[0];
                 }
                 else
                 {
                    ambiguousIndexesList.Add(codewordIndex);
                    ambiguousIndexValuesList.Add(values);
                 }
              }
           }
           int[][] ambiguousIndexValues = new int[ambiguousIndexValuesList.Count][];
           for (int i = 0; i < ambiguousIndexValues.Length; i++)
           {
              ambiguousIndexValues[i] = ambiguousIndexValuesList[i];
           }
           return createDecoderResultFromAmbiguousValues(detectionResult.ErrorCorrectionLevel, codewords,
                                                         erasures.ToArray(), ambiguousIndexesList.ToArray(), ambiguousIndexValues);
        }
  
        /// <summary>
        /// This method deals with the fact, that the decoding process doesn't always yield a single most likely value. The
        /// current error correction implementation doesn't deal with erasures very well, so it's better to provide a value
        /// for these ambiguous codewords instead of treating it as an erasure. The problem is that we don't know which of
        /// the ambiguous values to choose. We try decode using the first value, and if that fails, we use another of the
        /// ambiguous values and try to decode again. This usually only happens on very hard to read and decode barcodes,
        /// so decoding the normal barcodes is not affected by this.
        /// </summary>
        /// <returns>The decoder result from ambiguous values.</returns>
        /// <param name="ecLevel">Ec level.</param>
        /// <param name="codewords">Codewords.</param>
        /// <param name="erasureArray">contains the indexes of erasures.</param>
        /// <param name="ambiguousIndexes">array with the indexes that have more than one most likely value.</param>
        /// <param name="ambiguousIndexValues">two dimensional array that contains the ambiguous values. The first dimension must
        /// be the same Length as the ambiguousIndexes array.</param>
        private static DecoderResult createDecoderResultFromAmbiguousValues(int ecLevel,
                                                                            int[] codewords,
                                                                            int[] erasureArray,
                                                                            int[] ambiguousIndexes,
                                                                            int[][] ambiguousIndexValues)
        {
           int[] ambiguousIndexCount = new int[ambiguousIndexes.Length];
  
           int tries = 100;
           while (tries-- > 0)
           {
              for (int i = 0; i < ambiguousIndexCount.Length; i++)
              {
                 codewords[ambiguousIndexes[i]] = ambiguousIndexValues[i][ambiguousIndexCount[i]];
              }
              try
              {
                 var result = decodeCodewords(codewords, ecLevel, erasureArray);
                 if (result != null)
                    return result;
              }
              catch (ReaderException)
              {
                 // ignored, should not happen
              }
              if (ambiguousIndexCount.Length == 0)
              {
                 return null;
              }
              for (int i = 0; i < ambiguousIndexCount.Length; i++)
              {
                 if (ambiguousIndexCount[i] < ambiguousIndexValues[i].Length - 1)
                 {
                    ambiguousIndexCount[i]++;
                    break;
                 }
                 else
                 {
                    ambiguousIndexCount[i] = 0;
                    if (i == ambiguousIndexCount.Length - 1)
                    {
                       return null;
                    }
                 }
              }
           }
           return null;
        }
  
        /// <summary>
        /// Creates the barcode matrix.
        /// </summary>
        /// <returns>The barcode matrix.</returns>
        /// <param name="detectionResult">Detection result.</param>
        private static BarcodeValue[][] createBarcodeMatrix(DetectionResult detectionResult)
        {
           // Manually setup Jagged Array in C#
           BarcodeValue[][] barcodeMatrix = new BarcodeValue[detectionResult.RowCount][];
           for (int row = 0; row < barcodeMatrix.Length; row++)
           {
              barcodeMatrix[row] = new BarcodeValue[detectionResult.ColumnCount + 2];
              for (int col = 0; col < barcodeMatrix[row].Length; col++)
              {
                 barcodeMatrix[row][col] = new BarcodeValue();
              }
           }
  
           int column = -1;
           foreach (DetectionResultColumn detectionResultColumn in detectionResult.getDetectionResultColumns())
           {
              column++;
              if (detectionResultColumn == null)
              {
                 continue;
              }
              foreach (Codeword codeword in detectionResultColumn.Codewords)
              {
                 if (codeword == null || codeword.RowNumber == -1)
                 {
                    continue;
                 }
                 barcodeMatrix[codeword.RowNumber][column].setValue(codeword.Value);
              }
           }
           return barcodeMatrix;
        }
  
        /// <summary>
        /// Tests to see if the Barcode Column is Valid
        /// </summary>
        /// <returns><c>true</c>, if barcode column is valid, <c>false</c> otherwise.</returns>
        /// <param name="detectionResult">Detection result.</param>
        /// <param name="barcodeColumn">Barcode column.</param>
        private static bool isValidBarcodeColumn(DetectionResult detectionResult, int barcodeColumn)
        {
           return (barcodeColumn >= 0) && (barcodeColumn <= detectionResult.DetectionResultColumns.Length + 1);
        }
  
        /// <summary>
        /// Gets the start column.
        /// </summary>
        /// <returns>The start column.</returns>
        /// <param name="detectionResult">Detection result.</param>
        /// <param name="barcodeColumn">Barcode column.</param>
        /// <param name="imageRow">Image row.</param>
        /// <param name="leftToRight">If set to <c>true</c> left to right.</param>
        private static int getStartColumn(DetectionResult detectionResult,
                                          int barcodeColumn,
                                          int imageRow,
                                          bool leftToRight)
        {
           int offset = leftToRight ? 1 : -1;
           Codeword codeword = null;
           if (isValidBarcodeColumn(detectionResult, barcodeColumn - offset))
           {
              codeword = detectionResult.DetectionResultColumns[barcodeColumn - offset].getCodeword(imageRow);
           }
           if (codeword != null)
           {
              return leftToRight ? codeword.EndX : codeword.StartX;
           }
           codeword = detectionResult.DetectionResultColumns[barcodeColumn].getCodewordNearby(imageRow);
           if (codeword != null)
           {
              return leftToRight ? codeword.EndX : codeword.StartX;
           }
           if (isValidBarcodeColumn(detectionResult, barcodeColumn - offset))
           {
              codeword = detectionResult.DetectionResultColumns[barcodeColumn - offset].getCodewordNearby(imageRow);
           }
           if (codeword != null)
           {
              return leftToRight ? codeword.EndX : codeword.StartX;
           }
           int skippedColumns = 0;
  
           while (isValidBarcodeColumn(detectionResult, barcodeColumn - offset))
           {
              barcodeColumn -= offset;
              foreach (Codeword previousRowCodeword in detectionResult.DetectionResultColumns[barcodeColumn].Codewords)
              {
                 if (previousRowCodeword != null)
                 {
                    return (leftToRight ? previousRowCodeword.EndX : previousRowCodeword.StartX) +
                           offset*
                           skippedColumns*
                           (previousRowCodeword.EndX - previousRowCodeword.StartX);
                 }
              }
              skippedColumns++;
           }
           return leftToRight ? detectionResult.Box.MinX : detectionResult.Box.MaxX;
        }
  
        /// <summary>
        /// Detects the codeword.
        /// </summary>
        /// <returns>The codeword.</returns>
        /// <param name="image">Image.</param>
        /// <param name="minColumn">Minimum column.</param>
        /// <param name="maxColumn">Max column.</param>
        /// <param name="leftToRight">If set to <c>true</c> left to right.</param>
        /// <param name="startColumn">Start column.</param>
        /// <param name="imageRow">Image row.</param>
        /// <param name="minCodewordWidth">Minimum codeword width.</param>
        /// <param name="maxCodewordWidth">Max codeword width.</param>
        private static Codeword detectCodeword(BitMatrix image,
                                               int minColumn,
                                               int maxColumn,
                                               bool leftToRight,
                                               int startColumn,
                                               int imageRow,
                                               int minCodewordWidth,
                                               int maxCodewordWidth)
        {
           startColumn = adjustCodewordStartColumn(image, minColumn, maxColumn, leftToRight, startColumn, imageRow);
           // we usually know fairly exact now how long a codeword is. We should provide minimum and maximum expected length
           // and try to adjust the read pixels, e.g. remove single pixel errors or try to cut off exceeding pixels.
           // min and maxCodewordWidth should not be used as they are calculated for the whole barcode an can be inaccurate
           // for the current position
           int[] moduleBitCount = getModuleBitCount(image, minColumn, maxColumn, leftToRight, startColumn, imageRow);
           if (moduleBitCount == null)
           {
              return null;
           }
           int endColumn;
           int codewordBitCount = PDF417Common.getBitCountSum(moduleBitCount);
           if (leftToRight)
           {
              endColumn = startColumn + codewordBitCount;
           }
           else
           {
              for (int i = 0; i < (moduleBitCount.Length >> 1); i++)
              {
                 int tmpCount = moduleBitCount[i];
                 moduleBitCount[i] = moduleBitCount[moduleBitCount.Length - 1 - i];
                 moduleBitCount[moduleBitCount.Length - 1 - i] = tmpCount;
              }
              endColumn = startColumn;
              startColumn = endColumn - codewordBitCount;
           }
           // TODO implement check for width and correction of black and white bars
           // use start (and maybe stop pattern) to determine if blackbars are wider than white bars. If so, adjust.
           // should probably done only for codewords with a lot more than 17 bits. 
           // The following fixes 10-1.png, which has wide black bars and small white bars
           //    for (int i = 0; i < moduleBitCount.Length; i++) {
           //      if (i % 2 == 0) {
           //        moduleBitCount[i]--;
           //      } else {
           //        moduleBitCount[i]++;
           //      }
           //    }
  
           // We could also use the width of surrounding codewords for more accurate results, but this seems
           // sufficient for now
           if (!checkCodewordSkew(codewordBitCount, minCodewordWidth, maxCodewordWidth))
           {
              // We could try to use the startX and endX position of the codeword in the same column in the previous row,
              // create the bit count from it and normalize it to 8. This would help with single pixel errors.
              return null;
           }
  
           int decodedValue = PDF417CodewordDecoder.getDecodedValue(moduleBitCount);
           int codeword = PDF417Common.getCodeword(decodedValue);
           if (codeword == -1)
           {
              return null;
           }
           return new Codeword(startColumn, endColumn, getCodewordBucketNumber(decodedValue), codeword);
        }
  
        /// <summary>
        /// Gets the module bit count.
        /// </summary>
        /// <returns>The module bit count.</returns>
        /// <param name="image">Image.</param>
        /// <param name="minColumn">Minimum column.</param>
        /// <param name="maxColumn">Max column.</param>
        /// <param name="leftToRight">If set to <c>true</c> left to right.</param>
        /// <param name="startColumn">Start column.</param>
        /// <param name="imageRow">Image row.</param>
        private static int[] getModuleBitCount(BitMatrix image,
                                               int minColumn,
                                               int maxColumn,
                                               bool leftToRight,
                                               int startColumn,
                                               int imageRow)
        {
           int imageColumn = startColumn;
           int[] moduleBitCount = new int[8];
           int moduleNumber = 0;
           int increment = leftToRight ? 1 : -1;
           bool previousPixelValue = leftToRight;
           while (((leftToRight && imageColumn < maxColumn) || (!leftToRight && imageColumn >= minColumn)) &&
                  moduleNumber < moduleBitCount.Length)
           {
              if (image[imageColumn, imageRow] == previousPixelValue)
              {
                 moduleBitCount[moduleNumber]++;
                 imageColumn += increment;
              }
              else
              {
                 moduleNumber++;
                 previousPixelValue = !previousPixelValue;
              }
           }
           if (moduleNumber == moduleBitCount.Length ||
               (((leftToRight && imageColumn == maxColumn) || (!leftToRight && imageColumn == minColumn)) && moduleNumber == moduleBitCount.Length - 1))
           {
              return moduleBitCount;
           }
           return null;
        }
  
        /// <summary>
        /// Gets the number of EC code words.
        /// </summary>
        /// <returns>The number of EC code words.</returns>
        /// <param name="barcodeECLevel">Barcode EC level.</param>
        private static int getNumberOfECCodeWords(int barcodeECLevel)
        {
           return 2 << barcodeECLevel;
        }
  
        /// <summary>
        /// Adjusts the codeword start column.
        /// </summary>
        /// <returns>The codeword start column.</returns>
        /// <param name="image">Image.</param>
        /// <param name="minColumn">Minimum column.</param>
        /// <param name="maxColumn">Max column.</param>
        /// <param name="leftToRight">If set to <c>true</c> left to right.</param>
        /// <param name="codewordStartColumn">Codeword start column.</param>
        /// <param name="imageRow">Image row.</param>
        private static int adjustCodewordStartColumn(BitMatrix image,
                                                     int minColumn,
                                                     int maxColumn,
                                                     bool leftToRight,
                                                     int codewordStartColumn,
                                                     int imageRow)
        {
           int correctedStartColumn = codewordStartColumn;
           int increment = leftToRight ? -1 : 1;
           // there should be no black pixels before the start column. If there are, then we need to start earlier.
           for (int i = 0; i < 2; i++)
           {
              while (((leftToRight && correctedStartColumn >= minColumn) || (!leftToRight && correctedStartColumn < maxColumn)) &&
                     leftToRight == image[correctedStartColumn, imageRow])
              {
                 if (Math.Abs(codewordStartColumn - correctedStartColumn) > CODEWORD_SKEW_SIZE)
                 {
                    return codewordStartColumn;
                 }
                 correctedStartColumn += increment;
              }
              increment = -increment;
              leftToRight = !leftToRight;
           }
           return correctedStartColumn;
        }
  
        /// <summary>
        /// Checks the codeword for any skew.
        /// </summary>
        /// <returns><c>true</c>, if codeword is within the skew, <c>false</c> otherwise.</returns>
        /// <param name="codewordSize">Codeword size.</param>
        /// <param name="minCodewordWidth">Minimum codeword width.</param>
        /// <param name="maxCodewordWidth">Max codeword width.</param>
        private static bool checkCodewordSkew(int codewordSize, int minCodewordWidth, int maxCodewordWidth)
        {
           return minCodewordWidth - CODEWORD_SKEW_SIZE <= codewordSize &&
                  codewordSize <= maxCodewordWidth + CODEWORD_SKEW_SIZE;
        }
  
        /// <summary>
        /// Decodes the codewords.
        /// </summary>
        /// <returns>The codewords.</returns>
        /// <param name="codewords">Codewords.</param>
        /// <param name="ecLevel">Ec level.</param>
        /// <param name="erasures">Erasures.</param>
        private static DecoderResult decodeCodewords(int[] codewords, int ecLevel, int[] erasures)
        {
           if (codewords.Length == 0)
           {
              return null;
           }
  
           int numECCodewords = 1 << (ecLevel + 1);
  
           int correctedErrorsCount = correctErrors(codewords, erasures, numECCodewords);
           if (correctedErrorsCount < 0)
           {
              return null;
           }
           if (!verifyCodewordCount(codewords, numECCodewords))
           {
              return null;
           }
  
           // Decode the codewords
           DecoderResult decoderResult = DecodedBitStreamParser.decode(codewords, ecLevel.ToString());
           decoderResult.ErrorsCorrected = correctedErrorsCount;
           decoderResult.Erasures = erasures.Length;
           return decoderResult;
        }
  
        /// <summary>
        /// Given data and error-correction codewords received, possibly corrupted by errors, attempts to
        /// correct the errors in-place.
        /// </summary>
        /// <returns>The errors.</returns>
        /// <param name="codewords">data and error correction codewords.</param>
        /// <param name="erasures">positions of any known erasures.</param>
        /// <param name="numECCodewords">number of error correction codewords that are available in codewords.</param>
        private static int correctErrors(int[] codewords, int[] erasures, int numECCodewords)
        {
           if (erasures != null &&
               erasures.Length > numECCodewords/2 + MAX_ERRORS ||
               numECCodewords < 0 ||
               numECCodewords > MAX_EC_CODEWORDS)
           {
              // Too many errors or EC Codewords is corrupted
              return -1;
  
           }
           int errorCount;
           if (!errorCorrection.decode(codewords, numECCodewords, erasures, out errorCount))
           {
              return -1;
           }
           return errorCount;
        }
  
        /// <summary>
        /// Verifies that all is well with the the codeword array.
        /// </summary>
        /// <param name="codewords">Codewords.</param>
        /// <param name="numECCodewords">Number EC codewords.</param>
        private static bool verifyCodewordCount(int[] codewords, int numECCodewords)
        {
           if (codewords.Length < 4)
           {
              // Codeword array size should be at least 4 allowing for
              // Count CW, At least one Data CW, Error Correction CW, Error Correction CW
              return false;
           }
           // The first codeword, the Symbol Length Descriptor, shall always encode the total number of data
           // codewords in the symbol, including the Symbol Length Descriptor itself, data codewords and pad
           // codewords, but excluding the number of error correction codewords.
           int numberOfCodewords = codewords[0];
           if (numberOfCodewords > codewords.Length)
           {
              return false;
           }
  
gt:          if (numberOfCodewords == 0)