mirror of
https://gitee.com/akwkevin/aistudio.-wpf.-diagram
synced 2026-04-27 19:53:24 +08:00
356 lines
12 KiB
C#
356 lines
12 KiB
C#
/*
|
|
* Copyright 2013 ZXing.Net 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
|
|
{
|
|
/// <summary>
|
|
/// Decodes MSI barcodes.
|
|
/// </summary>
|
|
public sealed class MSIReader : OneDReader
|
|
{
|
|
internal static String ALPHABET_STRING = "0123456789";
|
|
private static readonly char[] ALPHABET = ALPHABET_STRING.ToCharArray();
|
|
|
|
/// <summary>
|
|
/// These represent the encodings of characters, as patterns of wide and narrow bars.
|
|
/// The 9 least-significant bits of each int correspond to the pattern of wide and narrow,
|
|
/// with 1s representing "wide" and 0s representing narrow.
|
|
/// </summary>
|
|
internal static int[] CHARACTER_ENCODINGS = {
|
|
0x924, 0x926, 0x934, 0x936, 0x9A4, 0x9A6, 0x9B4, 0x9B6, 0xD24, 0xD26 // 0-9
|
|
};
|
|
|
|
private const int START_ENCODING = 0x06;
|
|
private const int END_ENCODING = 0x09;
|
|
|
|
private readonly bool usingCheckDigit;
|
|
private readonly StringBuilder decodeRowResult;
|
|
private readonly int[] counters;
|
|
private int averageCounterWidth;
|
|
|
|
/// <summary>
|
|
/// Creates a reader that assumes all encoded data is data, and does not treat the final
|
|
/// character as a check digit.
|
|
/// </summary>
|
|
public MSIReader()
|
|
: this(false)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a reader that can be configured to check the last character as a check digit,
|
|
/// </summary>
|
|
/// <param name="usingCheckDigit">if true, treat the last data character as a check digit, not
|
|
/// data, and verify that the checksum passes.</param>
|
|
public MSIReader(bool usingCheckDigit)
|
|
{
|
|
this.usingCheckDigit = usingCheckDigit;
|
|
decodeRowResult = new StringBuilder(20);
|
|
counters = new int[8];
|
|
}
|
|
|
|
/// <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</returns>
|
|
override public Result decodeRow(int rowNumber, BitArray row, IDictionary<DecodeHintType, object> hints)
|
|
{
|
|
for (var index = 0; index < counters.Length; index++)
|
|
counters[index] = 0;
|
|
decodeRowResult.Length = 0;
|
|
|
|
int[] start = findStartPattern(row, counters);
|
|
if (start == null)
|
|
return null;
|
|
|
|
// Read off white space
|
|
int nextStart = row.getNextSet(start[1]);
|
|
|
|
char decodedChar;
|
|
int lastStart = nextStart;
|
|
int pattern;
|
|
do
|
|
{
|
|
if (!recordPattern(row, nextStart, counters, 8))
|
|
{
|
|
// not enough bars for a number but perhaps enough for the end pattern
|
|
var endPattern = findEndPattern(row, nextStart, counters);
|
|
if (endPattern == null)
|
|
return null;
|
|
lastStart = nextStart;
|
|
nextStart = endPattern[1];
|
|
break;
|
|
}
|
|
pattern = toPattern(counters, 8);
|
|
if (!patternToChar(pattern, out decodedChar))
|
|
{
|
|
// pattern doesn't result in an encoded number
|
|
// but it could be the end pattern followed by some black areas
|
|
var endPattern = findEndPattern(row, nextStart, counters);
|
|
if (endPattern == null)
|
|
return null;
|
|
lastStart = nextStart;
|
|
nextStart = endPattern[1];
|
|
break;
|
|
}
|
|
decodeRowResult.Append(decodedChar);
|
|
lastStart = nextStart;
|
|
foreach (int counter in counters)
|
|
{
|
|
nextStart += counter;
|
|
}
|
|
// Read off white space
|
|
nextStart = row.getNextSet(nextStart);
|
|
} while (decodedChar != '*');
|
|
|
|
// at least 3 digits to prevent false positives within other kind
|
|
// of codes like PDF417
|
|
if (decodeRowResult.Length < 3)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var rawBytes = Encoding.UTF8.GetBytes(decodeRowResult.ToString());
|
|
var resultString = decodeRowResult.ToString();
|
|
|
|
if (usingCheckDigit)
|
|
{
|
|
var resultStringWithoutChecksum = resultString.Substring(0, resultString.Length - 1);
|
|
int checkSum = CalculateChecksumLuhn(resultStringWithoutChecksum);
|
|
if ((char)(checkSum + 48) != resultString[resultStringWithoutChecksum.Length])
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
float left = (float)(start[1] + start[0]) / 2.0f;
|
|
float right = (float)(nextStart + lastStart) / 2.0f;
|
|
|
|
var resultPointCallback = hints == null || !hints.ContainsKey(DecodeHintType.NEED_RESULT_POINT_CALLBACK)
|
|
? null
|
|
: (ResultPointCallback)hints[DecodeHintType.NEED_RESULT_POINT_CALLBACK];
|
|
if (resultPointCallback != null)
|
|
{
|
|
resultPointCallback(new ResultPoint(left, rowNumber));
|
|
resultPointCallback(new ResultPoint(right, rowNumber));
|
|
}
|
|
|
|
return new Result(
|
|
resultString,
|
|
rawBytes,
|
|
new[]
|
|
{
|
|
new ResultPoint(left, rowNumber),
|
|
new ResultPoint(right, rowNumber)
|
|
},
|
|
BarcodeFormat.MSI);
|
|
}
|
|
|
|
private int[] findStartPattern(BitArray row, int[] counters)
|
|
{
|
|
const int patternLength = 2;
|
|
|
|
int width = row.Size;
|
|
int rowOffset = row.getNextSet(0);
|
|
|
|
int counterPosition = 0;
|
|
int patternStart = rowOffset;
|
|
bool isWhite = false;
|
|
|
|
counters[0] = 0;
|
|
counters[1] = 0;
|
|
for (int i = rowOffset; i < width; i++)
|
|
{
|
|
if (row[i] ^ isWhite)
|
|
{
|
|
counters[counterPosition]++;
|
|
}
|
|
else
|
|
{
|
|
if (counterPosition == patternLength - 1)
|
|
{
|
|
// narrow and wide areas should be as near as possible to factor 2
|
|
// lets say we will check 1.5 <= factor <= 5
|
|
var factorNarrowToWide = ((float)counters[0]) / ((float)counters[1]);
|
|
if (factorNarrowToWide >= 1.5 && factorNarrowToWide <= 5)
|
|
{
|
|
calculateAverageCounterWidth(counters, patternLength);
|
|
if (toPattern(counters, patternLength) == START_ENCODING)
|
|
{
|
|
// Look for whitespace before start pattern, >= 50% of width of start pattern
|
|
if (row.isRange(Math.Max(0, patternStart - ((i - patternStart) >> 1)), patternStart, false))
|
|
{
|
|
return new int[] {patternStart, i};
|
|
}
|
|
}
|
|
}
|
|
patternStart += counters[0] + counters[1];
|
|
Array.Copy(counters, 2, counters, 0, patternLength - 2);
|
|
counters[patternLength - 2] = 0;
|
|
counters[patternLength - 1] = 0;
|
|
counterPosition--;
|
|
}
|
|
else
|
|
{
|
|
counterPosition++;
|
|
}
|
|
counters[counterPosition] = 1;
|
|
isWhite = !isWhite;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private int[] findEndPattern(BitArray row, int rowOffset, int[] counters)
|
|
{
|
|
const int patternLength = 3;
|
|
|
|
int width = row.Size;
|
|
|
|
int counterPosition = 0;
|
|
int patternStart = rowOffset;
|
|
bool isWhite = false;
|
|
|
|
counters[0] = 0;
|
|
counters[1] = 0;
|
|
counters[2] = 0;
|
|
for (int i = rowOffset; i < width; i++)
|
|
{
|
|
if (row[i] ^ isWhite)
|
|
{
|
|
counters[counterPosition]++;
|
|
}
|
|
else
|
|
{
|
|
if (counterPosition == patternLength - 1)
|
|
{
|
|
var factorNarrowToWide = ((float)counters[1]) / ((float)counters[0]);
|
|
if (factorNarrowToWide >= 1.5 && factorNarrowToWide <= 5)
|
|
{
|
|
if (toPattern(counters, patternLength) == END_ENCODING)
|
|
{
|
|
// Look for whitespace after end pattern, >= 50% of width of end pattern
|
|
var minEndOfWhite = Math.Min(row.Size - 1, i + ((i - patternStart) >> 1));
|
|
if (row.isRange(i, minEndOfWhite, false))
|
|
{
|
|
return new int[] {patternStart, i};
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
counterPosition++;
|
|
counters[counterPosition] = 1;
|
|
isWhite = !isWhite;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private void calculateAverageCounterWidth(int[] counters, int patternLength)
|
|
{
|
|
// look for the minimum and the maximum width of the bars
|
|
// there are only two sizes for MSI barcodes
|
|
// all numbers are encoded as a chain of the pattern 100 and 110
|
|
// the complete pattern of one number always starts with 1 or 11 (black bar(s))
|
|
int minCounter = Int32.MaxValue;
|
|
int maxCounter = 0;
|
|
for (var index = 0; index < patternLength; index++)
|
|
{
|
|
var counter = counters[index];
|
|
if (counter < minCounter)
|
|
{
|
|
minCounter = counter;
|
|
}
|
|
if (counter > maxCounter)
|
|
{
|
|
maxCounter = counter;
|
|
}
|
|
}
|
|
// calculate the average of the minimum and maximum width
|
|
// using some bit shift to get a higher resolution without floating point arithmetic
|
|
averageCounterWidth = ((maxCounter << 8) + (minCounter << 8)) / 2;
|
|
}
|
|
|
|
private int toPattern(int[] counters, int patternLength)
|
|
{
|
|
// calculating the encoded value from the pattern
|
|
int pattern = 0;
|
|
int bit = 1;
|
|
int doubleBit = 3;
|
|
for (var index = 0; index < patternLength; index++)
|
|
{
|
|
var counter = counters[index];
|
|
if ((counter << 8) < averageCounterWidth)
|
|
{
|
|
pattern = (pattern << 1) | bit;
|
|
}
|
|
else
|
|
{
|
|
pattern = (pattern << 2) | doubleBit;
|
|
}
|
|
bit = bit ^ 1;
|
|
doubleBit = doubleBit ^ 3;
|
|
}
|
|
|
|
return pattern;
|
|
}
|
|
|
|
private static bool patternToChar(int pattern, out char c)
|
|
{
|
|
for (int i = 0; i < CHARACTER_ENCODINGS.Length; i++)
|
|
{
|
|
if (CHARACTER_ENCODINGS[i] == pattern)
|
|
{
|
|
c = ALPHABET[i];
|
|
return true;
|
|
}
|
|
}
|
|
c = '*';
|
|
return false;
|
|
}
|
|
|
|
private static readonly int[] doubleAndCrossSum = new [] { 0, 2, 4, 6, 8, 1, 3, 5, 7, 9 };
|
|
|
|
private static int CalculateChecksumLuhn(string number)
|
|
{
|
|
var checksum = 0;
|
|
|
|
for (var index = number.Length - 2; index >= 0; index -= 2)
|
|
{
|
|
var digit = number[index] - 48;
|
|
checksum += digit;
|
|
}
|
|
for (var index = number.Length - 1; index >= 0; index -= 2)
|
|
{
|
|
var digit = doubleAndCrossSum[number[index] - 48];
|
|
checksum += digit;
|
|
}
|
|
|
|
return (10 - (checksum % 10)) % 10;
|
|
}
|
|
}
|
|
} |