Skip to content

Commit

Permalink
Merge pull request #52 from rxing-core/50-pure-itf-barcode-not-being-…
Browse files Browse the repository at this point in the history
…decoded

50 pure itf barcode not being decoded
  • Loading branch information
hschimke authored Jul 24, 2024
2 parents ec30be5 + f478c5d commit ae6a261
Show file tree
Hide file tree
Showing 14 changed files with 187 additions and 65 deletions.
4 changes: 4 additions & 0 deletions src/binarizer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ pub trait Binarizer {
*/
fn get_black_row(&self, y: usize) -> Result<Cow<BitArray>>;

// 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<Cow<BitArray>>;

/**
* 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
Expand Down
2 changes: 1 addition & 1 deletion src/client/result/VCardResultParser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,7 @@ fn toTypes(lists: Option<Vec<Vec<String>>>) -> Vec<String> {
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 {
Expand Down
8 changes: 8 additions & 0 deletions src/common/adaptive_threshold_binarizer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,12 @@ impl<LS: LuminanceSource> Binarizer for AdaptiveThresholdBinarizer<LS> {
fn get_height(&self) -> usize {
self.source.get_height()
}

fn get_black_row_from_matrix(&self, y: usize) -> Result<Cow<BitArray>> {
if let Some(matrix) = self.matrix.get() {
Ok(Cow::Owned(matrix.getRow(y as u32)))
} else {
self.get_black_row(y)
}
}
}
12 changes: 12 additions & 0 deletions src/common/bit_array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<BaseType>, size: usize) -> Self {
Expand Down Expand Up @@ -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 {
Expand Down
8 changes: 8 additions & 0 deletions src/common/global_histogram_binarizer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,14 @@ impl<LS: LuminanceSource> Binarizer for GlobalHistogramBinarizer<LS> {
fn get_height(&self) -> usize {
self.height
}

fn get_black_row_from_matrix(&self, y: usize) -> Result<Cow<BitArray>> {
if let Some(matrix) = self.black_matrix.get() {
Ok(Cow::Owned(matrix.getRow(y as u32)))
} else {
self.get_black_row(y)
}
}
}

impl<LS: LuminanceSource> GlobalHistogramBinarizer<LS> {
Expand Down
8 changes: 8 additions & 0 deletions src/common/hybrid_binarizer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,14 @@ impl<LS: LuminanceSource> Binarizer for HybridBinarizer<LS> {
fn get_height(&self) -> usize {
self.ghb.get_height()
}

fn get_black_row_from_matrix(&self, y: usize) -> Result<Cow<BitArray>> {
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.
Expand Down
2 changes: 1 addition & 1 deletion src/multi_format_reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
2 changes: 1 addition & 1 deletion src/multi_use_multi_format_reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
61 changes: 2 additions & 59 deletions src/oned/one_d_code_writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<crate::common::BitMatrix> {
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<crate::common::BitMatrix> {
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::<u32>().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<Vec<bool>> {
unimplemented!()
}
}
}
58 changes: 57 additions & 1 deletion src/oned/one_d_reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};

/**
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -146,6 +168,40 @@ pub trait OneDReader: Reader {
row: &BitArray,
hints: &DecodingHintDictionary,
) -> Result<RXingResult>;

fn decode_pure(
&mut self,
rowNumber: u32,
row: &[u8],
hints: &DecodingHintDictionary,
) -> Result<RXingResult> {
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
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/rxing_result_metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion tests/common/multiimage_span.rs
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,7 @@ impl<T: MultipleBarcodeReader + Reader> MultiImageSpanAbstractBlackBoxTestCase<T
RXingResultMetadataType::FILTERED_RESOLUTION => {
// RXingResultMetadataValue::FilteredResolution(v.parse().unwrap())
let arr: Box<[usize]> = v
.split(",")
.split(',')
.map(|str_source| str_source.parse::<usize>().unwrap_or_default())
.take(2)
.collect();
Expand Down
83 changes: 83 additions & 0 deletions tests/github_issues.rs
Original file line number Diff line number Diff line change
Expand Up @@ -396,3 +396,86 @@ fn test_issue_49() {

assert_eq!(EXPECTED_ITF_TEXT, itf_result.getText());
}

#[cfg(feature = "image")]
#[test]
fn test_issue_50() {
use rxing::{
common::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));

hints
.entry(DecodeHintType::PURE_BARCODE)
.or_insert(DecodeHintValue::PureBarcode(true));

let result = scanner
.decode_with_hints(
&mut BinaryBitmap::new(HybridBinarizer::new(Luma8LuminanceSource::new(
img.to_luma8().into_raw(),
img.width(),
img.height(),
))),
&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());
}

#[cfg(feature = "image")]
#[test]
fn test_issue_50_2() {
use rxing::{
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";
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());
}

0 comments on commit ae6a261

Please sign in to comment.