/*
* 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
{
///
///
///
/// Guenther Grau
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();
///
/// 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.
///
/// Image.
/// Image top left.
/// Image bottom left.
/// Image top right.
/// Image bottom right.
/// Minimum codeword width.
/// Max codeword width.
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);
if (boundingBox == null)
return null;
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 , 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);
}
///
/// Merge the specified leftRowIndicatorColumn and rightRowIndicatorColumn.
///
/// Left row indicator column.
/// Right row indicator column.
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);
}
///
/// Adjusts the bounding box.
///
/// The bounding box.
/// Row indicator column.
private static BoundingBox adjustBoundingBox(DetectionResultRowIndicatorColumn rowIndicatorColumn)
{
if (rowIndicatorColumn == null)
{
return null;
}
int[] rowHeights = rowIndicatorColumn.getRowHeights();
if (rowHeights == null)
{
return null;
}
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;
}
///
/// Gets the barcode metadata.
///
/// The barcode metadata.
/// Left row indicator column.
/// Right row indicator column.
private static BarcodeMetadata getBarcodeMetadata(DetectionResultRowIndicatorColumn leftRowIndicatorColumn,
DetectionResultRowIndicatorColumn rightRowIndicatorColumn)
{
BarcodeMetadata leftBarcodeMetadata;
if (leftRowIndicatorColumn == null ||
(leftBarcodeMetadata = leftRowIndicatorColumn.getBarcodeMetadata()) == null)
{
return rightRowIndicatorColumn == null ? null : rightRowIndicatorColumn.getBarcodeMetadata();
}
BarcodeMetadata rightBarcodeMetadata;
if (rightRowIndicatorColumn == null ||
(rightBarcodeMetadata = rightRowIndicatorColumn.getBarcodeMetadata()) == null)
{
return leftBarcodeMetadata;
}
if (leftBarcodeMetadata.ColumnCount != rightBarcodeMetadata.ColumnCount &&
leftBarcodeMetadata.ErrorCorrectionLevel != rightBarcodeMetadata.ErrorCorrectionLevel &&
leftBarcodeMetadata.RowCount != rightBarcodeMetadata.RowCount)
{
return null;
}
return leftBarcodeMetadata;
}
///
/// Gets the row indicator column.
///
/// The row indicator column.
/// Image.
/// Bounding box.
/// Start point.
/// If set to true left to right.
/// Minimum codeword width.
/// Max codeword width.
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;
}
///
/// Adjusts the codeword count.
///
/// Detection result.
/// Barcode matrix.
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;
}
///
/// Creates the decoder result.
///
/// The decoder result.
/// Detection result.
private static DecoderResult createDecoderResult(DetectionResult detectionResult)
{
BarcodeValue[][] barcodeMatrix = createBarcodeMatrix(detectionResult);
if (barcodeMatrix == null)
return null;
if (!adjustCodewordCount(detectionResult, barcodeMatrix))
{
return null;
}
List erasures = new List();
int[] codewords = new int[detectionResult.RowCount*detectionResult.ColumnCount];
List ambiguousIndexValuesList = new List();
List ambiguousIndexesList = new List();
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);
}
///
/// 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.
///
/// The decoder result from ambiguous values.
/// Ec level.
/// Codewords.
/// contains the indexes of erasures.
/// array with the indexes that have more than one most likely value.
/// two dimensional array that contains the ambiguous values. The first dimension must
/// be the same Length as the ambiguousIndexes array.
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;
}
///
/// Creates the barcode matrix.
///
/// The barcode matrix.
/// Detection result.
private static BarcodeValue[][] createBarcodeMatrix(DetectionResult detectionResult)
{
// Manually setup Jagged Array in C#
var 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 = 0;
foreach (DetectionResultColumn detectionResultColumn in detectionResult.getDetectionResultColumns())
{
if (detectionResultColumn != null)
{
foreach (Codeword codeword in detectionResultColumn.Codewords)
{
if (codeword != null)
{
int rowNumber = codeword.RowNumber;
if (rowNumber >= 0)
{
if (rowNumber >= barcodeMatrix.Length)
{
return null;
}
barcodeMatrix[rowNumber][column].setValue(codeword.Value);
}
}
}
}
column++;
}
return barcodeMatrix;
}
///
/// Tests to see if the Barcode Column is Valid
///
/// true, if barcode column is valid, false otherwise.
/// Detection result.
/// Barcode column.
private static bool isValidBarcodeColumn(DetectionResult detectionResult, int barcodeColumn)
{
return (barcodeColumn >= 0) && (barcodeColumn < detectionResult.DetectionResultColumns.Length);
}
///
/// Gets the start column.
///
/// The start column.
/// Detection result.
/// Barcode column.
/// Image row.
/// If set to true left to right.
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.StartX : codeword.EndX;
}
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;
}
///
/// Detects the codeword.
///
/// The codeword.
/// Image.
/// Minimum column.
/// Max column.
/// If set to true left to right.
/// Start column.
/// Image row.
/// Minimum codeword width.
/// Max codeword width.
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);
}
///
/// Gets the module bit count.
///
/// The module bit count.
/// Image.
/// Minimum column.
/// Max column.
/// If set to true left to right.
/// Start column.
/// Image row.
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;
}
///
/// Gets the number of EC code words.
///
/// The number of EC code words.
/// Barcode EC level.
private static int getNumberOfECCodeWords(int barcodeECLevel)
{
return 2 << barcodeECLevel;
}
///
/// Adjusts the codeword start column.
///
/// The codeword start column.
/// Image.
/// Minimum column.
/// Max column.
/// If set to true left to right.
/// Codeword start column.
/// Image row.
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;
}
///
/// Checks the codeword for any skew.
///
/// true, if codeword is within the skew, false otherwise.
/// Codeword size.
/// Minimum codeword width.
/// Max codeword width.
private static bool checkCodewordSkew(int codewordSize, int minCodewordWidth, int maxCodewordWidth)
{
return minCodewordWidth - CODEWORD_SKEW_SIZE <= codewordSize &&
codewordSize <= maxCodewordWidth + CODEWORD_SKEW_SIZE;
}
///
/// Decodes the codewords.
///
/// The codewords.
/// Codewords.
/// Ec level.
/// Erasures.
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());
if (decoderResult != null)
{
decoderResult.ErrorsCorrected = correctedErrorsCount;
decoderResult.Erasures = erasures.Length;
}
return decoderResult;
}
///
/// Given data and error-correction codewords received, possibly corrupted by errors, attempts to
/// correct the errors in-place.
///
/// The errors.
/// data and error correction codewords.
/// positions of any known erasures.
/// number of error correction codewords that are available in codewords.
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;
}
///
/// Verifies that all is well with the the codeword array.
///
/// Codewords.
/// Number EC codewords.
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;
}
if (numberOfCodewords == 0)
{
// Reset to the Length of the array - 8 (Allow for at least level 3 Error Correction (8 Error Codewords)
if (numECCodewords < codewords.Length)
{
codewords[0] = codewords.Length - numECCodewords;
}
else
{
return false;
}
}
return true;
}
///
/// Gets the bit count for codeword.
///
/// The bit count for codeword.
/// Codeword.
private static int[] getBitCountForCodeword(int codeword)
{
int[] result = new int[8];
int previousValue = 0;
int i = result.Length - 1;
while (true)
{
if ((codeword & 0x1) != previousValue)
{
previousValue = codeword & 0x1;
i--;
if (i < 0)
{
break;
}
}
result[i]++;
codeword >>= 1;
}
return result;
}
///
/// Gets the codeword bucket number.
///
/// The codeword bucket number.
/// Codeword.
private static int getCodewordBucketNumber(int codeword)
{
return getCodewordBucketNumber(getBitCountForCodeword(codeword));
}
///
/// Gets the codeword bucket number.
///
/// The codeword bucket number.
/// Module bit count.
private static int getCodewordBucketNumber(int[] moduleBitCount)
{
return (moduleBitCount[0] - moduleBitCount[2] + moduleBitCount[4] - moduleBitCount[6] + 9)%9;
}
///
/// Returns a that represents the jagged array.
///
/// A that represents the jagged array.
/// Barcode matrix as a jagged array.
public static String ToString(BarcodeValue[][] barcodeMatrix)
{
StringBuilder formatter = new StringBuilder();
for (int row = 0; row < barcodeMatrix.Length; row++)
{
formatter.AppendFormat(CultureInfo.InvariantCulture, "Row {0,2}: ", row);
for (int column = 0; column < barcodeMatrix[row].Length; column++)
{
BarcodeValue barcodeValue = barcodeMatrix[row][column];
int[] values = barcodeValue.getValue();
if (values.Length == 0)
{
formatter.Append(" ");
}
else
{
formatter.AppendFormat(CultureInfo.InvariantCulture, "{0,4}({1,2})", values[0], barcodeValue.getConfidence(values[0]));
}
}
formatter.Append("\n");
}
return formatter.ToString();
}
}
}