mirror of
https://github.com/starovoid/rxing.git
synced 2026-07-25 20:02:34 +00:00
perfect images of aztec pass, real world fail
This commit is contained in:
@@ -20,6 +20,9 @@ chrono-tz = "0.4"
|
|||||||
image = {version = "0.24.3", optional = true}
|
image = {version = "0.24.3", optional = true}
|
||||||
imageproc = {version = "0.23.0", optional = true}
|
imageproc = {version = "0.23.0", optional = true}
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
java-properties = "1.4.1"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["image"]
|
default = ["image"]
|
||||||
image = ["dep:image", "dep:imageproc"]
|
image = ["dep:image", "dep:imageproc"]
|
||||||
@@ -67,6 +67,16 @@ fn test_error_in_parameter_locator_not_compact() {
|
|||||||
test_error_in_parameter_locator(&format!("{}{}{}", alphabet, alphabet, alphabet));
|
test_error_in_parameter_locator(&format!("{}{}{}", alphabet, alphabet, alphabet));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_aztec_rxing_result_sample() {
|
||||||
|
let matrix = BitMatrix::parse_strings(TEST_BARCODE, "X ", " ").expect("string parse success");
|
||||||
|
let r = Detector::new(matrix).detect(false);
|
||||||
|
assert!(r.is_ok());
|
||||||
|
let r = r.expect("result already tested as ok");
|
||||||
|
let res = decoder::decode(&r).expect("decode success");
|
||||||
|
assert_eq!("Histórico", res.getText());
|
||||||
|
}
|
||||||
|
|
||||||
// Test that we can tolerate errors in the parameter locator bits
|
// Test that we can tolerate errors in the parameter locator bits
|
||||||
fn test_error_in_parameter_locator(data: &str) {
|
fn test_error_in_parameter_locator(data: &str) {
|
||||||
let aztec = encoder::encoder::encode(data, 25, encoder::encoder::DEFAULT_AZTEC_LAYERS)
|
let aztec = encoder::encoder::encode(data, 25, encoder::encoder::DEFAULT_AZTEC_LAYERS)
|
||||||
@@ -260,3 +270,80 @@ fn get_orientation_points(code: &AztecCode) -> Vec<Point> {
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const TEST_BARCODE: &str = r" X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X
|
||||||
|
";
|
||||||
|
|||||||
@@ -177,8 +177,8 @@ fn testAztecWriter() {
|
|||||||
);
|
);
|
||||||
// Test AztecWriter defaults
|
// Test AztecWriter defaults
|
||||||
let data = "In ut magna vel mauris malesuada";
|
let data = "In ut magna vel mauris malesuada";
|
||||||
// let writer = AztecWriter{};
|
let writer = AztecWriter{};
|
||||||
let matrix = AztecWriter::encode(data, &BarcodeFormat::AZTEC, 0, 0).expect("matrix must exist");
|
let matrix = writer.encode(data, &BarcodeFormat::AZTEC, 0, 0).expect("matrix must exist");
|
||||||
let aztec = encoder::encode(
|
let aztec = encoder::encode(
|
||||||
data,
|
data,
|
||||||
encoder::DEFAULT_EC_PERCENT,
|
encoder::DEFAULT_EC_PERCENT,
|
||||||
@@ -732,7 +732,7 @@ fn testWriter(
|
|||||||
EncodeHintType::ERROR_CORRECTION,
|
EncodeHintType::ERROR_CORRECTION,
|
||||||
EncodeHintValue::ErrorCorrection(ecc_percent.to_string()),
|
EncodeHintValue::ErrorCorrection(ecc_percent.to_string()),
|
||||||
);
|
);
|
||||||
let mut matrix = AztecWriter::encode_with_hints(data, &BarcodeFormat::AZTEC, 0, 0, &hints)
|
let mut matrix = AztecWriter{}.encode_with_hints(data, &BarcodeFormat::AZTEC, 0, 0, &hints)
|
||||||
.expect("encoder created");
|
.expect("encoder created");
|
||||||
|
|
||||||
let cset = match charset {
|
let cset = match charset {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ use crate::{
|
|||||||
common::{DecoderRXingResult, DetectorRXingResult},
|
common::{DecoderRXingResult, DetectorRXingResult},
|
||||||
exceptions::Exceptions,
|
exceptions::Exceptions,
|
||||||
BarcodeFormat, BinaryBitmap, DecodeHintType, DecodeHintValue, RXingResult,
|
BarcodeFormat, BinaryBitmap, DecodeHintType, DecodeHintValue, RXingResult,
|
||||||
RXingResultMetadataType, Reader, RXingResultMetadataValue,
|
RXingResultMetadataType, RXingResultMetadataValue, Reader,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{decoder, detector::Detector};
|
use super::{decoder, detector::Detector};
|
||||||
@@ -40,7 +40,7 @@ impl Reader for AztecReader {
|
|||||||
* @throws NotFoundException if a Data Matrix code cannot be found
|
* @throws NotFoundException if a Data Matrix code cannot be found
|
||||||
* @throws FormatException if a Data Matrix code cannot be decoded
|
* @throws FormatException if a Data Matrix code cannot be decoded
|
||||||
*/
|
*/
|
||||||
fn decode(&self,image: &BinaryBitmap) -> Result<RXingResult, Exceptions> {
|
fn decode(&self, image: &BinaryBitmap) -> Result<RXingResult, Exceptions> {
|
||||||
self.decode_with_hints(image, &HashMap::new())
|
self.decode_with_hints(image, &HashMap::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,11 +52,19 @@ impl Reader for AztecReader {
|
|||||||
// let notFoundException = None;
|
// let notFoundException = None;
|
||||||
// let formatException = None;
|
// let formatException = None;
|
||||||
let mut detector = Detector::new(image.getBlackMatrix()?.clone());
|
let mut detector = Detector::new(image.getBlackMatrix()?.clone());
|
||||||
let mut points;
|
let points;
|
||||||
let mut decoderRXingResult: DecoderRXingResult;
|
let decoderRXingResult: DecoderRXingResult;
|
||||||
// try {
|
// try {
|
||||||
|
|
||||||
let detectorRXingResult = detector.detect(false)?;
|
let detectorRXingResult = if let Ok(det) = detector.detect(false) {
|
||||||
|
det
|
||||||
|
} else if let Ok(det) = detector.detect(true) {
|
||||||
|
det
|
||||||
|
} else {
|
||||||
|
return Err(Exceptions::NotFoundException(
|
||||||
|
"barcode not found".to_owned(),
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
points = detectorRXingResult.getPoints();
|
points = detectorRXingResult.getPoints();
|
||||||
decoderRXingResult = decoder::decode(&detectorRXingResult)?;
|
decoderRXingResult = decoder::decode(&detectorRXingResult)?;
|
||||||
@@ -67,9 +75,9 @@ impl Reader for AztecReader {
|
|||||||
// }
|
// }
|
||||||
// if (decoderRXingResult == null) {
|
// if (decoderRXingResult == null) {
|
||||||
// try {
|
// try {
|
||||||
let detectorRXingResult = detector.detect(true)?;
|
// let detectorRXingResult = detector.detect(true)?;
|
||||||
points = detectorRXingResult.getPoints();
|
// points = detectorRXingResult.getPoints();
|
||||||
decoderRXingResult = decoder::decode(&detectorRXingResult)?;
|
// decoderRXingResult = decoder::decode(&detectorRXingResult)?;
|
||||||
// } catch (NotFoundException | FormatException e) {
|
// } catch (NotFoundException | FormatException e) {
|
||||||
// if (notFoundException != null) {
|
// if (notFoundException != null) {
|
||||||
// throw notFoundException;
|
// throw notFoundException;
|
||||||
@@ -101,19 +109,26 @@ impl Reader for AztecReader {
|
|||||||
.as_millis(),
|
.as_millis(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let byteSegments = decoderRXingResult.getByteSegments();
|
let byteSegments = decoderRXingResult.getByteSegments();
|
||||||
if !byteSegments.is_empty() {
|
if !byteSegments.is_empty() {
|
||||||
result.putMetadata(RXingResultMetadataType::BYTE_SEGMENTS, RXingResultMetadataValue::ByteSegments(byteSegments.clone()));
|
result.putMetadata(
|
||||||
|
RXingResultMetadataType::BYTE_SEGMENTS,
|
||||||
|
RXingResultMetadataValue::ByteSegments(byteSegments.clone()),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
let ecLevel = decoderRXingResult.getECLevel();
|
let ecLevel = decoderRXingResult.getECLevel();
|
||||||
if !ecLevel.is_empty() {
|
if !ecLevel.is_empty() {
|
||||||
result.putMetadata(RXingResultMetadataType::ERROR_CORRECTION_LEVEL, RXingResultMetadataValue::ErrorCorrectionLevel(ecLevel.to_owned()));
|
result.putMetadata(
|
||||||
|
RXingResultMetadataType::ERROR_CORRECTION_LEVEL,
|
||||||
|
RXingResultMetadataValue::ErrorCorrectionLevel(ecLevel.to_owned()),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
result.putMetadata(
|
result.putMetadata(
|
||||||
RXingResultMetadataType::SYMBOLOGY_IDENTIFIER,
|
RXingResultMetadataType::SYMBOLOGY_IDENTIFIER,
|
||||||
RXingResultMetadataValue::SymbologyIdentifier(format!("]z{}", decoderRXingResult.getSymbologyModifier())),
|
RXingResultMetadataValue::SymbologyIdentifier(format!(
|
||||||
|
"]z{}",
|
||||||
|
decoderRXingResult.getSymbologyModifier()
|
||||||
|
)),
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
|
|||||||
@@ -21,12 +21,12 @@
|
|||||||
// import java.awt.image.BufferedImage;
|
// import java.awt.image.BufferedImage;
|
||||||
// import java.awt.image.WritableRaster;
|
// import java.awt.image.WritableRaster;
|
||||||
|
|
||||||
use image::{DynamicImage, Luma};
|
use image::{DynamicImage, Luma, GenericImage, EncodableLayout};
|
||||||
use imageproc::geometric_transformations::rotate_about_center;
|
use imageproc::geometric_transformations::rotate_about_center;
|
||||||
|
|
||||||
use crate::LuminanceSource;
|
use crate::LuminanceSource;
|
||||||
|
|
||||||
static MINUS_45_IN_RADIANS: f32 = -0.7853981633974483; // Math.toRadians(-45.0)
|
const MINUS_45_IN_RADIANS: f32 = -0.7853981633974483; // Math.toRadians(-45.0)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This LuminanceSource implementation is meant for J2SE clients and our blackbox unit tests.
|
* This LuminanceSource implementation is meant for J2SE clients and our blackbox unit tests.
|
||||||
@@ -98,8 +98,9 @@ impl BufferedImageLuminanceSource {
|
|||||||
|
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
image: image,
|
image: DynamicImage::from(image.to_luma8()),
|
||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
left: left,
|
left: left,
|
||||||
@@ -189,8 +190,8 @@ impl LuminanceSource for BufferedImageLuminanceSource {
|
|||||||
let img = rotate_about_center(
|
let img = rotate_about_center(
|
||||||
&self.image.to_luma8(),
|
&self.image.to_luma8(),
|
||||||
MINUS_45_IN_RADIANS,
|
MINUS_45_IN_RADIANS,
|
||||||
imageproc::geometric_transformations::Interpolation::Bicubic,
|
imageproc::geometric_transformations::Interpolation::Nearest,
|
||||||
Luma([0; 1]),
|
Luma([255; 1]),
|
||||||
);
|
);
|
||||||
|
|
||||||
let new_img = DynamicImage::from(img);
|
let new_img = DynamicImage::from(img);
|
||||||
@@ -3829,37 +3829,38 @@ impl Binarizer for HybridBinarizer {
|
|||||||
&& height >= HybridBinarizer::MINIMUM_DIMENSION
|
&& height >= HybridBinarizer::MINIMUM_DIMENSION
|
||||||
{
|
{
|
||||||
let luminances = source.getMatrix();
|
let luminances = source.getMatrix();
|
||||||
let mut subWidth = width >> HybridBinarizer::BLOCK_SIZE_POWER;
|
let mut sub_width = width >> HybridBinarizer::BLOCK_SIZE_POWER;
|
||||||
if (width & HybridBinarizer::BLOCK_SIZE_MASK) != 0 {
|
if (width & HybridBinarizer::BLOCK_SIZE_MASK) != 0 {
|
||||||
subWidth += 1;
|
sub_width += 1;
|
||||||
}
|
}
|
||||||
let mut subHeight = height >> HybridBinarizer::BLOCK_SIZE_POWER;
|
let mut sub_height = height >> HybridBinarizer::BLOCK_SIZE_POWER;
|
||||||
if (height & HybridBinarizer::BLOCK_SIZE_MASK) != 0 {
|
if (height & HybridBinarizer::BLOCK_SIZE_MASK) != 0 {
|
||||||
subHeight += 1;
|
sub_height += 1;
|
||||||
}
|
}
|
||||||
let blackPoints = Self::calculateBlackPoints(
|
let black_points = Self::calculateBlackPoints(
|
||||||
&luminances,
|
&luminances,
|
||||||
subWidth as u32,
|
sub_width as u32,
|
||||||
subHeight as u32,
|
sub_height as u32,
|
||||||
width as u32,
|
width as u32,
|
||||||
height as u32,
|
height as u32,
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut newMatrix = BitMatrix::new(width as u32, height as u32)?;
|
let mut new_matrix = BitMatrix::new(width as u32, height as u32)?;
|
||||||
Self::calculateThresholdForBlock(
|
Self::calculateThresholdForBlock(
|
||||||
&luminances,
|
&luminances,
|
||||||
subWidth as u32,
|
sub_width as u32,
|
||||||
subHeight as u32,
|
sub_height as u32,
|
||||||
width as u32,
|
width as u32,
|
||||||
height as u32,
|
height as u32,
|
||||||
&blackPoints,
|
&black_points,
|
||||||
&mut newMatrix,
|
&mut new_matrix,
|
||||||
);
|
);
|
||||||
matrix = newMatrix;
|
matrix = new_matrix;
|
||||||
} else {
|
} else {
|
||||||
// If the image is too small, fall back to the global histogram approach.
|
// If the image is too small, fall back to the global histogram approach.
|
||||||
matrix = self.ghb.getBlackMatrix()?;
|
matrix = self.ghb.getBlackMatrix()?;
|
||||||
}
|
}
|
||||||
|
// dbg!(matrix.to_string());
|
||||||
Ok(matrix)
|
Ok(matrix)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3997,16 +3998,16 @@ impl HybridBinarizer {
|
|||||||
if xoffset > maxXOffset as u32 {
|
if xoffset > maxXOffset as u32 {
|
||||||
xoffset = maxXOffset as u32;
|
xoffset = maxXOffset as u32;
|
||||||
}
|
}
|
||||||
let mut sum = 0;
|
let mut sum = 0u32;
|
||||||
let mut min = 0xFF;
|
let mut min = 0;
|
||||||
let mut max = 0;
|
let mut max = 0xFF;
|
||||||
let mut offset = yoffset * width + xoffset;
|
let mut offset = yoffset * width + xoffset;
|
||||||
for yy in 0..HybridBinarizer::BLOCK_SIZE {
|
for yy in 0..HybridBinarizer::BLOCK_SIZE {
|
||||||
// for (int yy = 0, offset = yoffset * width + xoffset; yy < HybridBinarizer::BLOCK_SIZE; yy++, offset += width) {
|
// for (int yy = 0, offset = yoffset * width + xoffset; yy < HybridBinarizer::BLOCK_SIZE; yy++, offset += width) {
|
||||||
for xx in 0..HybridBinarizer::BLOCK_SIZE {
|
for xx in 0..HybridBinarizer::BLOCK_SIZE {
|
||||||
// for (int xx = 0; xx < HybridBinarizer::BLOCK_SIZE; xx++) {
|
// for (int xx = 0; xx < HybridBinarizer::BLOCK_SIZE; xx++) {
|
||||||
let pixel = luminances[offset as usize + xx];
|
let pixel = luminances[offset as usize + xx];
|
||||||
sum += pixel;
|
sum += pixel as u32;
|
||||||
// still looking for good contrast
|
// still looking for good contrast
|
||||||
if pixel < min {
|
if pixel < min {
|
||||||
min = pixel;
|
min = pixel;
|
||||||
@@ -4023,7 +4024,7 @@ impl HybridBinarizer {
|
|||||||
// for (yy++, offset += width; yy < HybridBinarizer::BLOCK_SIZE; yy++, offset += width) {
|
// for (yy++, offset += width; yy < HybridBinarizer::BLOCK_SIZE; yy++, offset += width) {
|
||||||
for xx in 0..HybridBinarizer::BLOCK_SIZE {
|
for xx in 0..HybridBinarizer::BLOCK_SIZE {
|
||||||
// for (int xx = 0; xx < BLOCK_SIZE; xx++) {
|
// for (int xx = 0; xx < BLOCK_SIZE; xx++) {
|
||||||
sum += luminances[offset as usize + xx];
|
sum += luminances[offset as usize + xx] as u32;
|
||||||
}
|
}
|
||||||
offset += width;
|
offset += width;
|
||||||
}
|
}
|
||||||
@@ -4041,7 +4042,7 @@ impl HybridBinarizer {
|
|||||||
//
|
//
|
||||||
// The default assumption is that the block is light/background. Since no estimate for
|
// The default assumption is that the block is light/background. Since no estimate for
|
||||||
// the level of dark pixels exists locally, use half the min for the block.
|
// the level of dark pixels exists locally, use half the min for the block.
|
||||||
average = min / 2;
|
average = min as u32 / 2;
|
||||||
|
|
||||||
if y > 0 && x > 0 {
|
if y > 0 && x > 0 {
|
||||||
// Correct the "white background" assumption for blocks that have neighbors by comparing
|
// Correct the "white background" assumption for blocks that have neighbors by comparing
|
||||||
@@ -4051,16 +4052,16 @@ impl HybridBinarizer {
|
|||||||
// the boundaries is used for the interior.
|
// the boundaries is used for the interior.
|
||||||
|
|
||||||
// The (min < bp) is arbitrary but works better than other heuristics that were tried.
|
// The (min < bp) is arbitrary but works better than other heuristics that were tried.
|
||||||
let averageNeighborBlackPoint = (blackPoints[y as usize - 1][x as usize]
|
let average_neighbor_black_point = (blackPoints[y as usize - 1][x as usize]
|
||||||
+ (2 * blackPoints[y as usize][x as usize - 1])
|
+ (2 * blackPoints[y as usize][x as usize - 1])
|
||||||
+ blackPoints[y as usize - 1][x as usize - 1])
|
+ blackPoints[y as usize - 1][x as usize - 1])
|
||||||
/ 4;
|
/ 4;
|
||||||
if (min < averageNeighborBlackPoint) {
|
if (min < average_neighbor_black_point) {
|
||||||
average = averageNeighborBlackPoint;
|
average = average_neighbor_black_point as u32;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
blackPoints[y as usize][x as usize] = average;
|
blackPoints[y as usize][x as usize] = average as u8;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return blackPoints
|
return blackPoints
|
||||||
|
|||||||
50
src/lib.rs
50
src/lib.rs
@@ -1,13 +1,15 @@
|
|||||||
mod aztec;
|
pub mod aztec;
|
||||||
mod client;
|
pub mod client;
|
||||||
mod common;
|
pub mod common;
|
||||||
mod exceptions;
|
mod exceptions;
|
||||||
|
|
||||||
#[cfg(feature = "image")]
|
pub use exceptions::Exceptions;
|
||||||
mod BufferedImageLuminanceSource;
|
|
||||||
|
|
||||||
#[cfg(feature = "image")]
|
#[cfg(feature = "image")]
|
||||||
pub use BufferedImageLuminanceSource::*;
|
mod buffered_image_luminance_source;
|
||||||
|
|
||||||
|
#[cfg(feature = "image")]
|
||||||
|
pub use buffered_image_luminance_source::*;
|
||||||
|
|
||||||
use crate::common::{BitArray, BitMatrix};
|
use crate::common::{BitArray, BitMatrix};
|
||||||
use exceptions::*;
|
use exceptions::*;
|
||||||
@@ -454,7 +456,7 @@ pub enum EncodeHintValue {
|
|||||||
* @author dswitkin@google.com (Daniel Switkin)
|
* @author dswitkin@google.com (Daniel Switkin)
|
||||||
* @see Reader#decode(BinaryBitmap,java.util.Map)
|
* @see Reader#decode(BinaryBitmap,java.util.Map)
|
||||||
*/
|
*/
|
||||||
#[derive(Eq, PartialEq, Hash, Debug)]
|
#[derive(Eq, PartialEq, Hash, Debug, Clone, Copy)]
|
||||||
pub enum DecodeHintType {
|
pub enum DecodeHintType {
|
||||||
/**
|
/**
|
||||||
* Unspecified, application-specific hint. Maps to an unspecified {@link Object}.
|
* Unspecified, application-specific hint. Maps to an unspecified {@link Object}.
|
||||||
@@ -558,7 +560,7 @@ pub enum DecodeHintType {
|
|||||||
* @see DecodeHintType#NEED_RESULT_POINT_CALLBACK
|
* @see DecodeHintType#NEED_RESULT_POINT_CALLBACK
|
||||||
*/
|
*/
|
||||||
pub type RXingResultPointCallback = fn(&RXingResultPoint);
|
pub type RXingResultPointCallback = fn(&RXingResultPoint);
|
||||||
|
#[derive(Clone)]
|
||||||
pub enum DecodeHintValue {
|
pub enum DecodeHintValue {
|
||||||
/**
|
/**
|
||||||
* Unspecified, application-specific hint. Maps to an unspecified {@link Object}.
|
* Unspecified, application-specific hint. Maps to an unspecified {@link Object}.
|
||||||
@@ -671,6 +673,7 @@ pub trait Writer {
|
|||||||
* @throws WriterException if contents cannot be encoded legally in a format
|
* @throws WriterException if contents cannot be encoded legally in a format
|
||||||
*/
|
*/
|
||||||
fn encode(
|
fn encode(
|
||||||
|
&self,
|
||||||
contents: &str,
|
contents: &str,
|
||||||
format: &BarcodeFormat,
|
format: &BarcodeFormat,
|
||||||
width: i32,
|
width: i32,
|
||||||
@@ -687,6 +690,7 @@ pub trait Writer {
|
|||||||
* @throws WriterException if contents cannot be encoded legally in a format
|
* @throws WriterException if contents cannot be encoded legally in a format
|
||||||
*/
|
*/
|
||||||
fn encode_with_hints(
|
fn encode_with_hints(
|
||||||
|
&self,
|
||||||
contents: &str,
|
contents: &str,
|
||||||
format: &BarcodeFormat,
|
format: &BarcodeFormat,
|
||||||
width: i32,
|
width: i32,
|
||||||
@@ -735,7 +739,7 @@ pub trait Reader {
|
|||||||
* @throws ChecksumException if a potential barcode is found but does not pass its checksum
|
* @throws ChecksumException if a potential barcode is found but does not pass its checksum
|
||||||
* @throws FormatException if a potential barcode is found but format is invalid
|
* @throws FormatException if a potential barcode is found but format is invalid
|
||||||
*/
|
*/
|
||||||
fn decode(image: &BinaryBitmap) -> Result<RXingResult, Exceptions>;
|
fn decode(&self, image: &BinaryBitmap) -> Result<RXingResult, Exceptions>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Locates and decodes a barcode in some format within an image. This method also accepts
|
* Locates and decodes a barcode in some format within an image. This method also accepts
|
||||||
@@ -752,6 +756,7 @@ pub trait Reader {
|
|||||||
* @throws FormatException if a potential barcode is found but format is invalid
|
* @throws FormatException if a potential barcode is found but format is invalid
|
||||||
*/
|
*/
|
||||||
fn decode_with_hints(
|
fn decode_with_hints(
|
||||||
|
&self,
|
||||||
image: &BinaryBitmap,
|
image: &BinaryBitmap,
|
||||||
hints: &HashMap<DecodeHintType, DecodeHintValue>,
|
hints: &HashMap<DecodeHintType, DecodeHintValue>,
|
||||||
) -> Result<RXingResult, Exceptions>;
|
) -> Result<RXingResult, Exceptions>;
|
||||||
@@ -760,7 +765,7 @@ pub trait Reader {
|
|||||||
* Resets any internal state the implementation has after a decode, to prepare it
|
* Resets any internal state the implementation has after a decode, to prepare it
|
||||||
* for reuse.
|
* for reuse.
|
||||||
*/
|
*/
|
||||||
fn reset();
|
fn reset(&self);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -787,7 +792,7 @@ pub trait Reader {
|
|||||||
*
|
*
|
||||||
* @author Sean Owen
|
* @author Sean Owen
|
||||||
*/
|
*/
|
||||||
#[derive(Eq, PartialEq, Hash)]
|
#[derive(Eq, PartialEq, Hash, Debug)]
|
||||||
pub enum RXingResultMetadataType {
|
pub enum RXingResultMetadataType {
|
||||||
/**
|
/**
|
||||||
* Unspecified, application-specific metadata. Maps to an unspecified {@link Object}.
|
* Unspecified, application-specific metadata. Maps to an unspecified {@link Object}.
|
||||||
@@ -867,11 +872,32 @@ pub enum RXingResultMetadataType {
|
|||||||
SYMBOLOGY_IDENTIFIER,
|
SYMBOLOGY_IDENTIFIER,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<String> for RXingResultMetadataType {
|
||||||
|
fn from(in_str: String) -> Self {
|
||||||
|
match in_str.as_str() {
|
||||||
|
"OTHER" => RXingResultMetadataType::OTHER,
|
||||||
|
"ORIENTATION" => RXingResultMetadataType::ORIENTATION,
|
||||||
|
"BYTE_SEGMENTS" => RXingResultMetadataType::BYTE_SEGMENTS,
|
||||||
|
"ERROR_CORRECTION_LEVEL"=> RXingResultMetadataType::ERROR_CORRECTION_LEVEL,
|
||||||
|
"ISSUE_NUMBER"=> RXingResultMetadataType::ISSUE_NUMBER,
|
||||||
|
"SUGGESTED_PRICE"=> RXingResultMetadataType::SUGGESTED_PRICE,
|
||||||
|
"POSSIBLE_COUNTRY"=> RXingResultMetadataType::POSSIBLE_COUNTRY,
|
||||||
|
"UPC_EAN_EXTENSION"=>RXingResultMetadataType::UPC_EAN_EXTENSION,
|
||||||
|
"PDF417_EXTRA_METADATA"=> RXingResultMetadataType::PDF417_EXTRA_METADATA,
|
||||||
|
"STRUCTURED_APPEND_SEQUENCE"=> RXingResultMetadataType::STRUCTURED_APPEND_SEQUENCE,
|
||||||
|
"STRUCTURED_APPEND_PARITY"=>RXingResultMetadataType::STRUCTURED_APPEND_PARITY,
|
||||||
|
"SYMBOLOGY_IDENTIFIER"=>RXingResultMetadataType::SYMBOLOGY_IDENTIFIER,
|
||||||
|
_ => RXingResultMetadataType::OTHER,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug,PartialEq, Eq)]
|
||||||
pub enum RXingResultMetadataValue {
|
pub enum RXingResultMetadataValue {
|
||||||
/**
|
/**
|
||||||
* Unspecified, application-specific metadata. Maps to an unspecified {@link Object}.
|
* Unspecified, application-specific metadata. Maps to an unspecified {@link Object}.
|
||||||
*/
|
*/
|
||||||
OTHER,
|
OTHER(String),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Denotes the likely approximate orientation of the barcode in the image. This value
|
* Denotes the likely approximate orientation of the barcode in the image. This value
|
||||||
|
|||||||
@@ -11,6 +11,9 @@
|
|||||||
.bg_color {
|
.bg_color {
|
||||||
background-color: lightblue;
|
background-color: lightblue;
|
||||||
}
|
}
|
||||||
|
.super_small {
|
||||||
|
font-size: xx-small;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<script>
|
<script>
|
||||||
function token_text(raw_text, true_value, false_value) {
|
function token_text(raw_text, true_value, false_value) {
|
||||||
@@ -124,6 +127,10 @@
|
|||||||
new_th.appendChild(new_child_node);
|
new_th.appendChild(new_child_node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (column_count > 80) {
|
||||||
|
new_table.classList.add("super_small");
|
||||||
|
}
|
||||||
|
|
||||||
root.replaceChildren(new_table);
|
root.replaceChildren(new_table);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,22 +14,30 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.google.zxing.aztec;
|
use rxing::{aztec::AztecReader, BarcodeFormat};
|
||||||
|
|
||||||
import com.google.zxing.BarcodeFormat;
|
mod common;
|
||||||
import com.google.zxing.common.AbstractBlackBoxTestCase;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author David Olivier
|
* @author David Olivier
|
||||||
*/
|
*/
|
||||||
public final class AztecBlackBox1TestCase extends AbstractBlackBoxTestCase {
|
// public final class AztecBlackBox1TestCase extends AbstractBlackBoxTestCase {
|
||||||
|
|
||||||
public AztecBlackBox1TestCase() {
|
#[test]
|
||||||
super("src/test/resources/blackbox/aztec-1", new AztecReader(), BarcodeFormat.AZTEC);
|
fn aztec_black_box1_test_case() {
|
||||||
addTest(14, 14, 0.0f);
|
let mut tester = common::AbstractBlackBoxTestCase::new(
|
||||||
addTest(14, 14, 90.0f);
|
"test_resources/blackbox/aztec-1",
|
||||||
addTest(14, 14, 180.0f);
|
Box::new(AztecReader {}),
|
||||||
addTest(14, 14, 270.0f);
|
BarcodeFormat::AZTEC,
|
||||||
}
|
);
|
||||||
|
|
||||||
|
// super("src/test/resources/blackbox/aztec-1", AztecReader::new(), BarcodeFormat::AZTEC);
|
||||||
|
tester.addTest(14, 14, 0.0);
|
||||||
|
tester.addTest(14, 14, 90.0);
|
||||||
|
tester.addTest(14, 14, 180.0);
|
||||||
|
tester.addTest(14, 14, 270.0);
|
||||||
|
|
||||||
|
tester.testBlackBox();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// }
|
||||||
@@ -14,24 +14,26 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.google.zxing.aztec;
|
use rxing::{aztec::AztecReader, BarcodeFormat};
|
||||||
|
|
||||||
import com.google.zxing.BarcodeFormat;
|
mod common;
|
||||||
import com.google.zxing.common.AbstractBlackBoxTestCase;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A test of Aztec barcodes under real world lighting conditions, taken with a mobile phone.
|
* A test of Aztec barcodes under real world lighting conditions, taken with a mobile phone.
|
||||||
*
|
*
|
||||||
* @author dswitkin@google.com (Daniel Switkin)
|
* @author dswitkin@google.com (Daniel Switkin)
|
||||||
*/
|
*/
|
||||||
public final class AztecBlackBox2TestCase extends AbstractBlackBoxTestCase {
|
#[test]
|
||||||
|
fn AztecBlackBox2TestCase() {
|
||||||
|
let mut tester = common::AbstractBlackBoxTestCase::new(
|
||||||
|
"test_resources/blackbox/aztec-2",
|
||||||
|
Box::new(AztecReader{}),
|
||||||
|
BarcodeFormat::AZTEC);
|
||||||
|
// super(, new AztecReader(), BarcodeFormat.AZTEC);
|
||||||
|
tester.addTest(5, 5, 0.0);
|
||||||
|
tester.addTest(4, 4, 90.0);
|
||||||
|
tester.addTest(6, 6, 180.0);
|
||||||
|
tester.addTest(3, 3, 270.0);
|
||||||
|
|
||||||
public AztecBlackBox2TestCase() {
|
tester.testBlackBox();
|
||||||
super("src/test/resources/blackbox/aztec-2", new AztecReader(), BarcodeFormat.AZTEC);
|
|
||||||
addTest(5, 5, 0.0f);
|
|
||||||
addTest(4, 4, 90.0f);
|
|
||||||
addTest(6, 6, 180.0f);
|
|
||||||
addTest(3, 3, 270.0f);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,357 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2008 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.common;
|
|
||||||
|
|
||||||
import com.google.zxing.BarcodeFormat;
|
|
||||||
import com.google.zxing.BinaryBitmap;
|
|
||||||
import com.google.zxing.BufferedImageLuminanceSource;
|
|
||||||
import com.google.zxing.DecodeHintType;
|
|
||||||
import com.google.zxing.LuminanceSource;
|
|
||||||
import com.google.zxing.Reader;
|
|
||||||
import com.google.zxing.ReaderException;
|
|
||||||
import com.google.zxing.RXingResult;
|
|
||||||
import com.google.zxing.RXingResultMetadataType;
|
|
||||||
import org.junit.Assert;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
|
||||||
import java.awt.Graphics;
|
|
||||||
import java.awt.geom.AffineTransform;
|
|
||||||
import java.awt.geom.RectangularShape;
|
|
||||||
import java.awt.image.AffineTransformOp;
|
|
||||||
import java.awt.image.BufferedImage;
|
|
||||||
import java.awt.image.BufferedImageOp;
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.charset.Charset;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.nio.file.DirectoryStream;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.EnumMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Properties;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Sean Owen
|
|
||||||
* @author dswitkin@google.com (Daniel Switkin)
|
|
||||||
*/
|
|
||||||
public abstract class AbstractBlackBoxTestCase extends Assert {
|
|
||||||
|
|
||||||
private static final Logger log = Logger.getLogger(AbstractBlackBoxTestCase.class.getSimpleName());
|
|
||||||
|
|
||||||
private final Path testBase;
|
|
||||||
private final Reader barcodeReader;
|
|
||||||
private final BarcodeFormat expectedFormat;
|
|
||||||
private final List<TestRXingResult> testRXingResults;
|
|
||||||
private final EnumMap<DecodeHintType,Object> hints = new EnumMap<>(DecodeHintType.class);
|
|
||||||
|
|
||||||
public static Path buildTestBase(String testBasePathSuffix) {
|
|
||||||
// A little workaround to prevent aggravation in my IDE
|
|
||||||
Path testBase = Paths.get(testBasePathSuffix);
|
|
||||||
if (!Files.exists(testBase)) {
|
|
||||||
// try starting with 'core' since the test base is often given as the project root
|
|
||||||
testBase = Paths.get("core").resolve(testBasePathSuffix);
|
|
||||||
}
|
|
||||||
return testBase;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected AbstractBlackBoxTestCase(String testBasePathSuffix,
|
|
||||||
Reader barcodeReader,
|
|
||||||
BarcodeFormat expectedFormat) {
|
|
||||||
this.testBase = buildTestBase(testBasePathSuffix);
|
|
||||||
this.barcodeReader = barcodeReader;
|
|
||||||
this.expectedFormat = expectedFormat;
|
|
||||||
testRXingResults = new ArrayList<>();
|
|
||||||
|
|
||||||
System.setProperty("java.util.logging.SimpleFormatter.format", "%4$s: %5$s%6$s%n");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected final Path getTestBase() {
|
|
||||||
return testBase;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected final void addTest(int mustPassCount, int tryHarderCount, float rotation) {
|
|
||||||
addTest(mustPassCount, tryHarderCount, 0, 0, rotation);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void addHint(DecodeHintType hint) {
|
|
||||||
hints.put(hint, Boolean.TRUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a new test for the current directory of images.
|
|
||||||
*
|
|
||||||
* @param mustPassCount The number of images which must decode for the test to pass.
|
|
||||||
* @param tryHarderCount The number of images which must pass using the try harder flag.
|
|
||||||
* @param maxMisreads Maximum number of images which can fail due to successfully reading the wrong contents
|
|
||||||
* @param maxTryHarderMisreads Maximum number of images which can fail due to successfully
|
|
||||||
* reading the wrong contents using the try harder flag
|
|
||||||
* @param rotation The rotation in degrees clockwise to use for this test.
|
|
||||||
*/
|
|
||||||
protected final void addTest(int mustPassCount,
|
|
||||||
int tryHarderCount,
|
|
||||||
int maxMisreads,
|
|
||||||
int maxTryHarderMisreads,
|
|
||||||
float rotation) {
|
|
||||||
testRXingResults.add(new TestRXingResult(mustPassCount, tryHarderCount, maxMisreads, maxTryHarderMisreads, rotation));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected final List<Path> getImageFiles() throws IOException {
|
|
||||||
assertTrue("Please download and install test images, and run from the 'core' directory", Files.exists(testBase));
|
|
||||||
List<Path> paths = new ArrayList<>();
|
|
||||||
try (DirectoryStream<Path> pathIt = Files.newDirectoryStream(testBase, "*.{jpg,jpeg,gif,png,JPG,JPEG,GIF,PNG}")) {
|
|
||||||
for (Path path : pathIt) {
|
|
||||||
paths.add(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return paths;
|
|
||||||
}
|
|
||||||
|
|
||||||
final Reader getReader() {
|
|
||||||
return barcodeReader;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testBlackBox() throws IOException {
|
|
||||||
assertFalse(testRXingResults.isEmpty());
|
|
||||||
|
|
||||||
List<Path> imageFiles = getImageFiles();
|
|
||||||
int testCount = testRXingResults.size();
|
|
||||||
|
|
||||||
int[] passedCounts = new int[testCount];
|
|
||||||
int[] misreadCounts = new int[testCount];
|
|
||||||
int[] tryHarderCounts = new int[testCount];
|
|
||||||
int[] tryHarderMisreadCounts = new int[testCount];
|
|
||||||
|
|
||||||
for (Path testImage : imageFiles) {
|
|
||||||
log.info(String.format("Starting %s", testImage));
|
|
||||||
|
|
||||||
BufferedImage image = ImageIO.read(testImage.toFile());
|
|
||||||
|
|
||||||
String testImageFileName = testImage.getFileName().toString();
|
|
||||||
String fileBaseName = testImageFileName.substring(0, testImageFileName.indexOf('.'));
|
|
||||||
Path expectedTextFile = testBase.resolve(fileBaseName + ".txt");
|
|
||||||
String expectedText;
|
|
||||||
if (Files.exists(expectedTextFile)) {
|
|
||||||
expectedText = readFileAsString(expectedTextFile, StandardCharsets.UTF_8);
|
|
||||||
} else {
|
|
||||||
expectedTextFile = testBase.resolve(fileBaseName + ".bin");
|
|
||||||
assertTrue(Files.exists(expectedTextFile));
|
|
||||||
expectedText = readFileAsString(expectedTextFile, StandardCharsets.ISO_8859_1);
|
|
||||||
}
|
|
||||||
|
|
||||||
Path expectedMetadataFile = testBase.resolve(fileBaseName + ".metadata.txt");
|
|
||||||
Properties expectedMetadata = new Properties();
|
|
||||||
if (Files.exists(expectedMetadataFile)) {
|
|
||||||
try (BufferedReader reader = Files.newBufferedReader(expectedMetadataFile, StandardCharsets.UTF_8)) {
|
|
||||||
expectedMetadata.load(reader);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int x = 0; x < testCount; x++) {
|
|
||||||
float rotation = testRXingResults.get(x).getRotation();
|
|
||||||
BufferedImage rotatedImage = rotateImage(image, rotation);
|
|
||||||
LuminanceSource source = new BufferedImageLuminanceSource(rotatedImage);
|
|
||||||
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
|
|
||||||
try {
|
|
||||||
if (decode(bitmap, rotation, expectedText, expectedMetadata, false)) {
|
|
||||||
passedCounts[x]++;
|
|
||||||
} else {
|
|
||||||
misreadCounts[x]++;
|
|
||||||
}
|
|
||||||
} catch (ReaderException ignored) {
|
|
||||||
log.fine(String.format("could not read at rotation %f", rotation));
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
if (decode(bitmap, rotation, expectedText, expectedMetadata, true)) {
|
|
||||||
tryHarderCounts[x]++;
|
|
||||||
} else {
|
|
||||||
tryHarderMisreadCounts[x]++;
|
|
||||||
}
|
|
||||||
} catch (ReaderException ignored) {
|
|
||||||
log.fine(String.format("could not read at rotation %f w/TH", rotation));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print the results of all tests first
|
|
||||||
int totalFound = 0;
|
|
||||||
int totalMustPass = 0;
|
|
||||||
int totalMisread = 0;
|
|
||||||
int totalMaxMisread = 0;
|
|
||||||
|
|
||||||
for (int x = 0; x < testRXingResults.size(); x++) {
|
|
||||||
TestRXingResult testRXingResult = testRXingResults.get(x);
|
|
||||||
log.info(String.format("Rotation %d degrees:", (int) testRXingResult.getRotation()));
|
|
||||||
log.info(String.format(" %d of %d images passed (%d required)",
|
|
||||||
passedCounts[x], imageFiles.size(), testRXingResult.getMustPassCount()));
|
|
||||||
int failed = imageFiles.size() - passedCounts[x];
|
|
||||||
log.info(String.format(" %d failed due to misreads, %d not detected",
|
|
||||||
misreadCounts[x], failed - misreadCounts[x]));
|
|
||||||
log.info(String.format(" %d of %d images passed with try harder (%d required)",
|
|
||||||
tryHarderCounts[x], imageFiles.size(), testRXingResult.getTryHarderCount()));
|
|
||||||
failed = imageFiles.size() - tryHarderCounts[x];
|
|
||||||
log.info(String.format(" %d failed due to misreads, %d not detected",
|
|
||||||
tryHarderMisreadCounts[x], failed - tryHarderMisreadCounts[x]));
|
|
||||||
totalFound += passedCounts[x] + tryHarderCounts[x];
|
|
||||||
totalMustPass += testRXingResult.getMustPassCount() + testRXingResult.getTryHarderCount();
|
|
||||||
totalMisread += misreadCounts[x] + tryHarderMisreadCounts[x];
|
|
||||||
totalMaxMisread += testRXingResult.getMaxMisreads() + testRXingResult.getMaxTryHarderMisreads();
|
|
||||||
}
|
|
||||||
|
|
||||||
int totalTests = imageFiles.size() * testCount * 2;
|
|
||||||
log.info(String.format("Decoded %d images out of %d (%d%%, %d required)",
|
|
||||||
totalFound, totalTests, totalFound * 100 / totalTests, totalMustPass));
|
|
||||||
if (totalFound > totalMustPass) {
|
|
||||||
log.warning(String.format("+++ Test too lax by %d images", totalFound - totalMustPass));
|
|
||||||
} else if (totalFound < totalMustPass) {
|
|
||||||
log.warning(String.format("--- Test failed by %d images", totalMustPass - totalFound));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (totalMisread < totalMaxMisread) {
|
|
||||||
log.warning(String.format("+++ Test expects too many misreads by %d images", totalMaxMisread - totalMisread));
|
|
||||||
} else if (totalMisread > totalMaxMisread) {
|
|
||||||
log.warning(String.format("--- Test had too many misreads by %d images", totalMisread - totalMaxMisread));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Then run through again and assert if any failed
|
|
||||||
for (int x = 0; x < testCount; x++) {
|
|
||||||
TestRXingResult testRXingResult = testRXingResults.get(x);
|
|
||||||
String label = "Rotation " + testRXingResult.getRotation() + " degrees: Too many images failed";
|
|
||||||
assertTrue(label,
|
|
||||||
passedCounts[x] >= testRXingResult.getMustPassCount());
|
|
||||||
assertTrue("Try harder, " + label,
|
|
||||||
tryHarderCounts[x] >= testRXingResult.getTryHarderCount());
|
|
||||||
label = "Rotation " + testRXingResult.getRotation() + " degrees: Too many images misread";
|
|
||||||
assertTrue(label,
|
|
||||||
misreadCounts[x] <= testRXingResult.getMaxMisreads());
|
|
||||||
assertTrue("Try harder, " + label,
|
|
||||||
tryHarderMisreadCounts[x] <= testRXingResult.getMaxTryHarderMisreads());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean decode(BinaryBitmap source,
|
|
||||||
float rotation,
|
|
||||||
String expectedText,
|
|
||||||
Map<?,?> expectedMetadata,
|
|
||||||
boolean tryHarder) throws ReaderException {
|
|
||||||
|
|
||||||
String suffix = String.format(" (%srotation: %d)", tryHarder ? "try harder, " : "", (int) rotation);
|
|
||||||
|
|
||||||
Map<DecodeHintType,Object> hints = this.hints.clone();
|
|
||||||
if (tryHarder) {
|
|
||||||
hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try in 'pure' mode mostly to exercise PURE_BARCODE code paths for exceptions;
|
|
||||||
// not expected to pass, generally
|
|
||||||
RXingResult result = null;
|
|
||||||
try {
|
|
||||||
Map<DecodeHintType,Object> pureHints = new EnumMap<>(hints);
|
|
||||||
pureHints.put(DecodeHintType.PURE_BARCODE, Boolean.TRUE);
|
|
||||||
result = barcodeReader.decode(source, pureHints);
|
|
||||||
} catch (ReaderException re) {
|
|
||||||
// continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result == null) {
|
|
||||||
result = barcodeReader.decode(source, hints);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (expectedFormat != result.getBarcodeFormat()) {
|
|
||||||
log.info(String.format("Format mismatch: expected '%s' but got '%s'%s",
|
|
||||||
expectedFormat, result.getBarcodeFormat(), suffix));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
String resultText = result.getText();
|
|
||||||
if (!expectedText.equals(resultText)) {
|
|
||||||
log.info(String.format("Content mismatch: expected '%s' but got '%s'%s",
|
|
||||||
expectedText, resultText, suffix));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<RXingResultMetadataType,?> resultMetadata = result.getRXingResultMetadata();
|
|
||||||
for (Map.Entry<?,?> metadatum : expectedMetadata.entrySet()) {
|
|
||||||
RXingResultMetadataType key = RXingResultMetadataType.valueOf(metadatum.getKey().toString());
|
|
||||||
Object expectedValue = metadatum.getValue();
|
|
||||||
Object actualValue = resultMetadata == null ? null : resultMetadata.get(key);
|
|
||||||
if (!expectedValue.equals(actualValue)) {
|
|
||||||
log.info(String.format("Metadata mismatch for key '%s': expected '%s' but got '%s'",
|
|
||||||
key, expectedValue, actualValue));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static String readFileAsString(Path file, Charset charset) throws IOException {
|
|
||||||
String stringContents = new String(Files.readAllBytes(file), charset);
|
|
||||||
if (stringContents.endsWith("\n")) {
|
|
||||||
log.info("String contents of file " + file + " end with a newline. " +
|
|
||||||
"This may not be intended and cause a test failure");
|
|
||||||
}
|
|
||||||
return stringContents;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static BufferedImage rotateImage(BufferedImage original, float degrees) {
|
|
||||||
if (degrees == 0.0f) {
|
|
||||||
return original;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (original.getType()) {
|
|
||||||
case BufferedImage.TYPE_BYTE_INDEXED:
|
|
||||||
case BufferedImage.TYPE_BYTE_BINARY:
|
|
||||||
BufferedImage argb = new BufferedImage(original.getWidth(),
|
|
||||||
original.getHeight(),
|
|
||||||
BufferedImage.TYPE_INT_ARGB);
|
|
||||||
Graphics g = argb.createGraphics();
|
|
||||||
g.drawImage(original, 0, 0, null);
|
|
||||||
g.dispose();
|
|
||||||
original = argb;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
double radians = Math.toRadians(degrees);
|
|
||||||
|
|
||||||
// Transform simply to find out the new bounding box (don't actually run the image through it)
|
|
||||||
AffineTransform at = new AffineTransform();
|
|
||||||
at.rotate(radians, original.getWidth() / 2.0, original.getHeight() / 2.0);
|
|
||||||
BufferedImageOp op = new AffineTransformOp(at, AffineTransformOp.TYPE_BICUBIC);
|
|
||||||
|
|
||||||
RectangularShape r = op.getBounds2D(original);
|
|
||||||
int width = (int) Math.ceil(r.getWidth());
|
|
||||||
int height = (int) Math.ceil(r.getHeight());
|
|
||||||
|
|
||||||
// Real transform, now that we know the size of the new image and how to translate after we rotate
|
|
||||||
// to keep it centered
|
|
||||||
at = new AffineTransform();
|
|
||||||
at.rotate(radians, width / 2.0, height / 2.0);
|
|
||||||
at.translate((width - original.getWidth()) / 2.0,
|
|
||||||
(height - original.getHeight()) / 2.0);
|
|
||||||
op = new AffineTransformOp(at, AffineTransformOp.TYPE_BICUBIC);
|
|
||||||
|
|
||||||
return op.filter(original, new BufferedImage(width, height, original.getType()));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2008 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.common;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encapsulates the result of one test over a batch of black-box images.
|
|
||||||
*/
|
|
||||||
public final class TestRXingResult {
|
|
||||||
|
|
||||||
private final int mustPassCount;
|
|
||||||
private final int tryHarderCount;
|
|
||||||
private final int maxMisreads;
|
|
||||||
private final int maxTryHarderMisreads;
|
|
||||||
private final float rotation;
|
|
||||||
|
|
||||||
public TestRXingResult(int mustPassCount, int tryHarderCount, int maxMisreads, int maxTryHarderMisreads, float rotation) {
|
|
||||||
this.mustPassCount = mustPassCount;
|
|
||||||
this.tryHarderCount = tryHarderCount;
|
|
||||||
this.maxMisreads = maxMisreads;
|
|
||||||
this.maxTryHarderMisreads = maxTryHarderMisreads;
|
|
||||||
this.rotation = rotation;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getMustPassCount() {
|
|
||||||
return mustPassCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getTryHarderCount() {
|
|
||||||
return tryHarderCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getMaxMisreads() {
|
|
||||||
return maxMisreads;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getMaxTryHarderMisreads() {
|
|
||||||
return maxTryHarderMisreads;
|
|
||||||
}
|
|
||||||
|
|
||||||
public float getRotation() {
|
|
||||||
return rotation;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
64
tests/common/TestResult.rs
Normal file
64
tests/common/TestResult.rs
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2008 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encapsulates the result of one test over a batch of black-box images.
|
||||||
|
*/
|
||||||
|
pub struct TestRXingResult {
|
||||||
|
must_pass_count: u32,
|
||||||
|
try_harder_count: u32,
|
||||||
|
max_misreads: u32,
|
||||||
|
max_try_harder_misreads: u32,
|
||||||
|
rotation: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestRXingResult {
|
||||||
|
pub fn new(
|
||||||
|
must_pass_count: u32,
|
||||||
|
try_harder_count: u32,
|
||||||
|
max_misreads: u32,
|
||||||
|
max_try_harder_misreads: u32,
|
||||||
|
rotation: f32,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
must_pass_count,
|
||||||
|
try_harder_count,
|
||||||
|
max_misreads,
|
||||||
|
max_try_harder_misreads,
|
||||||
|
rotation,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getMustPassCount(&self) -> u32 {
|
||||||
|
self.must_pass_count
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getTryHarderCount(&self) -> u32 {
|
||||||
|
self.try_harder_count
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getMaxMisreads(&self) -> u32 {
|
||||||
|
self.max_misreads
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getMaxTryHarderMisreads(&self) -> u32 {
|
||||||
|
self.max_try_harder_misreads
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getRotation(&self) -> f32 {
|
||||||
|
self.rotation
|
||||||
|
}
|
||||||
|
}
|
||||||
545
tests/common/abstract_black_box_test_case.rs
Normal file
545
tests/common/abstract_black_box_test_case.rs
Normal file
@@ -0,0 +1,545 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2008 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::{
|
||||||
|
collections::HashMap,
|
||||||
|
fs::{read_dir, read_to_string},
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
|
use rxing::{
|
||||||
|
common::HybridBinarizer, BarcodeFormat, BinaryBitmap, BufferedImageLuminanceSource,
|
||||||
|
DecodeHintType, DecodeHintValue, RXingResultMetadataType, RXingResultMetadataValue, Reader,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::TestRXingResult;
|
||||||
|
|
||||||
|
use image;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Sean Owen
|
||||||
|
* @author dswitkin@google.com (Daniel Switkin)
|
||||||
|
*/
|
||||||
|
pub struct AbstractBlackBoxTestCase {
|
||||||
|
test_base: Box<Path>,
|
||||||
|
barcode_reader: Box<dyn Reader>,
|
||||||
|
expected_format: BarcodeFormat,
|
||||||
|
test_rxing_results: Vec<TestRXingResult>,
|
||||||
|
hints: HashMap<DecodeHintType, DecodeHintValue>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AbstractBlackBoxTestCase {
|
||||||
|
pub fn buildTestBase(testBasePathSuffix: &str) -> Box<Path> {
|
||||||
|
// A little workaround to prevent aggravation in my IDE
|
||||||
|
let test_base = Path::new(testBasePathSuffix);
|
||||||
|
// if !testBase.exists() {
|
||||||
|
// // try starting with 'core' since the test base is often given as the project root
|
||||||
|
// testBase = Paths.get("core").resolve(testBasePathSuffix);
|
||||||
|
// }
|
||||||
|
test_base.to_owned().into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(
|
||||||
|
test_base_path_suffix: &str,
|
||||||
|
barcode_reader: Box<dyn Reader>,
|
||||||
|
expected_format: BarcodeFormat,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
test_base: Self::buildTestBase(test_base_path_suffix),
|
||||||
|
barcode_reader,
|
||||||
|
expected_format,
|
||||||
|
test_rxing_results: Vec::new(),
|
||||||
|
hints: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getTestBase(&self) -> &Box<Path> {
|
||||||
|
&self.test_base
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn addTest(&mut self, must_pass_count: u32, try_harder_count: u32, rotation: f32) {
|
||||||
|
self.addTestComplex(must_pass_count, try_harder_count, 0, 0, rotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn addHint(&mut self, hint: DecodeHintType, value: DecodeHintValue) {
|
||||||
|
self.hints.insert(hint, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new test for the current directory of images.
|
||||||
|
*
|
||||||
|
* @param mustPassCount The number of images which must decode for the test to pass.
|
||||||
|
* @param tryHarderCount The number of images which must pass using the try harder flag.
|
||||||
|
* @param maxMisreads Maximum number of images which can fail due to successfully reading the wrong contents
|
||||||
|
* @param maxTryHarderMisreads Maximum number of images which can fail due to successfully
|
||||||
|
* reading the wrong contents using the try harder flag
|
||||||
|
* @param rotation The rotation in degrees clockwise to use for this test.
|
||||||
|
*/
|
||||||
|
pub fn addTestComplex(
|
||||||
|
&mut self,
|
||||||
|
must_pass_count: u32,
|
||||||
|
try_harder_count: u32,
|
||||||
|
max_misreads: u32,
|
||||||
|
max_try_harder_misreads: u32,
|
||||||
|
rotation: f32,
|
||||||
|
) {
|
||||||
|
self.test_rxing_results.push(TestRXingResult::new(
|
||||||
|
must_pass_count,
|
||||||
|
try_harder_count,
|
||||||
|
max_misreads,
|
||||||
|
max_try_harder_misreads,
|
||||||
|
rotation,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getImageFiles(&self) -> Vec<PathBuf> {
|
||||||
|
assert!(
|
||||||
|
self.test_base.exists(),
|
||||||
|
"Please download and install test images, and run from the 'core' directory"
|
||||||
|
);
|
||||||
|
// let paths = Vec::new();
|
||||||
|
let path_search = read_dir(&self.test_base);
|
||||||
|
const possible_extensions: &str = "jpg,jpeg,gif,png,JPG,JPEG,GIF,PNG";
|
||||||
|
|
||||||
|
let paths = path_search
|
||||||
|
.unwrap()
|
||||||
|
.into_iter()
|
||||||
|
.filter(|r| r.is_ok()) // Get rid of Err variants for Result<DirEntry>
|
||||||
|
.map(|r| r.unwrap().path()) // This is safe, since we only have the Ok variants
|
||||||
|
.filter(|r| r.is_file()) // Filter out non-folders
|
||||||
|
.filter(|r| possible_extensions.contains(r.extension().unwrap().to_str().unwrap()))
|
||||||
|
// .map(|r| r.into_boxed_path())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
paths
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getReader(&self) -> &Box<dyn Reader> {
|
||||||
|
&self.barcode_reader
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn testBlackBox(&self) {
|
||||||
|
assert!(!self.test_rxing_results.is_empty());
|
||||||
|
|
||||||
|
let image_files = self.getImageFiles();
|
||||||
|
let test_count = self.test_rxing_results.len();
|
||||||
|
|
||||||
|
let mut passed_counts = vec![0usize; test_count];
|
||||||
|
let mut misread_counts = vec![0usize; test_count];
|
||||||
|
let mut try_harder_counts = vec![0usize; test_count];
|
||||||
|
let mut try_harder_misread_counts = vec![0usize; test_count];
|
||||||
|
|
||||||
|
for testImage in &image_files {
|
||||||
|
// for (Path testImage : imageFiles) {
|
||||||
|
log::info(format!("Starting {}", testImage.to_string_lossy()));
|
||||||
|
|
||||||
|
let image = image::open(testImage).unwrap(); //ImageIO.read(testImage.toFile());
|
||||||
|
|
||||||
|
//let testImageFileName = testImage.getFileName().toString();
|
||||||
|
let file_base_name = testImage.file_stem().unwrap();
|
||||||
|
//let expectedTextFile = self.testBase.resolve(fileBaseName + ".txt");
|
||||||
|
let mut expected_text_file = PathBuf::from(testImage.clone());
|
||||||
|
expected_text_file.set_extension("txt");
|
||||||
|
let expected_text = if expected_text_file.exists() {
|
||||||
|
Self::read_file_as_string(expected_text_file)
|
||||||
|
} else {
|
||||||
|
let mut new_path = self.test_base.clone().to_path_buf();
|
||||||
|
new_path.push(file_base_name);
|
||||||
|
new_path.set_extension("bin");
|
||||||
|
//expectedTextFile = testBase.resolve(fileBaseName + ".bin");
|
||||||
|
assert!(new_path.exists());
|
||||||
|
Self::read_file_as_string(new_path)
|
||||||
|
}
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut expected_metadata_file: PathBuf = self.test_base.clone().to_path_buf();
|
||||||
|
expected_metadata_file.push(format!("{}.metadata", file_base_name.to_str().unwrap()));
|
||||||
|
expected_metadata_file.set_extension("txt");
|
||||||
|
let expectedMetadata_unfinished = if expected_metadata_file.exists() {
|
||||||
|
java_properties::read(
|
||||||
|
std::fs::File::open(expected_metadata_file)
|
||||||
|
.expect("file exists, we already know that"),
|
||||||
|
)
|
||||||
|
.expect("valid java properties file")
|
||||||
|
// try (BufferedReader reader = Files.newBufferedReader(expectedMetadataFile, StandardCharsets.UTF_8)) {
|
||||||
|
// expectedMetadata.load(reader);
|
||||||
|
// }
|
||||||
|
} else {
|
||||||
|
HashMap::new()
|
||||||
|
};
|
||||||
|
let expected_metadata = HashMap::new();
|
||||||
|
for (k, v) in expectedMetadata_unfinished {
|
||||||
|
let new_k = RXingResultMetadataType::from(k);
|
||||||
|
let new_v = match new_k {
|
||||||
|
RXingResultMetadataType::OTHER => RXingResultMetadataValue::OTHER(v),
|
||||||
|
RXingResultMetadataType::ORIENTATION => {
|
||||||
|
RXingResultMetadataValue::Orientation(v.parse().unwrap_or_default())
|
||||||
|
}
|
||||||
|
RXingResultMetadataType::BYTE_SEGMENTS => {
|
||||||
|
RXingResultMetadataValue::ByteSegments(v.into_bytes())
|
||||||
|
}
|
||||||
|
RXingResultMetadataType::ERROR_CORRECTION_LEVEL => {
|
||||||
|
RXingResultMetadataValue::ErrorCorrectionLevel(v)
|
||||||
|
}
|
||||||
|
RXingResultMetadataType::ISSUE_NUMBER => {
|
||||||
|
RXingResultMetadataValue::IssueNumber(v.parse().unwrap_or_default())
|
||||||
|
}
|
||||||
|
RXingResultMetadataType::SUGGESTED_PRICE => {
|
||||||
|
RXingResultMetadataValue::SuggestedPrice(v)
|
||||||
|
}
|
||||||
|
RXingResultMetadataType::POSSIBLE_COUNTRY => {
|
||||||
|
RXingResultMetadataValue::PossibleCountry(v)
|
||||||
|
}
|
||||||
|
RXingResultMetadataType::UPC_EAN_EXTENSION => {
|
||||||
|
RXingResultMetadataValue::UpcEanExtension(v)
|
||||||
|
}
|
||||||
|
RXingResultMetadataType::PDF417_EXTRA_METADATA => {
|
||||||
|
RXingResultMetadataValue::Pdf417ExtraMetadata(v)
|
||||||
|
}
|
||||||
|
RXingResultMetadataType::STRUCTURED_APPEND_SEQUENCE => {
|
||||||
|
RXingResultMetadataValue::StructuredAppendSequence(
|
||||||
|
v.parse().unwrap_or_default(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
RXingResultMetadataType::STRUCTURED_APPEND_PARITY => {
|
||||||
|
RXingResultMetadataValue::StructuredAppendParity(
|
||||||
|
v.parse().unwrap_or_default(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
RXingResultMetadataType::SYMBOLOGY_IDENTIFIER => {
|
||||||
|
RXingResultMetadataValue::SymbologyIdentifier(v)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
for x in 0..test_count {
|
||||||
|
// for (int x = 0; x < testCount; x++) {
|
||||||
|
let rotation = self.test_rxing_results.get(x).unwrap().getRotation();
|
||||||
|
let rotated_image = Self::rotate_image(&image, rotation);
|
||||||
|
let source = BufferedImageLuminanceSource::new(rotated_image);
|
||||||
|
let bitmap = BinaryBitmap::new(Box::new(HybridBinarizer::new(Box::new(source))));
|
||||||
|
if let Ok(decoded) =
|
||||||
|
self.decode(&bitmap, rotation, &expected_text, &expected_metadata, false)
|
||||||
|
{
|
||||||
|
if decoded {
|
||||||
|
passed_counts[x] += 1;
|
||||||
|
} else {
|
||||||
|
misread_counts[x] += 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log::fine(format!("could not read at rotation {}", rotation));
|
||||||
|
}
|
||||||
|
// try {
|
||||||
|
// if (decode(bitmap, rotation, expectedText, expectedMetadata, false)) {
|
||||||
|
// passedCounts[x]+=1;
|
||||||
|
// } else {
|
||||||
|
// misreadCounts[x]+=1;
|
||||||
|
// }
|
||||||
|
// } catch (ReaderException ignored) {
|
||||||
|
// log::fine(format!("could not read at rotation {}", rotation));
|
||||||
|
// }
|
||||||
|
if let Ok(decoded) =
|
||||||
|
self.decode(&bitmap, rotation, &expected_text, &expected_metadata, true)
|
||||||
|
{
|
||||||
|
if decoded {
|
||||||
|
try_harder_counts[x] += 1;
|
||||||
|
} else {
|
||||||
|
try_harder_misread_counts[x] += 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log::fine(format!("could not read at rotation {} w/TH", rotation));
|
||||||
|
}
|
||||||
|
// try {
|
||||||
|
// if (decode(bitmap, rotation, expectedText, expectedMetadata, true)) {
|
||||||
|
// tryHarderCounts[x]+=1;
|
||||||
|
// } else {
|
||||||
|
// tryHarderMisreadCounts[x]+=1;
|
||||||
|
// }
|
||||||
|
// } catch (ReaderException ignored) {
|
||||||
|
// log::fine(format!("could not read at rotation {} w/TH", rotation));
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print the results of all tests first
|
||||||
|
let mut total_found = 0;
|
||||||
|
let mut total_must_pass = 0;
|
||||||
|
let mut total_misread = 0;
|
||||||
|
let mut total_max_misread = 0;
|
||||||
|
|
||||||
|
for x in 0..self.test_rxing_results.len() {
|
||||||
|
// for (int x = 0; x < testRXingResults.size(); x++) {
|
||||||
|
let test_rxing_result = self.test_rxing_results.get(x).unwrap();
|
||||||
|
log::info(format!(
|
||||||
|
"Rotation {} degrees:",
|
||||||
|
test_rxing_result.getRotation()
|
||||||
|
));
|
||||||
|
log::info(format!(
|
||||||
|
" {} of {} images passed ({} required)",
|
||||||
|
passed_counts[x],
|
||||||
|
image_files.len(),
|
||||||
|
test_rxing_result.getMustPassCount()
|
||||||
|
));
|
||||||
|
let mut failed = image_files.len() - passed_counts[x];
|
||||||
|
log::info(format!(
|
||||||
|
" {} failed due to misreads, {} not detected",
|
||||||
|
misread_counts[x],
|
||||||
|
failed - misread_counts[x]
|
||||||
|
));
|
||||||
|
log::info(format!(
|
||||||
|
" {} of {} images passed with try harder ({} required)",
|
||||||
|
try_harder_counts[x],
|
||||||
|
image_files.len(),
|
||||||
|
test_rxing_result.getTryHarderCount()
|
||||||
|
));
|
||||||
|
failed = image_files.len() - try_harder_counts[x];
|
||||||
|
log::info(format!(
|
||||||
|
" {} failed due to misreads, {} not detected",
|
||||||
|
try_harder_misread_counts[x],
|
||||||
|
failed - try_harder_misread_counts[x]
|
||||||
|
));
|
||||||
|
total_found += passed_counts[x] + try_harder_counts[x];
|
||||||
|
total_must_pass +=
|
||||||
|
test_rxing_result.getMustPassCount() + test_rxing_result.getTryHarderCount();
|
||||||
|
total_misread += misread_counts[x] + try_harder_misread_counts[x];
|
||||||
|
total_max_misread +=
|
||||||
|
test_rxing_result.getMaxMisreads() + test_rxing_result.getMaxTryHarderMisreads();
|
||||||
|
}
|
||||||
|
|
||||||
|
let totalTests = image_files.len() * test_count * 2;
|
||||||
|
log::info(format!(
|
||||||
|
"Decoded {} images out of {} ({}, {} required)",
|
||||||
|
total_found,
|
||||||
|
totalTests,
|
||||||
|
total_found * 100 / totalTests,
|
||||||
|
total_must_pass
|
||||||
|
));
|
||||||
|
if total_found > total_must_pass as usize {
|
||||||
|
log::warning(format!(
|
||||||
|
"+++ Test too lax by {} images",
|
||||||
|
total_found - total_must_pass as usize
|
||||||
|
));
|
||||||
|
} else if total_found < total_must_pass as usize {
|
||||||
|
log::warning(format!(
|
||||||
|
"--- Test failed by {} images",
|
||||||
|
total_must_pass as usize - total_found
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if total_misread < total_max_misread as usize {
|
||||||
|
log::warning(format!(
|
||||||
|
"+++ Test expects too many misreads by {} images",
|
||||||
|
total_max_misread as usize - total_misread
|
||||||
|
));
|
||||||
|
} else if total_misread > total_max_misread as usize {
|
||||||
|
log::warning(format!(
|
||||||
|
"--- Test had too many misreads by {} images",
|
||||||
|
total_misread - total_max_misread as usize
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then run through again and assert if any failed
|
||||||
|
for x in 0..test_count {
|
||||||
|
// for (int x = 0; x < testCount; x++) {
|
||||||
|
let testRXingResult = self.test_rxing_results.get(x).unwrap();
|
||||||
|
let label = format!(
|
||||||
|
"Rotation {} degrees: Too many images failed",
|
||||||
|
testRXingResult.getRotation()
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
passed_counts[x] >= testRXingResult.getMustPassCount() as usize,
|
||||||
|
"{}",
|
||||||
|
label
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
try_harder_counts[x] >= testRXingResult.getTryHarderCount() as usize,
|
||||||
|
"Try harder, {}",
|
||||||
|
label,
|
||||||
|
);
|
||||||
|
let label = format!(
|
||||||
|
"Rotation {} degrees: Too many images misread",
|
||||||
|
testRXingResult.getRotation()
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
misread_counts[x] <= testRXingResult.getMaxMisreads() as usize,
|
||||||
|
"{}",
|
||||||
|
label
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
try_harder_misread_counts[x] <= testRXingResult.getMaxTryHarderMisreads() as usize,
|
||||||
|
"Try harder, {}",
|
||||||
|
label
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode(
|
||||||
|
&self,
|
||||||
|
source: &BinaryBitmap,
|
||||||
|
rotation: f32,
|
||||||
|
expected_text: &str,
|
||||||
|
expected_metadata: &HashMap<RXingResultMetadataType, RXingResultMetadataValue>,
|
||||||
|
try_harder: bool,
|
||||||
|
) -> Result<bool, rxing::Exceptions> {
|
||||||
|
let suffix = format!(
|
||||||
|
" ({}rotation: {})",
|
||||||
|
if try_harder { "try harder, " } else { "" },
|
||||||
|
rotation
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut hints = self.hints.clone();
|
||||||
|
if try_harder {
|
||||||
|
hints.insert(DecodeHintType::TRY_HARDER, DecodeHintValue::TryHarder(true));
|
||||||
|
// hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try in 'pure' mode mostly to exercise PURE_BARCODE code paths for exceptions;
|
||||||
|
// not expected to pass, generally
|
||||||
|
let mut result = None;
|
||||||
|
let mut pure_hints = HashMap::new();
|
||||||
|
pure_hints.insert(
|
||||||
|
DecodeHintType::PURE_BARCODE,
|
||||||
|
DecodeHintValue::PureBarcode(true),
|
||||||
|
);
|
||||||
|
result = if let Ok(res) = self.barcode_reader.decode_with_hints(source, &pure_hints) {
|
||||||
|
Some(res)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
if result.is_none() {
|
||||||
|
result = Some(self.barcode_reader.decode_with_hints(source, &hints)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = result.unwrap();
|
||||||
|
|
||||||
|
if &self.expected_format != result.getBarcodeFormat() {
|
||||||
|
log::info(format!(
|
||||||
|
"Format mismatch: expected '{:?}' but got '{:?}'{}",
|
||||||
|
self.expected_format,
|
||||||
|
result.getBarcodeFormat(),
|
||||||
|
suffix
|
||||||
|
));
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
let result_text = result.getText();
|
||||||
|
if !(expected_text == result_text) {
|
||||||
|
log::info(format!(
|
||||||
|
"Content mismatch: expected '{}' but got '{}'{}",
|
||||||
|
expected_text, result_text, suffix
|
||||||
|
));
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
let result_metadata = result.getRXingResultMetadata();
|
||||||
|
for (key, value) in expected_metadata {
|
||||||
|
// let key = RXingResultMetadataType.valueOf(metadatum.getKey().toString());
|
||||||
|
// Object expectedValue = metadatum.getValue();
|
||||||
|
let actual_value = result_metadata.get(key);
|
||||||
|
if actual_value.is_none() || !(value == actual_value.unwrap()) {
|
||||||
|
log::info(format!(
|
||||||
|
"Metadata mismatch for key '{:?}': expected '{:?}' but got '{:?}'",
|
||||||
|
key, value, actual_value
|
||||||
|
));
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_file_as_string(file: PathBuf) -> Result<String, std::io::Error> {
|
||||||
|
let string_contents = read_to_string(&file)?; //new String(Files.readAllBytes(file), charset);
|
||||||
|
if string_contents.ends_with("\n") {
|
||||||
|
log::info(format!("String contents of file {} end with a newline. This may not be intended and cause a test failure",file.to_string_lossy()));
|
||||||
|
}
|
||||||
|
Ok(string_contents)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rotate_image(original: &image::DynamicImage, degrees: f32) -> image::DynamicImage {
|
||||||
|
if degrees == 0.0 {
|
||||||
|
return original.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
// switch (original.getType()) {
|
||||||
|
// case BufferedImage.TYPE_BYTE_INDEXED:
|
||||||
|
// case BufferedImage.TYPE_BYTE_BINARY:
|
||||||
|
// BufferedImage argb = new BufferedImage(original.getWidth(),
|
||||||
|
// original.getHeight(),
|
||||||
|
// BufferedImage.TYPE_INT_ARGB);
|
||||||
|
// Graphics g = argb.createGraphics();
|
||||||
|
// g.drawImage(original, 0, 0, null);
|
||||||
|
// g.dispose();
|
||||||
|
// original = argb;
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
|
||||||
|
let radians = degrees.to_radians();
|
||||||
|
|
||||||
|
{
|
||||||
|
use image::DynamicImage;
|
||||||
|
use image::Luma;
|
||||||
|
use imageproc::geometric_transformations::*;
|
||||||
|
|
||||||
|
let i = rotate_about_center(
|
||||||
|
&original.to_luma8(),
|
||||||
|
radians,
|
||||||
|
Interpolation::Nearest,
|
||||||
|
Luma([255; 1]),
|
||||||
|
);
|
||||||
|
|
||||||
|
DynamicImage::from(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// // Transform simply to find out the new bounding box (don't actually run the image through it)
|
||||||
|
// AffineTransform at = new AffineTransform();
|
||||||
|
// at.rotate(radians, original.getWidth() / 2.0, original.getHeight() / 2.0);
|
||||||
|
// BufferedImageOp op = new AffineTransformOp(at, AffineTransformOp.TYPE_BICUBIC);
|
||||||
|
|
||||||
|
// RectangularShape r = op.getBounds2D(original);
|
||||||
|
// int width = (int) Math.ceil(r.getWidth());
|
||||||
|
// int height = (int) Math.ceil(r.getHeight());
|
||||||
|
|
||||||
|
// // Real transform, now that we know the size of the new image and how to translate after we rotate
|
||||||
|
// // to keep it centered
|
||||||
|
// at = new AffineTransform();
|
||||||
|
// at.rotate(radians, width / 2.0, height / 2.0);
|
||||||
|
// at.translate((width - original.getWidth()) / 2.0,
|
||||||
|
// (height - original.getHeight()) / 2.0);
|
||||||
|
// op = new AffineTransformOp(at, AffineTransformOp.TYPE_BICUBIC);
|
||||||
|
|
||||||
|
// return op.filter(original, new BufferedImage(width, height, original.getType()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod log {
|
||||||
|
pub fn info(data: String) {
|
||||||
|
prn("INFO", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fine(data: String) {
|
||||||
|
prn("FINE", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn warning(data: String) {
|
||||||
|
prn("WARN", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prn(level: &str, data: String) {
|
||||||
|
println!("{} :: {}", level, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
5
tests/common/mod.rs
Normal file
5
tests/common/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
mod TestResult;
|
||||||
|
mod abstract_black_box_test_case;
|
||||||
|
|
||||||
|
pub use TestResult::*;
|
||||||
|
pub use abstract_black_box_test_case::*;
|
||||||
Reference in New Issue
Block a user