From 8539363c0f52b838bf53fe23ddcfc54d58522de5 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Thu, 11 Jul 2024 14:47:27 -0500 Subject: [PATCH 1/5] feat: basic "pure" barcode decoding warning: this might be somewhat ineficient. The code is only run if "PureBarcode(true)" is included in the Hints dictionary. --- src/common/bit_array.rs | 12 ++++ src/oned/one_d_reader.rs | 58 +++++++++++++++++- src/rxing_result_metadata.rs | 2 +- ...8-16acfb7a-4a41-4b15-af78-7ccf061e72bd.png | Bin 0 -> 882 bytes tests/github_issues.rs | 44 +++++++++++++ 5 files changed, 114 insertions(+), 2 deletions(-) create mode 100644 test_resources/blackbox/github_issue_cases/346304318-16acfb7a-4a41-4b15-af78-7ccf061e72bd.png diff --git a/src/common/bit_array.rs b/src/common/bit_array.rs index 7d1448b1..3392f92a 100644 --- a/src/common/bit_array.rs +++ b/src/common/bit_array.rs @@ -60,6 +60,14 @@ impl BitArray { } } + pub fn with_capacity(size: usize) -> Self { + Self { + bits: makeArray(size), + size: 0, + read_offset: 0, + } + } + /// For testing only #[cfg(test)] pub fn with_initial_values(bits: Vec, size: usize) -> Self { @@ -319,6 +327,10 @@ impl BitArray { } pub fn appendBitArray(&mut self, other: BitArray) { + self.appendBitArrayRef(&other) + } + + pub fn appendBitArrayRef(&mut self, other: &BitArray) { let otherSize = other.size; self.ensure_capacity(self.size + otherSize); for i in 0..otherSize { diff --git a/src/oned/one_d_reader.rs b/src/oned/one_d_reader.rs index ec2c7bcf..648819d4 100644 --- a/src/oned/one_d_reader.rs +++ b/src/oned/one_d_reader.rs @@ -17,7 +17,8 @@ use crate::{ common::{BitArray, Result}, point_f, Binarizer, BinaryBitmap, DecodeHintType, DecodeHintValue, DecodingHintDictionary, - Exceptions, RXingResult, RXingResultMetadataType, RXingResultMetadataValue, Reader, + Exceptions, LuminanceSource, RXingResult, RXingResultMetadataType, RXingResultMetadataValue, + Reader, }; /** @@ -28,6 +29,7 @@ use crate::{ * @author Sean Owen */ pub trait OneDReader: Reader { + const QUIET_ZONE: usize = 15; /** * We're going to examine rows from the middle outward, searching alternately above and below the * middle, and farther out each time. rowStep is the number of rows between each successive @@ -55,6 +57,26 @@ pub trait OneDReader: Reader { hints.get(&DecodeHintType::TRY_HARDER), Some(DecodeHintValue::TryHarder(true)) ); + + let try_pure = matches!( + hints.get(&DecodeHintType::PURE_BARCODE), + Some(DecodeHintValue::PureBarcode(true)) + ); + + // Attempt to decode the barcode as "pure". This method may be very inneficient and uses + // a very poor version of a binarizer. + // ToDo: Add a better binarizer for pure barcodes + if try_pure { + let mid_line = 1.max(image.get_height() / 2); + + let rw = image.get_source().get_row(mid_line); + + let decoded = self.decode_pure(mid_line as u32, &rw, &hints); + if decoded.is_ok() { + return decoded; + } + } + let row_step = 1.max(height >> (if try_harder { 8 } else { 5 })); let max_lines = if try_harder { height // Look at the whole image, not just the center @@ -146,6 +168,40 @@ pub trait OneDReader: Reader { row: &BitArray, hints: &DecodingHintDictionary, ) -> Result; + + fn decode_pure( + &mut self, + rowNumber: u32, + row: &[u8], + hints: &DecodingHintDictionary, + ) -> Result { + let new_row = pad_bitarray(row, Self::QUIET_ZONE); + + self.decode_row(rowNumber, &new_row, hints) + } +} + +// Add a buffer on either side of the row to mimic a quiet zone. This may not exist in a "pure barcode" +fn pad_bitarray(bits: &[u8], quiet_zone: usize) -> BitArray { + const PIXEL_COLOR_SPLIT_POINT: u8 = u8::MAX / 2; + + let mut new_row = BitArray::with_capacity(bits.len() + (quiet_zone * 2)); + + let value = !(bits[0] < PIXEL_COLOR_SPLIT_POINT); + + for _ in 0..quiet_zone { + new_row.appendBit(value); + } + + for bit in bits { + new_row.appendBit(bit < &PIXEL_COLOR_SPLIT_POINT) + } + + for _ in 0..quiet_zone { + new_row.appendBit(value); + } + + new_row } /** diff --git a/src/rxing_result_metadata.rs b/src/rxing_result_metadata.rs index ceadaa63..630533db 100644 --- a/src/rxing_result_metadata.rs +++ b/src/rxing_result_metadata.rs @@ -18,7 +18,7 @@ use std::rc::Rc; -use crate::{pdf417::PDF417RXingResultMetadata, Point, PointI, PointU}; +use crate::pdf417::PDF417RXingResultMetadata; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; diff --git a/test_resources/blackbox/github_issue_cases/346304318-16acfb7a-4a41-4b15-af78-7ccf061e72bd.png b/test_resources/blackbox/github_issue_cases/346304318-16acfb7a-4a41-4b15-af78-7ccf061e72bd.png new file mode 100644 index 0000000000000000000000000000000000000000..122cd766dab426f0141dc4896479249fc53296af GIT binary patch literal 882 zcmcgr-D}fO6i*1$QWi8KqSL8#>4UlsF;hA=@q=tJrp{4Tqw5BpNXn=ebULa}R_Tih zBJqpp6kl3o!!kC#`+AYRNTqI!urkda=FpeIUIY;YjpxL_LGQyQ_uO-SUzgnK>`?eX zZGd4IUOg_y7=~?y@6CQ6?7eNfRvCtCR^_8zSJ|}{_MWJkqPp?Uur;H|vSApfkx`NQUjx2`J-Y(~6o?3zVsG(;DGnrHKNEtoHQ1X`YXH3!}g-euQqNF8QvqVE5 zDeF+8Rl1MqT!O&uQui<-x!Xsq4es2b1;d`CiK_O z;&CN*Te1fvzAQ(iI)ScLo@l#wyjw7VPO`Hg02(Aazlrm}Qw7wZjRD;k?4$%S z5u|<=Wr!e88%Tc;`W)YoH!V%a(70M%y(1#*n>Z0)peZi~<$leFh_9vaj zd<5BnktEE&JL}0#r0@Du1z=315$c(RzCbXWDqs` Date: Thu, 11 Jul 2024 15:20:54 -0500 Subject: [PATCH 2/5] feat: add a new method to get a binarized row from the matrix cache. for future work --- src/binarizer.rs | 4 ++++ src/common/adaptive_threshold_binarizer.rs | 8 ++++++++ src/common/global_histogram_binarizer.rs | 8 ++++++++ src/common/hybrid_binarizer.rs | 8 ++++++++ 4 files changed, 28 insertions(+) diff --git a/src/binarizer.rs b/src/binarizer.rs index 513e6462..32ff6355 100644 --- a/src/binarizer.rs +++ b/src/binarizer.rs @@ -55,6 +55,10 @@ pub trait Binarizer { */ fn get_black_row(&self, y: usize) -> Result>; + // An alternate version of get_black_row that fetches the line from the matrix if + // it has already been generated, falling back to get_black_row if it hasn't. + fn get_black_row_from_matrix(&self, y: usize) -> Result>; + /** * Converts a 2D array of luminance data to 1 bit data. As above, assume this method is expensive * and do not call it repeatedly. This method is intended for decoding 2D barcodes and may or diff --git a/src/common/adaptive_threshold_binarizer.rs b/src/common/adaptive_threshold_binarizer.rs index 8f53ab88..f77930a8 100644 --- a/src/common/adaptive_threshold_binarizer.rs +++ b/src/common/adaptive_threshold_binarizer.rs @@ -91,4 +91,12 @@ impl Binarizer for AdaptiveThresholdBinarizer { fn get_height(&self) -> usize { self.source.get_height() } + + fn get_black_row_from_matrix(&self, y: usize) -> Result> { + if let Some(matrix) = self.matrix.get() { + Ok(Cow::Owned(matrix.getRow(y as u32))) + } else { + self.get_black_row(y) + } + } } diff --git a/src/common/global_histogram_binarizer.rs b/src/common/global_histogram_binarizer.rs index 59093b25..0f9833af 100644 --- a/src/common/global_histogram_binarizer.rs +++ b/src/common/global_histogram_binarizer.rs @@ -177,6 +177,14 @@ impl Binarizer for GlobalHistogramBinarizer { fn get_height(&self) -> usize { self.height } + + fn get_black_row_from_matrix(&self, y: usize) -> Result> { + if let Some(matrix) = self.black_matrix.get() { + Ok(Cow::Owned(matrix.getRow(y as u32))) + } else { + self.get_black_row(y) + } + } } impl GlobalHistogramBinarizer { diff --git a/src/common/hybrid_binarizer.rs b/src/common/hybrid_binarizer.rs index e1f795f1..c0111cb0 100644 --- a/src/common/hybrid_binarizer.rs +++ b/src/common/hybrid_binarizer.rs @@ -91,6 +91,14 @@ impl Binarizer for HybridBinarizer { fn get_height(&self) -> usize { self.ghb.get_height() } + + fn get_black_row_from_matrix(&self, y: usize) -> Result> { + if let Some(matrix) = self.black_matrix.get() { + Ok(Cow::Owned(matrix.getRow(y as u32))) + } else { + self.get_black_row(y) + } + } } // This class uses 5x5 blocks to compute local luminance, where each block is 8x8 pixels. From 01f9d48b9579b4292de6fbe02c0391ab4054d3f3 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Thu, 11 Jul 2024 15:27:39 -0500 Subject: [PATCH 3/5] test: add option to test basic searth with AdaptiveThresholdBinarizer --- tests/github_issues.rs | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/github_issues.rs b/tests/github_issues.rs index 03eef021..32a9dee7 100644 --- a/tests/github_issues.rs +++ b/tests/github_issues.rs @@ -440,3 +440,42 @@ fn test_issue_50() { assert_eq!(EXPECTED_FORMAT, result.getBarcodeFormat()); } + +#[cfg(feature = "image")] +#[test] +fn test_issue_50_2() { + use rxing::{ + common::{AdaptiveThresholdBinarizer, HybridBinarizer}, + BarcodeFormat, BinaryBitmap, DecodeHintType, DecodeHintValue, DecodingHintDictionary, + Exceptions, Luma8LuminanceSource, MultiUseMultiFormatReader, Reader, + }; + + const FILE_NAME : &str = "test_resources/blackbox/github_issue_cases/346304318-16acfb7a-4a41-4b15-af78-7ccf061e72bd.png"; + let mut hints = DecodingHintDictionary::default(); + + let img = image::open(FILE_NAME) + .map_err(|e| Exceptions::runtime_with(format!("couldn't read {FILE_NAME}: {e}"))) + .unwrap(); + let mut scanner = MultiUseMultiFormatReader::default(); + + hints + .entry(DecodeHintType::TRY_HARDER) + .or_insert(DecodeHintValue::TryHarder(true)); + + let result = scanner + .decode_with_hints( + &mut BinaryBitmap::new(AdaptiveThresholdBinarizer::new( + Luma8LuminanceSource::new(img.to_luma8().into_raw(), img.width(), img.height()), + 1, + )), + &hints, + ) + .expect("must not fault during read"); + + const EXPECTED_FORMAT: &BarcodeFormat = &BarcodeFormat::ITF; + const EXPECTED_ITF_TEXT: &str = "85680000001403303242024070501202400002535294"; + + assert_eq!(EXPECTED_ITF_TEXT, result.getText()); + + assert_eq!(EXPECTED_FORMAT, result.getBarcodeFormat()); +} From de87cb5c2b0abdcbf8c544609abf031ac1193fc0 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Thu, 11 Jul 2024 15:47:56 -0500 Subject: [PATCH 4/5] chore: clippy cleanup --- src/client/result/VCardResultParser.rs | 2 +- src/multi_format_reader.rs | 2 +- src/multi_use_multi_format_reader.rs | 2 +- src/oned/one_d_reader.rs | 2 +- tests/common/multiimage_span.rs | 2 +- tests/github_issues.rs | 6 +++--- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/client/result/VCardResultParser.rs b/src/client/result/VCardResultParser.rs index dd3cd8ab..db052b0a 100644 --- a/src/client/result/VCardResultParser.rs +++ b/src/client/result/VCardResultParser.rs @@ -483,7 +483,7 @@ fn toTypes(lists: Option>>) -> Vec { let metadatum = list.get(i).unwrap_or(&String::default()).clone(); if let Some(equals) = metadatum.find('=') { if "TYPE" == (metadatum[0..equals]).to_uppercase() { - v_type = metadatum[equals + 1..].to_owned(); + metadatum[equals + 1..].clone_into(&mut v_type); break; } } else { diff --git a/src/multi_format_reader.rs b/src/multi_format_reader.rs index cfca1a7a..cdb746df 100644 --- a/src/multi_format_reader.rs +++ b/src/multi_format_reader.rs @@ -109,7 +109,7 @@ impl MultiFormatReader { * @param hints The set of hints to use for subsequent calls to decode(image) */ pub fn set_hints(&mut self, hints: &DecodingHintDictionary) { - self.hints.clone_from(&hints); + self.hints.clone_from(hints); self.try_harder = matches!( self.hints.get(&DecodeHintType::TRY_HARDER), diff --git a/src/multi_use_multi_format_reader.rs b/src/multi_use_multi_format_reader.rs index 4518e097..2bbebf9e 100644 --- a/src/multi_use_multi_format_reader.rs +++ b/src/multi_use_multi_format_reader.rs @@ -133,7 +133,7 @@ impl MultiUseMultiFormatReader { * @param hints The set of hints to use for subsequent calls to decode(image) */ pub fn set_hints(&mut self, hints: &DecodingHintDictionary) { - self.hints.clone_from(&hints); + self.hints.clone_from(hints); self.try_harder = matches!( self.hints.get(&DecodeHintType::TRY_HARDER), diff --git a/src/oned/one_d_reader.rs b/src/oned/one_d_reader.rs index 648819d4..b4db044b 100644 --- a/src/oned/one_d_reader.rs +++ b/src/oned/one_d_reader.rs @@ -187,7 +187,7 @@ fn pad_bitarray(bits: &[u8], quiet_zone: usize) -> BitArray { let mut new_row = BitArray::with_capacity(bits.len() + (quiet_zone * 2)); - let value = !(bits[0] < PIXEL_COLOR_SPLIT_POINT); + let value = bits[0] >= PIXEL_COLOR_SPLIT_POINT; for _ in 0..quiet_zone { new_row.appendBit(value); diff --git a/tests/common/multiimage_span.rs b/tests/common/multiimage_span.rs index fda87641..17f5cf99 100644 --- a/tests/common/multiimage_span.rs +++ b/tests/common/multiimage_span.rs @@ -409,7 +409,7 @@ impl MultiImageSpanAbstractBlackBoxTestCase { // RXingResultMetadataValue::FilteredResolution(v.parse().unwrap()) let arr: Box<[usize]> = v - .split(",") + .split(',') .map(|str_source| str_source.parse::().unwrap_or_default()) .take(2) .collect(); diff --git a/tests/github_issues.rs b/tests/github_issues.rs index 32a9dee7..c1032f21 100644 --- a/tests/github_issues.rs +++ b/tests/github_issues.rs @@ -445,9 +445,9 @@ fn test_issue_50() { #[test] fn test_issue_50_2() { use rxing::{ - common::{AdaptiveThresholdBinarizer, HybridBinarizer}, - BarcodeFormat, BinaryBitmap, DecodeHintType, DecodeHintValue, DecodingHintDictionary, - Exceptions, Luma8LuminanceSource, MultiUseMultiFormatReader, Reader, + common::AdaptiveThresholdBinarizer, BarcodeFormat, BinaryBitmap, DecodeHintType, + DecodeHintValue, DecodingHintDictionary, Exceptions, Luma8LuminanceSource, + MultiUseMultiFormatReader, Reader, }; const FILE_NAME : &str = "test_resources/blackbox/github_issue_cases/346304318-16acfb7a-4a41-4b15-af78-7ccf061e72bd.png"; From f478c5df8b9aaf0c0a07a1380b93517b3351b8b3 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Mon, 22 Jul 2024 08:52:10 -0500 Subject: [PATCH 5/5] cleanup: remove unused code --- src/oned/one_d_code_writer.rs | 61 ++--------------------------------- 1 file changed, 2 insertions(+), 59 deletions(-) diff --git a/src/oned/one_d_code_writer.rs b/src/oned/one_d_code_writer.rs index b57c5895..cc27f340 100644 --- a/src/oned/one_d_code_writer.rs +++ b/src/oned/one_d_code_writer.rs @@ -14,11 +14,10 @@ * limitations under the License. */ -use std::collections::HashMap; use crate::{ common::{BitMatrix, Result}, - BarcodeFormat, EncodeHintType, EncodeHintValue, Exceptions, Writer, + BarcodeFormat, Exceptions, Writer, }; use once_cell::sync::Lazy; @@ -141,60 +140,4 @@ pub trait OneDimensionalCodeWriter: Writer { // This seems like a decent idea for a default for all formats. 10 } -} - -struct L; -impl Writer for L { - fn encode( - &self, - contents: &str, - format: &crate::BarcodeFormat, - width: i32, - height: i32, - ) -> Result { - self.encode_with_hints(contents, format, width, height, &HashMap::new()) - } - - fn encode_with_hints( - &self, - contents: &str, - format: &crate::BarcodeFormat, - width: i32, - height: i32, - hints: &crate::EncodingHintDictionary, - ) -> Result { - if contents.is_empty() { - return Err(Exceptions::illegal_argument_with("Found empty contents")); - } - - if width < 0 || height < 0 { - return Err(Exceptions::illegal_argument_with(format!( - "Negative size is not allowed. Input: {width}x{height}" - ))); - } - if let Some(supportedFormats) = self.getSupportedWriteFormats() { - if !supportedFormats.contains(format) { - return Err(Exceptions::illegal_argument_with(format!( - "Can only encode {supportedFormats:?}, but got {format:?}" - ))); - } - } - - let mut sidesMargin = self.getDefaultMargin(); - if let Some(EncodeHintValue::Margin(margin)) = hints.get(&EncodeHintType::MARGIN) { - sidesMargin = margin.parse::().map_err(|e| { - Exceptions::illegal_argument_with(format!("couldnt parse {margin}: {e}")) - })?; - } - - let code = self.encode_oned_with_hints(contents, hints)?; - - Self::renderRXingResult(&code, width, height, sidesMargin) - } -} - -impl OneDimensionalCodeWriter for L { - fn encode_oned(&self, _contents: &str) -> Result> { - unimplemented!() - } -} +} \ No newline at end of file