diff --git a/Cargo.lock b/Cargo.lock index a67bbf8..9eabddd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -767,7 +767,7 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "rxing-cli" -version = "0.0.7" +version = "0.0.8" dependencies = [ "anyhow", "base64", @@ -781,7 +781,7 @@ dependencies = [ [[package]] name = "rxing-reader" -version = "0.0.7" +version = "0.0.8" dependencies = [ "anyhow", "bitvec", @@ -797,7 +797,7 @@ dependencies = [ [[package]] name = "rxing-wasm" -version = "0.0.7" +version = "0.0.8" dependencies = [ "js-sys", "rxing-reader", diff --git a/Cargo.toml b/Cargo.toml index e0ca7fa..ea78cf9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ members = [ resolver = "3" [workspace.package] -version = "0.0.7" +version = "0.0.8" [profile.release] codegen-units = 1 diff --git a/rxing-reader/src/binarizer.rs b/rxing-reader/src/binarizer.rs index 6c03900..43f27ec 100644 --- a/rxing-reader/src/binarizer.rs +++ b/rxing-reader/src/binarizer.rs @@ -25,12 +25,12 @@ use crate::{LuminanceSource, common::BitMatrix}; pub trait Binarizer { type Source: LuminanceSource; - fn get_luminance_source(&self) -> &Self::Source; + fn luminance_source(&self) -> &Self::Source; /// Converts the image to a black/white matrix for QR detection. /// Returns the image bits, where `true` means black. /// Returns an error if the image cannot be binarized into a matrix. - fn get_black_matrix(&self) -> Result<&BitMatrix>; + fn black_matrix(&self) -> Result<&BitMatrix>; - fn get_black_matrix_mut(&mut self) -> Result<&mut BitMatrix>; + fn black_matrix_mut(&mut self) -> Result<&mut BitMatrix>; } diff --git a/rxing-reader/src/binary_bitmap.rs b/rxing-reader/src/binary_bitmap.rs index 5701c61..50589e2 100644 --- a/rxing-reader/src/binary_bitmap.rs +++ b/rxing-reader/src/binary_bitmap.rs @@ -34,15 +34,15 @@ impl BinaryBitmap { /// Returns the mutable image matrix, where `true` means black. /// Returns an error if the image cannot be binarized into a matrix. - pub fn get_black_matrix_mut(&mut self) -> Result<&mut BitMatrix> { - self.binarizer.get_black_matrix_mut() + pub fn black_matrix_mut(&mut self) -> Result<&mut BitMatrix> { + self.binarizer.black_matrix_mut() } /// Converts the image to a black/white matrix for QR detection. /// Returns the image matrix, where `true` means black. /// Returns an error if the image cannot be binarized into a matrix. - pub fn get_black_matrix(&self) -> Result<&BitMatrix> { - self.binarizer.get_black_matrix() + pub fn black_matrix(&self) -> Result<&BitMatrix> { + self.binarizer.black_matrix() } /// Apply a 3×3 morphological close to the cached BitMatrix: dilate (set @@ -52,7 +52,7 @@ impl BinaryBitmap { /// photos (cf. zxing-cpp's `tryDenoise`). The 1-pixel border is left /// unchanged. No-op if the matrix is smaller than 3×3. pub fn close(&mut self) -> Result<()> { - let matrix = self.get_black_matrix_mut()?; + let matrix = self.black_matrix_mut()?; let (w, h) = (matrix.width(), matrix.height()); if w < 3 || h < 3 { return Ok(()); @@ -84,7 +84,7 @@ fn sum_filter_3x3 bool>(input: &BitMatrix, output: &mut BitMatrix, impl fmt::Display for BinaryBitmap { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.binarizer.get_black_matrix() { + match self.binarizer.black_matrix() { Ok(matrix) => write!(f, "{matrix:?}"), Err(_) => write!(f, ""), } diff --git a/rxing-reader/src/common/bit_array.rs b/rxing-reader/src/common/bit_array.rs index 3a811a3..da31b6a 100644 --- a/rxing-reader/src/common/bit_array.rs +++ b/rxing-reader/src/common/bit_array.rs @@ -61,11 +61,17 @@ impl BitArray { } } - pub fn get_size(&self) -> usize { + #[must_use] + pub fn len(&self) -> usize { self.bits.len() } - pub fn get_size_in_bytes(&self) -> usize { + #[must_use] + pub fn is_empty(&self) -> bool { + self.bits.is_empty() + } + + pub fn byte_len(&self) -> usize { self.bits.len().div_ceil(8) } @@ -90,7 +96,7 @@ impl BitArray { self.bits.set(i, !v); } - pub fn get_next_set(&self, from: usize) -> usize { + pub fn next_set(&self, from: usize) -> usize { let size = self.bits.len(); if from >= size { return size; @@ -101,7 +107,7 @@ impl BitArray { .unwrap_or(size) } - pub fn get_next_unset(&self, from: usize) -> usize { + pub fn next_unset(&self, from: usize) -> usize { let size = self.bits.len(); if from >= size { return size; @@ -124,7 +130,7 @@ impl BitArray { return Err(Error::InvalidArgument { message: format!( "set_range: start={start}, end={end} out of range for bit array of length {len}" - ), + ).into(), } .into()); } @@ -145,7 +151,7 @@ impl BitArray { return Err(Error::InvalidArgument { message: format!( "is_range: start={start}, end={end} out of range for bit array of length {len}" - ), + ).into(), } .into()); } @@ -165,7 +171,7 @@ impl BitArray { pub fn append_bits(&mut self, value: BaseType, num_bits: usize) -> Result<()> { if num_bits > BASE_BITS { return Err(Error::InvalidArgument { - message: format!("num bits must be between 0 and {}", BaseType::BITS), + message: format!("num bits must be between 0 and {}", BaseType::BITS).into(), } .into()); } @@ -190,7 +196,7 @@ impl BitArray { "xor: bit array sizes differ (self={}, other={})", self.bits.len(), other.bits.len(), - ), + ).into(), } .into()); } @@ -214,7 +220,7 @@ impl BitArray { } } - pub fn get_bit_array(&self) -> &[BaseType] { + pub fn words(&self) -> &[BaseType] { self.bits.as_raw_slice() } @@ -251,8 +257,8 @@ impl From for Vec { impl From<&BitArray> for Vec { fn from(value: &BitArray) -> Self { - let mut array = vec![0; value.get_size_in_bytes()]; - value.to_bytes(0, &mut array, 0, value.get_size_in_bytes()); + let mut array = vec![0; value.byte_len()]; + value.to_bytes(0, &mut array, 0, value.byte_len()); array } } @@ -358,7 +364,7 @@ mod tests { let bytes = [0b1000_0001, 0b0101_1010, 0]; let bits = BitArray::from(bytes.to_vec()); - assert_eq!(bits.get_size(), bytes.len() * 8); + assert_eq!(bits.len(), bytes.len() * 8); assert_eq!(Vec::::from(bits), bytes); } @@ -368,6 +374,6 @@ mod tests { bits.set_bulk(5, 0b101); - assert_eq!(bits.get_bit_array()[0], 0b101); + assert_eq!(bits.words()[0], 0b101); } } diff --git a/rxing-reader/src/common/bit_matrix.rs b/rxing-reader/src/common/bit_matrix.rs index bc720ef..de65bf3 100644 --- a/rxing-reader/src/common/bit_matrix.rs +++ b/rxing-reader/src/common/bit_matrix.rs @@ -60,7 +60,7 @@ impl BitMatrix { return Err(Error::InvalidArgument { message: format!( "BitMatrix::new: both dimensions must be greater than 0 (got width={width}, height={height})" - ), + ).into(), } .into()); } @@ -153,7 +153,7 @@ impl BitMatrix { return Err(Error::InvalidArgument { message: format!( "parse_strings: row lengths do not match (row {n_rows} has {this_row_length} cells, expected {row_length})" - ), + ).into(), } .into()); } @@ -174,7 +174,7 @@ impl BitMatrix { message: format!( "illegal character encountered: {}", string_representation[pos..].to_owned() - ), + ).into(), } .into()); } @@ -189,7 +189,7 @@ impl BitMatrix { return Err(Error::InvalidArgument { message: format!( "parse_strings: final row lengths do not match (row {n_rows} has {this_row_length} cells, expected {row_length})" - ), + ).into(), } .into()); } @@ -212,7 +212,7 @@ impl BitMatrix { /// returns the value of given bit in matrix, or false if the requested point is out of bounds of the image #[inline(always)] pub fn get(&self, x: u32, y: u32) -> bool { - let offset = self.get_offset(y, x); + let offset = self.offset(y, x); if offset >= self.bits.len() { return false; } @@ -220,29 +220,29 @@ impl BitMatrix { } #[inline(always)] - pub fn get_point(&self, point: Point) -> bool { + pub fn at_point(&self, point: Point) -> bool { self.get(point.x as u32, point.y as u32) } #[inline(always)] - pub fn get_index>(&self, index: T) -> bool { - self.get_point(self.calculate_point_from_index(index.into())) + pub fn at_index>(&self, index: T) -> bool { + self.at_point(self.calculate_point_from_index(index.into())) } #[inline(always)] fn calculate_point_from_index(&self, index: usize) -> Point { - let row = index / (self.get_width() as usize); - let column = index % (self.get_width() as usize); + let row = index / (self.width() as usize); + let column = index % (self.width() as usize); point_i(column as u32, row as u32) } #[inline(always)] - fn get_offset(&self, y: u32, x: u32) -> usize { + fn offset(&self, y: u32, x: u32) -> usize { y as usize * self.row_size + (x as usize / BASE_BITS) } pub fn try_get(&self, x: u32, y: u32) -> Option { - let offset = self.get_offset(y, x); + let offset = self.offset(y, x); if offset >= self.bits.len() { return None; } @@ -250,11 +250,11 @@ impl BitMatrix { } #[inline(always)] - pub fn try_get_point(&self, point: Point) -> Option { + pub fn try_at_point(&self, point: Point) -> Option { self.try_get(point.x as u32, point.y as u32) } - pub fn try_get_area(&self, x: u32, y: u32, box_size: u32) -> Option { + pub fn try_area_majority(&self, x: u32, y: u32, box_size: u32) -> Option { let mut matrix = Vec::with_capacity((box_size * box_size) as usize); let start_x = (x as i32 - box_size as i32 / 2).max(0) as u32; let end_x = x + box_size / 2; @@ -277,7 +277,7 @@ impl BitMatrix { #[inline(always)] pub fn check_in_bounds(&self, x: u32, y: u32) -> bool { - self.get_offset(y, x) < self.bits.len() + self.offset(y, x) < self.bits.len() } #[inline(always)] @@ -291,7 +291,7 @@ impl BitMatrix { /// - `y`: The vertical component (i.e. which row) #[inline(always)] pub fn set(&mut self, x: u32, y: u32) { - let offset = self.get_offset(y, x); + let offset = self.offset(y, x); self.bits[offset] |= 1 << (x as usize & BASE_SHIFT); } @@ -306,7 +306,7 @@ impl BitMatrix { #[inline(always)] pub fn unset(&mut self, x: u32, y: u32) { - let offset = self.get_offset(y, x); + let offset = self.offset(y, x); self.bits[offset] &= !(1 << (x as usize & BASE_SHIFT)); } @@ -316,7 +316,7 @@ impl BitMatrix { /// - `y`: The vertical component (i.e. which row) #[inline(always)] pub fn flip_coords(&mut self, x: u32, y: u32) { - let offset = self.get_offset(y, x); + let offset = self.offset(y, x); self.bits[offset] ^= 1 << (x as usize & BASE_SHIFT); } @@ -340,14 +340,14 @@ impl BitMatrix { "BitMatrix::xor: input matrix dimensions do not match (self={}x{} row_size={}, mask={}x{} row_size={})", self.width, self.height, self.row_size, mask.width, mask.height, mask.row_size, - ), + ).into(), } .into()); } for y in 0..self.height { let offset = y as usize * self.row_size; - let row_array = mask.get_row(y); - let row = row_array.get_bit_array(); + let row_array = mask.row(y); + let row = row_array.words(); for (x, row_x) in row.iter().enumerate().take(self.row_size) { self.bits[offset + x] ^= *row_x; } @@ -372,7 +372,7 @@ impl BitMatrix { return Err(Error::InvalidArgument { message: format!( "set_region: width and height must be at least 1 (got width={width}, height={height})" - ), + ).into(), } .into()); } @@ -383,7 +383,7 @@ impl BitMatrix { message: format!( "set_region: region (left={left}, top={top}, width={width}, height={height} -> right={right}, bottom={bottom}) does not fit inside matrix {}x{}", self.width, self.height, - ), + ).into(), } .into()); } @@ -401,7 +401,7 @@ impl BitMatrix { /// - `y`: The row to retrieve /// /// Returns the requested row as a new [`BitArray`]. - pub fn get_row(&self, y: u32) -> BitArray { + pub fn row(&self, y: u32) -> BitArray { let mut rw = BitArray::with_size(self.width as usize); let offset = y as usize * self.row_size; @@ -414,7 +414,7 @@ impl BitMatrix { /// Returns a column of the bit matrix. /// /// The current implementation may be very slow. - pub fn get_col(&self, x: u32) -> BitArray { + pub fn column(&self, x: u32) -> BitArray { let mut cw = BitArray::with_size(self.height as usize); for y in 0..self.height { @@ -430,7 +430,7 @@ impl BitMatrix { /// - `row`: [`BitArray`] to copy from pub fn set_row(&mut self, y: u32, row: &BitArray) { self.bits[y as usize * self.row_size..y as usize * self.row_size + self.row_size] - .clone_from_slice(&row.get_bit_array()[0..self.row_size]) + .clone_from_slice(&row.words()[0..self.row_size]) } /// Modifies this `BitMatrix` to represent the same but rotated the given degrees (0, 90, 180, 270) @@ -455,7 +455,7 @@ impl BitMatrix { other => Err(Error::InvalidArgument { message: format!( "rotate: degrees must be 0, 90, 180, or 270 (got {degrees}, normalized to {other})" - ), + ).into(), } .into()), } @@ -465,9 +465,9 @@ impl BitMatrix { pub fn rotate180(&mut self) { let max_height = self.height.div_ceil(2); for i in 0..max_height { - let mut top_row = self.get_row(i); + let mut top_row = self.row(i); let bottom_row_index = self.height - 1 - i; - let mut bottom_row = self.get_row(bottom_row_index); + let mut bottom_row = self.row(bottom_row_index); top_row.reverse(); bottom_row.reverse(); self.set_row(i, &bottom_row); @@ -484,7 +484,7 @@ impl BitMatrix { for y in 0..self.height { for x in 0..self.width { - let offset = self.get_offset(y, x); + let offset = self.offset(y, x); if ((self.bits[offset] >> (x as usize & BASE_SHIFT)) & 1) != 0 { let new_offset: usize = ((new_height - 1 - x) * new_row_size + (y / BASE_BITS as u32)) as usize; @@ -503,7 +503,7 @@ impl BitMatrix { /// Returns the `left, top, width, height` rectangle enclosing all set bits. /// /// Returns `None` when the matrix is all white. - pub fn get_enclosing_rectangle(&self) -> Option<[u32; 4]> { + pub fn enclosing_rectangle(&self) -> Option<[u32; 4]> { let mut left = self.width; let mut top = self.height; let mut right: u32 = 0; @@ -537,7 +537,7 @@ impl BitMatrix { /// Returns the coordinate of the top-left-most set bit. /// /// Returns `None` when the matrix is all white. - pub fn get_top_left_on_bit(&self) -> Option { + pub fn top_left_on_bit(&self) -> Option { let mut bits_offset = 0; while bits_offset < self.bits.len() && self.bits[bits_offset] == 0 { bits_offset += 1; @@ -557,7 +557,7 @@ impl BitMatrix { Some(point(x as f32, y as f32)) } - pub fn get_bottom_right_on_bit(&self) -> Option { + pub fn bottom_right_on_bit(&self) -> Option { let mut bits_offset = self.bits.len() as i64 - 1; while bits_offset >= 0 && self.bits[bits_offset as usize] == 0 { bits_offset -= 1; @@ -579,31 +579,18 @@ impl BitMatrix { Some(point(x as f32, y as f32)) } - /// Returns the width of the matrix. - #[inline(always)] - pub const fn get_width(&self) -> u32 { - self.width() - } - #[inline(always)] pub const fn width(&self) -> u32 { self.width } - /// Returns the height of the matrix. - #[inline(always)] - pub const fn get_height(&self) -> u32 { - self.height() - } - #[inline(always)] pub const fn height(&self) -> u32 { self.height } - /// Returns the row size of the matrix. #[inline(always)] - pub fn get_row_size(&self) -> usize { + pub fn row_size(&self) -> usize { self.row_size } @@ -647,7 +634,7 @@ impl BitMatrix { return Err(Error::InvalidArgument { message: format!( "crop: width and height must be greater than 0 (got width={width}, height={height})" - ), + ).into(), } .into()); } @@ -658,7 +645,7 @@ impl BitMatrix { message: format!( "crop: region (left={left}, top={top}, width={width}, height={height}) does not fit inside matrix {}x{}", self.width, self.height, - ), + ).into(), } .into()); } @@ -683,9 +670,9 @@ impl BitMatrix { #[inline(always)] pub fn is_in_with_border(&self, p: Point, b: i32) -> bool { b as f32 <= p.x - && p.x < self.get_width() as f32 - b as f32 + && p.x < self.width() as f32 - b as f32 && b as f32 <= p.y - && p.y < self.get_height() as f32 - b as f32 + && p.y < self.height() as f32 - b as f32 } } diff --git a/rxing-reader/src/common/bit_source.rs b/rxing-reader/src/common/bit_source.rs index a4493af..fc0c012 100644 --- a/rxing-reader/src/common/bit_source.rs +++ b/rxing-reader/src/common/bit_source.rs @@ -55,7 +55,10 @@ impl<'a> BitSource<'a> { if num_bits > available_bits { message.push_str(&format!("; available bits: {available_bits}")); } - return Err(Error::InvalidArgument { message }.into()); + return Err(Error::InvalidArgument { + message: message.into(), + } + .into()); } let mut result: u32 = 0; @@ -106,7 +109,10 @@ impl<'a> BitSource<'a> { if num_bits > available_bits { message.push_str(&format!("; available bits: {available_bits}")); } - return Err(Error::InvalidArgument { message }.into()); + return Err(Error::InvalidArgument { + message: message.into(), + } + .into()); } let mut bit_offset = self.bit_offset; diff --git a/rxing-reader/src/common/character_set.rs b/rxing-reader/src/common/character_set.rs index 7f508e8..8631c10 100644 --- a/rxing-reader/src/common/character_set.rs +++ b/rxing-reader/src/common/character_set.rs @@ -57,7 +57,7 @@ pub enum CharacterSet { } impl CharacterSet { - fn get_encoding(&self) -> Option<&'static encoding_rs::Encoding> { + fn encoding(&self) -> Option<&'static encoding_rs::Encoding> { match self { CharacterSet::ISO8859_2 => Some(encoding_rs::ISO_8859_2), CharacterSet::ISO8859_3 => Some(encoding_rs::ISO_8859_3), @@ -101,7 +101,7 @@ impl CharacterSet { CharacterSet::UTF32BE => { if !input.len().is_multiple_of(4) { return Err(Error::InvalidFormat { - message: "Invalid UTF-32BE: trailing bytes".to_owned(), + message: "Invalid UTF-32BE: trailing bytes".into(), } .into()); } @@ -111,7 +111,7 @@ impl CharacterSet { let val = u32::from_be_bytes([c[0], c[1], c[2], c[3]]); char::from_u32(val).ok_or_else(|| { Error::InvalidFormat { - message: "Invalid UTF-32BE".to_owned(), + message: "Invalid UTF-32BE".into(), } .into() }) @@ -121,7 +121,7 @@ impl CharacterSet { CharacterSet::UTF32LE => { if !input.len().is_multiple_of(4) { return Err(Error::InvalidFormat { - message: "Invalid UTF-32LE: trailing bytes".to_owned(), + message: "Invalid UTF-32LE: trailing bytes".into(), } .into()); } @@ -131,7 +131,7 @@ impl CharacterSet { let val = u32::from_le_bytes([c[0], c[1], c[2], c[3]]); char::from_u32(val).ok_or_else(|| { Error::InvalidFormat { - message: "Invalid UTF-32LE".to_owned(), + message: "Invalid UTF-32LE".into(), } .into() }) @@ -146,7 +146,7 @@ impl CharacterSet { for &b in input { if b > 0x7F { return Err(Error::InvalidFormat { - message: "Invalid ASCII".to_owned(), + message: "Invalid ASCII".into(), } .into()); } @@ -155,18 +155,18 @@ impl CharacterSet { Ok(s) } _ => { - if let Some(enc) = self.get_encoding() { + if let Some(enc) = self.encoding() { let (res, _, had_errors) = enc.decode(input); if had_errors { return Err(Error::InvalidFormat { - message: "Could not decode character".to_owned(), + message: "Could not decode character".into(), } .into()); } Ok(res.into_owned()) } else { Err(Error::InvalidFormat { - message: "Unsupported encoding".to_owned(), + message: "Unsupported encoding".into(), } .into()) } diff --git a/rxing-reader/src/common/cpp_essentials/base_extensions/bitmatrix.rs b/rxing-reader/src/common/cpp_essentials/base_extensions/bitmatrix.rs index 2c0fea7..3c6f46f 100644 --- a/rxing-reader/src/common/cpp_essentials/base_extensions/bitmatrix.rs +++ b/rxing-reader/src/common/cpp_essentials/base_extensions/bitmatrix.rs @@ -22,7 +22,7 @@ impl BitMatrix { && sample_x < self.width() as f32 && y_offset >= 0.0 && y_offset < self.height() as f32 - && self.get_point(point(sample_x, y_offset)) + && self.at_point(point(sample_x, y_offset)) { result.set(x, y); } @@ -32,26 +32,6 @@ impl BitMatrix { Ok(result) } - pub fn get_top_left_on_bit_with_position(&self, left: &mut u32, top: &mut u32) -> bool { - let Some(Point { x, y }) = self.get_top_left_on_bit() else { - return false; - }; - *left = x as u32; - *top = y as u32; - - true - } - - pub fn get_bottom_right_on_bit_with_position(&self, right: &mut u32, bottom: &mut u32) -> bool { - let Some(Point { x, y }) = self.get_bottom_right_on_bit() else { - return false; - }; - *right = x as u32; - *bottom = y as u32; - - true - } - pub fn find_bounding_box( &self, left: u32, @@ -65,13 +45,16 @@ impl BitMatrix { let mut width = width; let mut height = height; - let mut right = 0; - let mut bottom = 0; - if !self.get_top_left_on_bit_with_position(&mut left, &mut top) - || !self.get_bottom_right_on_bit_with_position(&mut right, &mut bottom) - || bottom - top + 1 < min_size - || right - left + 1 < min_size - { + let (Some(Point { x: left_on_bit, y: top_on_bit }), Some(Point { x: right_on_bit, y: bottom_on_bit })) = + (self.top_left_on_bit(), self.bottom_right_on_bit()) + else { + return (false, left, top, width, height); + }; + left = left_on_bit as u32; + top = top_on_bit as u32; + let mut right = right_on_bit as u32; + let bottom = bottom_on_bit as u32; + if bottom - top + 1 < min_size || right - left + 1 < min_size { return (false, left, top, width, height); } diff --git a/rxing-reader/src/common/cpp_essentials/base_extensions/qrcode_version.rs b/rxing-reader/src/common/cpp_essentials/base_extensions/qrcode_version.rs index 765f6b0..e2d6c11 100644 --- a/rxing-reader/src/common/cpp_essentials/base_extensions/qrcode_version.rs +++ b/rxing-reader/src/common/cpp_essentials/base_extensions/qrcode_version.rs @@ -17,7 +17,7 @@ impl Version { Err(Error::InvalidArgument { message: format!( "Version::model2: version_number {version_number} out of range (expected 1..=40)" - ), + ).into(), } .into()) } else { @@ -50,11 +50,11 @@ impl Version { // We can tolerate up to 3 bits of error since no two version info codewords will // differ in less than 8 bits. if best_difference <= 3 { - return Self::get_version_for_number(best_version as u32); + return Self::for_number(best_version as u32); } // If we didn't find a close enough match, fail Err(Error::InvalidState { - message: "required internal state is missing".to_owned(), + message: "required internal state is missing".into(), } .into()) } @@ -80,7 +80,7 @@ impl Version { Self::is_valid_size(point(matrix.width() as i32, matrix.height() as i32)) } - pub fn number_point(size: PointI) -> u32 { + pub fn number_from_size(size: PointI) -> u32 { if Self::is_valid_size(size) { ((size.x - 17) / 4) as u32 } else { @@ -88,7 +88,7 @@ impl Version { } } - pub fn number(bit_matrix: &BitMatrix) -> u32 { - Self::number_point(point(bit_matrix.width() as i32, bit_matrix.height() as i32)) + pub fn number_from_matrix(bit_matrix: &BitMatrix) -> u32 { + Self::number_from_size(point(bit_matrix.width() as i32, bit_matrix.height() as i32)) } } diff --git a/rxing-reader/src/common/cpp_essentials/bitmatrix_cursor_trait.rs b/rxing-reader/src/common/cpp_essentials/bitmatrix_cursor_trait.rs index 8f59327..e216c2f 100644 --- a/rxing-reader/src/common/cpp_essentials/bitmatrix_cursor_trait.rs +++ b/rxing-reader/src/common/cpp_essentials/bitmatrix_cursor_trait.rs @@ -49,7 +49,11 @@ pub trait BitMatrixCursorTrait { fn set_direction(&mut self, dir: Point); - fn step(&mut self, s: Option) -> bool; + fn step_by(&mut self, distance: f32) -> bool; + + fn step(&mut self) -> bool { + self.step_by(1.0) + } fn turned_back(&self) -> Self; @@ -58,11 +62,13 @@ pub trait BitMatrixCursorTrait { /// - `backup`: whether or not to backup one step so we land in front of the edge /// /// Returns number of steps taken or 0 if moved outside of range/image. - fn step_to_edge(&mut self, nth: Option, range: Option, backup: Option) -> i32; + fn step_to_edge(&mut self, nth: i32, range: i32, backup: bool) -> i32; - fn step_along_edge(&mut self, dir: Direction, skip_corner: Option) -> bool { - let skip_corner = skip_corner.unwrap_or_default(); + fn step_along_edge(&mut self, dir: Direction) -> bool { + self.step_along_edge_with_corner_skip(dir, false) + } + fn step_along_edge_with_corner_skip(&mut self, dir: Direction, skip_corner: bool) -> bool { if !self.edge_at_direction(dir).is_valid() { self.turn(dir); } else if self.edge_at_front().is_valid() { @@ -75,11 +81,11 @@ pub trait BitMatrixCursorTrait { } } - let mut ret = self.step(None); + let mut ret = self.step(); if ret && skip_corner && !self.edge_at_direction(dir).is_valid() { self.turn(dir); - ret = self.step(None); + ret = self.step(); } ret diff --git a/rxing-reader/src/common/cpp_essentials/concentric_finder.rs b/rxing-reader/src/common/cpp_essentials/concentric_finder.rs index a5e51d7..869e751 100644 --- a/rxing-reader/src/common/cpp_essentials/concentric_finder.rs +++ b/rxing-reader/src/common/cpp_essentials/concentric_finder.rs @@ -29,7 +29,7 @@ pub fn read_symmetric_pattern( let mut cuo = cur.turned_back(); let mut next = |cur: &mut Cursor, i: isize| { - let v = cur.step_to_edge(Some(1), Some(range), None); + let v = cur.step_to_edge(1, range, false); res[(s_2 + i) as usize] = (res[(s_2 + i) as usize] as i32 + v) as u16; if range != 0 { range -= v; @@ -102,7 +102,7 @@ pub fn check_symmetric_pattern< } if is_pattern::( - &PatternView::new(&res), + &PatternView::from_bars(res.as_slice()), &FixedPattern::::with_reference(pattern), None, 0.0, @@ -113,7 +113,7 @@ pub fn check_symmetric_pattern< } if update_position { - cur.step(Some((res[s_2] as i32 / 2 - (center_bwd - 1)) as f32)); + cur.step_by((res[s_2] as i32 / 2 - (center_bwd - 1)) as f32); } res.into_iter().sum::() as i32 @@ -130,7 +130,7 @@ pub fn average_edge_pixels( if !cur.is_in_self() { return None; } - cur.step_to_edge(Some(1), Some(range), None); + cur.step_to_edge(1, range, false); sum += cur.p().centered() + (cur.p() + cur.back()).centered(); } Some(sum / (2 * num_of_edges) as f32) @@ -173,7 +173,7 @@ pub fn center_of_ring( let inner = nth < 0; let nth = nth.abs(); let mut cur = EdgeTracer::new(image, center, point(0.0, 1.0)); - if cur.step_to_edge(Some(nth), Some(radius), Some(inner)) == 0 { + if cur.step_to_edge(nth, radius, inner) == 0 { return None; } cur.turn_right(); // move clock wise and keep edge on the right/left depending on backup @@ -199,7 +199,7 @@ pub fn center_of_ring( point(1.0, 3.0), )) as u32; - if !cur.step_along_edge(edge_dir, None) { + if !cur.step_along_edge(edge_dir) { return None; } @@ -260,7 +260,7 @@ pub fn collect_ring_points( let center_i = center.floor(); let radius = range; let mut cur = EdgeTracer::new(image, center_i, point(0.0, 1.0)); - if cur.step_to_edge(Some(edge_index), Some(radius), Some(backup)) == 0 { + if cur.step_to_edge(edge_index, radius, backup) == 0 { return Vec::default(); } cur.turn_right(); // move clock wise and keep edge on the right/left depending on backup @@ -285,7 +285,7 @@ pub fn collect_ring_points( point(1.0, 3.0), )) as u32; - if !cur.step_along_edge(edge_dir, None) { + if !cur.step_along_edge(edge_dir) { return Vec::default(); } @@ -524,7 +524,7 @@ pub fn locate_concentric_pattern Option { let mut cur = EdgeTracer::new(image, center.floor(), Point::default()); - let mut min_spread = image.get_width() as i32; + let mut min_spread = image.width() as i32; let mut max_spread = 0_i32; // TODO: setting max_error to 1 can subtantially help with detecting symbols with low print quality resulting in damaged @@ -579,12 +579,12 @@ pub fn finetune_concentric_pattern_center( ) -> Option { // make sure we have at least one path of white around the center if let Some(res1) = center_of_ring(image, center.floor(), range, 1, true) { - if !image.get_point(res1) { + if !image.at_point(res1) { return None; } // and then either at least one more ring around that if let Some(res2) = center_of_rings(image, res1, range, finder_pattern_size / 2) { - return if image.get_point(res2) { + return if image.at_point(res2) { Some(res2) } else { None @@ -598,7 +598,7 @@ pub fn finetune_concentric_pattern_center( if let Some(res2) = center_of_double_cross(image, res1.floor(), range, finder_pattern_size / 2 + 1) { - return if image.get_point(res2) { + return if image.at_point(res2) { Some(res2) } else { None diff --git a/rxing-reader/src/common/cpp_essentials/dm_regression_line.rs b/rxing-reader/src/common/cpp_essentials/dm_regression_line.rs index 7c254f4..75e9b42 100644 --- a/rxing-reader/src/common/cpp_essentials/dm_regression_line.rs +++ b/rxing-reader/src/common/cpp_essentials/dm_regression_line.rs @@ -51,7 +51,7 @@ impl RegressionLineTrait for DMRegressionLine { fn add(&mut self, p: Point) -> Result<()> { if self.direction_inward == Point::default() { return Err(Error::InvalidState { - message: "required internal state is missing".to_owned(), + message: "required internal state is missing".into(), } .into()); } @@ -66,14 +66,7 @@ impl RegressionLineTrait for DMRegressionLine { self.direction_inward = Point::normalized(d); } - fn evaluate_max_distance( - &mut self, - max_signed_dist: Option, - update_points: Option, - ) -> bool { - let max_signed_dist = max_signed_dist.unwrap_or(-1.0); - let update_points = update_points.unwrap_or_default(); - + fn evaluate_max_distance_with(&mut self, max_signed_dist: f64, update_points: bool) -> bool { let mut ret = self.evaluate_self(); if max_signed_dist > 0.0 { let mut points = Vec::with_capacity(self.points.len()); @@ -99,12 +92,11 @@ impl RegressionLineTrait for DMRegressionLine { } fn is_high_res(&self) -> bool { - let Some(mut min) = self.points.first().copied() else { - return false; - }; - let Some(mut max) = self.points.first().copied() else { + let Some(first) = self.points.first().copied() else { return false; }; + let mut min = first; + let mut max = first; for p in &self.points { min.x = f32::min(min.x, p.x); min.y = f32::min(min.y, p.y); diff --git a/rxing-reader/src/common/cpp_essentials/edge_tracer.rs b/rxing-reader/src/common/cpp_essentials/edge_tracer.rs index ee0dd59..ee85589 100644 --- a/rxing-reader/src/common/cpp_essentials/edge_tracer.rs +++ b/rxing-reader/src/common/cpp_essentials/edge_tracer.rs @@ -13,7 +13,7 @@ pub struct EdgeTracer<'a> { impl BitMatrixCursorTrait for EdgeTracer<'_> { fn test_at(&self, p: Point) -> Value { if self.img.is_in_with_border(p, 0) { - Value::from(self.img.get_point(p)) + Value::from(self.img.at_point(p)) } else { Value::Invalid } @@ -85,9 +85,8 @@ impl BitMatrixCursorTrait for EdgeTracer<'_> { self.d = dir.bresenham_direction(); } - fn step(&mut self, s: Option) -> bool { - let s = s.unwrap_or(1.0); - self.p += self.d * s; + fn step_by(&mut self, distance: f32) -> bool { + self.p += self.d * distance; self.is_in(self.p) } @@ -104,10 +103,8 @@ impl BitMatrixCursorTrait for EdgeTracer<'_> { /// - `backup`: whether or not to backup one step so we land in front of the edge /// /// Returns number of steps taken or 0 if moved outside of range/image. - fn step_to_edge(&mut self, nth: Option, range: Option, backup: Option) -> i32 { - let mut nth = nth.unwrap_or(1); - let range = range.unwrap_or(0); - let backup = backup.unwrap_or(false); + fn step_to_edge(&mut self, nth: i32, range: i32, backup: bool) -> i32 { + let mut nth = nth; // TODO: provide an alternative and faster out-of-bounds check than is_in() inside test_at() let mut steps = 0; let mut lv = self.test_at(self.p); diff --git a/rxing-reader/src/common/cpp_essentials/fast_edge_to_edge_counter.rs b/rxing-reader/src/common/cpp_essentials/fast_edge_to_edge_counter.rs index 46d3fd5..b961bfa 100644 --- a/rxing-reader/src/common/cpp_essentials/fast_edge_to_edge_counter.rs +++ b/rxing-reader/src/common/cpp_essentials/fast_edge_to_edge_counter.rs @@ -24,7 +24,7 @@ impl FastEdgeToEdgeCounter<'_> { let image_len = width .checked_mul(height) .ok_or_else(|| Error::InvalidArgument { - message: format!("FastEdgeToEdgeCounter: image size overflow ({width} x {height})"), + message: format!("FastEdgeToEdgeCounter: image size overflow ({width} x {height})").into(), })?; if !(0..image_len).contains(&p) { return Err(Error::InvalidArgument { @@ -32,7 +32,8 @@ impl FastEdgeToEdgeCounter<'_> { "FastEdgeToEdgeCounter: cursor index {p} is outside image of size {width}x{height} (cursor=({}, {}))", cur.p().x, cur.p().y, - ), + ) + .into(), } .into()); } @@ -84,7 +85,7 @@ impl FastEdgeToEdgeCounter<'_> { return 0; }; - if self.under_array.get_index(idx_pt) != self.under_array.get_index(self.p as usize) { + if self.under_array.at_index(idx_pt) != self.under_array.at_index(self.p as usize) { break; } } diff --git a/rxing-reader/src/common/cpp_essentials/matrix.rs b/rxing-reader/src/common/cpp_essentials/matrix.rs index 66814c6..12fb19d 100644 --- a/rxing-reader/src/common/cpp_essentials/matrix.rs +++ b/rxing-reader/src/common/cpp_essentials/matrix.rs @@ -13,7 +13,7 @@ impl Matrix { let size = width .checked_mul(height) .ok_or_else(|| Error::InvalidArgument { - message: format!("Matrix::new: width * height overflow ({width} x {height})"), + message: format!("Matrix::new: width * height overflow ({width} x {height})").into(), })?; Ok(Self { width, @@ -22,14 +22,14 @@ impl Matrix { }) } - fn get_offset(x: usize, y: usize, width: usize) -> usize { + fn offset(x: usize, y: usize, width: usize) -> usize { y * width + x } pub fn get(&self, x: usize, y: usize) -> Option { if x >= self.width || y >= self.height { None - } else if let Some(Some(d)) = self.data.get(Self::get_offset(x, y, self.width)) { + } else if let Some(Some(d)) = self.data.get(Self::offset(x, y, self.width)) { Some(*d) } else { None @@ -42,11 +42,11 @@ impl Matrix { message: format!( "set: coordinates ({x}, {y}) outside {}x{} matrix", self.width, self.height - ), + ).into(), } .into()); } - let offset = Self::get_offset(x, y, self.width); + let offset = Self::offset(x, y, self.width); self.data[offset] = Some(value); Ok(value) } diff --git a/rxing-reader/src/common/cpp_essentials/pattern.rs b/rxing-reader/src/common/cpp_essentials/pattern.rs index 8686001..17adab3 100644 --- a/rxing-reader/src/common/cpp_essentials/pattern.rs +++ b/rxing-reader/src/common/cpp_essentials/pattern.rs @@ -47,6 +47,10 @@ impl PatternRow { pub fn rev(&mut self) { self.0.reverse() } + + pub fn as_slice(&self) -> &[PatternType] { + &self.0 + } } impl IntoIterator for PatternRow { @@ -95,7 +99,7 @@ impl Iterator for PatternViewIterator<'_> { self.current_position += 1; Some( - *self.pattern_view.data.0.get( + *self.pattern_view.data.get( self.current_position - 1 + self.pattern_view.start + self.pattern_view.current, )?, ) @@ -104,20 +108,36 @@ impl Iterator for PatternViewIterator<'_> { #[derive(Debug, Clone, Copy)] pub struct PatternView<'a> { - data: &'a PatternRow, + data: &'a [PatternType], start: usize, count: usize, current: usize, } impl<'a> PatternView<'a> { - // A PatternRow always starts with the width of whitespace in front of the first black bar. - // The first element of the PatternView is the first bar. + // A PatternRow always starts with the width of whitespace in front of the + // first black bar (see `get_pattern_row`). `start = 1` skips that + // leading-whitespace element, so `view[0]` is the first bar; `count` + // is the number of view elements (bars + spaces, excluding the + // leading-whitespace prefix). pub fn new(bars: &'a PatternRow) -> PatternView<'a> { PatternView { - data: bars, + data: &bars.0, start: 1, - count: bars.0.len(), + count: bars.0.len().saturating_sub(1), + current: 0, + } + } + + /// Construct a view directly over a bar-only buffer (no leading-whitespace + /// element at index 0). `view[0]` is `bars[0]`; use this for buffers that + /// were built as raw bar-width arrays rather than fed through + /// `get_pattern_row`. + pub fn from_bars(bars: &'a [PatternType]) -> PatternView<'a> { + PatternView { + data: bars, + start: 0, + count: bars.len(), current: 0, } } @@ -130,32 +150,29 @@ impl<'a> PatternView<'a> { _end: usize, ) -> PatternView<'a> { PatternView { - data: bars, + data: &bars.0, start, count: size, current: base, } } - pub fn data(&self) -> &PatternRow { + pub fn data(&self) -> &[PatternType] { self.data } pub fn begin(&self) -> Option { - Some(*self.data.0.get(self.start)?) + Some(*self.data.get(self.start)?) } pub fn end(&self) -> Option { - Some(self.data.0.len() as PatternType) + Some(self.data.len() as PatternType) } - pub fn sum(&self, n: Option) -> PatternType { - if self.count == self.data.len() { - return self.data.0.iter().sum::(); - } - - let n = n.unwrap_or(self.count); + pub fn sum(&self) -> PatternType { + self.sum_first(self.count) + } + pub fn sum_first(&self, n: usize) -> PatternType { self.data - .0 .iter() .skip(self.start + self.current) .take(n) @@ -180,7 +197,6 @@ impl<'a> PatternView<'a> { } pub fn pixels_in_front(&self) -> PatternType { self.data - .0 .iter() .take(self.start + self.current) .copied() @@ -188,7 +204,6 @@ impl<'a> PatternView<'a> { } pub fn pixels_till_end(&self) -> PatternType { self.data - .0 .iter() .take(self.start + self.current + self.count) .copied() @@ -202,41 +217,36 @@ impl<'a> PatternView<'a> { self.current == self.start + self.count - 1 } pub fn is_valid_with_n(&self, n: usize) -> bool { - !self.data.0.is_empty() + !self.data.is_empty() && self.start <= self.current + self.start - && self.current + n < (self.data.0.len()) + && self.current + n < (self.data.len()) } pub fn is_valid(&self) -> bool { self.is_valid_with_n(self.size()) } - pub fn has_quiet_zone_before(&self, scale: f32, accept_if_at_first_bar: Option) -> bool { - if accept_if_at_first_bar.unwrap_or(false) && self.is_at_first_bar() { + pub fn has_quiet_zone_before(&self, scale: f32, accept_if_at_first_bar: bool) -> bool { + if accept_if_at_first_bar && self.is_at_first_bar() { return true; } let prev_idx = (self.start + self.current).checked_sub(1); - match prev_idx.and_then(|i| self.data.0.get(i)) { - Some(v) => Into::::into(*v) >= Into::::into(self.sum(None)) * scale, + match prev_idx.and_then(|i| self.data.get(i)) { + Some(v) => Into::::into(*v) >= Into::::into(self.sum()) * scale, None => false, } } - pub fn has_quiet_zone_after(&self, scale: f32, accept_if_at_last_bar: Option) -> bool { - if accept_if_at_last_bar.unwrap_or(true) && self.is_at_last_bar() { + pub fn has_quiet_zone_after(&self, scale: f32, accept_if_at_last_bar: bool) -> bool { + if accept_if_at_last_bar && self.is_at_last_bar() { return true; } - match self.data.0.get(self.start + self.current + self.count) { - Some(v) => Into::::into(*v) >= Into::::into(self.sum(None)) * scale, + match self.data.get(self.start + self.current + self.count) { + Some(v) => Into::::into(*v) >= Into::::into(self.sum()) * scale, None => false, } } - pub fn sub_view(&self, offset: usize, size: Option) -> PatternView<'a> { - let mut size = size.unwrap_or(0); - if size == 0 { - size = self.count - offset; - } - + pub fn sub_view(&self, offset: usize, size: usize) -> PatternView<'a> { PatternView { data: self.data, start: self.start + offset, @@ -247,7 +257,7 @@ impl<'a> PatternView<'a> { pub fn shift(&mut self, n: usize) -> bool { self.current += n; - !self.data.0.is_empty() + !self.data.is_empty() } pub fn skip_pair(&mut self) -> bool { @@ -269,19 +279,21 @@ impl<'a> PatternView<'a> { ) as usize } - fn try_get_index(&self, index: isize) -> Option { - if index.abs() > self.data.0.len() as isize { + /// Translate a signed view-relative index into an absolute `self.data` + /// position, returning `None` when the index would land outside the + /// underlying buffer. Returns the position rather than the value so + /// `Index` / `Index` can resolve to `&self.data[spot]` + /// without recomputing the offset. + fn try_index(&self, index: isize) -> Option { + if index.abs() > self.data.len() as isize { return None; } - if index >= 0 { - let fetch_spot = ((self.start + self.current) as isize + index) as usize; - return self.data.0.get(fetch_spot).copied(); - } - if index.abs() > (self.start + self.current) as isize { + let base = (self.start + self.current) as isize; + if index < 0 && index.abs() > base { return None; } - let fetch_spot = ((self.start + self.current) as isize + index) as usize; - self.data.0.get(fetch_spot).copied() + let spot = (base + index) as usize; + (spot < self.data.len()).then_some(spot) } } @@ -289,17 +301,13 @@ impl std::ops::Index for PatternView<'_> { type Output = PatternType; fn index(&self, index: isize) -> &Self::Output { - if self.count == self.data.len() && index >= 0 { - return &self.data[index as usize]; - } - if self.try_get_index(index).is_none() { + let spot = self.try_index(index).unwrap_or_else(|| { panic!( "index out of bounds: the len is {} but the index is {}", self.count, index ) - } - let fetch_spot = ((self.start + self.current) as isize + index) as usize; - &self.data.0[fetch_spot] + }); + &self.data[spot] } } @@ -307,11 +315,7 @@ impl std::ops::Index for PatternView<'_> { type Output = PatternType; fn index(&self, index: usize) -> &Self::Output { - if self.count == self.data.len() { - return &self.data[index]; - } - - match self.data.0.get(self.start + self.current + index) { + match self.data.get(self.start + self.current + index) { Some(value) => value, None => panic!( "index out of bounds: the len is {} but the index is {}", @@ -352,13 +356,9 @@ impl<'a, const LEN: usize> From<&PatternView<'a>> for [PatternType; LEN] { impl<'a> From<&PatternView<'a>> for &'a [PatternType] { fn from(value: &PatternView<'a>) -> Self { - if value.data.0.len() == value.count { - &value.data.0 - } else { - let start_idx = value.current + value.start; - let end_idx = (start_idx + value.count).min(value.data.0.len()); - &value.data.0[start_idx..end_idx] - } + let start_idx = value.current + value.start; + let end_idx = (start_idx + value.count).min(value.data.len()); + &value.data[start_idx..end_idx] } } @@ -496,7 +496,7 @@ pub fn is_pattern) if view.size() < min_size { return Err(Error::InvalidState { - message: "required internal state is missing".to_owned(), + message: "required internal state is missing".into(), } .into()); } - let mut window = view.sub_view(0, Some(LEN)); + let mut window = view.sub_view(0, LEN); if window.is_at_first_bar() && is_guard(&window, Some(f32::MAX)) { return Ok(window); } let end = Into::::into(view.end().ok_or_else(|| Error::InvalidState { - message: "pattern view has no end index".to_owned(), + message: "pattern view has no end index".into(), })?) - min_size; while (window.start + window.current) < end { - let prev = window.try_get_index(PREV_IDX).map(|v| v as f32); + let prev = window + .try_index(PREV_IDX) + .map(|spot| window.data[spot] as f32); if is_guard(&window, prev) { return Ok(window); } @@ -564,7 +566,7 @@ pub fn find_left_guard_by) } Err(Error::InvalidState { - message: "required internal state is missing".to_owned(), + message: "required internal state is missing".into(), } .into()) } @@ -586,9 +588,9 @@ impl> From for Color { pub fn get_pattern_row_tp(matrix: &BitMatrix, r: u32, pr: &mut PatternRow, transpose: bool) { let row = if transpose { - matrix.get_col(r) + matrix.column(r) } else { - matrix.get_row(r) + matrix.row(r) }; let pixel_states: Vec = row.into(); @@ -715,15 +717,24 @@ mod tests { ], &mut p_row, ); + // p_row layout (11 elements): [leading_whitespace, bar, space, bar, + // space, bar, space, bar, space, bar, trailing_zero] + // = [1, 1, 1, 1, + // 2, 3, 2, 6, 4, 1, 0] + // PatternView::new sets start = 1, so view[i] = data[1 + i]. let mut pv = PatternView::new(&p_row); - assert_eq!(pv.data().0, p_row.0); + assert_eq!(pv.data(), p_row.0.as_slice()); - assert_eq!(pv[0], 1_u16); - assert_eq!(pv[1], 1_u16); - assert_eq!(pv[4], 2_u16); - assert_eq!(pv[7], 6_u16); + // view[0] is the first bar (1) — the leading-whitespace element at + // data[0] is no longer reachable via positive indexing; it sits one + // slot "before" view[0] and is read via has_quiet_zone_before / + // try_index(-1). + assert_eq!(pv[0], 1_u16); // data[1]: first bar + assert_eq!(pv[1], 1_u16); // data[2]: first space + assert_eq!(pv[4], 3_u16); // data[5]: third bar + assert_eq!(pv[7], 4_u16); // data[8]: fourth space assert_eq!(pv.index(), 0); assert!(pv.shift(1)); diff --git a/rxing-reader/src/common/cpp_essentials/regression_line.rs b/rxing-reader/src/common/cpp_essentials/regression_line.rs index 504fcc4..291246c 100644 --- a/rxing-reader/src/common/cpp_essentials/regression_line.rs +++ b/rxing-reader/src/common/cpp_essentials/regression_line.rs @@ -51,7 +51,7 @@ impl RegressionLineTrait for RegressionLine { fn add(&mut self, p: Point) -> Result<()> { if self.direction_inward == Point::default() { return Err(Error::InvalidState { - message: "required internal state is missing".to_owned(), + message: "required internal state is missing".into(), } .into()); } @@ -66,14 +66,7 @@ impl RegressionLineTrait for RegressionLine { self.direction_inward = Point::normalized(d); } - fn evaluate_max_distance( - &mut self, - max_signed_dist: Option, - update_points: Option, - ) -> bool { - let max_signed_dist = max_signed_dist.unwrap_or(-1.0); - let update_points = update_points.unwrap_or_default(); - + fn evaluate_max_distance_with(&mut self, max_signed_dist: f64, update_points: bool) -> bool { let mut ret = self.evaluate_self(); if max_signed_dist > 0.0 { let mut points = Vec::with_capacity(self.points.len()); diff --git a/rxing-reader/src/common/cpp_essentials/regression_line_trait.rs b/rxing-reader/src/common/cpp_essentials/regression_line_trait.rs index 3fa33b8..fccd309 100644 --- a/rxing-reader/src/common/cpp_essentials/regression_line_trait.rs +++ b/rxing-reader/src/common/cpp_essentials/regression_line_trait.rs @@ -39,11 +39,7 @@ pub trait RegressionLineTrait { fn set_direction_inward(&mut self, d: Point); - fn evaluate_max_distance( - &mut self, - max_signed_dist: Option, - update_points: Option, - ) -> bool; + fn evaluate_max_distance_with(&mut self, max_signed_dist: f64, update_points: bool) -> bool; fn is_high_res(&self) -> bool; fn a(&self) -> f32; diff --git a/rxing-reader/src/common/cpp_essentials/util.rs b/rxing-reader/src/common/cpp_essentials/util.rs index 6e01753..085eb12 100644 --- a/rxing-reader/src/common/cpp_essentials/util.rs +++ b/rxing-reader/src/common/cpp_essentials/util.rs @@ -36,7 +36,7 @@ pub fn to_string>(val: T, len: usize) -> Result { } if val != 0 { return Err(Error::InvalidFormat { - message: "Invalid value".to_owned(), + message: "Invalid value".into(), } .into()); } diff --git a/rxing-reader/src/common/default_grid_sampler.rs b/rxing-reader/src/common/default_grid_sampler.rs index d126cad..f96b726 100644 --- a/rxing-reader/src/common/default_grid_sampler.rs +++ b/rxing-reader/src/common/default_grid_sampler.rs @@ -32,7 +32,7 @@ impl GridSampler for DefaultGridSampler { ) -> Result { if dimension_x == 0 || dimension_y == 0 { return Err(Error::NotFound { - message: "barcode pattern was not detected".to_owned(), + message: "barcode pattern was not detected".into(), } .into()); } @@ -51,7 +51,7 @@ impl GridSampler for DefaultGridSampler { || !is_inside(p0.x, p1.y - 1.0) { return Err(Error::NotFound { - message: "barcode pattern was not detected".to_owned(), + message: "barcode pattern was not detected".into(), } .into()); } @@ -64,7 +64,7 @@ impl GridSampler for DefaultGridSampler { let p = transform .transform_point(Point::from((x, y)).centered()) .ok_or(Error::NotFound { - message: "barcode pattern was not detected".to_owned(), + message: "barcode pattern was not detected".into(), })?; // Due to a "numerical instability" in the PerspectiveTransform generation/application it has been observed @@ -74,12 +74,12 @@ impl GridSampler for DefaultGridSampler { // TODO: Check some mathematical/numercial property of mod2_pix to determine if it is a perspective transforation. if !image.is_in(p) { return Err(Error::NotFound { - message: "barcode pattern was not detected".to_owned(), + message: "barcode pattern was not detected".into(), } .into()); } - if image.get_point(p) { + if image.at_point(p) { bits.set(x as u32, y as u32); } } diff --git a/rxing-reader/src/common/eci.rs b/rxing-reader/src/common/eci.rs index 4678151..b3908cc 100644 --- a/rxing-reader/src/common/eci.rs +++ b/rxing-reader/src/common/eci.rs @@ -50,7 +50,7 @@ impl TryFrom for Eci { Ok((value as i32).into()) } else { Err(Error::InvalidArgument { - message: format!("ECI value {value} exceeds i32::MAX"), + message: format!("ECI value {value} exceeds i32::MAX").into(), } .into()) } diff --git a/rxing-reader/src/common/global_histogram_binarizer.rs b/rxing-reader/src/common/global_histogram_binarizer.rs index bd8bcbc..d0334cc 100644 --- a/rxing-reader/src/common/global_histogram_binarizer.rs +++ b/rxing-reader/src/common/global_histogram_binarizer.rs @@ -45,24 +45,24 @@ pub struct GlobalHistogramBinarizer { impl Binarizer for GlobalHistogramBinarizer { type Source = LS; - fn get_luminance_source(&self) -> &Self::Source { + fn luminance_source(&self) -> &Self::Source { &self.source } // Does not sharpen the data, as this call is intended to only be used by 2D Readers. - fn get_black_matrix(&self) -> Result<&BitMatrix> { + fn black_matrix(&self) -> Result<&BitMatrix> { let matrix = self .black_matrix .get_or_try_init(|| Self::build_black_matrix(&self.source))?; Ok(matrix) } - fn get_black_matrix_mut(&mut self) -> Result<&mut BitMatrix> { + fn black_matrix_mut(&mut self) -> Result<&mut BitMatrix> { self.black_matrix .get_or_try_init(|| Self::build_black_matrix(&self.source))?; self.black_matrix.get_mut().ok_or_else(|| { Error::InvalidState { - message: "black matrix cache was not initialized".to_owned(), + message: "black matrix cache was not initialized".into(), } .into() }) @@ -78,8 +78,8 @@ impl GlobalHistogramBinarizer { } pub(super) fn build_black_matrix(source: &LS) -> Result { - let width = source.get_width(); - let height = source.get_height(); + let width = source.width(); + let height = source.height(); let mut matrix = BitMatrix::new(width as u32, height as u32)?; // Quickly calculates the histogram by sampling four rows from the image. This proved to be @@ -87,8 +87,8 @@ impl GlobalHistogramBinarizer { let mut local_buckets = [0; LUMINANCE_BUCKETS]; for y in 1..5 { let row = height * y / 5; - let local_luminances = source.get_row(row).ok_or_else(|| Error::InvalidState { - message: format!("luminance source returned no data for sampled row {row}"), + let local_luminances = source.row(row).ok_or_else(|| Error::InvalidState { + message: format!("luminance source returned no data for sampled row {row}").into(), })?; let right = (width * 4) / 5; for pixel in &local_luminances[(width / 5)..right] { @@ -100,7 +100,7 @@ impl GlobalHistogramBinarizer { // We delay reading the entire image luminance until the black point estimation succeeds. // Although we end up reading four rows twice, it is consistent with our motto of // "fail quickly" which is necessary for continuous scanning. - let local_luminances = source.get_matrix(); + let local_luminances = source.matrix(); for y in 0..height { let offset = y * width; for x in 0..width { @@ -146,14 +146,14 @@ impl GlobalHistogramBinarizer { // Make sure first_peak corresponds to the black peak. if first_peak > second_peak { - std::mem::swap(&mut first_peak, &mut second_peak); + (first_peak, second_peak) = (second_peak, first_peak); } // If there is too little contrast in the image to pick a meaningful black point, throw rather // than waste time trying to decode the image, and risk false positives. if second_peak - first_peak <= BUCKET_COUNT / 16 { return Err(Error::NotFound { - message: "second_peak - first_peak <= numBuckets / 16 ".to_owned(), + message: "second_peak - first_peak <= numBuckets / 16 ".into(), } .into()); } diff --git a/rxing-reader/src/common/grid_sampler.rs b/rxing-reader/src/common/grid_sampler.rs index 923f4f8..b5547a3 100644 --- a/rxing-reader/src/common/grid_sampler.rs +++ b/rxing-reader/src/common/grid_sampler.rs @@ -35,7 +35,7 @@ pub trait GridSampler { ) -> Result { if dimension_x == 0 || dimension_y == 0 { return Err(Error::NotFound { - message: "barcode pattern was not detected".to_owned(), + message: "barcode pattern was not detected".into(), } .into()); } @@ -59,7 +59,7 @@ pub trait GridSampler { if image .try_get(point.x as u32, point.y as u32) .ok_or(Error::NotFound { - message: "grid point transformed outside image bounds".to_owned(), + message: "grid point transformed outside image bounds".into(), })? { // Black(-ish) pixel @@ -86,8 +86,8 @@ pub trait GridSampler { /// /// Returns a not-found error if an endpoint is lies outside the image boundaries fn check_and_nudge_points(&self, image: &BitMatrix, points: &mut [Point]) -> Result<()> { - let width = image.get_width(); - let height = image.get_height(); + let width = image.width(); + let height = image.height(); // Check and nudge points from start until we see some that are OK: let mut nudged; if points.is_empty() { @@ -98,7 +98,7 @@ pub trait GridSampler { let (x, y) = (point.x as i32, point.y as i32); if x < -1 || x > width as i32 || y < -1 || y > height as i32 { return Err(Error::NotFound { - message: "barcode pattern was not detected".to_owned(), + message: "barcode pattern was not detected".into(), } .into()); } @@ -126,7 +126,7 @@ pub trait GridSampler { let (x, y) = (point.x as i32, point.y as i32); if x < -1 || x > width as i32 || y < -1 || y > height as i32 { return Err(Error::NotFound { - message: "barcode pattern was not detected".to_owned(), + message: "barcode pattern was not detected".into(), } .into()); } diff --git a/rxing-reader/src/common/hybrid_binarizer.rs b/rxing-reader/src/common/hybrid_binarizer.rs index db7a5ce..9d1fb87 100644 --- a/rxing-reader/src/common/hybrid_binarizer.rs +++ b/rxing-reader/src/common/hybrid_binarizer.rs @@ -43,26 +43,26 @@ pub struct HybridBinarizer { impl Binarizer for HybridBinarizer { type Source = LS; - fn get_luminance_source(&self) -> &LS { - self.ghb.get_luminance_source() + fn luminance_source(&self) -> &LS { + self.ghb.luminance_source() } /// Calculates the final BitMatrix once for all requests. This could be called once from the /// constructor instead, but there are some advantages to doing it lazily, such as making /// profiling easier, and not doing heavy lifting when callers don't expect it. - fn get_black_matrix(&self) -> Result<&BitMatrix> { + fn black_matrix(&self) -> Result<&BitMatrix> { let matrix = self .black_matrix .get_or_try_init(|| Self::calculate_black_matrix(&self.ghb))?; Ok(matrix) } - fn get_black_matrix_mut(&mut self) -> Result<&mut BitMatrix> { + fn black_matrix_mut(&mut self) -> Result<&mut BitMatrix> { self.black_matrix .get_or_try_init(|| Self::calculate_black_matrix(&self.ghb))?; self.black_matrix.get_mut().ok_or_else(|| { Error::InvalidState { - message: "black matrix cache was not initialized".to_owned(), + message: "black matrix cache was not initialized".into(), } .into() }) @@ -89,12 +89,12 @@ impl HybridBinarizer { fn calculate_black_matrix( ghb: &GlobalHistogramBinarizer, ) -> Result { - let source = ghb.get_luminance_source(); - let width = source.get_width(); - let height = source.get_height(); + let source = ghb.luminance_source(); + let width = source.width(); + let height = source.height(); if width >= MINIMUM_DIMENSION && height >= MINIMUM_DIMENSION { - let luminances = source.get_matrix(); + let luminances = source.matrix(); let mut sub_width = width >> BLOCK_SIZE_POWER; if (width & BLOCK_SIZE_MASK) != 0 { sub_width += 1; diff --git a/rxing-reader/src/common/perspective_transform.rs b/rxing-reader/src/common/perspective_transform.rs index 709178f..3c93b06 100644 --- a/rxing-reader/src/common/perspective_transform.rs +++ b/rxing-reader/src/common/perspective_transform.rs @@ -74,7 +74,7 @@ impl PerspectiveTransform { pub fn quadrilateral_to_quadrilateral(dst: Quadrilateral, src: Quadrilateral) -> Result { if !src.is_convex() || !dst.is_convex() { return Err(Error::InvalidState { - message: "required internal state is missing".to_owned(), + message: "required internal state is missing".into(), } .into()); } @@ -99,7 +99,7 @@ impl PerspectiveTransform { pub fn transform_points_single(&self, points: &mut [Point]) -> Result<()> { for point in points.iter_mut() { *point = self.transform_point(*point).ok_or(Error::NotFound { - message: "barcode pattern was not detected".to_owned(), + message: "barcode pattern was not detected".into(), })?; } Ok(()) @@ -130,7 +130,7 @@ impl PerspectiveTransform { let denominator = d1.cross(d2); if denominator.abs() < DENOMINATOR_EPSILON { return Err(Error::InvalidState { - message: "required internal state is missing".to_owned(), + message: "required internal state is missing".into(), } .into()); } diff --git a/rxing-reader/src/common/quad.rs b/rxing-reader/src/common/quad.rs index 1887fe9..81a48cd 100644 --- a/rxing-reader/src/common/quad.rs +++ b/rxing-reader/src/common/quad.rs @@ -260,7 +260,7 @@ impl TryFrom<&Vec> for Quadrilateral { message: format!( "quadrilateral requires exactly 4 points, got {}", value.len() - ), + ).into(), } .into()) } diff --git a/rxing-reader/src/decode.rs b/rxing-reader/src/decode.rs index a14d606..e07f692 100644 --- a/rxing-reader/src/decode.rs +++ b/rxing-reader/src/decode.rs @@ -1,4 +1,4 @@ -use std::sync::Arc; +use std::borrow::Cow; use anyhow::Result as RxingResult; @@ -52,7 +52,7 @@ pub fn decode_with_optional_invert( if !results.is_empty() { return results; } - if try_invert && let Ok(matrix) = bitmap.get_black_matrix_mut() { + if try_invert && let Ok(matrix) = bitmap.black_matrix_mut() { matrix.flip_self(); return QrReader .decode_set_number_with_hints(bitmap, hints, max_number_of_symbols) @@ -63,7 +63,7 @@ pub fn decode_with_optional_invert( /// Try one resolution and close-pass combination using the selected binarizer. pub fn decode_one_layer( - source: Luma8LuminanceSource, + source: Luma8LuminanceSource<'_>, hints: &DecodeHints, use_hybrid_binarizer: bool, max_number_of_symbols: u32, @@ -99,7 +99,7 @@ pub fn decode_qr_codes_luma( let hints = DecodeHints { try_harder }; if !try_harder { - let source = Luma8LuminanceSource::new(luma.to_vec(), width, height)?; + let source = Luma8LuminanceSource::new(luma, width, height)?; return Ok(decode_one_layer( source, &hints, @@ -110,12 +110,12 @@ pub fn decode_qr_codes_luma( )); } - let mut cur_luma: Arc> = Arc::new(luma.to_vec()); + let mut cur_luma = Cow::Borrowed(luma); let mut cur_w = width; let mut cur_h = height; loop { for &close in &[false, true] { - let source = Luma8LuminanceSource::new(Arc::clone(&cur_luma), cur_w, cur_h)?; + let source = Luma8LuminanceSource::new(cur_luma.as_ref(), cur_w, cur_h)?; let results = decode_one_layer( source, &hints, @@ -134,8 +134,8 @@ pub fn decode_qr_codes_luma( return Ok(Vec::new()); } let (next_luma, next_w, next_h) = - downscale_luma_buffer(cur_luma.as_slice(), cur_w, cur_h, PYRAMID_DOWNSCALE_FACTOR)?; - cur_luma = Arc::new(next_luma); + downscale_luma_buffer(cur_luma.as_ref(), cur_w, cur_h, PYRAMID_DOWNSCALE_FACTOR)?; + cur_luma = Cow::Owned(next_luma); cur_w = next_w; cur_h = next_h; } diff --git a/rxing-reader/src/error.rs b/rxing-reader/src/error.rs index b502822..30083fc 100644 --- a/rxing-reader/src/error.rs +++ b/rxing-reader/src/error.rs @@ -1,15 +1,19 @@ +use std::borrow::Cow; + use thiserror::Error; +type ErrorMessage = Cow<'static, str>; + #[derive(Debug, Error)] pub enum Error { #[error("invalid argument: {message}")] - InvalidArgument { message: String }, + InvalidArgument { message: ErrorMessage }, #[error("invalid state: {message}")] - InvalidState { message: String }, + InvalidState { message: ErrorMessage }, #[error("not found: {message}")] - NotFound { message: String }, + NotFound { message: ErrorMessage }, #[error("invalid format: {message}")] - InvalidFormat { message: String }, + InvalidFormat { message: ErrorMessage }, #[error("checksum failed: {message}")] - Checksum { message: String }, + Checksum { message: ErrorMessage }, } diff --git a/rxing-reader/src/luma_luma_source.rs b/rxing-reader/src/luma_luma_source.rs index df72557..298bfb1 100644 --- a/rxing-reader/src/luma_luma_source.rs +++ b/rxing-reader/src/luma_luma_source.rs @@ -1,60 +1,53 @@ -use std::{borrow::Cow, sync::Arc}; +use std::borrow::Cow; use crate::{Error, LuminanceSource}; use anyhow::Result; /// A simple luma8 source for bytes. #[derive(Debug)] -pub struct Luma8LuminanceSource { +pub struct Luma8LuminanceSource<'a> { /// image dimension in form (x,y) dimensions: (u32, u32), /// raw data for luma 8 - data: Arc>, + data: Cow<'a, [u8]>, } -impl LuminanceSource for Luma8LuminanceSource { - fn get_row(&'_ self, y: usize) -> Option> { - let chunk_size = self.dimensions.0 as usize; - let row_skip = y; - let column_skip = 0; - let column_take = self.dimensions.0 as usize; - - let data_start = (chunk_size * row_skip) + column_skip; - let data_end = (chunk_size * row_skip) + column_skip + column_take; - - let row = &self.data[data_start..data_end]; - - Some(Cow::Borrowed(row)) +impl LuminanceSource for Luma8LuminanceSource<'_> { + fn row(&'_ self, y: usize) -> Option> { + let width = self.width(); + let data_start = width.checked_mul(y)?; + let data_end = data_start.checked_add(width)?; + self.data.get(data_start..data_end).map(Cow::Borrowed) } - fn get_matrix(&self) -> Cow<'_, [u8]> { - Cow::Borrowed(self.data.as_slice()) + fn matrix(&self) -> Cow<'_, [u8]> { + Cow::Borrowed(self.data.as_ref()) } - fn get_width(&self) -> usize { + fn width(&self) -> usize { self.dimensions.0 as usize } - fn get_height(&self) -> usize { + fn height(&self) -> usize { self.dimensions.1 as usize } } -impl Luma8LuminanceSource { - pub fn new(source: impl Into>>, width: u32, height: u32) -> Result { +impl<'a> Luma8LuminanceSource<'a> { + pub fn new(source: impl Into>, width: u32, height: u32) -> Result { let data = source.into(); let expected = (width as usize) .checked_mul(height as usize) .ok_or_else(|| Error::InvalidArgument { message: format!( "Luma8LuminanceSource::new: image dimensions overflow usize ({width} x {height})" - ), + ).into(), })?; if data.len() != expected { return Err(Error::InvalidArgument { message: format!( "Luma8LuminanceSource::new: luma length {} != width*height ({expected})", data.len() - ), + ).into(), } .into()); } @@ -79,7 +72,7 @@ pub fn downscale_luma_buffer( ) -> Result<(Vec, u32, u32)> { if factor == 0 { return Err(Error::InvalidArgument { - message: format!("downscale_luma_buffer: factor must be at least 1 (got {factor})"), + message: format!("downscale_luma_buffer: factor must be at least 1 (got {factor})").into(), } .into()); } @@ -88,14 +81,14 @@ pub fn downscale_luma_buffer( .ok_or_else(|| Error::InvalidArgument { message: format!( "downscale_luma_buffer: image dimensions overflow usize ({width} x {height})" - ), + ).into(), })?; if src.len() != expected { return Err(Error::InvalidArgument { message: format!( "downscale_luma_buffer: src.len() {} must equal width * height ({expected})", src.len() - ), + ).into(), } .into()); } diff --git a/rxing-reader/src/luminance_source.rs b/rxing-reader/src/luminance_source.rs index 17c79f5..5e7cdc5 100644 --- a/rxing-reader/src/luminance_source.rs +++ b/rxing-reader/src/luminance_source.rs @@ -30,18 +30,18 @@ pub trait LuminanceSource { /// - `y`: The row to fetch, which must be in `[0, height)`. /// /// Returns the luminance data for the row. - fn get_row(&self, y: usize) -> Option>; + fn row(&self, y: usize) -> Option>; /// Fetches row-major luminance data for the underlying image. /// /// Returns row-major luminance values. Implementations may return a backing /// buffer that is larger than `width * height`; callers should only read the /// image region described by the dimensions. - fn get_matrix(&self) -> Cow<'_, [u8]>; + fn matrix(&self) -> Cow<'_, [u8]>; /// Returns the width of the source image. - fn get_width(&self) -> usize; + fn width(&self) -> usize; /// Returns the height of the source image. - fn get_height(&self) -> usize; + fn height(&self) -> usize; } diff --git a/rxing-reader/src/qrcode/cpp_port/bitmatrix_parser.rs b/rxing-reader/src/qrcode/cpp_port/bitmatrix_parser.rs index 15a596c..d39d166 100644 --- a/rxing-reader/src/qrcode/cpp_port/bitmatrix_parser.rs +++ b/rxing-reader/src/qrcode/cpp_port/bitmatrix_parser.rs @@ -12,10 +12,10 @@ use crate::{ qrcode::{FormatInformation, Version, VersionRef}, }; -use super::data_mask::get_data_mask_bit; +use super::data_mask::data_mask_bit; use crate::common::cpp_essentials::append_bit; -pub fn get_bit(bit_matrix: &BitMatrix, x: u32, y: u32, mirrored: Option) -> bool { +pub fn bit(bit_matrix: &BitMatrix, x: u32, y: u32, mirrored: Option) -> bool { let mirrored = mirrored.unwrap_or(false); if mirrored { bit_matrix.get(y, x) @@ -31,29 +31,29 @@ pub fn read_version(bit_matrix: &BitMatrix) -> Result { "QR data is malformed: matrix size {}x{} is not a valid QR size", bit_matrix.width(), bit_matrix.height(), - ), + ).into(), } .into()); } - let number = Version::number(bit_matrix); + let number = Version::number_from_matrix(bit_matrix); - Version::get_version_for_number(number) + Version::for_number(number) } pub fn read_format_information(bit_matrix: &BitMatrix) -> Result { // Read top-left format info bits let mut format_info_bits1 = 0; for x in 0..6 { - append_bit(&mut format_info_bits1, get_bit(bit_matrix, x, 8, None)); + append_bit(&mut format_info_bits1, bit(bit_matrix, x, 8, None)); } // .. and skip a bit in the timing pattern ... - append_bit(&mut format_info_bits1, get_bit(bit_matrix, 7, 8, None)); - append_bit(&mut format_info_bits1, get_bit(bit_matrix, 8, 8, None)); - append_bit(&mut format_info_bits1, get_bit(bit_matrix, 8, 7, None)); + append_bit(&mut format_info_bits1, bit(bit_matrix, 7, 8, None)); + append_bit(&mut format_info_bits1, bit(bit_matrix, 8, 8, None)); + append_bit(&mut format_info_bits1, bit(bit_matrix, 8, 7, None)); // .. and skip a bit in the timing pattern ... for y in (0..=5).rev() { - append_bit(&mut format_info_bits1, get_bit(bit_matrix, 8, y, None)); + append_bit(&mut format_info_bits1, bit(bit_matrix, 8, y, None)); } // Read the top-right/bottom-left pattern including the 'Dark Module' from the bottom-left @@ -62,10 +62,10 @@ pub fn read_format_information(bit_matrix: &BitMatrix) -> Result Result> { let function_pattern: BitMatrix = version.build_function_pattern()?; - let mut result = Vec::with_capacity(version.get_total_codewords() as usize); + let mut result = Vec::with_capacity(version.total_codewords() as usize); let mut current_byte = 0; let mut reading_up = true; let mut bits_read = 0; @@ -103,8 +103,8 @@ pub fn read_qrcodewords( // Read a bit append_bit( &mut current_byte, - get_data_mask_bit(format_info.data_mask as u32, xx, y)? - != get_bit(bit_matrix, xx, y, Some(format_info.is_mirrored)), + data_mask_bit(format_info.data_mask as u32, xx, y)? + != bit(bit_matrix, xx, y, Some(format_info.is_mirrored)), ); // If we've made a whole byte, save it off bits_read += 1; @@ -118,13 +118,13 @@ pub fn read_qrcodewords( x -= 2; } - let expected_codewords = version.get_total_codewords() as usize; + let expected_codewords = version.total_codewords() as usize; let actual_codewords = result.len(); if actual_codewords != expected_codewords { return Err(Error::InvalidFormat { message: format!( "QR data is malformed: expected {expected_codewords} codewords, found {actual_codewords}" - ), + ).into(), } .into()); } diff --git a/rxing-reader/src/qrcode/cpp_port/data_mask.rs b/rxing-reader/src/qrcode/cpp_port/data_mask.rs index eb98b69..2e1d7a9 100644 --- a/rxing-reader/src/qrcode/cpp_port/data_mask.rs +++ b/rxing-reader/src/qrcode/cpp_port/data_mask.rs @@ -11,7 +11,7 @@ use anyhow::Result; /// /// Note that the diagram in section 6.8.1 is misleading since it indicates that i is column position /// and j is row position. In fact, as the text says, i is row position and j is column position. -pub fn get_data_mask_bit(mask_index: u32, x: u32, y: u32) -> Result { +pub fn data_mask_bit(mask_index: u32, x: u32, y: u32) -> Result { match mask_index { 0 => return Ok((y + x).is_multiple_of(2)), 1 => return Ok(y.is_multiple_of(2)), @@ -25,7 +25,7 @@ pub fn get_data_mask_bit(mask_index: u32, x: u32, y: u32) -> Result { } Err(Error::InvalidArgument { - message: format!("QRCode maskIndex {mask_index} out of range (expected 0..=7)"), + message: format!("QRCode maskIndex {mask_index} out of range (expected 0..=7)").into(), } .into()) } diff --git a/rxing-reader/src/qrcode/cpp_port/decoder.rs b/rxing-reader/src/qrcode/cpp_port/decoder.rs index 03c19ad..503f6f1 100644 --- a/rxing-reader/src/qrcode/cpp_port/decoder.rs +++ b/rxing-reader/src/qrcode/cpp_port/decoder.rs @@ -105,7 +105,7 @@ pub fn to_alpha_numeric_char(value: u32) -> Result { message: format!( "to_alpha_numeric_char: value {value} out of range (expected 0..{})", ALPHANUMERIC_CHARS.len() - ), + ).into(), } .into()); } @@ -198,7 +198,7 @@ pub fn parse_ecivalue(bits: &mut BitSource) -> Result { Err(Error::InvalidFormat { message: format!( "parse_ecivalue: invalid leading byte 0x{first_byte:02X} (top bits do not match any ECI length encoding)" - ), + ).into(), } .into()) } @@ -213,7 +213,7 @@ pub fn parse_ecivalue(bits: &mut BitSource) -> Result { /// - `bits`: the stream of bits that might have a terminator code /// - `version`: the QR code version pub fn is_end_of_stream(bits: &mut BitSource, version: &Version) -> Result { - let bits_required = Mode::get_terminator_bit_length(version); //super::qr_codec_mode::TerminatorBitsLength(version); + let bits_required = Mode::terminator_bit_length(version); let bits_available = std::cmp::min(bits.available(), bits_required as usize); Ok(bits_available == 0 || bits.peek_bits(bits_available)? == 0) } @@ -232,7 +232,7 @@ pub fn decode_bit_stream(bytes: &[u8], version: &Version) -> Result = (|| { while !is_end_of_stream(&mut bits, version)? { @@ -245,7 +245,7 @@ pub fn decode_bit_stream(bytes: &[u8], version: &Version) -> Result { if !result.is_empty() { - return Err(Error::InvalidFormat { message: "AIM Application Indicator (FNC1 in second position) at illegal position".to_owned() }.into()); + return Err(Error::InvalidFormat { message: "AIM Application Indicator (FNC1 in second position) at illegal position".into() }.into()); } result.symbology.modifier = b'5'; // ISO/IEC 18004:2015 7.4.8.3 AIM Application Indicator (FNC1 in second position), "00-99" or "A-Za-z" @@ -261,7 +261,7 @@ pub fn decode_bit_stream(bytes: &[u8], version: &Version) -> Result Result Result Result Result { if !Version::has_valid_size(bits) { return Err(Error::InvalidFormat { - message: format!("Invalid QR symbol size: {}x{}", bits.width(), bits.height()), + message: format!("Invalid QR symbol size: {}x{}", bits.width(), bits.height()).into(), } .into()); } @@ -333,7 +333,7 @@ pub fn decode(bits: &BitMatrix) -> Result { "Invalid format information in {}x{} symbol: {e}", bits.width(), bits.height() - ), + ).into(), })?; let version = read_version(bits).map_err(|e| Error::InvalidFormat { @@ -341,7 +341,7 @@ pub fn decode(bits: &BitMatrix) -> Result { "Invalid version in {}x{} QR symbol: {e}", bits.width(), bits.height() - ), + ).into(), })?; // Read codewords @@ -350,40 +350,40 @@ pub fn decode(bits: &BitMatrix) -> Result { return Err(Error::InvalidFormat { message: format!( "Failed to read codewords for version {} ({}x{} symbol)", - version.get_version_number(), + version.number(), bits.width(), bits.height() - ), + ).into(), } .into()); } // Separate into data blocks let data_blocks: Vec = - DataBlock::get_data_blocks(&codewords, version, format_info.error_correction_level)?; + DataBlock::data_blocks(&codewords, version, format_info.error_correction_level)?; if data_blocks.is_empty() { return Err(Error::InvalidFormat { message: format!( "Failed to get data blocks for version {} (codewords={}, ec_level={:?})", - version.get_version_number(), + version.number(), codewords.len(), format_info.error_correction_level - ), + ).into(), } .into()); } // Count total number of data bytes let op = - |total_bytes, data_block: &DataBlock| total_bytes + data_block.get_num_data_codewords(); + |total_bytes, data_block: &DataBlock| total_bytes + data_block.num_data_codewords(); let total_bytes = data_blocks.iter().fold(0, op); let mut result_bytes = vec![0u8; total_bytes as usize]; let mut result_iterator = 0; // Error-correct and copy data blocks together into a stream of bytes - for data_block in data_blocks.iter() { - let mut codeword_bytes = data_block.get_codewords().to_vec(); - let num_data_codewords = data_block.get_num_data_codewords() as usize; + for data_block in data_blocks { + let num_data_codewords = data_block.num_data_codewords() as usize; + let mut codeword_bytes = data_block.codewords; correct_errors(&mut codeword_bytes, num_data_codewords)?; @@ -409,31 +409,31 @@ impl DataBlock { } } - fn get_data_blocks( + fn data_blocks( raw_codewords: &[u8], version: VersionRef, ec_level: ErrorCorrectionLevel, ) -> Result> { - if raw_codewords.len() as u32 != version.get_total_codewords() { + if raw_codewords.len() as u32 != version.total_codewords() { return Err(Error::InvalidArgument { message: format!( "raw codewords length {} does not match expected total codewords {}", raw_codewords.len(), - version.get_total_codewords() - ), + version.total_codewords() + ).into(), } .into()); } - let ec_blocks = version.get_ecblocks_for_level(ec_level)?; + let ec_blocks = version.ec_blocks_for_level(ec_level)?; let mut result = Vec::new(); let mut num_result_blocks = 0; - for ec_block in ec_blocks.get_ecblocks() { - for _ in 0..ec_block.get_count() { - let num_data_codewords = ec_block.get_data_codewords(); + for ec_block in ec_blocks.blocks() { + for _ in 0..ec_block.count() { + let num_data_codewords = ec_block.data_codewords(); let num_block_codewords = - ec_blocks.get_eccodewords_per_block() + num_data_codewords; + ec_blocks.ec_codewords_per_block() + num_data_codewords; result.push(DataBlock::new( num_data_codewords, vec![0u8; num_block_codewords as usize], @@ -445,7 +445,7 @@ impl DataBlock { if result.is_empty() { return Err(Error::InvalidArgument { message: "result block list is empty; possible data corruption or misconfiguration" - .to_owned(), + .into(), } .into()); } @@ -465,7 +465,7 @@ impl DataBlock { } let shorter_blocks_num_data_codewords = - shorter_blocks_total_codewords - ec_blocks.get_eccodewords_per_block() as usize; + shorter_blocks_total_codewords - ec_blocks.ec_codewords_per_block() as usize; let mut raw_codewords_offset = 0; for i in 0..shorter_blocks_num_data_codewords { for result_j in result.iter_mut().take(num_result_blocks) { @@ -492,18 +492,15 @@ impl DataBlock { Ok(result) } - fn get_num_data_codewords(&self) -> u32 { + fn num_data_codewords(&self) -> u32 { self.num_data_codewords } - fn get_codewords(&self) -> &[u8] { - &self.codewords - } } fn correct_errors(codeword_bytes: &mut [u8], num_data_codewords: usize) -> Result<()> { correct_qr_errors(codeword_bytes, num_data_codewords).map_err(|e| Error::Checksum { - message: format!("{e:?}"), + message: format!("{e:?}").into(), })?; Ok(()) } diff --git a/rxing-reader/src/qrcode/cpp_port/detector.rs b/rxing-reader/src/qrcode/cpp_port/detector.rs index cb77e45..2b9b3a7 100644 --- a/rxing-reader/src/qrcode/cpp_port/detector.rs +++ b/rxing-reader/src/qrcode/cpp_port/detector.rs @@ -170,11 +170,11 @@ pub fn generate_finder_pattern_sets(patterns: &mut FinderPatterns) -> FinderPatt let mut dist_ac2 = squared_distance(*a, *c); if dist_bc2 >= dist_ab2 && dist_bc2 >= dist_ac2 { - std::mem::swap(&mut a, &mut b); - std::mem::swap(&mut dist_bc2, &mut dist_ac2); + (a, b) = (b, a); + (dist_bc2, dist_ac2) = (dist_ac2, dist_bc2); } else if dist_ab2 >= dist_ac2 && dist_ab2 >= dist_bc2 { - std::mem::swap(&mut b, &mut c); - std::mem::swap(&mut dist_ab2, &mut dist_ac2); + (b, c) = (c, b); + (dist_ab2, dist_ac2) = (dist_ac2, dist_ab2); } let dist_ab = dist_ab2.sqrt(); @@ -250,17 +250,17 @@ pub fn estimate_module_size( let mut cur = EdgeTracer::new(image, a.p, b.p - a.p); if !cur.is_black() { return Err(Error::NotFound { - message: "barcode pattern was not detected".to_owned(), + message: "barcode pattern was not detected".into(), } .into()); } let pattern = read_symmetric_pattern::<5, _>(&mut cur, a.size * 2).ok_or(Error::NotFound { - message: "barcode pattern was not detected".to_owned(), + message: "barcode pattern was not detected".into(), })?; if !(is_pattern::( - &PatternView::new(&PatternRow::new(pattern.to_vec())), + &PatternView::from_bars(&pattern), &PATTERN, None, 0.0, @@ -268,7 +268,7 @@ pub fn estimate_module_size( ) != 0.0) { return Err(Error::NotFound { - message: "barcode pattern was not detected".to_owned(), + message: "barcode pattern was not detected".into(), } .into()); } @@ -326,7 +326,7 @@ pub fn trace_line( line.set_direction_inward(cur.back()); // collect points inside the black line -> backup on 3rd edge - cur.step_to_edge(Some(edge), Some(0), Some(edge == 3)); + cur.step_to_edge(edge, 0, edge == 3); if edge == 3 { cur.turn_back(); } @@ -340,7 +340,7 @@ pub fn trace_line( } else if cur_i.edge_at_right().into() { cur_i.turn_left(); } else { - cur_i.step(Some(-1.0)); + cur_i.step_by(-1.0); } } @@ -351,13 +351,13 @@ pub fn trace_line( line.add(Point::centered(c.p))?; step_count -= 1; - if !(step_count > 0 && c.step_along_edge(dir, Some(true))) { + if !(step_count > 0 && c.step_along_edge_with_corner_skip(dir, true)) { break; } } } - line.evaluate_max_distance(Some(1.0), Some(true)); + line.evaluate_max_distance_with(1.0, true); Ok(line) } @@ -416,7 +416,7 @@ pub fn locate_alignment_pattern( }; // if we did not land on a black pixel the concentric pattern finder will fail - if !image.get_point(cor) { + if !image.at_point(cor) { continue; } @@ -452,7 +452,7 @@ pub fn read_version( if !image.is_in(pix) { version_bits = -1; } else { - append_bit(&mut version_bits, image.get_point(pix)); + append_bit(&mut version_bits, image.at_point(pix)); } } bits[usize::from(mirror)] = version_bits; @@ -472,7 +472,7 @@ pub fn sample_qr(image: &BitMatrix, fp: &FinderPatternSet) -> Result Result 21 @@ -551,14 +551,14 @@ pub fn sample_qr(image: &BitMatrix, fp: &FinderPatternSet) -> Result= Version::symbol_size(7).x { let version = read_version(image, dimension as u32, mod_to_pix).map_err(|_| Error::NotFound { - message: "barcode pattern was not detected".to_owned(), + message: "barcode pattern was not detected".into(), })?; - let version_dimension = version.get_dimension_for_version() as i32; + let version_dimension = version.dimension() as i32; // if the version bits are garbage -> discard the detection if (version_dimension - dimension).abs() > 8 { return Err(Error::NotFound { - message: "barcode pattern was not detected".to_owned(), + message: "barcode pattern was not detected".into(), } .into()); } @@ -570,7 +570,7 @@ pub fn sample_qr(image: &BitMatrix, fp: &FinderPatternSet) -> Result Result Result Result anyhow::Result>> { - let bin_img = image.get_black_matrix()?; + let bin_img = image.black_matrix()?; let max_symbols = count; let try_harder = hints.try_harder; diff --git a/rxing-reader/src/qrcode/error_correction_level.rs b/rxing-reader/src/qrcode/error_correction_level.rs index e86b8d8..4e34eb9 100644 --- a/rxing-reader/src/qrcode/error_correction_level.rs +++ b/rxing-reader/src/qrcode/error_correction_level.rs @@ -46,13 +46,13 @@ impl ErrorCorrectionLevel { 2 => Ok(Self::H), 3 => Ok(Self::Q), _ => Err(Error::InvalidArgument { - message: format!("{bits} is not a valid bit selection"), + message: format!("{bits} is not a valid bit selection").into(), } .into()), } } - pub const fn get_value(&self) -> u8 { + pub const fn value(&self) -> u8 { match self { ErrorCorrectionLevel::L => 0x01, ErrorCorrectionLevel::M => 0x00, @@ -62,7 +62,7 @@ impl ErrorCorrectionLevel { } } - pub const fn get_ordinal(&self) -> u8 { + pub const fn ordinal(&self) -> u8 { match self { ErrorCorrectionLevel::L => 0, ErrorCorrectionLevel::M => 1, @@ -83,7 +83,7 @@ impl TryFrom for ErrorCorrectionLevel { impl From for u8 { fn from(value: ErrorCorrectionLevel) -> Self { - value.get_value() + value.value() } } @@ -111,7 +111,7 @@ impl FromStr for ErrorCorrectionLevel { } Err(Error::InvalidArgument { - message: format!("could not parse {s} into an ec level"), + message: format!("could not parse {s} into an ec level").into(), } .into()) } @@ -135,14 +135,14 @@ mod tests { #[test] fn invalid_value_uses_distinct_sentinel() { - assert_eq!(ErrorCorrectionLevel::Invalid.get_value(), 0xFF); + assert_eq!(ErrorCorrectionLevel::Invalid.value(), 0xFF); assert_ne!( - ErrorCorrectionLevel::Invalid.get_value(), - ErrorCorrectionLevel::M.get_value() + ErrorCorrectionLevel::Invalid.value(), + ErrorCorrectionLevel::M.value() ); assert_eq!( u8::from(ErrorCorrectionLevel::Invalid), - ErrorCorrectionLevel::Invalid.get_value() + ErrorCorrectionLevel::Invalid.value() ); } } diff --git a/rxing-reader/src/qrcode/mode.rs b/rxing-reader/src/qrcode/mode.rs index bd2b6cd..54fbe13 100644 --- a/rxing-reader/src/qrcode/mode.rs +++ b/rxing-reader/src/qrcode/mode.rs @@ -59,7 +59,7 @@ impl Mode { Ok(Self::Hanzi) } _ => Err(Error::InvalidArgument { - message: format!("{bits} is not valid"), + message: format!("{bits} is not valid").into(), } .into()), } @@ -69,8 +69,8 @@ impl Mode { /// /// The returned value is the number of bits used to encode the count of /// characters that follow this mode indicator. - pub fn get_character_count_bits(&self, version: &Version) -> u8 { - let number = version.get_version_number(); + pub fn character_count_bits_u8(&self, version: &Version) -> u8 { + let number = version.number(); let offset = if number <= 9 { 0 @@ -79,10 +79,10 @@ impl Mode { } else { 2 }; - self.get_character_counts()[offset] + self.character_counts()[offset] } - fn get_character_counts(&self) -> &[u8] { + fn character_counts(&self) -> &[u8] { match self { Mode::Terminator => &[0, 0, 0], Mode::Numeric => &[10, 12, 14], @@ -97,7 +97,7 @@ impl Mode { } } - pub fn get_bits(&self) -> u8 { + pub fn bits(&self) -> u8 { match self { Mode::Terminator => 0x00, Mode::Numeric => 0x01, @@ -112,12 +112,12 @@ impl Mode { } } - pub fn get_terminator_bit_length(version: &Version) -> u8 { + pub fn terminator_bit_length(version: &Version) -> u8 { let _ = version; 4 } - pub fn get_codec_mode_bits_length(version: &Version) -> u8 { + pub fn codec_mode_bits_length(version: &Version) -> u8 { let _ = version; 4 } @@ -132,7 +132,7 @@ impl Mode { } Err(Error::InvalidFormat { - message: format!("Invalid QR codec mode bits 0x{bits:X}"), + message: format!("Invalid QR codec mode bits 0x{bits:X}").into(), } .into()) } @@ -142,13 +142,13 @@ impl Mode { /// The returned value is the number of bits used to encode the count of /// characters that follow this mode indicator. pub fn character_count_bits(&self, version: &Version) -> u32 { - self.get_character_count_bits(version) as u32 + self.character_count_bits_u8(version) as u32 } } impl From for u8 { fn from(value: Mode) -> Self { - value.get_bits() + value.bits() } } @@ -166,7 +166,7 @@ impl TryFrom for Mode { fn try_from(value: u32) -> Result { if value > u32::from(u8::MAX) { return Err(Error::InvalidArgument { - message: format!("{value} is not valid"), + message: format!("{value} is not valid").into(), } .into()); } diff --git a/rxing-reader/src/qrcode/version.rs b/rxing-reader/src/qrcode/version.rs index 8fc36a2..cb87747 100755 --- a/rxing-reader/src/qrcode/version.rs +++ b/rxing-reader/src/qrcode/version.rs @@ -51,10 +51,10 @@ impl Version { ec_blocks: [ECBlocks; 4], ) -> Self { let mut total = 0; - let ec_codewords = ec_blocks[1].get_eccodewords_per_block(); - let ecb_array = ec_blocks[1].get_ecblocks(); + let ec_codewords = ec_blocks[1].ec_codewords_per_block(); + let ecb_array = ec_blocks[1].blocks(); for ecb in ecb_array { - total += ecb.get_count() * (ecb.get_data_codewords() + ec_codewords); + total += ecb.count() * (ecb.data_codewords() + ec_codewords); } Self { @@ -65,32 +65,32 @@ impl Version { } } - pub const fn get_version_number(&self) -> u32 { + pub const fn number(&self) -> u32 { self.version_number } - pub const fn get_alignment_pattern_centers(&self) -> &[u32] { + pub const fn alignment_pattern_centers(&self) -> &[u32] { &self.alignment_pattern_centers } - pub const fn get_total_codewords(&self) -> u32 { + pub const fn total_codewords(&self) -> u32 { self.total_codewords } - pub fn get_dimension_for_version(&self) -> u32 { + pub fn dimension(&self) -> u32 { Self::dimension_of_version(self.version_number) } - pub fn get_ecblocks_for_level(&self, ec_level: ErrorCorrectionLevel) -> Result<&ECBlocks> { + pub fn ec_blocks_for_level(&self, ec_level: ErrorCorrectionLevel) -> Result<&ECBlocks> { self.ec_blocks - .get(ec_level.get_ordinal() as usize) + .get(ec_level.ordinal() as usize) .ok_or_else(|| { Error::InvalidArgument { message: format!( "ErrorCorrectionLevel ordinal {} out of range for {} EC blocks", - ec_level.get_ordinal(), + ec_level.ordinal(), self.ec_blocks.len() - ), + ).into(), } .into() }) @@ -103,22 +103,23 @@ impl Version { /// Returns Version for a QR Code of that dimension. /// Version 1 has dimension 21. Returns an invalid-format error if /// dimension is less than 21 or (dimension - 1) % 4 != 0. - pub fn get_provisional_version_for_dimension(dimension: u32) -> Result { + pub fn provisional_for_dimension(dimension: u32) -> Result { if dimension % 4 != 1 || dimension < 21 { return Err(Error::InvalidFormat { message: format!( "QR dimension {dimension} is invalid (expected >= 21 and (dimension - 1) % 4 == 0)" - ), + ) + .into(), } .into()); } - Self::get_version_for_number((dimension - 17) / 4) + Self::for_number((dimension - 17) / 4) } - pub fn get_version_for_number(version_number: u32) -> Result { + pub fn for_number(version_number: u32) -> Result { if !(1..=40).contains(&version_number) { return Err(Error::InvalidArgument { - message: format!("QR version {version_number} is out of spec (expected 1..=40)"), + message: format!("QR version {version_number} is out of spec (expected 1..=40)").into(), } .into()); } @@ -127,7 +128,7 @@ impl Version { /// See ISO 18004:2006 Annex E pub fn build_function_pattern(&self) -> Result { - let dimension = self.get_dimension_for_version(); + let dimension = self.dimension(); let mut bit_matrix = BitMatrix::with_single_dimension(dimension)?; // Top left finder pattern + separator + format @@ -190,23 +191,23 @@ impl ECBlocks { } } - pub const fn get_eccodewords_per_block(&self) -> u32 { + pub const fn ec_codewords_per_block(&self) -> u32 { self.ec_codewords_per_block } - pub fn get_num_blocks(&self) -> u32 { + pub fn num_blocks(&self) -> u32 { let mut total = 0; for ec_block in self.ec_blocks.iter() { - total += ec_block.get_count(); + total += ec_block.count(); } total } - pub fn get_total_eccodewords(&self) -> u32 { - self.ec_codewords_per_block * self.get_num_blocks() + pub fn total_ec_codewords(&self) -> u32 { + self.ec_codewords_per_block * self.num_blocks() } - pub fn get_ecblocks(&self) -> &[Ecb] { + pub fn blocks(&self) -> &[Ecb] { &self.ec_blocks } } @@ -228,11 +229,11 @@ impl Ecb { } } - pub const fn get_count(&self) -> u32 { + pub const fn count(&self) -> u32 { self.count } - pub const fn get_data_codewords(&self) -> u32 { + pub const fn data_codewords(&self) -> u32 { self.data_codewords } } diff --git a/rxing-reader/tests/fixtures/qr_base45_alphanumeric.png b/rxing-reader/tests/fixtures/qr_base45_alphanumeric.png new file mode 100644 index 0000000..0a9301e Binary files /dev/null and b/rxing-reader/tests/fixtures/qr_base45_alphanumeric.png differ diff --git a/rxing-reader/tests/fixtures/qr_numeric.png b/rxing-reader/tests/fixtures/qr_numeric.png new file mode 100644 index 0000000..123af39 Binary files /dev/null and b/rxing-reader/tests/fixtures/qr_numeric.png differ diff --git a/rxing-reader/tests/qr_decode.rs b/rxing-reader/tests/qr_decode.rs index 702c7b7..48726e7 100644 --- a/rxing-reader/tests/qr_decode.rs +++ b/rxing-reader/tests/qr_decode.rs @@ -690,3 +690,145 @@ fn decodes_three_qr_codes_in_single_image() { assert_eq!(sample_hits, 2, "expected qr_sample payload twice"); assert_eq!(complex_hits, 1, "expected qr_code_complex payload once"); } + +// --------------------------------------------------------------------------- +// QR encoding-mode coverage. The fixtures above all happen to be encoded in +// QR Byte mode (their payloads contain at least one character outside the +// numeric / alphanumeric subsets), so they only exercise +// `decode_byte_segment`. The two fixtures below pin the other two ISO-18004 +// data modes that the `qrcode` module actually decodes: +// +// * `qr_numeric.png` — pure-digit payload that the encoder packs into +// Numeric mode (3 digits per 10 bits). Exercises `decode_numeric_segment` +// including the trailing-group unpack (the 36-digit payload is exactly +// 12 three-digit groups, so a regression that mis-handles the 2- or +// 1-digit terminal group would not surface here — that's intentional; +// it isolates the steady-state 10-bit loop). +// +// * `qr_base45_alphanumeric.png` — base45-encoded binary payload that, by +// construction, uses only the 45-character QR alphanumeric charset +// (`0-9 A-Z` plus ` $%*+-./:`), so the encoder picks Alphanumeric mode +// (2 chars per 11 bits). The test base45-decodes the decoded string and +// asserts byte-for-byte equality with the original bytes, so it pins +// both `decode_alphanumeric_segment` and the round-trip contract used by +// real-world consumers (EU Digital COVID Certificate, etc.). +// +// Both fixtures are produced one-off by `/tmp/qrgen` (a small Rust +// generator using the `qrcode` crate's low-level `Bits` API to force the +// encoding mode) and committed to the repo so tests do not depend on the +// generator at run time. +// --------------------------------------------------------------------------- + +#[test] +fn decodes_numeric_mode_qr_in_every_combination() { + let (rgba, w, h) = load_image_as_rgba("tests/fixtures/qr_numeric.png"); + let expected = b"012345678901234567890123456789012345"; + for combo in ALL_COMBOS { + let bytes = decode_combo(&rgba, w, h, combo) + .unwrap_or_else(|| panic!("qr_numeric.png failed to decode for combo={:?}", combo)); + assert_eq!( + bytes.as_slice(), + expected.as_slice(), + "unexpected bytes for combo={:?}", + combo + ); + } +} + +const BASE45_ALPHABET: &[u8] = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:"; + +/// Minimal RFC 9285 base45 decoder. Returns `None` on any out-of-charset +/// character, malformed trailing group, or 16-bit overflow. +fn base45_decode(input: &[u8]) -> Option> { + fn idx(c: u8) -> Option { + BASE45_ALPHABET.iter().position(|&b| b == c).map(|i| i as u16) + } + let mut out = Vec::new(); + let mut i = 0; + while i + 3 <= input.len() { + // Per RFC 9285 the value packed into a 3-char group is bounded by + // u16::MAX (0xFFFF), so a `u16` overflow on the additions would + // already signal a malformed input. Use a `u32` for the intermediate + // so we can detect that overflow instead of wrapping silently. + let n = idx(input[i])? as u32 + + idx(input[i + 1])? as u32 * 45 + + idx(input[i + 2])? as u32 * 45 * 45; + if n > 0xFFFF { + return None; + } + out.push((n / 256) as u8); + out.push((n % 256) as u8); + i += 3; + } + match input.len() - i { + 0 => Some(out), + 2 => { + // Same u16-overflow concern as the 3-char branch above; a single + // byte's value is bounded by 0xFF so a 2-char group whose decoded + // value exceeds that is malformed. + let n = idx(input[i])? as u32 + idx(input[i + 1])? as u32 * 45; + if n > 0xFF { + return None; + } + out.push(n as u8); + Some(out) + } + _ => None, + } +} + +#[test] +fn decodes_base45_alphanumeric_mode_qr_and_round_trips() { + let (rgba, w, h) = load_image_as_rgba("tests/fixtures/qr_base45_alphanumeric.png"); + // Raw bytes the generator base45-encoded into the QR alphanumeric payload. + // The 0x00 / 0xff / 0xde 0xad 0xbe 0xef bytes guarantee the payload cannot + // round-trip as plain ASCII — the base45 layer is load-bearing. + let expected_raw: &[u8] = + b"\x00\xff\x01\xfe rxing-reader base45 round-trip \xde\xad\xbe\xef"; + for combo in ALL_COMBOS { + let bytes = decode_combo(&rgba, w, h, combo).unwrap_or_else(|| { + panic!( + "qr_base45_alphanumeric.png failed to decode for combo={:?}", + combo + ) + }); + // Every byte returned by the decoder must be in the base45 charset — + // if any wasn't, the encoder would have spilled into Byte mode and + // this fixture would no longer cover `decode_alphanumeric_segment`. + for &b in &bytes { + assert!( + BASE45_ALPHABET.contains(&b), + "decoded byte {:#x} outside base45 charset for combo={:?} \ + (fixture is no longer pure-alphanumeric)", + b, + combo + ); + } + let decoded = base45_decode(&bytes).unwrap_or_else(|| { + panic!( + "base45 decode failed for combo={:?}; got {:?}", + combo, + String::from_utf8_lossy(&bytes) + ) + }); + assert_eq!( + decoded.as_slice(), + expected_raw, + "base45 round-trip mismatch for combo={:?}", + combo + ); + } +} + +#[test] +fn base45_decode_round_trips_known_vectors() { + // Sanity test on the in-test base45 decoder so a bug here doesn't masquerade + // as a QR decode regression. Vectors from RFC 9285 §4.4 ("AB" → "BB8", + // "Hello!!" → "%69 VD92EX0") plus the empty string. + assert_eq!(base45_decode(b"").unwrap(), b""); + assert_eq!(base45_decode(b"BB8").unwrap(), b"AB"); + assert_eq!(base45_decode(b"%69 VD92EX0").unwrap(), b"Hello!!"); + // Out-of-charset and malformed inputs are rejected. + assert!(base45_decode(b"!!!").is_none(), "rejects non-charset"); + assert!(base45_decode(b"B").is_none(), "rejects 1-char trailing group"); +}