mirror of
https://gitee.com/akwkevin/aistudio.-wpf.-diagram
synced 2026-04-08 10:16:36 +08:00
423 lines
16 KiB
C#
423 lines
16 KiB
C#
/*
|
|
* Copyright 2008 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
|
|
{
|
|
/// <summary>
|
|
/// <p>Decodes Codabar barcodes.</p>
|
|
///
|
|
/// <author>Bas Vijfwinkel</author>
|
|
/// </summary>
|
|
public sealed class CodaBarReader : OneDReader
|
|
{
|
|
// These values are critical for determining how permissive the decoding
|
|
// will be. All stripe sizes must be within the window these define, as
|
|
// compared to the average stripe size.
|
|
private static readonly int MAX_ACCEPTABLE = (int)(PATTERN_MATCH_RESULT_SCALE_FACTOR * 2.0f);
|
|
private static readonly int PADDING = (int)(PATTERN_MATCH_RESULT_SCALE_FACTOR * 1.5f);
|
|
|
|
private const String ALPHABET_STRING = "0123456789-$:/.+ABCD";
|
|
internal static readonly char[] ALPHABET = ALPHABET_STRING.ToCharArray();
|
|
|
|
/**
|
|
* These represent the encodings of characters, as patterns of wide and narrow bars. The 7 least-significant bits of
|
|
* each int correspond to the pattern of wide and narrow, with 1s representing "wide" and 0s representing narrow.
|
|
*/
|
|
|
|
internal static int[] CHARACTER_ENCODINGS = {
|
|
0x003, 0x006, 0x009, 0x060, 0x012, 0x042, 0x021, 0x024, 0x030, 0x048, // 0-9
|
|
0x00c, 0x018, 0x045, 0x051, 0x054, 0x015, 0x01A, 0x029, 0x00B, 0x00E, // -$:/.+ABCD
|
|
};
|
|
|
|
// minimal number of characters that should be present (including start and stop characters)
|
|
// under normal circumstances this should be set to 3, but can be set higher
|
|
// as a last-ditch attempt to reduce false positives.
|
|
private const int MIN_CHARACTER_LENGTH = 3;
|
|
|
|
// official start and end patterns
|
|
private static readonly char[] STARTEND_ENCODING = { 'A', 'B', 'C', 'D' };
|
|
// some Codabar generator allow the Codabar string to be closed by every
|
|
// character. This will cause lots of false positives!
|
|
|
|
// some industries use a checksum standard but this is not part of the original Codabar standard
|
|
// for more information see : http://www.mecsw.com/specs/codabar.html
|
|
|
|
// Keep some instance variables to avoid reallocations
|
|
private readonly StringBuilder decodeRowResult;
|
|
private int[] counters;
|
|
private int counterLength;
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
public CodaBarReader()
|
|
{
|
|
decodeRowResult = new StringBuilder(20);
|
|
counters = new int[80];
|
|
counterLength = 0;
|
|
}
|
|
/// <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>
|
|
public override Result decodeRow(int rowNumber, BitArray row, IDictionary<DecodeHintType, object> hints)
|
|
{
|
|
for (var index = 0; index < counters.Length; index++)
|
|
counters[index] = 0;
|
|
|
|
if (!setCounters(row))
|
|
return null;
|
|
|
|
int startOffset = findStartPattern();
|
|
if (startOffset < 0)
|
|
return null;
|
|
|
|
int nextStart = startOffset;
|
|
|
|
decodeRowResult.Length = 0;
|
|
do
|
|
{
|
|
int charOffset = toNarrowWidePattern(nextStart);
|
|
if (charOffset == -1)
|
|
{
|
|
return null;
|
|
}
|
|
// Hack: We store the position in the alphabet table into a
|
|
// StringBuilder, so that we can access the decoded patterns in
|
|
// validatePattern. We'll translate to the actual characters later.
|
|
decodeRowResult.Append((char)charOffset);
|
|
nextStart += 8;
|
|
// Stop as soon as we see the end character.
|
|
if (decodeRowResult.Length > 1 &&
|
|
arrayContains(STARTEND_ENCODING, ALPHABET[charOffset]))
|
|
{
|
|
break;
|
|
}
|
|
} while (nextStart < counterLength); // no fixed end pattern so keep on reading while data is available
|
|
|
|
// Look for whitespace after pattern:
|
|
int trailingWhitespace = counters[nextStart - 1];
|
|
int lastPatternSize = 0;
|
|
for (int i = -8; i < -1; i++)
|
|
{
|
|
lastPatternSize += counters[nextStart + i];
|
|
}
|
|
|
|
// We need to see whitespace equal to 50% of the last pattern size,
|
|
// otherwise this is probably a false positive. The exception is if we are
|
|
// at the end of the row. (I.e. the barcode barely fits.)
|
|
if (nextStart < counterLength && trailingWhitespace < lastPatternSize / 2)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
if (!validatePattern(startOffset))
|
|
return null;
|
|
|
|
// Translate character table offsets to actual characters.
|
|
for (int i = 0; i < decodeRowResult.Length; i++)
|
|
{
|
|
decodeRowResult[i] = ALPHABET[decodeRowResult[i]];
|
|
}
|
|
// Ensure a valid start and end character
|
|
char startchar = decodeRowResult[0];
|
|
if (!arrayContains(STARTEND_ENCODING, startchar))
|
|
{
|
|
return null;
|
|
}
|
|
char endchar = decodeRowResult[decodeRowResult.Length - 1];
|
|
if (!arrayContains(STARTEND_ENCODING, endchar))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
// remove stop/start characters character and check if a long enough string is contained
|
|
if (decodeRowResult.Length <= MIN_CHARACTER_LENGTH)
|
|
{
|
|
// Almost surely a false positive ( start + stop + at least 1 character)
|
|
return null;
|
|
}
|
|
|
|
if (!SupportClass.GetValue(hints, DecodeHintType.RETURN_CODABAR_START_END, false))
|
|
{
|
|
decodeRowResult.Remove(decodeRowResult.Length - 1, 1);
|
|
decodeRowResult.Remove(0, 1);
|
|
}
|
|
|
|
int runningCount = 0;
|
|
for (int i = 0; i < startOffset; i++)
|
|
{
|
|
runningCount += counters[i];
|
|
}
|
|
float left = runningCount;
|
|
for (int i = startOffset; i < nextStart - 1; i++)
|
|
{
|
|
runningCount += counters[i];
|
|
}
|
|
float right = runningCount;
|
|
|
|
var resultPointCallback = SupportClass.GetValue(hints, DecodeHintType.NEED_RESULT_POINT_CALLBACK, (ResultPointCallback)null);
|
|
if (resultPointCallback != null)
|
|
{
|
|
resultPointCallback(new ResultPoint(left, rowNumber));
|
|
resultPointCallback(new ResultPoint(right, rowNumber));
|
|
}
|
|
|
|
return new Result(
|
|
decodeRowResult.ToString(),
|
|
null,
|
|
new[]
|
|
{
|
|
new ResultPoint(left, rowNumber),
|
|
new ResultPoint(right, rowNumber)
|
|
},
|
|
BarcodeFormat.CODABAR);
|
|
}
|
|
|
|
private bool validatePattern(int start)
|
|
{
|
|
// First, sum up the total size of our four categories of stripe sizes;
|
|
int[] sizes = { 0, 0, 0, 0 };
|
|
int[] counts = { 0, 0, 0, 0 };
|
|
int end = decodeRowResult.Length - 1;
|
|
|
|
// We break out of this loop in the middle, in order to handle
|
|
// inter-character spaces properly.
|
|
int pos = start;
|
|
for (int i = 0; true; i++)
|
|
{
|
|
int pattern = CHARACTER_ENCODINGS[decodeRowResult[i]];
|
|
for (int j = 6; j >= 0; j--)
|
|
{
|
|
// Even j = bars, while odd j = spaces. Categories 2 and 3 are for
|
|
// long stripes, while 0 and 1 are for short stripes.
|
|
int category = (j & 1) + (pattern & 1) * 2;
|
|
sizes[category] += counters[pos + j];
|
|
counts[category]++;
|
|
pattern >>= 1;
|
|
}
|
|
if (i >= end)
|
|
{
|
|
break;
|
|
}
|
|
// We ignore the inter-character space - it could be of any size.
|
|
pos += 8;
|
|
}
|
|
|
|
// Calculate our allowable size thresholds using fixed-point math.
|
|
int[] maxes = new int[4];
|
|
int[] mins = new int[4];
|
|
// Define the threshold of acceptability to be the midpoint between the
|
|
// average small stripe and the average large stripe. No stripe lengths
|
|
// should be on the "wrong" side of that line.
|
|
for (int i = 0; i < 2; i++)
|
|
{
|
|
mins[i] = 0; // Accept arbitrarily small "short" stripes.
|
|
mins[i + 2] = ((sizes[i] << INTEGER_MATH_SHIFT) / counts[i] +
|
|
(sizes[i + 2] << INTEGER_MATH_SHIFT) / counts[i + 2]) >> 1;
|
|
maxes[i] = mins[i + 2];
|
|
maxes[i + 2] = (sizes[i + 2] * MAX_ACCEPTABLE + PADDING) / counts[i + 2];
|
|
}
|
|
|
|
// Now verify that all of the stripes are within the thresholds.
|
|
pos = start;
|
|
for (int i = 0; true; i++)
|
|
{
|
|
int pattern = CHARACTER_ENCODINGS[decodeRowResult[i]];
|
|
for (int j = 6; j >= 0; j--)
|
|
{
|
|
// Even j = bars, while odd j = spaces. Categories 2 and 3 are for
|
|
// long stripes, while 0 and 1 are for short stripes.
|
|
int category = (j & 1) + (pattern & 1) * 2;
|
|
int size = counters[pos + j] << INTEGER_MATH_SHIFT;
|
|
if (size < mins[category] || size > maxes[category])
|
|
{
|
|
return false;
|
|
}
|
|
pattern >>= 1;
|
|
}
|
|
if (i >= end)
|
|
{
|
|
break;
|
|
}
|
|
pos += 8;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Records the size of all runs of white and black pixels, starting with white.
|
|
/// This is just like recordPattern, except it records all the counters, and
|
|
/// uses our builtin "counters" member for storage.
|
|
/// </summary>
|
|
/// <param name="row">row to count from</param>
|
|
private bool setCounters(BitArray row)
|
|
{
|
|
counterLength = 0;
|
|
// Start from the first white bit.
|
|
int i = row.getNextUnset(0);
|
|
int end = row.Size;
|
|
if (i >= end)
|
|
{
|
|
return false;
|
|
}
|
|
bool isWhite = true;
|
|
int count = 0;
|
|
while (i < end)
|
|
{
|
|
if (row[i] != isWhite)
|
|
{
|
|
count++;
|
|
}
|
|
else
|
|
{
|
|
counterAppend(count);
|
|
count = 1;
|
|
isWhite = !isWhite;
|
|
}
|
|
i++;
|
|
}
|
|
counterAppend(count);
|
|
return true;
|
|
}
|
|
|
|
private void counterAppend(int e)
|
|
{
|
|
counters[counterLength] = e;
|
|
counterLength++;
|
|
if (counterLength >= counters.Length)
|
|
{
|
|
int[] temp = new int[counterLength * 2];
|
|
Array.Copy(counters, 0, temp, 0, counterLength);
|
|
counters = temp;
|
|
}
|
|
}
|
|
|
|
private int findStartPattern()
|
|
{
|
|
for (int i = 1; i < counterLength; i += 2)
|
|
{
|
|
int charOffset = toNarrowWidePattern(i);
|
|
if (charOffset != -1 && arrayContains(STARTEND_ENCODING, ALPHABET[charOffset]))
|
|
{
|
|
// Look for whitespace before start pattern, >= 50% of width of start pattern
|
|
// We make an exception if the whitespace is the first element.
|
|
int patternSize = 0;
|
|
for (int j = i; j < i + 7; j++)
|
|
{
|
|
patternSize += counters[j];
|
|
}
|
|
if (i == 1 || counters[i - 1] >= patternSize / 2)
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
internal static bool arrayContains(char[] array, char key)
|
|
{
|
|
if (array != null)
|
|
{
|
|
foreach (char c in array)
|
|
{
|
|
if (c == key)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Assumes that counters[position] is a bar.
|
|
private int toNarrowWidePattern(int position)
|
|
{
|
|
int end = position + 7;
|
|
if (end >= counterLength)
|
|
{
|
|
return -1;
|
|
}
|
|
int[] theCounters = counters;
|
|
|
|
int maxBar = 0;
|
|
int minBar = Int32.MaxValue;
|
|
for (int j = position; j < end; j += 2)
|
|
{
|
|
int currentCounter = theCounters[j];
|
|
if (currentCounter < minBar)
|
|
{
|
|
minBar = currentCounter;
|
|
}
|
|
if (currentCounter > maxBar)
|
|
{
|
|
maxBar = currentCounter;
|
|
}
|
|
}
|
|
int thresholdBar = (minBar + maxBar) / 2;
|
|
|
|
int maxSpace = 0;
|
|
int minSpace = Int32.MaxValue;
|
|
for (int j = position + 1; j < end; j += 2)
|
|
{
|
|
int currentCounter = theCounters[j];
|
|
if (currentCounter < minSpace)
|
|
{
|
|
minSpace = currentCounter;
|
|
}
|
|
if (currentCounter > maxSpace)
|
|
{
|
|
maxSpace = currentCounter;
|
|
}
|
|
}
|
|
int thresholdSpace = (minSpace + maxSpace) / 2;
|
|
|
|
int bitmask = 1 << 7;
|
|
int pattern = 0;
|
|
for (int i = 0; i < 7; i++)
|
|
{
|
|
int threshold = (i & 1) == 0 ? thresholdBar : thresholdSpace;
|
|
bitmask >>= 1;
|
|
if (theCounters[position + i] > threshold)
|
|
{
|
|
pattern |= bitmask;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < CHARACTER_ENCODINGS.Length; i++)
|
|
{
|
|
if (CHARACTER_ENCODINGS[i] == pattern)
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
}
|
|
} |