mirror of
https://github.com/starovoid/rxing.git
synced 2026-07-26 04:12:34 +00:00
954 lines
34 KiB
Rust
954 lines
34 KiB
Rust
/*
|
|
* Copyright 2013 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 std::rc::Rc;
|
|
|
|
use crate::{
|
|
common::{BitMatrix, DecoderRXingResult},
|
|
pdf417::pdf_417_common,
|
|
Exceptions, RXingResultPoint, ResultPoint,
|
|
};
|
|
|
|
use super::{
|
|
decoded_bit_stream_parser, ec, pdf_417_codeword_decoder, BarcodeMetadata, BarcodeValue,
|
|
BoundingBox, Codeword, DetectionRXingResult, DetectionRXingResultColumn,
|
|
DetectionRXingResultColumnTrait, DetectionRXingResultRowIndicatorColumn,
|
|
};
|
|
|
|
/**
|
|
* @author Guenther Grau
|
|
*/
|
|
|
|
const CODEWORD_SKEW_SIZE: u32 = 2;
|
|
|
|
const MAX_ERRORS: u32 = 3;
|
|
const MAX_EC_CODEWORDS: u32 = 512;
|
|
// const errorCorrection:ErrorCorrection = ErrorCorrection::new();
|
|
|
|
// TODO don't pass in minCodewordWidth and maxCodewordWidth, pass in barcode columns for start and stop pattern
|
|
// columns. That way width can be deducted from the pattern column.
|
|
// This approach also allows to detect more details about the barcode, e.g. if a bar type (white or black) is wider
|
|
// than it should be. This can happen if the scanner used a bad blackpoint.
|
|
pub fn decode(
|
|
image: &BitMatrix,
|
|
imageTopLeft: Option<RXingResultPoint>,
|
|
imageBottomLeft: Option<RXingResultPoint>,
|
|
imageTopRight: Option<RXingResultPoint>,
|
|
imageBottomRight: Option<RXingResultPoint>,
|
|
minCodewordWidth: u32,
|
|
maxCodewordWidth: u32,
|
|
) -> Result<DecoderRXingResult, Exceptions> {
|
|
let mut minCodewordWidth = minCodewordWidth;
|
|
let mut maxCodewordWidth = maxCodewordWidth;
|
|
let mut boundingBox = Rc::new(BoundingBox::new(
|
|
Rc::new(image.clone()),
|
|
imageTopLeft,
|
|
imageBottomLeft,
|
|
imageTopRight,
|
|
imageBottomRight,
|
|
)?);
|
|
let mut leftRowIndicatorColumn = None;
|
|
let mut rightRowIndicatorColumn = None;
|
|
let mut detectionRXingResult = None;
|
|
for firstPass in [true, false] {
|
|
// for (boolean firstPass = true; ; firstPass = false) {
|
|
if imageTopLeft.is_some() {
|
|
leftRowIndicatorColumn = Some(getRowIndicatorColumn(
|
|
image,
|
|
boundingBox.clone(),
|
|
imageTopLeft.as_ref().unwrap(),
|
|
true,
|
|
minCodewordWidth,
|
|
maxCodewordWidth,
|
|
));
|
|
}
|
|
if imageTopRight.is_some() {
|
|
rightRowIndicatorColumn = Some(getRowIndicatorColumn(
|
|
image,
|
|
boundingBox.clone(),
|
|
imageTopRight.as_ref().unwrap(),
|
|
false,
|
|
minCodewordWidth,
|
|
maxCodewordWidth,
|
|
));
|
|
}
|
|
detectionRXingResult = merge(&mut leftRowIndicatorColumn, &mut rightRowIndicatorColumn)?;
|
|
if detectionRXingResult.is_none() {
|
|
return Err(Exceptions::NotFoundException("".to_owned()));
|
|
}
|
|
// detectionRXingResult = detectionRXingResult;
|
|
|
|
let resultBox = detectionRXingResult.as_ref().unwrap().getBoundingBox();
|
|
if firstPass
|
|
&& (resultBox.getMinY() < boundingBox.getMinY()
|
|
|| resultBox.getMaxY() > boundingBox.getMaxY())
|
|
// if firstPass && resultBox.is_some() &&
|
|
{
|
|
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);
|
|
|
|
// let leftToRight = leftRowIndicatorColumn.is_some();
|
|
for barcodeColumnCount in 1..=maxBarcodeColumn {
|
|
// for (int barcodeColumnCount = 1; barcodeColumnCount <= maxBarcodeColumn; barcodeColumnCount++) {
|
|
let barcodeColumn = if leftToRight {
|
|
barcodeColumnCount
|
|
} else {
|
|
maxBarcodeColumn - barcodeColumnCount
|
|
};
|
|
if detectionRXingResult
|
|
.getDetectionRXingResultColumn(barcodeColumn)
|
|
.is_some()
|
|
{
|
|
// This will be the case for the opposite row indicator column, which doesn't need to be decoded again.
|
|
continue;
|
|
}
|
|
let detectionRXingResultColumn = if barcodeColumn == 0 || barcodeColumn == maxBarcodeColumn
|
|
{
|
|
DetectionRXingResultColumn::new_with_is_left(boundingBox.clone(), barcodeColumn == 0)
|
|
} else {
|
|
DetectionRXingResultColumn::new(boundingBox.clone())
|
|
};
|
|
|
|
detectionRXingResult
|
|
.setDetectionRXingResultColumn(barcodeColumn, Some(detectionRXingResultColumn));
|
|
|
|
let mut startColumn: i32 = -1;
|
|
let mut previousStartColumn = startColumn;
|
|
// TODO start at a row for which we know the start position, then detect upwards and downwards from there.
|
|
for imageRow in boundingBox.getMinY()..=boundingBox.getMaxY() {
|
|
// for (int imageRow = boundingBox.getMinY(); imageRow <= boundingBox.getMaxY(); imageRow++) {
|
|
startColumn =
|
|
getStartColumn(&detectionRXingResult, barcodeColumn, imageRow, leftToRight) 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 codeword.is_some() {
|
|
let codeword = codeword.unwrap();
|
|
//detectionRXingResultColumn.setCodeword(imageRow, 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>, Exceptions> {
|
|
if leftRowIndicatorColumn.is_none() && rightRowIndicatorColumn.is_none() {
|
|
return Ok(None);
|
|
}
|
|
let barcodeMetadata = getBarcodeMetadata(leftRowIndicatorColumn, rightRowIndicatorColumn);
|
|
if barcodeMetadata.is_none() {
|
|
return Ok(None);
|
|
}
|
|
let boundingBox = Rc::new(BoundingBox::merge(
|
|
adjustBoundingBox(leftRowIndicatorColumn)?,
|
|
adjustBoundingBox(rightRowIndicatorColumn)?,
|
|
)?);
|
|
|
|
Ok(Some(DetectionRXingResult::new(
|
|
barcodeMetadata.unwrap(),
|
|
boundingBox,
|
|
)))
|
|
}
|
|
|
|
fn adjustBoundingBox<T: DetectionRXingResultRowIndicatorColumn>(
|
|
rowIndicatorColumn: &mut Option<T>,
|
|
) -> Result<Option<BoundingBox>, Exceptions> {
|
|
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 {
|
|
// for (int rowHeight : rowHeights) {
|
|
missingStartRows += maxRowHeight - rowHeight;
|
|
if *rowHeight > 0 {
|
|
break;
|
|
}
|
|
}
|
|
let codewords = rowIndicatorColumn.getCodewords();
|
|
|
|
let mut row = 0;
|
|
while missingStartRows > 0 && codewords[row].is_none() {
|
|
// for (int row = 0; missingStartRows > 0 && codewords[row] == null; row++) {
|
|
missingStartRows -= 1;
|
|
row += 1;
|
|
}
|
|
let mut missingEndRows = 0;
|
|
for row in (0..rowHeights.len()).rev() {
|
|
// for (int row = rowHeights.length - 1; row >= 0; row--) {
|
|
missingEndRows += maxRowHeight - rowHeights[row];
|
|
if rowHeights[row] > 0 {
|
|
break;
|
|
}
|
|
}
|
|
let mut row = codewords.len() - 1;
|
|
while missingEndRows > 0 && codewords[row].is_none() {
|
|
// for (int row = codewords.length - 1; missingEndRows > 0 && codewords[row] == null; row--) {
|
|
missingEndRows -= 1;
|
|
|
|
row -= 1;
|
|
}
|
|
Ok(Some(rowIndicatorColumn.getBoundingBox().addMissingRows(
|
|
missingStartRows,
|
|
missingEndRows,
|
|
rowIndicatorColumn.isLeft(),
|
|
)?))
|
|
}
|
|
|
|
fn getMax(values: &[u32]) -> u32 {
|
|
// let maxValue = -1;
|
|
// for (int value : values) {
|
|
// maxValue = Math.max(maxValue, value);
|
|
// }
|
|
// return maxValue;
|
|
*values.iter().max().unwrap()
|
|
}
|
|
|
|
fn getBarcodeMetadata<T: DetectionRXingResultRowIndicatorColumn>(
|
|
leftRowIndicatorColumn: &mut Option<T>,
|
|
rightRowIndicatorColumn: &mut Option<T>,
|
|
) -> Option<BarcodeMetadata> {
|
|
let leftBarcodeMetadata;
|
|
|
|
if leftRowIndicatorColumn.is_none() {
|
|
return if rightRowIndicatorColumn.is_none() {
|
|
None
|
|
} else {
|
|
rightRowIndicatorColumn
|
|
.as_mut()
|
|
.unwrap()
|
|
.getBarcodeMetadata()
|
|
};
|
|
} else {
|
|
if leftRowIndicatorColumn
|
|
.as_mut()
|
|
.unwrap()
|
|
.getBarcodeMetadata()
|
|
.is_none()
|
|
{
|
|
return if rightRowIndicatorColumn.is_none() {
|
|
None
|
|
} else {
|
|
rightRowIndicatorColumn
|
|
.as_mut()
|
|
.unwrap()
|
|
.getBarcodeMetadata()
|
|
};
|
|
} else {
|
|
leftBarcodeMetadata = leftRowIndicatorColumn
|
|
.as_mut()
|
|
.unwrap()
|
|
.getBarcodeMetadata()
|
|
}
|
|
}
|
|
// if leftRowIndicatorColumn.is_none() ||
|
|
// (leftBarcodeMetadata = leftRowIndicatorColumn.getBarcodeMetadata()).is_none() {
|
|
// return if rightRowIndicatorColumn.is_none() {None} else {rightRowIndicatorColumn.getBarcodeMetadata()};
|
|
// }
|
|
|
|
let rightBarcodeMetadata;
|
|
|
|
if rightRowIndicatorColumn.is_none() {
|
|
return leftBarcodeMetadata;
|
|
} else {
|
|
if rightRowIndicatorColumn
|
|
.as_mut()
|
|
.unwrap()
|
|
.getBarcodeMetadata()
|
|
.is_none()
|
|
{
|
|
return leftBarcodeMetadata;
|
|
} else {
|
|
rightBarcodeMetadata = rightRowIndicatorColumn
|
|
.as_mut()
|
|
.unwrap()
|
|
.getBarcodeMetadata();
|
|
}
|
|
}
|
|
// if rightRowIndicatorColumn.is_none() ||
|
|
// (rightBarcodeMetadata = rightRowIndicatorColumn.getBarcodeMetadata()).is_none() {
|
|
// return leftBarcodeMetadata;
|
|
// }
|
|
|
|
if leftBarcodeMetadata.as_ref().unwrap().getColumnCount()
|
|
!= rightBarcodeMetadata.as_ref().unwrap().getColumnCount()
|
|
&& leftBarcodeMetadata
|
|
.as_ref()
|
|
.unwrap()
|
|
.getErrorCorrectionLevel()
|
|
!= rightBarcodeMetadata
|
|
.as_ref()
|
|
.unwrap()
|
|
.getErrorCorrectionLevel()
|
|
&& leftBarcodeMetadata.as_ref().unwrap().getRowCount()
|
|
!= rightBarcodeMetadata.as_ref().unwrap().getRowCount()
|
|
{
|
|
return None;
|
|
}
|
|
|
|
leftBarcodeMetadata
|
|
}
|
|
|
|
fn getRowIndicatorColumn<'a>(
|
|
image: &BitMatrix,
|
|
boundingBox: Rc<BoundingBox>,
|
|
startPoint: &RXingResultPoint,
|
|
leftToRight: bool,
|
|
minCodewordWidth: u32,
|
|
maxCodewordWidth: u32,
|
|
) -> impl DetectionRXingResultRowIndicatorColumn + 'a {
|
|
let mut rowIndicatorColumn =
|
|
DetectionRXingResultColumn::new_with_is_left(boundingBox.clone(), leftToRight);
|
|
for i in 0..2 {
|
|
// for (int i = 0; i < 2; i++) {
|
|
let increment: i32 = if i == 0 { 1 } else { -1 };
|
|
let mut startColumn: u32 = startPoint.getX() as u32;
|
|
let mut imageRow: i32 = startPoint.getY() as i32;
|
|
while imageRow <= boundingBox.getMaxY() as i32 && imageRow >= boundingBox.getMinY() as i32 {
|
|
// for (int imageRow = (int) startPoint.getY(); imageRow <= boundingBox.getMaxY() &&
|
|
// imageRow >= boundingBox.getMinY(); imageRow += increment) {
|
|
let codeword = detectCodeword(
|
|
image,
|
|
0,
|
|
image.getWidth(),
|
|
leftToRight,
|
|
startColumn,
|
|
imageRow as u32,
|
|
minCodewordWidth,
|
|
maxCodewordWidth,
|
|
);
|
|
if let Some(codeword) = codeword {
|
|
// if codeword.is_some() {
|
|
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<Vec<BarcodeValue>>,
|
|
) -> Result<(), Exceptions> {
|
|
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.len() == 0 {
|
|
if calculatedNumberOfCodewords < 1
|
|
|| calculatedNumberOfCodewords > pdf_417_common::MAX_CODEWORDS_IN_BARCODE
|
|
{
|
|
return Err(Exceptions::NotFoundException("".to_owned()));
|
|
}
|
|
barcodeMatrix01.setValue(calculatedNumberOfCodewords);
|
|
} else if numberOfCodewords[0] != calculatedNumberOfCodewords {
|
|
if calculatedNumberOfCodewords >= 1
|
|
&& calculatedNumberOfCodewords <= pdf_417_common::MAX_CODEWORDS_IN_BARCODE
|
|
{
|
|
// The calculated one is more reliable as it is derived from the row indicator columns
|
|
barcodeMatrix01.setValue(calculatedNumberOfCodewords);
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn createDecoderRXingResult(
|
|
detectionRXingResult: &mut DetectionRXingResult,
|
|
) -> Result<DecoderRXingResult, Exceptions> {
|
|
let mut barcodeMatrix = createBarcodeMatrix(detectionRXingResult);
|
|
adjustCodewordCount(detectionRXingResult, &mut barcodeMatrix)?;
|
|
let mut erasures = Vec::new(); //new ArrayList<>();
|
|
let mut codewords = vec![
|
|
0;
|
|
detectionRXingResult.getBarcodeRowCount() as usize
|
|
* detectionRXingResult.getBarcodeColumnCount()
|
|
];
|
|
let mut ambiguousIndexValuesList: Vec<Vec<u32>> = Vec::new();
|
|
let mut ambiguousIndexesList = Vec::new(); //new ArrayList<>();
|
|
for row in 0..detectionRXingResult.getBarcodeRowCount() {
|
|
// for (int row = 0; row < detectionRXingResult.getBarcodeRowCount(); row++) {
|
|
for column in 0..detectionRXingResult.getBarcodeColumnCount() {
|
|
// for (int column = 0; column < detectionRXingResult.getBarcodeColumnCount(); column++) {
|
|
let values = barcodeMatrix[row as usize][column + 1].getValue();
|
|
let codewordIndex =
|
|
row as usize * detectionRXingResult.getBarcodeColumnCount() + column;
|
|
if values.len() == 0 {
|
|
erasures.push(codewordIndex as u32);
|
|
} else if values.len() == 1 {
|
|
codewords[codewordIndex] = values[0];
|
|
} else {
|
|
ambiguousIndexesList.push(codewordIndex as u32);
|
|
ambiguousIndexValuesList.push(values);
|
|
}
|
|
}
|
|
}
|
|
let mut ambiguousIndexValues = Vec::new(); //new int[ambiguousIndexValuesList.size()][];
|
|
for value in ambiguousIndexValuesList {
|
|
ambiguousIndexValues.push(value);
|
|
}
|
|
// for i in 0..ambiguousIndexValuesList.len() {
|
|
// // for (int i = 0; i < ambiguousIndexValues.length; i++) {
|
|
// ambiguousIndexValues[i] = ambiguousIndexValuesList.get(i) as u32;
|
|
// }
|
|
createDecoderRXingResultFromAmbiguousValues(
|
|
detectionRXingResult.getBarcodeECLevel(),
|
|
&mut codewords,
|
|
&mut erasures,
|
|
&mut ambiguousIndexesList,
|
|
&ambiguousIndexValues,
|
|
)
|
|
}
|
|
|
|
/**
|
|
* This method deals with the fact, that the decoding process doesn't always yield a single most likely value. The
|
|
* current error correction implementation doesn't deal with erasures very well, so it's better to provide a value
|
|
* for these ambiguous codewords instead of treating it as an erasure. The problem is that we don't know which of
|
|
* the ambiguous values to choose. We try decode using the first value, and if that fails, we use another of the
|
|
* ambiguous values and try to decode again. This usually only happens on very hard to read and decode barcodes,
|
|
* so decoding the normal barcodes is not affected by this.
|
|
*
|
|
* @param erasureArray contains the indexes of erasures
|
|
* @param ambiguousIndexes array with the indexes that have more than one most likely value
|
|
* @param ambiguousIndexValues two dimensional array that contains the ambiguous values. The first dimension must
|
|
* be the same length as the ambiguousIndexes array
|
|
*/
|
|
fn createDecoderRXingResultFromAmbiguousValues(
|
|
ecLevel: u32,
|
|
codewords: &mut [u32],
|
|
erasureArray: &mut [u32],
|
|
ambiguousIndexes: &mut [u32],
|
|
ambiguousIndexValues: &Vec<Vec<u32>>,
|
|
) -> Result<DecoderRXingResult, Exceptions> {
|
|
let mut ambiguousIndexCount = vec![0; ambiguousIndexes.len()];
|
|
|
|
let mut tries = 100;
|
|
while tries > 0 {
|
|
for i in 0..ambiguousIndexCount.len() {
|
|
// for (int i = 0; i < ambiguousIndexCount.length; i++) {
|
|
codewords[ambiguousIndexes[i] as usize] =
|
|
ambiguousIndexValues[i][ambiguousIndexCount[i]];
|
|
}
|
|
let attempted_decode = decodeCodewords(codewords, ecLevel, erasureArray);
|
|
if attempted_decode.is_ok() {
|
|
return attempted_decode;
|
|
}
|
|
// try {
|
|
// return decodeCodewords(codewords, ecLevel, erasureArray);
|
|
// } catch (ChecksumException ignored) {
|
|
// //
|
|
// }
|
|
if ambiguousIndexCount.len() == 0 {
|
|
return Err(Exceptions::ChecksumException("".to_owned()));
|
|
}
|
|
for i in 0..ambiguousIndexCount.len() {
|
|
// for (int i = 0; i < ambiguousIndexCount.length; i++) {
|
|
if ambiguousIndexCount[i] < ambiguousIndexValues[i].len() - 1 {
|
|
ambiguousIndexCount[i] += 1;
|
|
break;
|
|
} else {
|
|
ambiguousIndexCount[i] = 0;
|
|
if i == ambiguousIndexCount.len() - 1 {
|
|
return Err(Exceptions::ChecksumException("".to_owned()));
|
|
}
|
|
}
|
|
}
|
|
|
|
tries -= 1;
|
|
}
|
|
Err(Exceptions::ChecksumException("".to_owned()))
|
|
}
|
|
|
|
fn createBarcodeMatrix(detectionRXingResult: &mut DetectionRXingResult) -> Vec<Vec<BarcodeValue>> {
|
|
let mut barcodeMatrix =
|
|
vec![
|
|
vec![BarcodeValue::new(); detectionRXingResult.getBarcodeColumnCount() + 2];
|
|
detectionRXingResult.getBarcodeRowCount() as usize
|
|
];
|
|
// BarcodeValue[][] barcodeMatrix =
|
|
// new BarcodeValue[detectionRXingResult.getBarcodeRowCount()][detectionRXingResult.getBarcodeColumnCount() + 2];
|
|
// for row in 0..barcodeMatrix.len() {
|
|
// // for (int row = 0; row < barcodeMatrix.length; row++) {
|
|
// for column in 0..barcodeMatrix[row].len() {
|
|
// // for (int column = 0; column < barcodeMatrix[row].length; column++) {
|
|
// barcodeMatrix[row][column] = BarcodeValue::new();
|
|
// }
|
|
// }
|
|
|
|
let mut column = 0;
|
|
for detectionRXingResultColumn in detectionRXingResult.getDetectionRXingResultColumns() {
|
|
// for (DetectionRXingResultColumn detectionRXingResultColumn : detectionRXingResult.getDetectionRXingResultColumns()) {
|
|
if detectionRXingResultColumn.is_some() {
|
|
for codeword in detectionRXingResultColumn.as_ref().unwrap().getCodewords() {
|
|
// for (Codeword codeword : detectionRXingResultColumn.getCodewords()) {
|
|
if let Some(codeword) = codeword {
|
|
// if codeword.is_some() {
|
|
let rowNumber = codeword.getRowNumber();
|
|
if rowNumber >= 0 {
|
|
if rowNumber as usize >= barcodeMatrix.len() {
|
|
// We have more rows than the barcode metadata allows for, ignore them.
|
|
continue;
|
|
}
|
|
barcodeMatrix[rowNumber as usize][column].setValue(codeword.getValue());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
column += 1;
|
|
}
|
|
barcodeMatrix
|
|
}
|
|
|
|
fn isValidBarcodeColumn(detectionRXingResult: &DetectionRXingResult, barcodeColumn: usize) -> bool {
|
|
/*barcodeColumn >= 0 &&*/
|
|
barcodeColumn <= detectionRXingResult.getBarcodeColumnCount() + 1
|
|
}
|
|
|
|
fn getStartColumn(
|
|
detectionRXingResult: &DetectionRXingResult,
|
|
barcodeColumn: usize,
|
|
imageRow: u32,
|
|
leftToRight: bool,
|
|
) -> 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 as isize - offset) as usize)
|
|
.as_ref()
|
|
.unwrap()
|
|
.getCodeword(imageRow);
|
|
}
|
|
if let Some(codeword) = codeword {
|
|
return if leftToRight {
|
|
codeword.getEndX()
|
|
} else {
|
|
codeword.getStartX()
|
|
};
|
|
}
|
|
|
|
if detectionRXingResult
|
|
.getDetectionRXingResultColumn(barcodeColumn as usize)
|
|
.is_some()
|
|
{
|
|
codeword = detectionRXingResult
|
|
.getDetectionRXingResultColumn(barcodeColumn as usize)
|
|
.as_ref()
|
|
.unwrap()
|
|
.getCodewordNearby(imageRow);
|
|
}
|
|
|
|
if let Some(codeword) = codeword {
|
|
return if leftToRight {
|
|
codeword.getStartX()
|
|
} else {
|
|
codeword.getEndX()
|
|
};
|
|
}
|
|
if isValidBarcodeColumn(detectionRXingResult, (barcodeColumn - offset) as usize) {
|
|
codeword = detectionRXingResult
|
|
.getDetectionRXingResultColumn((barcodeColumn - offset) as usize)
|
|
.as_ref()
|
|
.unwrap()
|
|
.getCodewordNearby(imageRow);
|
|
}
|
|
if let Some(codeword) = codeword {
|
|
return if leftToRight {
|
|
codeword.getEndX()
|
|
} else {
|
|
codeword.getStartX()
|
|
};
|
|
}
|
|
let mut skippedColumns = 0;
|
|
|
|
while isValidBarcodeColumn(detectionRXingResult, (barcodeColumn - offset) as usize) {
|
|
barcodeColumn -= offset;
|
|
for previousRowCodeword in detectionRXingResult
|
|
.getDetectionRXingResultColumn(barcodeColumn as usize)
|
|
.as_ref()
|
|
.unwrap()
|
|
.getCodewords()
|
|
{
|
|
// for (Codeword previousRowCodeword : detectionRXingResult.getDetectionRXingResultColumn(barcodeColumn).getCodewords()) {
|
|
if let Some(previousRowCodeword) = previousRowCodeword {
|
|
// if previousRowCodeword.is_some() {
|
|
return ((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 {
|
|
detectionRXingResult.getBoundingBox().getMinX()
|
|
} else {
|
|
detectionRXingResult.getBoundingBox().getMaxX()
|
|
}
|
|
}
|
|
|
|
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,
|
|
);
|
|
// we usually know fairly exact now how long a codeword is. We should provide minimum and maximum expected length
|
|
// and try to adjust the read pixels, e.g. remove single pixel errors or try to cut off exceeding pixels.
|
|
// min and maxCodewordWidth should not be used as they are calculated for the whole barcode an can be inaccurate
|
|
// for the current position
|
|
let moduleBitCount = getModuleBitCount(
|
|
image,
|
|
minColumn,
|
|
maxColumn,
|
|
leftToRight,
|
|
startColumn,
|
|
imageRow,
|
|
);
|
|
if moduleBitCount.is_none() {
|
|
return None;
|
|
}
|
|
let mut moduleBitCount = moduleBitCount.unwrap();
|
|
|
|
let endColumn;
|
|
let codewordBitCount = moduleBitCount.iter().sum::<u32>();
|
|
if leftToRight {
|
|
endColumn = startColumn + codewordBitCount;
|
|
} else {
|
|
for i in 0..(moduleBitCount.len() / 2) {
|
|
// for (int i = 0; i < moduleBitCount.length / 2; i++) {
|
|
|
|
let len = moduleBitCount.len();
|
|
moduleBitCount.swap(i, len - 1 - i);
|
|
|
|
// let tmpCount = moduleBitCount[i];
|
|
// moduleBitCount[i] = moduleBitCount[moduleBitCount.len() - 1 - i];
|
|
// moduleBitCount[moduleBitCount.len() - 1 - i] = tmpCount;
|
|
}
|
|
endColumn = startColumn;
|
|
startColumn = endColumn - codewordBitCount;
|
|
}
|
|
// TODO implement check for width and correction of black and white bars
|
|
// use start (and maybe stop pattern) to determine if black bars are wider than white bars. If so, adjust.
|
|
// should probably done only for codewords with a lot more than 17 bits.
|
|
// The following fixes 10-1.png, which has wide black bars and small white bars
|
|
// for (int i = 0; i < moduleBitCount.length; i++) {
|
|
// if (i % 2 == 0) {
|
|
// moduleBitCount[i]--;
|
|
// } else {
|
|
// moduleBitCount[i]++;
|
|
// }
|
|
// }
|
|
|
|
// We could also use the width of surrounding codewords for more accurate results, but this seems
|
|
// sufficient for now
|
|
if !checkCodewordSkew(codewordBitCount, minCodewordWidth, maxCodewordWidth) {
|
|
// We could try to use the startX and endX position of the codeword in the same column in the previous row,
|
|
// create the bit count from it and normalize it to 8. This would help with single pixel errors.
|
|
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;
|
|
// there should be no black pixels before the start column. If there are, then we need to start earlier.
|
|
for _i in 0..2 {
|
|
// for (int i = 0; i < 2; i++) {
|
|
while (if leftToRight {
|
|
correctedStartColumn >= minColumn
|
|
} else {
|
|
correctedStartColumn < maxColumn
|
|
}) && leftToRight == image.get(correctedStartColumn, imageRow)
|
|
{
|
|
if (codewordStartColumn as i64 - correctedStartColumn as i64).abs() as u32
|
|
> CODEWORD_SKEW_SIZE
|
|
{
|
|
return codewordStartColumn;
|
|
}
|
|
correctedStartColumn = (correctedStartColumn as i32 + increment) as u32;
|
|
}
|
|
increment = -increment;
|
|
leftToRight = !leftToRight;
|
|
}
|
|
return correctedStartColumn;
|
|
}
|
|
|
|
fn checkCodewordSkew(codewordSize: u32, minCodewordWidth: u32, maxCodewordWidth: u32) -> bool {
|
|
return minCodewordWidth - CODEWORD_SKEW_SIZE <= codewordSize
|
|
&& codewordSize <= maxCodewordWidth + CODEWORD_SKEW_SIZE;
|
|
}
|
|
|
|
fn decodeCodewords(
|
|
codewords: &mut [u32],
|
|
ecLevel: u32,
|
|
erasures: &mut [u32],
|
|
) -> Result<DecoderRXingResult, Exceptions> {
|
|
if codewords.is_empty() {
|
|
return Err(Exceptions::FormatException("".to_owned()));
|
|
}
|
|
|
|
let numECCodewords = 1 << (ecLevel + 1);
|
|
let correctedErrorsCount = correctErrors(codewords, erasures, numECCodewords)?;
|
|
verifyCodewordCount(codewords, numECCodewords)?;
|
|
|
|
// Decode the codewords
|
|
let mut decoderRXingResult =
|
|
decoded_bit_stream_parser::decode(codewords, &ecLevel.to_string())?;
|
|
decoderRXingResult.setErrorsCorrected(correctedErrorsCount);
|
|
decoderRXingResult.setErasures(erasures.len());
|
|
|
|
Ok(decoderRXingResult)
|
|
}
|
|
|
|
/**
|
|
* <p>Given data and error-correction codewords received, possibly corrupted by errors, attempts to
|
|
* correct the errors in-place.</p>
|
|
*
|
|
* @param codewords data and error correction codewords
|
|
* @param erasures positions of any known erasures
|
|
* @param numECCodewords number of error correction codewords that are available in codewords
|
|
* @throws ChecksumException if error correction fails
|
|
*/
|
|
fn correctErrors(
|
|
codewords: &mut [u32],
|
|
erasures: &mut [u32],
|
|
numECCodewords: u32,
|
|
) -> Result<usize, Exceptions> {
|
|
if !erasures.is_empty() && erasures.len() as u32 > numECCodewords / 2 + MAX_ERRORS
|
|
/*|| numECCodewords < 0*/
|
|
|| numECCodewords > MAX_EC_CODEWORDS
|
|
{
|
|
// Too many errors or EC Codewords is corrupted
|
|
return Err(Exceptions::ChecksumException("".to_owned()));
|
|
}
|
|
ec::error_correction::decode(codewords, numECCodewords, erasures)
|
|
}
|
|
|
|
/**
|
|
* Verify that all is OK with the codeword array.
|
|
*/
|
|
fn verifyCodewordCount(codewords: &mut [u32], numECCodewords: u32) -> Result<(), Exceptions> {
|
|
if codewords.len() < 4 {
|
|
// Codeword array size should be at least 4 allowing for
|
|
// Count CW, At least one Data CW, Error Correction CW, Error Correction CW
|
|
return Err(Exceptions::FormatException("".to_owned()));
|
|
}
|
|
// The first codeword, the Symbol Length Descriptor, shall always encode the total number of data
|
|
// codewords in the symbol, including the Symbol Length Descriptor itself, data codewords and pad
|
|
// codewords, but excluding the number of error correction codewords.
|
|
let numberOfCodewords = codewords[0];
|
|
if numberOfCodewords > codewords.len() as u32 {
|
|
return Err(Exceptions::FormatException("".to_owned()));
|
|
}
|
|
if numberOfCodewords == 0 {
|
|
// Reset to the length of the array - 8 (Allow for at least level 3 Error Correction (8 Error Codewords)
|
|
if numECCodewords < codewords.len() as u32 {
|
|
codewords[0] = codewords.len() as u32 - numECCodewords;
|
|
} else {
|
|
return Err(Exceptions::FormatException("".to_owned()));
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn getBitCountForCodeword(codeword: u32) -> [u32; 8] {
|
|
let mut codeword = codeword;
|
|
let mut result = [0; 8]; //new int[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
|
|
}
|
|
|
|
// fn toString( barcodeMatrix:Vec<Vec<BarcodeValue>>) -> String{
|
|
// try (Formatter formatter = new Formatter()) {
|
|
// for (int row = 0; row < barcodeMatrix.length; row++) {
|
|
// formatter.format("Row %2d: ", row);
|
|
// for (int column = 0; column < barcodeMatrix[row].length; column++) {
|
|
// BarcodeValue barcodeValue = barcodeMatrix[row][column];
|
|
// if (barcodeValue.getValue().length == 0) {
|
|
// formatter.format(" ", (Object[]) null);
|
|
// } else {
|
|
// formatter.format("%4d(%2d)", barcodeValue.getValue()[0],
|
|
// barcodeValue.getConfidence(barcodeValue.getValue()[0]));
|
|
// }
|
|
// }
|
|
// formatter.format("%n");
|
|
// }
|
|
// return formatter.toString();
|
|
// }
|
|
// }
|