Files
aistudio-wpf-diagram/zxing.core/xx/oned/rss/RSS14Reader.cs
2021-07-23 09:42:22 +08:00

589 lines
18 KiB
C#

/*
* 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.Collections.Generic;
using System.Text;
using ZXing.Common;
namespace ZXing.OneD.RSS
{
/// <summary>
/// Decodes RSS-14, including truncated and stacked variants. See ISO/IEC 24724:2006.
/// </summary>
public sealed class RSS14Reader : AbstractRSSReader
{
private static readonly int[] OUTSIDE_EVEN_TOTAL_SUBSET = { 1, 10, 34, 70, 126 };
private static readonly int[] INSIDE_ODD_TOTAL_SUBSET = { 4, 20, 48, 81 };
private static readonly int[] OUTSIDE_GSUM = { 0, 161, 961, 2015, 2715 };
private static readonly int[] INSIDE_GSUM = { 0, 336, 1036, 1516 };
private static readonly int[] OUTSIDE_ODD_WIDEST = { 8, 6, 4, 3, 1 };
private static readonly int[] INSIDE_ODD_WIDEST = { 2, 4, 6, 8 };
private static readonly int[][] FINDER_PATTERNS = {
new[] {3, 8, 2, 1},
new[] {3, 5, 5, 1},
new[] {3, 3, 7, 1},
new[] {3, 1, 9, 1},
new[] {2, 7, 4, 1},
new[] {2, 5, 6, 1},
new[] {2, 3, 8, 1},
new[] {1, 5, 7, 1},
new[] {1, 3, 9, 1},
};
private readonly List<Pair> possibleLeftPairs;
private readonly List<Pair> possibleRightPairs;
/// <summary>
/// Initializes a new instance of the <see cref="RSS14Reader"/> class.
/// </summary>
public RSS14Reader()
{
possibleLeftPairs = new List<Pair>();
possibleRightPairs = new List<Pair>();
}
/// <summary>
/// <p>Attempts to decode a one-dimensional barcode format given a single row of
/// an image.</p>
/// </summary>
/// <param name="rowNumber">row number from top of the row</param>
/// <param name="row">the black/white pixel data of the row</param>
/// <param name="hints">decode hints</param>
/// <returns>
/// <see cref="Result"/>containing encoded string and start/end of barcode or null, if an error occurs or barcode cannot be found
/// </returns>
override public Result decodeRow(int rowNumber,
BitArray row,
IDictionary<DecodeHintType, object> hints)
{
Pair leftPair = decodePair(row, false, rowNumber, hints);
addOrTally(possibleLeftPairs, leftPair);
row.reverse();
Pair rightPair = decodePair(row, true, rowNumber, hints);
addOrTally(possibleRightPairs, rightPair);
row.reverse();
int lefSize = possibleLeftPairs.Count;
for (int i = 0; i < lefSize; i++)
{
Pair left = possibleLeftPairs[i];
if (left.Count > 1)
{
int rightSize = possibleRightPairs.Count;
for (int j = 0; j < rightSize; j++)
{
Pair right = possibleRightPairs[j];
if (right.Count > 1)
{
if (checkChecksum(left, right))
{
return constructResult(left, right);
}
}
}
}
}
return null;
}
private static void addOrTally(IList<Pair> possiblePairs, Pair pair)
{
if (pair == null)
{
return;
}
bool found = false;
foreach (Pair other in possiblePairs)
{
if (other.Value == pair.Value)
{
other.incrementCount();
found = true;
break;
}
}
if (!found)
{
possiblePairs.Add(pair);
}
}
/// <summary>
/// Resets this instance.
/// </summary>
public override void reset()
{
possibleLeftPairs.Clear();
possibleRightPairs.Clear();
}
private static Result constructResult(Pair leftPair, Pair rightPair)
{
long symbolValue = 4537077L * leftPair.Value + rightPair.Value;
String text = symbolValue.ToString();
StringBuilder buffer = new StringBuilder(14);
for (int i = 13 - text.Length; i > 0; i--)
{
buffer.Append('0');
}
buffer.Append(text);
int checkDigit = 0;
for (int i = 0; i < 13; i++)
{
int digit = buffer[i] - '0';
checkDigit += (i & 0x01) == 0 ? 3 * digit : digit;
}
checkDigit = 10 - (checkDigit % 10);
if (checkDigit == 10)
{
checkDigit = 0;
}
buffer.Append(checkDigit);
ResultPoint[] leftPoints = leftPair.FinderPattern.ResultPoints;
ResultPoint[] rightPoints = rightPair.FinderPattern.ResultPoints;
return new Result(
buffer.ToString(),
null,
new ResultPoint[] { leftPoints[0], leftPoints[1], rightPoints[0], rightPoints[1], },
BarcodeFormat.RSS_14);
}
private static bool checkChecksum(Pair leftPair, Pair rightPair)
{
//int leftFPValue = leftPair.FinderPattern.Value;
//int rightFPValue = rightPair.FinderPattern.Value;
//if ((leftFPValue == 0 && rightFPValue == 8) ||
// (leftFPValue == 8 && rightFPValue == 0))
//{
//}
int checkValue = (leftPair.ChecksumPortion + 16 * rightPair.ChecksumPortion) % 79;
int targetCheckValue =
9 * leftPair.FinderPattern.Value + rightPair.FinderPattern.Value;
if (targetCheckValue > 72)
{
targetCheckValue--;
}
if (targetCheckValue > 8)
{
targetCheckValue--;
}
return checkValue == targetCheckValue;
}
private Pair decodePair(BitArray row, bool right, int rowNumber, IDictionary<DecodeHintType, object> hints)
{
int[] startEnd = findFinderPattern(row, 0, right);
if (startEnd == null)
return null;
FinderPattern pattern = parseFoundFinderPattern(row, rowNumber, right, startEnd);
if (pattern == null)
return null;
ResultPointCallback resultPointCallback = hints == null || !hints.ContainsKey(DecodeHintType.NEED_RESULT_POINT_CALLBACK) ? null :
(ResultPointCallback)hints[DecodeHintType.NEED_RESULT_POINT_CALLBACK];
if (resultPointCallback != null)
{
float center = (startEnd[0] + startEnd[1]) / 2.0f;
if (right)
{
// row is actually reversed
center = row.Size - 1 - center;
}
resultPointCallback(new ResultPoint(center, rowNumber));
}
DataCharacter outside = decodeDataCharacter(row, pattern, true);
if (outside == null)
return null;
DataCharacter inside = decodeDataCharacter(row, pattern, false);
if (inside == null)
return null;
return new Pair(1597*outside.Value + inside.Value,
outside.ChecksumPortion + 4*inside.ChecksumPortion,
pattern);
}
private DataCharacter decodeDataCharacter(BitArray row, FinderPattern pattern, bool outsideChar)
{
int[] counters = getDataCharacterCounters();
counters[0] = 0;
counters[1] = 0;
counters[2] = 0;
counters[3] = 0;
counters[4] = 0;
counters[5] = 0;
counters[6] = 0;
counters[7] = 0;
if (outsideChar)
{
recordPatternInReverse(row, pattern.StartEnd[0], counters);
}
else
{
recordPattern(row, pattern.StartEnd[1] + 1, counters);
// reverse it
for (int i = 0, j = counters.Length - 1; i < j; i++, j--)
{
int temp = counters[i];
counters[i] = counters[j];
counters[j] = temp;
}
}
int numModules = outsideChar ? 16 : 15;
float elementWidth = (float)count(counters) / (float)numModules;
int[] oddCounts = this.getOddCounts();
int[] evenCounts = this.getEvenCounts();
float[] oddRoundingErrors = this.getOddRoundingErrors();
float[] evenRoundingErrors = this.getEvenRoundingErrors();
for (int i = 0; i < counters.Length; i++)
{
float value = (float)counters[i] / elementWidth;
int rounded = (int)(value + 0.5f); // Round
if (rounded < 1)
{
rounded = 1;
}
else if (rounded > 8)
{
rounded = 8;
}
int offset = i >> 1;
if ((i & 0x01) == 0)
{
oddCounts[offset] = rounded;
oddRoundingErrors[offset] = value - rounded;
}
else
{
evenCounts[offset] = rounded;
evenRoundingErrors[offset] = value - rounded;
}
}
if (!adjustOddEvenCounts(outsideChar, numModules))
return null;
int oddSum = 0;
int oddChecksumPortion = 0;
for (int i = oddCounts.Length - 1; i >= 0; i--)
{
oddChecksumPortion *= 9;
oddChecksumPortion += oddCounts[i];
oddSum += oddCounts[i];
}
int evenChecksumPortion = 0;
int evenSum = 0;
for (int i = evenCounts.Length - 1; i >= 0; i--)
{
evenChecksumPortion *= 9;
evenChecksumPortion += evenCounts[i];
evenSum += evenCounts[i];
}
int checksumPortion = oddChecksumPortion + 3 * evenChecksumPortion;
if (outsideChar)
{
if ((oddSum & 0x01) != 0 || oddSum > 12 || oddSum < 4)
{
return null;
}
int group = (12 - oddSum) / 2;
int oddWidest = OUTSIDE_ODD_WIDEST[group];
int evenWidest = 9 - oddWidest;
int vOdd = RSSUtils.getRSSvalue(oddCounts, oddWidest, false);
int vEven = RSSUtils.getRSSvalue(evenCounts, evenWidest, true);
int tEven = OUTSIDE_EVEN_TOTAL_SUBSET[group];
int gSum = OUTSIDE_GSUM[group];
return new DataCharacter(vOdd * tEven + vEven + gSum, checksumPortion);
}
else
{
if ((evenSum & 0x01) != 0 || evenSum > 10 || evenSum < 4)
{
return null;
}
int group = (10 - evenSum) / 2;
int oddWidest = INSIDE_ODD_WIDEST[group];
int evenWidest = 9 - oddWidest;
int vOdd = RSSUtils.getRSSvalue(oddCounts, oddWidest, true);
int vEven = RSSUtils.getRSSvalue(evenCounts, evenWidest, false);
int tOdd = INSIDE_ODD_TOTAL_SUBSET[group];
int gSum = INSIDE_GSUM[group];
return new DataCharacter(vEven * tOdd + vOdd + gSum, checksumPortion);
}
}
private int[] findFinderPattern(BitArray row, int rowOffset, bool rightFinderPattern)
{
int[] counters = getDecodeFinderCounters();
counters[0] = 0;
counters[1] = 0;
counters[2] = 0;
counters[3] = 0;
int width = row.Size;
bool isWhite = false;
while (rowOffset < width)
{
isWhite = !row[rowOffset];
if (rightFinderPattern == isWhite)
{
// Will encounter white first when searching for right finder pattern
break;
}
rowOffset++;
}
int counterPosition = 0;
int patternStart = rowOffset;
for (int x = rowOffset; x < width; x++)
{
if (row[x] ^ isWhite)
{
counters[counterPosition]++;
}
else
{
if (counterPosition == 3)
{
if (isFinderPattern(counters))
{
return new int[] { patternStart, x };
}
patternStart += counters[0] + counters[1];
counters[0] = counters[2];
counters[1] = counters[3];
counters[2] = 0;
counters[3] = 0;
counterPosition--;
}
else
{
counterPosition++;
}
counters[counterPosition] = 1;
isWhite = !isWhite;
}
}
return null;
}
private FinderPattern parseFoundFinderPattern(BitArray row, int rowNumber, bool right, int[] startEnd)
{
// Actually we found elements 2-5
bool firstIsBlack = row[startEnd[0]];
int firstElementStart = startEnd[0] - 1;
// Locate element 1
while (firstElementStart >= 0 && firstIsBlack ^ row[firstElementStart])
{
firstElementStart--;
}
firstElementStart++;
int firstCounter = startEnd[0] - firstElementStart;
// Make 'counters' hold 1-4
int[] counters = getDecodeFinderCounters();
Array.Copy(counters, 0, counters, 1, counters.Length - 1);
counters[0] = firstCounter;
int value;
if (!parseFinderValue(counters, FINDER_PATTERNS, out value))
return null;
int start = firstElementStart;
int end = startEnd[1];
if (right)
{
// row is actually reversed
start = row.Size - 1 - start;
end = row.Size - 1 - end;
}
return new FinderPattern(value, new int[] { firstElementStart, startEnd[1] }, start, end, rowNumber);
}
private bool adjustOddEvenCounts(bool outsideChar, int numModules)
{
int oddSum = count(getOddCounts());
int evenSum = count(getEvenCounts());
int mismatch = oddSum + evenSum - numModules;
bool oddParityBad = (oddSum & 0x01) == (outsideChar ? 1 : 0);
bool evenParityBad = (evenSum & 0x01) == 1;
bool incrementOdd = false;
bool decrementOdd = false;
bool incrementEven = false;
bool decrementEven = false;
if (outsideChar)
{
if (oddSum > 12)
{
decrementOdd = true;
}
else if (oddSum < 4)
{
incrementOdd = true;
}
if (evenSum > 12)
{
decrementEven = true;
}
else if (evenSum < 4)
{
incrementEven = true;
}
}
else
{
if (oddSum > 11)
{
decrementOdd = true;
}
else if (oddSum < 5)
{
incrementOdd = true;
}
if (evenSum > 10)
{
decrementEven = true;
}
else if (evenSum < 4)
{
incrementEven = true;
}
}
/*if (mismatch == 2) {
if (!(oddParityBad && evenParityBad)) {
throw ReaderException.Instance;
}
decrementOdd = true;
decrementEven = true;
} else if (mismatch == -2) {
if (!(oddParityBad && evenParityBad)) {
throw ReaderException.Instance;
}
incrementOdd = true;
incrementEven = true;
} else */
if (mismatch == 1)
{
if (oddParityBad)
{
if (evenParityBad)
{
return false;
}
decrementOdd = true;
}
else
{
if (!evenParityBad)
{
return false;
}
decrementEven = true;
}
}
else if (mismatch == -1)
{
if (oddParityBad)
{
if (evenParityBad)
{
return false;
}
incrementOdd = true;
}
else
{
if (!evenParityBad)
{
return false;
}
incrementEven = true;
}
}
else if (mismatch == 0)
{
if (oddParityBad)
{
if (!evenParityBad)
{
return false;
}
// Both bad
if (oddSum < evenSum)
{
incrementOdd = true;
decrementEven = true;
}
else
{
decrementOdd = true;
incrementEven = true;
}
}
else
{
if (evenParityBad)
{
return false;
}
// Nothing to do!
}
}
else
{
return false;
}
if (incrementOdd)
{
if (decrementOdd)
{
return false;
}
increment(getOddCounts(), getOddRoundingErrors());
}
if (decrementOdd)
{
decrement(getOddCounts(), getOddRoundingErrors());
}
if (incrementEven)
{
if (decrementEven)
{
return false;
}
increment(getEvenCounts(), getOddRoundingErrors());
}
if (decrementEven)
{
decrement(getEvenCounts(), getEvenRoundingErrors());
}
return true;
}
}
}