mirror of
https://github.com/starovoid/rxing.git
synced 2026-07-27 04:42:35 +00:00
pdf_417 detector port
This commit is contained in:
@@ -1,356 +0,0 @@
|
||||
/*
|
||||
* Copyright 2009 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.detector;
|
||||
|
||||
import com.google.zxing.BinaryBitmap;
|
||||
import com.google.zxing.DecodeHintType;
|
||||
import com.google.zxing.NotFoundException;
|
||||
import com.google.zxing.RXingResultPoint;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* <p>Encapsulates logic that can detect a PDF417 Code in an image, even if the
|
||||
* PDF417 Code is rotated or skewed, or partially obscured.</p>
|
||||
*
|
||||
* @author SITA Lab (kevin.osullivan@sita.aero)
|
||||
* @author dswitkin@google.com (Daniel Switkin)
|
||||
* @author Guenther Grau
|
||||
*/
|
||||
public final class Detector {
|
||||
|
||||
private static final int[] INDEXES_START_PATTERN = {0, 4, 1, 5};
|
||||
private static final int[] INDEXES_STOP_PATTERN = {6, 2, 7, 3};
|
||||
private static final float MAX_AVG_VARIANCE = 0.42f;
|
||||
private static final float MAX_INDIVIDUAL_VARIANCE = 0.8f;
|
||||
|
||||
// B S B S B S B S Bar/Space pattern
|
||||
// 11111111 0 1 0 1 0 1 000
|
||||
private static final int[] START_PATTERN = {8, 1, 1, 1, 1, 1, 1, 3};
|
||||
// 1111111 0 1 000 1 0 1 00 1
|
||||
private static final int[] STOP_PATTERN = {7, 1, 1, 3, 1, 1, 1, 2, 1};
|
||||
private static final int MAX_PIXEL_DRIFT = 3;
|
||||
private static final int MAX_PATTERN_DRIFT = 5;
|
||||
// if we set the value too low, then we don't detect the correct height of the bar if the start patterns are damaged.
|
||||
// if we set the value too high, then we might detect the start pattern from a neighbor barcode.
|
||||
private static final int SKIPPED_ROW_COUNT_MAX = 25;
|
||||
// A PDF471 barcode should have at least 3 rows, with each row being >= 3 times the module width.
|
||||
// Therefore it should be at least 9 pixels tall. To be conservative, we use about half the size to
|
||||
// ensure we don't miss it.
|
||||
private static final int ROW_STEP = 5;
|
||||
private static final int BARCODE_MIN_HEIGHT = 10;
|
||||
private static final int[] ROTATIONS = {0, 180, 270, 90};
|
||||
|
||||
private Detector() {
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Detects a PDF417 Code in an image. Checks 0, 90, 180, and 270 degree rotations.</p>
|
||||
*
|
||||
* @param image barcode image to decode
|
||||
* @param hints optional hints to detector
|
||||
* @param multiple if true, then the image is searched for multiple codes. If false, then at most one code will
|
||||
* be found and returned
|
||||
* @return {@link PDF417DetectorRXingResult} encapsulating results of detecting a PDF417 code
|
||||
* @throws NotFoundException if no PDF417 Code can be found
|
||||
*/
|
||||
public static PDF417DetectorRXingResult detect(BinaryBitmap image, Map<DecodeHintType,?> hints, boolean multiple)
|
||||
throws NotFoundException {
|
||||
// TODO detection improvement, tryHarder could try several different luminance thresholds/blackpoints or even
|
||||
// different binarizers
|
||||
//boolean tryHarder = hints != null && hints.containsKey(DecodeHintType.TRY_HARDER);
|
||||
|
||||
BitMatrix originalMatrix = image.getBlackMatrix();
|
||||
for (int rotation : ROTATIONS) {
|
||||
BitMatrix bitMatrix = applyRotation(originalMatrix, rotation);
|
||||
List<RXingResultPoint[]> barcodeCoordinates = detect(multiple, bitMatrix);
|
||||
if (!barcodeCoordinates.isEmpty()) {
|
||||
return new PDF417DetectorRXingResult(bitMatrix, barcodeCoordinates, rotation);
|
||||
}
|
||||
}
|
||||
return new PDF417DetectorRXingResult(originalMatrix, new ArrayList<>(), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a rotation to the supplied BitMatrix.
|
||||
* @param matrix bit matrix to apply rotation to
|
||||
* @param rotation the degrees of rotation to apply
|
||||
* @return BitMatrix with applied rotation
|
||||
*/
|
||||
private static BitMatrix applyRotation(BitMatrix matrix, int rotation) {
|
||||
if (rotation % 360 == 0) {
|
||||
return matrix;
|
||||
}
|
||||
|
||||
BitMatrix newMatrix = matrix.clone();
|
||||
newMatrix.rotate(rotation);
|
||||
return newMatrix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects PDF417 codes in an image. Only checks 0 degree rotation
|
||||
* @param multiple if true, then the image is searched for multiple codes. If false, then at most one code will
|
||||
* be found and returned
|
||||
* @param bitMatrix bit matrix to detect barcodes in
|
||||
* @return List of RXingResultPoint arrays containing the coordinates of found barcodes
|
||||
*/
|
||||
private static List<RXingResultPoint[]> detect(boolean multiple, BitMatrix bitMatrix) {
|
||||
List<RXingResultPoint[]> barcodeCoordinates = new ArrayList<>();
|
||||
int row = 0;
|
||||
int column = 0;
|
||||
boolean foundBarcodeInRow = false;
|
||||
while (row < bitMatrix.getHeight()) {
|
||||
RXingResultPoint[] vertices = findVertices(bitMatrix, row, column);
|
||||
|
||||
if (vertices[0] == null && vertices[3] == null) {
|
||||
if (!foundBarcodeInRow) {
|
||||
// we didn't find any barcode so that's the end of searching
|
||||
break;
|
||||
}
|
||||
// we didn't find a barcode starting at the given column and row. Try again from the first column and slightly
|
||||
// below the lowest barcode we found so far.
|
||||
foundBarcodeInRow = false;
|
||||
column = 0;
|
||||
for (RXingResultPoint[] barcodeCoordinate : barcodeCoordinates) {
|
||||
if (barcodeCoordinate[1] != null) {
|
||||
row = (int) Math.max(row, barcodeCoordinate[1].getY());
|
||||
}
|
||||
if (barcodeCoordinate[3] != null) {
|
||||
row = Math.max(row, (int) barcodeCoordinate[3].getY());
|
||||
}
|
||||
}
|
||||
row += ROW_STEP;
|
||||
continue;
|
||||
}
|
||||
foundBarcodeInRow = true;
|
||||
barcodeCoordinates.add(vertices);
|
||||
if (!multiple) {
|
||||
break;
|
||||
}
|
||||
// if we didn't find a right row indicator column, then continue the search for the next barcode after the
|
||||
// start pattern of the barcode just found.
|
||||
if (vertices[2] != null) {
|
||||
column = (int) vertices[2].getX();
|
||||
row = (int) vertices[2].getY();
|
||||
} else {
|
||||
column = (int) vertices[4].getX();
|
||||
row = (int) vertices[4].getY();
|
||||
}
|
||||
}
|
||||
return barcodeCoordinates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Locate the vertices and the codewords area of a black blob using the Start
|
||||
* and Stop patterns as locators.
|
||||
*
|
||||
* @param matrix the scanned barcode image.
|
||||
* @return an array containing the vertices:
|
||||
* vertices[0] x, y top left barcode
|
||||
* vertices[1] x, y bottom left barcode
|
||||
* vertices[2] x, y top right barcode
|
||||
* vertices[3] x, y bottom right barcode
|
||||
* vertices[4] x, y top left codeword area
|
||||
* vertices[5] x, y bottom left codeword area
|
||||
* vertices[6] x, y top right codeword area
|
||||
* vertices[7] x, y bottom right codeword area
|
||||
*/
|
||||
private static RXingResultPoint[] findVertices(BitMatrix matrix, int startRow, int startColumn) {
|
||||
int height = matrix.getHeight();
|
||||
int width = matrix.getWidth();
|
||||
|
||||
RXingResultPoint[] result = new RXingResultPoint[8];
|
||||
copyToRXingResult(result, findRowsWithPattern(matrix, height, width, startRow, startColumn, START_PATTERN),
|
||||
INDEXES_START_PATTERN);
|
||||
|
||||
if (result[4] != null) {
|
||||
startColumn = (int) result[4].getX();
|
||||
startRow = (int) result[4].getY();
|
||||
}
|
||||
copyToRXingResult(result, findRowsWithPattern(matrix, height, width, startRow, startColumn, STOP_PATTERN),
|
||||
INDEXES_STOP_PATTERN);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void copyToRXingResult(RXingResultPoint[] result, RXingResultPoint[] tmpRXingResult, int[] destinationIndexes) {
|
||||
for (int i = 0; i < destinationIndexes.length; i++) {
|
||||
result[destinationIndexes[i]] = tmpRXingResult[i];
|
||||
}
|
||||
}
|
||||
|
||||
private static RXingResultPoint[] findRowsWithPattern(BitMatrix matrix,
|
||||
int height,
|
||||
int width,
|
||||
int startRow,
|
||||
int startColumn,
|
||||
int[] pattern) {
|
||||
RXingResultPoint[] result = new RXingResultPoint[4];
|
||||
boolean found = false;
|
||||
int[] counters = new int[pattern.length];
|
||||
for (; startRow < height; startRow += ROW_STEP) {
|
||||
int[] loc = findGuardPattern(matrix, startColumn, startRow, width, pattern, counters);
|
||||
if (loc != null) {
|
||||
while (startRow > 0) {
|
||||
int[] previousRowLoc = findGuardPattern(matrix, startColumn, --startRow, width, pattern, counters);
|
||||
if (previousRowLoc != null) {
|
||||
loc = previousRowLoc;
|
||||
} else {
|
||||
startRow++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
result[0] = new RXingResultPoint(loc[0], startRow);
|
||||
result[1] = new RXingResultPoint(loc[1], startRow);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
int stopRow = startRow + 1;
|
||||
// Last row of the current symbol that contains pattern
|
||||
if (found) {
|
||||
int skippedRowCount = 0;
|
||||
int[] previousRowLoc = {(int) result[0].getX(), (int) result[1].getX()};
|
||||
for (; stopRow < height; stopRow++) {
|
||||
int[] loc = findGuardPattern(matrix, previousRowLoc[0], stopRow, width, pattern, counters);
|
||||
// a found pattern is only considered to belong to the same barcode if the start and end positions
|
||||
// don't differ too much. Pattern drift should be not bigger than two for consecutive rows. With
|
||||
// a higher number of skipped rows drift could be larger. To keep it simple for now, we allow a slightly
|
||||
// larger drift and don't check for skipped rows.
|
||||
if (loc != null &&
|
||||
Math.abs(previousRowLoc[0] - loc[0]) < MAX_PATTERN_DRIFT &&
|
||||
Math.abs(previousRowLoc[1] - loc[1]) < MAX_PATTERN_DRIFT) {
|
||||
previousRowLoc = loc;
|
||||
skippedRowCount = 0;
|
||||
} else {
|
||||
if (skippedRowCount > SKIPPED_ROW_COUNT_MAX) {
|
||||
break;
|
||||
} else {
|
||||
skippedRowCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
stopRow -= skippedRowCount + 1;
|
||||
result[2] = new RXingResultPoint(previousRowLoc[0], stopRow);
|
||||
result[3] = new RXingResultPoint(previousRowLoc[1], stopRow);
|
||||
}
|
||||
if (stopRow - startRow < BARCODE_MIN_HEIGHT) {
|
||||
Arrays.fill(result, null);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param matrix row of black/white values to search
|
||||
* @param column x position to start search
|
||||
* @param row y position to start search
|
||||
* @param width the number of pixels to search on this row
|
||||
* @param pattern pattern of counts of number of black and white pixels that are
|
||||
* being searched for as a pattern
|
||||
* @param counters array of counters, as long as pattern, to re-use
|
||||
* @return start/end horizontal offset of guard pattern, as an array of two ints.
|
||||
*/
|
||||
private static int[] findGuardPattern(BitMatrix matrix,
|
||||
int column,
|
||||
int row,
|
||||
int width,
|
||||
int[] pattern,
|
||||
int[] counters) {
|
||||
Arrays.fill(counters, 0, counters.length, 0);
|
||||
int patternStart = column;
|
||||
int pixelDrift = 0;
|
||||
|
||||
// if there are black pixels left of the current pixel shift to the left, but only for MAX_PIXEL_DRIFT pixels
|
||||
while (matrix.get(patternStart, row) && patternStart > 0 && pixelDrift++ < MAX_PIXEL_DRIFT) {
|
||||
patternStart--;
|
||||
}
|
||||
int x = patternStart;
|
||||
int counterPosition = 0;
|
||||
int patternLength = pattern.length;
|
||||
for (boolean isWhite = false; x < width; x++) {
|
||||
boolean pixel = matrix.get(x, row);
|
||||
if (pixel != isWhite) {
|
||||
counters[counterPosition]++;
|
||||
} else {
|
||||
if (counterPosition == patternLength - 1) {
|
||||
if (patternMatchVariance(counters, pattern) < MAX_AVG_VARIANCE) {
|
||||
return new int[] {patternStart, x};
|
||||
}
|
||||
patternStart += counters[0] + counters[1];
|
||||
System.arraycopy(counters, 2, counters, 0, counterPosition - 1);
|
||||
counters[counterPosition - 1] = 0;
|
||||
counters[counterPosition] = 0;
|
||||
counterPosition--;
|
||||
} else {
|
||||
counterPosition++;
|
||||
}
|
||||
counters[counterPosition] = 1;
|
||||
isWhite = !isWhite;
|
||||
}
|
||||
}
|
||||
if (counterPosition == patternLength - 1 &&
|
||||
patternMatchVariance(counters, pattern) < MAX_AVG_VARIANCE) {
|
||||
return new int[] {patternStart, x - 1};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines how closely a set of observed counts of runs of black/white
|
||||
* values matches a given target pattern. This is reported as the ratio of
|
||||
* the total variance from the expected pattern proportions across all
|
||||
* pattern elements, to the length of the pattern.
|
||||
*
|
||||
* @param counters observed counters
|
||||
* @param pattern expected pattern
|
||||
* @return ratio of total variance between counters and pattern compared to total pattern size
|
||||
*/
|
||||
private static float patternMatchVariance(int[] counters, int[] pattern) {
|
||||
int numCounters = counters.length;
|
||||
int total = 0;
|
||||
int patternLength = 0;
|
||||
for (int i = 0; i < numCounters; i++) {
|
||||
total += counters[i];
|
||||
patternLength += pattern[i];
|
||||
}
|
||||
if (total < patternLength) {
|
||||
// If we don't even have one pixel per unit of bar width, assume this
|
||||
// is too small to reliably match, so fail:
|
||||
return Float.POSITIVE_INFINITY;
|
||||
}
|
||||
// We're going to fake floating-point math in integers. We just need to use more bits.
|
||||
// Scale up patternLength so that intermediate values below like scaledCounter will have
|
||||
// more "significant digits".
|
||||
float unitBarWidth = (float) total / patternLength;
|
||||
float maxIndividualVariance = MAX_INDIVIDUAL_VARIANCE * unitBarWidth;
|
||||
|
||||
float totalVariance = 0.0f;
|
||||
for (int x = 0; x < numCounters; x++) {
|
||||
int counter = counters[x];
|
||||
float scaledPattern = pattern[x] * unitBarWidth;
|
||||
float variance = counter > scaledPattern ? counter - scaledPattern : scaledPattern - counter;
|
||||
if (variance > maxIndividualVariance) {
|
||||
return Float.POSITIVE_INFINITY;
|
||||
}
|
||||
totalVariance += variance;
|
||||
}
|
||||
return totalVariance / total;
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* Copyright 2007 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.detector;
|
||||
|
||||
import com.google.zxing.RXingResultPoint;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Guenther Grau
|
||||
*/
|
||||
public final class PDF417DetectorRXingResult {
|
||||
|
||||
private final BitMatrix bits;
|
||||
private final List<RXingResultPoint[]> points;
|
||||
private final int rotation;
|
||||
|
||||
public PDF417DetectorRXingResult(BitMatrix bits, List<RXingResultPoint[]> points, int rotation) {
|
||||
this.bits = bits;
|
||||
this.points = points;
|
||||
this.rotation = rotation;
|
||||
}
|
||||
|
||||
public PDF417DetectorRXingResult(BitMatrix bits, List<RXingResultPoint[]> points) {
|
||||
this(bits, points, 0);
|
||||
}
|
||||
|
||||
public BitMatrix getBits() {
|
||||
return bits;
|
||||
}
|
||||
|
||||
public List<RXingResultPoint[]> getPoints() {
|
||||
return points;
|
||||
}
|
||||
|
||||
public int getRotation() {
|
||||
return rotation;
|
||||
}
|
||||
|
||||
}
|
||||
437
src/pdf417/detector/detector.rs
Normal file
437
src/pdf417/detector/detector.rs
Normal file
@@ -0,0 +1,437 @@
|
||||
/*
|
||||
* Copyright 2009 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 crate::{
|
||||
common::BitMatrix, BinaryBitmap, DecodingHintDictionary, Exceptions, RXingResultPoint,
|
||||
ResultPoint,
|
||||
};
|
||||
|
||||
use super::PDF417DetectorRXingResult;
|
||||
|
||||
/**
|
||||
* <p>Encapsulates logic that can detect a PDF417 Code in an image, even if the
|
||||
* PDF417 Code is rotated or skewed, or partially obscured.</p>
|
||||
*
|
||||
* @author SITA Lab (kevin.osullivan@sita.aero)
|
||||
* @author dswitkin@google.com (Daniel Switkin)
|
||||
* @author Guenther Grau
|
||||
*/
|
||||
|
||||
const INDEXES_START_PATTERN: [u32; 4] = [0, 4, 1, 5];
|
||||
const INDEXES_STOP_PATTERN: [u32; 4] = [6, 2, 7, 3];
|
||||
const MAX_AVG_VARIANCE: f32 = 0.42;
|
||||
const MAX_INDIVIDUAL_VARIANCE: f32 = 0.8;
|
||||
|
||||
// B S B S B S B S Bar/Space pattern
|
||||
// 11111111 0 1 0 1 0 1 000
|
||||
const START_PATTERN: [u32; 8] = [8, 1, 1, 1, 1, 1, 1, 3];
|
||||
// 1111111 0 1 000 1 0 1 00 1
|
||||
const STOP_PATTERN: [u32; 9] = [7, 1, 1, 3, 1, 1, 1, 2, 1];
|
||||
const MAX_PIXEL_DRIFT: u32 = 3;
|
||||
const MAX_PATTERN_DRIFT: u32 = 5;
|
||||
// if we set the value too low, then we don't detect the correct height of the bar if the start patterns are damaged.
|
||||
// if we set the value too high, then we might detect the start pattern from a neighbor barcode.
|
||||
const SKIPPED_ROW_COUNT_MAX: u32 = 25;
|
||||
// A PDF471 barcode should have at least 3 rows, with each row being >= 3 times the module width.
|
||||
// Therefore it should be at least 9 pixels tall. To be conservative, we use about half the size to
|
||||
// ensure we don't miss it.
|
||||
const ROW_STEP: u32 = 5;
|
||||
const BARCODE_MIN_HEIGHT: u32 = 10;
|
||||
const ROTATIONS: [u32; 4] = [0, 180, 270, 90];
|
||||
|
||||
/**
|
||||
* <p>Detects a PDF417 Code in an image. Checks 0, 90, 180, and 270 degree rotations.</p>
|
||||
*
|
||||
* @param image barcode image to decode
|
||||
* @param hints optional hints to detector
|
||||
* @param multiple if true, then the image is searched for multiple codes. If false, then at most one code will
|
||||
* be found and returned
|
||||
* @return {@link PDF417DetectorRXingResult} encapsulating results of detecting a PDF417 code
|
||||
* @throws NotFoundException if no PDF417 Code can be found
|
||||
*/
|
||||
pub fn detect_with_hints(
|
||||
image: &BinaryBitmap,
|
||||
_hints: &DecodingHintDictionary,
|
||||
multiple: bool,
|
||||
) -> Result<PDF417DetectorRXingResult, Exceptions> {
|
||||
// TODO detection improvement, tryHarder could try several different luminance thresholds/blackpoints or even
|
||||
// different binarizers
|
||||
//boolean tryHarder = hints != null && hints.containsKey(DecodeHintType.TRY_HARDER);
|
||||
|
||||
let originalMatrix = image.getBlackMatrix();
|
||||
for rotation in ROTATIONS {
|
||||
// for (int rotation : ROTATIONS) {
|
||||
let bitMatrix = applyRotation(originalMatrix, rotation);
|
||||
let barcodeCoordinates = detect(multiple, &bitMatrix);
|
||||
if !barcodeCoordinates.is_empty() {
|
||||
return Ok(PDF417DetectorRXingResult::with_rotation(
|
||||
bitMatrix,
|
||||
barcodeCoordinates,
|
||||
rotation,
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(PDF417DetectorRXingResult::with_rotation(
|
||||
originalMatrix.clone(),
|
||||
Vec::new(),
|
||||
0,
|
||||
))
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a rotation to the supplied BitMatrix.
|
||||
* @param matrix bit matrix to apply rotation to
|
||||
* @param rotation the degrees of rotation to apply
|
||||
* @return BitMatrix with applied rotation
|
||||
*/
|
||||
fn applyRotation(matrix: &BitMatrix, rotation: u32) -> BitMatrix {
|
||||
if rotation % 360 == 0 {
|
||||
matrix.clone()
|
||||
} else {
|
||||
let mut newMatrix = matrix.clone();
|
||||
newMatrix.rotate(rotation).expect("rotation must complete");
|
||||
newMatrix
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects PDF417 codes in an image. Only checks 0 degree rotation
|
||||
* @param multiple if true, then the image is searched for multiple codes. If false, then at most one code will
|
||||
* be found and returned
|
||||
* @param bitMatrix bit matrix to detect barcodes in
|
||||
* @return List of RXingResultPoint arrays containing the coordinates of found barcodes
|
||||
*/
|
||||
fn detect(multiple: bool, bitMatrix: &BitMatrix) -> Vec<[Option<RXingResultPoint>; 8]> {
|
||||
let mut barcodeCoordinates: Vec<[Option<RXingResultPoint>; 8]> = Vec::new();
|
||||
let mut row = 0;
|
||||
let mut column = 0;
|
||||
let mut foundBarcodeInRow = false;
|
||||
while row < bitMatrix.getHeight() {
|
||||
let vertices = findVertices(bitMatrix, row, column);
|
||||
|
||||
if vertices[0].is_none() && vertices[3].is_none() {
|
||||
if !foundBarcodeInRow {
|
||||
// we didn't find any barcode so that's the end of searching
|
||||
break;
|
||||
}
|
||||
// we didn't find a barcode starting at the given column and row. Try again from the first column and slightly
|
||||
// below the lowest barcode we found so far.
|
||||
foundBarcodeInRow = false;
|
||||
column = 0;
|
||||
for barcodeCoordinate in &barcodeCoordinates {
|
||||
// for (RXingResultPoint[] barcodeCoordinate : barcodeCoordinates) {
|
||||
if let Some(coord_1) = barcodeCoordinate[1] {
|
||||
// if barcodeCoordinate[1].is_some() {
|
||||
row = row.max(coord_1.getY() as u32);
|
||||
}
|
||||
if let Some(coord_3) = barcodeCoordinate[3] {
|
||||
row = row.max(coord_3.getY() as u32);
|
||||
}
|
||||
}
|
||||
row += ROW_STEP;
|
||||
continue;
|
||||
}
|
||||
foundBarcodeInRow = true;
|
||||
barcodeCoordinates.push(vertices);
|
||||
if !multiple {
|
||||
break;
|
||||
}
|
||||
// if we didn't find a right row indicator column, then continue the search for the next barcode after the
|
||||
// start pattern of the barcode just found.
|
||||
if let Some(vert_2) = vertices[2] {
|
||||
column = vert_2.getX() as u32;
|
||||
row = vert_2.getY() as u32;
|
||||
} else {
|
||||
column = vertices[4].as_ref().unwrap().getX() as u32;
|
||||
row = vertices[4].as_ref().unwrap().getY() as u32;
|
||||
}
|
||||
}
|
||||
barcodeCoordinates
|
||||
}
|
||||
|
||||
/**
|
||||
* Locate the vertices and the codewords area of a black blob using the Start
|
||||
* and Stop patterns as locators.
|
||||
*
|
||||
* @param matrix the scanned barcode image.
|
||||
* @return an array containing the vertices:
|
||||
* vertices[0] x, y top left barcode
|
||||
* vertices[1] x, y bottom left barcode
|
||||
* vertices[2] x, y top right barcode
|
||||
* vertices[3] x, y bottom right barcode
|
||||
* vertices[4] x, y top left codeword area
|
||||
* vertices[5] x, y bottom left codeword area
|
||||
* vertices[6] x, y top right codeword area
|
||||
* vertices[7] x, y bottom right codeword area
|
||||
*/
|
||||
fn findVertices(
|
||||
matrix: &BitMatrix,
|
||||
startRow: u32,
|
||||
startColumn: u32,
|
||||
) -> [Option<RXingResultPoint>; 8] {
|
||||
let height = matrix.getHeight();
|
||||
let width = matrix.getWidth();
|
||||
let mut startRow = startRow;
|
||||
let mut startColumn = startColumn;
|
||||
|
||||
let mut result = [None::<RXingResultPoint>; 8]; //RXingResultPoint[8];
|
||||
copyToRXingResult(
|
||||
&mut result,
|
||||
&findRowsWithPattern(matrix, height, width, startRow, startColumn, &START_PATTERN),
|
||||
&INDEXES_START_PATTERN,
|
||||
);
|
||||
|
||||
if let Some(result_4) = result[4] {
|
||||
startColumn = result_4.getX() as u32;
|
||||
startRow = result_4.getY() as u32;
|
||||
}
|
||||
copyToRXingResult(
|
||||
&mut result,
|
||||
&findRowsWithPattern(matrix, height, width, startRow, startColumn, &STOP_PATTERN),
|
||||
&INDEXES_STOP_PATTERN,
|
||||
);
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn copyToRXingResult(
|
||||
result: &mut [Option<RXingResultPoint>],
|
||||
tmpRXingResult: &[Option<RXingResultPoint>],
|
||||
destinationIndexes: &[u32],
|
||||
) {
|
||||
for i in 0..destinationIndexes.len() {
|
||||
// for (int i = 0; i < destinationIndexes.length; i++) {
|
||||
result[destinationIndexes[i] as usize] = tmpRXingResult[i];
|
||||
}
|
||||
}
|
||||
|
||||
fn findRowsWithPattern(
|
||||
matrix: &BitMatrix,
|
||||
height: u32,
|
||||
width: u32,
|
||||
startRow: u32,
|
||||
startColumn: u32,
|
||||
pattern: &[u32],
|
||||
) -> [Option<RXingResultPoint>; 4] {
|
||||
let mut startRow = startRow;
|
||||
let mut result = [None; 4]; //new RXingResultPoint[4];
|
||||
let mut found = false;
|
||||
let mut counters = vec![0_u32; pattern.len()];
|
||||
while startRow < height {
|
||||
// for (; startRow < height; startRow += ROW_STEP) {
|
||||
let mut loc_store; // = None;
|
||||
if let Some(loc) =
|
||||
findGuardPattern(matrix, startColumn, startRow, width, pattern, &mut counters)
|
||||
{
|
||||
// if (loc != null) {
|
||||
loc_store = Some(loc);
|
||||
while startRow > 0 {
|
||||
startRow -= 1;
|
||||
if let Some(previousRowLoc) =
|
||||
findGuardPattern(matrix, startColumn, startRow, width, pattern, &mut counters)
|
||||
{
|
||||
// if (previousRowLoc != null) {
|
||||
//loc = previousRowLoc;
|
||||
loc_store = Some(previousRowLoc);
|
||||
} else {
|
||||
startRow += 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
result[0] = Some(RXingResultPoint::new(
|
||||
loc_store.as_ref().unwrap()[0] as f32,
|
||||
startRow as f32,
|
||||
));
|
||||
result[1] = Some(RXingResultPoint::new(
|
||||
loc_store.as_ref().unwrap()[1] as f32,
|
||||
startRow as f32,
|
||||
));
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
startRow += ROW_STEP;
|
||||
}
|
||||
let mut stopRow = startRow + 1;
|
||||
// Last row of the current symbol that contains pattern
|
||||
if found {
|
||||
let mut skippedRowCount = 0;
|
||||
let mut previousRowLoc = [
|
||||
result[0].as_ref().unwrap().getX() as u32,
|
||||
result[1].as_ref().unwrap().getX() as u32,
|
||||
];
|
||||
while stopRow < height {
|
||||
// for (; stopRow < height; stopRow++) {
|
||||
if let Some(loc) = findGuardPattern(
|
||||
matrix,
|
||||
previousRowLoc[0],
|
||||
stopRow,
|
||||
width,
|
||||
pattern,
|
||||
&mut counters,
|
||||
) {
|
||||
// a found pattern is only considered to belong to the same barcode if the start and end positions
|
||||
// don't differ too much. Pattern drift should be not bigger than two for consecutive rows. With
|
||||
// a higher number of skipped rows drift could be larger. To keep it simple for now, we allow a slightly
|
||||
// larger drift and don't check for skipped rows.
|
||||
if ((previousRowLoc[0] as i32 - loc[0] as i32).abs() as u32) < MAX_PATTERN_DRIFT
|
||||
&& ((previousRowLoc[1] as i32 - loc[1] as i32).abs() as u32) < MAX_PATTERN_DRIFT
|
||||
{
|
||||
previousRowLoc = loc;
|
||||
skippedRowCount = 0;
|
||||
} else {
|
||||
if skippedRowCount > SKIPPED_ROW_COUNT_MAX {
|
||||
break;
|
||||
} else {
|
||||
skippedRowCount += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stopRow += 1;
|
||||
}
|
||||
stopRow -= skippedRowCount + 1;
|
||||
result[2] = Some(RXingResultPoint::new(
|
||||
previousRowLoc[0] as f32,
|
||||
stopRow as f32,
|
||||
));
|
||||
result[3] = Some(RXingResultPoint::new(
|
||||
previousRowLoc[1] as f32,
|
||||
stopRow as f32,
|
||||
));
|
||||
}
|
||||
if stopRow - startRow < BARCODE_MIN_HEIGHT {
|
||||
result.fill(None);
|
||||
// Arrays.fill(result, null);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/**
|
||||
* @param matrix row of black/white values to search
|
||||
* @param column x position to start search
|
||||
* @param row y position to start search
|
||||
* @param width the number of pixels to search on this row
|
||||
* @param pattern pattern of counts of number of black and white pixels that are
|
||||
* being searched for as a pattern
|
||||
* @param counters array of counters, as long as pattern, to re-use
|
||||
* @return start/end horizontal offset of guard pattern, as an array of two ints.
|
||||
*/
|
||||
fn findGuardPattern(
|
||||
matrix: &BitMatrix,
|
||||
column: u32,
|
||||
row: u32,
|
||||
width: u32,
|
||||
pattern: &[u32],
|
||||
counters: &mut [u32],
|
||||
) -> Option<[u32; 2]> {
|
||||
counters.fill(0);
|
||||
// Arrays.fill(counters, 0, counters.length, 0);
|
||||
let mut patternStart = column;
|
||||
let mut pixelDrift = 0;
|
||||
|
||||
// if there are black pixels left of the current pixel shift to the left, but only for MAX_PIXEL_DRIFT pixels
|
||||
while matrix.get(patternStart, row) && patternStart > 0 && pixelDrift < MAX_PIXEL_DRIFT {
|
||||
pixelDrift += 1;
|
||||
patternStart -= 1;
|
||||
}
|
||||
let mut x = patternStart;
|
||||
let mut counterPosition = 0;
|
||||
let patternLength = pattern.len();
|
||||
let mut isWhite = false;
|
||||
while x < width {
|
||||
// for (boolean isWhite = false; x < width; x++) {
|
||||
let pixel = matrix.get(x, row);
|
||||
if pixel != isWhite {
|
||||
counters[counterPosition] += 1;
|
||||
} else {
|
||||
if counterPosition == patternLength - 1 {
|
||||
if patternMatchVariance(counters, pattern) < MAX_AVG_VARIANCE {
|
||||
return Some([patternStart, x]);
|
||||
}
|
||||
patternStart += counters[0] + counters[1];
|
||||
|
||||
counters.copy_within(2..counterPosition - 1 + 2, 0);
|
||||
// System.arraycopy(counters, 2, counters, 0, counterPosition - 1);
|
||||
|
||||
counters[counterPosition - 1] = 0;
|
||||
counters[counterPosition] = 0;
|
||||
counterPosition -= 1;
|
||||
} else {
|
||||
counterPosition += 1;
|
||||
}
|
||||
counters[counterPosition] = 1;
|
||||
isWhite = !isWhite;
|
||||
}
|
||||
x += 1;
|
||||
}
|
||||
if counterPosition == patternLength - 1
|
||||
&& patternMatchVariance(counters, pattern) < MAX_AVG_VARIANCE
|
||||
{
|
||||
return Some([patternStart, x - 1]);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines how closely a set of observed counts of runs of black/white
|
||||
* values matches a given target pattern. This is reported as the ratio of
|
||||
* the total variance from the expected pattern proportions across all
|
||||
* pattern elements, to the length of the pattern.
|
||||
*
|
||||
* @param counters observed counters
|
||||
* @param pattern expected pattern
|
||||
* @return ratio of total variance between counters and pattern compared to total pattern size
|
||||
*/
|
||||
fn patternMatchVariance(counters: &[u32], pattern: &[u32]) -> f32 {
|
||||
let numCounters = counters.len();
|
||||
let mut total = 0;
|
||||
let mut patternLength = 0;
|
||||
for i in 0..numCounters {
|
||||
// for (int i = 0; i < numCounters; i++) {
|
||||
total += counters[i];
|
||||
patternLength += pattern[i];
|
||||
}
|
||||
if total < patternLength {
|
||||
// If we don't even have one pixel per unit of bar width, assume this
|
||||
// is too small to reliably match, so fail:
|
||||
return f32::INFINITY; //Float.POSITIVE_INFINITY;
|
||||
}
|
||||
// We're going to fake floating-point math in integers. We just need to use more bits.
|
||||
// Scale up patternLength so that intermediate values below like scaledCounter will have
|
||||
// more "significant digits".
|
||||
let unitBarWidth: f32 = (total / patternLength) as f32;
|
||||
let maxIndividualVariance = MAX_INDIVIDUAL_VARIANCE * unitBarWidth;
|
||||
|
||||
let mut totalVariance = 0.0;
|
||||
for x in 0..numCounters {
|
||||
// for (int x = 0; x < numCounters; x++) {
|
||||
let counter = counters[x];
|
||||
let scaledPattern: f32 = pattern[x] as f32 * unitBarWidth;
|
||||
let variance: f32 = if counter as f32 > scaledPattern {
|
||||
counter as f32 - scaledPattern
|
||||
} else {
|
||||
scaledPattern - counter as f32
|
||||
};
|
||||
if variance > maxIndividualVariance {
|
||||
return f32::INFINITY;
|
||||
}
|
||||
totalVariance += variance;
|
||||
}
|
||||
totalVariance / total as f32
|
||||
}
|
||||
@@ -1 +1,6 @@
|
||||
|
||||
|
||||
mod pdf_417_detector_result;
|
||||
pub use pdf_417_detector_result::*;
|
||||
|
||||
pub mod detector;
|
||||
56
src/pdf417/detector/pdf_417_detector_result.rs
Normal file
56
src/pdf417/detector/pdf_417_detector_result.rs
Normal file
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2007 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 crate::{common::BitMatrix, RXingResultPoint};
|
||||
|
||||
/**
|
||||
* @author Guenther Grau
|
||||
*/
|
||||
pub struct PDF417DetectorRXingResult {
|
||||
bits: BitMatrix,
|
||||
points: Vec<[Option<RXingResultPoint>; 8]>,
|
||||
rotation: u32,
|
||||
}
|
||||
|
||||
impl PDF417DetectorRXingResult {
|
||||
pub fn with_rotation(
|
||||
bits: BitMatrix,
|
||||
points: Vec<[Option<RXingResultPoint>; 8]>,
|
||||
rotation: u32,
|
||||
) -> Self {
|
||||
Self {
|
||||
bits,
|
||||
points,
|
||||
rotation,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(bits: BitMatrix, points: Vec<[Option<RXingResultPoint>; 8]>) -> Self {
|
||||
Self::with_rotation(bits, points, 0)
|
||||
}
|
||||
|
||||
pub fn getBits(&self) -> &BitMatrix {
|
||||
&self.bits
|
||||
}
|
||||
|
||||
pub fn getPoints(&self) -> &Vec<[Option<RXingResultPoint>; 8]> {
|
||||
&self.points
|
||||
}
|
||||
|
||||
pub fn getRotation(&self) -> u32 {
|
||||
self.rotation
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user