use crate::{
common::{BitMatrix, Result},
point_f, Binarizer, BinaryBitmap, DecodingHintDictionary, Exceptions, Point,
};
use std::borrow::Cow;
use super::PDF417DetectorRXingResult;
const INDEXES_START_PATTERN: [u32; 4] = [0, 4, 1, 5];
const INDEXES_STOP_PATTERN: [u32; 4] = [6, 2, 7, 3];
const MAX_AVG_VARIANCE: f64 = 0.42;
const MAX_INDIVIDUAL_VARIANCE: f64 = 0.8;
const START_PATTERN: [u32; 8] = [8, 1, 1, 1, 1, 1, 1, 3];
const STOP_PATTERN: [u32; 9] = [7, 1, 1, 3, 1, 1, 1, 2, 1];
const MAX_PIXEL_DRIFT: u32 = 3;
const MAX_PATTERN_DRIFT: u32 = 5;
const SKIPPED_ROW_COUNT_MAX: u32 = 25;
const ROW_STEP: u32 = 5;
const BARCODE_MIN_HEIGHT: u32 = 10;
const ROTATIONS: [u32; 4] = [0, 180, 270, 90];
pub fn detect_with_hints<B: Binarizer>(
image: &mut BinaryBitmap<B>,
_hints: &DecodingHintDictionary,
multiple: bool,
) -> Result<PDF417DetectorRXingResult> {
let originalMatrix = image.get_black_matrix();
for rotation in ROTATIONS {
let bitMatrix = applyRotation(originalMatrix, rotation)?;
let barcodeCoordinates = detect(multiple, &bitMatrix).ok_or(Exceptions::NOT_FOUND)?;
if !barcodeCoordinates.is_empty() {
return Ok(PDF417DetectorRXingResult::with_rotation(
bitMatrix.into_owned(),
barcodeCoordinates,
rotation,
));
}
}
Ok(PDF417DetectorRXingResult::with_rotation(
originalMatrix.clone(),
Vec::new(),
0,
))
}
fn applyRotation(matrix: &BitMatrix, rotation: u32) -> Result<Cow<BitMatrix>> {
if rotation % 360 == 0 {
Ok(Cow::Borrowed(matrix))
} else {
let mut newMatrix = matrix.clone();
newMatrix.rotate(rotation)?;
Ok(Cow::Owned(newMatrix))
}
}
pub fn detect(multiple: bool, bitMatrix: &BitMatrix) -> Option<Vec<[Option<Point>; 8]>> {
let mut barcodeCoordinates: Vec<[Option<Point>; 8]> = Vec::new();
let mut row = 0;
let mut column = 0;
let mut foundBarcodeInRow = false;
while row < bitMatrix.getHeight() {
let vertices = findVertices(bitMatrix, row, column)?;
if vertices[0].is_none() && vertices[3].is_none() {
if !foundBarcodeInRow {
break;
}
foundBarcodeInRow = false;
column = 0;
for barcodeCoordinate in &barcodeCoordinates {
if let Some(coord_1) = barcodeCoordinate[1] {
row = row.max(coord_1.y as u32);
}
if let Some(coord_3) = barcodeCoordinate[3] {
row = row.max(coord_3.y as u32);
}
}
row += ROW_STEP;
continue;
}
foundBarcodeInRow = true;
barcodeCoordinates.push(vertices);
if !multiple {
break;
}
if let Some(vert_2) = vertices[2] {
column = vert_2.x as u32;
row = vert_2.y as u32;
} else {
column = vertices[4].as_ref().unwrap().x as u32;
row = vertices[4].as_ref().unwrap().y as u32;
}
}
Some(barcodeCoordinates)
}
fn findVertices(matrix: &BitMatrix, startRow: u32, startColumn: u32) -> Option<[Option<Point>; 8]> {
let height = matrix.getHeight();
let width = matrix.getWidth();
let mut startRow = startRow;
let mut startColumn = startColumn;
let mut result = [None::<Point>; 8]; copyToRXingResult(
&mut result,
&findRowsWithPattern(matrix, height, width, startRow, startColumn, &START_PATTERN)?,
&INDEXES_START_PATTERN,
);
if let Some(result_4) = result[4] {
startColumn = result_4.x as u32;
startRow = result_4.y as u32;
}
copyToRXingResult(
&mut result,
&findRowsWithPattern(matrix, height, width, startRow, startColumn, &STOP_PATTERN)?,
&INDEXES_STOP_PATTERN,
);
Some(result)
}
fn copyToRXingResult(
result: &mut [Option<Point>],
tmpRXingResult: &[Option<Point>],
destinationIndexes: &[u32],
) {
for i in 0..destinationIndexes.len() {
result[destinationIndexes[i] as usize] = tmpRXingResult[i];
}
}
fn findRowsWithPattern(
matrix: &BitMatrix,
height: u32,
width: u32,
startRow: u32,
startColumn: u32,
pattern: &[u32],
) -> Option<[Option<Point>; 4]> {
let mut startRow = startRow;
let mut result = [None; 4];
let mut found = false;
let mut counters = vec![0_u32; pattern.len()];
while startRow < height {
let mut loc_store;
if let Some(loc) =
findGuardPattern(matrix, startColumn, startRow, width, pattern, &mut counters)
{
loc_store = Some(loc);
while startRow > 0 {
startRow -= 1;
if let Some(previousRowLoc) =
findGuardPattern(matrix, startColumn, startRow, width, pattern, &mut counters)
{
loc_store.replace(previousRowLoc);
} else {
startRow += 1;
break;
}
}
result[0] = Some(point_f(loc_store.as_ref()?[0] as f32, startRow as f32));
result[1] = Some(point_f(loc_store.as_ref()?[1] as f32, startRow as f32));
found = true;
break;
}
startRow += ROW_STEP;
}
let mut stopRow = startRow + 1;
if found {
let mut skippedRowCount = 0;
let mut previousRowLoc = [result[0].as_ref()?.x as u32, result[1].as_ref()?.x as u32];
while stopRow < height {
if let Some(loc) = findGuardPattern(
matrix,
previousRowLoc[0],
stopRow,
width,
pattern,
&mut counters,
) {
if (previousRowLoc[0] as i32 - loc[0] as i32).unsigned_abs() < MAX_PATTERN_DRIFT
&& (previousRowLoc[1] as i32 - loc[1] as i32).unsigned_abs() < MAX_PATTERN_DRIFT
{
previousRowLoc = loc;
skippedRowCount = 0;
} else if skippedRowCount > SKIPPED_ROW_COUNT_MAX {
break;
} else {
skippedRowCount += 1;
}
} else if skippedRowCount > SKIPPED_ROW_COUNT_MAX {
break;
} else {
skippedRowCount += 1;
}
stopRow += 1;
}
stopRow -= skippedRowCount + 1;
result[2] = Some(point_f(previousRowLoc[0] as f32, stopRow as f32));
result[3] = Some(point_f(previousRowLoc[1] as f32, stopRow as f32));
}
if stopRow - startRow < BARCODE_MIN_HEIGHT {
result.fill(None);
}
Some(result)
}
fn findGuardPattern(
matrix: &BitMatrix,
column: u32,
row: u32,
width: u32,
pattern: &[u32],
counters: &mut [u32],
) -> Option<[u32; 2]> {
counters.fill(0);
let mut patternStart = column;
let mut pixelDrift = 0;
while matrix.get(patternStart, row) && patternStart > 0 && pixelDrift < MAX_PIXEL_DRIFT {
pixelDrift += 1;
patternStart -= 1;
}
let mut x = patternStart;
let mut counterPosition = 0;
let patternLength = pattern.len();
let mut isWhite = false;
while x < width {
let pixel = matrix.get(x, row);
if pixel != isWhite {
counters[counterPosition] += 1;
} else {
if counterPosition == patternLength - 1 {
if patternMatchVariance(counters, pattern) < MAX_AVG_VARIANCE {
return Some([patternStart, x]);
}
patternStart += counters[0] + counters[1];
counters.copy_within(2..counterPosition - 1 + 2, 0);
counters[counterPosition - 1] = 0;
counters[counterPosition] = 0;
counterPosition -= 1;
} else {
counterPosition += 1;
}
counters[counterPosition] = 1;
isWhite = !isWhite;
}
x += 1;
}
if counterPosition == patternLength - 1
&& patternMatchVariance(counters, pattern) < MAX_AVG_VARIANCE
{
return Some([patternStart, x - 1]);
}
None
}
fn patternMatchVariance(counters: &[u32], pattern: &[u32]) -> f64 {
let numCounters = counters.len();
let total = counters.iter().take(numCounters).sum::<u32>();
let patternLength = pattern.iter().take(numCounters).sum::<u32>();
if total < patternLength {
return f64::INFINITY; }
let unitBarWidth: f64 = total as f64 / patternLength as f64;
let maxIndividualVariance = MAX_INDIVIDUAL_VARIANCE * unitBarWidth;
let mut totalVariance = 0.0;
for x in 0..numCounters {
let counter = counters[x];
let scaledPattern: f64 = pattern[x] as f64 * unitBarWidth;
let variance: f64 = if counter as f64 > scaledPattern {
counter as f64 - scaledPattern
} else {
scaledPattern - counter as f64
};
if variance > maxIndividualVariance {
return f64::INFINITY;
}
totalVariance += variance;
}
totalVariance / total as f64
}