mirror of
https://gitee.com/akwkevin/aistudio.-wpf.-diagram
synced 2026-04-28 20:23:25 +08:00
添加项目文件。
This commit is contained in:
40
zxing.core/xx/pdf417/decoder/BarcodeMetadata.cs
Normal file
40
zxing.core/xx/pdf417/decoder/BarcodeMetadata.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
80
zxing.core/xx/pdf417/decoder/BarcodeValue.cs
Normal file
80
zxing.core/xx/pdf417/decoder/BarcodeValue.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
218
zxing.core/xx/pdf417/decoder/BoundingBox.cs
Normal file
218
zxing.core/xx/pdf417/decoder/BoundingBox.cs
Normal 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();
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
97
zxing.core/xx/pdf417/decoder/Codeword.cs
Normal file
97
zxing.core/xx/pdf417/decoder/Codeword.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
808
zxing.core/xx/pdf417/decoder/DecodedBitStreamParser.cs
Normal file
808
zxing.core/xx/pdf417/decoder/DecodedBitStreamParser.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
379
zxing.core/xx/pdf417/decoder/DetectionResult.cs
Normal file
379
zxing.core/xx/pdf417/decoder/DetectionResult.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
161
zxing.core/xx/pdf417/decoder/DetectionResultColumn.cs
Normal file
161
zxing.core/xx/pdf417/decoder/DetectionResultColumn.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
170
zxing.core/xx/pdf417/decoder/PDF417CodewordDecoder.cs
Normal file
170
zxing.core/xx/pdf417/decoder/PDF417CodewordDecoder.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
916
zxing.core/xx/pdf417/decoder/PDF417ScanningDecoder.cs
Normal file
916
zxing.core/xx/pdf417/decoder/PDF417ScanningDecoder.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
241
zxing.core/xx/pdf417/decoder/ec/ErrorCorrection.cs
Normal file
241
zxing.core/xx/pdf417/decoder/ec/ErrorCorrection.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
121
zxing.core/xx/pdf417/decoder/ec/ModulusGF.cs
Normal file
121
zxing.core/xx/pdf417/decoder/ec/ModulusGF.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
366
zxing.core/xx/pdf417/decoder/ec/ModulusPoly.cs
Normal file
366
zxing.core/xx/pdf417/decoder/ec/ModulusPoly.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user