mirror of
https://github.com/starovoid/rxing.git
synced 2026-07-28 05:12:34 +00:00
coda_bar reader passes
This commit is contained in:
427
src/oned/coda_bar_reader.rs
Normal file
427
src/oned/coda_bar_reader.rs
Normal file
@@ -0,0 +1,427 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
use one_d_reader_derive::OneDReader;
|
||||
|
||||
use crate::common::BitArray;
|
||||
use crate::BarcodeFormat;
|
||||
use crate::Exceptions;
|
||||
use crate::RXingResult;
|
||||
|
||||
use super::OneDReader;
|
||||
|
||||
/**
|
||||
* <p>Decodes Codabar barcodes.</p>
|
||||
*
|
||||
* @author Bas Vijfwinkel
|
||||
* @author David Walker
|
||||
*/
|
||||
#[derive(OneDReader)]
|
||||
pub struct CodaBarReader {
|
||||
// Keep some instance variables to avoid reallocations
|
||||
decodeRowRXingResult: String,
|
||||
counters: Vec<u32>,
|
||||
counterLength: usize,
|
||||
}
|
||||
|
||||
impl Default for CodaBarReader {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
decodeRowRXingResult: Default::default(),
|
||||
counters: Default::default(),
|
||||
counterLength: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OneDReader for CodaBarReader {
|
||||
fn decodeRow(
|
||||
&mut self,
|
||||
rowNumber: u32,
|
||||
row: &crate::common::BitArray,
|
||||
hints: &crate::DecodingHintDictionary,
|
||||
) -> Result<crate::RXingResult, crate::Exceptions> {
|
||||
self.counters.fill(0);
|
||||
// Arrays.fill(counters, 0);
|
||||
self.setCounters(row)?;
|
||||
let startOffset = self.findStartPattern()? as usize;
|
||||
let mut nextStart = startOffset;
|
||||
|
||||
self.decodeRowRXingResult.clear();
|
||||
loop {
|
||||
let charOffset = self.toNarrowWidePattern(nextStart);
|
||||
if charOffset == -1 {
|
||||
return Err(Exceptions::NotFoundException("".to_owned()));
|
||||
}
|
||||
// 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.
|
||||
self.decodeRowRXingResult
|
||||
.push(char::from_u32(charOffset as u32).unwrap());
|
||||
nextStart += 8;
|
||||
// Stop as soon as we see the end character.
|
||||
if self.decodeRowRXingResult.chars().count() > 1
|
||||
&& Self::arrayContains(
|
||||
&Self::STARTEND_ENCODING,
|
||||
Self::ALPHABET[charOffset as usize],
|
||||
)
|
||||
{
|
||||
break;
|
||||
}
|
||||
if !(nextStart < self.counterLength) {
|
||||
break;
|
||||
} // no fixed end pattern so keep on reading while data is available
|
||||
} //while (nextStart < counterLength); // no fixed end pattern so keep on reading while data is available
|
||||
|
||||
// Look for whitespace after pattern:
|
||||
let trailingWhitespace = self.counters[nextStart - 1];
|
||||
let mut lastPatternSize = 0;
|
||||
for i in -8isize..-1isize {
|
||||
// for (int i = -8; i < -1; i++) {
|
||||
lastPatternSize += self.counters[(nextStart as isize + i) as usize];
|
||||
}
|
||||
|
||||
// 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 < self.counterLength && trailingWhitespace < lastPatternSize / 2 {
|
||||
return Err(Exceptions::NotFoundException("".to_owned()));
|
||||
}
|
||||
|
||||
self.validatePattern(startOffset)?;
|
||||
|
||||
// Translate character table offsets to actual characters.
|
||||
for i in 0..self.decodeRowRXingResult.chars().count() {
|
||||
// for (int i = 0; i < decodeRowRXingResult.length(); i++) {
|
||||
self.decodeRowRXingResult.replace_range(
|
||||
i..=i,
|
||||
&Self::ALPHABET[self.decodeRowRXingResult.chars().nth(i).unwrap() as usize]
|
||||
.to_string(),
|
||||
);
|
||||
// self.decodeRowRXingResult.setCharAt(i, Self::ALPHABET[self.decodeRowRXingResult.chars().nth(i).unwrap() as usize]);
|
||||
}
|
||||
// Ensure a valid start and end character
|
||||
let startchar = self.decodeRowRXingResult.chars().nth(0).unwrap();
|
||||
if !Self::arrayContains(&Self::STARTEND_ENCODING, startchar) {
|
||||
return Err(Exceptions::NotFoundException("".to_owned()));
|
||||
}
|
||||
let endchar = self
|
||||
.decodeRowRXingResult
|
||||
.chars()
|
||||
.nth(self.decodeRowRXingResult.chars().count() - 1)
|
||||
.unwrap();
|
||||
if !Self::arrayContains(&Self::STARTEND_ENCODING, endchar) {
|
||||
return Err(Exceptions::NotFoundException("".to_owned()));
|
||||
}
|
||||
|
||||
// remove stop/start characters character and check if a long enough string is contained
|
||||
if (self.decodeRowRXingResult.chars().count()) <= Self::MIN_CHARACTER_LENGTH as usize {
|
||||
// Almost surely a false positive ( start + stop + at least 1 character)
|
||||
return Err(Exceptions::NotFoundException("".to_owned()));
|
||||
}
|
||||
|
||||
if !hints.contains_key(&DecodeHintType::RETURN_CODABAR_START_END) {
|
||||
// self.decodeRowRXingResult.deleteCharAt(self.decodeRowRXingResult.chars().count() - 1);
|
||||
// self.decodeRowRXingResult.deleteCharAt(0);
|
||||
self.decodeRowRXingResult =
|
||||
self.decodeRowRXingResult[1..self.decodeRowRXingResult.len()-1].to_owned();
|
||||
}
|
||||
|
||||
let mut runningCount = 0;
|
||||
for i in 0..startOffset {
|
||||
// for (int i = 0; i < startOffset; i++) {
|
||||
runningCount += self.counters[i];
|
||||
}
|
||||
let left: f32 = runningCount as f32;
|
||||
for i in startOffset..(nextStart - 1) {
|
||||
// for (int i = startOffset; i < nextStart - 1; i++) {
|
||||
runningCount += self.counters[i];
|
||||
}
|
||||
let right: f32 = runningCount as f32;
|
||||
|
||||
let mut result = RXingResult::new(
|
||||
&self.decodeRowRXingResult,
|
||||
Vec::new(),
|
||||
vec![
|
||||
RXingResultPoint::new(left, rowNumber as f32),
|
||||
RXingResultPoint::new(right, rowNumber as f32),
|
||||
],
|
||||
BarcodeFormat::CODABAR,
|
||||
);
|
||||
|
||||
result.putMetadata(
|
||||
RXingResultMetadataType::SYMBOLOGY_IDENTIFIER,
|
||||
RXingResultMetadataValue::SymbologyIdentifier("]F0".to_owned()),
|
||||
);
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
impl CodaBarReader {
|
||||
// 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.
|
||||
const MAX_ACCEPTABLE: f32 = 2.0;
|
||||
const PADDING: f32 = 1.5;
|
||||
|
||||
// const ALPHABET_STRING : &str= "0123456789-$:/.+ABCD";
|
||||
const ALPHABET: [char; 20] = [
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '$', ':', '/', '.', '+', 'A', 'B',
|
||||
'C', 'D',
|
||||
];
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
const CHARACTER_ENCODINGS: [u32; 20] = [
|
||||
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.
|
||||
const MIN_CHARACTER_LENGTH: u32 = 3;
|
||||
|
||||
// official start and end patterns
|
||||
const STARTEND_ENCODING: [char; 4] = ['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
|
||||
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
decodeRowRXingResult: String::with_capacity(20),
|
||||
counters: vec![0; 80], //Vec::with_capacity(80),
|
||||
counterLength: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn validatePattern(&self, start: usize) -> Result<(), Exceptions> {
|
||||
// First, sum up the total size of our four categories of stripe sizes;
|
||||
let mut sizes = [0, 0, 0, 0];
|
||||
let mut counts = [0, 0, 0, 0];
|
||||
let end = self.decodeRowRXingResult.chars().count() - 1;
|
||||
|
||||
// We break out of this loop in the middle, in order to handle
|
||||
// inter-character spaces properly.
|
||||
let mut pos = start;
|
||||
for i in 0..=end {
|
||||
// for (int i = 0; i <= end; i++) {
|
||||
let mut pattern = Self::CHARACTER_ENCODINGS
|
||||
[self.decodeRowRXingResult.chars().nth(i).unwrap() as usize];
|
||||
for j in (0_usize..=6).rev() {
|
||||
// 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.
|
||||
let category = (j & 1) + ((pattern as usize) & 1) * 2;
|
||||
sizes[category] += self.counters[(pos + j) as usize];
|
||||
counts[category] += 1;
|
||||
pattern >>= 1;
|
||||
}
|
||||
// We ignore the inter-character space - it could be of any size.
|
||||
pos += 8;
|
||||
}
|
||||
|
||||
// Calculate our allowable size thresholds using fixed-point math.
|
||||
let mut maxes = [0.0; 4]; //new float[4];
|
||||
let mut mins = [0.0; 4]; //new float[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 i in 0..2 {
|
||||
// for (int i = 0; i < 2; i++) {
|
||||
mins[i] = 0.0; // Accept arbitrarily small "short" stripes.
|
||||
mins[i + 2] = ((sizes[i] as f32) / (counts[i] as f32)
|
||||
+ (sizes[i + 2] as f32) / (counts[i + 2] as f32))
|
||||
/ 2.0;
|
||||
maxes[i] = mins[i + 2];
|
||||
maxes[i + 2] = ((sizes[i + 2] as f32) * Self::MAX_ACCEPTABLE + Self::PADDING)
|
||||
/ (counts[i + 2] as f32);
|
||||
}
|
||||
|
||||
// Now verify that all of the stripes are within the thresholds.
|
||||
pos = start;
|
||||
for i in 0..=end {
|
||||
// for (int i = 0; i <= end; i++) {
|
||||
let mut pattern = Self::CHARACTER_ENCODINGS
|
||||
[self.decodeRowRXingResult.chars().nth(i).unwrap() as usize];
|
||||
for j in (0usize..=6).rev() {
|
||||
// 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.
|
||||
let category = (j & 1) + ((pattern as usize) & 1) * 2;
|
||||
let size = self.counters[(pos + j)];
|
||||
if (size as f32) < mins[category] || (size as f32) > maxes[category] {
|
||||
return Err(Exceptions::NotFoundException("".to_owned()));
|
||||
}
|
||||
pattern >>= 1;
|
||||
}
|
||||
pos += 8;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @param row row to count from
|
||||
*/
|
||||
fn setCounters(&mut self, row: &BitArray) -> Result<(), Exceptions> {
|
||||
self.counterLength = 0;
|
||||
// Start from the first white bit.
|
||||
let mut i = row.getNextUnset(0);
|
||||
let end = row.getSize();
|
||||
if i >= end {
|
||||
return Err(Exceptions::NotFoundException("".to_owned()));
|
||||
}
|
||||
let mut isWhite = true;
|
||||
let mut count = 0;
|
||||
while i < end {
|
||||
if row.get(i) != isWhite {
|
||||
count += 1;
|
||||
} else {
|
||||
self.counterAppend(count);
|
||||
count = 1;
|
||||
isWhite = !isWhite;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
self.counterAppend(count);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn counterAppend(&mut self, e: u32) {
|
||||
self.counters[self.counterLength] = e;
|
||||
self.counterLength += 1;
|
||||
if self.counterLength >= self.counters.len() {
|
||||
let mut temp = vec![0; self.counterLength * 2]; //new int[counterLength * 2];
|
||||
temp[0..self.counterLength].clone_from_slice(&self.counters[..]);
|
||||
// System.arraycopy(counters, 0, temp, 0, counterLength);
|
||||
self.counters = temp;
|
||||
}
|
||||
}
|
||||
|
||||
fn findStartPattern(&mut self) -> Result<u32, Exceptions> {
|
||||
let mut i = 1;
|
||||
while i < self.counterLength {
|
||||
// for (int i = 1; i < counterLength; i += 2) {
|
||||
let charOffset = self.toNarrowWidePattern(i);
|
||||
if charOffset != -1
|
||||
&& Self::arrayContains(
|
||||
&Self::STARTEND_ENCODING,
|
||||
Self::ALPHABET[charOffset as usize],
|
||||
)
|
||||
{
|
||||
// Look for whitespace before start pattern, >= 50% of width of start pattern
|
||||
// We make an exception if the whitespace is the first element.
|
||||
let mut patternSize = 0;
|
||||
for j in i..(i + 7) {
|
||||
// for (int j = i; j < i + 7; j++) {
|
||||
patternSize += self.counters[j];
|
||||
}
|
||||
if i == 1 || self.counters[i - 1] >= patternSize / 2 {
|
||||
return Ok(i as u32);
|
||||
}
|
||||
}
|
||||
|
||||
i += 2;
|
||||
}
|
||||
Err(Exceptions::NotFoundException("".to_owned()))
|
||||
}
|
||||
|
||||
pub fn arrayContains(array: &[char], key: char) -> bool {
|
||||
// if (array != null) {
|
||||
for c in array {
|
||||
if c == &key {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// }
|
||||
return false;
|
||||
}
|
||||
|
||||
// Assumes that counters[position] is a bar.
|
||||
fn toNarrowWidePattern(&mut self, position: usize) -> i32 {
|
||||
let end = position + 7;
|
||||
if end >= self.counterLength {
|
||||
return -1;
|
||||
}
|
||||
|
||||
let theCounters = &self.counters;
|
||||
|
||||
let mut maxBar = 0;
|
||||
let mut minBar = u32::MAX;
|
||||
let mut j = position;
|
||||
while j < end {
|
||||
// for (int j = position; j < end; j += 2) {
|
||||
let currentCounter = theCounters[j];
|
||||
if currentCounter < minBar {
|
||||
minBar = currentCounter;
|
||||
}
|
||||
if currentCounter > maxBar {
|
||||
maxBar = currentCounter;
|
||||
}
|
||||
|
||||
j += 2;
|
||||
}
|
||||
let thresholdBar = (minBar + maxBar) / 2;
|
||||
|
||||
let mut maxSpace = 0;
|
||||
let mut minSpace = u32::MAX;
|
||||
let mut j = position + 1;
|
||||
while j < end {
|
||||
// for (int j = position + 1; j < end; j += 2) {
|
||||
let currentCounter = theCounters[j];
|
||||
if currentCounter < minSpace {
|
||||
minSpace = currentCounter;
|
||||
}
|
||||
if currentCounter > maxSpace {
|
||||
maxSpace = currentCounter;
|
||||
}
|
||||
|
||||
j += 2;
|
||||
}
|
||||
let thresholdSpace = (minSpace + maxSpace) / 2;
|
||||
|
||||
let mut bitmask = 1 << 7;
|
||||
let mut pattern = 0;
|
||||
for i in 0..7 {
|
||||
// for (int i = 0; i < 7; i++) {
|
||||
let threshold = if (i & 1) == 0 {
|
||||
thresholdBar
|
||||
} else {
|
||||
thresholdSpace
|
||||
};
|
||||
bitmask >>= 1;
|
||||
if theCounters[position + i] > threshold {
|
||||
pattern |= bitmask;
|
||||
}
|
||||
}
|
||||
|
||||
for i in 0..Self::CHARACTER_ENCODINGS.len() {
|
||||
// for (int i = 0; i < CHARACTER_ENCODINGS.length; i++) {
|
||||
if Self::CHARACTER_ENCODINGS[i] == pattern {
|
||||
return i as i32;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user