添加项目文件。

This commit is contained in:
akwkevin
2021-07-23 09:42:22 +08:00
commit f25a958797
2798 changed files with 352360 additions and 0 deletions

View File

@@ -0,0 +1,40 @@
/*
* 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.
*/
namespace ZXing.PDF417.Internal
{
/// <summary>
/// Metadata about a PDF417 Barcode
/// </summary>
/// <author>Guenther Grau</author>
public sealed class BarcodeMetadata
{
public int ColumnCount { get; private set; }
public int ErrorCorrectionLevel { get; private set; }
public int RowCountUpper { get; private set; }
public int RowCountLower { get; private set; }
public int RowCount { get; private set; }
public BarcodeMetadata(int columnCount, int rowCountUpperPart, int rowCountLowerPart, int errorCorrectionLevel)
{
this.ColumnCount = columnCount;
this.ErrorCorrectionLevel = errorCorrectionLevel;
this.RowCountUpper = rowCountUpperPart;
this.RowCountLower = rowCountLowerPart;
this.RowCount = rowCountLowerPart + rowCountUpperPart;
}
}
}

View File

@@ -0,0 +1,80 @@
/*
* 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.Collections.Generic;
namespace ZXing.PDF417.Internal
{
/// <summary>
/// A Barcode Value for the PDF417 barcode.
/// The scanner will iterate through the bitmatrix,
/// and given the different methods or iterations
/// will increment a given barcode value's confidence.
///
/// When done, this will return the values of highest confidence.
/// </summary>
/// <author>Guenther Grau</author>
public sealed class BarcodeValue
{
private readonly IDictionary<int, int> values = new Dictionary<int, int>();
/// <summary>
/// Incremenets the Confidence for a given value. (Adds an occurance of a value)
///
/// </summary>
/// <param name="value">Value.</param>
public void setValue(int @value)
{
int confidence;
values.TryGetValue(@value, out confidence);
confidence++;
values[@value] = confidence;
}
/// <summary>
/// Determines the maximum occurrence of a set value and returns all values which were set with this occurrence.
/// </summary>
/// <returns>an array of int, containing the values with the highest occurrence, or null, if no value was set.</returns>
public int[] getValue()
{
int maxConfidence = -1;
List<int> result = new List<int>();
foreach (var entry in values)
{
if (entry.Value > maxConfidence)
{
maxConfidence = entry.Value;
result.Clear();
result.Add(entry.Key);
}
else if (entry.Value == maxConfidence)
{
result.Add(entry.Key);
}
}
return result.ToArray();
}
/// <summary>
/// Returns the confience value for a given barcode value
/// </summary>
/// <param name="barcodeValue">Barcode value.</param>
public int getConfidence(int barcodeValue)
{
return values.ContainsKey(barcodeValue) ? values[barcodeValue] : 0;
}
}
}

View File

@@ -0,0 +1,218 @@
/*
* 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 ZXing.Common;
namespace ZXing.PDF417.Internal
{
/// <summary>
/// A Bounding Box helper class
/// </summary>
/// <author>Guenther Grau</author>
public sealed class BoundingBox
{
private readonly BitMatrix image;
public ResultPoint TopLeft { get; private set; }
public ResultPoint TopRight { get; private set; }
public ResultPoint BottomLeft { get; private set; }
public ResultPoint BottomRight { get; private set; }
public int MinX { get; private set; }
public int MaxX { get; private set; }
public int MinY { get; private set; }
public int MaxY { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="ZXing.PDF417.Internal.BoundingBox"/> class.
/// returns null if the corner points don't match up correctly
/// </summary>
/// <param name="image">The image.</param>
/// <param name="topLeft">The top left.</param>
/// <param name="bottomLeft">The bottom left.</param>
/// <param name="topRight">The top right.</param>
/// <param name="bottomRight">The bottom right.</param>
/// <returns></returns>
public static BoundingBox Create(BitMatrix image,
ResultPoint topLeft,
ResultPoint bottomLeft,
ResultPoint topRight,
ResultPoint bottomRight)
{
if ((topLeft == null && topRight == null) ||
(bottomLeft == null && bottomRight == null) ||
(topLeft != null && bottomLeft == null) ||
(topRight != null && bottomRight == null))
{
return null;
}
return new BoundingBox(image, topLeft, bottomLeft,topRight, bottomRight);
}
/// <summary>
/// Creates the specified box.
/// </summary>
/// <param name="box">The box.</param>
/// <returns></returns>
public static BoundingBox Create(BoundingBox box)
{
return new BoundingBox(box.image, box.TopLeft, box.BottomLeft, box.TopRight, box.BottomRight);
}
/// <summary>
/// Initializes a new instance of the <see cref="ZXing.PDF417.Internal.BoundingBox"/> class.
/// Will throw an exception if the corner points don't match up correctly
/// </summary>
/// <param name="image">Image.</param>
/// <param name="topLeft">Top left.</param>
/// <param name="topRight">Top right.</param>
/// <param name="bottomLeft">Bottom left.</param>
/// <param name="bottomRight">Bottom right.</param>
private BoundingBox(BitMatrix image,
ResultPoint topLeft,
ResultPoint bottomLeft,
ResultPoint topRight,
ResultPoint bottomRight)
{
this.image = image;
this.TopLeft = topLeft;
this.TopRight = topRight;
this.BottomLeft = bottomLeft;
this.BottomRight = bottomRight;
calculateMinMaxValues();
}
/// <summary>
/// Merge two Bounding Boxes, getting the left corners of left, and the right corners of right
/// (Images should be the same)
/// </summary>
/// <param name="leftBox">Left.</param>
/// <param name="rightBox">Right.</param>
internal static BoundingBox merge(BoundingBox leftBox, BoundingBox rightBox)
{
if (leftBox == null)
return rightBox;
if (rightBox == null)
return leftBox;
return new BoundingBox(leftBox.image, leftBox.TopLeft, leftBox.BottomLeft, rightBox.TopRight, rightBox.BottomRight);
}
/// <summary>
/// Adds the missing rows.
/// </summary>
/// <returns>The missing rows.</returns>
/// <param name="missingStartRows">Missing start rows.</param>
/// <param name="missingEndRows">Missing end rows.</param>
/// <param name="isLeft">If set to <c>true</c> is left.</param>
public BoundingBox addMissingRows(int missingStartRows, int missingEndRows, bool isLeft)
{
ResultPoint newTopLeft = TopLeft;
ResultPoint newBottomLeft = BottomLeft;
ResultPoint newTopRight = TopRight;
ResultPoint newBottomRight = BottomRight;
if (missingStartRows > 0)
{
ResultPoint top = isLeft ? TopLeft : TopRight;
int newMinY = (int) top.Y - missingStartRows;
if (newMinY < 0)
{
newMinY = 0;
}
// TODO use existing points to better interpolate the new x positions
ResultPoint newTop = new ResultPoint(top.X, newMinY);
if (isLeft)
{
newTopLeft = newTop;
}
else
{
newTopRight = newTop;
}
}
if (missingEndRows > 0)
{
ResultPoint bottom = isLeft ? BottomLeft : BottomRight;
int newMaxY = (int) bottom.Y + missingEndRows;
if (newMaxY >= image.Height)
{
newMaxY = image.Height - 1;
}
// TODO use existing points to better interpolate the new x positions
ResultPoint newBottom = new ResultPoint(bottom.X, newMaxY);
if (isLeft)
{
newBottomLeft = newBottom;
}
else
{
newBottomRight = newBottom;
}
}
calculateMinMaxValues();
return new BoundingBox(image, newTopLeft, newBottomLeft, newTopRight, newBottomRight);
}
/// <summary>
/// Calculates the minimum and maximum X & Y values based on the corner points.
/// </summary>
private void calculateMinMaxValues()
{
// Constructor ensures that either Left or Right is not null
if (TopLeft == null)
{
TopLeft = new ResultPoint(0, TopRight.Y);
BottomLeft = new ResultPoint(0, BottomRight.Y);
}
else if (TopRight == null)
{
TopRight = new ResultPoint(image.Width - 1, TopLeft.Y);
BottomRight = new ResultPoint(image.Width - 1, TopLeft.Y);
}
MinX = (int) Math.Min(TopLeft.X, BottomLeft.X);
MaxX = (int) Math.Max(TopRight.X, BottomRight.X);
MinY = (int) Math.Min(TopLeft.Y, TopRight.Y);
MaxY = (int) Math.Max(BottomLeft.Y, BottomRight.Y);
}
/// <summary>
/// If we adjust the width, set a new right corner coordinate and recalculate
/// </summary>
/// <param name="bottomRight">Bottom right.</param>
internal void SetBottomRight(ResultPoint bottomRight)
{
this.BottomRight = bottomRight;
calculateMinMaxValues();
}
/*
/// <summary>
/// If we adjust the width, set a new right corner coordinate and recalculate
/// </summary>
/// <param name="topRight">Top right.</param>
internal void SetTopRight(ResultPoint topRight)
{
this.TopRight = topRight;
calculateMinMaxValues();
}
*/
}
}

View File

@@ -0,0 +1,97 @@
/*
* 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.
*/
namespace ZXing.PDF417.Internal
{
/// <summary>
/// A Codeword in the PDF417 barcode
/// </summary>
/// <author>Guenther Grau</author>
public sealed class Codeword
{
/// <summary>
/// Default value for the RowNumber (-1 being an invalid real number)
/// </summary>
private static readonly int BARCODE_ROW_UNKNOWN = -1;
public int StartX { get; private set; }
public int EndX { get; private set; }
public int Bucket { get; private set; }
public int Value { get; private set; }
public int RowNumber { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="ZXing.PDF417.Internal.Codeword"/> class.
/// </summary>
/// <param name="startX">Start x.</param>
/// <param name="endX">End x.</param>
/// <param name="bucket">Bucket.</param>
/// <param name="value">Value.</param>
public Codeword(int startX, int endX, int bucket, int value)
{
this.StartX = startX;
this.EndX = endX;
this.Bucket = bucket;
this.Value = value;
this.RowNumber = BARCODE_ROW_UNKNOWN;
}
/// <summary>
/// Gets the width.
/// </summary>
/// <value>The width.</value>
public int Width
{
get { return EndX - StartX; }
}
/// <summary>
/// Gets a value indicating whether this instance has valid row number.
/// </summary>
/// <value><c>true</c> if this instance has valid row number; otherwise, <c>false</c>.</value>
public bool HasValidRowNumber
{
get { return IsValidRowNumber(RowNumber); }
}
/// <summary>
/// Determines whether this instance is valid row number the specified rowNumber.
/// </summary>
/// <returns><c>true</c> if this instance is valid row number the specified rowNumber; otherwise, <c>false</c>.</returns>
/// <param name="rowNumber">Row number.</param>
public bool IsValidRowNumber(int rowNumber)
{
return rowNumber != BARCODE_ROW_UNKNOWN && Bucket == (rowNumber%3)*3;
}
/// <summary>
/// Sets the row number as the row's indicator column.
/// </summary>
public void setRowNumberAsRowIndicatorColumn()
{
this.RowNumber = (Value/30)*3 + Bucket/3;
}
/// <summary>
/// Returns a <see cref="System.String"/> that represents the current <see cref="ZXing.PDF417.Internal.Codeword"/>.
/// </summary>
/// <returns>A <see cref="System.String"/> that represents the current <see cref="ZXing.PDF417.Internal.Codeword"/>.</returns>
public override string ToString()
{
return RowNumber + "|" + Value;
}
}
}

View File

@@ -0,0 +1,808 @@
/*
* Copyright 2009 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.Text;
using System.Numerics;
using ZXing.Common;
namespace ZXing.PDF417.Internal
{
/// <summary>
/// <p>This class contains the methods for decoding the PDF417 codewords.</p>
///
/// <author>SITA Lab (kevin.osullivan@sita.aero)</author>
/// </summary>
internal static class DecodedBitStreamParser
{
private enum Mode
{
ALPHA,
LOWER,
MIXED,
PUNCT,
ALPHA_SHIFT,
PUNCT_SHIFT
}
private const int TEXT_COMPACTION_MODE_LATCH = 900;
private const int BYTE_COMPACTION_MODE_LATCH = 901;
private const int NUMERIC_COMPACTION_MODE_LATCH = 902;
private const int BYTE_COMPACTION_MODE_LATCH_6 = 924;
private const int ECI_USER_DEFINED = 925;
private const int ECI_GENERAL_PURPOSE = 926;
private const int ECI_CHARSET = 927;
private const int BEGIN_MACRO_PDF417_CONTROL_BLOCK = 928;
private const int BEGIN_MACRO_PDF417_OPTIONAL_FIELD = 923;
private const int MACRO_PDF417_TERMINATOR = 922;
private const int MODE_SHIFT_TO_BYTE_COMPACTION_MODE = 913;
private const int MAX_NUMERIC_CODEWORDS = 15;
private const int PL = 25;
private const int LL = 27;
private const int AS = 27;
private const int ML = 28;
private const int AL = 28;
private const int PS = 29;
private const int PAL = 29;
private static readonly char[] PUNCT_CHARS = {
';', '<', '>', '@', '[', '\\', ']', '_', '`', '~', '!',
'\r', '\t', ',', ':', '\n', '-', '.', '$', '/', '"', '|', '*',
'(', ')', '?', '{', '}', '\''
};
private static readonly char[] MIXED_CHARS = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '&',
'\r', '\t', ',', ':', '#', '-', '.', '$', '/', '+', '%', '*',
'=', '^'
};
/// <summary>
/// Table containing values for the exponent of 900.
/// This is used in the numeric compaction decode algorithm.
/// </summary>
private static readonly BigInteger[] EXP900;
static DecodedBitStreamParser()
{
EXP900 = new BigInteger[16];
EXP900[0] = BigInteger.One;
BigInteger nineHundred = new BigInteger(900);
EXP900[1] = nineHundred;
for (int i = 2; i < EXP900.Length; i++)
{
EXP900[i] = BigInteger.Multiply(EXP900[i - 1], nineHundred);
}
}
private const int NUMBER_OF_SEQUENCE_CODEWORDS = 2;
internal static DecoderResult decode(int[] codewords, String ecLevel)
{
var result = new StringBuilder(codewords.Length * 2);
// Get compaction mode
int codeIndex = 1;
int code = codewords[codeIndex++];
var resultMetadata = new PDF417ResultMetadata();
Encoding encoding = null;
while (codeIndex < codewords[0])
{
switch (code)
{
case TEXT_COMPACTION_MODE_LATCH:
codeIndex = textCompaction(codewords, codeIndex, result);
break;
case BYTE_COMPACTION_MODE_LATCH:
case BYTE_COMPACTION_MODE_LATCH_6:
codeIndex = byteCompaction(code, codewords, encoding ?? (encoding = getEncoding(PDF417HighLevelEncoder.DEFAULT_ENCODING_NAME)), codeIndex, result);
break;
case MODE_SHIFT_TO_BYTE_COMPACTION_MODE:
result.Append((char)codewords[codeIndex++]);
break;
case NUMERIC_COMPACTION_MODE_LATCH:
codeIndex = numericCompaction(codewords, codeIndex, result);
break;
case ECI_CHARSET:
var charsetECI = CharacterSetECI.getCharacterSetECIByValue(codewords[codeIndex++]);
encoding = getEncoding(charsetECI.EncodingName);
break;
case ECI_GENERAL_PURPOSE:
// Can't do anything with generic ECI; skip its 2 characters
codeIndex += 2;
break;
case ECI_USER_DEFINED:
// Can't do anything with user ECI; skip its 1 character
codeIndex++;
break;
case BEGIN_MACRO_PDF417_CONTROL_BLOCK:
codeIndex = decodeMacroBlock(codewords, codeIndex, resultMetadata);
break;
case BEGIN_MACRO_PDF417_OPTIONAL_FIELD:
case MACRO_PDF417_TERMINATOR:
// Should not see these outside a macro block
return null;
default:
// Default to text compaction. During testing numerous barcodes
// appeared to be missing the starting mode. In these cases defaulting
// to text compaction seems to work.
codeIndex--;
codeIndex = textCompaction(codewords, codeIndex, result);
break;
}
if (codeIndex < 0)
return null;
if (codeIndex < codewords.Length)
{
code = codewords[codeIndex++];
}
else
{
return null;
}
}
if (result.Length == 0)
{
return null;
}
var decoderResult = new DecoderResult(null, result.ToString(), null, ecLevel);
decoderResult.Other = resultMetadata;
return decoderResult;
}
private static Encoding getEncoding(string encodingName)
{
Encoding encoding = null;
try
{
encoding = Encoding.GetEncoding(encodingName);
}
#if (WINDOWS_PHONE70 || WINDOWS_PHONE71 || SILVERLIGHT4 || SILVERLIGHT5 || NETFX_CORE || MONOANDROID || MONOTOUCH)
catch (ArgumentException)
{
try
{
// Silverlight only supports a limited number of character sets, trying fallback to UTF-8
encoding = Encoding.GetEncoding("UTF-8");
}
catch (Exception)
{
}
}
#endif
#if WindowsCE
catch (PlatformNotSupportedException)
{
try
{
// WindowsCE doesn't support all encodings. But it is device depended.
// So we try here the some different ones
if (encodingName == "ISO-8859-1")
{
encoding = Encoding.GetEncoding(1252);
}
else
{
encoding = Encoding.GetEncoding("UTF-8");
}
}
catch (Exception)
{
}
}
#endif
catch (Exception)
{
return null;
}
return encoding;
}
private static int decodeMacroBlock(int[] codewords, int codeIndex, PDF417ResultMetadata resultMetadata)
{
if (codeIndex + NUMBER_OF_SEQUENCE_CODEWORDS > codewords[0])
{
// we must have at least two bytes left for the segment index
return -1;
}
int[] segmentIndexArray = new int[NUMBER_OF_SEQUENCE_CODEWORDS];
for (int i = 0; i < NUMBER_OF_SEQUENCE_CODEWORDS; i++, codeIndex++)
{
segmentIndexArray[i] = codewords[codeIndex];
}
String s = decodeBase900toBase10(segmentIndexArray, NUMBER_OF_SEQUENCE_CODEWORDS);
if (s == null)
return -1;
resultMetadata.SegmentIndex = Int32.Parse(s);
StringBuilder fileId = new StringBuilder();
codeIndex = textCompaction(codewords, codeIndex, fileId);
resultMetadata.FileId = fileId.ToString();
if (codewords[codeIndex] == BEGIN_MACRO_PDF417_OPTIONAL_FIELD)
{
codeIndex++;
int[] additionalOptionCodeWords = new int[codewords[0] - codeIndex];
int additionalOptionCodeWordsIndex = 0;
bool end = false;
while ((codeIndex < codewords[0]) && !end)
{
int code = codewords[codeIndex++];
if (code < TEXT_COMPACTION_MODE_LATCH)
{
additionalOptionCodeWords[additionalOptionCodeWordsIndex++] = code;
}
else
{
switch (code)
{
case MACRO_PDF417_TERMINATOR:
resultMetadata.IsLastSegment = true;
codeIndex++;
end = true;
break;
default:
return -1;
}
}
}
resultMetadata.OptionalData = new int[additionalOptionCodeWordsIndex];
Array.Copy(additionalOptionCodeWords, resultMetadata.OptionalData, additionalOptionCodeWordsIndex);
}
else if (codewords[codeIndex] == MACRO_PDF417_TERMINATOR)
{
resultMetadata.IsLastSegment = true;
codeIndex++;
}
return codeIndex;
}
/// <summary>
/// Text Compaction mode (see 5.4.1.5) permits all printable ASCII characters to be
/// encoded, i.e. values 32 - 126 inclusive in accordance with ISO/IEC 646 (IRV), as
/// well as selected control characters.
///
/// <param name="codewords">The array of codewords (data + error)</param>
/// <param name="codeIndex">The current index into the codeword array.</param>
/// <param name="result">The decoded data is appended to the result.</param>
/// <returns>The next index into the codeword array.</returns>
/// </summary>
private static int textCompaction(int[] codewords, int codeIndex, StringBuilder result)
{
// 2 character per codeword
int[] textCompactionData = new int[(codewords[0] - codeIndex) << 1];
// Used to hold the byte compaction value if there is a mode shift
int[] byteCompactionData = new int[(codewords[0] - codeIndex) << 1];
int index = 0;
bool end = false;
while ((codeIndex < codewords[0]) && !end)
{
int code = codewords[codeIndex++];
if (code < TEXT_COMPACTION_MODE_LATCH)
{
textCompactionData[index] = code / 30;
textCompactionData[index + 1] = code % 30;
index += 2;
}
else
{
switch (code)
{
case TEXT_COMPACTION_MODE_LATCH:
// reinitialize text compaction mode to alpha sub mode
textCompactionData[index++] = TEXT_COMPACTION_MODE_LATCH;
break;
case BYTE_COMPACTION_MODE_LATCH:
case BYTE_COMPACTION_MODE_LATCH_6:
case NUMERIC_COMPACTION_MODE_LATCH:
case BEGIN_MACRO_PDF417_CONTROL_BLOCK:
case BEGIN_MACRO_PDF417_OPTIONAL_FIELD:
case MACRO_PDF417_TERMINATOR:
codeIndex--;
end = true;
break;
case MODE_SHIFT_TO_BYTE_COMPACTION_MODE:
// The Mode Shift codeword 913 shall cause a temporary
// switch from Text Compaction mode to Byte Compaction mode.
// This switch shall be in effect for only the next codeword,
// after which the mode shall revert to the prevailing sub-mode
// of the Text Compaction mode. Codeword 913 is only available
// in Text Compaction mode; its use is described in 5.4.2.4.
textCompactionData[index] = MODE_SHIFT_TO_BYTE_COMPACTION_MODE;
code = codewords[codeIndex++];
byteCompactionData[index] = code;
index++;
break;
}
}
}
decodeTextCompaction(textCompactionData, byteCompactionData, index, result);
return codeIndex;
}
/// <summary>
/// The Text Compaction mode includes all the printable ASCII characters
/// (i.e. values from 32 to 126) and three ASCII control characters: HT or tab
/// (ASCII value 9), LF or line feed (ASCII value 10), and CR or carriage
/// return (ASCII value 13). The Text Compaction mode also includes various latch
/// and shift characters which are used exclusively within the mode. The Text
/// Compaction mode encodes up to 2 characters per codeword. The compaction rules
/// for converting data into PDF417 codewords are defined in 5.4.2.2. The sub-mode
/// switches are defined in 5.4.2.3.
///
/// <param name="textCompactionData">The text compaction data.</param>
/// <param name="byteCompactionData">The byte compaction data if there</param>
/// was a mode shift.
/// <param name="length">The size of the text compaction and byte compaction data.</param>
/// <param name="result">The decoded data is appended to the result.</param>
/// </summary>
private static void decodeTextCompaction(int[] textCompactionData,
int[] byteCompactionData,
int length,
StringBuilder result)
{
// Beginning from an initial state of the Alpha sub-mode
// The default compaction mode for PDF417 in effect at the start of each symbol shall always be Text
// Compaction mode Alpha sub-mode (uppercase alphabetic). A latch codeword from another mode to the Text
// Compaction mode shall always switch to the Text Compaction Alpha sub-mode.
Mode subMode = Mode.ALPHA;
Mode priorToShiftMode = Mode.ALPHA;
int i = 0;
while (i < length)
{
int subModeCh = textCompactionData[i];
char? ch = null;
switch (subMode)
{
case Mode.ALPHA:
// Alpha (uppercase alphabetic)
if (subModeCh < 26)
{
// Upper case Alpha Character
ch = (char)('A' + subModeCh);
}
else
{
if (subModeCh == 26)
{
ch = ' ';
}
else if (subModeCh == LL)
{
subMode = Mode.LOWER;
}
else if (subModeCh == ML)
{
subMode = Mode.MIXED;
}
else if (subModeCh == PS)
{
// Shift to punctuation
priorToShiftMode = subMode;
subMode = Mode.PUNCT_SHIFT;
}
else if (subModeCh == MODE_SHIFT_TO_BYTE_COMPACTION_MODE)
{
// TODO Does this need to use the current character encoding? See other occurrences below
result.Append((char)byteCompactionData[i]);
}
else if (subModeCh == TEXT_COMPACTION_MODE_LATCH)
{
subMode = Mode.ALPHA;
}
}
break;
case Mode.LOWER:
// Lower (lowercase alphabetic)
if (subModeCh < 26)
{
ch = (char)('a' + subModeCh);
}
else
{
if (subModeCh == 26)
{
ch = ' ';
}
else if (subModeCh == AS)
{
// Shift to alpha
priorToShiftMode = subMode;
subMode = Mode.ALPHA_SHIFT;
}
else if (subModeCh == ML)
{
subMode = Mode.MIXED;
}
else if (subModeCh == PS)
{
// Shift to punctuation
priorToShiftMode = subMode;
subMode = Mode.PUNCT_SHIFT;
}
else if (subModeCh == MODE_SHIFT_TO_BYTE_COMPACTION_MODE)
{
result.Append((char)byteCompactionData[i]);
}
else if (subModeCh == TEXT_COMPACTION_MODE_LATCH)
{
subMode = Mode.ALPHA;
}
}
break;
case Mode.MIXED:
// Mixed (numeric and some punctuation)
if (subModeCh < PL)
{
ch = MIXED_CHARS[subModeCh];
}
else
{
if (subModeCh == PL)
{
subMode = Mode.PUNCT;
}
else if (subModeCh == 26)
{
ch = ' ';
}
else if (subModeCh == LL)
{
subMode = Mode.LOWER;
}
else if (subModeCh == AL)
{
subMode = Mode.ALPHA;
}
else if (subModeCh == PS)
{
// Shift to punctuation
priorToShiftMode = subMode;
subMode = Mode.PUNCT_SHIFT;
}
else if (subModeCh == MODE_SHIFT_TO_BYTE_COMPACTION_MODE)
{
result.Append((char)byteCompactionData[i]);
}
else if (subModeCh == TEXT_COMPACTION_MODE_LATCH)
{
subMode = Mode.ALPHA;
}
}
break;
case Mode.PUNCT:
// Punctuation
if (subModeCh < PAL)
{
ch = PUNCT_CHARS[subModeCh];
}
else
{
if (subModeCh == PAL)
{
subMode = Mode.ALPHA;
}
else if (subModeCh == MODE_SHIFT_TO_BYTE_COMPACTION_MODE)
{
result.Append((char)byteCompactionData[i]);
}
else if (subModeCh == TEXT_COMPACTION_MODE_LATCH)
{
subMode = Mode.ALPHA;
}
}
break;
case Mode.ALPHA_SHIFT:
// Restore sub-mode
subMode = priorToShiftMode;
if (subModeCh < 26)
{
ch = (char)('A' + subModeCh);
}
else
{
if (subModeCh == 26)
{
ch = ' ';
}
else if (subModeCh == TEXT_COMPACTION_MODE_LATCH)
{
subMode = Mode.ALPHA;
}
}
break;
case Mode.PUNCT_SHIFT:
// Restore sub-mode
subMode = priorToShiftMode;
if (subModeCh < PAL)
{
ch = PUNCT_CHARS[subModeCh];
}
else
{
if (subModeCh == PAL)
{
subMode = Mode.ALPHA;
}
else if (subModeCh == MODE_SHIFT_TO_BYTE_COMPACTION_MODE)
{
// PS before Shift-to-Byte is used as a padding character,
// see 5.4.2.4 of the specification
result.Append((char)byteCompactionData[i]);
}
else if (subModeCh == TEXT_COMPACTION_MODE_LATCH)
{
subMode = Mode.ALPHA;
}
}
break;
}
if (ch != null)
{
// Append decoded character to result
result.Append(ch.Value);
}
i++;
}
}
/// <summary>
/// Byte Compaction mode (see 5.4.3) permits all 256 possible 8-bit byte values to be encoded.
/// This includes all ASCII characters value 0 to 127 inclusive and provides for international
/// character set support.
///
/// <param name="mode">The byte compaction mode i.e. 901 or 924</param>
/// <param name="codewords">The array of codewords (data + error)</param>
/// <param name="encoding">Currently active character encoding</param>
/// <param name="codeIndex">The current index into the codeword array.</param>
/// <param name="result">The decoded data is appended to the result.</param>
/// <returns>The next index into the codeword array.</returns>
/// </summary>
private static int byteCompaction(int mode, int[] codewords, Encoding encoding, int codeIndex, StringBuilder result)
{
var decodedBytes = new System.IO.MemoryStream();
if (mode == BYTE_COMPACTION_MODE_LATCH)
{
// Total number of Byte Compaction characters to be encoded
// is not a multiple of 6
int count = 0;
long value = 0;
int[] byteCompactedCodewords = new int[6];
bool end = false;
int nextCode = codewords[codeIndex++];
while ((codeIndex < codewords[0]) && !end)
{
byteCompactedCodewords[count++] = nextCode;
// Base 900
value = 900 * value + nextCode;
nextCode = codewords[codeIndex++];
// perhaps it should be ok to check only nextCode >= TEXT_COMPACTION_MODE_LATCH
if (nextCode == TEXT_COMPACTION_MODE_LATCH ||
nextCode == BYTE_COMPACTION_MODE_LATCH ||
nextCode == NUMERIC_COMPACTION_MODE_LATCH ||
nextCode == BYTE_COMPACTION_MODE_LATCH_6 ||
nextCode == BEGIN_MACRO_PDF417_CONTROL_BLOCK ||
nextCode == BEGIN_MACRO_PDF417_OPTIONAL_FIELD ||
nextCode == MACRO_PDF417_TERMINATOR)
{
codeIndex--;
end = true;
}
else
{
if ((count%5 == 0) && (count > 0))
{
// Decode every 5 codewords
// Convert to Base 256
for (int j = 0; j < 6; ++j)
{
decodedBytes.WriteByte((byte)(value >> (8 * (5 - j))));
}
value = 0;
count = 0;
}
}
}
// if the end of all codewords is reached the last codeword needs to be added
if (codeIndex == codewords[0] && nextCode < TEXT_COMPACTION_MODE_LATCH)
byteCompactedCodewords[count++] = nextCode;
// If Byte Compaction mode is invoked with codeword 901,
// the last group of codewords is interpreted directly
// as one byte per codeword, without compaction.
for (int i = 0; i < count; i++)
{
decodedBytes.WriteByte((byte)byteCompactedCodewords[i]);
}
}
else if (mode == BYTE_COMPACTION_MODE_LATCH_6)
{
// Total number of Byte Compaction characters to be encoded
// is an integer multiple of 6
int count = 0;
long value = 0;
bool end = false;
while (codeIndex < codewords[0] && !end)
{
int code = codewords[codeIndex++];
if (code < TEXT_COMPACTION_MODE_LATCH)
{
count++;
// Base 900
value = 900 * value + code;
}
else
{
if (code == TEXT_COMPACTION_MODE_LATCH ||
code == BYTE_COMPACTION_MODE_LATCH ||
code == NUMERIC_COMPACTION_MODE_LATCH ||
code == BYTE_COMPACTION_MODE_LATCH_6 ||
code == BEGIN_MACRO_PDF417_CONTROL_BLOCK ||
code == BEGIN_MACRO_PDF417_OPTIONAL_FIELD ||
code == MACRO_PDF417_TERMINATOR)
{
codeIndex--;
end = true;
}
}
if ((count % 5 == 0) && (count > 0))
{
// Decode every 5 codewords
// Convert to Base 256
for (int j = 0; j < 6; ++j)
{
decodedBytes.WriteByte((byte)(value >> (8 * (5 - j))));
}
value = 0;
count = 0;
}
}
}
var bytes = decodedBytes.ToArray();
result.Append(encoding.GetString(bytes, 0, bytes.Length));
return codeIndex;
}
/// <summary>
/// Numeric Compaction mode (see 5.4.4) permits efficient encoding of numeric data strings.
///
/// <param name="codewords">The array of codewords (data + error)</param>
/// <param name="codeIndex">The current index into the codeword array.</param>
/// <param name="result">The decoded data is appended to the result.</param>
/// <returns>The next index into the codeword array.</returns>
/// </summary>
private static int numericCompaction(int[] codewords, int codeIndex, StringBuilder result)
{
int count = 0;
bool end = false;
int[] numericCodewords = new int[MAX_NUMERIC_CODEWORDS];
while (codeIndex < codewords[0] && !end)
{
int code = codewords[codeIndex++];
if (codeIndex == codewords[0])
{
end = true;
}
if (code < TEXT_COMPACTION_MODE_LATCH)
{
numericCodewords[count] = code;
count++;
}
else
{
if (code == TEXT_COMPACTION_MODE_LATCH ||
code == BYTE_COMPACTION_MODE_LATCH ||
code == BYTE_COMPACTION_MODE_LATCH_6 ||
code == BEGIN_MACRO_PDF417_CONTROL_BLOCK ||
code == BEGIN_MACRO_PDF417_OPTIONAL_FIELD ||
code == MACRO_PDF417_TERMINATOR)
{
codeIndex--;
end = true;
}
}
if (count % MAX_NUMERIC_CODEWORDS == 0 ||
code == NUMERIC_COMPACTION_MODE_LATCH ||
end)
{
// Re-invoking Numeric Compaction mode (by using codeword 902
// while in Numeric Compaction mode) serves to terminate the
// current Numeric Compaction mode grouping as described in 5.4.4.2,
// and then to start a new one grouping.
if (count > 0)
{
String s = decodeBase900toBase10(numericCodewords, count);
if (s == null)
return -1;
result.Append(s);
count = 0;
}
}
}
return codeIndex;
}
/// <summary>
/// Convert a list of Numeric Compacted codewords from Base 900 to Base 10.
/// EXAMPLE
/// Encode the fifteen digit numeric string 000213298174000
/// Prefix the numeric string with a 1 and set the initial value of
/// t = 1 000 213 298 174 000
/// Calculate codeword 0
/// d0 = 1 000 213 298 174 000 mod 900 = 200
///
/// t = 1 000 213 298 174 000 div 900 = 1 111 348 109 082
/// Calculate codeword 1
/// d1 = 1 111 348 109 082 mod 900 = 282
///
/// t = 1 111 348 109 082 div 900 = 1 234 831 232
/// Calculate codeword 2
/// d2 = 1 234 831 232 mod 900 = 632
///
/// t = 1 234 831 232 div 900 = 1 372 034
/// Calculate codeword 3
/// d3 = 1 372 034 mod 900 = 434
///
/// t = 1 372 034 div 900 = 1 524
/// Calculate codeword 4
/// d4 = 1 524 mod 900 = 624
///
/// t = 1 524 div 900 = 1
/// Calculate codeword 5
/// d5 = 1 mod 900 = 1
/// t = 1 div 900 = 0
/// Codeword sequence is: 1, 624, 434, 632, 282, 200
///
/// Decode the above codewords involves
/// 1 x 900 power of 5 + 624 x 900 power of 4 + 434 x 900 power of 3 +
/// 632 x 900 power of 2 + 282 x 900 power of 1 + 200 x 900 power of 0 = 1000213298174000
///
/// Remove leading 1 => Result is 000213298174000
/// <param name="codewords">The array of codewords</param>
/// <param name="count">The number of codewords</param>
/// <returns>The decoded string representing the Numeric data.</returns>
/// </summary>
private static String decodeBase900toBase10(int[] codewords, int count)
{
BigInteger result = BigInteger.Zero;
for (int i = 0; i < count; i++)
{
result = BigInteger.Add(result, BigInteger.Multiply(EXP900[count - i - 1], new BigInteger(codewords[i])));
}
String resultString = result.ToString();
if (resultString[0] != '1')
{
return null;
}
return resultString.Substring(1);
}
}
}

View File

@@ -0,0 +1,379 @@
/*
* 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.Globalization;
using System.Text;
namespace ZXing.PDF417.Internal
{
/// <summary>
///
/// </summary>
/// <author>Guenther Grau</author>
public class DetectionResult
{
private const int ADJUST_ROW_NUMBER_SKIP = 2;
public BarcodeMetadata Metadata { get; private set; }
public DetectionResultColumn[] DetectionResultColumns { get; set; }
public BoundingBox Box { get; set; }
public int ColumnCount { get; private set; }
public int RowCount
{
get { return Metadata.RowCount; }
}
public int ErrorCorrectionLevel
{
get { return Metadata.ErrorCorrectionLevel; }
}
public DetectionResult(BarcodeMetadata metadata, BoundingBox box)
{
Metadata = metadata;
Box = box;
ColumnCount = metadata.ColumnCount;
DetectionResultColumns = new DetectionResultColumn[ColumnCount + 2];
}
/// <summary>
/// Returns the DetectionResult Columns. This does a fair bit of calculation, so call it sparingly.
/// </summary>
/// <returns>The detection result columns.</returns>
public DetectionResultColumn[] getDetectionResultColumns()
{
adjustIndicatorColumnRowNumbers(DetectionResultColumns[0]);
adjustIndicatorColumnRowNumbers(DetectionResultColumns[ColumnCount + 1]);
int unadjustedCodewordCount = PDF417Common.MAX_CODEWORDS_IN_BARCODE;
int previousUnadjustedCount;
do
{
previousUnadjustedCount = unadjustedCodewordCount;
unadjustedCodewordCount = adjustRowNumbers();
} while (unadjustedCodewordCount > 0 && unadjustedCodewordCount < previousUnadjustedCount);
return DetectionResultColumns;
}
/// <summary>
/// Adjusts the indicator column row numbers.
/// </summary>
/// <param name="detectionResultColumn">Detection result column.</param>
private void adjustIndicatorColumnRowNumbers(DetectionResultColumn detectionResultColumn)
{
if (detectionResultColumn != null)
{
((DetectionResultRowIndicatorColumn) detectionResultColumn)
.adjustCompleteIndicatorColumnRowNumbers(Metadata);
}
}
/// <summary>
/// return number of codewords which don't have a valid row number. Note that the count is not accurate as codewords .
/// will be counted several times. It just serves as an indicator to see when we can stop adjusting row numbers
/// </summary>
/// <returns>The row numbers.</returns>
private int adjustRowNumbers()
{
// TODO ensure that no detected codewords with unknown row number are left
// we should be able to estimate the row height and use it as a hint for the row number
// we should also fill the rows top to bottom and bottom to top
int unadjustedCount = adjustRowNumbersByRow();
if (unadjustedCount == 0)
{
return 0;
}
for (int barcodeColumn = 1; barcodeColumn < ColumnCount + 1; barcodeColumn++)
{
Codeword[] codewords = DetectionResultColumns[barcodeColumn].Codewords;
for (int codewordsRow = 0; codewordsRow < codewords.Length; codewordsRow++)
{
if (codewords[codewordsRow] == null)
{
continue;
}
if (!codewords[codewordsRow].HasValidRowNumber)
{
adjustRowNumbers(barcodeColumn, codewordsRow, codewords);
}
}
}
return unadjustedCount;
}
/// <summary>
/// Adjusts the row numbers by row.
/// </summary>
/// <returns>The row numbers by row.</returns>
private int adjustRowNumbersByRow()
{
adjustRowNumbersFromBothRI(); // RI = RowIndicators
// TODO we should only do full row adjustments if row numbers of left and right row indicator column match.
// Maybe it's even better to calculated the height (in codeword rows) and divide it by the number of barcode
// rows. This, together with the LRI and RRI row numbers should allow us to get a good estimate where a row
// number starts and ends.
int unadjustedCount = adjustRowNumbersFromLRI();
return unadjustedCount + adjustRowNumbersFromRRI();
}
/// <summary>
/// Adjusts the row numbers from both Row Indicators
/// </summary>
/// <returns> zero </returns>
private void adjustRowNumbersFromBothRI()
{
if (DetectionResultColumns[0] == null || DetectionResultColumns[ColumnCount + 1] == null)
{
return;
}
Codeword[] LRIcodewords = DetectionResultColumns[0].Codewords;
Codeword[] RRIcodewords = DetectionResultColumns[ColumnCount + 1].Codewords;
for (int codewordsRow = 0; codewordsRow < LRIcodewords.Length; codewordsRow++)
{
if (LRIcodewords[codewordsRow] != null &&
RRIcodewords[codewordsRow] != null &&
LRIcodewords[codewordsRow].RowNumber == RRIcodewords[codewordsRow].RowNumber)
{
for (int barcodeColumn = 1; barcodeColumn <= ColumnCount; barcodeColumn++)
{
Codeword codeword = DetectionResultColumns[barcodeColumn].Codewords[codewordsRow];
if (codeword == null)
{
continue;
}
codeword.RowNumber = LRIcodewords[codewordsRow].RowNumber;
if (!codeword.HasValidRowNumber)
{
// LOG.info("Removing codeword with invalid row number, cw[" + codewordsRow + "][" + barcodeColumn + "]");
DetectionResultColumns[barcodeColumn].Codewords[codewordsRow] = null;
}
}
}
}
}
/// <summary>
/// Adjusts the row numbers from Right Row Indicator.
/// </summary>
/// <returns>The unadjusted row count.</returns>
private int adjustRowNumbersFromRRI()
{
if (DetectionResultColumns[ColumnCount + 1] == null)
{
return 0;
}
int unadjustedCount = 0;
Codeword[] codewords = DetectionResultColumns[ColumnCount + 1].Codewords;
for (int codewordsRow = 0; codewordsRow < codewords.Length; codewordsRow++)
{
if (codewords[codewordsRow] == null)
{
continue;
}
int rowIndicatorRowNumber = codewords[codewordsRow].RowNumber;
int invalidRowCounts = 0;
for (int barcodeColumn = ColumnCount + 1; barcodeColumn > 0 && invalidRowCounts < ADJUST_ROW_NUMBER_SKIP; barcodeColumn--)
{
Codeword codeword = DetectionResultColumns[barcodeColumn].Codewords[codewordsRow];
if (codeword != null)
{
invalidRowCounts = adjustRowNumberIfValid(rowIndicatorRowNumber, invalidRowCounts, codeword);
if (!codeword.HasValidRowNumber)
{
unadjustedCount++;
}
}
}
}
return unadjustedCount;
}
/// <summary>
/// Adjusts the row numbers from Left Row Indicator.
/// </summary>
/// <returns> Unadjusted row Count.</returns>
private int adjustRowNumbersFromLRI()
{
if (DetectionResultColumns[0] == null)
{
return 0;
}
int unadjustedCount = 0;
Codeword[] codewords = DetectionResultColumns[0].Codewords;
for (int codewordsRow = 0; codewordsRow < codewords.Length; codewordsRow++)
{
if (codewords[codewordsRow] == null)
{
continue;
}
int rowIndicatorRowNumber = codewords[codewordsRow].RowNumber;
int invalidRowCounts = 0;
for (int barcodeColumn = 1; barcodeColumn < ColumnCount + 1 && invalidRowCounts < ADJUST_ROW_NUMBER_SKIP; barcodeColumn++)
{
Codeword codeword = DetectionResultColumns[barcodeColumn].Codewords[codewordsRow];
if (codeword != null)
{
invalidRowCounts = adjustRowNumberIfValid(rowIndicatorRowNumber, invalidRowCounts, codeword);
if (!codeword.HasValidRowNumber)
{
unadjustedCount++;
}
}
}
}
return unadjustedCount;
}
/// <summary>
/// Adjusts the row number if valid.
/// </summary>
/// <returns>The invalid rows</returns>
/// <param name="rowIndicatorRowNumber">Row indicator row number.</param>
/// <param name="invalidRowCounts">Invalid row counts.</param>
/// <param name="codeword">Codeword.</param>
private static int adjustRowNumberIfValid(int rowIndicatorRowNumber, int invalidRowCounts, Codeword codeword)
{
if (codeword == null)
{
return invalidRowCounts;
}
if (!codeword.HasValidRowNumber)
{
if (codeword.IsValidRowNumber(rowIndicatorRowNumber))
{
codeword.RowNumber = rowIndicatorRowNumber;
invalidRowCounts = 0;
}
else
{
++invalidRowCounts;
}
}
return invalidRowCounts;
}
/// <summary>
/// Adjusts the row numbers.
/// </summary>
/// <param name="barcodeColumn">Barcode column.</param>
/// <param name="codewordsRow">Codewords row.</param>
/// <param name="codewords">Codewords.</param>
private void adjustRowNumbers(int barcodeColumn, int codewordsRow, Codeword[] codewords)
{
Codeword codeword = codewords[codewordsRow];
Codeword[] previousColumnCodewords = DetectionResultColumns[barcodeColumn - 1].Codewords;
Codeword[] nextColumnCodewords = previousColumnCodewords;
if (DetectionResultColumns[barcodeColumn + 1] != null)
{
nextColumnCodewords = DetectionResultColumns[barcodeColumn + 1].Codewords;
}
Codeword[] otherCodewords = new Codeword[14];
otherCodewords[2] = previousColumnCodewords[codewordsRow];
otherCodewords[3] = nextColumnCodewords[codewordsRow];
if (codewordsRow > 0)
{
otherCodewords[0] = codewords[codewordsRow - 1];
otherCodewords[4] = previousColumnCodewords[codewordsRow - 1];
otherCodewords[5] = nextColumnCodewords[codewordsRow - 1];
}
if (codewordsRow > 1)
{
otherCodewords[8] = codewords[codewordsRow - 2];
otherCodewords[10] = previousColumnCodewords[codewordsRow - 2];
otherCodewords[11] = nextColumnCodewords[codewordsRow - 2];
}
if (codewordsRow < codewords.Length - 1)
{
otherCodewords[1] = codewords[codewordsRow + 1];
otherCodewords[6] = previousColumnCodewords[codewordsRow + 1];
otherCodewords[7] = nextColumnCodewords[codewordsRow + 1];
}
if (codewordsRow < codewords.Length - 2)
{
otherCodewords[9] = codewords[codewordsRow + 2];
otherCodewords[12] = previousColumnCodewords[codewordsRow + 2];
otherCodewords[13] = nextColumnCodewords[codewordsRow + 2];
}
foreach (Codeword otherCodeword in otherCodewords)
{
if (adjustRowNumber(codeword, otherCodeword))
{
return;
}
}
}
/// <summary>
/// Adjusts the row number.
/// </summary>
/// <returns><c>true</c>, if row number was adjusted, <c>false</c> otherwise.</returns>
/// <param name="codeword">Codeword.</param>
/// <param name="otherCodeword">Other codeword.</param>
private static bool adjustRowNumber(Codeword codeword, Codeword otherCodeword)
{
if (otherCodeword == null)
{
return false;
}
if (otherCodeword.HasValidRowNumber && otherCodeword.Bucket == codeword.Bucket)
{
codeword.RowNumber = otherCodeword.RowNumber;
return true;
}
return false;
}
/// <summary>
/// Returns a <see cref="System.String"/> that represents the current <see cref="ZXing.PDF417.Internal.DetectionResult"/>.
/// </summary>
/// <returns>A <see cref="System.String"/> that represents the current <see cref="ZXing.PDF417.Internal.DetectionResult"/>.</returns>
public override string ToString()
{
StringBuilder formatter = new StringBuilder();
DetectionResultColumn rowIndicatorColumn = DetectionResultColumns[0];
if (rowIndicatorColumn == null)
{
rowIndicatorColumn = DetectionResultColumns[ColumnCount + 1];
}
for (int codewordsRow = 0; codewordsRow < rowIndicatorColumn.Codewords.Length; codewordsRow++)
{
formatter.AppendFormat(CultureInfo.InvariantCulture, "CW {0,3}:", codewordsRow);
for (int barcodeColumn = 0; barcodeColumn < ColumnCount + 2; barcodeColumn++)
{
if (DetectionResultColumns[barcodeColumn] == null)
{
formatter.Append(" | ");
continue;
}
Codeword codeword = DetectionResultColumns[barcodeColumn].Codewords[codewordsRow];
if (codeword == null)
{
formatter.Append(" | ");
continue;
}
formatter.AppendFormat(CultureInfo.InvariantCulture, " {0,3}|{1,3}", codeword.RowNumber, codeword.Value);
}
formatter.Append("\n");
}
return formatter.ToString();
}
}
}

View File

@@ -0,0 +1,161 @@
/*
* 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.Globalization;
using System.Text;
namespace ZXing.PDF417.Internal
{
/// <summary>
/// Represents a Column in the Detection Result
/// </summary>
/// <author>Guenther Grau</author>
public class DetectionResultColumn
{
/// <summary>
/// The maximum distance to search in the codeword array in both the positive and negative directions
/// </summary>
private const int MAX_NEARBY_DISTANCE = 5;
/// <summary>
/// The Bounding Box around the column (in the BitMatrix)
/// </summary>
/// <value>The box.</value>
public BoundingBox Box { get; private set; }
/// <summary>
/// The Codewords the Box encodes for, offset by the Box minY.
/// Remember to Access this ONLY through GetCodeword(imageRow) if you're accessing it in that manner.
/// </summary>
/// <value>The codewords.</value>
public Codeword[] Codewords { get; set; }
// TODO convert this to a dictionary? Dictionary<imageRow, Codeword> ??
/// <summary>
/// Initializes a new instance of the <see cref="ZXing.PDF417.Internal.DetectionResultColumn"/> class.
/// </summary>
/// <param name="box">The Bounding Box around the column (in the BitMatrix)</param>
public DetectionResultColumn(BoundingBox box)
{
this.Box = BoundingBox.Create(box);
this.Codewords = new Codeword[Box.MaxY - Box.MinY + 1];
}
/// <summary>
/// Converts the Image's Row to the index in the Codewords array
/// </summary>
/// <returns>The Codeword Index.</returns>
/// <param name="imageRow">Image row.</param>
public int IndexForRow(int imageRow)
{
return imageRow - Box.MinY;
}
/// <summary>
/// Converts the Codeword array index into a Row in the Image (BitMatrix)
/// </summary>
/// <returns>The Image Row.</returns>
/// <param name="codewordIndex">Codeword index.</param>
public int RowForIndex(int codewordIndex)
{
return Box.MinY + codewordIndex;
}
/// <summary>
/// Gets the codeword for a given row
/// </summary>
/// <returns>The codeword.</returns>
/// <param name="imageRow">Image row.</param>
public Codeword getCodeword(int imageRow)
{
return Codewords[imageRowToCodewordIndex(imageRow)];
}
/// <summary>
/// Gets the codeword closest to the specified row in the image
/// </summary>
/// <param name="imageRow">Image row.</param>
public Codeword getCodewordNearby(int imageRow)
{
Codeword codeword = getCodeword(imageRow);
if (codeword != null)
{
return codeword;
}
for (int i = 1; i < MAX_NEARBY_DISTANCE; i++)
{
int nearImageRow = imageRowToCodewordIndex(imageRow) - i;
if (nearImageRow >= 0)
{
codeword = Codewords[nearImageRow];
if (codeword != null)
{
return codeword;
}
}
nearImageRow = imageRowToCodewordIndex(imageRow) + i;
if (nearImageRow < Codewords.Length)
{
codeword = Codewords[nearImageRow];
if (codeword != null)
{
return codeword;
}
}
}
return null;
}
internal int imageRowToCodewordIndex(int imageRow)
{
return imageRow - Box.MinY;
}
/// <summary>
/// Sets the codeword for an image row
/// </summary>
/// <param name="imageRow">Image row.</param>
/// <param name="codeword">Codeword.</param>
public void setCodeword(int imageRow, Codeword codeword)
{
Codewords[IndexForRow(imageRow)] = codeword;
}
/// <summary>
/// Returns a <see cref="System.String"/> that represents the current <see cref="ZXing.PDF417.Internal.DetectionResultColumn"/>.
/// </summary>
/// <returns>A <see cref="System.String"/> that represents the current <see cref="ZXing.PDF417.Internal.DetectionResultColumn"/>.</returns>
public override string ToString()
{
StringBuilder builder = new StringBuilder();
int row = 0;
foreach (var cw in Codewords)
{
if (cw == null)
{
builder.AppendFormat(CultureInfo.InvariantCulture, "{0,3}: | \n", row++);
}
else
{
builder.AppendFormat(CultureInfo.InvariantCulture, "{0,3}: {1,3}|{2,3}\n", row++, cw.RowNumber, cw.Value);
}
}
return builder.ToString();
// return "Valid Codewords: " + (from cw in Codewords where cw != null select cw).Count().ToString();
}
}
}

View File

@@ -0,0 +1,376 @@
/*
* 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;
namespace ZXing.PDF417.Internal
{
/// <summary>
/// Represents a Column in the Detection Result
/// </summary>
/// <author>Guenther Grau</author>
public sealed class DetectionResultRowIndicatorColumn : DetectionResultColumn
{
/// <summary>
/// Gets or sets a value indicating whether this instance is the left indicator
/// </summary>
/// <value><c>true</c> if this instance is left; otherwise, <c>false</c>.</value>
public bool IsLeft { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="ZXing.PDF417.Internal.DetectionResultRowIndicatorColumn"/> class.
/// </summary>
/// <param name="box">Box.</param>
/// <param name="isLeft">If set to <c>true</c> is left.</param>
public DetectionResultRowIndicatorColumn(BoundingBox box, bool isLeft)
: base(box)
{
this.IsLeft = isLeft;
}
/// <summary>
/// Sets the Row Numbers as Inidicator Columns
/// </summary>
public void setRowNumbers()
{
foreach (var cw in Codewords)
{
if (cw != null)
{
cw.setRowNumberAsRowIndicatorColumn();
}
}
}
/// <summary>
/// TODO implement properly
/// TODO maybe we should add missing codewords to store the correct row number to make
/// finding row numbers for other columns easier
/// use row height count to make detection of invalid row numbers more reliable
/// </summary>
/// <returns>The indicator column row numbers.</returns>
/// <param name="metadata">Metadata.</param>
public int adjustCompleteIndicatorColumnRowNumbers(BarcodeMetadata metadata)
{
var codewords = Codewords;
setRowNumbers(); // Assign this as an indicator column
removeIncorrectCodewords(codewords, metadata);
ResultPoint top = IsLeft ? Box.TopLeft : Box.TopRight;
ResultPoint bottom = IsLeft ? Box.BottomLeft : Box.BottomRight;
int firstRow = imageRowToCodewordIndex((int) top.Y);
int lastRow = imageRowToCodewordIndex((int) bottom.Y);
// We need to be careful using the average row height.
// Barcode could be skewed so that we have smaller and taller rows
float averageRowHeight = (lastRow - firstRow)/(float) metadata.RowCount;
// initialize loop
int barcodeRow = -1;
int maxRowHeight = 1;
int currentRowHeight = 0;
for (int codewordRow = firstRow; codewordRow < lastRow; codewordRow++)
{
var codeword = codewords[codewordRow];
if (codeword == null)
{
continue;
}
// float expectedRowNumber = (codewordsRow - firstRow) / averageRowHeight;
// if (Math.abs(codeword.getRowNumber() - expectedRowNumber) > 2) {
// SimpleLog.log(LEVEL.WARNING,
// "Removing codeword, rowNumberSkew too high, codeword[" + codewordsRow + "]: Expected Row: " +
// expectedRowNumber + ", RealRow: " + codeword.getRowNumber() + ", value: " + codeword.getValue());
// codewords[codewordsRow] = null;
// }
int rowDifference = codeword.RowNumber - barcodeRow;
// TODO improve handling with case where first row indicator doesn't start with 0
if (rowDifference == 0)
{
currentRowHeight++;
}
else if (rowDifference == 1)
{
maxRowHeight = Math.Max(maxRowHeight, currentRowHeight);
currentRowHeight = 1;
barcodeRow = codeword.RowNumber;
}
else if (rowDifference < 0 ||
codeword.RowNumber >= metadata.RowCount ||
rowDifference > codewordRow)
{
codewords[codewordRow] = null;
}
else
{
int checkedRows;
if (maxRowHeight > 2)
{
checkedRows = (maxRowHeight - 2)*rowDifference;
}
else
{
checkedRows = rowDifference;
}
bool closePreviousCodewordFound = checkedRows > codewordRow;
for (int i = 1; i <= checkedRows && !closePreviousCodewordFound; i++)
{
// there must be (height * rowDifference) number of codewords missing. For now we assume height = 1.
// This should hopefully get rid of most problems already.
closePreviousCodewordFound = codewords[codewordRow - i] != null;
}
if (closePreviousCodewordFound)
{
codewords[codewordRow] = null;
}
else
{
barcodeRow = codeword.RowNumber;
currentRowHeight = 1;
}
}
}
return (int) (averageRowHeight + 0.5);
}
/// <summary>
/// Gets the row heights.
/// </summary>
/// <returns>The row heights.</returns>
public int[] getRowHeights()
{
BarcodeMetadata barcodeMetadata = getBarcodeMetadata();
if (barcodeMetadata == null)
{
return null;
}
adjustIncompleteIndicatorColumnRowNumbers(barcodeMetadata);
int[] result = new int[barcodeMetadata.RowCount];
foreach (var codeword in Codewords)
{
if (codeword != null)
{
int rowNumber = codeword.RowNumber;
if (rowNumber >= result.Length)
{
return null;
}
result[rowNumber]++;
} // else throw exception? (or return null)
}
return result;
}
/// <summary>
/// Adjusts the in omplete indicator column row numbers.
/// </summary>
/// <param name="metadata">Metadata.</param>
public int adjustIncompleteIndicatorColumnRowNumbers(BarcodeMetadata metadata)
{
// TODO maybe we should add missing codewords to store the correct row number to make
// finding row numbers for other columns easier
// use row height count to make detection of invalid row numbers more reliable
ResultPoint top = IsLeft ? Box.TopLeft : Box.TopRight;
ResultPoint bottom = IsLeft ? Box.BottomLeft : Box.BottomRight;
int firstRow = imageRowToCodewordIndex((int) top.Y);
int lastRow = imageRowToCodewordIndex((int) bottom.Y);
// We need to be careful using the average row height.
// Barcode could be skewed so that we have smaller and taller rows
float averageRowHeight = (lastRow - firstRow)/(float) metadata.RowCount;
var codewords = Codewords;
// initialize loop
int barcodeRow = -1;
int maxRowHeight = 1;
int currentRowHeight = 0;
for (int codewordRow = firstRow; codewordRow < lastRow; codewordRow++)
{
var codeword = codewords[codewordRow];
if (codeword == null)
{
continue;
}
codeword.setRowNumberAsRowIndicatorColumn();
int rowDifference = codeword.RowNumber - barcodeRow;
// TODO improve handling with case where first row indicator doesn't start with 0
if (rowDifference == 0)
{
currentRowHeight++;
}
else if (rowDifference == 1)
{
maxRowHeight = Math.Max(maxRowHeight, currentRowHeight);
currentRowHeight = 1;
barcodeRow = codeword.RowNumber;
}
else if (codeword.RowNumber > metadata.RowCount)
{
Codewords[codewordRow] = null;
}
else
{
barcodeRow = codeword.RowNumber;
currentRowHeight = 1;
}
}
return (int) (averageRowHeight + 0.5);
}
/// <summary>
/// Gets the barcode metadata.
/// </summary>
/// <returns>The barcode metadata.</returns>
public BarcodeMetadata getBarcodeMetadata()
{
var codewords = Codewords;
BarcodeValue barcodeColumnCount = new BarcodeValue();
BarcodeValue barcodeRowCountUpperPart = new BarcodeValue();
BarcodeValue barcodeRowCountLowerPart = new BarcodeValue();
BarcodeValue barcodeECLevel = new BarcodeValue();
foreach (Codeword codeword in codewords)
{
if (codeword == null)
{
continue;
}
codeword.setRowNumberAsRowIndicatorColumn();
int rowIndicatorValue = codeword.Value%30;
int codewordRowNumber = codeword.RowNumber;
if (!IsLeft)
{
codewordRowNumber += 2;
}
switch (codewordRowNumber%3)
{
case 0:
barcodeRowCountUpperPart.setValue(rowIndicatorValue*3 + 1);
break;
case 1:
barcodeECLevel.setValue(rowIndicatorValue/3);
barcodeRowCountLowerPart.setValue(rowIndicatorValue%3);
break;
case 2:
barcodeColumnCount.setValue(rowIndicatorValue + 1);
break;
}
}
// Maybe we should check if we have ambiguous values?
var barcodeColumnCountValues = barcodeColumnCount.getValue();
var barcodeRowCountUpperPartValues = barcodeRowCountUpperPart.getValue();
var barcodeRowCountLowerPartValues = barcodeRowCountLowerPart.getValue();
var barcodeECLevelValues = barcodeECLevel.getValue();
if ((barcodeColumnCountValues.Length == 0) ||
(barcodeRowCountUpperPartValues.Length == 0) ||
(barcodeRowCountLowerPartValues.Length == 0) ||
(barcodeECLevelValues.Length == 0) ||
barcodeColumnCountValues[0] < 1 ||
barcodeRowCountUpperPartValues[0] + barcodeRowCountLowerPartValues[0] < PDF417Common.MIN_ROWS_IN_BARCODE ||
barcodeRowCountUpperPartValues[0] + barcodeRowCountLowerPartValues[0] > PDF417Common.MAX_ROWS_IN_BARCODE)
{
return null;
}
var barcodeMetadata = new BarcodeMetadata(barcodeColumnCountValues[0],
barcodeRowCountUpperPartValues[0],
barcodeRowCountLowerPartValues[0],
barcodeECLevelValues[0]);
removeIncorrectCodewords(codewords, barcodeMetadata);
return barcodeMetadata;
}
/// <summary>
/// Prune the codewords which do not match the metadata
/// TODO Maybe we should keep the incorrect codewords for the start and end positions?
/// </summary>
/// <param name="codewords">Codewords.</param>
/// <param name="metadata">Metadata.</param>
private void removeIncorrectCodewords(Codeword[] codewords, BarcodeMetadata metadata)
{
for (int row = 0; row < codewords.Length; row++)
{
var codeword = codewords[row];
if (codeword == null)
continue;
int indicatorValue = codeword.Value%30;
int rowNumber = codeword.RowNumber;
// Row does not exist in the metadata
if (rowNumber >= metadata.RowCount) // different to java rowNumber > metadata.RowCount
{
codewords[row] = null; // remove this.
continue;
}
if (!IsLeft)
{
rowNumber += 2;
}
switch (rowNumber%3)
{
default:
case 0:
if (indicatorValue*3 + 1 != metadata.RowCountUpper)
{
codewords[row] = null;
}
break;
case 1:
if (indicatorValue%3 != metadata.RowCountLower ||
indicatorValue/3 != metadata.ErrorCorrectionLevel)
{
codewords[row] = null;
}
break;
case 2:
if (indicatorValue + 1 != metadata.ColumnCount)
{
codewords[row] = null;
}
break;
}
}
}
/// <summary>
/// Returns a <see cref="System.String"/> that represents the current <see cref="ZXing.PDF417.Internal.DetectionResultRowIndicatorColumn"/>.
/// </summary>
/// <returns>A <see cref="System.String"/> that represents the current <see cref="ZXing.PDF417.Internal.DetectionResultRowIndicatorColumn"/>.</returns>
public override string ToString()
{
return "Is Left: " + IsLeft + " \n" + base.ToString();
}
}
}

View File

@@ -0,0 +1,170 @@
/*
* 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.
*/
namespace ZXing.PDF417.Internal
{
/// <summary>
///
/// </summary>
/// <author>Guenther Grau</author>
/// <author>creatale GmbH (christoph.schulz@creatale.de)</author>
public static class PDF417CodewordDecoder
{
/// <summary>
/// The ratios table
/// </summary>
private static readonly float[][] RATIOS_TABLE; // = new float[PDF417Common.SYMBOL_TABLE.Length][PDF417Common.BARS_IN_MODULE];
/// <summary>
/// Initializes the <see cref="ZXing.PDF417.Internal.PDF417CodewordDecoder"/> class & Pre-computes the symbol ratio table.
/// </summary>
static PDF417CodewordDecoder()
{
// Jagged arrays in Java assign the memory automatically, but C# has no equivalent. (Jon Skeet says so!)
// http://stackoverflow.com/a/5313879/266252
RATIOS_TABLE = new float[PDF417Common.SYMBOL_TABLE.Length][];
for (int s = 0; s < RATIOS_TABLE.Length; s++)
{
RATIOS_TABLE[s] = new float[PDF417Common.BARS_IN_MODULE];
}
// Pre-computes the symbol ratio table.
for (int i = 0; i < PDF417Common.SYMBOL_TABLE.Length; i++)
{
int currentSymbol = PDF417Common.SYMBOL_TABLE[i];
int currentBit = currentSymbol & 0x1;
for (int j = 0; j < PDF417Common.BARS_IN_MODULE; j++)
{
float size = 0.0f;
while ((currentSymbol & 0x1) == currentBit)
{
size += 1.0f;
currentSymbol >>= 1;
}
currentBit = currentSymbol & 0x1;
RATIOS_TABLE[i][PDF417Common.BARS_IN_MODULE - j - 1] = size/PDF417Common.MODULES_IN_CODEWORD;
}
}
}
/// <summary>
/// Gets the decoded value.
/// </summary>
/// <returns>The decoded value.</returns>
/// <param name="moduleBitCount">Module bit count.</param>
public static int getDecodedValue(int[] moduleBitCount)
{
int decodedValue = getDecodedCodewordValue(sampleBitCounts(moduleBitCount));
if (decodedValue != PDF417Common.INVALID_CODEWORD)
{
return decodedValue;
}
return getClosestDecodedValue(moduleBitCount);
}
/// <summary>
/// Samples the bit counts.
/// </summary>
/// <returns>The bit counts.</returns>
/// <param name="moduleBitCount">Module bit count.</param>
private static int[] sampleBitCounts(int[] moduleBitCount)
{
float bitCountSum = PDF417Common.getBitCountSum(moduleBitCount);
int[] result = new int[PDF417Common.BARS_IN_MODULE];
int bitCountIndex = 0;
int sumPreviousBits = 0;
for (int i = 0; i < PDF417Common.MODULES_IN_CODEWORD; i++)
{
float sampleIndex =
bitCountSum/(2*PDF417Common.MODULES_IN_CODEWORD) +
(i*bitCountSum)/PDF417Common.MODULES_IN_CODEWORD;
if (sumPreviousBits + moduleBitCount[bitCountIndex] <= sampleIndex)
{
sumPreviousBits += moduleBitCount[bitCountIndex];
bitCountIndex++;
}
result[bitCountIndex]++;
}
return result;
}
/// <summary>
/// Gets the decoded codeword value.
/// </summary>
/// <returns>The decoded codeword value.</returns>
/// <param name="moduleBitCount">Module bit count.</param>
private static int getDecodedCodewordValue(int[] moduleBitCount)
{
int decodedValue = getBitValue(moduleBitCount);
return PDF417Common.getCodeword(decodedValue) == PDF417Common.INVALID_CODEWORD ? PDF417Common.INVALID_CODEWORD : decodedValue;
}
/// <summary>
/// Gets the bit value.
/// </summary>
/// <returns>The bit value.</returns>
/// <param name="moduleBitCount">Module bit count.</param>
private static int getBitValue(int[] moduleBitCount)
{
ulong result = 0;
for (ulong i = 0; i < (ulong) moduleBitCount.Length; i++)
{
for (int bit = 0; bit < moduleBitCount[i]; bit++)
{
result = (result << 1) | (i%2ul == 0ul ? 1ul : 0ul); // C# was warning about using the bit-wise 'OR' here with a mix of int/longs.
}
}
return (int) result;
}
/// <summary>
/// Gets the closest decoded value.
/// </summary>
/// <returns>The closest decoded value.</returns>
/// <param name="moduleBitCount">Module bit count.</param>
private static int getClosestDecodedValue(int[] moduleBitCount)
{
int bitCountSum = PDF417Common.getBitCountSum(moduleBitCount);
float[] bitCountRatios = new float[PDF417Common.BARS_IN_MODULE];
for (int i = 0; i < bitCountRatios.Length; i++)
{
bitCountRatios[i] = moduleBitCount[i]/(float) bitCountSum;
}
float bestMatchError = float.MaxValue;
int bestMatch = PDF417Common.INVALID_CODEWORD;
for (int j = 0; j < RATIOS_TABLE.Length; j++)
{
float error = 0.0f;
float[] ratioTableRow = RATIOS_TABLE[j];
for (int k = 0; k < PDF417Common.BARS_IN_MODULE; k++)
{
float diff = ratioTableRow[k] - bitCountRatios[k];
error += diff*diff;
if (error >= bestMatchError)
{
break;
}
}
if (error < bestMatchError)
{
bestMatchError = error;
bestMatch = PDF417Common.SYMBOL_TABLE[j];
}
}
return bestMatch;
}
}
}

View File

@@ -0,0 +1,916 @@
/*
* 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);
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 <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();
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;
}
/// <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)
{
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;
}
/// <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 (barcodeMatrix == null)
return null;
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#
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;
}
/// <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);
}
/// <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.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;
}
/// <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());
if (decoderResult != null)
{
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;
}
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;
}
/// <summary>
/// Gets the bit count for codeword.
/// </summary>
/// <returns>The bit count for codeword.</returns>
/// <param name="codeword">Codeword.</param>
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;
}
/// <summary>
/// Gets the codeword bucket number.
/// </summary>
/// <returns>The codeword bucket number.</returns>
/// <param name="codeword">Codeword.</param>
private static int getCodewordBucketNumber(int codeword)
{
return getCodewordBucketNumber(getBitCountForCodeword(codeword));
}
/// <summary>
/// Gets the codeword bucket number.
/// </summary>
/// <returns>The codeword bucket number.</returns>
/// <param name="moduleBitCount">Module bit count.</param>
private static int getCodewordBucketNumber(int[] moduleBitCount)
{
return (moduleBitCount[0] - moduleBitCount[2] + moduleBitCount[4] - moduleBitCount[6] + 9)%9;
}
/// <summary>
/// Returns a <see cref="System.String"/> that represents the <see cref="ZXing.PDF417.Internal.BarcodeValue"/> jagged array.
/// </summary>
/// <returns>A <see cref="System.String"/> that represents the <see cref="ZXing.PDF417.Internal.BarcodeValue"/> jagged array.</returns>
/// <param name="barcodeMatrix">Barcode matrix as a jagged array.</param>
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();
}
}
}

View File

@@ -0,0 +1,241 @@
/*
* Copyright 2012 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.
*/
namespace ZXing.PDF417.Internal.EC
{
/// <summary>
/// <p>PDF417 error correction implementation.</p>
/// <p>This <a href="http://en.wikipedia.org/wiki/Reed%E2%80%93Solomon_error_correction#Example">example</a>
/// is quite useful in understanding the algorithm.</p>
/// <author>Sean Owen</author>
/// <see cref="ZXing.Common.ReedSolomon.ReedSolomonDecoder" />
/// </summary>
public sealed class ErrorCorrection
{
private readonly ModulusGF field;
/// <summary>
/// Initializes a new instance of the <see cref="ErrorCorrection"/> class.
/// </summary>
public ErrorCorrection()
{
this.field = ModulusGF.PDF417_GF;
}
/// <summary>
/// Decodes the specified received.
/// </summary>
/// <param name="received">received codewords</param>
/// <param name="numECCodewords">number of those codewords used for EC</param>
/// <param name="erasures">location of erasures</param>
/// <param name="errorLocationsCount">The error locations count.</param>
/// <returns></returns>
public bool decode(int[] received, int numECCodewords, int[] erasures, out int errorLocationsCount)
{
ModulusPoly poly = new ModulusPoly(field, received);
int[] S = new int[numECCodewords];
bool error = false;
errorLocationsCount = 0;
for (int i = numECCodewords; i > 0; i--)
{
int eval = poly.evaluateAt(field.exp(i));
S[numECCodewords - i] = eval;
if (eval != 0)
{
error = true;
}
}
if (!error)
{
return true;
}
ModulusPoly knownErrors = field.One;
if (erasures != null)
{
foreach (int erasure in erasures)
{
int b = field.exp(received.Length - 1 - erasure);
// Add (1 - bx) term:
ModulusPoly term = new ModulusPoly(field, new int[] {field.subtract(0, b), 1});
knownErrors = knownErrors.multiply(term);
}
}
ModulusPoly syndrome = new ModulusPoly(field, S);
//syndrome = syndrome.multiply(knownErrors);
ModulusPoly[] sigmaOmega = runEuclideanAlgorithm(field.buildMonomial(numECCodewords, 1), syndrome, numECCodewords);
if (sigmaOmega == null)
{
return false;
}
ModulusPoly sigma = sigmaOmega[0];
ModulusPoly omega = sigmaOmega[1];
if (sigma == null || omega == null)
{
return false;
}
//sigma = sigma.multiply(knownErrors);
int[] errorLocations = findErrorLocations(sigma);
if (errorLocations == null)
{
return false;
}
int[] errorMagnitudes = findErrorMagnitudes(omega, sigma, errorLocations);
for (int i = 0; i < errorLocations.Length; i++)
{
int position = received.Length - 1 - field.log(errorLocations[i]);
if (position < 0)
{
return false;
}
received[position] = field.subtract(received[position], errorMagnitudes[i]);
}
errorLocationsCount = errorLocations.Length;
return true;
}
/// <summary>
/// Runs the euclidean algorithm (Greatest Common Divisor) until r's degree is less than R/2
/// </summary>
/// <returns>The euclidean algorithm.</returns>
private ModulusPoly[] runEuclideanAlgorithm(ModulusPoly a, ModulusPoly b, int R)
{
// Assume a's degree is >= b's
if (a.Degree < b.Degree)
{
ModulusPoly temp = a;
a = b;
b = temp;
}
ModulusPoly rLast = a;
ModulusPoly r = b;
ModulusPoly tLast = field.Zero;
ModulusPoly t = field.One;
// Run Euclidean algorithm until r's degree is less than R/2
while (r.Degree >= R / 2)
{
ModulusPoly rLastLast = rLast;
ModulusPoly tLastLast = tLast;
rLast = r;
tLast = t;
// Divide rLastLast by rLast, with quotient in q and remainder in r
if (rLast.isZero)
{
// Oops, Euclidean algorithm already terminated?
return null;
}
r = rLastLast;
ModulusPoly q = field.Zero;
int denominatorLeadingTerm = rLast.getCoefficient(rLast.Degree);
int dltInverse = field.inverse(denominatorLeadingTerm);
while (r.Degree >= rLast.Degree && !r.isZero)
{
int degreeDiff = r.Degree - rLast.Degree;
int scale = field.multiply(r.getCoefficient(r.Degree), dltInverse);
q = q.add(field.buildMonomial(degreeDiff, scale));
r = r.subtract(rLast.multiplyByMonomial(degreeDiff, scale));
}
t = q.multiply(tLast).subtract(tLastLast).getNegative();
}
int sigmaTildeAtZero = t.getCoefficient(0);
if (sigmaTildeAtZero == 0)
{
return null;
}
int inverse = field.inverse(sigmaTildeAtZero);
ModulusPoly sigma = t.multiply(inverse);
ModulusPoly omega = r.multiply(inverse);
return new ModulusPoly[] { sigma, omega };
}
/// <summary>
/// Finds the error locations as a direct application of Chien's search
/// </summary>
/// <returns>The error locations.</returns>
/// <param name="errorLocator">Error locator.</param>
private int[] findErrorLocations(ModulusPoly errorLocator)
{
// This is a direct application of Chien's search
int numErrors = errorLocator.Degree;
int[] result = new int[numErrors];
int e = 0;
for (int i = 1; i < field.Size && e < numErrors; i++)
{
if (errorLocator.evaluateAt(i) == 0)
{
result[e] = field.inverse(i);
e++;
}
}
if (e != numErrors)
{
return null;
}
return result;
}
/// <summary>
/// Finds the error magnitudes by directly applying Forney's Formula
/// </summary>
/// <returns>The error magnitudes.</returns>
/// <param name="errorEvaluator">Error evaluator.</param>
/// <param name="errorLocator">Error locator.</param>
/// <param name="errorLocations">Error locations.</param>
private int[] findErrorMagnitudes(ModulusPoly errorEvaluator,
ModulusPoly errorLocator,
int[] errorLocations)
{
int errorLocatorDegree = errorLocator.Degree;
int[] formalDerivativeCoefficients = new int[errorLocatorDegree];
for (int i = 1; i <= errorLocatorDegree; i++)
{
formalDerivativeCoefficients[errorLocatorDegree - i] =
field.multiply(i, errorLocator.getCoefficient(i));
}
ModulusPoly formalDerivative = new ModulusPoly(field, formalDerivativeCoefficients);
// This is directly applying Forney's Formula
int s = errorLocations.Length;
int[] result = new int[s];
for (int i = 0; i < s; i++)
{
int xiInverse = field.inverse(errorLocations[i]);
int numerator = field.subtract(0, errorEvaluator.evaluateAt(xiInverse));
int denominator = field.inverse(formalDerivative.evaluateAt(xiInverse));
result[i] = field.multiply(numerator, denominator);
}
return result;
}
}
}

View File

@@ -0,0 +1,121 @@
/*
* Copyright 2012 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;
namespace ZXing.PDF417.Internal.EC
{
/// <summary>
/// <p>A field based on powers of a generator integer, modulo some modulus.</p>
/// @see com.google.zxing.common.reedsolomon.GenericGF
/// </summary>
/// <author>Sean Owen</author>
internal sealed class ModulusGF
{
public static ModulusGF PDF417_GF = new ModulusGF(PDF417Common.NUMBER_OF_CODEWORDS, 3);
private readonly int[] expTable;
private readonly int[] logTable;
public ModulusPoly Zero { get; private set; }
public ModulusPoly One { get; private set; }
private readonly int modulus;
public ModulusGF(int modulus, int generator)
{
this.modulus = modulus;
expTable = new int[modulus];
logTable = new int[modulus];
int x = 1;
for (int i = 0; i < modulus; i++)
{
expTable[i] = x;
x = (x * generator) % modulus;
}
for (int i = 0; i < modulus - 1; i++)
{
logTable[expTable[i]] = i;
}
// logTable[0] == 0 but this should never be used
Zero = new ModulusPoly(this, new int[] {0});
One = new ModulusPoly(this, new int[] {1});
}
internal ModulusPoly buildMonomial(int degree, int coefficient)
{
if (degree < 0)
{
throw new ArgumentException();
}
if (coefficient == 0)
{
return Zero;
}
int[] coefficients = new int[degree + 1];
coefficients[0] = coefficient;
return new ModulusPoly(this, coefficients);
}
internal int add(int a, int b)
{
return (a + b)%modulus;
}
internal int subtract(int a, int b)
{
return (modulus + a - b)%modulus;
}
internal int exp(int a)
{
return expTable[a];
}
internal int log(int a)
{
if (a == 0)
{
throw new ArgumentException();
}
return logTable[a];
}
internal int inverse(int a)
{
if (a == 0)
{
throw new ArithmeticException();
}
return expTable[modulus - logTable[a] - 1];
}
internal int multiply(int a, int b)
{
if (a == 0 || b == 0)
{
return 0;
}
return expTable[(logTable[a] + logTable[b]) % (modulus - 1)];
}
internal int Size
{
get
{
return modulus;
}
}
}
}

View File

@@ -0,0 +1,366 @@
/*
* Copyright 2012 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.Text;
namespace ZXing.PDF417.Internal.EC
{
/// <summary>
/// <see cref="com.google.zxing.common.reedsolomon.GenericGFPoly"/>
/// </summary>
/// <author>Sean Owen</author>
internal sealed class ModulusPoly
{
private readonly ModulusGF field;
private readonly int[] coefficients;
public ModulusPoly(ModulusGF field, int[] coefficients)
{
if (coefficients.Length == 0)
{
throw new ArgumentException();
}
this.field = field;
int coefficientsLength = coefficients.Length;
if (coefficientsLength > 1 && coefficients[0] == 0)
{
// Leading term must be non-zero for anything except the constant polynomial "0"
int firstNonZero = 1;
while (firstNonZero < coefficientsLength && coefficients[firstNonZero] == 0)
{
firstNonZero++;
}
if (firstNonZero == coefficientsLength)
{
this.coefficients = new int[]{0};
}
else
{
this.coefficients = new int[coefficientsLength - firstNonZero];
Array.Copy(coefficients,
firstNonZero,
this.coefficients,
0,
this.coefficients.Length);
}
}
else
{
this.coefficients = coefficients;
}
}
/// <summary>
/// Gets the coefficients.
/// </summary>
/// <value>The coefficients.</value>
internal int[] Coefficients
{
get { return coefficients; }
}
/// <summary>
/// degree of this polynomial
/// </summary>
internal int Degree
{
get
{
return coefficients.Length - 1;
}
}
/// <summary>
/// Gets a value indicating whether this instance is zero.
/// </summary>
/// <value>true if this polynomial is the monomial "0"
/// </value>
internal bool isZero
{
get { return coefficients[0] == 0; }
}
/// <summary>
/// coefficient of x^degree term in this polynomial
/// </summary>
/// <param name="degree">The degree.</param>
/// <returns>coefficient of x^degree term in this polynomial</returns>
internal int getCoefficient(int degree)
{
return coefficients[coefficients.Length - 1 - degree];
}
/// <summary>
/// evaluation of this polynomial at a given point
/// </summary>
/// <param name="a">A.</param>
/// <returns>evaluation of this polynomial at a given point</returns>
internal int evaluateAt(int a)
{
if (a == 0)
{
// Just return the x^0 coefficient
return getCoefficient(0);
}
int size = coefficients.Length;
int result = 0;
if (a == 1)
{
// Just the sum of the coefficients
foreach (var coefficient in coefficients)
{
result = field.add(result, coefficient);
}
return result;
}
result = coefficients[0];
for (int i = 1; i < size; i++)
{
result = field.add(field.multiply(a, result), coefficients[i]);
}
return result;
}
/// <summary>
/// Adds another Modulus
/// </summary>
/// <param name="other">Other.</param>
internal ModulusPoly add(ModulusPoly other)
{
if (!field.Equals(other.field))
{
throw new ArgumentException("ModulusPolys do not have same ModulusGF field");
}
if (isZero)
{
return other;
}
if (other.isZero)
{
return this;
}
int[] smallerCoefficients = this.coefficients;
int[] largerCoefficients = other.coefficients;
if (smallerCoefficients.Length > largerCoefficients.Length)
{
int[] temp = smallerCoefficients;
smallerCoefficients = largerCoefficients;
largerCoefficients = temp;
}
int[] sumDiff = new int[largerCoefficients.Length];
int lengthDiff = largerCoefficients.Length - smallerCoefficients.Length;
// Copy high-order terms only found in higher-degree polynomial's coefficients
Array.Copy(largerCoefficients, 0, sumDiff, 0, lengthDiff);
for (int i = lengthDiff; i < largerCoefficients.Length; i++)
{
sumDiff[i] = field.add(smallerCoefficients[i - lengthDiff], largerCoefficients[i]);
}
return new ModulusPoly(field, sumDiff);
}
/// <summary>
/// Subtract another Modulus
/// </summary>
/// <param name="other">Other.</param>
internal ModulusPoly subtract(ModulusPoly other)
{
if (!field.Equals(other.field))
{
throw new ArgumentException("ModulusPolys do not have same ModulusGF field");
}
if (other.isZero)
{
return this;
}
return add(other.getNegative());
}
/// <summary>
/// Multiply by another Modulus
/// </summary>
/// <param name="other">Other.</param>
internal ModulusPoly multiply(ModulusPoly other)
{
if (!field.Equals(other.field))
{
throw new ArgumentException("ModulusPolys do not have same ModulusGF field");
}
if (isZero || other.isZero)
{
return field.Zero;
}
int[] aCoefficients = this.coefficients;
int aLength = aCoefficients.Length;
int[] bCoefficients = other.coefficients;
int bLength = bCoefficients.Length;
int[] product = new int[aLength + bLength - 1];
for (int i = 0; i < aLength; i++)
{
int aCoeff = aCoefficients[i];
for (int j = 0; j < bLength; j++)
{
product[i + j] = field.add(product[i + j], field.multiply(aCoeff, bCoefficients[j]));
}
}
return new ModulusPoly(field, product);
}
/// <summary>
/// Returns a Negative version of this instance
/// </summary>
internal ModulusPoly getNegative()
{
int size = coefficients.Length;
int[] negativeCoefficients = new int[size];
for (int i = 0; i < size; i++)
{
negativeCoefficients[i] = field.subtract(0, coefficients[i]);
}
return new ModulusPoly(field, negativeCoefficients);
}
/// <summary>
/// Multiply by a Scalar.
/// </summary>
/// <param name="scalar">Scalar.</param>
internal ModulusPoly multiply(int scalar)
{
if (scalar == 0)
{
return field.Zero;
}
if (scalar == 1)
{
return this;
}
int size = coefficients.Length;
int[] product = new int[size];
for (int i = 0; i < size; i++)
{
product[i] = field.multiply(coefficients[i], scalar);
}
return new ModulusPoly(field, product);
}
/// <summary>
/// Multiplies by a Monomial
/// </summary>
/// <returns>The by monomial.</returns>
/// <param name="degree">Degree.</param>
/// <param name="coefficient">Coefficient.</param>
internal ModulusPoly multiplyByMonomial(int degree, int coefficient)
{
if (degree < 0)
{
throw new ArgumentException();
}
if (coefficient == 0)
{
return field.Zero;
}
int size = coefficients.Length;
int[] product = new int[size + degree];
for (int i = 0; i < size; i++)
{
product[i] = field.multiply(coefficients[i], coefficient);
}
return new ModulusPoly(field, product);
}
/// <summary>
/// Divide by another modulus
/// </summary>
/// <param name="other">Other.</param>
internal ModulusPoly[] divide(ModulusPoly other)
{
if (!field.Equals(other.field))
{
throw new ArgumentException("ModulusPolys do not have same ModulusGF field");
}
if (other.isZero)
{
throw new DivideByZeroException();
}
ModulusPoly quotient = field.Zero;
ModulusPoly remainder = this;
int denominatorLeadingTerm = other.getCoefficient(other.Degree);
int inverseDenominatorLeadingTerm = field.inverse(denominatorLeadingTerm);
while (remainder.Degree >= other.Degree && !remainder.isZero)
{
int degreeDifference = remainder.Degree - other.Degree;
int scale = field.multiply(remainder.getCoefficient(remainder.Degree), inverseDenominatorLeadingTerm);
ModulusPoly term = other.multiplyByMonomial(degreeDifference, scale);
ModulusPoly iterationQuotient = field.buildMonomial(degreeDifference, scale);
quotient = quotient.add(iterationQuotient);
remainder = remainder.subtract(term);
}
return new ModulusPoly[] { quotient, remainder };
}
/// <summary>
/// Returns a <see cref="System.String"/> that represents the current <see cref="ZXing.PDF417.Internal.EC.ModulusPoly"/>.
/// </summary>
/// <returns>A <see cref="System.String"/> that represents the current <see cref="ZXing.PDF417.Internal.EC.ModulusPoly"/>.</returns>
public override String ToString()
{
var result = new StringBuilder(8 * Degree);
for (int degree = Degree; degree >= 0; degree--)
{
int coefficient = getCoefficient(degree);
if (coefficient != 0)
{
if (coefficient < 0)
{
result.Append(" - ");
coefficient = -coefficient;
}
else
{
if (result.Length > 0)
{
result.Append(" + ");
}
}
if (degree == 0 || coefficient != 1)
{
result.Append(coefficient);
}
if (degree != 0)
{
if (degree == 1)
{
result.Append('x');
}
else
{
result.Append("x^");
result.Append(degree);
}
}
}
}
return result.ToString();
}
}
}