diff --git a/src/aztec/detector.rs b/src/aztec/detector.rs index 9e86ca1..acd4578 100644 --- a/src/aztec/detector.rs +++ b/src/aztec/detector.rs @@ -85,7 +85,7 @@ impl Detector { let mut bulls_eye_corners = self.get_bulls_eye_corners(p_center)?; if is_mirror { - bulls_eye_corners.swap(0,2); + bulls_eye_corners.swap(0, 2); } // 3. Get the size of the matrix and other parameters from the bull's eye diff --git a/src/common/global_histogram_binarizer.rs b/src/common/global_histogram_binarizer.rs index 01e91a7..1a78afa 100644 --- a/src/common/global_histogram_binarizer.rs +++ b/src/common/global_histogram_binarizer.rs @@ -214,7 +214,6 @@ impl GlobalHistogramBinarizer { // Make sure firstPeak corresponds to the black peak. if firstPeak > secondPeak { - std::mem::swap(&mut firstPeak, &mut secondPeak); } diff --git a/src/common/reedsolomon/generic_gf_poly.rs b/src/common/reedsolomon/generic_gf_poly.rs index dfce085..de63ff0 100644 --- a/src/common/reedsolomon/generic_gf_poly.rs +++ b/src/common/reedsolomon/generic_gf_poly.rs @@ -154,7 +154,7 @@ impl GenericGFPoly { let mut smallerCoefficients = self.coefficients.clone(); let mut largerCoefficients = other.coefficients.clone(); if smallerCoefficients.len() > largerCoefficients.len() { - std::mem::swap(&mut smallerCoefficients,&mut largerCoefficients) + std::mem::swap(&mut smallerCoefficients, &mut largerCoefficients) } let mut sumDiff = vec![0; largerCoefficients.len()]; diff --git a/src/oned/rss/rss_14_reader.rs b/src/oned/rss/rss_14_reader.rs index e77c43b..d613b61 100644 --- a/src/oned/rss/rss_14_reader.rs +++ b/src/oned/rss/rss_14_reader.rs @@ -307,8 +307,8 @@ impl RSS14Reader { let mut j = counters.len() - 1; while i < j { // for (int i = 0, j = counters.length - 1; i < j; i++, j--) { - - counters.swap(i,j); + + counters.swap(i, j); i += 1; j -= 1; diff --git a/src/pdf417/decoder/DetectionResult.java b/src/pdf417/decoder/DetectionResult.java deleted file mode 100644 index 33179d2..0000000 --- a/src/pdf417/decoder/DetectionResult.java +++ /dev/null @@ -1,299 +0,0 @@ -/* - * 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. - */ - -package com.google.zxing.pdf417.decoder; - -import com.google.zxing.pdf417.PDF417Common; - -import java.util.Formatter; - -/** - * @author Guenther Grau - */ -final class DetectionRXingResult { - - private static final int ADJUST_ROW_NUMBER_SKIP = 2; - - private final BarcodeMetadata barcodeMetadata; - private final DetectionRXingResultColumn[] detectionRXingResultColumns; - private BoundingBox boundingBox; - private final int barcodeColumnCount; - - DetectionRXingResult(BarcodeMetadata barcodeMetadata, BoundingBox boundingBox) { - this.barcodeMetadata = barcodeMetadata; - this.barcodeColumnCount = barcodeMetadata.getColumnCount(); - this.boundingBox = boundingBox; - detectionRXingResultColumns = new DetectionRXingResultColumn[barcodeColumnCount + 2]; - } - - DetectionRXingResultColumn[] getDetectionRXingResultColumns() { - adjustIndicatorColumnRowNumbers(detectionRXingResultColumns[0]); - adjustIndicatorColumnRowNumbers(detectionRXingResultColumns[barcodeColumnCount + 1]); - int unadjustedCodewordCount = PDF417Common.MAX_CODEWORDS_IN_BARCODE; - int previousUnadjustedCount; - do { - previousUnadjustedCount = unadjustedCodewordCount; - unadjustedCodewordCount = adjustRowNumbers(); - } while (unadjustedCodewordCount > 0 && unadjustedCodewordCount < previousUnadjustedCount); - return detectionRXingResultColumns; - } - - private void adjustIndicatorColumnRowNumbers(DetectionRXingResultColumn detectionRXingResultColumn) { - if (detectionRXingResultColumn != null) { - ((DetectionRXingResultRowIndicatorColumn) detectionRXingResultColumn) - .adjustCompleteIndicatorColumnRowNumbers(barcodeMetadata); - } - } - - // TODO ensure that no detected codewords with unknown row number are left - // we should be able to estimate the row height and use it as a hint for the row number - // we should also fill the rows top to bottom and bottom to top - /** - * @return number of codewords which don't have a valid row number. Note that the count is not accurate as codewords - * will be counted several times. It just serves as an indicator to see when we can stop adjusting row numbers - */ - private int adjustRowNumbers() { - int unadjustedCount = adjustRowNumbersByRow(); - if (unadjustedCount == 0) { - return 0; - } - for (int barcodeColumn = 1; barcodeColumn < barcodeColumnCount + 1; barcodeColumn++) { - Codeword[] codewords = detectionRXingResultColumns[barcodeColumn].getCodewords(); - for (int codewordsRow = 0; codewordsRow < codewords.length; codewordsRow++) { - if (codewords[codewordsRow] == null) { - continue; - } - if (!codewords[codewordsRow].hasValidRowNumber()) { - adjustRowNumbers(barcodeColumn, codewordsRow, codewords); - } - } - } - return unadjustedCount; - } - - private int adjustRowNumbersByRow() { - adjustRowNumbersFromBothRI(); - // TODO we should only do full row adjustments if row numbers of left and right row indicator column match. - // Maybe it's even better to calculated the height (in codeword rows) and divide it by the number of barcode - // rows. This, together with the LRI and RRI row numbers should allow us to get a good estimate where a row - // number starts and ends. - int unadjustedCount = adjustRowNumbersFromLRI(); - return unadjustedCount + adjustRowNumbersFromRRI(); - } - - private void adjustRowNumbersFromBothRI() { - if (detectionRXingResultColumns[0] == null || detectionRXingResultColumns[barcodeColumnCount + 1] == null) { - return; - } - Codeword[] LRIcodewords = detectionRXingResultColumns[0].getCodewords(); - Codeword[] RRIcodewords = detectionRXingResultColumns[barcodeColumnCount + 1].getCodewords(); - for (int codewordsRow = 0; codewordsRow < LRIcodewords.length; codewordsRow++) { - if (LRIcodewords[codewordsRow] != null && - RRIcodewords[codewordsRow] != null && - LRIcodewords[codewordsRow].getRowNumber() == RRIcodewords[codewordsRow].getRowNumber()) { - for (int barcodeColumn = 1; barcodeColumn <= barcodeColumnCount; barcodeColumn++) { - Codeword codeword = detectionRXingResultColumns[barcodeColumn].getCodewords()[codewordsRow]; - if (codeword == null) { - continue; - } - codeword.setRowNumber(LRIcodewords[codewordsRow].getRowNumber()); - if (!codeword.hasValidRowNumber()) { - detectionRXingResultColumns[barcodeColumn].getCodewords()[codewordsRow] = null; - } - } - } - } - } - - private int adjustRowNumbersFromRRI() { - if (detectionRXingResultColumns[barcodeColumnCount + 1] == null) { - return 0; - } - int unadjustedCount = 0; - Codeword[] codewords = detectionRXingResultColumns[barcodeColumnCount + 1].getCodewords(); - for (int codewordsRow = 0; codewordsRow < codewords.length; codewordsRow++) { - if (codewords[codewordsRow] == null) { - continue; - } - int rowIndicatorRowNumber = codewords[codewordsRow].getRowNumber(); - int invalidRowCounts = 0; - for (int barcodeColumn = barcodeColumnCount + 1; - barcodeColumn > 0 && invalidRowCounts < ADJUST_ROW_NUMBER_SKIP; - barcodeColumn--) { - Codeword codeword = detectionRXingResultColumns[barcodeColumn].getCodewords()[codewordsRow]; - if (codeword != null) { - invalidRowCounts = adjustRowNumberIfValid(rowIndicatorRowNumber, invalidRowCounts, codeword); - if (!codeword.hasValidRowNumber()) { - unadjustedCount++; - } - } - } - } - return unadjustedCount; - } - - private int adjustRowNumbersFromLRI() { - if (detectionRXingResultColumns[0] == null) { - return 0; - } - int unadjustedCount = 0; - Codeword[] codewords = detectionRXingResultColumns[0].getCodewords(); - for (int codewordsRow = 0; codewordsRow < codewords.length; codewordsRow++) { - if (codewords[codewordsRow] == null) { - continue; - } - int rowIndicatorRowNumber = codewords[codewordsRow].getRowNumber(); - int invalidRowCounts = 0; - for (int barcodeColumn = 1; - barcodeColumn < barcodeColumnCount + 1 && invalidRowCounts < ADJUST_ROW_NUMBER_SKIP; - barcodeColumn++) { - Codeword codeword = detectionRXingResultColumns[barcodeColumn].getCodewords()[codewordsRow]; - if (codeword != null) { - invalidRowCounts = adjustRowNumberIfValid(rowIndicatorRowNumber, invalidRowCounts, codeword); - if (!codeword.hasValidRowNumber()) { - unadjustedCount++; - } - } - } - } - return unadjustedCount; - } - - private static int adjustRowNumberIfValid(int rowIndicatorRowNumber, int invalidRowCounts, Codeword codeword) { - if (codeword == null) { - return invalidRowCounts; - } - if (!codeword.hasValidRowNumber()) { - if (codeword.isValidRowNumber(rowIndicatorRowNumber)) { - codeword.setRowNumber(rowIndicatorRowNumber); - invalidRowCounts = 0; - } else { - ++invalidRowCounts; - } - } - return invalidRowCounts; - } - - private void adjustRowNumbers(int barcodeColumn, int codewordsRow, Codeword[] codewords) { - Codeword codeword = codewords[codewordsRow]; - Codeword[] previousColumnCodewords = detectionRXingResultColumns[barcodeColumn - 1].getCodewords(); - Codeword[] nextColumnCodewords = previousColumnCodewords; - if (detectionRXingResultColumns[barcodeColumn + 1] != null) { - nextColumnCodewords = detectionRXingResultColumns[barcodeColumn + 1].getCodewords(); - } - - Codeword[] otherCodewords = new Codeword[14]; - - otherCodewords[2] = previousColumnCodewords[codewordsRow]; - otherCodewords[3] = nextColumnCodewords[codewordsRow]; - - if (codewordsRow > 0) { - otherCodewords[0] = codewords[codewordsRow - 1]; - otherCodewords[4] = previousColumnCodewords[codewordsRow - 1]; - otherCodewords[5] = nextColumnCodewords[codewordsRow - 1]; - } - if (codewordsRow > 1) { - otherCodewords[8] = codewords[codewordsRow - 2]; - otherCodewords[10] = previousColumnCodewords[codewordsRow - 2]; - otherCodewords[11] = nextColumnCodewords[codewordsRow - 2]; - } - if (codewordsRow < codewords.length - 1) { - otherCodewords[1] = codewords[codewordsRow + 1]; - otherCodewords[6] = previousColumnCodewords[codewordsRow + 1]; - otherCodewords[7] = nextColumnCodewords[codewordsRow + 1]; - } - if (codewordsRow < codewords.length - 2) { - otherCodewords[9] = codewords[codewordsRow + 2]; - otherCodewords[12] = previousColumnCodewords[codewordsRow + 2]; - otherCodewords[13] = nextColumnCodewords[codewordsRow + 2]; - } - for (Codeword otherCodeword : otherCodewords) { - if (adjustRowNumber(codeword, otherCodeword)) { - return; - } - } - } - - /** - * @return true, if row number was adjusted, false otherwise - */ - private static boolean adjustRowNumber(Codeword codeword, Codeword otherCodeword) { - if (otherCodeword == null) { - return false; - } - if (otherCodeword.hasValidRowNumber() && otherCodeword.getBucket() == codeword.getBucket()) { - codeword.setRowNumber(otherCodeword.getRowNumber()); - return true; - } - return false; - } - - int getBarcodeColumnCount() { - return barcodeColumnCount; - } - - int getBarcodeRowCount() { - return barcodeMetadata.getRowCount(); - } - - int getBarcodeECLevel() { - return barcodeMetadata.getErrorCorrectionLevel(); - } - - void setBoundingBox(BoundingBox boundingBox) { - this.boundingBox = boundingBox; - } - - BoundingBox getBoundingBox() { - return boundingBox; - } - - void setDetectionRXingResultColumn(int barcodeColumn, DetectionRXingResultColumn detectionRXingResultColumn) { - detectionRXingResultColumns[barcodeColumn] = detectionRXingResultColumn; - } - - DetectionRXingResultColumn getDetectionRXingResultColumn(int barcodeColumn) { - return detectionRXingResultColumns[barcodeColumn]; - } - - @Override - public String toString() { - DetectionRXingResultColumn rowIndicatorColumn = detectionRXingResultColumns[0]; - if (rowIndicatorColumn == null) { - rowIndicatorColumn = detectionRXingResultColumns[barcodeColumnCount + 1]; - } - try (Formatter formatter = new Formatter()) { - for (int codewordsRow = 0; codewordsRow < rowIndicatorColumn.getCodewords().length; codewordsRow++) { - formatter.format("CW %3d:", codewordsRow); - for (int barcodeColumn = 0; barcodeColumn < barcodeColumnCount + 2; barcodeColumn++) { - if (detectionRXingResultColumns[barcodeColumn] == null) { - formatter.format(" | "); - continue; - } - Codeword codeword = detectionRXingResultColumns[barcodeColumn].getCodewords()[codewordsRow]; - if (codeword == null) { - formatter.format(" | "); - continue; - } - formatter.format(" %3d|%3d", codeword.getRowNumber(), codeword.getValue()); - } - formatter.format("%n"); - } - return formatter.toString(); - } - } - -} diff --git a/src/pdf417/decoder/DetectionResultRowIndicatorColumn.java b/src/pdf417/decoder/DetectionResultRowIndicatorColumn.java deleted file mode 100644 index 002d75c..0000000 --- a/src/pdf417/decoder/DetectionResultRowIndicatorColumn.java +++ /dev/null @@ -1,261 +0,0 @@ -/* - * 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. - */ - -package com.google.zxing.pdf417.decoder; - -import com.google.zxing.RXingResultPoint; -import com.google.zxing.pdf417.PDF417Common; - -/** - * @author Guenther Grau - */ -final class DetectionRXingResultRowIndicatorColumn extends DetectionRXingResultColumn { - - private final boolean isLeft; - - DetectionRXingResultRowIndicatorColumn(BoundingBox boundingBox, boolean isLeft) { - super(boundingBox); - this.isLeft = isLeft; - } - - private void setRowNumbers() { - for (Codeword codeword : getCodewords()) { - if (codeword != null) { - codeword.setRowNumberAsRowIndicatorColumn(); - } - } - } - - // TODO implement properly - // TODO maybe we should add missing codewords to store the correct row number to make - // finding row numbers for other columns easier - // use row height count to make detection of invalid row numbers more reliable - void adjustCompleteIndicatorColumnRowNumbers(BarcodeMetadata barcodeMetadata) { - Codeword[] codewords = getCodewords(); - setRowNumbers(); - removeIncorrectCodewords(codewords, barcodeMetadata); - BoundingBox boundingBox = getBoundingBox(); - RXingResultPoint top = isLeft ? boundingBox.getTopLeft() : boundingBox.getTopRight(); - RXingResultPoint bottom = isLeft ? boundingBox.getBottomLeft() : boundingBox.getBottomRight(); - int firstRow = imageRowToCodewordIndex((int) top.getY()); - int lastRow = imageRowToCodewordIndex((int) bottom.getY()); - // We need to be careful using the average row height. Barcode could be skewed so that we have smaller and - // taller rows - //float averageRowHeight = (lastRow - firstRow) / (float) barcodeMetadata.getRowCount(); - int barcodeRow = -1; - int maxRowHeight = 1; - int currentRowHeight = 0; - for (int codewordsRow = firstRow; codewordsRow < lastRow; codewordsRow++) { - if (codewords[codewordsRow] == null) { - continue; - } - Codeword codeword = codewords[codewordsRow]; - - int rowDifference = codeword.getRowNumber() - barcodeRow; - - // TODO improve handling with case where first row indicator doesn't start with 0 - - if (rowDifference == 0) { - currentRowHeight++; - } else if (rowDifference == 1) { - maxRowHeight = Math.max(maxRowHeight, currentRowHeight); - currentRowHeight = 1; - barcodeRow = codeword.getRowNumber(); - } else if (rowDifference < 0 || - codeword.getRowNumber() >= barcodeMetadata.getRowCount() || - rowDifference > codewordsRow) { - codewords[codewordsRow] = null; - } else { - int checkedRows; - if (maxRowHeight > 2) { - checkedRows = (maxRowHeight - 2) * rowDifference; - } else { - checkedRows = rowDifference; - } - boolean closePreviousCodewordFound = checkedRows >= codewordsRow; - for (int i = 1; i <= checkedRows && !closePreviousCodewordFound; i++) { - // there must be (height * rowDifference) number of codewords missing. For now we assume height = 1. - // This should hopefully get rid of most problems already. - closePreviousCodewordFound = codewords[codewordsRow - i] != null; - } - if (closePreviousCodewordFound) { - codewords[codewordsRow] = null; - } else { - barcodeRow = codeword.getRowNumber(); - currentRowHeight = 1; - } - } - } - //return (int) (averageRowHeight + 0.5); - } - - int[] getRowHeights() { - BarcodeMetadata barcodeMetadata = getBarcodeMetadata(); - if (barcodeMetadata == null) { - return null; - } - adjustIncompleteIndicatorColumnRowNumbers(barcodeMetadata); - int[] result = new int[barcodeMetadata.getRowCount()]; - for (Codeword codeword : getCodewords()) { - if (codeword != null) { - int rowNumber = codeword.getRowNumber(); - if (rowNumber >= result.length) { - // We have more rows than the barcode metadata allows for, ignore them. - continue; - } - result[rowNumber]++; - } // else throw exception? - } - return result; - } - - // TODO maybe we should add missing codewords to store the correct row number to make - // finding row numbers for other columns easier - // use row height count to make detection of invalid row numbers more reliable - private void adjustIncompleteIndicatorColumnRowNumbers(BarcodeMetadata barcodeMetadata) { - BoundingBox boundingBox = getBoundingBox(); - RXingResultPoint top = isLeft ? boundingBox.getTopLeft() : boundingBox.getTopRight(); - RXingResultPoint bottom = isLeft ? boundingBox.getBottomLeft() : boundingBox.getBottomRight(); - int firstRow = imageRowToCodewordIndex((int) top.getY()); - int lastRow = imageRowToCodewordIndex((int) bottom.getY()); - //float averageRowHeight = (lastRow - firstRow) / (float) barcodeMetadata.getRowCount(); - Codeword[] codewords = getCodewords(); - int barcodeRow = -1; - int maxRowHeight = 1; - int currentRowHeight = 0; - for (int codewordsRow = firstRow; codewordsRow < lastRow; codewordsRow++) { - if (codewords[codewordsRow] == null) { - continue; - } - Codeword codeword = codewords[codewordsRow]; - - codeword.setRowNumberAsRowIndicatorColumn(); - - int rowDifference = codeword.getRowNumber() - barcodeRow; - - // TODO improve handling with case where first row indicator doesn't start with 0 - - if (rowDifference == 0) { - currentRowHeight++; - } else if (rowDifference == 1) { - maxRowHeight = Math.max(maxRowHeight, currentRowHeight); - currentRowHeight = 1; - barcodeRow = codeword.getRowNumber(); - } else if (codeword.getRowNumber() >= barcodeMetadata.getRowCount()) { - codewords[codewordsRow] = null; - } else { - barcodeRow = codeword.getRowNumber(); - currentRowHeight = 1; - } - } - //return (int) (averageRowHeight + 0.5); - } - - BarcodeMetadata getBarcodeMetadata() { - Codeword[] codewords = getCodewords(); - BarcodeValue barcodeColumnCount = new BarcodeValue(); - BarcodeValue barcodeRowCountUpperPart = new BarcodeValue(); - BarcodeValue barcodeRowCountLowerPart = new BarcodeValue(); - BarcodeValue barcodeECLevel = new BarcodeValue(); - for (Codeword codeword : codewords) { - if (codeword == null) { - continue; - } - codeword.setRowNumberAsRowIndicatorColumn(); - int rowIndicatorValue = codeword.getValue() % 30; - int codewordRowNumber = codeword.getRowNumber(); - if (!isLeft) { - codewordRowNumber += 2; - } - switch (codewordRowNumber % 3) { - case 0: - barcodeRowCountUpperPart.setValue(rowIndicatorValue * 3 + 1); - break; - case 1: - barcodeECLevel.setValue(rowIndicatorValue / 3); - barcodeRowCountLowerPart.setValue(rowIndicatorValue % 3); - break; - case 2: - barcodeColumnCount.setValue(rowIndicatorValue + 1); - break; - } - } - // Maybe we should check if we have ambiguous values? - if ((barcodeColumnCount.getValue().length == 0) || - (barcodeRowCountUpperPart.getValue().length == 0) || - (barcodeRowCountLowerPart.getValue().length == 0) || - (barcodeECLevel.getValue().length == 0) || - barcodeColumnCount.getValue()[0] < 1 || - barcodeRowCountUpperPart.getValue()[0] + barcodeRowCountLowerPart.getValue()[0] < - PDF417Common.MIN_ROWS_IN_BARCODE || - barcodeRowCountUpperPart.getValue()[0] + barcodeRowCountLowerPart.getValue()[0] > - PDF417Common.MAX_ROWS_IN_BARCODE) { - return null; - } - BarcodeMetadata barcodeMetadata = new BarcodeMetadata(barcodeColumnCount.getValue()[0], - barcodeRowCountUpperPart.getValue()[0], barcodeRowCountLowerPart.getValue()[0], barcodeECLevel.getValue()[0]); - removeIncorrectCodewords(codewords, barcodeMetadata); - return barcodeMetadata; - } - - private void removeIncorrectCodewords(Codeword[] codewords, BarcodeMetadata barcodeMetadata) { - // Remove codewords which do not match the metadata - // TODO Maybe we should keep the incorrect codewords for the start and end positions? - for (int codewordRow = 0; codewordRow < codewords.length; codewordRow++) { - Codeword codeword = codewords[codewordRow]; - if (codewords[codewordRow] == null) { - continue; - } - int rowIndicatorValue = codeword.getValue() % 30; - int codewordRowNumber = codeword.getRowNumber(); - if (codewordRowNumber > barcodeMetadata.getRowCount()) { - codewords[codewordRow] = null; - continue; - } - if (!isLeft) { - codewordRowNumber += 2; - } - switch (codewordRowNumber % 3) { - case 0: - if (rowIndicatorValue * 3 + 1 != barcodeMetadata.getRowCountUpperPart()) { - codewords[codewordRow] = null; - } - break; - case 1: - if (rowIndicatorValue / 3 != barcodeMetadata.getErrorCorrectionLevel() || - rowIndicatorValue % 3 != barcodeMetadata.getRowCountLowerPart()) { - codewords[codewordRow] = null; - } - break; - case 2: - if (rowIndicatorValue + 1 != barcodeMetadata.getColumnCount()) { - codewords[codewordRow] = null; - } - break; - } - } - } - - boolean isLeft() { - return isLeft; - } - - @Override - public String toString() { - return "IsLeft: " + isLeft + '\n' + super.toString(); - } - -} diff --git a/src/pdf417/decoder/barcode_metadata.rs b/src/pdf417/decoder/barcode_metadata.rs index 4b03d9b..f718877 100644 --- a/src/pdf417/decoder/barcode_metadata.rs +++ b/src/pdf417/decoder/barcode_metadata.rs @@ -17,6 +17,7 @@ /** * @author Guenther Grau */ +#[derive(Clone, Copy)] pub struct BarcodeMetadata { columnCount: u32, errorCorrectionLevel: u32, diff --git a/src/pdf417/decoder/detection_result.rs b/src/pdf417/decoder/detection_result.rs new file mode 100644 index 0000000..bbef729 --- /dev/null +++ b/src/pdf417/decoder/detection_result.rs @@ -0,0 +1,564 @@ +/* + * 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::fmt::Display; + +use crate::pdf417::pdf_417_common; + +use super::{BarcodeMetadata, BoundingBox, Codeword, DetectionRXingResultColumn}; + +const ADJUST_ROW_NUMBER_SKIP: u32 = 2; + +/** + * @author Guenther Grau + */ +pub struct DetectionRXingResult<'a> { + barcodeMetadata: BarcodeMetadata, + detectionRXingResultColumns: Vec>>, + boundingBox: BoundingBox<'a>, + barcodeColumnCount: usize, +} + +impl<'a> DetectionRXingResult<'_> { + pub fn new( + barcodeMetadata: BarcodeMetadata, + boundingBox: BoundingBox<'a>, + ) -> DetectionRXingResult<'a> { + DetectionRXingResult { + barcodeColumnCount: barcodeMetadata.getColumnCount() as usize, + detectionRXingResultColumns: vec![None; barcodeMetadata.getColumnCount() as usize + 2], + barcodeMetadata, + boundingBox, + } + // this.barcodeMetadata = barcodeMetadata; + // this.barcodeColumnCount = barcodeMetadata.getColumnCount(); + // this.boundingBox = boundingBox; + // detectionRXingResultColumns = new DetectionRXingResultColumn[barcodeColumnCount + 2]; + } + + pub fn getDetectionRXingResultColumns(&mut self) -> &Vec> { + self.adjustIndicatorColumnRowNumbers(&self.detectionRXingResultColumns[0]); + self.adjustIndicatorColumnRowNumbers( + &self.detectionRXingResultColumns[self.barcodeColumnCount + 1], + ); + let mut unadjustedCodewordCount = pdf_417_common::MAX_CODEWORDS_IN_BARCODE; + let mut previousUnadjustedCount; + loop { + previousUnadjustedCount = unadjustedCodewordCount; + unadjustedCodewordCount = self.adjustRowNumbers(); + if !(unadjustedCodewordCount > 0 && unadjustedCodewordCount < previousUnadjustedCount) { + break; + } + } //while (unadjustedCodewordCount > 0 && unadjustedCodewordCount < previousUnadjustedCount); + &self.detectionRXingResultColumns + } + + fn adjustIndicatorColumnRowNumbers( + &self, + detectionRXingResultColumn: &Option, + ) { + if let Some(col) = detectionRXingResultColumn { + // if (detectionRXingResultColumn != null) { + // ((DetectionRXingResultRowIndicatorColumn) detectionRXingResultColumn) + // .adjustCompleteIndicatorColumnRowNumbers(barcodeMetadata); + // } + col.as_row_indicator() + .adjustCompleteIndicatorColumnRowNumbers(&self.barcodeMetadata); + } + } + + // TODO ensure that no detected codewords with unknown row number are left + // we should be able to estimate the row height and use it as a hint for the row number + // we should also fill the rows top to bottom and bottom to top + /** + * @return number of codewords which don't have a valid row number. Note that the count is not accurate as codewords + * will be counted several times. It just serves as an indicator to see when we can stop adjusting row numbers + */ + fn adjustRowNumbers(&mut self) -> u32 { + let unadjustedCount = self.adjustRowNumbersByRow(); + if unadjustedCount == 0 { + return 0; + } + for barcodeColumn in 1..(self.barcodeColumnCount + 1) { + // for (int barcodeColumn = 1; barcodeColumn < barcodeColumnCount + 1; barcodeColumn++) { + if self.detectionRXingResultColumns[barcodeColumn].is_some() { + let codewords = self.detectionRXingResultColumns[barcodeColumn] + .as_ref() + .unwrap() + .getCodewords(); + for codewordsRow in 0..codewords.len() { + // for (int codewordsRow = 0; codewordsRow < codewords.length; codewordsRow++) { + if let Some(cw_row) = codewords[codewordsRow] { + if !cw_row.hasValidRowNumber() { + self.adjustRowNumbersWithCodewords( + barcodeColumn, + codewordsRow, + codewords, + ); + } + } else { + continue; + } + // if (codewords[codewordsRow] == null) { + // continue; + // } + // if (!codewords[codewordsRow].hasValidRowNumber()) { + // self.adjustRowNumbers(barcodeColumn, codewordsRow, codewords); + // } + } + } + } + return unadjustedCount; + } + + fn adjustRowNumbersByRow(&mut self) -> u32 { + self.adjustRowNumbersFromBothRI(); + // TODO we should only do full row adjustments if row numbers of left and right row indicator column match. + // Maybe it's even better to calculated the height (in codeword rows) and divide it by the number of barcode + // rows. This, together with the LRI and RRI row numbers should allow us to get a good estimate where a row + // number starts and ends. + let unadjustedCount = self.adjustRowNumbersFromLRI(); + unadjustedCount + self.adjustRowNumbersFromRRI() + } + + fn adjustRowNumbersFromBothRI(&mut self) { + if self.detectionRXingResultColumns[0].is_some() + && self.detectionRXingResultColumns[self.barcodeColumnCount as usize + 1].is_some() + { + // let LRIcodewords = self.detectionRXingResultColumns[0].as_ref().unwrap().getCodewords(); + // let RRIcodewords = self.detectionRXingResultColumns[self.barcodeColumnCount as usize + 1].as_ref().unwrap().getCodewords(); + for codewordsRow in 0..self.detectionRXingResultColumns[0] + .as_ref() + .unwrap() + .getCodewords() + .len() + { + // for (int codewordsRow = 0; codewordsRow < LRIcodewords.length; codewordsRow++) { + if + //let (Some(lricw), Some(rricw)) = + self.detectionRXingResultColumns[0] + .as_ref() + .unwrap() + .getCodewords()[codewordsRow] + .is_some() + && self.detectionRXingResultColumns[self.barcodeColumnCount as usize + 1] + .as_ref() + .unwrap() + .getCodewords()[codewordsRow] + .is_some() + { + if self.detectionRXingResultColumns[0] + .as_ref() + .unwrap() + .getCodewords()[codewordsRow] + .as_ref() + .unwrap() + .getRowNumber() + == self.detectionRXingResultColumns[self.barcodeColumnCount as usize + 1] + .as_ref() + .unwrap() + .getCodewords()[codewordsRow] + .as_ref() + .unwrap() + .getRowNumber() + { + // if (LRIcodewords[codewordsRow] != null && + // RRIcodewords[codewordsRow] != null && + // LRIcodewords[codewordsRow].getRowNumber() == RRIcodewords[codewordsRow].getRowNumber()) { + for barcodeColumn in 1..=self.barcodeColumnCount { + // for (int barcodeColumn = 1; barcodeColumn <= barcodeColumnCount; barcodeColumn++) { + if self.detectionRXingResultColumns[barcodeColumn].is_some() + //let Some(dc_col) = + //&mut self.detectionRXingResultColumns[barcodeColumn] + { + if self.detectionRXingResultColumns[barcodeColumn] + .as_mut() + .unwrap() + .getCodewordsMut()[codewordsRow] + .is_some() + { + //let Some(codeword) = &mut self.detectionRXingResultColumns[barcodeColumn].as_mut().unwrap().getCodewordsMut()[codewordsRow] { + let new_row_number = self.detectionRXingResultColumns[0] + .as_ref() + .unwrap() + .getCodewords()[codewordsRow] + .as_ref() + .unwrap() + .getRowNumber(); + self.detectionRXingResultColumns[barcodeColumn] + .as_mut() + .unwrap() + .getCodewordsMut()[codewordsRow] + .as_mut() + .unwrap() + .setRowNumber(new_row_number); + if !self.detectionRXingResultColumns[barcodeColumn] + .as_mut() + .unwrap() + .getCodewordsMut()[codewordsRow] + .as_ref() + .unwrap() + .hasValidRowNumber() + { + // self.detectionRXingResultColumns[barcodeColumn].getCodewords()[codewordsRow] = None; + self.detectionRXingResultColumns[barcodeColumn] + .as_mut() + .unwrap() + .getCodewordsMut()[codewordsRow] = None; + } + } else { + continue; + } + } else { + continue; + } + // let codeword = self.detectionRXingResultColumns[barcodeColumn].getCodewords()[codewordsRow]; + // if (codeword == null) { + // continue; + // } + } + } + } + } + } + // if (detectionRXingResultColumns[0] == null || detectionRXingResultColumns[barcodeColumnCount + 1] == null) { + // return; + // } + } + + fn adjustRowNumbersFromRRI(&self) -> u32 { + if let Some(col) = &self.detectionRXingResultColumns[self.barcodeColumnCount as usize + 1] { + let mut unadjustedCount = 0; + let codewords = col.getCodewords(); + for codewordsRow in 0..codewords.len() { + // for (int codewordsRow = 0; codewordsRow < codewords.length; codewordsRow++) { + if let Some(codeword_col) = codewords[codewordsRow] { + let rowIndicatorRowNumber = codeword_col.getRowNumber(); + let mut invalidRowCounts = 0; + let mut barcodeColumn = self.barcodeColumnCount as usize + 1; + while barcodeColumn > 0 && invalidRowCounts < ADJUST_ROW_NUMBER_SKIP { + // for (int barcodeColumn = barcodeColumnCount + 1; + // barcodeColumn > 0 && invalidRowCounts < ADJUST_ROW_NUMBER_SKIP; + // barcodeColumn--) { + if let Some(bc_col) = &self.detectionRXingResultColumns[barcodeColumn] { + if let Some(codeword) = bc_col.getCodewords()[codewordsRow] { + invalidRowCounts = Self::adjustRowNumberIfValid( + rowIndicatorRowNumber, + invalidRowCounts, + &mut Some(codeword), + ); + if !codeword.hasValidRowNumber() { + unadjustedCount += 1; + } + } + } + barcodeColumn -= 1; + } + } else { + continue; + } + } + unadjustedCount + } else { + 0 + } + // if (detectionRXingResultColumns[barcodeColumnCount + 1] == null) { + // return 0; + // } + // int unadjustedCount = 0; + // Codeword[] codewords = detectionRXingResultColumns[barcodeColumnCount + 1].getCodewords(); + // for (int codewordsRow = 0; codewordsRow < codewords.length; codewordsRow++) { + // if (codewords[codewordsRow] == null) { + // continue; + // } + // int rowIndicatorRowNumber = codewords[codewordsRow].getRowNumber(); + // int invalidRowCounts = 0; + // for (int barcodeColumn = barcodeColumnCount + 1; + // barcodeColumn > 0 && invalidRowCounts < ADJUST_ROW_NUMBER_SKIP; + // barcodeColumn--) { + // Codeword codeword = detectionRXingResultColumns[barcodeColumn].getCodewords()[codewordsRow]; + // if (codeword != null) { + // invalidRowCounts = adjustRowNumberIfValid(rowIndicatorRowNumber, invalidRowCounts, codeword); + // if (!codeword.hasValidRowNumber()) { + // unadjustedCount++; + // } + // } + // } + // } + // return unadjustedCount; + } + + fn adjustRowNumbersFromLRI(&self) -> u32 { + if let Some(col) = &self.detectionRXingResultColumns[0] { + let mut unadjustedCount = 0; + let codewords = col.getCodewords(); + for codewordsRow in 0..codewords.len() { + // for (int codewordsRow = 0; codewordsRow < codewords.length; codewordsRow++) { + if let Some(codeword_in_row) = codewords[codewordsRow] { + let rowIndicatorRowNumber = codeword_in_row.getRowNumber(); + let mut invalidRowCounts = 0; + let mut barcodeColumn = 1_usize; + while barcodeColumn < self.barcodeColumnCount as usize + 1 + && invalidRowCounts < ADJUST_ROW_NUMBER_SKIP + { + // for (int barcodeColumn = 1; + // barcodeColumn < barcodeColumnCount + 1 && invalidRowCounts < ADJUST_ROW_NUMBER_SKIP; + // barcodeColumn++) { + if let Some(bc_column) = &self.detectionRXingResultColumns[barcodeColumn] { + if let Some(codeword) = bc_column.getCodewords()[codewordsRow] { + invalidRowCounts = Self::adjustRowNumberIfValid( + rowIndicatorRowNumber, + invalidRowCounts, + &mut Some(codeword), + ); + if !codeword.hasValidRowNumber() { + unadjustedCount += 1; + } + } + } + // let codeword = self.detectionRXingResultColumns[barcodeColumn].getCodewords()[codewordsRow]; + // if (codeword != null) { + // invalidRowCounts = adjustRowNumberIfValid(rowIndicatorRowNumber, invalidRowCounts, codeword); + // if (!codeword.hasValidRowNumber()) { + // unadjustedCount+=1; + // } + // } + barcodeColumn += 1; + } + } else { + continue; + } + // if (codewords[codewordsRow] == null) { + // continue; + // } + // let rowIndicatorRowNumber = codewords[codewordsRow].getRowNumber(); + // let invalidRowCounts = 0; + // for (int barcodeColumn = 1; + // barcodeColumn < barcodeColumnCount + 1 && invalidRowCounts < ADJUST_ROW_NUMBER_SKIP; + // barcodeColumn++) { + // Codeword codeword = detectionRXingResultColumns[barcodeColumn].getCodewords()[codewordsRow]; + // if (codeword != null) { + // invalidRowCounts = adjustRowNumberIfValid(rowIndicatorRowNumber, invalidRowCounts, codeword); + // if (!codeword.hasValidRowNumber()) { + // unadjustedCount++; + // } + // } + // } + } + unadjustedCount + } else { + 0 + } + + // if (detectionRXingResultColumns[0] == null) { + // return 0; + // } + // int unadjustedCount = 0; + // Codeword[] codewords = detectionRXingResultColumns[0].getCodewords(); + // for (int codewordsRow = 0; codewordsRow < codewords.length; codewordsRow++) { + // if (codewords[codewordsRow] == null) { + // continue; + // } + // int rowIndicatorRowNumber = codewords[codewordsRow].getRowNumber(); + // int invalidRowCounts = 0; + // for (int barcodeColumn = 1; + // barcodeColumn < barcodeColumnCount + 1 && invalidRowCounts < ADJUST_ROW_NUMBER_SKIP; + // barcodeColumn++) { + // Codeword codeword = detectionRXingResultColumns[barcodeColumn].getCodewords()[codewordsRow]; + // if (codeword != null) { + // invalidRowCounts = adjustRowNumberIfValid(rowIndicatorRowNumber, invalidRowCounts, codeword); + // if (!codeword.hasValidRowNumber()) { + // unadjustedCount++; + // } + // } + // } + // } + // return unadjustedCount; + } + + fn adjustRowNumberIfValid( + rowIndicatorRowNumber: i32, + mut invalidRowCounts: u32, + codeword: &mut Option, + ) -> u32 { + if let Some(codeword) = codeword { + if !codeword.hasValidRowNumber() { + if codeword.isValidRowNumber(rowIndicatorRowNumber) { + codeword.setRowNumber(rowIndicatorRowNumber); + invalidRowCounts = 0; + } else { + invalidRowCounts += 1; + } + } + invalidRowCounts + } else { + invalidRowCounts + } + // if (codeword == null) { + // return invalidRowCounts; + // } + // if (!codeword.hasValidRowNumber()) { + // if (codeword.isValidRowNumber(rowIndicatorRowNumber)) { + // codeword.setRowNumber(rowIndicatorRowNumber); + // invalidRowCounts = 0; + // } else { + // invalidRowCounts+=1; + // } + // } + // return invalidRowCounts; + } + + fn adjustRowNumbersWithCodewords( + &self, + barcodeColumn: usize, + codewordsRow: usize, + codewords: &[Option], + ) { + let mut codeword = codewords[codewordsRow]; + let previousColumnCodewords = self.detectionRXingResultColumns[barcodeColumn - 1] + .as_ref() + .unwrap() + .getCodewords(); + let mut nextColumnCodewords = previousColumnCodewords; + if self.detectionRXingResultColumns[barcodeColumn + 1].is_some() { + nextColumnCodewords = self.detectionRXingResultColumns[barcodeColumn + 1] + .as_ref() + .unwrap() + .getCodewords(); //col.getCodewords(); + } + // if (self.detectionRXingResultColumns[barcodeColumn + 1] != null) { + // nextColumnCodewords = self.detectionRXingResultColumns[barcodeColumn + 1].getCodewords(); + // } + + let mut otherCodewords = [None; 14]; // new Codeword[14]; + + otherCodewords[2] = previousColumnCodewords[codewordsRow]; + otherCodewords[3] = nextColumnCodewords[codewordsRow]; + + if codewordsRow > 0 { + otherCodewords[0] = codewords[codewordsRow - 1]; + otherCodewords[4] = previousColumnCodewords[codewordsRow - 1]; + otherCodewords[5] = nextColumnCodewords[codewordsRow - 1]; + } + if codewordsRow > 1 { + otherCodewords[8] = codewords[codewordsRow - 2]; + otherCodewords[10] = previousColumnCodewords[codewordsRow - 2]; + otherCodewords[11] = nextColumnCodewords[codewordsRow - 2]; + } + if codewordsRow < codewords.len() - 1 { + otherCodewords[1] = codewords[codewordsRow + 1]; + otherCodewords[6] = previousColumnCodewords[codewordsRow + 1]; + otherCodewords[7] = nextColumnCodewords[codewordsRow + 1]; + } + if codewordsRow < codewords.len() - 2 { + otherCodewords[9] = codewords[codewordsRow + 2]; + otherCodewords[12] = previousColumnCodewords[codewordsRow + 2]; + otherCodewords[13] = nextColumnCodewords[codewordsRow + 2]; + } + for otherCodeword in otherCodewords { + if Self::adjustRowNumber(codeword.as_mut().unwrap(), &otherCodeword) { + return; + } + } + // for (Codeword otherCodeword : otherCodewords) { + // if (adjustRowNumber(codeword, otherCodeword)) { + // return; + // } + // } + } + + /** + * @return true, if row number was adjusted, false otherwise + */ + fn adjustRowNumber(codeword: &mut Codeword, otherCodeword: &Option) -> bool { + if let Some(otherCodeword) = otherCodeword { + if otherCodeword.hasValidRowNumber() + && otherCodeword.getBucket() == codeword.getBucket() + { + codeword.setRowNumber(otherCodeword.getRowNumber()); + true + } else { + false + } + } else { + false + } + } + + pub fn getBarcodeColumnCount(&self) -> usize { + self.barcodeColumnCount + } + + pub fn getBarcodeRowCount(&self) -> u32 { + self.barcodeMetadata.getRowCount() + } + + pub fn getBarcodeECLevel(&self) -> u32 { + self.barcodeMetadata.getErrorCorrectionLevel() + } + + // pub fn setBoundingBox(&'a mut self, boundingBox:BoundingBox<'a>) { + // self.boundingBox = boundingBox; + // } + + pub fn getBoundingBox(&self) -> &BoundingBox { + &self.boundingBox + } + + // pub fn setDetectionRXingResultColumn(&mut self, barcodeColumn:usize, detectionRXingResultColumn:Option>) { + // self.detectionRXingResultColumns[barcodeColumn] = detectionRXingResultColumn; + // } + + pub fn getDetectionRXingResultColumn( + &self, + barcodeColumn: usize, + ) -> &Option { + &self.detectionRXingResultColumns[barcodeColumn] + } +} + +impl Display for DetectionRXingResult<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + todo!() + } +} + +// @Override +// public String toString() { +// DetectionRXingResultColumn rowIndicatorColumn = detectionRXingResultColumns[0]; +// if (rowIndicatorColumn == null) { +// rowIndicatorColumn = detectionRXingResultColumns[barcodeColumnCount + 1]; +// } +// try (Formatter formatter = new Formatter()) { +// for (int codewordsRow = 0; codewordsRow < rowIndicatorColumn.getCodewords().length; codewordsRow++) { +// formatter.format("CW %3d:", codewordsRow); +// for (int barcodeColumn = 0; barcodeColumn < barcodeColumnCount + 2; barcodeColumn++) { +// if (detectionRXingResultColumns[barcodeColumn] == null) { +// formatter.format(" | "); +// continue; +// } +// Codeword codeword = detectionRXingResultColumns[barcodeColumn].getCodewords()[codewordsRow]; +// if (codeword == null) { +// formatter.format(" | "); +// continue; +// } +// formatter.format(" %3d|%3d", codeword.getRowNumber(), codeword.getValue()); +// } +// formatter.format("%n"); +// } +// return formatter.toString(); +// } +// } diff --git a/src/pdf417/decoder/detection_result_column.rs b/src/pdf417/decoder/detection_result_column.rs index a78e1be..5aa71bf 100644 --- a/src/pdf417/decoder/detection_result_column.rs +++ b/src/pdf417/decoder/detection_result_column.rs @@ -16,13 +16,14 @@ use std::fmt::Display; -use super::{BoundingBox, Codeword}; +use super::{BoundingBox, Codeword, DetectionRXingResultRowIndicatorColumn}; const MAX_NEARBY_DISTANCE: u32 = 5; /** * @author Guenther Grau */ +#[derive(Clone)] pub struct DetectionRXingResultColumn<'a> { boundingBox: BoundingBox<'a>, codewords: Vec>, @@ -83,6 +84,12 @@ impl<'a> DetectionRXingResultColumn<'_> { pub fn getCodewords(&self) -> &[Option] { &self.codewords } + pub(super) fn getCodewordsMut(&mut self) -> &mut [Option] { + &mut self.codewords + } + pub fn as_row_indicator(&self) -> DetectionRXingResultRowIndicatorColumn { + DetectionRXingResultRowIndicatorColumn::new(&self.boundingBox, false) + } } impl Display for DetectionRXingResultColumn<'_> { diff --git a/src/pdf417/decoder/detection_result_row_indicator_column.rs b/src/pdf417/decoder/detection_result_row_indicator_column.rs new file mode 100644 index 0000000..93437dd --- /dev/null +++ b/src/pdf417/decoder/detection_result_row_indicator_column.rs @@ -0,0 +1,311 @@ +/* + * 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::fmt::Display; + +use crate::{pdf417::pdf_417_common, ResultPoint}; + +use super::{BarcodeMetadata, BarcodeValue, BoundingBox, Codeword, DetectionRXingResultColumn}; + +/** + * @author Guenther Grau + */ +pub struct DetectionRXingResultRowIndicatorColumn<'a>(DetectionRXingResultColumn<'a>, bool); +impl<'a> DetectionRXingResultRowIndicatorColumn<'_> { + // private final boolean isLeft; + + pub fn new( + boundingBox: &'a BoundingBox, + isLeft: bool, + ) -> DetectionRXingResultRowIndicatorColumn<'a> { + DetectionRXingResultRowIndicatorColumn(DetectionRXingResultColumn::new(boundingBox), isLeft) + } + + fn setRowNumbers(&mut self) { + for codeword_opt in self.0.getCodewordsMut() { + // for (Codeword codeword : getCodewords()) { + if let Some(codeword) = codeword_opt { + // if (codeword != null) { + codeword.setRowNumberAsRowIndicatorColumn(); + } + } + } + + // TODO implement properly + // TODO maybe we should add missing codewords to store the correct row number to make + // finding row numbers for other columns easier + // use row height count to make detection of invalid row numbers more reliable + pub fn adjustCompleteIndicatorColumnRowNumbers(&mut self, barcodeMetadata: &BarcodeMetadata) { + // let codewords = self.0.getCodewordsMut(); + self.setRowNumbers(); + Self::removeIncorrectCodewords(self.0.getCodewordsMut(), barcodeMetadata, self.1); + let boundingBox = self.0.getBoundingBox(); + let top = if self.1 { + boundingBox.getTopLeft() + } else { + boundingBox.getTopRight() + }; + let bottom = if self.1 { + boundingBox.getBottomLeft() + } else { + boundingBox.getBottomRight() + }; + let firstRow = self.0.imageRowToCodewordIndex(top.getY() as u32); + let lastRow = self.0.imageRowToCodewordIndex(bottom.getY() as u32); + // We need to be careful using the average row height. Barcode could be skewed so that we have smaller and + // taller rows + //float averageRowHeight = (lastRow - firstRow) / (float) barcodeMetadata.getRowCount(); + let mut barcodeRow = -1; + let mut maxRowHeight = 1; + let mut currentRowHeight = 0; + for codewordsRow in firstRow..lastRow { + // for (int codewordsRow = firstRow; codewordsRow < lastRow; codewordsRow++) { + if let Some(codeword) = self.0.getCodewordsMut()[codewordsRow] { + // if (codewords[codewordsRow] == null) { + // continue; + // } + // let codeword = codewords[codewordsRow]; + + let rowDifference = codeword.getRowNumber() - barcodeRow; + + // TODO improve handling with case where first row indicator doesn't start with 0 + + if rowDifference == 0 { + currentRowHeight += 1; + } else if rowDifference == 1 { + maxRowHeight = maxRowHeight.max(currentRowHeight); + currentRowHeight = 1; + barcodeRow = codeword.getRowNumber(); + } else if rowDifference < 0 + || codeword.getRowNumber() >= barcodeMetadata.getRowCount() as i32 + || rowDifference > codewordsRow as i32 + { + self.0.getCodewordsMut()[codewordsRow] = None; + } else { + let checkedRows; + if maxRowHeight > 2 { + checkedRows = (maxRowHeight - 2) * rowDifference; + } else { + checkedRows = rowDifference; + } + let mut closePreviousCodewordFound = checkedRows >= codewordsRow as i32; + let mut i = 1; + while i <= checkedRows && !closePreviousCodewordFound { + // for (int i = 1; i <= checkedRows && !closePreviousCodewordFound; i++) { + // there must be (height * rowDifference) number of codewords missing. For now we assume height = 1. + // This should hopefully get rid of most problems already. + closePreviousCodewordFound = + self.0.getCodewords()[codewordsRow as usize - i as usize].is_some(); + + i += 1; + } + if closePreviousCodewordFound { + self.0.getCodewordsMut()[codewordsRow] = None; + } else { + barcodeRow = codeword.getRowNumber(); + currentRowHeight = 1; + } + } + } else { + continue; + } + } + //return (int) (averageRowHeight + 0.5); + } + + pub fn getRowHeights(&mut self) -> Option> { + if let Some(barcodeMetadata) = self.getBarcodeMetadata() { + self.adjustIncompleteIndicatorColumnRowNumbers(&barcodeMetadata); + let mut result = vec![0; barcodeMetadata.getRowCount() as usize]; + for codeword_opt in self.0.getCodewords() { + // for (Codeword codeword : getCodewords()) { + if let Some(codeword) = codeword_opt { + let rowNumber = codeword.getRowNumber(); + if rowNumber as usize >= result.len() { + // We have more rows than the barcode metadata allows for, ignore them. + continue; + } + result[rowNumber as usize] += 1; + } + // else throw exception? + else { + continue; + } + } + Some(result) + } else { + None + } + } + + // TODO maybe we should add missing codewords to store the correct row number to make + // finding row numbers for other columns easier + // use row height count to make detection of invalid row numbers more reliable + fn adjustIncompleteIndicatorColumnRowNumbers(&mut self, barcodeMetadata: &BarcodeMetadata) { + let boundingBox = self.0.getBoundingBox(); + let top = if self.1 { + boundingBox.getTopLeft() + } else { + boundingBox.getTopRight() + }; + let bottom = if self.1 { + boundingBox.getBottomLeft() + } else { + boundingBox.getBottomRight() + }; + let firstRow = self.0.imageRowToCodewordIndex(top.getY() as u32); + let lastRow = self.0.imageRowToCodewordIndex(bottom.getY() as u32); + //float averageRowHeight = (lastRow - firstRow) / (float) barcodeMetadata.getRowCount(); + let codewords = self.0.getCodewordsMut(); + let mut barcodeRow = -1; + let mut maxRowHeight = 1; + let mut currentRowHeight = 0; + for codewordsRow in firstRow..lastRow { + // for (int codewordsRow = firstRow; codewordsRow < lastRow; codewordsRow++) { + + if let Some(codeword) = &mut codewords[codewordsRow] { + codeword.setRowNumberAsRowIndicatorColumn(); + + let rowDifference = codeword.getRowNumber() - barcodeRow; + + // TODO improve handling with case where first row indicator doesn't start with 0 + + if rowDifference == 0 { + currentRowHeight += 1; + } else if rowDifference == 1 { + maxRowHeight = maxRowHeight.max(currentRowHeight); + currentRowHeight = 1; + barcodeRow = codeword.getRowNumber(); + } else if codeword.getRowNumber() >= barcodeMetadata.getRowCount() as i32 { + codewords[codewordsRow] = None; + } else { + barcodeRow = codeword.getRowNumber(); + currentRowHeight = 1; + } + } else { + continue; + } + } + //return (int) (averageRowHeight + 0.5); + } + + pub fn getBarcodeMetadata(&mut self) -> Option { + let codewords = self.0.getCodewordsMut(); + let mut barcodeColumnCount = BarcodeValue::new(); + let mut barcodeRowCountUpperPart = BarcodeValue::new(); + let mut barcodeRowCountLowerPart = BarcodeValue::new(); + let mut barcodeECLevel = BarcodeValue::new(); + for codeword_opt in codewords.iter_mut() { + // for (Codeword codeword : codewords) { + if let Some(codeword) = codeword_opt { + codeword.setRowNumberAsRowIndicatorColumn(); + let rowIndicatorValue = codeword.getValue() % 30; + let mut codewordRowNumber = codeword.getRowNumber(); + if !self.1 { + codewordRowNumber += 2; + } + match codewordRowNumber % 3 { + 0 => barcodeRowCountUpperPart.setValue(rowIndicatorValue * 3 + 1), + 1 => { + barcodeECLevel.setValue(rowIndicatorValue / 3); + barcodeRowCountLowerPart.setValue(rowIndicatorValue % 3); + } + 2 => barcodeColumnCount.setValue(rowIndicatorValue + 1), + _ => {} + } + } else { + continue; + } + } + // Maybe we should check if we have ambiguous values? + if (barcodeColumnCount.getValue().len() == 0) + || (barcodeRowCountUpperPart.getValue().len() == 0) + || (barcodeRowCountLowerPart.getValue().len() == 0) + || (barcodeECLevel.getValue().len() == 0) + || barcodeColumnCount.getValue()[0] < 1 + || barcodeRowCountUpperPart.getValue()[0] + barcodeRowCountLowerPart.getValue()[0] + < pdf_417_common::MIN_ROWS_IN_BARCODE + || barcodeRowCountUpperPart.getValue()[0] + barcodeRowCountLowerPart.getValue()[0] + > pdf_417_common::MAX_ROWS_IN_BARCODE + { + return None; + } + let barcodeMetadata = BarcodeMetadata::new( + barcodeColumnCount.getValue()[0], + barcodeRowCountUpperPart.getValue()[0], + barcodeRowCountLowerPart.getValue()[0], + barcodeECLevel.getValue()[0], + ); + Self::removeIncorrectCodewords(codewords, &barcodeMetadata, self.1); + + Some(barcodeMetadata) + } + + fn removeIncorrectCodewords( + codewords: &mut [Option], + barcodeMetadata: &BarcodeMetadata, + isLeft: bool, + ) { + // Remove codewords which do not match the metadata + // TODO Maybe we should keep the incorrect codewords for the start and end positions? + for codewordRow in 0..codewords.len() { + // for (int codewordRow = 0; codewordRow < codewords.length; codewordRow++) { + if let Some(codeword) = codewords[codewordRow] { + let rowIndicatorValue = codeword.getValue() % 30; + let mut codewordRowNumber = codeword.getRowNumber(); + if codewordRowNumber > barcodeMetadata.getRowCount() as i32 { + codewords[codewordRow] = None; + continue; + } + if !isLeft { + codewordRowNumber += 2; + } + match codewordRowNumber % 3 { + 0 => { + if rowIndicatorValue * 3 + 1 != barcodeMetadata.getRowCountUpperPart() { + codewords[codewordRow] = None; + } + } + 1 => { + if rowIndicatorValue / 3 != barcodeMetadata.getErrorCorrectionLevel() + || rowIndicatorValue % 3 != barcodeMetadata.getRowCountLowerPart() + { + codewords[codewordRow] = None; + } + } + 2 => { + if rowIndicatorValue + 1 != barcodeMetadata.getColumnCount() { + codewords[codewordRow] = None; + } + } + _ => {} + } + } else { + continue; + } + } + } + + pub fn isLeft(&self) -> bool { + self.1 + } +} + +impl Display for DetectionRXingResultRowIndicatorColumn<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "IsLeft: {} \n {}", self.1, self.0) + } +} diff --git a/src/pdf417/decoder/ec/error_correction.rs b/src/pdf417/decoder/ec/error_correction.rs index 775f93d..e3f859b 100644 --- a/src/pdf417/decoder/ec/error_correction.rs +++ b/src/pdf417/decoder/ec/error_correction.rs @@ -124,7 +124,7 @@ fn runEuclideanAlgorithm( let mut a = a; let mut b = b; if a.getDegree() < b.getDegree() { - std::mem::swap(&mut a,&mut b); + std::mem::swap(&mut a, &mut b); } let mut rLast = a; diff --git a/src/pdf417/decoder/mod.rs b/src/pdf417/decoder/mod.rs index 3322731..8292a2d 100644 --- a/src/pdf417/decoder/mod.rs +++ b/src/pdf417/decoder/mod.rs @@ -16,3 +16,11 @@ mod detection_result_column; pub use detection_result_column::*; pub mod pdf_417_codeword_decoder; + +mod detection_result_row_indicator_column; +pub use detection_result_row_indicator_column::*; + +mod detection_result; +pub use detection_result::*; + +// pub mod pdf_417_scanning_decoder; \ No newline at end of file diff --git a/src/pdf417/decoder/PDF417ScanningDecoder.java b/src/pdf417/decoder/pdf_417_scanning_decoder.rs similarity index 81% rename from src/pdf417/decoder/PDF417ScanningDecoder.java rename to src/pdf417/decoder/pdf_417_scanning_decoder.rs index 70796c9..4cf4700 100644 --- a/src/pdf417/decoder/PDF417ScanningDecoder.java +++ b/src/pdf417/decoder/pdf_417_scanning_decoder.rs @@ -14,49 +14,32 @@ * limitations under the License. */ -package com.google.zxing.pdf417.decoder; +use crate::Exceptions; -import com.google.zxing.ChecksumException; -import com.google.zxing.FormatException; -import com.google.zxing.NotFoundException; -import com.google.zxing.RXingResultPoint; -import com.google.zxing.common.BitMatrix; -import com.google.zxing.common.DecoderRXingResult; -import com.google.zxing.common.detector.MathUtils; -import com.google.zxing.pdf417.PDF417Common; -import com.google.zxing.pdf417.decoder.ec.ErrorCorrection; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Formatter; -import java.util.List; /** * @author Guenther Grau */ -public final class PDF417ScanningDecoder { - private static final int CODEWORD_SKEW_SIZE = 2; + const CODEWORD_SKEW_SIZE :u32= 2; - private static final int MAX_ERRORS = 3; - private static final int MAX_EC_CODEWORDS = 512; - private static final ErrorCorrection errorCorrection = new ErrorCorrection(); + const MAX_ERRORS :u32= 3; + const MAX_EC_CODEWORDS:u32 = 512; + // const errorCorrection:ErrorCorrection = ErrorCorrection::new(); - private PDF417ScanningDecoder() { - } // 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. - public static DecoderRXingResult decode(BitMatrix image, - RXingResultPoint imageTopLeft, - RXingResultPoint imageBottomLeft, - RXingResultPoint imageTopRight, - RXingResultPoint imageBottomRight, - int minCodewordWidth, - int maxCodewordWidth) - throws NotFoundException, FormatException, ChecksumException { + pub fn decode( image:&BitMatrix, + imageTopLeft:RXingResultPoint, + imageBottomLeft:RXingResultPoint, + imageTopRight:RXingResultPoint, + imageBottomRight:RXingResultPoint, + minCodewordWidth:u32, + maxCodewordWidth:u32) + -> Result { BoundingBox boundingBox = new BoundingBox(image, imageTopLeft, imageBottomLeft, imageTopRight, imageBottomRight); DetectionRXingResultRowIndicatorColumn leftRowIndicatorColumn = null; DetectionRXingResultRowIndicatorColumn rightRowIndicatorColumn = null; @@ -125,9 +108,9 @@ public final class PDF417ScanningDecoder { return createDecoderRXingResult(detectionRXingResult); } - private static DetectionRXingResult merge(DetectionRXingResultRowIndicatorColumn leftRowIndicatorColumn, - DetectionRXingResultRowIndicatorColumn rightRowIndicatorColumn) - throws NotFoundException { + fn merge( leftRowIndicatorColumn:&DetectionRXingResultRowIndicatorColumn, + rightRowIndicatorColumn:&DetectionRXingResultRowIndicatorColumn) + -> Result { if (leftRowIndicatorColumn == null && rightRowIndicatorColumn == null) { return null; } @@ -140,8 +123,8 @@ public final class PDF417ScanningDecoder { return new DetectionRXingResult(barcodeMetadata, boundingBox); } - private static BoundingBox adjustBoundingBox(DetectionRXingResultRowIndicatorColumn rowIndicatorColumn) - throws NotFoundException { + fn adjustBoundingBox( rowIndicatorColumn:&DetectionRXingResultRowIndicatorColumn) + -> Result { if (rowIndicatorColumn == null) { return null; } @@ -175,7 +158,7 @@ public final class PDF417ScanningDecoder { rowIndicatorColumn.isLeft()); } - private static int getMax(int[] values) { + fn getMax( values:&[u32]) -> u32{ int maxValue = -1; for (int value : values) { maxValue = Math.max(maxValue, value); @@ -183,8 +166,8 @@ public final class PDF417ScanningDecoder { return maxValue; } - private static BarcodeMetadata getBarcodeMetadata(DetectionRXingResultRowIndicatorColumn leftRowIndicatorColumn, - DetectionRXingResultRowIndicatorColumn rightRowIndicatorColumn) { + fn getBarcodeMetadata( leftRowIndicatorColumn:&DetectionRXingResultRowIndicatorColumn, + rightRowIndicatorColumn:&DetectionRXingResultRowIndicatorColumn) -> BarcodeMetadata{ BarcodeMetadata leftBarcodeMetadata; if (leftRowIndicatorColumn == null || (leftBarcodeMetadata = leftRowIndicatorColumn.getBarcodeMetadata()) == null) { @@ -204,12 +187,12 @@ public final class PDF417ScanningDecoder { return leftBarcodeMetadata; } - private static DetectionRXingResultRowIndicatorColumn getRowIndicatorColumn(BitMatrix image, - BoundingBox boundingBox, - RXingResultPoint startPoint, - boolean leftToRight, - int minCodewordWidth, - int maxCodewordWidth) { + fn getRowIndicatorColumn( image:&BitMatrix, + boundingBox:&BoundingBox, + startPoint:&RXingResultPoint, + leftToRight:u32, + minCodewordWidth:u32, + maxCodewordWidth:u32)-> DetectionRXingResultRowIndicatorColumn { DetectionRXingResultRowIndicatorColumn rowIndicatorColumn = new DetectionRXingResultRowIndicatorColumn(boundingBox, leftToRight); for (int i = 0; i < 2; i++) { @@ -232,7 +215,7 @@ public final class PDF417ScanningDecoder { return rowIndicatorColumn; } - private static void adjustCodewordCount(DetectionRXingResult detectionRXingResult, BarcodeValue[][] barcodeMatrix) + fn adjustCodewordCount( detectionRXingResult:&DetectionRXingResult, barcodeMatrix:&Vec>) throws NotFoundException { BarcodeValue barcodeMatrix01 = barcodeMatrix[0][1]; int[] numberOfCodewords = barcodeMatrix01.getValue(); @@ -252,8 +235,7 @@ public final class PDF417ScanningDecoder { } } - private static DecoderRXingResult createDecoderRXingResult(DetectionRXingResult detectionRXingResult) throws FormatException, - ChecksumException, NotFoundException { + fn createDecoderRXingResult( detectionRXingResult:&DetectionRXingResult) -> Result { BarcodeValue[][] barcodeMatrix = createBarcodeMatrix(detectionRXingResult); adjustCodewordCount(detectionRXingResult, barcodeMatrix); Collection erasures = new ArrayList<>(); @@ -295,12 +277,12 @@ public final class PDF417ScanningDecoder { * @param ambiguousIndexValues two dimensional array that contains the ambiguous values. The first dimension must * be the same length as the ambiguousIndexes array */ - private static DecoderRXingResult createDecoderRXingResultFromAmbiguousValues(int ecLevel, - int[] codewords, - int[] erasureArray, - int[] ambiguousIndexes, - int[][] ambiguousIndexValues) - throws FormatException, ChecksumException { + fn createDecoderRXingResultFromAmbiguousValues( ecLevel:u21, + codewords:&[u32], + erasureArray&[u32], + ambiguousIndexes&[u32], + ambiguousIndexValues:&Vec>) + -> Result { int[] ambiguousIndexCount = new int[ambiguousIndexes.length]; int tries = 100; @@ -331,7 +313,7 @@ public final class PDF417ScanningDecoder { throw ChecksumException.getChecksumInstance(); } - private static BarcodeValue[][] createBarcodeMatrix(DetectionRXingResult detectionRXingResult) { + fn createBarcodeMatrix( detectionRXingResult:&DetectionRXingResult) -> Vec>{ BarcodeValue[][] barcodeMatrix = new BarcodeValue[detectionRXingResult.getBarcodeRowCount()][detectionRXingResult.getBarcodeColumnCount() + 2]; for (int row = 0; row < barcodeMatrix.length; row++) { @@ -361,14 +343,14 @@ public final class PDF417ScanningDecoder { return barcodeMatrix; } - private static boolean isValidBarcodeColumn(DetectionRXingResult detectionRXingResult, int barcodeColumn) { + fn isValidBarcodeColumn( detectionRXingResult:&DetectionRXingResult, barcodeColumn:u32) -> bool{ return barcodeColumn >= 0 && barcodeColumn <= detectionRXingResult.getBarcodeColumnCount() + 1; } - private static int getStartColumn(DetectionRXingResult detectionRXingResult, - int barcodeColumn, - int imageRow, - boolean leftToRight) { + fn getStartColumn( detectionRXingResult:&DetectionRXingResult, + barcodeColumn:u32, + imageRow:u32, + leftToRight:bool) -> u32{ int offset = leftToRight ? 1 : -1; Codeword codeword = null; if (isValidBarcodeColumn(detectionRXingResult, barcodeColumn - offset)) { @@ -404,14 +386,14 @@ public final class PDF417ScanningDecoder { return leftToRight ? detectionRXingResult.getBoundingBox().getMinX() : detectionRXingResult.getBoundingBox().getMaxX(); } - private static Codeword detectCodeword(BitMatrix image, - int minColumn, - int maxColumn, - boolean leftToRight, - int startColumn, - int imageRow, - int minCodewordWidth, - int maxCodewordWidth) { + fn detectCodeword( image:&BitMatrix, + minColumn:u32, + maxColumn:u32, + leftToRight:bool, + startColumn:u32, + imageRow:u32, + minCodewordWidth:u32, + maxCodewordWidth:u32) -> Codeword{ 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. @@ -462,12 +444,12 @@ public final class PDF417ScanningDecoder { return new Codeword(startColumn, endColumn, getCodewordBucketNumber(decodedValue), codeword); } - private static int[] getModuleBitCount(BitMatrix image, - int minColumn, - int maxColumn, - boolean leftToRight, - int startColumn, - int imageRow) { + fn getModuleBitCount( image:&BitMatrix, + minColumn:u32, + maxColumn:u32, + leftToRight:bool, + startColumn:u32, + imageRow:u32) -> Option<[u32;8]>{ int imageColumn = startColumn; int[] moduleBitCount = new int[8]; int moduleNumber = 0; @@ -491,16 +473,16 @@ public final class PDF417ScanningDecoder { return null; } - private static int getNumberOfECCodeWords(int barcodeECLevel) { + fn getNumberOfECCodeWords(int barcodeECLevel) -> u32{ return 2 << barcodeECLevel; } - private static int adjustCodewordStartColumn(BitMatrix image, - int minColumn, - int maxColumn, - boolean leftToRight, - int codewordStartColumn, - int imageRow) { + fn adjustCodewordStartColumn( image:&BitMatrix, + minColumn:u32, + maxColumn:u32, + leftToRight:bool, + codewordStartColumn:u32, + imageRow:u32) -> u32{ int correctedStartColumn = codewordStartColumn; int increment = leftToRight ? -1 : 1; // there should be no black pixels before the start column. If there are, then we need to start earlier. @@ -518,13 +500,12 @@ public final class PDF417ScanningDecoder { return correctedStartColumn; } - private static boolean checkCodewordSkew(int codewordSize, int minCodewordWidth, int maxCodewordWidth) { + fn checkCodewordSkew( codewordSize:u32, minCodewordWidth:u32, maxCodewordWidth:u32) -> bool{ return minCodewordWidth - CODEWORD_SKEW_SIZE <= codewordSize && codewordSize <= maxCodewordWidth + CODEWORD_SKEW_SIZE; } - private static DecoderRXingResult decodeCodewords(int[] codewords, int ecLevel, int[] erasures) throws FormatException, - ChecksumException { + fn decodeCodewords( codewords:&[u32], ecLevel:u32, erasures:&[u32]) -> Result{ if (codewords.length == 0) { throw FormatException.getFormatInstance(); } @@ -549,7 +530,7 @@ public final class PDF417ScanningDecoder { * @param numECCodewords number of error correction codewords that are available in codewords * @throws ChecksumException if error correction fails */ - private static int correctErrors(int[] codewords, int[] erasures, int numECCodewords) throws ChecksumException { + fn correctErrors( codewords:&[u32], erasures:&[u32], numECCodewords:u32) -> Result { if (erasures != null && erasures.length > numECCodewords / 2 + MAX_ERRORS || numECCodewords < 0 || @@ -563,7 +544,7 @@ public final class PDF417ScanningDecoder { /** * Verify that all is OK with the codeword array. */ - private static void verifyCodewordCount(int[] codewords, int numECCodewords) throws FormatException { + fn verifyCodewordCount( codewords:&[u32], numECCodewords:u32) -> Result<(),Exceptions> { if (codewords.length < 4) { // Codeword array size should be at least 4 allowing for // Count CW, At least one Data CW, Error Correction CW, Error Correction CW @@ -586,7 +567,7 @@ public final class PDF417ScanningDecoder { } } - private static int[] getBitCountForCodeword(int codeword) { + fn getBitCountForCodeword( codeword:u32) -> [u32;8]{ int[] result = new int[8]; int previousValue = 0; int i = result.length - 1; @@ -604,15 +585,15 @@ public final class PDF417ScanningDecoder { return result; } - private static int getCodewordBucketNumber(int codeword) { + fn getCodewordBucketNumber( codeword:u32) -> u32{ return getCodewordBucketNumber(getBitCountForCodeword(codeword)); } - private static int getCodewordBucketNumber(int[] moduleBitCount) { + fn getCodewordBucketNumber( moduleBitCount:&[u32]) -> u32{ return (moduleBitCount[0] - moduleBitCount[2] + moduleBitCount[4] - moduleBitCount[6] + 9) % 9; } - public static String toString(BarcodeValue[][] barcodeMatrix) { + fn toString( barcodeMatrix:Vec>) -> String{ try (Formatter formatter = new Formatter()) { for (int row = 0; row < barcodeMatrix.length; row++) { formatter.format("Row %2d: ", row); @@ -631,4 +612,3 @@ public final class PDF417ScanningDecoder { } } -}