use std::sync::Arc;
use crate::{
Exceptions, Point,
common::{BitMatrix, DecoderRXingResult, Result},
pdf417::pdf_417_common,
};
use super::{
BarcodeMetadata, BarcodeValue, BoundingBox, Codeword, DetectionRXingResult,
DetectionRXingResultColumn, DetectionRXingResultColumnTrait,
DetectionRXingResultRowIndicatorColumn, decoded_bit_stream_parser, ec,
pdf_417_codeword_decoder,
};
const CODEWORD_SKEW_SIZE: u32 = 2;
const MAX_ERRORS: u32 = 3;
const MAX_EC_CODEWORDS: u32 = 512;
pub fn decode(
image: &BitMatrix,
image_top_left: Option<Point>,
imageBottomLeft: Option<Point>,
image_top_right: Option<Point>,
imageBottomRight: Option<Point>,
minCodewordWidth: u32,
maxCodewordWidth: u32,
) -> Result<DecoderRXingResult> {
let mut minCodewordWidth = minCodewordWidth;
let mut maxCodewordWidth = maxCodewordWidth;
let mut boundingBox = BoundingBox::new(
Arc::new(image.clone()),
image_top_left,
imageBottomLeft,
image_top_right,
imageBottomRight,
)?;
let mut leftRowIndicatorColumn = None;
let mut rightRowIndicatorColumn = None;
let mut detectionRXingResult = None;
for firstPass in [true, false] {
if let Some(image_top_left) = image_top_left {
leftRowIndicatorColumn = Some(getRowIndicatorColumn(
image,
&boundingBox,
image_top_left,
true,
minCodewordWidth,
maxCodewordWidth,
));
}
if let Some(image_top_right) = image_top_right {
rightRowIndicatorColumn = Some(getRowIndicatorColumn(
image,
&boundingBox,
image_top_right,
false,
minCodewordWidth,
maxCodewordWidth,
));
}
detectionRXingResult = merge(&mut leftRowIndicatorColumn, &mut rightRowIndicatorColumn)?;
if detectionRXingResult.is_none() {
return Err(Exceptions::NOT_FOUND);
}
let resultBox = detectionRXingResult.as_ref().unwrap().getBoundingBox();
if firstPass
&& (resultBox.getMinY() < boundingBox.getMinY()
|| resultBox.getMaxY() > boundingBox.getMaxY())
{
boundingBox = resultBox.clone();
} else {
break;
}
}
let mut detectionRXingResult = detectionRXingResult.unwrap();
let leftToRight = leftRowIndicatorColumn.is_some();
detectionRXingResult.setBoundingBox(boundingBox.clone());
let maxBarcodeColumn = detectionRXingResult.getBarcodeColumnCount() + 1;
detectionRXingResult.setDetectionRXingResultColumn(0, leftRowIndicatorColumn);
detectionRXingResult.setDetectionRXingResultColumn(maxBarcodeColumn, rightRowIndicatorColumn);
for barcodeColumnCount in 1..=maxBarcodeColumn {
let barcodeColumn = if leftToRight {
barcodeColumnCount
} else {
maxBarcodeColumn - barcodeColumnCount
};
if detectionRXingResult
.getDetectionRXingResultColumn(barcodeColumn)
.is_some()
{
continue;
}
let detectionRXingResultColumn = if barcodeColumn == 0 || barcodeColumn == maxBarcodeColumn
{
DetectionRXingResultColumn::new_with_is_left(&boundingBox, barcodeColumn == 0)
} else {
DetectionRXingResultColumn::new_column(&boundingBox)
};
detectionRXingResult
.setDetectionRXingResultColumn(barcodeColumn, Some(detectionRXingResultColumn));
let mut startColumn: i32 = -1;
let mut previousStartColumn = startColumn;
for imageRow in boundingBox.getMinY()..=boundingBox.getMaxY() {
startColumn =
getStartColumn(&detectionRXingResult, barcodeColumn, imageRow, leftToRight)
.ok_or(Exceptions::ILLEGAL_STATE)? as i32;
if startColumn < 0 || startColumn > boundingBox.getMaxX() as i32 {
if previousStartColumn == -1 {
continue;
}
startColumn = previousStartColumn;
}
let codeword = detectCodeword(
image,
boundingBox.getMinX(),
boundingBox.getMaxX(),
leftToRight,
startColumn as u32,
imageRow,
minCodewordWidth,
maxCodewordWidth,
);
if let Some(codeword) = codeword {
detectionRXingResult
.getDetectionRXingResultColumnMut(barcodeColumn)
.as_mut()
.unwrap()
.setCodeword(imageRow, codeword);
previousStartColumn = startColumn;
minCodewordWidth = minCodewordWidth.min(codeword.getWidth());
maxCodewordWidth = maxCodewordWidth.max(codeword.getWidth());
}
}
}
createDecoderRXingResult(&mut detectionRXingResult)
}
fn merge<'a, T: DetectionRXingResultRowIndicatorColumn>(
leftRowIndicatorColumn: &'a mut Option<T>,
rightRowIndicatorColumn: &'a mut Option<T>,
) -> Result<Option<DetectionRXingResult>> {
if leftRowIndicatorColumn.is_none() && rightRowIndicatorColumn.is_none() {
return Ok(None);
}
let barcodeMetadata = getBarcodeMetadata(leftRowIndicatorColumn, rightRowIndicatorColumn);
if barcodeMetadata.is_none() {
return Ok(None);
}
let boundingBox = BoundingBox::merge(
adjustBoundingBox(leftRowIndicatorColumn)?,
adjustBoundingBox(rightRowIndicatorColumn)?,
)?;
Ok(Some(DetectionRXingResult::new(
barcodeMetadata.unwrap(),
boundingBox,
)))
}
fn adjustBoundingBox<T: DetectionRXingResultRowIndicatorColumn>(
rowIndicatorColumn: &mut Option<T>,
) -> Result<Option<BoundingBox>> {
if rowIndicatorColumn.is_none() {
return Ok(None);
}
let rowIndicatorColumn = rowIndicatorColumn.as_mut().unwrap();
let rowHeights = rowIndicatorColumn.getRowHeights();
if rowHeights.is_none() {
return Ok(None);
}
let rowHeights = rowHeights.unwrap();
let maxRowHeight = getMax(&rowHeights);
let mut missingStartRows = 0;
for rowHeight in &rowHeights {
missingStartRows += maxRowHeight - rowHeight;
if *rowHeight > 0 {
break;
}
}
let codewords = rowIndicatorColumn.getCodewords();
let mut row = 0;
while missingStartRows > 0 && codewords[row].is_none() {
missingStartRows -= 1;
row += 1;
}
let mut missingEndRows = 0;
for row in (0..rowHeights.len()).rev() {
missingEndRows += maxRowHeight - rowHeights[row];
if rowHeights[row] > 0 {
break;
}
}
let mut row = codewords.len() - 1;
while missingEndRows > 0 && codewords[row].is_none() {
missingEndRows -= 1;
row -= 1;
}
Ok(Some(rowIndicatorColumn.getBoundingBox().addMissingRows(
missingStartRows,
missingEndRows,
rowIndicatorColumn.isLeft(),
)?))
}
fn getMax(values: &[u32]) -> u32 {
*values.iter().max().unwrap()
}
fn getBarcodeMetadata<T: DetectionRXingResultRowIndicatorColumn>(
leftRowIndicatorColumn: &mut Option<T>,
rightRowIndicatorColumn: &mut Option<T>,
) -> Option<BarcodeMetadata> {
let left_ri_md = leftRowIndicatorColumn
.as_mut()
.map_or_else(|| None, |col| col.getBarcodeMetadata());
let right_ri_md = rightRowIndicatorColumn
.as_mut()
.map_or_else(|| None, |col| col.getBarcodeMetadata());
if leftRowIndicatorColumn.is_none() && rightRowIndicatorColumn.is_none() {
return None;
} else if leftRowIndicatorColumn.is_none() {
return right_ri_md;
} else if rightRowIndicatorColumn.is_none() && right_ri_md.is_none() {
return left_ri_md;
} else if let Some((leftBarcodeMetadata, rightBarcodeMetadata)) =
left_ri_md.as_ref().zip(right_ri_md.as_ref())
{
if leftBarcodeMetadata.getColumnCount() != rightBarcodeMetadata.getColumnCount()
&& leftBarcodeMetadata.getErrorCorrectionLevel()
!= rightBarcodeMetadata.getErrorCorrectionLevel()
&& leftBarcodeMetadata.getRowCount() != rightBarcodeMetadata.getRowCount()
{
return None;
}
}
left_ri_md
}
fn getRowIndicatorColumn<'a>(
image: &BitMatrix,
boundingBox: &BoundingBox,
startPoint: Point,
leftToRight: bool,
minCodewordWidth: u32,
maxCodewordWidth: u32,
) -> impl DetectionRXingResultRowIndicatorColumn + 'a {
let mut rowIndicatorColumn =
DetectionRXingResultColumn::new_with_is_left(boundingBox, leftToRight);
for i in 0..2 {
let increment: i32 = if i == 0 { 1 } else { -1 };
let mut startColumn: u32 = startPoint.x as u32;
let mut imageRow: i32 = startPoint.y as i32;
while imageRow <= boundingBox.getMaxY() as i32 && imageRow >= boundingBox.getMinY() as i32 {
let codeword = detectCodeword(
image,
0,
image.getWidth(),
leftToRight,
startColumn,
imageRow as u32,
minCodewordWidth,
maxCodewordWidth,
);
if let Some(codeword) = codeword {
rowIndicatorColumn.setCodeword(imageRow as u32, codeword);
if leftToRight {
startColumn = codeword.getStartX();
} else {
startColumn = codeword.getEndX();
}
}
imageRow += increment;
}
}
rowIndicatorColumn
}
fn adjustCodewordCount(
detectionRXingResult: &DetectionRXingResult,
barcodeMatrix: &mut [Vec<BarcodeValue>],
) -> Result<()> {
let barcodeMatrix01 = &mut barcodeMatrix[0][1];
let numberOfCodewords = barcodeMatrix01.getValue();
let calculatedNumberOfCodewords = (detectionRXingResult.getBarcodeColumnCount() as isize
* detectionRXingResult.getBarcodeRowCount() as isize
- getNumberOfECCodeWords(detectionRXingResult.getBarcodeECLevel()) as isize)
as u32;
if numberOfCodewords.is_empty() {
if !(1..=pdf_417_common::MAX_CODEWORDS_IN_BARCODE).contains(&calculatedNumberOfCodewords) {
return Err(Exceptions::NOT_FOUND);
}
barcodeMatrix01.setValue(calculatedNumberOfCodewords);
} else if numberOfCodewords[0] != calculatedNumberOfCodewords
&& (1..=pdf_417_common::MAX_CODEWORDS_IN_BARCODE).contains(&calculatedNumberOfCodewords)
{
barcodeMatrix01.setValue(calculatedNumberOfCodewords);
}
Ok(())
}
fn createDecoderRXingResult(
detectionRXingResult: &mut DetectionRXingResult,
) -> Result<DecoderRXingResult> {
let mut barcodeMatrix = createBarcodeMatrix(detectionRXingResult);
adjustCodewordCount(detectionRXingResult, &mut barcodeMatrix)?;
let mut erasures = Vec::new();
let mut codewords = vec![
0;
detectionRXingResult.getBarcodeRowCount() as usize
* detectionRXingResult.getBarcodeColumnCount()
];
let mut ambiguousIndexValuesList: Vec<Vec<u32>> = Vec::new();
let mut ambiguousIndexesList = Vec::new();
for row in 0..detectionRXingResult.getBarcodeRowCount() {
for column in 0..detectionRXingResult.getBarcodeColumnCount() {
let values = barcodeMatrix[row as usize][column + 1].getValue();
let codewordIndex =
row as usize * detectionRXingResult.getBarcodeColumnCount() + column;
if values.is_empty() {
erasures.push(codewordIndex as u32);
} else if values.len() == 1 {
codewords[codewordIndex] = values[0];
} else {
ambiguousIndexesList.push(codewordIndex as u32);
ambiguousIndexValuesList.push(values);
}
}
}
let ambiguousIndexValues = Vec::from_iter(ambiguousIndexValuesList);
createDecoderRXingResultFromAmbiguousValues(
detectionRXingResult.getBarcodeECLevel(),
&mut codewords,
&mut erasures,
&mut ambiguousIndexesList,
&ambiguousIndexValues,
)
}
fn createDecoderRXingResultFromAmbiguousValues(
ecLevel: u32,
codewords: &mut [u32],
erasureArray: &mut [u32],
ambiguousIndexes: &mut [u32],
ambiguousIndexValues: &[Vec<u32>],
) -> Result<DecoderRXingResult> {
let mut ambiguousIndexCount = vec![0; ambiguousIndexes.len()];
let mut tries = 100;
while tries > 0 {
for i in 0..ambiguousIndexCount.len() {
codewords[ambiguousIndexes[i] as usize] =
ambiguousIndexValues[i][ambiguousIndexCount[i]];
}
let attempted_decode = decodeCodewords(codewords, ecLevel, erasureArray);
if attempted_decode.is_ok() {
return attempted_decode;
}
if ambiguousIndexCount.is_empty() {
return Err(Exceptions::CHECKSUM);
}
for i in 0..ambiguousIndexCount.len() {
if ambiguousIndexCount[i] < ambiguousIndexValues[i].len() - 1 {
ambiguousIndexCount[i] += 1;
break;
} else {
ambiguousIndexCount[i] = 0;
if i == ambiguousIndexCount.len() - 1 {
return Err(Exceptions::CHECKSUM);
}
}
}
tries -= 1;
}
Err(Exceptions::CHECKSUM)
}
fn createBarcodeMatrix(detectionRXingResult: &mut DetectionRXingResult) -> Vec<Vec<BarcodeValue>> {
let mut barcodeMatrix =
vec![
vec![BarcodeValue::new(); detectionRXingResult.getBarcodeColumnCount() + 2];
detectionRXingResult.getBarcodeRowCount() as usize
];
let mut column = 0;
for detectionRXingResultColumn in detectionRXingResult.getDetectionRXingResultColumns() {
if detectionRXingResultColumn.is_some() {
for codeword in detectionRXingResultColumn
.as_ref()
.unwrap()
.getCodewords()
.iter()
.flatten()
{
let rowNumber = codeword.getRowNumber();
if rowNumber >= 0 {
if rowNumber as usize >= barcodeMatrix.len() {
continue;
}
barcodeMatrix[rowNumber as usize][column].setValue(codeword.getValue());
}
}
}
column += 1;
}
barcodeMatrix
}
fn isValidBarcodeColumn(detectionRXingResult: &DetectionRXingResult, barcodeColumn: usize) -> bool {
barcodeColumn <= detectionRXingResult.getBarcodeColumnCount() + 1
}
fn getStartColumn(
detectionRXingResult: &DetectionRXingResult,
barcodeColumn: usize,
imageRow: u32,
leftToRight: bool,
) -> Option<u32> {
let offset: isize = if leftToRight { 1 } else { -1 };
let mut barcodeColumn = barcodeColumn as isize;
let mut codeword = &None;
if isValidBarcodeColumn(detectionRXingResult, (barcodeColumn - offset) as usize) {
codeword = detectionRXingResult
.getDetectionRXingResultColumn((barcodeColumn - offset) as usize)
.as_ref()?
.getCodeword(imageRow);
}
if let Some(codeword) = codeword {
return if leftToRight {
Some(codeword.getEndX())
} else {
Some(codeword.getStartX())
};
}
if detectionRXingResult
.getDetectionRXingResultColumn(barcodeColumn as usize)
.is_some()
{
codeword = detectionRXingResult
.getDetectionRXingResultColumn(barcodeColumn as usize)
.as_ref()?
.getCodewordNearby(imageRow);
}
if let Some(codeword) = codeword {
return if leftToRight {
Some(codeword.getStartX())
} else {
Some(codeword.getEndX())
};
}
if isValidBarcodeColumn(detectionRXingResult, (barcodeColumn - offset) as usize) {
codeword = detectionRXingResult
.getDetectionRXingResultColumn((barcodeColumn - offset) as usize)
.as_ref()?
.getCodewordNearby(imageRow);
}
if let Some(codeword) = codeword {
return if leftToRight {
Some(codeword.getEndX())
} else {
Some(codeword.getStartX())
};
}
let mut skippedColumns = 0;
while isValidBarcodeColumn(detectionRXingResult, (barcodeColumn - offset) as usize) {
barcodeColumn -= offset;
if let Some(previousRowCodeword) = detectionRXingResult
.getDetectionRXingResultColumn(barcodeColumn as usize)
.as_ref()?
.getCodewords()
.iter()
.flatten()
.next()
{
return Some(
((if leftToRight {
previousRowCodeword.getEndX()
} else {
previousRowCodeword.getStartX()
}) as isize
+ offset
* skippedColumns as isize
* (previousRowCodeword.getEndX() - previousRowCodeword.getStartX())
as isize) as u32,
);
}
skippedColumns += 1;
}
if leftToRight {
Some(detectionRXingResult.getBoundingBox().getMinX())
} else {
Some(detectionRXingResult.getBoundingBox().getMaxX())
}
}
#[allow(clippy::too_many_arguments)]
fn detectCodeword(
image: &BitMatrix,
minColumn: u32,
maxColumn: u32,
leftToRight: bool,
startColumn: u32,
imageRow: u32,
minCodewordWidth: u32,
maxCodewordWidth: u32,
) -> Option<Codeword> {
let mut startColumn = adjustCodewordStartColumn(
image,
minColumn,
maxColumn,
leftToRight,
startColumn,
imageRow,
);
let mut moduleBitCount = getModuleBitCount(
image,
minColumn,
maxColumn,
leftToRight,
startColumn,
imageRow,
)?;
let endColumn;
let codewordBitCount = moduleBitCount.iter().sum::<u32>();
if leftToRight {
endColumn = startColumn + codewordBitCount;
} else {
for i in 0..(moduleBitCount.len() / 2) {
let len = moduleBitCount.len();
moduleBitCount.swap(i, len - 1 - i);
}
endColumn = startColumn;
startColumn = endColumn - codewordBitCount;
}
if !checkCodewordSkew(codewordBitCount, minCodewordWidth, maxCodewordWidth) {
return None;
}
let decodedValue = pdf_417_codeword_decoder::getDecodedValue(&moduleBitCount);
let codeword = pdf_417_common::getCodeword(decodedValue);
if codeword == -1 {
return None;
}
Some(Codeword::new(
startColumn,
endColumn,
getCodewordBucketNumber(decodedValue),
codeword as u32,
))
}
fn getModuleBitCount(
image: &BitMatrix,
minColumn: u32,
maxColumn: u32,
leftToRight: bool,
startColumn: u32,
imageRow: u32,
) -> Option<[u32; 8]> {
let mut imageColumn = startColumn as i32;
let mut moduleBitCount = [0_u32; 8];
let mut moduleNumber = 0;
let increment: i32 = if leftToRight { 1 } else { -1 };
let mut previousPixelValue = leftToRight;
while (if leftToRight {
imageColumn < maxColumn as i32
} else {
imageColumn >= minColumn as i32
}) && moduleNumber < moduleBitCount.len()
{
if image.get(imageColumn as u32, imageRow) == previousPixelValue {
moduleBitCount[moduleNumber] += 1;
imageColumn += increment;
} else {
moduleNumber += 1;
previousPixelValue = !previousPixelValue;
}
}
if moduleNumber == moduleBitCount.len()
|| ((imageColumn
== (if leftToRight {
maxColumn as i32
} else {
minColumn as i32
}))
&& moduleNumber == moduleBitCount.len() - 1)
{
return Some(moduleBitCount);
}
None
}
fn getNumberOfECCodeWords(barcodeECLevel: u32) -> u32 {
2 << barcodeECLevel
}
fn adjustCodewordStartColumn(
image: &BitMatrix,
minColumn: u32,
maxColumn: u32,
leftToRight: bool,
codewordStartColumn: u32,
imageRow: u32,
) -> u32 {
let mut correctedStartColumn = codewordStartColumn;
let mut increment: i32 = if leftToRight { -1 } else { 1 };
let mut leftToRight = leftToRight;
for _i in 0..2 {
while (if leftToRight {
correctedStartColumn >= minColumn
} else {
correctedStartColumn < maxColumn
}) && leftToRight == image.get(correctedStartColumn, imageRow)
{
if (codewordStartColumn as i64 - correctedStartColumn as i64).unsigned_abs() as u32
> CODEWORD_SKEW_SIZE
{
return codewordStartColumn;
}
correctedStartColumn = (correctedStartColumn as i32 + increment) as u32;
if image.check_in_bounds(correctedStartColumn, imageRow) {
return 0;
}
}
increment = -increment;
leftToRight = !leftToRight;
}
correctedStartColumn
}
fn checkCodewordSkew(codewordSize: u32, minCodewordWidth: u32, maxCodewordWidth: u32) -> bool {
minCodewordWidth as i64 - CODEWORD_SKEW_SIZE as i64 <= codewordSize as i64
&& codewordSize <= maxCodewordWidth + CODEWORD_SKEW_SIZE
}
fn decodeCodewords(
codewords: &mut [u32],
ecLevel: u32,
erasures: &mut [u32],
) -> Result<DecoderRXingResult> {
if codewords.is_empty() {
return Err(Exceptions::FORMAT);
}
let numECCodewords = 1 << (ecLevel + 1);
let correctedErrorsCount = correctErrors(codewords, erasures, numECCodewords)?;
verifyCodewordCount(codewords, numECCodewords)?;
let mut decoderRXingResult =
decoded_bit_stream_parser::decode(codewords, &ecLevel.to_string())?;
decoderRXingResult.setErrorsCorrected(correctedErrorsCount);
decoderRXingResult.setErasures(erasures.len());
Ok(decoderRXingResult)
}
fn correctErrors(
codewords: &mut [u32],
erasures: &mut [u32],
numECCodewords: u32,
) -> Result<usize> {
if !erasures.is_empty() && erasures.len() as u32 > numECCodewords / 2 + MAX_ERRORS
|| numECCodewords > MAX_EC_CODEWORDS
{
return Err(Exceptions::CHECKSUM);
}
ec::error_correction::decode(codewords, numECCodewords, erasures)
}
fn verifyCodewordCount(codewords: &mut [u32], numECCodewords: u32) -> Result<()> {
if codewords.len() < 4 {
return Err(Exceptions::FORMAT);
}
let numberOfCodewords = codewords[0];
if numberOfCodewords > codewords.len() as u32 {
return Err(Exceptions::FORMAT);
}
if numberOfCodewords == 0 {
if numECCodewords < codewords.len() as u32 {
codewords[0] = codewords.len() as u32 - numECCodewords;
} else {
return Err(Exceptions::FORMAT);
}
}
Ok(())
}
fn getBitCountForCodeword(codeword: u32) -> [u32; 8] {
let mut codeword = codeword;
let mut result = [0; 8];
let mut previousValue = 0;
let mut i = result.len() as isize - 1;
loop {
if (codeword & 0x1) != previousValue {
previousValue = codeword & 0x1;
i -= 1;
if i < 0 {
break;
}
}
result[i as usize] += 1;
codeword >>= 1;
}
result
}
fn getCodewordBucketNumber(codeword: u32) -> u32 {
getCodewordBucketNumberArray(&getBitCountForCodeword(codeword))
}
fn getCodewordBucketNumberArray(moduleBitCount: &[u32]) -> u32 {
(moduleBitCount[0] as i32 - moduleBitCount[2] as i32 + moduleBitCount[4] as i32
- moduleBitCount[6] as i32
+ 9) as u32
% 9
}