From b1e1ea71d9f1a079ef69de8a019ba1a0cc896688 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Sun, 16 Oct 2022 17:50:42 -0500 Subject: [PATCH] break out many mods --- src/common/bit_array.rs | 405 ++ src/common/bit_matrix.rs | 628 +++ src/common/bit_source.rs | 125 + src/common/bit_source_builder.rs | 68 + src/common/character_set_eci.rs | 289 ++ src/common/decoder_rxing_result.rs | 209 + src/common/default_grid_sampler.rs | 121 + src/common/detector/mod.rs | 697 +-- .../detector/monochrome_rectangle_detector.rs | 312 ++ .../detector/white_rectangle_detector.rs | 387 ++ src/common/eci_encoder_set.rs | 254 + src/common/eci_input.rs | 109 + src/common/eci_string_builder.rs | 173 + src/common/global_histogram_binarizer.rs | 247 + src/common/grid_sampler.rs | 200 + src/common/hybrid_binarizer.rs | 320 ++ src/common/input_edge.rs | 115 + src/common/minimal_eci_input.rs | 384 ++ src/common/mod.rs | 4081 +---------------- src/common/perspective_transform.rs | 213 + src/common/reedsolomon/generic_gf.rs | 178 + src/common/reedsolomon/generic_gf_poly.rs | 340 ++ src/common/reedsolomon/mod.rs | 925 +--- src/common/reedsolomon/reedsolomon_decoder.rs | 311 ++ src/common/reedsolomon/reedsolomon_encoder.rs | 109 + src/common/string_utils.rs | 275 ++ 26 files changed, 5818 insertions(+), 5657 deletions(-) create mode 100644 src/common/bit_array.rs create mode 100644 src/common/bit_matrix.rs create mode 100644 src/common/bit_source.rs create mode 100644 src/common/bit_source_builder.rs create mode 100644 src/common/character_set_eci.rs create mode 100644 src/common/decoder_rxing_result.rs create mode 100644 src/common/default_grid_sampler.rs create mode 100644 src/common/detector/monochrome_rectangle_detector.rs create mode 100644 src/common/detector/white_rectangle_detector.rs create mode 100644 src/common/eci_encoder_set.rs create mode 100644 src/common/eci_input.rs create mode 100644 src/common/eci_string_builder.rs create mode 100644 src/common/global_histogram_binarizer.rs create mode 100644 src/common/grid_sampler.rs create mode 100644 src/common/hybrid_binarizer.rs create mode 100644 src/common/input_edge.rs create mode 100644 src/common/minimal_eci_input.rs create mode 100644 src/common/perspective_transform.rs create mode 100644 src/common/reedsolomon/generic_gf.rs create mode 100644 src/common/reedsolomon/generic_gf_poly.rs create mode 100644 src/common/reedsolomon/reedsolomon_decoder.rs create mode 100644 src/common/reedsolomon/reedsolomon_encoder.rs create mode 100644 src/common/string_utils.rs diff --git a/src/common/bit_array.rs b/src/common/bit_array.rs new file mode 100644 index 0000000..42ac843 --- /dev/null +++ b/src/common/bit_array.rs @@ -0,0 +1,405 @@ + +/* + * Copyright 2007 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// package com.google.zxing.common; + +// import java.util.Arrays; + +use std::{fmt, cmp}; + +use crate::Exceptions; + +static EMPTY_BITS: [u32; 0] = [0; 0]; +static LOAD_FACTOR: f32 = 0.75f32; + +/** + *

A simple, fast array of bits, represented compactly by an array of ints internally.

+ * + * @author Sean Owen + */ +#[derive(Debug, PartialEq, Eq, Clone, Hash)] +pub struct BitArray { + bits: Vec, + size: usize, +} + +impl BitArray { + pub fn new() -> Self { + Self { + bits: EMPTY_BITS.to_vec(), + size: 0, + } + } + + pub fn with_size(size: usize) -> Self { + Self { + bits: BitArray::makeArray(size), + size: size, + } + } + + // For testing only + pub fn with_initial_values(bits: Vec, size: usize) -> Self { + Self { + bits: bits, + size: size, + } + } + + pub fn getSize(&self) -> usize { + self.size + } + + pub fn getSizeInBytes(&self) -> usize { + return (self.size + 7) / 8; + } + + fn ensure_capacity(&mut self, newSize: usize) { + if newSize > self.bits.len() * 32 { + let mut newBits = BitArray::makeArray((newSize as f32 / LOAD_FACTOR).ceil() as usize); + //System.arraycopy(bits, 0, newBits, 0, bits.length); + newBits[0..self.bits.len()].clone_from_slice(&self.bits[0..self.bits.len()]); + self.bits = newBits; + } + } + + /** + * @param i bit to get + * @return true iff bit i is set + */ + pub fn get(&self, i: usize) -> bool { + return (self.bits[i / 32] & (1 << (i & 0x1F))) != 0; + } + + /** + * Sets bit i. + * + * @param i bit to set + */ + pub fn set(&mut self, i: usize) { + self.bits[i / 32] |= 1 << (i & 0x1F); + } + + /** + * Flips bit i. + * + * @param i bit to set + */ + pub fn flip(&mut self, i: usize) { + self.bits[i / 32] ^= 1 << (i & 0x1F); + } + + /** + * @param from first bit to check + * @return index of first bit that is set, starting from the given index, or size if none are set + * at or beyond this given index + * @see #getNextUnset(int) + */ + pub fn getNextSet(&self, from: usize) -> usize { + if from >= self.size { + return self.size; + } + let mut bitsOffset = from / 32; + let mut currentBits = self.bits[bitsOffset] as i64; + // mask off lesser bits first + currentBits &= -(1 << (from & 0x1F)); + while currentBits == 0 { + bitsOffset += 1; + if bitsOffset == self.bits.len() { + return self.size; + } + currentBits = self.bits[bitsOffset] as i64; + } + let result = (bitsOffset * 32) + currentBits.trailing_zeros() as usize; + cmp::min(result, self.size) + } + + /** + * @param from index to start looking for unset bit + * @return index of next unset bit, or {@code size} if none are unset until the end + * @see #getNextSet(int) + */ + pub fn getNextUnset(&self, from: usize) -> usize { + if from >= self.size { + return self.size; + } + let mut bitsOffset = from / 32; + let mut currentBits = !self.bits[bitsOffset] as i32; + // mask off lesser bits first + currentBits &= -(1 << (from & 0x1F)); + while currentBits == 0 { + bitsOffset += 1; + if bitsOffset == self.bits.len() { + return self.size; + } + currentBits = !self.bits[bitsOffset] as i32; + } + let result = (bitsOffset * 32) + currentBits.trailing_zeros() as usize; + return cmp::min(result, self.size); + } + + /** + * Sets a block of 32 bits, starting at bit i. + * + * @param i first bit to set + * @param newBits the new value of the next 32 bits. Note again that the least-significant bit + * corresponds to bit i, the next-least-significant to i+1, and so on. + */ + pub fn setBulk(&mut self, i: usize, newBits: u32) { + self.bits[i / 32] = newBits; + } + + /** + * Sets a range of bits. + * + * @param start start of range, inclusive. + * @param end end of range, exclusive + */ + pub fn setRange(&mut self, start: usize, end: usize) -> Result<(), Exceptions> { + let mut end = end; + if end < start || end > self.size { + return Err(Exceptions::IllegalArgumentException( + "end < start || start < 0 || end > self.size".to_owned(), + )); + } + if end == start { + return Ok(()); + } + end -= 1; // will be easier to treat this as the last actually set bit -- inclusive + let firstInt = start / 32; + let lastInt = end / 32; + for i in firstInt..=lastInt { + //for (int i = firstInt; i <= lastInt; i++) { + let firstBit = if i > firstInt { 0 } else { start & 0x1F }; + let lastBit = if i < lastInt { 31 } else { end & 0x1F }; + // Ones from firstBit to lastBit, inclusive + let mask: u64 = (2 << lastBit) - (1 << firstBit); + self.bits[i] |= mask as u32; + } + Ok(()) + } + + /** + * Clears all bits (sets to false). + */ + pub fn clear(&mut self) { + let max = self.bits.len(); + for i in 0..max { + //for (int i = 0; i < max; i++) { + self.bits[i] = 0; + } + } + + /** + * Efficient method to check if a range of bits is set, or not set. + * + * @param start start of range, inclusive. + * @param end end of range, exclusive + * @param value if true, checks that bits in range are set, otherwise checks that they are not set + * @return true iff all bits are set or not set in range, according to value argument + * @throws IllegalArgumentException if end is less than start or the range is not contained in the array + */ + pub fn isRange(&self, start: usize, end: usize, value: bool) -> Result { + let mut end = end; + if end < start || end > self.size { + return Err(Exceptions::IllegalArgumentException( + "end < start || start < 0 || end > self.size".to_owned(), + )); + } + if end == start { + return Ok(true); // empty range matches + } + end -= 1; // will be easier to treat this as the last actually set bit -- inclusive + let firstInt = start / 32; + let lastInt = end / 32; + for i in firstInt..=lastInt { + //for (int i = firstInt; i <= lastInt; i++) { + let firstBit = if i > firstInt { 0 } else { start & 0x1F }; + let lastBit = if i < lastInt { 31 } else { end & 0x1F }; + // Ones from firstBit to lastBit, inclusive + let mask: u64 = (2 << lastBit) - (1 << firstBit); + + // Return false if we're looking for 1s and the masked bits[i] isn't all 1s (that is, + // equals the mask, or we're looking for 0s and the masked portion is not all 0s + if (self.bits[i] & mask as u32) != (if value { mask as u32 } else { 0 }) { + return Ok(false); + } + } + return Ok(true); + } + + pub fn appendBit(&mut self, bit: bool) { + self.ensure_capacity(self.size + 1); + if bit { + self.bits[self.size / 32] |= 1 << (self.size & 0x1F); + } + self.size += 1; + } + + /** + * Appends the least-significant bits, from value, in order from most-significant to + * least-significant. For example, appending 6 bits from 0x000001E will append the bits + * 0, 1, 1, 1, 1, 0 in that order. + * + * @param value {@code int} containing bits to append + * @param numBits bits from value to append + */ + pub fn appendBits(&mut self, value: u32, num_bits: usize) -> Result<(), Exceptions> { + if num_bits > 32 { + return Err(Exceptions::IllegalArgumentException( + "Num bits must be between 0 and 32".to_owned(), + )); + } + + if num_bits == 0 { + return Ok(()); + } + + let mut next_size = self.size; + self.ensure_capacity(next_size + num_bits); + for numBitsLeft in (0..num_bits).rev() { + //for (int numBitsLeft = numBits - 1; numBitsLeft >= 0; numBitsLeft--) { + if (value & (1 << numBitsLeft)) != 0 { + self.bits[next_size / 32] |= 1 << (next_size & 0x1F); + } + next_size += 1; + } + self.size = next_size; + Ok(()) + } + + pub fn appendBitArray(&mut self, other: BitArray) { + let otherSize = other.size; + self.ensure_capacity(self.size + otherSize); + for i in 0..otherSize { + //for (int i = 0; i < otherSize; i++) { + self.appendBit(other.get(i)); + } + } + + pub fn xor(&mut self, other: &BitArray) -> Result<(), Exceptions> { + if self.size != other.size { + return Err(Exceptions::IllegalArgumentException( + "Sizes don't match".to_owned(), + )); + } + for i in 0..self.bits.len() { + //for (int i = 0; i < bits.length; i++) { + // The last int could be incomplete (i.e. not have 32 bits in + // it) but there is no problem since 0 XOR 0 == 0. + self.bits[i] ^= other.bits[i]; + } + Ok(()) + } + + /** + * + * @param bitOffset first bit to start writing + * @param array array to write into. Bytes are written most-significant byte first. This is the opposite + * of the internal representation, which is exposed by {@link #getBitArray()} + * @param offset position in array to start writing + * @param numBytes how many bytes to write + */ + pub fn toBytes(&self, bitOffset: usize, array: &mut [u8], offset: usize, numBytes: usize) { + let mut bitOffset = bitOffset; + for i in 0..numBytes { + //for (int i = 0; i < numBytes; i++) { + let mut theByte = 0; + for j in 0..8 { + //for (int j = 0; j < 8; j++) { + if self.get(bitOffset) { + theByte |= 1 << (7 - j); + } + bitOffset += 1; + } + array[offset + i] = theByte; + } + } + + /** + * @return underlying array of ints. The first element holds the first 32 bits, and the least + * significant bit is bit 0. + */ + pub fn getBitArray(&self) -> &Vec { + return &self.bits; + } + + /** + * Reverses all bits in the array. + */ + pub fn reverse(&mut self) { + let mut newBits = vec![0; self.bits.len()]; + // reverse all int's first + let len = (self.size - 1) / 32; + let oldBitsLen = len + 1; + for i in 0..oldBitsLen { + //for (int i = 0; i < oldBitsLen; i++) { + newBits[len - i] = self.bits[i].reverse_bits(); + } + // now correct the int's if the bit size isn't a multiple of 32 + if self.size != oldBitsLen * 32 { + let leftOffset = oldBitsLen * 32 - self.size; + let mut currentInt = newBits[0] >> leftOffset; + for i in 1..oldBitsLen { + //for (int i = 1; i < oldBitsLen; i++) { + let nextInt = newBits[i]; + currentInt |= nextInt << (32 - leftOffset); + newBits[i - 1] = currentInt; + currentInt = nextInt >> leftOffset; + } + newBits[oldBitsLen - 1] = currentInt; + } + self.bits = newBits; + } + + fn makeArray(size: usize) -> Vec { + return vec![0; (size + 31) / 32]; + } + + // @Override + // public boolean equals(Object o) { + // if (!(o instanceof BitArray)) { + // return false; + // } + // BitArray other = (BitArray) o; + // return size == other.size && Arrays.equals(bits, other.bits); + // } + + // @Override + // public int hashCode() { + // return 31 * size + Arrays.hashCode(bits); + // } + + // @Override + // public BitArray clone() { + // return new BitArray(bits.clone(), size); + // } +} + +impl fmt::Display for BitArray { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut _str = String::with_capacity(self.size + (self.size / 8) + 1); + for i in 0..self.size { + //for (int i = 0; i < size; i++) { + if (i & 0x07) == 0 { + _str.push_str(" "); + } + _str.push_str(if self.get(i) { "X" } else { "." }); + } + write!(f, "{}", _str) + } +} \ No newline at end of file diff --git a/src/common/bit_matrix.rs b/src/common/bit_matrix.rs new file mode 100644 index 0000000..b7a0520 --- /dev/null +++ b/src/common/bit_matrix.rs @@ -0,0 +1,628 @@ + +/* + * Copyright 2007 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// package com.google.zxing.common; + +// import java.util.Arrays; + +use std::fmt; + +use crate::Exceptions; + +use super::BitArray; + +/** + *

Represents a 2D matrix of bits. In function arguments below, and throughout the common + * module, x is the column position, and y is the row position. The ordering is always x, y. + * The origin is at the top-left.

+ * + *

Internally the bits are represented in a 1-D array of 32-bit ints. However, each row begins + * with a new int. This is done intentionally so that we can copy out a row into a BitArray very + * efficiently.

+ * + *

The ordering of bits is row-major. Within each int, the least significant bits are used first, + * meaning they represent lower x values. This is compatible with BitArray's implementation.

+ * + * @author Sean Owen + * @author dswitkin@google.com (Daniel Switkin) + */ +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct BitMatrix { + width: u32, + height: u32, + row_size: usize, + bits: Vec, +} + +impl BitMatrix { + /** + * Creates an empty square {@code BitMatrix}. + * + * @param dimension height and width + */ + pub fn with_single_dimension(dimension: u32) -> Self { + Self::new(dimension, dimension).unwrap() + } + + /** + * Creates an empty {@code BitMatrix}. + * + * @param width bit matrix width + * @param height bit matrix height + */ + pub fn new(width: u32, height: u32) -> Result { + if width < 1 || height < 1 { + return Err(Exceptions::IllegalArgumentException( + "Both dimensions must be greater than 0".to_owned(), + )); + } + Ok(Self { + width, + height, + row_size: ((width + 31) / 32) as usize, + bits: vec![0; (((width + 31) / 32) * height) as usize], + }) + // this.width = width; + // this.height = height; + // this.rowSize = (width + 31) / 32; + // bits = new int[rowSize * height]; + } + + fn with_all_data(&self, width: u32, height: u32, rowSize: usize, bits: Vec) -> Self { + Self { + width, + height, + row_size: rowSize, + bits, + } + } + + /** + * Interprets a 2D array of booleans as a {@code BitMatrix}, where "true" means an "on" bit. + * + * @param image bits of the image, as a row-major 2D array. Elements are arrays representing rows + * @return {@code BitMatrix} representation of image + */ + pub fn parse_bools(image: &Vec>) -> Self { + let height: u32 = image.len().try_into().unwrap(); + let width: u32 = image[0].len().try_into().unwrap(); + let mut bits = BitMatrix::new(width, height).unwrap(); + for i in 0..height as usize { + //for (int i = 0; i < height; i++) { + let imageI = &image[i]; + for j in 0..width as usize { + //for (int j = 0; j < width; j++) { + if imageI[j] { + bits.set(j as u32, i as u32); + } + } + } + return bits; + } + + pub fn parse_strings( + string_representation: &str, + set_string: &str, + unset_string: &str, + ) -> Result { + // cannot pass nulls in rust + // if (stringRepresentation == null) { + // throw new IllegalArgumentException(); + // } + + let mut bits = vec![false; string_representation.len()]; + let mut bitsPos = 0; + let mut rowStartPos = 0; + let mut rowLength = 0; //-1; + let mut first_run = true; + let mut nRows = 0; + let mut pos = 0; + while pos < string_representation.len() { + if string_representation.chars().nth(pos).unwrap() == '\n' + || string_representation.chars().nth(pos).unwrap() == '\r' + { + if bitsPos > rowStartPos { + //if rowLength == -1 { + if first_run { + first_run = false; + rowLength = bitsPos - rowStartPos; + } else if bitsPos - rowStartPos != rowLength { + return Err(Exceptions::IllegalArgumentException( + "row lengths do not match".to_owned(), + )); + } + rowStartPos = bitsPos; + nRows += 1; + } + pos += 1; + } else if string_representation[pos..].starts_with(set_string) { + pos += set_string.len(); + bits[bitsPos] = true; + bitsPos += 1; + } else if string_representation[pos..].starts_with(unset_string) { + pos += unset_string.len(); + bits[bitsPos] = false; + bitsPos += 1; + } else { + return Err(Exceptions::IllegalArgumentException(format!( + "illegal character encountered: {}", + string_representation[pos..].to_owned() + ))); + } + } + + // no EOL at end? + if bitsPos > rowStartPos { + //if rowLength == -1 { + if first_run { + first_run = false; + rowLength = bitsPos - rowStartPos; + } else if bitsPos - rowStartPos != rowLength { + return Err(Exceptions::IllegalArgumentException( + "row lengths do not match".to_owned(), + )); + } + nRows += 1; + } + + let mut matrix = BitMatrix::new(rowLength.try_into().unwrap(), nRows)?; + for i in 0..bitsPos { + //for (int i = 0; i < bitsPos; i++) { + if bits[i] { + matrix.set( + (i % rowLength).try_into().unwrap(), + (i / rowLength).try_into().unwrap(), + ); + } + } + return Ok(matrix); + } + + /** + *

Gets the requested bit, where true means black.

+ * + * @param x The horizontal component (i.e. which column) + * @param y The vertical component (i.e. which row) + * @return value of given bit in matrix + */ + pub fn get(&self, x: u32, y: u32) -> bool { + let offset = y as usize * self.row_size + (x as usize / 32); + return ((self.bits[offset] >> (x & 0x1f)) & 1) != 0; + } + + /** + *

Sets the given bit to true.

+ * + * @param x The horizontal component (i.e. which column) + * @param y The vertical component (i.e. which row) + */ + pub fn set(&mut self, x: u32, y: u32) { + let offset = y as usize * self.row_size + (x as usize / 32); + self.bits[offset] |= 1 << (x & 0x1f); + } + + pub fn unset(&mut self, x: u32, y: u32) { + let offset = y as usize * self.row_size + (x as usize / 32); + self.bits[offset] &= !(1 << (x & 0x1f)); + } + + /** + *

Flips the given bit.

+ * + * @param x The horizontal component (i.e. which column) + * @param y The vertical component (i.e. which row) + */ + pub fn flip_coords(&mut self, x: u32, y: u32) { + let offset = y as usize * self.row_size + (x as usize / 32); + self.bits[offset] ^= 1 << (x & 0x1f); + } + + /** + *

Flips every bit in the matrix.

+ */ + pub fn flip_self(&mut self) { + let max = self.bits.len(); + for i in 0..max { + //for (int i = 0; i < max; i++) { + self.bits[i] = !self.bits[i]; + } + } + + /** + * Exclusive-or (XOR): Flip the bit in this {@code BitMatrix} if the corresponding + * mask bit is set. + * + * @param mask XOR mask + */ + pub fn xor(&mut self, mask: &BitMatrix) -> Result<(), Exceptions> { + if self.width != mask.width || self.height != mask.height || self.row_size != mask.row_size + { + return Err(Exceptions::IllegalArgumentException( + "input matrix dimensions do not match".to_owned(), + )); + } + let rowArray = BitArray::with_size(self.width as usize); + for y in 0..self.height { + //for (int y = 0; y < height; y++) { + let offset = y as usize * self.row_size; + let tmp = mask.getRow(y, &rowArray); + let row = tmp.getBitArray(); + for x in 0..self.row_size { + //for (int x = 0; x < rowSize; x++) { + self.bits[offset + x] ^= row[x]; + } + } + Ok(()) + } + + /** + * Clears all bits (sets to false). + */ + pub fn clear(&mut self) { + let max = self.bits.len(); + for i in 0..max { + //for (int i = 0; i < max; i++) { + self.bits[i] = 0; + } + } + + /** + *

Sets a square region of the bit matrix to true.

+ * + * @param left The horizontal position to begin at (inclusive) + * @param top The vertical position to begin at (inclusive) + * @param width The width of the region + * @param height The height of the region + */ + pub fn setRegion( + &mut self, + left: u32, + top: u32, + width: u32, + height: u32, + ) -> Result<(), Exceptions> { + // if top < 0 || left < 0 { + // return Err(Exceptions::IllegalArgumentException( + // "Left and top must be nonnegative".to_owned(), + // )); + // } + if height < 1 || width < 1 { + return Err(Exceptions::IllegalArgumentException( + "Height and width must be at least 1".to_owned(), + )); + } + let right = left + width; + let bottom = top + height; + if bottom > self.height || right > self.width { + return Err(Exceptions::IllegalArgumentException( + "The region must fit inside the matrix".to_owned(), + )); + } + for y in top..bottom { + //for (int y = top; y < bottom; y++) { + let offset = y as usize * self.row_size; + for x in left..right { + //for (int x = left; x < right; x++) { + self.bits[offset + (x as usize / 32)] |= 1 << (x & 0x1f); + } + } + Ok(()) + } + + /** + * A fast method to retrieve one row of data from the matrix as a BitArray. + * + * @param y The row to retrieve + * @param row An optional caller-allocated BitArray, will be allocated if null or too small + * @return The resulting BitArray - this reference should always be used even when passing + * your own row + */ + pub fn getRow(&self, y: u32, row: &BitArray) -> BitArray { + let mut rw: BitArray = if row.getSize() < self.width as usize { + BitArray::with_size(self.width as usize) + } else { + let mut z = row.clone(); + z.clear(); + z + // row.clear(); + // row.clone() + }; + + let offset = y as usize * self.row_size; + for x in 0..self.row_size { + //for (int x = 0; x < rowSize; x++) { + rw.setBulk(x * 32, self.bits[offset + x]); + } + return rw; + } + + /** + * @param y row to set + * @param row {@link BitArray} to copy from + */ + pub fn setRow(&mut self, y: u32, row: &BitArray) { + return self.bits[y as usize * self.row_size..y as usize * self.row_size + self.row_size] + .clone_from_slice(&row.getBitArray()[0..self.row_size]); + //System.arraycopy(row.getBitArray(), 0, self.bits, y * self.rowSize, self.rowSize); + } + + /** + * Modifies this {@code BitMatrix} to represent the same but rotated the given degrees (0, 90, 180, 270) + * + * @param degrees number of degrees to rotate through counter-clockwise (0, 90, 180, 270) + */ + pub fn rotate(&mut self, degrees: u32) -> Result<(), Exceptions> { + match degrees % 360 { + 0 => Ok(()), + 90 => { + self.rotate90(); + Ok(()) + } + 180 => { + self.rotate180(); + Ok(()) + } + 270 => { + self.rotate90(); + self.rotate180(); + Ok(()) + } + _ => Err(Exceptions::IllegalArgumentException( + "degrees must be a multiple of 0, 90, 180, or 270".to_owned(), + )), + } + } + + /** + * Modifies this {@code BitMatrix} to represent the same but rotated 180 degrees + */ + pub fn rotate180(&mut self) { + let mut topRow = BitArray::with_size(self.width as usize); + let mut bottomRow = BitArray::with_size(self.width as usize); + let maxHeight = (self.height + 1) / 2; + for i in 0..maxHeight { + //for (int i = 0; i < maxHeight; i++) { + topRow = self.getRow(i, &topRow); + let bottomRowIndex = self.height - 1 - i; + bottomRow = self.getRow(bottomRowIndex, &bottomRow); + topRow.reverse(); + bottomRow.reverse(); + self.setRow(i, &bottomRow); + self.setRow(bottomRowIndex, &topRow); + } + } + + /** + * Modifies this {@code BitMatrix} to represent the same but rotated 90 degrees counterclockwise + */ + pub fn rotate90(&mut self) { + let newWidth = self.height; + let newHeight = self.width; + let newRowSize = (newWidth + 31) / 32; + let mut newBits = vec![0; (newRowSize * newHeight).try_into().unwrap()]; + + for y in 0..self.height { + //for (int y = 0; y < height; y++) { + for x in 0..self.width { + //for (int x = 0; x < width; x++) { + let offset = y as usize * self.row_size + (x as usize / 32); + if ((self.bits[offset] >> (x & 0x1f)) & 1) != 0 { + let newOffset: usize = ((newHeight - 1 - x) * newRowSize + (y / 32)) + .try_into() + .unwrap(); + newBits[newOffset] |= 1 << (y & 0x1f); + } + } + } + self.width = newWidth; + self.height = newHeight; + self.row_size = newRowSize.try_into().unwrap(); + self.bits = newBits; + } + + /** + * This is useful in detecting the enclosing rectangle of a 'pure' barcode. + * + * @return {@code left,top,width,height} enclosing rectangle of all 1 bits, or null if it is all white + */ + pub fn getEnclosingRectangle(&self) -> Option> { + let mut left = self.width; + let mut top = self.height; + // let right = -1; + // let bottom = -1; + let mut right: u32 = 0; + let mut bottom = 0; + + for y in 0..self.height { + //for (int y = 0; y < height; y++) { + for x32 in 0..self.row_size { + //for (int x32 = 0; x32 < rowSize; x32++) { + let theBits = self.bits[y as usize * self.row_size + x32]; + if theBits != 0 { + if y < top { + top = y; + } + if y > bottom { + bottom = y; + } + if x32 * 32 < left.try_into().unwrap() { + let mut bit = 0; + while (theBits << (31 - bit)) == 0 { + bit += 1; + } + if (x32 * 32 + bit) < left.try_into().unwrap() { + left = (x32 * 32 + bit).try_into().unwrap(); + } + } + if x32 * 32 + 31 > right.try_into().unwrap() { + let mut bit = 31; + while (theBits >> bit) == 0 { + bit -= 1; + } + if (x32 * 32 + bit) > right.try_into().unwrap() { + right = (x32 * 32 + bit).try_into().unwrap(); + } + } + } + } + } + + if right < left || bottom < top { + return None; + } + + return Some(vec![left, top, right - left + 1, bottom - top + 1]); + } + + /** + * This is useful in detecting a corner of a 'pure' barcode. + * + * @return {@code x,y} coordinate of top-left-most 1 bit, or null if it is all white + */ + pub fn getTopLeftOnBit(&self) -> Option> { + let mut bitsOffset = 0; + while bitsOffset < self.bits.len() && self.bits[bitsOffset] == 0 { + bitsOffset += 1; + } + if bitsOffset == self.bits.len() { + return None; + } + let y = bitsOffset / self.row_size; + let mut x = (bitsOffset % self.row_size) * 32; + + let theBits = self.bits[bitsOffset]; + let mut bit = 0; + while (theBits << (31 - bit)) == 0 { + bit += 1; + } + x += bit; + return Some(vec![x as u32, y as u32]); + } + + pub fn getBottomRightOnBit(&self) -> Option> { + let mut bitsOffset = self.bits.len() as i64 - 1; + while bitsOffset >= 0 && self.bits[bitsOffset as usize] == 0 { + bitsOffset -= 1; + } + if bitsOffset < 0 { + return None; + } + + let y = bitsOffset as usize / self.row_size; + let mut x = (bitsOffset as usize % self.row_size) * 32; + + let theBits = self.bits[bitsOffset as usize]; + let mut bit = 31; + while (theBits >> bit) == 0 { + bit -= 1; + } + x += bit; + + return Some(vec![x as u32, y as u32]); + } + + /** + * @return The width of the matrix + */ + pub fn getWidth(&self) -> u32 { + return self.width; + } + + /** + * @return The height of the matrix + */ + pub fn getHeight(&self) -> u32 { + return self.height; + } + + /** + * @return The row size of the matrix + */ + pub fn getRowSize(&self) -> usize { + return self.row_size; + } + + // @Override + // public boolean equals(Object o) { + // if (!(o instanceof BitMatrix)) { + // return false; + // } + // BitMatrix other = (BitMatrix) o; + // return width == other.width && height == other.height && rowSize == other.rowSize && + // Arrays.equals(bits, other.bits); + // } + + // @Override + // public int hashCode() { + // int hash = width; + // hash = 31 * hash + width; + // hash = 31 * hash + height; + // hash = 31 * hash + rowSize; + // hash = 31 * hash + Arrays.hashCode(bits); + // return hash; + // } + + /** + * @param setString representation of a set bit + * @param unsetString representation of an unset bit + * @return string representation of entire matrix utilizing given strings + */ + pub fn toString(&self, setString: &str, unsetString: &str) -> String { + return self.buildToString(setString, unsetString, "\n"); + } + + /** + * @param setString representation of a set bit + * @param unsetString representation of an unset bit + * @param lineSeparator newline character in string representation + * @return string representation of entire matrix utilizing given strings and line separator + * @deprecated call {@link #toString(String,String)} only, which uses \n line separator always + */ + // @Deprecated + // public String toString(String setString, String unsetString, String lineSeparator) { + // return buildToString(setString, unsetString, lineSeparator); + // } + + fn buildToString(&self, setString: &str, unsetString: &str, lineSeparator: &str) -> String { + let mut result = + String::with_capacity((self.height * (self.width + 1)).try_into().unwrap()); + for y in 0..self.height { + //for (int y = 0; y < height; y++) { + for x in 0..self.width { + //for (int x = 0; x < width; x++) { + result.push_str(if self.get(x, y) { + setString + } else { + unsetString + }); + } + result.push_str(lineSeparator); + } + return result; + } + + // @Override + // public BitMatrix clone() { + // return new BitMatrix(width, height, rowSize, bits.clone()); + // } +} + +impl fmt::Display for BitMatrix { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.toString("X ", " ")) + } +} \ No newline at end of file diff --git a/src/common/bit_source.rs b/src/common/bit_source.rs new file mode 100644 index 0000000..9e0cbba --- /dev/null +++ b/src/common/bit_source.rs @@ -0,0 +1,125 @@ + +/* + * Copyright 2007 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//package com.google.zxing.common; + +use std::cmp; + +use crate::Exceptions; + +/** + *

This provides an easy abstraction to read bits at a time from a sequence of bytes, where the + * number of bits read is not often a multiple of 8.

+ * + *

This class is thread-safe but not reentrant -- unless the caller modifies the bytes array + * it passed in, in which case all bets are off.

+ * + * @author Sean Owen + */ +pub struct BitSource { + bytes: Vec, + byte_offset: usize, + bit_offset: usize, +} + +impl BitSource { + /** + * @param bytes bytes from which this will read bits. Bits will be read from the first byte first. + * Bits are read within a byte from most-significant to least-significant bit. + */ + pub fn new(bytes: Vec) -> Self { + Self { + bytes, + byte_offset: 0, + bit_offset: 0, + } + } + + /** + * @return index of next bit in current byte which would be read by the next call to {@link #readBits(int)}. + */ + pub fn getBitOffset(&self) -> usize { + return self.bit_offset; + } + + /** + * @return index of next byte in input byte array which would be read by the next call to {@link #readBits(int)}. + */ + pub fn getByteOffset(&self) -> usize { + return self.byte_offset; + } + + /** + * @param numBits number of bits to read + * @return int representing the bits read. The bits will appear as the least-significant + * bits of the int + * @throws IllegalArgumentException if numBits isn't in [1,32] or more than is available + */ + pub fn readBits(&mut self, numBits: usize) -> Result { + if numBits < 1 || numBits > 32 || numBits > self.available() { + return Err(Exceptions::IllegalArgumentException(numBits.to_string())); + } + + let mut result: u32 = 0; + + let mut num_bits = numBits; + + // First, read remainder from current byte + if self.bit_offset > 0 { + let bitsLeft = 8 - self.bit_offset; + let toRead = cmp::min(num_bits, bitsLeft); + let bitsToNotRead = bitsLeft - toRead; + let mask = (0xFF >> (8 - toRead)) << bitsToNotRead; + + result = (self.bytes[self.byte_offset] & mask) as u32 >> bitsToNotRead; + num_bits -= toRead; + self.bit_offset += toRead; + if self.bit_offset == 8 { + self.bit_offset = 0; + self.byte_offset += 1; + } + } + + // Next read whole bytes + if num_bits > 0 { + while num_bits >= 8 { + result = (result << 8) | (self.bytes[self.byte_offset] & 0xFF) as u32; + // result = ((result as u16) << 8) as u8 | (self.bytes[self.byte_offset]); + self.byte_offset += 1; + num_bits -= 8; + } + + // Finally read a partial byte + if num_bits > 0 { + let bits_to_not_read = 8 - num_bits; + let mask = (0xFF >> bits_to_not_read) << bits_to_not_read; + result = (result << num_bits) + | ((self.bytes[self.byte_offset] & mask) as u32 >> bits_to_not_read); + self.bit_offset += num_bits; + } + } + + return Ok(result); + } + + /** + * @return number of bits that can be read successfully + */ + pub fn available(&self) -> usize { + return 8 * (self.bytes.len() - self.byte_offset) - self.bit_offset; + } +} \ No newline at end of file diff --git a/src/common/bit_source_builder.rs b/src/common/bit_source_builder.rs new file mode 100644 index 0000000..fd6f34c --- /dev/null +++ b/src/common/bit_source_builder.rs @@ -0,0 +1,68 @@ + +/* + * Copyright 2008 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// package com.google.zxing.common; + +// import java.io.ByteArrayOutputStream; + +/** + * Class that lets one easily build an array of bytes by appending bits at a time. + * + * @author Sean Owen + */ +pub struct BitSourceBuilder { + output: Vec, + nextByte: u32, + bitsLeftInNextByte: u32, +} + +impl BitSourceBuilder { + pub fn new() -> Self { + Self { + output: Vec::new(), + nextByte: 0, + bitsLeftInNextByte: 8, + } + } + + pub fn write(&mut self, value: u32, numBits: u32) { + if numBits <= self.bitsLeftInNextByte { + self.nextByte <<= numBits; + self.nextByte |= value; + self.bitsLeftInNextByte -= numBits; + if self.bitsLeftInNextByte == 0 { + self.output.push(self.nextByte as u8); + self.nextByte = 0; + self.bitsLeftInNextByte = 8; + } + } else { + let bitsToWriteNow = self.bitsLeftInNextByte; + let numRestOfBits = numBits - bitsToWriteNow; + let mask = 0xFF >> (8 - bitsToWriteNow); + let valueToWriteNow = (value >> numRestOfBits) & mask; + self.write(valueToWriteNow, bitsToWriteNow); + self.write(value, numRestOfBits); + } + } + + pub fn toByteArray(&mut self) -> &Vec { + if self.bitsLeftInNextByte < 8 { + self.write(0, self.bitsLeftInNextByte); + } + &self.output + } +} \ No newline at end of file diff --git a/src/common/character_set_eci.rs b/src/common/character_set_eci.rs new file mode 100644 index 0000000..2d8c511 --- /dev/null +++ b/src/common/character_set_eci.rs @@ -0,0 +1,289 @@ + +/* + * Copyright 2008 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// package com.google.zxing.common; + +// import com.google.zxing.FormatException; + +// import java.nio.charset.Charset; + +// import java.util.HashMap; +// import java.util.Map; + +use encoding::EncodingRef; + +use crate::Exceptions; + +/** + * Encapsulates a Character Set ECI, according to "Extended Channel Interpretations" 5.3.1.1 + * of ISO 18004. + * + * @author Sean Owen + */ +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum CharacterSetECI { + // Enum name is a Java encoding valid for java.lang and java.io + Cp437, //(new int[]{0,2}), + ISO8859_1, //(new int[]{1,3}, "ISO-8859-1"), + ISO8859_2, //(4, "ISO-8859-2"), + ISO8859_3, //(5, "ISO-8859-3"), + ISO8859_4, //(6, "ISO-8859-4"), + ISO8859_5, //(7, "ISO-8859-5"), + ISO8859_6, //(8, "ISO-8859-6"), + ISO8859_7, //(9, "ISO-8859-7"), + ISO8859_8, //(10, "ISO-8859-8"), + ISO8859_9, //(11, "ISO-8859-9"), + ISO8859_10, //(12, "ISO-8859-10"), + ISO8859_11, //(13, "ISO-8859-11"), + ISO8859_13, //(15, "ISO-8859-13"), + ISO8859_14, //(16, "ISO-8859-14"), + ISO8859_15, //(17, "ISO-8859-15"), + ISO8859_16, //(18, "ISO-8859-16"), + SJIS, //(20, "Shift_JIS"), + Cp1250, //(21, "windows-1250"), + Cp1251, //(22, "windows-1251"), + Cp1252, //(23, "windows-1252"), + Cp1256, //(24, "windows-1256"), + UnicodeBigUnmarked, //(25, "UTF-16BE", "UnicodeBig"), + UTF8, //(26, "UTF-8"), + ASCII, //(new int[] {27, 170}, "US-ASCII"), + Big5, //(28), + GB18030, //(29, "GB2312", "EUC_CN", "GBK"), + EUC_KR, //(30, "EUC-KR"); +} +impl CharacterSetECI { + // private static final Map VALUE_TO_ECI = new HashMap<>(); + // private static final Map NAME_TO_ECI = new HashMap<>(); + // static { + // for (CharacterSetECI eci : values()) { + // for (int value : eci.values) { + // VALUE_TO_ECI.put(value, eci); + // } + // NAME_TO_ECI.put(eci.name(), eci); + // for (String name : eci.otherEncodingNames) { + // NAME_TO_ECI.put(name, eci); + // } + // } + // } + + // private final int[] values; + // private final String[] otherEncodingNames; + + // CharacterSetECI(int value) { + // this(new int[] {value}); + // } + + // CharacterSetECI(int value, String... otherEncodingNames) { + // this.values = new int[] {value}; + // this.otherEncodingNames = otherEncodingNames; + // } + + // CharacterSetECI(int[] values, String... otherEncodingNames) { + // this.values = values; + // this.otherEncodingNames = otherEncodingNames; + // } + + pub fn getValueSelf(&self) -> u32 { + Self::getValue(self) + } + + pub fn getValue(cs_eci: &CharacterSetECI) -> u32 { + match cs_eci { + CharacterSetECI::Cp437 => 0, + CharacterSetECI::ISO8859_1 => 1, + CharacterSetECI::ISO8859_2 => 4, + CharacterSetECI::ISO8859_3 => 5, + CharacterSetECI::ISO8859_4 => 6, + CharacterSetECI::ISO8859_5 => 7, + CharacterSetECI::ISO8859_6 => 8, + CharacterSetECI::ISO8859_7 => 9, + CharacterSetECI::ISO8859_8 => 10, + CharacterSetECI::ISO8859_9 => 11, + CharacterSetECI::ISO8859_10 => 12, + CharacterSetECI::ISO8859_11 => 13, + CharacterSetECI::ISO8859_13 => 15, + CharacterSetECI::ISO8859_14 => 16, + CharacterSetECI::ISO8859_15 => 17, + CharacterSetECI::ISO8859_16 => 18, + CharacterSetECI::SJIS => 20, + CharacterSetECI::Cp1250 => 21, + CharacterSetECI::Cp1251 => 22, + CharacterSetECI::Cp1252 => 23, + CharacterSetECI::Cp1256 => 24, + CharacterSetECI::UnicodeBigUnmarked => 25, + CharacterSetECI::UTF8 => 26, + CharacterSetECI::ASCII => 27, + CharacterSetECI::Big5 => 28, + CharacterSetECI::GB18030 => 29, + CharacterSetECI::EUC_KR => 30, + } + } + + pub fn getCharset(cs_eci: &CharacterSetECI) -> EncodingRef { + let name = match cs_eci { + // CharacterSetECI::Cp437 => "CP437", + CharacterSetECI::Cp437 => "UTF-8", + CharacterSetECI::ISO8859_1 => "ISO-8859-1", + CharacterSetECI::ISO8859_2 => "ISO-8859-2", + CharacterSetECI::ISO8859_3 => "ISO-8859-3", + CharacterSetECI::ISO8859_4 => "ISO-8859-4", + CharacterSetECI::ISO8859_5 => "ISO-8859-5", + CharacterSetECI::ISO8859_6 => "ISO-8859-6", + CharacterSetECI::ISO8859_7 => "ISO-8859-7", + CharacterSetECI::ISO8859_8 => "ISO-8859-8", + CharacterSetECI::ISO8859_9 => "ISO-8859-9", + CharacterSetECI::ISO8859_10 => "ISO-8859-10", + CharacterSetECI::ISO8859_11 => "ISO-8859-11", + CharacterSetECI::ISO8859_13 => "ISO-8859-13", + CharacterSetECI::ISO8859_14 => "ISO-8859-14", + CharacterSetECI::ISO8859_15 => "ISO-8859-15", + CharacterSetECI::ISO8859_16 => "ISO-8859-16", + CharacterSetECI::SJIS => "Shift_JIS", + CharacterSetECI::Cp1250 => "windows-1250", + CharacterSetECI::Cp1251 => "windows-1251", + CharacterSetECI::Cp1252 => "windows-1252", + CharacterSetECI::Cp1256 => "windows-1256", + CharacterSetECI::UnicodeBigUnmarked => "UTF-16BE", + CharacterSetECI::UTF8 => "UTF-8", + CharacterSetECI::ASCII => "US-ASCII", + CharacterSetECI::Big5 => "Big5", + CharacterSetECI::GB18030 => "GB2312", + CharacterSetECI::EUC_KR => "EUC-KR", + }; + encoding::label::encoding_from_whatwg_label(name).unwrap() + } + + /** + * @param charset Java character set object + * @return CharacterSetECI representing ECI for character encoding, or null if it is legal + * but unsupported + */ + pub fn getCharacterSetECI(charset: EncodingRef) -> Option { + let name = if let Some(nm) = charset.whatwg_name() { + nm + } else { + charset.name() + }; + match name { + "CP437" => Some(CharacterSetECI::Cp437), + "iso-8859-1" => Some(CharacterSetECI::ISO8859_1), + "iso-8859-2" => Some(CharacterSetECI::ISO8859_2), + "iso-8859-3" => Some(CharacterSetECI::ISO8859_3), + "iso-8859-4" => Some(CharacterSetECI::ISO8859_4), + "iso-8859-5" => Some(CharacterSetECI::ISO8859_5), + "iso-8859-6" => Some(CharacterSetECI::ISO8859_6), + "iso-8859-7" => Some(CharacterSetECI::ISO8859_7), + "iso-8859-8" => Some(CharacterSetECI::ISO8859_8), + "iso-8859-9" => Some(CharacterSetECI::ISO8859_9), + "iso-8859-10" => Some(CharacterSetECI::ISO8859_10), + "iso-8859-11" => Some(CharacterSetECI::ISO8859_11), + "iso-8859-13" => Some(CharacterSetECI::ISO8859_13), + "iso-8859-14" => Some(CharacterSetECI::ISO8859_14), + "iso-8859-15" => Some(CharacterSetECI::ISO8859_15), + "iso-8859-16" => Some(CharacterSetECI::ISO8859_16), + "shift_jis" => Some(CharacterSetECI::SJIS), + "windows-1250" => Some(CharacterSetECI::Cp1250), + "windows-1251" => Some(CharacterSetECI::Cp1251), + "windows-1252" => Some(CharacterSetECI::Cp1252), + "windows-1256" => Some(CharacterSetECI::Cp1256), + "utf-16be" => Some(CharacterSetECI::UnicodeBigUnmarked), + "utf-8" => Some(CharacterSetECI::UTF8), + "us-ascii" => Some(CharacterSetECI::ASCII), + "big5" => Some(CharacterSetECI::Big5), + "gb2312" => Some(CharacterSetECI::GB18030), + "euc-kr" => Some(CharacterSetECI::EUC_KR), + _ => None, + } + } + + /** + * @param value character set ECI value + * @return {@code CharacterSetECI} representing ECI of given value, or null if it is legal but + * unsupported + * @throws FormatException if ECI value is invalid + */ + pub fn getCharacterSetECIByValue(value: u32) -> Result { + match value { + 0 | 2 => Ok(CharacterSetECI::Cp437), + 1 | 3 => Ok(CharacterSetECI::ISO8859_1), + 4 => Ok(CharacterSetECI::ISO8859_2), + 5 => Ok(CharacterSetECI::ISO8859_3), + 6 => Ok(CharacterSetECI::ISO8859_4), + 7 => Ok(CharacterSetECI::ISO8859_5), + 8 => Ok(CharacterSetECI::ISO8859_6), + 9 => Ok(CharacterSetECI::ISO8859_7), + 10 => Ok(CharacterSetECI::ISO8859_8), + 11 => Ok(CharacterSetECI::ISO8859_9), + 12 => Ok(CharacterSetECI::ISO8859_10), + 13 => Ok(CharacterSetECI::ISO8859_11), + 15 => Ok(CharacterSetECI::ISO8859_13), + 16 => Ok(CharacterSetECI::ISO8859_14), + 17 => Ok(CharacterSetECI::ISO8859_15), + 18 => Ok(CharacterSetECI::ISO8859_16), + 20 => Ok(CharacterSetECI::SJIS), + 21 => Ok(CharacterSetECI::Cp1250), + 22 => Ok(CharacterSetECI::Cp1251), + 23 => Ok(CharacterSetECI::Cp1252), + 24 => Ok(CharacterSetECI::Cp1256), + 25 => Ok(CharacterSetECI::UnicodeBigUnmarked), + 26 => Ok(CharacterSetECI::UTF8), + 27 | 170 => Ok(CharacterSetECI::ASCII), + 28 => Ok(CharacterSetECI::Big5), + 29 => Ok(CharacterSetECI::GB18030), + 30 => Ok(CharacterSetECI::EUC_KR), + _ => Err(Exceptions::NotFoundException("Bad ECI Value".to_owned())), + } + } + + /** + * @param name character set ECI encoding name + * @return CharacterSetECI representing ECI for character encoding, or null if it is legal + * but unsupported + */ + pub fn getCharacterSetECIByName(name: &str) -> Option { + match name { + "CP437" => Some(CharacterSetECI::Cp437), + "ISO-8859-1" => Some(CharacterSetECI::ISO8859_1), + "ISO-8859-2" => Some(CharacterSetECI::ISO8859_2), + "ISO-8859-3" => Some(CharacterSetECI::ISO8859_3), + "ISO-8859-4" => Some(CharacterSetECI::ISO8859_4), + "ISO-8859-5" => Some(CharacterSetECI::ISO8859_5), + "ISO-8859-6" => Some(CharacterSetECI::ISO8859_6), + "ISO-8859-7" => Some(CharacterSetECI::ISO8859_7), + "ISO-8859-8" => Some(CharacterSetECI::ISO8859_8), + "ISO-8859-9" => Some(CharacterSetECI::ISO8859_9), + "ISO-8859-10" => Some(CharacterSetECI::ISO8859_10), + "ISO-8859-11" => Some(CharacterSetECI::ISO8859_11), + "ISO-8859-13" => Some(CharacterSetECI::ISO8859_13), + "ISO-8859-14" => Some(CharacterSetECI::ISO8859_14), + "ISO-8859-15" => Some(CharacterSetECI::ISO8859_15), + "ISO-8859-16" => Some(CharacterSetECI::ISO8859_16), + "Shift_JIS" => Some(CharacterSetECI::SJIS), + "windows-1250" => Some(CharacterSetECI::Cp1250), + "windows-1251" => Some(CharacterSetECI::Cp1251), + "windows-1252" => Some(CharacterSetECI::Cp1252), + "windows-1256" => Some(CharacterSetECI::Cp1256), + "UTF-16BE" => Some(CharacterSetECI::UnicodeBigUnmarked), + "UTF-8" => Some(CharacterSetECI::UTF8), + "US-ASCII" => Some(CharacterSetECI::ASCII), + "Big5" => Some(CharacterSetECI::Big5), + "GB2312" => Some(CharacterSetECI::GB18030), + "EUC-KR" => Some(CharacterSetECI::EUC_KR), + _ => None, + } + } +} \ No newline at end of file diff --git a/src/common/decoder_rxing_result.rs b/src/common/decoder_rxing_result.rs new file mode 100644 index 0000000..9c39c3b --- /dev/null +++ b/src/common/decoder_rxing_result.rs @@ -0,0 +1,209 @@ + +/* + * Copyright 2007 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// package com.google.zxing.common; + +// import java.util.List; + +use std::any::Any; + +/** + *

Encapsulates the result of decoding a matrix of bits. This typically + * applies to 2D barcode formats. For now it contains the raw bytes obtained, + * as well as a String interpretation of those bytes, if applicable.

+ * + * @author Sean Owen + */ +pub struct DecoderRXingResult { + rawBytes: Vec, + numBits: usize, + text: String, + byteSegments: Vec>, + ecLevel: String, + errorsCorrected: u64, + erasures: u64, + other: Box, + structuredAppendParity: i32, + structuredAppendSequenceNumber: i32, + symbologyModifier: u32, +} + +impl DecoderRXingResult { + pub fn new( + rawBytes: Vec, + text: String, + byteSegments: Vec>, + ecLevel: String, + ) -> Self { + Self::with_all(rawBytes, text, byteSegments, ecLevel, -2, -2, 0) + } + + pub fn with_symbology( + rawBytes: Vec, + text: String, + byteSegments: Vec>, + ecLevel: String, + symbologyModifier: u32, + ) -> Self { + Self::with_all( + rawBytes, + text, + byteSegments, + ecLevel, + -1, + -1, + symbologyModifier, + ) + } + + pub fn with_sa( + rawBytes: Vec, + text: String, + byteSegments: Vec>, + ecLevel: String, + saSequence: i32, + saParity: i32, + ) -> Self { + Self::with_all( + rawBytes, + text, + byteSegments, + ecLevel, + saSequence, + saParity, + 0, + ) + } + + pub fn with_all( + rawBytes: Vec, + text: String, + byteSegments: Vec>, + ecLevel: String, + saSequence: i32, + saParity: i32, + symbologyModifier: u32, + ) -> Self { + let nb = rawBytes.len(); + Self { + rawBytes, + numBits: nb, + text, + byteSegments, + ecLevel, + errorsCorrected: 0, + erasures: 0, + other: Box::new(false), + structuredAppendParity: saParity, + structuredAppendSequenceNumber: saSequence, + symbologyModifier, + } + } + + /** + * @return raw bytes representing the result, or {@code null} if not applicable + */ + pub fn getRawBytes(&self) -> &Vec { + &self.rawBytes + } + + /** + * @return how many bits of {@link #getRawBytes()} are valid; typically 8 times its length + * @since 3.3.0 + */ + pub fn getNumBits(&self) -> usize { + self.numBits + } + + /** + * @param numBits overrides the number of bits that are valid in {@link #getRawBytes()} + * @since 3.3.0 + */ + pub fn setNumBits(&mut self, numBits: usize) { + self.numBits = numBits; + } + + /** + * @return text representation of the result + */ + pub fn getText(&self) -> &str { + &self.text + } + + /** + * @return list of byte segments in the result, or {@code null} if not applicable + */ + pub fn getByteSegments(&self) -> &Vec> { + &self.byteSegments + } + + /** + * @return name of error correction level used, or {@code null} if not applicable + */ + pub fn getECLevel(&self) -> &str { + &self.ecLevel + } + + /** + * @return number of errors corrected, or {@code null} if not applicable + */ + pub fn getErrorsCorrected(&self) -> u64 { + self.errorsCorrected + } + + pub fn setErrorsCorrected(&mut self, errorsCorrected: u64) { + self.errorsCorrected = errorsCorrected; + } + + /** + * @return number of erasures corrected, or {@code null} if not applicable + */ + pub fn getErasures(&self) -> u64 { + self.erasures + } + + pub fn setErasures(&mut self, erasures: u64) { + self.erasures = erasures + } + + /** + * @return arbitrary additional metadata + */ + pub fn getOther(&self) -> &Box { + &self.other + } + + pub fn setOther(&mut self, other: Box) { + self.other = other + } + + pub fn hasStructuredAppend(&self) -> bool { + self.structuredAppendParity >= 0 && self.structuredAppendSequenceNumber >= 0 + } + + pub fn getStructuredAppendParity(&self) -> i32 { + self.structuredAppendParity + } + + pub fn getStructuredAppendSequenceNumber(&self) -> i32 { + self.structuredAppendSequenceNumber + } + + pub fn getSymbologyModifier(&self) -> u32 { + self.symbologyModifier + } +} \ No newline at end of file diff --git a/src/common/default_grid_sampler.rs b/src/common/default_grid_sampler.rs new file mode 100644 index 0000000..eeaf523 --- /dev/null +++ b/src/common/default_grid_sampler.rs @@ -0,0 +1,121 @@ + +/* + * Copyright 2007 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// package com.google.zxing.common; + +// import com.google.zxing.NotFoundException; + +use crate::Exceptions; + +use super::{BitMatrix, PerspectiveTransform, GridSampler}; + +/** + * @author Sean Owen + */ +pub struct DefaultGridSampler {} + +impl GridSampler for DefaultGridSampler { + fn sample_grid_detailed( + &self, + image: &BitMatrix, + dimensionX: u32, + dimensionY: u32, + p1ToX: f32, + p1ToY: f32, + p2ToX: f32, + p2ToY: f32, + p3ToX: f32, + p3ToY: f32, + p4ToX: f32, + p4ToY: f32, + p1FromX: f32, + p1FromY: f32, + p2FromX: f32, + p2FromY: f32, + p3FromX: f32, + p3FromY: f32, + p4FromX: f32, + p4FromY: f32, + ) -> Result { + let transform = PerspectiveTransform::quadrilateralToQuadrilateral( + p1ToX, p1ToY, p2ToX, p2ToY, p3ToX, p3ToY, p4ToX, p4ToY, p1FromX, p1FromY, p2FromX, + p2FromY, p3FromX, p3FromY, p4FromX, p4FromY, + ); + + self.sample_grid(image, dimensionX, dimensionY, &transform) + } + + fn sample_grid( + &self, + image: &BitMatrix, + dimensionX: u32, + dimensionY: u32, + transform: &PerspectiveTransform, + ) -> Result { + if dimensionX == 0 || dimensionY == 0 { + return Err(Exceptions::NotFoundException( + "getNotFoundInstance".to_owned(), + )); + } + let mut bits = BitMatrix::new(dimensionX, dimensionY)?; + let mut points = vec![0_f32; 2 * dimensionX as usize]; + for y in 0..dimensionY { + // for (int y = 0; y < dimensionY; y++) { + let max = points.len(); + let i_value = y as f32 + 0.5f32; + let mut x = 0; + while x < max { + // for (int x = 0; x < max; x += 2) { + points[x] = (x as f32 / 2.0) + 0.5f32; + points[x + 1] = i_value; + x += 2; + } + transform.transform_points_single(&mut points); + // Quick check to see if points transformed to something inside the image; + // sufficient to check the endpoints + self.checkAndNudgePoints(image, &mut points)?; + // try { + let mut x = 0; + while x < max { + // for (int x = 0; x < max; x += 2) { + if points[x].floor() as u32 >= image.getWidth() + || points[x + 1].floor() as u32 >= image.getHeight() + { + return Err(Exceptions::NotFoundException( + "index out of bounds, see documentation in file for explanation".to_owned(), + )); + } + if image.get(points[x].floor() as u32, points[x + 1].floor() as u32) { + // Black(-ish) pixel + bits.set(x as u32 / 2, y); + } + x += 2; + } + // } catch (ArrayIndexOutOfBoundsException aioobe) { + // // This feels wrong, but, sometimes if the finder patterns are misidentified, the resulting + // // transform gets "twisted" such that it maps a straight line of points to a set of points + // // whose endpoints are in bounds, but others are not. There is probably some mathematical + // // way to detect this about the transformation that I don't know yet. + // // This results in an ugly runtime exception despite our clever checks above -- can't have + // // that. We could check each point's coordinates but that feels duplicative. We settle for + // // catching and wrapping ArrayIndexOutOfBoundsException. + // throw NotFoundException.getNotFoundInstance(); + // } + } + return Ok(bits); + } +} \ No newline at end of file diff --git a/src/common/detector/mod.rs b/src/common/detector/mod.rs index cd93b16..7deada3 100644 --- a/src/common/detector/mod.rs +++ b/src/common/detector/mod.rs @@ -2,697 +2,8 @@ pub mod MathUtils; use crate::common::BitMatrix; use crate::{Exceptions, RXingResultPoint, ResultPoint}; -/* - * Copyright 2009 ZXing authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +mod monochrome_rectangle_detector; +pub use monochrome_rectangle_detector::*; -//package com.google.zxing.common.detector; - -/** - *

A somewhat generic detector that looks for a barcode-like rectangular region within an image. - * It looks within a mostly white region of an image for a region of black and white, but mostly - * black. It returns the four corners of the region, as best it can determine.

- * - * @author Sean Owen - * @deprecated without replacement since 3.3.0 - */ -const MAX_MODULES: i32 = 32; -#[deprecated] -pub struct MonochromeRectangleDetector { - image: BitMatrix, -} - -impl MonochromeRectangleDetector { - pub fn new(image: &BitMatrix) -> Self { - Self { image: image.clone() } - } - - /** - *

Detects a rectangular region of black and white -- mostly black -- with a region of mostly - * white, in an image.

- * - * @return {@link RXingResultPoint}[] describing the corners of the rectangular region. The first and - * last points are opposed on the diagonal, as are the second and third. The first point will be - * the topmost point and the last, the bottommost. The second point will be leftmost and the - * third, the rightmost - * @throws NotFoundException if no Data Matrix Code can be found - */ - pub fn detect(&self) -> Result, Exceptions> { - let height = self.image.getHeight() as i32; - let width = self.image.getWidth() as i32; - let halfHeight= height / 2; - let halfWidth = width / 2; - let deltaY = 1.max(height as i32 / (MAX_MODULES * 8)); - let deltaX = 1.max(width as i32 / (MAX_MODULES * 8)); - - let mut top = 0; - let mut bottom = height; - let mut left = 0; - let mut right = width; - let mut pointA = self.findCornerFromCenter( - halfWidth, - 0, - left, - right, - halfHeight, - -deltaY, - top, - bottom, - halfWidth / 2, - )?; - top = (pointA.getY() - 1f32) as i32; - let pointB = self.findCornerFromCenter( - halfWidth, - -deltaX, - left, - right, - halfHeight, - 0, - top, - bottom, - halfHeight / 2, - )?; - left = (pointB.getX() - 1f32) as i32; - let pointC = self.findCornerFromCenter( - halfWidth, - deltaX, - left, - right, - halfHeight, - 0, - top, - bottom, - halfHeight / 2, - )?; - right = (pointC.getX() + 1f32) as i32; - let pointD = self.findCornerFromCenter( - halfWidth, - 0, - left, - right, - halfHeight, - deltaY, - top, - bottom, - halfWidth / 2, - )?; - bottom = (pointD.getY() + 1f32) as i32; - - // Go try to find point A again with better information -- might have been off at first. - pointA = self.findCornerFromCenter( - halfWidth, - 0, - left, - right, - halfHeight, - -deltaY, - top, - bottom, - halfWidth / 4, - )?; - - return Ok(vec![pointA, pointB, pointC, pointD]); - } - - /** - * Attempts to locate a corner of the barcode by scanning up, down, left or right from a center - * point which should be within the barcode. - * - * @param centerX center's x component (horizontal) - * @param deltaX same as deltaY but change in x per step instead - * @param left minimum value of x - * @param right maximum value of x - * @param centerY center's y component (vertical) - * @param deltaY change in y per step. If scanning up this is negative; down, positive; - * left or right, 0 - * @param top minimum value of y to search through (meaningless when di == 0) - * @param bottom maximum value of y - * @param maxWhiteRun maximum run of white pixels that can still be considered to be within - * the barcode - * @return a {@link RXingResultPoint} encapsulating the corner that was found - * @throws NotFoundException if such a point cannot be found - */ - fn findCornerFromCenter( - &self, - centerX: i32, - deltaX: i32, - left: i32, - right: i32, - centerY: i32, - deltaY: i32, - top: i32, - bottom: i32, - maxWhiteRun: i32, - ) -> Result { - let mut lastRange_z: Option> = None; - let mut y: i32 = centerY; - let mut x: i32 = centerX; - while y < bottom && y >= top && x < right && x >= left { - let range: Option>; - if deltaX == 0 { - // horizontal slices, up and down - range = self.blackWhiteRange(y, maxWhiteRun, left, right, true); - } else { - // vertical slices, left and right - range = self.blackWhiteRange(x, maxWhiteRun, top, bottom, false); - } - if range.is_none() { - if let Some(lastRange) = lastRange_z { - // lastRange was found - if deltaX == 0 { - let lastY = y - deltaY; - if lastRange[0] < centerX { - if lastRange[1] > centerX { - // straddle, choose one or the other based on direction - return Ok(RXingResultPoint::new( - lastRange[if deltaY > 0 { 0 } else { 1 }] as f32, - lastY as f32, - )); - } - return Ok(RXingResultPoint::new( - lastRange[0] as f32, - lastY as f32, - )); - } else { - return Ok(RXingResultPoint::new( - lastRange[1] as f32, - lastY as f32, - )); - } - } else { - let lastX = x - deltaX; - if lastRange[0] < centerY { - if lastRange[1] > centerY { - return Ok(RXingResultPoint::new( - lastX as f32, - lastRange[if deltaX < 0 { 0 } else { 1 }] as f32, - )); - } - return Ok(RXingResultPoint::new( - lastX as f32, - lastRange[0] as f32, - )); - } else { - return Ok(RXingResultPoint::new( - lastX as f32, - lastRange[1] as f32, - )); - } - } - }}else { - return Err(Exceptions::NotFoundException("".to_owned())); - } - lastRange_z = range; - y += deltaY; - x += deltaX - } - return Err(Exceptions::NotFoundException("".to_owned())); - } - - /** - * Computes the start and end of a region of pixels, either horizontally or vertically, that could - * be part of a Data Matrix barcode. - * - * @param fixedDimension if scanning horizontally, this is the row (the fixed vertical location) - * where we are scanning. If scanning vertically it's the column, the fixed horizontal location - * @param maxWhiteRun largest run of white pixels that can still be considered part of the - * barcode region - * @param minDim minimum pixel location, horizontally or vertically, to consider - * @param maxDim maximum pixel location, horizontally or vertically, to consider - * @param horizontal if true, we're scanning left-right, instead of up-down - * @return int[] with start and end of found range, or null if no such range is found - * (e.g. only white was found) - */ - fn blackWhiteRange( - &self, - fixedDimension: i32, - maxWhiteRun: i32, - minDim: i32, - maxDim: i32, - horizontal: bool, - ) -> Option> { - let center = (minDim + maxDim) / 2; - - // Scan left/up first - let mut start = center; - while (start >= minDim) { - if if horizontal { - self.image.get(start as u32, fixedDimension as u32) - } else { - self.image.get(fixedDimension as u32, start as u32) - } { - start = start - 1; - } else { - let whiteRunStart = start; - start = start - 1; - while start >= minDim - && !(if horizontal { - self.image.get(start as u32, fixedDimension as u32) - } else { - self.image.get(fixedDimension as u32, start as u32) - }) - { - start = start - 1; - } - let whiteRunSize = whiteRunStart - start; - if start < minDim || whiteRunSize > maxWhiteRun { - start = whiteRunStart; - break; - } - } - } - start = start + 1; - - // Then try right/down - let mut end = center; - while (end < maxDim) { - if if horizontal { - self.image.get(end as u32, fixedDimension as u32) - } else { - self.image.get(fixedDimension as u32, end as u32) - } { - end = end + 1; - } else { - let whiteRunStart = end; - end = end + 1; - while end < maxDim - && !(if horizontal { - self.image.get(end as u32, fixedDimension as u32) - } else { - self.image.get(fixedDimension as u32, end as u32) - }) - { - end = end + 1; - } - let whiteRunSize = end - whiteRunStart; - if end >= maxDim || whiteRunSize > maxWhiteRun { - end = whiteRunStart; - break; - } - } - } - end = end - 1; - - return if end > start { - Some(vec![start, end]) - } else { - None - }; - } -} - -/* - * Copyright 2010 ZXing authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -//package com.google.zxing.common.detector; - -/** - *

- * Detects a candidate barcode-like rectangular region within an image. It - * starts around the center of the image, increases the size of the candidate - * region until it finds a white rectangular region. By keeping track of the - * last black points it encountered, it determines the corners of the barcode. - *

- * - * @author David Olivier - */ -const INIT_SIZE: i32 = 10; -const CORR: i32 = 1; -pub struct WhiteRectangleDetector { - image: BitMatrix, - height: i32, - width: i32, - leftInit: i32, - rightInit: i32, - downInit: i32, - upInit: i32, -} - -impl WhiteRectangleDetector { - pub fn new_from_image(image: &BitMatrix) -> Result { - Self::new( - image, - INIT_SIZE, - image.getWidth() as i32 / 2, - image.getHeight() as i32 / 2, - ) - } - - /** - * @param image barcode image to find a rectangle in - * @param initSize initial size of search area around center - * @param x x position of search center - * @param y y position of search center - * @throws NotFoundException if image is too small to accommodate {@code initSize} - */ - pub fn new( - image: &BitMatrix, - initSize: i32, - x: i32, - y: i32, - ) -> Result { - - let halfsize = initSize / 2; - - let leftInit = x - halfsize; - let rightInit = x + halfsize; - let upInit = y - halfsize; - let downInit = y + halfsize; - - if upInit < 0 - || leftInit < 0 - || downInit >= image.getHeight() as i32 - || rightInit >= image.getWidth() as i32 - { - return Err(Exceptions::NotFoundException("".to_owned())); - } - - Ok(Self{ - image: image.clone(), - height: image.getHeight() as i32, - width: image.getWidth() as i32, - leftInit: leftInit, - rightInit: rightInit, - downInit: downInit, - upInit: upInit, - }) - } - - /** - *

- * Detects a candidate barcode-like rectangular region within an image. It - * starts around the center of the image, increases the size of the candidate - * region until it finds a white rectangular region. - *

- * - * @return {@link RXingResultPoint}[] describing the corners of the rectangular - * region. The first and last points are opposed on the diagonal, as - * are the second and third. The first point will be the topmost - * point and the last, the bottommost. The second point will be - * leftmost and the third, the rightmost - * @throws NotFoundException if no Data Matrix Code can be found - */ - pub fn detect(&self) -> Result, Exceptions> { - let mut left: i32 = self.leftInit; - let mut right: i32 = self.rightInit; - let mut up: i32 = self.upInit; - let mut down: i32 = self.downInit; - let mut size_exceeded = false; - let mut a_black_point_found_on_border = true; - - let mut at_least_one_black_point_found_on_right = false; - let mut at_least_one_black_point_found_on_bottom = false; - let mut at_least_one_black_point_found_on_left = false; - let mut at_least_one_black_point_found_on_top = false; - - while a_black_point_found_on_border { - a_black_point_found_on_border = false; - - // ..... - // . | - // ..... - let mut right_border_not_white = true; - while (right_border_not_white || !at_least_one_black_point_found_on_right) && right < self.width { - right_border_not_white = self.contains_black_point(up, down, right, false); - if right_border_not_white { - right += 1; - a_black_point_found_on_border = true; - at_least_one_black_point_found_on_right = true; - } else if !at_least_one_black_point_found_on_right { - right += 1; - } - } - - if right >= self.width { - size_exceeded = true; - break; - } - - // ..... - // . . - // .___. - let mut bottom_border_not_white = true; - while (bottom_border_not_white || !at_least_one_black_point_found_on_bottom) && down < self.height - { - bottom_border_not_white = self.contains_black_point(left, right, down, true); - if bottom_border_not_white { - down += 1; - a_black_point_found_on_border = true; - at_least_one_black_point_found_on_bottom = true; - } else if !at_least_one_black_point_found_on_bottom { - down += 1; - } - } - - if down >= self.height { - size_exceeded = true; - break; - } - - // ..... - // | . - // ..... - let mut left_border_not_white = true; - while (left_border_not_white || !at_least_one_black_point_found_on_left) && left >= 0 { - left_border_not_white = self.contains_black_point(up, down, left, false); - if left_border_not_white { - left -= 1; - a_black_point_found_on_border = true; - at_least_one_black_point_found_on_left = true; - } else if !at_least_one_black_point_found_on_left { - left -= 1; - } - } - - if left < 0 { - size_exceeded = true; - break; - } - - // .___. - // . . - // ..... - let mut top_border_not_white = true; - while (top_border_not_white || !at_least_one_black_point_found_on_top) && up >= 0 { - top_border_not_white = self.contains_black_point(left, right, up, true); - if top_border_not_white { - up -= 1; - a_black_point_found_on_border = true; - at_least_one_black_point_found_on_top = true; - } else if !at_least_one_black_point_found_on_top { - up -= 1; - } - } - - if up < 0 { - size_exceeded = true; - break; - } - } - - if !size_exceeded { - let max_size = right - left; - - let mut z: Option = None; - let mut i = 1; - while z.is_none() && i < max_size { - //for (int i = 1; z == null && i < maxSize; i++) { - z = self.get_black_point_on_segment( - left as f32, - (down - i) as f32, - (left + i) as f32, - down as f32, - ); - i += 1; - } - - if z.is_none() { - return Err(Exceptions::NotFoundException("".to_owned())); - } - - let mut t: Option = None; - //go down right - let mut i = 1; - while t.is_none() && i < max_size { - //for (int i = 1; t == null && i < maxSize; i++) { - t = self.get_black_point_on_segment( - left as f32, - (up + i) as f32, - (left + i) as f32, - up as f32, - ); - i += 1; - } - - if t.is_none() { - return Err(Exceptions::NotFoundException("".to_owned())); - } - - let mut x: Option = None; - //go down left - let mut i = 1; - while x.is_none() && i < max_size { - //for (int i = 1; x == null && i < maxSize; i++) { - x = self.get_black_point_on_segment( - right as f32, - (up + i) as f32, - (right - i) as f32, - up as f32, - ); - i += 1; - } - - if x.is_none() { - return Err(Exceptions::NotFoundException("".to_owned())); - } - - let mut y: Option = None; - //go up left - let mut i = 1; - while y.is_none() && i < max_size { - //for (int i = 1; y == null && i < maxSize; i++) { - y = self.get_black_point_on_segment( - right as f32, - (down - i) as f32, - (right - i) as f32, - down as f32, - ); - i += 1; - } - - if y.is_none() { - return Err(Exceptions::NotFoundException("".to_owned())); - } - - return Ok(self.center_edges(&y.unwrap(), &z.unwrap(), &x.unwrap(), &t.unwrap())); - } else { - return Err(Exceptions::NotFoundException("".to_owned())); - } - } - - fn get_black_point_on_segment( - &self, - a_x: f32, - a_y: f32, - b_x: f32, - b_y: f32, - ) -> Option { - let dist = MathUtils::round(MathUtils::distance_float(a_x, a_y, b_x, b_y)); - let x_step: f32 = (b_x - a_x) / dist as f32; - let y_step: f32 = (b_y - a_y) / dist as f32; - - for i in 0..dist { - let x = MathUtils::round(a_x + i as f32 * x_step); - let y = MathUtils::round(a_y + i as f32 * y_step); - if self.image.get(x as u32, y as u32) { - return Some(RXingResultPoint::new(x as f32, y as f32)); - } - } - return None; - } - - /** - * recenters the points of a constant distance towards the center - * - * @param y bottom most point - * @param z left most point - * @param x right most point - * @param t top most point - * @return {@link RXingResultPoint}[] describing the corners of the rectangular - * region. The first and last points are opposed on the diagonal, as - * are the second and third. The first point will be the topmost - * point and the last, the bottommost. The second point will be - * leftmost and the third, the rightmost - */ - fn center_edges( - &self, - y: &RXingResultPoint, - z: &RXingResultPoint, - x: &RXingResultPoint, - t: &RXingResultPoint, - ) -> Vec { - // - // t t - // z x - // x OR z - // y y - // - - let yi = y.getX(); - let yj = y.getY(); - let zi = z.getX(); - let zj = z.getY(); - let xi = x.getX(); - let xj = x.getY(); - let ti = t.getX(); - let tj = t.getY(); - - if yi < self.width as f32 / 2.0f32 { - return vec![ - RXingResultPoint::new(ti - CORR as f32, tj + CORR as f32), - RXingResultPoint::new(zi + CORR as f32, zj + CORR as f32), - RXingResultPoint::new(xi - CORR as f32, xj - CORR as f32), - RXingResultPoint::new(yi + CORR as f32, yj - CORR as f32), - ]; - } else { - return vec![ - RXingResultPoint::new(ti + CORR as f32, tj + CORR as f32), - RXingResultPoint::new(zi + CORR as f32, zj - CORR as f32), - RXingResultPoint::new(xi - CORR as f32, xj + CORR as f32), - RXingResultPoint::new(yi - CORR as f32, yj - CORR as f32), - ]; - } - } - - /** - * Determines whether a segment contains a black point - * - * @param a min value of the scanned coordinate - * @param b max value of the scanned coordinate - * @param fixed value of fixed coordinate - * @param horizontal set to true if scan must be horizontal, false if vertical - * @return true if a black point has been found, else false. - */ - fn contains_black_point(&self, a: i32, b: i32, fixed: i32, horizontal: bool) -> bool { - if horizontal { - for x in a..=b { - if self.image.get(x as u32, fixed as u32) { - return true; - } - } - } else { - for y in a..=b { - if self.image.get(fixed as u32, y as u32) { - return true; - } - } - } - - return false; - } -} +mod white_rectangle_detector; +pub use white_rectangle_detector::*; \ No newline at end of file diff --git a/src/common/detector/monochrome_rectangle_detector.rs b/src/common/detector/monochrome_rectangle_detector.rs new file mode 100644 index 0000000..d856e9f --- /dev/null +++ b/src/common/detector/monochrome_rectangle_detector.rs @@ -0,0 +1,312 @@ +/* + * Copyright 2009 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//package com.google.zxing.common.detector; + +use crate::{Exceptions, RXingResultPoint, common::BitMatrix, ResultPoint}; + +/** + *

A somewhat generic detector that looks for a barcode-like rectangular region within an image. + * It looks within a mostly white region of an image for a region of black and white, but mostly + * black. It returns the four corners of the region, as best it can determine.

+ * + * @author Sean Owen + * @deprecated without replacement since 3.3.0 + */ +const MAX_MODULES: i32 = 32; +#[deprecated] +pub struct MonochromeRectangleDetector { + image: BitMatrix, +} + +impl MonochromeRectangleDetector { + pub fn new(image: &BitMatrix) -> Self { + Self { image: image.clone() } + } + + /** + *

Detects a rectangular region of black and white -- mostly black -- with a region of mostly + * white, in an image.

+ * + * @return {@link RXingResultPoint}[] describing the corners of the rectangular region. The first and + * last points are opposed on the diagonal, as are the second and third. The first point will be + * the topmost point and the last, the bottommost. The second point will be leftmost and the + * third, the rightmost + * @throws NotFoundException if no Data Matrix Code can be found + */ + pub fn detect(&self) -> Result, Exceptions> { + let height = self.image.getHeight() as i32; + let width = self.image.getWidth() as i32; + let halfHeight= height / 2; + let halfWidth = width / 2; + let deltaY = 1.max(height as i32 / (MAX_MODULES * 8)); + let deltaX = 1.max(width as i32 / (MAX_MODULES * 8)); + + let mut top = 0; + let mut bottom = height; + let mut left = 0; + let mut right = width; + let mut pointA = self.findCornerFromCenter( + halfWidth, + 0, + left, + right, + halfHeight, + -deltaY, + top, + bottom, + halfWidth / 2, + )?; + top = (pointA.getY() - 1f32) as i32; + let pointB = self.findCornerFromCenter( + halfWidth, + -deltaX, + left, + right, + halfHeight, + 0, + top, + bottom, + halfHeight / 2, + )?; + left = (pointB.getX() - 1f32) as i32; + let pointC = self.findCornerFromCenter( + halfWidth, + deltaX, + left, + right, + halfHeight, + 0, + top, + bottom, + halfHeight / 2, + )?; + right = (pointC.getX() + 1f32) as i32; + let pointD = self.findCornerFromCenter( + halfWidth, + 0, + left, + right, + halfHeight, + deltaY, + top, + bottom, + halfWidth / 2, + )?; + bottom = (pointD.getY() + 1f32) as i32; + + // Go try to find point A again with better information -- might have been off at first. + pointA = self.findCornerFromCenter( + halfWidth, + 0, + left, + right, + halfHeight, + -deltaY, + top, + bottom, + halfWidth / 4, + )?; + + return Ok(vec![pointA, pointB, pointC, pointD]); + } + + /** + * Attempts to locate a corner of the barcode by scanning up, down, left or right from a center + * point which should be within the barcode. + * + * @param centerX center's x component (horizontal) + * @param deltaX same as deltaY but change in x per step instead + * @param left minimum value of x + * @param right maximum value of x + * @param centerY center's y component (vertical) + * @param deltaY change in y per step. If scanning up this is negative; down, positive; + * left or right, 0 + * @param top minimum value of y to search through (meaningless when di == 0) + * @param bottom maximum value of y + * @param maxWhiteRun maximum run of white pixels that can still be considered to be within + * the barcode + * @return a {@link RXingResultPoint} encapsulating the corner that was found + * @throws NotFoundException if such a point cannot be found + */ + fn findCornerFromCenter( + &self, + centerX: i32, + deltaX: i32, + left: i32, + right: i32, + centerY: i32, + deltaY: i32, + top: i32, + bottom: i32, + maxWhiteRun: i32, + ) -> Result { + let mut lastRange_z: Option> = None; + let mut y: i32 = centerY; + let mut x: i32 = centerX; + while y < bottom && y >= top && x < right && x >= left { + let range: Option>; + if deltaX == 0 { + // horizontal slices, up and down + range = self.blackWhiteRange(y, maxWhiteRun, left, right, true); + } else { + // vertical slices, left and right + range = self.blackWhiteRange(x, maxWhiteRun, top, bottom, false); + } + if range.is_none() { + if let Some(lastRange) = lastRange_z { + // lastRange was found + if deltaX == 0 { + let lastY = y - deltaY; + if lastRange[0] < centerX { + if lastRange[1] > centerX { + // straddle, choose one or the other based on direction + return Ok(RXingResultPoint::new( + lastRange[if deltaY > 0 { 0 } else { 1 }] as f32, + lastY as f32, + )); + } + return Ok(RXingResultPoint::new( + lastRange[0] as f32, + lastY as f32, + )); + } else { + return Ok(RXingResultPoint::new( + lastRange[1] as f32, + lastY as f32, + )); + } + } else { + let lastX = x - deltaX; + if lastRange[0] < centerY { + if lastRange[1] > centerY { + return Ok(RXingResultPoint::new( + lastX as f32, + lastRange[if deltaX < 0 { 0 } else { 1 }] as f32, + )); + } + return Ok(RXingResultPoint::new( + lastX as f32, + lastRange[0] as f32, + )); + } else { + return Ok(RXingResultPoint::new( + lastX as f32, + lastRange[1] as f32, + )); + } + } + }}else { + return Err(Exceptions::NotFoundException("".to_owned())); + } + lastRange_z = range; + y += deltaY; + x += deltaX + } + return Err(Exceptions::NotFoundException("".to_owned())); + } + + /** + * Computes the start and end of a region of pixels, either horizontally or vertically, that could + * be part of a Data Matrix barcode. + * + * @param fixedDimension if scanning horizontally, this is the row (the fixed vertical location) + * where we are scanning. If scanning vertically it's the column, the fixed horizontal location + * @param maxWhiteRun largest run of white pixels that can still be considered part of the + * barcode region + * @param minDim minimum pixel location, horizontally or vertically, to consider + * @param maxDim maximum pixel location, horizontally or vertically, to consider + * @param horizontal if true, we're scanning left-right, instead of up-down + * @return int[] with start and end of found range, or null if no such range is found + * (e.g. only white was found) + */ + fn blackWhiteRange( + &self, + fixedDimension: i32, + maxWhiteRun: i32, + minDim: i32, + maxDim: i32, + horizontal: bool, + ) -> Option> { + let center = (minDim + maxDim) / 2; + + // Scan left/up first + let mut start = center; + while (start >= minDim) { + if if horizontal { + self.image.get(start as u32, fixedDimension as u32) + } else { + self.image.get(fixedDimension as u32, start as u32) + } { + start = start - 1; + } else { + let whiteRunStart = start; + start = start - 1; + while start >= minDim + && !(if horizontal { + self.image.get(start as u32, fixedDimension as u32) + } else { + self.image.get(fixedDimension as u32, start as u32) + }) + { + start = start - 1; + } + let whiteRunSize = whiteRunStart - start; + if start < minDim || whiteRunSize > maxWhiteRun { + start = whiteRunStart; + break; + } + } + } + start = start + 1; + + // Then try right/down + let mut end = center; + while (end < maxDim) { + if if horizontal { + self.image.get(end as u32, fixedDimension as u32) + } else { + self.image.get(fixedDimension as u32, end as u32) + } { + end = end + 1; + } else { + let whiteRunStart = end; + end = end + 1; + while end < maxDim + && !(if horizontal { + self.image.get(end as u32, fixedDimension as u32) + } else { + self.image.get(fixedDimension as u32, end as u32) + }) + { + end = end + 1; + } + let whiteRunSize = end - whiteRunStart; + if end >= maxDim || whiteRunSize > maxWhiteRun { + end = whiteRunStart; + break; + } + } + } + end = end - 1; + + return if end > start { + Some(vec![start, end]) + } else { + None + }; + } +} \ No newline at end of file diff --git a/src/common/detector/white_rectangle_detector.rs b/src/common/detector/white_rectangle_detector.rs new file mode 100644 index 0000000..f9cd1d9 --- /dev/null +++ b/src/common/detector/white_rectangle_detector.rs @@ -0,0 +1,387 @@ +/* + * Copyright 2010 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//package com.google.zxing.common.detector; + +use crate::{RXingResultPoint, Exceptions, common::BitMatrix, ResultPoint}; + +use super::MathUtils; + +/** + *

+ * Detects a candidate barcode-like rectangular region within an image. It + * starts around the center of the image, increases the size of the candidate + * region until it finds a white rectangular region. By keeping track of the + * last black points it encountered, it determines the corners of the barcode. + *

+ * + * @author David Olivier + */ +const INIT_SIZE: i32 = 10; +const CORR: i32 = 1; +pub struct WhiteRectangleDetector { + image: BitMatrix, + height: i32, + width: i32, + leftInit: i32, + rightInit: i32, + downInit: i32, + upInit: i32, +} + +impl WhiteRectangleDetector { + pub fn new_from_image(image: &BitMatrix) -> Result { + Self::new( + image, + INIT_SIZE, + image.getWidth() as i32 / 2, + image.getHeight() as i32 / 2, + ) + } + + /** + * @param image barcode image to find a rectangle in + * @param initSize initial size of search area around center + * @param x x position of search center + * @param y y position of search center + * @throws NotFoundException if image is too small to accommodate {@code initSize} + */ + pub fn new( + image: &BitMatrix, + initSize: i32, + x: i32, + y: i32, + ) -> Result { + + let halfsize = initSize / 2; + + let leftInit = x - halfsize; + let rightInit = x + halfsize; + let upInit = y - halfsize; + let downInit = y + halfsize; + + if upInit < 0 + || leftInit < 0 + || downInit >= image.getHeight() as i32 + || rightInit >= image.getWidth() as i32 + { + return Err(Exceptions::NotFoundException("".to_owned())); + } + + Ok(Self{ + image: image.clone(), + height: image.getHeight() as i32, + width: image.getWidth() as i32, + leftInit: leftInit, + rightInit: rightInit, + downInit: downInit, + upInit: upInit, + }) + } + + /** + *

+ * Detects a candidate barcode-like rectangular region within an image. It + * starts around the center of the image, increases the size of the candidate + * region until it finds a white rectangular region. + *

+ * + * @return {@link RXingResultPoint}[] describing the corners of the rectangular + * region. The first and last points are opposed on the diagonal, as + * are the second and third. The first point will be the topmost + * point and the last, the bottommost. The second point will be + * leftmost and the third, the rightmost + * @throws NotFoundException if no Data Matrix Code can be found + */ + pub fn detect(&self) -> Result, Exceptions> { + let mut left: i32 = self.leftInit; + let mut right: i32 = self.rightInit; + let mut up: i32 = self.upInit; + let mut down: i32 = self.downInit; + let mut size_exceeded = false; + let mut a_black_point_found_on_border = true; + + let mut at_least_one_black_point_found_on_right = false; + let mut at_least_one_black_point_found_on_bottom = false; + let mut at_least_one_black_point_found_on_left = false; + let mut at_least_one_black_point_found_on_top = false; + + while a_black_point_found_on_border { + a_black_point_found_on_border = false; + + // ..... + // . | + // ..... + let mut right_border_not_white = true; + while (right_border_not_white || !at_least_one_black_point_found_on_right) && right < self.width { + right_border_not_white = self.contains_black_point(up, down, right, false); + if right_border_not_white { + right += 1; + a_black_point_found_on_border = true; + at_least_one_black_point_found_on_right = true; + } else if !at_least_one_black_point_found_on_right { + right += 1; + } + } + + if right >= self.width { + size_exceeded = true; + break; + } + + // ..... + // . . + // .___. + let mut bottom_border_not_white = true; + while (bottom_border_not_white || !at_least_one_black_point_found_on_bottom) && down < self.height + { + bottom_border_not_white = self.contains_black_point(left, right, down, true); + if bottom_border_not_white { + down += 1; + a_black_point_found_on_border = true; + at_least_one_black_point_found_on_bottom = true; + } else if !at_least_one_black_point_found_on_bottom { + down += 1; + } + } + + if down >= self.height { + size_exceeded = true; + break; + } + + // ..... + // | . + // ..... + let mut left_border_not_white = true; + while (left_border_not_white || !at_least_one_black_point_found_on_left) && left >= 0 { + left_border_not_white = self.contains_black_point(up, down, left, false); + if left_border_not_white { + left -= 1; + a_black_point_found_on_border = true; + at_least_one_black_point_found_on_left = true; + } else if !at_least_one_black_point_found_on_left { + left -= 1; + } + } + + if left < 0 { + size_exceeded = true; + break; + } + + // .___. + // . . + // ..... + let mut top_border_not_white = true; + while (top_border_not_white || !at_least_one_black_point_found_on_top) && up >= 0 { + top_border_not_white = self.contains_black_point(left, right, up, true); + if top_border_not_white { + up -= 1; + a_black_point_found_on_border = true; + at_least_one_black_point_found_on_top = true; + } else if !at_least_one_black_point_found_on_top { + up -= 1; + } + } + + if up < 0 { + size_exceeded = true; + break; + } + } + + if !size_exceeded { + let max_size = right - left; + + let mut z: Option = None; + let mut i = 1; + while z.is_none() && i < max_size { + //for (int i = 1; z == null && i < maxSize; i++) { + z = self.get_black_point_on_segment( + left as f32, + (down - i) as f32, + (left + i) as f32, + down as f32, + ); + i += 1; + } + + if z.is_none() { + return Err(Exceptions::NotFoundException("".to_owned())); + } + + let mut t: Option = None; + //go down right + let mut i = 1; + while t.is_none() && i < max_size { + //for (int i = 1; t == null && i < maxSize; i++) { + t = self.get_black_point_on_segment( + left as f32, + (up + i) as f32, + (left + i) as f32, + up as f32, + ); + i += 1; + } + + if t.is_none() { + return Err(Exceptions::NotFoundException("".to_owned())); + } + + let mut x: Option = None; + //go down left + let mut i = 1; + while x.is_none() && i < max_size { + //for (int i = 1; x == null && i < maxSize; i++) { + x = self.get_black_point_on_segment( + right as f32, + (up + i) as f32, + (right - i) as f32, + up as f32, + ); + i += 1; + } + + if x.is_none() { + return Err(Exceptions::NotFoundException("".to_owned())); + } + + let mut y: Option = None; + //go up left + let mut i = 1; + while y.is_none() && i < max_size { + //for (int i = 1; y == null && i < maxSize; i++) { + y = self.get_black_point_on_segment( + right as f32, + (down - i) as f32, + (right - i) as f32, + down as f32, + ); + i += 1; + } + + if y.is_none() { + return Err(Exceptions::NotFoundException("".to_owned())); + } + + return Ok(self.center_edges(&y.unwrap(), &z.unwrap(), &x.unwrap(), &t.unwrap())); + } else { + return Err(Exceptions::NotFoundException("".to_owned())); + } + } + + fn get_black_point_on_segment( + &self, + a_x: f32, + a_y: f32, + b_x: f32, + b_y: f32, + ) -> Option { + let dist = MathUtils::round(MathUtils::distance_float(a_x, a_y, b_x, b_y)); + let x_step: f32 = (b_x - a_x) / dist as f32; + let y_step: f32 = (b_y - a_y) / dist as f32; + + for i in 0..dist { + let x = MathUtils::round(a_x + i as f32 * x_step); + let y = MathUtils::round(a_y + i as f32 * y_step); + if self.image.get(x as u32, y as u32) { + return Some(RXingResultPoint::new(x as f32, y as f32)); + } + } + return None; + } + + /** + * recenters the points of a constant distance towards the center + * + * @param y bottom most point + * @param z left most point + * @param x right most point + * @param t top most point + * @return {@link RXingResultPoint}[] describing the corners of the rectangular + * region. The first and last points are opposed on the diagonal, as + * are the second and third. The first point will be the topmost + * point and the last, the bottommost. The second point will be + * leftmost and the third, the rightmost + */ + fn center_edges( + &self, + y: &RXingResultPoint, + z: &RXingResultPoint, + x: &RXingResultPoint, + t: &RXingResultPoint, + ) -> Vec { + // + // t t + // z x + // x OR z + // y y + // + + let yi = y.getX(); + let yj = y.getY(); + let zi = z.getX(); + let zj = z.getY(); + let xi = x.getX(); + let xj = x.getY(); + let ti = t.getX(); + let tj = t.getY(); + + if yi < self.width as f32 / 2.0f32 { + return vec![ + RXingResultPoint::new(ti - CORR as f32, tj + CORR as f32), + RXingResultPoint::new(zi + CORR as f32, zj + CORR as f32), + RXingResultPoint::new(xi - CORR as f32, xj - CORR as f32), + RXingResultPoint::new(yi + CORR as f32, yj - CORR as f32), + ]; + } else { + return vec![ + RXingResultPoint::new(ti + CORR as f32, tj + CORR as f32), + RXingResultPoint::new(zi + CORR as f32, zj - CORR as f32), + RXingResultPoint::new(xi - CORR as f32, xj + CORR as f32), + RXingResultPoint::new(yi - CORR as f32, yj - CORR as f32), + ]; + } + } + + /** + * Determines whether a segment contains a black point + * + * @param a min value of the scanned coordinate + * @param b max value of the scanned coordinate + * @param fixed value of fixed coordinate + * @param horizontal set to true if scan must be horizontal, false if vertical + * @return true if a black point has been found, else false. + */ + fn contains_black_point(&self, a: i32, b: i32, fixed: i32, horizontal: bool) -> bool { + if horizontal { + for x in a..=b { + if self.image.get(x as u32, fixed as u32) { + return true; + } + } + } else { + for y in a..=b { + if self.image.get(fixed as u32, y as u32) { + return true; + } + } + } + + return false; + } +} diff --git a/src/common/eci_encoder_set.rs b/src/common/eci_encoder_set.rs new file mode 100644 index 0000000..3c10cce --- /dev/null +++ b/src/common/eci_encoder_set.rs @@ -0,0 +1,254 @@ + +/* + * Copyright 2021 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// package com.google.zxing.common; + +// import java.nio.charset.Charset; +// import java.nio.charset.CharsetEncoder; +// import java.nio.charset.StandardCharsets; +// import java.nio.charset.UnsupportedCharsetException; +// import java.util.ArrayList; +// import java.util.List; + +use encoding::{EncodingRef, Encoding}; +use unicode_segmentation::UnicodeSegmentation; + +use super::CharacterSetECI; + +use lazy_static::lazy_static; + +lazy_static! { + static ref ENCODERS : Vec = { + let mut enc_vec = Vec::new(); + for name in NAMES { + if let Some(enc) = CharacterSetECI::getCharacterSetECIByName(name) { + // try { + enc_vec.push(CharacterSetECI::getCharset(&enc)); + // } catch (UnsupportedCharsetException e) { + // continue + // } + } + } + enc_vec + }; +} +const NAMES: [&str; 20] = [ + "IBM437", + "ISO-8859-2", + "ISO-8859-3", + "ISO-8859-4", + "ISO-8859-5", + "ISO-8859-6", + "ISO-8859-7", + "ISO-8859-8", + "ISO-8859-9", + "ISO-8859-10", + "ISO-8859-11", + "ISO-8859-13", + "ISO-8859-14", + "ISO-8859-15", + "ISO-8859-16", + "windows-1250", + "windows-1251", + "windows-1252", + "windows-1256", + "Shift_JIS", +]; + +/** + * Set of CharsetEncoders for a given input string + * + * Invariants: + * - The list contains only encoders from CharacterSetECI (list is shorter then the list of encoders available on + * the platform for which ECI values are defined). + * - The list contains encoders at least one encoder for every character in the input. + * - The first encoder in the list is always the ISO-8859-1 encoder even of no character in the input can be encoded + * by it. + * - If the input contains a character that is not in ISO-8859-1 then the last two entries in the list will be the + * UTF-8 encoder and the UTF-16BE encoder. + * + * @author Alex Geller + */ +#[derive(Clone)] +pub struct ECIEncoderSet { + encoders: Vec, + priorityEncoderIndex: Option, +} + +impl ECIEncoderSet { + /** + * Constructs an encoder set + * + * @param stringToEncode the string that needs to be encoded + * @param priorityCharset The preferred {@link Charset} or null. + * @param fnc1 fnc1 denotes the character in the input that represents the FNC1 character or -1 for a non-GS1 bar + * code. When specified, it is considered an error to pass it as argument to the methods canEncode() or encode(). + */ + pub fn new( + stringToEncodeMain: &str, + priorityCharset: Option, + fnc1: Option<&str>, + ) -> Self { + // List of encoders that potentially encode characters not in ISO-8859-1 in one byte. + + let mut encoders: Vec; + let mut priorityEncoderIndexValue = None; + + let mut neededEncoders: Vec = Vec::new(); + + let stringToEncode = stringToEncodeMain.graphemes(true).collect::>(); + + //we always need the ISO-8859-1 encoder. It is the default encoding + neededEncoders.push(encoding::all::ISO_8859_1); + let mut needUnicodeEncoder = if let Some(pc) = priorityCharset { + pc.name().starts_with("UTF") + } else { + false + }; + + //Walk over the input string and see if all characters can be encoded with the list of encoders + for i in 0..stringToEncode.len() { + // for (int i = 0; i < stringToEncode.length(); i++) { + let mut canEncode = false; + for encoder in &neededEncoders { + // for (CharsetEncoder encoder : neededEncoders) { + let c = stringToEncode.get(i).unwrap(); + if (fnc1.is_some() && c == fnc1.as_ref().unwrap()) + || encoder.encode(c, encoding::EncoderTrap::Strict).is_ok() + { + canEncode = true; + break; + } + } + if !canEncode { + //for the character at position i we don't yet have an encoder in the list + for i_encoder in 0..ENCODERS.len() { + // for encoder in ENCODERS { + let encoder = ENCODERS.get(i_encoder).unwrap(); + // for (CharsetEncoder encoder : ENCODERS) { + if encoder + .encode( + &stringToEncode.get(i).unwrap(), + encoding::EncoderTrap::Strict, + ) + .is_ok() + { + //Good, we found an encoder that can encode the character. We add him to the list and continue scanning + //the input + neededEncoders.push(*encoder); + canEncode = true; + break; + } + } + } + + if !canEncode { + //The character is not encodeable by any of the single byte encoders so we remember that we will need a + //Unicode encoder. + needUnicodeEncoder = true; + } + } + + if neededEncoders.len() == 1 && !needUnicodeEncoder { + //the entire input can be encoded by the ISO-8859-1 encoder + encoders = vec![encoding::all::ISO_8859_1]; + } else { + // we need more than one single byte encoder or we need a Unicode encoder. + // In this case we append a UTF-8 and UTF-16 encoder to the list + // encoders = [] new CharsetEncoder[neededEncoders.size() + 2]; + encoders = Vec::new(); + let index = 0; + + for encoder in neededEncoders { + // for (CharsetEncoder encoder : neededEncoders) { + //encoders[index++] = encoder; + encoders.push(encoder); + } + + encoders.push(encoding::all::UTF_8); + encoders.push(encoding::all::UTF_16BE); + } + + //Compute priorityEncoderIndex by looking up priorityCharset in encoders + // if priorityCharset != null { + if priorityCharset.is_some() { + for i in 0..encoders.len() { + // for (int i = 0; i < encoders.length; i++) { + if priorityCharset.as_ref().unwrap().name() == encoders[i].name() { + priorityEncoderIndexValue = Some(i); + break; + } + } + } + // } + //invariants + assert_eq!(encoders[0].name(), encoding::all::ISO_8859_1.name()); + Self { + encoders: encoders, + priorityEncoderIndex: priorityEncoderIndexValue, + } + } + + pub fn len(&self) -> usize { + return self.encoders.len(); + } + + pub fn getCharsetName(&self, index: usize) -> &'static str { + assert!(index < self.len()); + return self.encoders[index].name(); + } + + pub fn getCharset(&self, index: usize) -> EncodingRef { + assert!(index < self.len()); + return self.encoders[index]; + } + + pub fn getECIValue(&self, encoderIndex: usize) -> u32 { + CharacterSetECI::getValue( + &CharacterSetECI::getCharacterSetECI(self.encoders[encoderIndex]).unwrap(), + ) + } + + /* + * returns -1 if no priority charset was defined + */ + pub fn getPriorityEncoderIndex(&self) -> Option { + self.priorityEncoderIndex + } + + pub fn canEncode(&self, c: &str, encoderIndex: usize) -> bool { + assert!(encoderIndex < self.len()); + let encoder = self.encoders[encoderIndex]; + let enc_data = encoder.encode(c, encoding::EncoderTrap::Strict); + + enc_data.is_ok() + } + + pub fn encode_char(&self, c: &str, encoderIndex: usize) -> Vec { + assert!(encoderIndex < self.len()); + let encoder = self.encoders[encoderIndex]; + let enc_data = encoder.encode(&c.to_string(), encoding::EncoderTrap::Strict); + assert!(enc_data.is_ok()); + return enc_data.unwrap(); + } + + pub fn encode_string(&self, s: &str, encoderIndex: usize) -> Vec { + assert!(encoderIndex < self.len()); + let encoder = self.encoders[encoderIndex]; + encoder.encode(s, encoding::EncoderTrap::Replace).unwrap() + } +} \ No newline at end of file diff --git a/src/common/eci_input.rs b/src/common/eci_input.rs new file mode 100644 index 0000000..b84db68 --- /dev/null +++ b/src/common/eci_input.rs @@ -0,0 +1,109 @@ + +/* + * Copyright 2021 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//package com.google.zxing.common; + +use crate::Exceptions; + +/** + * Interface to navigate a sequence of ECIs and bytes. + * + * @author Alex Geller + */ +pub trait ECIInput { + /** + * Returns the length of this input. The length is the number + * of {@code byte}s in or ECIs in the sequence. + * + * @return the number of {@code char}s in this sequence + */ + fn length(&self) -> usize; + + /** + * Returns the {@code byte} value at the specified index. An index ranges from zero + * to {@code length() - 1}. The first {@code byte} value of the sequence is at + * index zero, the next at index one, and so on, as for array + * indexing. + * + * @param index the index of the {@code byte} value to be returned + * + * @return the specified {@code byte} value as character or the FNC1 character + * + * @throws IndexOutOfBoundsException + * if the {@code index} argument is negative or not less than + * {@code length()} + * @throws IllegalArgumentException + * if the value at the {@code index} argument is an ECI (@see #isECI) + */ + fn charAt(&self, index: usize) -> Result; + + /** + * Returns a {@code CharSequence} that is a subsequence of this sequence. + * The subsequence starts with the {@code char} value at the specified index and + * ends with the {@code char} value at index {@code end - 1}. The length + * (in {@code char}s) of the + * returned sequence is {@code end - start}, so if {@code start == end} + * then an empty sequence is returned. + * + * @param start the start index, inclusive + * @param end the end index, exclusive + * + * @return the specified subsequence + * + * @throws IndexOutOfBoundsException + * if {@code start} or {@code end} are negative, + * if {@code end} is greater than {@code length()}, + * or if {@code start} is greater than {@code end} + * @throws IllegalArgumentException + * if a value in the range {@code start}-{@code end} is an ECI (@see #isECI) + */ + fn subSequence(&self, start: usize, end: usize) -> Result, Exceptions>; + + /** + * Determines if a value is an ECI + * + * @param index the index of the value + * + * @return true if the value at position {@code index} is an ECI + * + * @throws IndexOutOfBoundsException + * if the {@code index} argument is negative or not less than + * {@code length()} + */ + fn isECI(&self, index: u32) -> Result; + + /** + * Returns the {@code int} ECI value at the specified index. An index ranges from zero + * to {@code length() - 1}. The first {@code byte} value of the sequence is at + * index zero, the next at index one, and so on, as for array + * indexing. + * + * @param index the index of the {@code int} value to be returned + * + * @return the specified {@code int} ECI value. + * The ECI specified the encoding of all bytes with a higher index until the + * next ECI or until the end of the input if no other ECI follows. + * + * @throws IndexOutOfBoundsException + * if the {@code index} argument is negative or not less than + * {@code length()} + * @throws IllegalArgumentException + * if the value at the {@code index} argument is not an ECI (@see #isECI) + */ + fn getECIValue(&self, index: usize) -> Result; + fn haveNCharacters(&self, index: usize, n: usize) -> bool; +} \ No newline at end of file diff --git a/src/common/eci_string_builder.rs b/src/common/eci_string_builder.rs new file mode 100644 index 0000000..0b78fde --- /dev/null +++ b/src/common/eci_string_builder.rs @@ -0,0 +1,173 @@ + +/* + * Copyright 2022 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// package com.google.zxing.common; + +// import com.google.zxing.FormatException; + +// import java.nio.charset.Charset; +// import java.nio.charset.StandardCharsets; + +use std::fmt; + +use encoding::{EncodingRef, Encoding}; + +use crate::Exceptions; + +use super::CharacterSetECI; + +/** + * Class that converts a sequence of ECIs and bytes into a string + * + * @author Alex Geller + */ +pub struct ECIStringBuilder { + current_bytes: Vec, + result: String, + current_charset: EncodingRef, //= StandardCharsets.ISO_8859_1; +} + +impl ECIStringBuilder { + pub fn new() -> Self { + Self { + current_bytes: Vec::new(), + result: String::new(), + current_charset: encoding::all::UTF_8, + } + } + pub fn with_capacity(initial_capacity: usize) -> Self { + Self { + current_bytes: Vec::with_capacity(initial_capacity), + result: String::new(), + current_charset: encoding::all::ISO_8859_1, + } + } + + /** + * Appends {@code value} as a byte value + * + * @param value character whose lowest byte is to be appended + */ + pub fn append_char(&mut self, value: char) { + self.current_bytes.push(value as u8); + } + + /** + * Appends {@code value} as a byte value + * + * @param value byte to append + */ + pub fn append_byte(&mut self, value: u8) { + self.current_bytes.push(value); + } + + /** + * Appends the characters in {@code value} as bytes values + * + * @param value string to append + */ + pub fn append_string(&mut self, value: &str) { + value.as_bytes().iter().map(|b| self.current_bytes.push(*b)); + // self.current_bytes.push(value.as_bytes()); + } + + /** + * Append the string repesentation of {@code value} (short for {@code append(String.valueOf(value))}) + * + * @param value int to append as a string + */ + pub fn append(&mut self, value: i32) { + self.append_string(&format!("{}", value)); + } + + /** + * Appends ECI value to output. + * + * @param value ECI value to append, as an int + * @throws FormatException on invalid ECI value + */ + pub fn appendECI(&mut self, value: u32) -> Result<(), Exceptions> { + self.encodeCurrentBytesIfAny(); + let character_set_eci = CharacterSetECI::getCharacterSetECIByValue(value)?; + // if (character_set_eci == null) { + // throw FormatException.getFormatInstance(); + // } + self.current_charset = CharacterSetECI::getCharset(&character_set_eci); + Ok(()) + } + + pub fn encodeCurrentBytesIfAny(&mut self) { + if self.current_charset.name() == encoding::all::UTF_8.name() { + if !self.current_bytes.is_empty() { + // if result == null { + // result = currentBytes; + // currentBytes = new StringBuilder(); + // } else { + self.result + .push_str(&String::from_utf8(self.current_bytes.clone()).unwrap()); + self.current_bytes.clear(); + // } + } + } else if !self.current_bytes.is_empty() { + let bytes = self.current_bytes.clone(); + self.current_bytes.clear(); + // if (result == null) { + // result = new StringBuilder(new String(bytes, currentCharset)); + // } else { + let encoded_value = self + .current_charset + .decode(&bytes, encoding::DecoderTrap::Replace) + .unwrap(); + self.result.push_str(&encoded_value); + // } + } + } + + /** + * Appends the characters from {@code value} (unlike all other append methods of this class who append bytes) + * + * @param value characters to append + */ + pub fn appendCharacters(&mut self, value: &str) { + self.encodeCurrentBytesIfAny(); + self.result.push_str(value); + } + + /** + * Short for {@code toString().length()} (if possible, use {@link #isEmpty()} instead) + * + * @return length of string representation in characters + */ + pub fn len(&mut self) -> usize { + self.encodeCurrentBytesIfAny(); //return toString().length(); + self.result.len() + } + + /** + * @return true iff nothing has been appended + */ + pub fn is_empty(&self) -> bool { + return self.current_bytes.is_empty() && self.result.is_empty(); + } +} + +impl fmt::Display for ECIStringBuilder { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + //self.encodeCurrentBytesIfAny(); + write!(f, "{}", self.result) + } +} \ No newline at end of file diff --git a/src/common/global_histogram_binarizer.rs b/src/common/global_histogram_binarizer.rs new file mode 100644 index 0000000..415b5b3 --- /dev/null +++ b/src/common/global_histogram_binarizer.rs @@ -0,0 +1,247 @@ + +/* + * Copyright 2009 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// package com.google.zxing.common; + +// import com.google.zxing.Binarizer; +// import com.google.zxing.LuminanceSource; +// import com.google.zxing.NotFoundException; + +use crate::{Exceptions, LuminanceSource, Binarizer}; + +use super::{BitMatrix, BitArray}; + +/** + * This Binarizer implementation uses the old ZXing global histogram approach. It is suitable + * for low-end mobile devices which don't have enough CPU or memory to use a local thresholding + * algorithm. However, because it picks a global black point, it cannot handle difficult shadows + * and gradients. + * + * Faster mobile devices and all desktop applications should probably use HybridBinarizer instead. + * + * @author dswitkin@google.com (Daniel Switkin) + * @author Sean Owen + */ +pub struct GlobalHistogramBinarizer { + luminances: Vec, + buckets: Vec, + width: usize, + height: usize, + source: Box, +} + +impl Binarizer for GlobalHistogramBinarizer { + fn getLuminanceSource(&self) -> &Box { + &self.source + } + + // Applies simple sharpening to the row data to improve performance of the 1D Readers. + fn getBlackRow(&self, y: usize, row: &mut BitArray) -> Result { + let source = self.getLuminanceSource(); + let width = source.getWidth(); + let mut row = if row.getSize() < width { + BitArray::with_size(width) + } else { + let mut z = row.clone(); + z.clear(); + z + }; + + // self.initArrays(width); + let localLuminances = source.getRow(y, &self.luminances); + let mut localBuckets = self.buckets.clone(); + for x in 0..width { + // for (int x = 0; x < width; x++) { + localBuckets + [((localLuminances[x]) >> GlobalHistogramBinarizer::LUMINANCE_SHIFT) as usize] += 1; + } + let blackPoint = self.estimateBlackPoint(&localBuckets)?; + + if width < 3 { + // Special case for very small images + for x in 0..width { + // for (int x = 0; x < width; x++) { + if (localLuminances[x] as u32) < blackPoint { + row.set(x); + } + } + } else { + let mut left = localLuminances[0]; // & 0xff; + let mut center = localLuminances[1]; // & 0xff; + for x in 1..width - 1 { + // for (int x = 1; x < width - 1; x++) { + let right = localLuminances[x + 1] & 0xff; + // A simple -1 4 -1 box filter with a weight of 2. + if ((center * 4) - left - right) as u32 / 2 < blackPoint { + row.set(x); + } + left = center; + center = right; + } + } + Ok(row) + } + + // Does not sharpen the data, as this call is intended to only be used by 2D Readers. + fn getBlackMatrix(&self) -> Result { + let source = self.getLuminanceSource(); + let width = source.getWidth(); + let height = source.getHeight(); + 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 + // more robust on the blackbox tests than sampling a diagonal as we used to do. + // self.initArrays(width); + let mut localBuckets = self.buckets.clone(); + for y in 1..5 { + // for (int y = 1; y < 5; y++) { + let row = height * y / 5; + let localLuminances = source.getRow(row, &self.luminances); + let right = (width * 4) / 5; + let mut x = width / 5; + while x < right { + // for (int x = width / 5; x < right; x++) { + let pixel = localLuminances[x]; + localBuckets[(pixel >> GlobalHistogramBinarizer::LUMINANCE_SHIFT) as usize] += 1; + x += 1; + } + } + let blackPoint = self.estimateBlackPoint(&localBuckets)?; + + // 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 localLuminances = source.getMatrix(); + for y in 0..height { + // for (int y = 0; y < height; y++) { + let offset = y * width; + for x in 0..width { + // for (int x = 0; x < width; x++) { + let pixel = localLuminances[offset + x] & 0xff; + if (pixel as u32) < blackPoint { + matrix.set(x as u32, y as u32); + } + } + } + + Ok(matrix) + } + + fn createBinarizer(&self, source: Box) -> Box { + return Box::new(GlobalHistogramBinarizer::new(source)); + } + + fn getWidth(&self) -> usize { + self.width + } + + fn getHeight(&self) -> usize { + self.height + } +} + +impl GlobalHistogramBinarizer { + const LUMINANCE_BITS: usize = 5; + const LUMINANCE_SHIFT: usize = 8 - GlobalHistogramBinarizer::LUMINANCE_BITS; + const LUMINANCE_BUCKETS: usize = 1 << GlobalHistogramBinarizer::LUMINANCE_BITS; + const EMPTY: [u8; 0] = [0; 0]; + + pub fn new(source: Box) -> Self { + Self { + luminances: vec![0; source.getWidth()], + buckets: vec![0; GlobalHistogramBinarizer::LUMINANCE_BUCKETS], + width: source.getWidth(), + height: source.getHeight(), + source: source, + } + } + + // fn initArrays(&mut self, luminanceSize: usize) { + // // if self.luminances.len() < luminanceSize { + // // self.luminances = ; + // // } + // // // for x in 0..GlobalHistogramBinarizer::LUMINANCE_BUCKETS { + // // // for (int x = 0; x < LUMINANCE_BUCKETS; x++) { + // // self.buckets[x] = 0; + // // } + // } + + fn estimateBlackPoint(&self, buckets: &[u32]) -> Result { + // Find the tallest peak in the histogram. + let numBuckets = buckets.len(); + let mut maxBucketCount = 0; + let mut firstPeak = 0; + let mut firstPeakSize = 0; + for x in 0..numBuckets { + // for (int x = 0; x < numBuckets; x++) { + if buckets[x] > firstPeakSize { + firstPeak = x; + firstPeakSize = buckets[x]; + } + if buckets[x] > maxBucketCount { + maxBucketCount = buckets[x]; + } + } + + // Find the second-tallest peak which is somewhat far from the tallest peak. + let mut secondPeak = 0; + let mut secondPeakScore = 0; + for x in 0..numBuckets { + // for (int x = 0; x < numBuckets; x++) { + let distanceToBiggest = x - firstPeak; + // Encourage more distant second peaks by multiplying by square of distance. + let score = buckets[x] * distanceToBiggest as u32 * distanceToBiggest as u32; + if score > secondPeakScore { + secondPeak = x; + secondPeakScore = score; + } + } + + // Make sure firstPeak corresponds to the black peak. + if firstPeak > secondPeak { + let temp = firstPeak; + firstPeak = secondPeak; + secondPeak = temp; + } + + // 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 secondPeak - firstPeak <= numBuckets / 16 { + return Err(Exceptions::NotFoundException( + "secondPeak - firstPeak <= numBuckets / 16 ".to_owned(), + )); + } + + // Find a valley between them that is low and closer to the white peak. + let mut bestValley = secondPeak - 1; + let mut bestValleyScore = -1i32; + let mut x = secondPeak; + while x > firstPeak { + // for (int x = secondPeak - 1; x > firstPeak; x--) { + let fromFirst = x - firstPeak; + let score = + fromFirst * fromFirst * (secondPeak - x) * (maxBucketCount - buckets[x]) as usize; + if score as i32 > bestValleyScore { + bestValley = x; + bestValleyScore = score as i32; + } + x -= 1; + } + + Ok((bestValley as u32) << GlobalHistogramBinarizer::LUMINANCE_SHIFT) + } +} \ No newline at end of file diff --git a/src/common/grid_sampler.rs b/src/common/grid_sampler.rs new file mode 100644 index 0000000..b535a11 --- /dev/null +++ b/src/common/grid_sampler.rs @@ -0,0 +1,200 @@ + +/* + * Copyright 2007 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// package com.google.zxing.common; + +// import com.google.zxing.NotFoundException; + +use crate::Exceptions; + +use super::{BitMatrix, PerspectiveTransform}; + +/** + * Implementations of this class can, given locations of finder patterns for a QR code in an + * image, sample the right points in the image to reconstruct the QR code, accounting for + * perspective distortion. It is abstracted since it is relatively expensive and should be allowed + * to take advantage of platform-specific optimized implementations, like Sun's Java Advanced + * Imaging library, but which may not be available in other environments such as J2ME, and vice + * versa. + * + * The implementation used can be controlled by calling {@link #setGridSampler(GridSampler)} + * with an instance of a class which implements this interface. + * + * @author Sean Owen + */ + +pub trait GridSampler { + // /** + // * Sets the implementation of GridSampler used by the library. One global + // * instance is stored, which may sound problematic. But, the implementation provided + // * ought to be appropriate for the entire platform, and all uses of this library + // * in the whole lifetime of the JVM. For instance, an Android activity can swap in + // * an implementation that takes advantage of native platform libraries. + // * + // * @param newGridSampler The platform-specific object to install. + // */ + // public static void setGridSampler(GridSampler newGridSampler) { + // gridSampler = newGridSampler; + // } + + // /** + // * @return the current implementation of GridSampler + // */ + // public static GridSampler getInstance() { + // return gridSampler; + // } + + /** + * Samples an image for a rectangular matrix of bits of the given dimension. The sampling + * transformation is determined by the coordinates of 4 points, in the original and transformed + * image space. + * + * @param image image to sample + * @param dimensionX width of {@link BitMatrix} to sample from image + * @param dimensionY height of {@link BitMatrix} to sample from image + * @param p1ToX point 1 preimage X + * @param p1ToY point 1 preimage Y + * @param p2ToX point 2 preimage X + * @param p2ToY point 2 preimage Y + * @param p3ToX point 3 preimage X + * @param p3ToY point 3 preimage Y + * @param p4ToX point 4 preimage X + * @param p4ToY point 4 preimage Y + * @param p1FromX point 1 image X + * @param p1FromY point 1 image Y + * @param p2FromX point 2 image X + * @param p2FromY point 2 image Y + * @param p3FromX point 3 image X + * @param p3FromY point 3 image Y + * @param p4FromX point 4 image X + * @param p4FromY point 4 image Y + * @return {@link BitMatrix} representing a grid of points sampled from the image within a region + * defined by the "from" parameters + * @throws NotFoundException if image can't be sampled, for example, if the transformation defined + * by the given points is invalid or results in sampling outside the image boundaries + */ + fn sample_grid_detailed( + &self, + image: &BitMatrix, + dimensionX: u32, + dimensionY: u32, + p1ToX: f32, + p1ToY: f32, + p2ToX: f32, + p2ToY: f32, + p3ToX: f32, + p3ToY: f32, + p4ToX: f32, + p4ToY: f32, + p1FromX: f32, + p1FromY: f32, + p2FromX: f32, + p2FromY: f32, + p3FromX: f32, + p3FromY: f32, + p4FromX: f32, + p4FromY: f32, + ) -> Result; + + fn sample_grid( + &self, + image: &BitMatrix, + dimensionX: u32, + dimensionY: u32, + transform: &PerspectiveTransform, + ) -> Result; + + /** + *

Checks a set of points that have been transformed to sample points on an image against + * the image's dimensions to see if the point are even within the image.

+ * + *

This method will actually "nudge" the endpoints back onto the image if they are found to be + * barely (less than 1 pixel) off the image. This accounts for imperfect detection of finder + * patterns in an image where the QR Code runs all the way to the image border.

+ * + *

For efficiency, the method will check points from either end of the line until one is found + * to be within the image. Because the set of points are assumed to be linear, this is valid.

+ * + * @param image image into which the points should map + * @param points actual points in x1,y1,...,xn,yn form + * @throws NotFoundException if an endpoint is lies outside the image boundaries + */ + fn checkAndNudgePoints(&self, image: &BitMatrix, points: &mut [f32]) -> Result<(), Exceptions> { + let width = image.getWidth(); + let height = image.getHeight(); + // Check and nudge points from start until we see some that are OK: + let mut nudged = true; + let max_offset = points.len() - 1; // points.length must be even + let mut offset = 0; + while offset < max_offset && nudged { + // for (int offset = 0; offset < maxOffset && nudged; offset += 2) { + let x = points[offset] as i32; + let y = points[offset + 1] as i32; + if x < -1 || x > width.try_into().unwrap() || y < -1 || y > height.try_into().unwrap() { + return Err(Exceptions::NotFoundException( + "getNotFoundInstance".to_owned(), + )); + } + nudged = false; + if x == -1 { + points[offset] = 0.0f32; + nudged = true; + } else if x == width.try_into().unwrap() { + points[offset] = width as f32 - 1f32; + nudged = true; + } + if y == -1 { + points[offset + 1] = 0.0f32; + nudged = true; + } else if (y == height.try_into().unwrap()) { + points[offset + 1] = height as f32 - 1f32; + nudged = true; + } + offset += 2; + } + // Check and nudge points from end: + nudged = true; + let mut offset = points.len() as isize - 2; + while offset >= 0 && nudged { + // for (int offset = points.length - 2; offset >= 0 && nudged; offset -= 2) { + let x = points[offset as usize] as i32; + let y = points[offset as usize + 1] as i32; + if x < -1 || x > width.try_into().unwrap() || y < -1 || y > height.try_into().unwrap() { + return Err(Exceptions::NotFoundException( + "getNotFoundInstance".to_owned(), + )); + } + nudged = false; + if x == -1 { + points[offset as usize] = 0.0f32; + nudged = true; + } else if x == width.try_into().unwrap() { + points[offset as usize] = width as f32 - 1f32; + nudged = true; + } + if y == -1 { + points[offset as usize + 1] = 0.0f32; + nudged = true; + } else if y == height.try_into().unwrap() { + points[offset as usize + 1] = height as f32 - 1f32; + nudged = true; + } + offset += -2; + } + Ok(()) + } +} \ No newline at end of file diff --git a/src/common/hybrid_binarizer.rs b/src/common/hybrid_binarizer.rs new file mode 100644 index 0000000..62cfb0b --- /dev/null +++ b/src/common/hybrid_binarizer.rs @@ -0,0 +1,320 @@ +/* + * Copyright 2009 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// package com.google.zxing.common; + +// import com.google.zxing.Binarizer; +// import com.google.zxing.LuminanceSource; +// import com.google.zxing.NotFoundException; + +use crate::{LuminanceSource, Binarizer, Exceptions}; + +use super::{GlobalHistogramBinarizer, BitMatrix, BitArray}; + +/** + * This class implements a local thresholding algorithm, which while slower than the + * GlobalHistogramBinarizer, is fairly efficient for what it does. It is designed for + * high frequency images of barcodes with black data on white backgrounds. For this application, + * it does a much better job than a global blackpoint with severe shadows and gradients. + * However it tends to produce artifacts on lower frequency images and is therefore not + * a good general purpose binarizer for uses outside ZXing. + * + * This class extends GlobalHistogramBinarizer, using the older histogram approach for 1D readers, + * and the newer local approach for 2D readers. 1D decoding using a per-row histogram is already + * inherently local, and only fails for horizontal gradients. We can revisit that problem later, + * but for now it was not a win to use local blocks for 1D. + * + * This Binarizer is the default for the unit tests and the recommended class for library users. + * + * @author dswitkin@google.com (Daniel Switkin) + */ +pub struct HybridBinarizer { + //width: usize, + //height: usize, + //source: Box, + ghb: GlobalHistogramBinarizer, + // matrix :Option, +} +impl Binarizer for HybridBinarizer { + fn getLuminanceSource(&self) -> &Box { + self.ghb.getLuminanceSource() + } + + fn getBlackRow(&self, y: usize, row: &mut BitArray) -> Result { + self.ghb.getBlackRow(y, row) + } + + /** + * 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 getBlackMatrix(&self) -> Result { + // if self.matrix.is_some() { + // return Ok(self.matrix.clone().unwrap()) + // } + let matrix; + let source = self.getLuminanceSource(); + let width = source.getWidth(); + let height = source.getHeight(); + if width >= HybridBinarizer::MINIMUM_DIMENSION + && height >= HybridBinarizer::MINIMUM_DIMENSION + { + let luminances = source.getMatrix(); + let mut sub_width = width >> HybridBinarizer::BLOCK_SIZE_POWER; + if (width & HybridBinarizer::BLOCK_SIZE_MASK) != 0 { + sub_width += 1; + } + let mut sub_height = height >> HybridBinarizer::BLOCK_SIZE_POWER; + if (height & HybridBinarizer::BLOCK_SIZE_MASK) != 0 { + sub_height += 1; + } + let black_points = Self::calculateBlackPoints( + &luminances, + sub_width as u32, + sub_height as u32, + width as u32, + height as u32, + ); + + let mut new_matrix = BitMatrix::new(width as u32, height as u32)?; + Self::calculateThresholdForBlock( + &luminances, + sub_width as u32, + sub_height as u32, + width as u32, + height as u32, + &black_points, + &mut new_matrix, + ); + matrix = new_matrix; + } else { + // If the image is too small, fall back to the global histogram approach. + matrix = self.ghb.getBlackMatrix()?; + } + // dbg!(matrix.to_string()); + Ok(matrix) + } + + fn createBinarizer(&self, source: Box) -> Box { + Box::new(HybridBinarizer::new(source)) + } + + fn getWidth(&self) -> usize { + self.ghb.getWidth() + } + + fn getHeight(&self) -> usize { + self.ghb.getHeight() + } +} +impl HybridBinarizer { + // This class uses 5x5 blocks to compute local luminance, where each block is 8x8 pixels. + // So this is the smallest dimension in each axis we can accept. + const BLOCK_SIZE_POWER: usize = 3; + const BLOCK_SIZE: usize = 1 << HybridBinarizer::BLOCK_SIZE_POWER; // ...0100...00 + const BLOCK_SIZE_MASK: usize = HybridBinarizer::BLOCK_SIZE - 1; // ...0011...11 + const MINIMUM_DIMENSION: usize = HybridBinarizer::BLOCK_SIZE * 5; + const MIN_DYNAMIC_RANGE: usize = 24; + + pub fn new(source: Box) -> Self { + Self { + ghb: GlobalHistogramBinarizer::new(source), + // matrix: None, + } + } + + /** + * For each block in the image, calculate the average black point using a 5x5 grid + * of the blocks around it. Also handles the corner cases (fractional blocks are computed based + * on the last pixels in the row/column which are also used in the previous block). + */ + fn calculateThresholdForBlock( + luminances: &[u8], + sub_width: u32, + sub_height: u32, + width: u32, + height: u32, + black_points: &Vec>, + matrix: &mut BitMatrix, + ) { + let maxYOffset = height - HybridBinarizer::BLOCK_SIZE as u32; + let maxXOffset = width - HybridBinarizer::BLOCK_SIZE as u32; + for y in 0..sub_height { + // for (int y = 0; y < subHeight; y++) { + let mut yoffset = y << HybridBinarizer::BLOCK_SIZE_POWER; + if yoffset > maxYOffset { + yoffset = maxYOffset; + } + let top = Self::cap(y, sub_height - 3); + for x in 0..sub_width { + // for (int x = 0; x < subWidth; x++) { + let mut xoffset = x << HybridBinarizer::BLOCK_SIZE_POWER; + if xoffset > maxXOffset { + xoffset = maxXOffset; + } + let left = Self::cap(x, sub_width - 3); + let mut sum = 0; + for z in -2i32..=2 { + // for (int z = -2; z <= 2; z++) { + let blackRow = &black_points[(top as i32 + z) as usize]; + sum += blackRow[(left - 2) as usize] + + blackRow[(left - 1) as usize] + + blackRow[left as usize] + + blackRow[(left + 1) as usize] + + blackRow[(left + 2) as usize]; + } + let average = sum / 25; + Self::thresholdBlock(luminances, xoffset, yoffset, average, width, matrix); + } + } + } + + fn cap(value: u32, max: u32) -> u32 { + if value < 2 { + 2 + } else { + value.min(max) + } + } + + /** + * Applies a single threshold to a block of pixels. + */ + fn thresholdBlock( + luminances: &[u8], + xoffset: u32, + yoffset: u32, + threshold: u32, + stride: u32, + matrix: &mut BitMatrix, + ) { + let mut offset = yoffset * stride + xoffset; + for y in 0..HybridBinarizer::BLOCK_SIZE { + // for (int y = 0, offset = yoffset * stride + xoffset; y < HybridBinarizer::BLOCK_SIZE; y++, offset += stride) { + for x in 0..HybridBinarizer::BLOCK_SIZE { + // for (int x = 0; x < HybridBinarizer::BLOCK_SIZE; x++) { + // Comparison needs to be <= so that black == 0 pixels are black even if the threshold is 0. + if luminances[offset as usize + x] as u32 <= threshold { + matrix.set(xoffset + x as u32, yoffset + y as u32); + } + } + offset += stride; + } + } + + /** + * Calculates a single black point for each block of pixels and saves it away. + * See the following thread for a discussion of this algorithm: + * http://groups.google.com/group/zxing/browse_thread/thread/d06efa2c35a7ddc0 + */ + fn calculateBlackPoints( + luminances: &[u8], + subWidth: u32, + subHeight: u32, + width: u32, + height: u32, + ) -> Vec> { + let maxYOffset = height as usize - HybridBinarizer::BLOCK_SIZE; + let maxXOffset = width as usize - HybridBinarizer::BLOCK_SIZE; + let mut blackPoints = vec![vec![0; subWidth as usize]; subHeight as usize]; + for y in 0..subHeight { + // for (int y = 0; y < subHeight; y++) { + let mut yoffset = y << HybridBinarizer::BLOCK_SIZE_POWER; + if yoffset > maxYOffset as u32 { + yoffset = maxYOffset as u32; + } + for x in 0..subWidth { + // for (int x = 0; x < subWidth; x++) { + let mut xoffset = x << HybridBinarizer::BLOCK_SIZE_POWER; + if xoffset > maxXOffset as u32 { + xoffset = maxXOffset as u32; + } + let mut sum = 0u32; + let mut min = 0xff; + let mut max = 0; + + let mut offset = yoffset * width + xoffset; + let mut yy = 0; + while yy < HybridBinarizer::BLOCK_SIZE { + // for (int yy = 0, offset = yoffset * width + xoffset; yy < HybridBinarizer::BLOCK_SIZE; yy++, offset += width) { + for xx in 0..HybridBinarizer::BLOCK_SIZE { + // for (int xx = 0; xx < HybridBinarizer::BLOCK_SIZE; xx++) { + let pixel = luminances[offset as usize + xx]; + sum += pixel as u32; + // still looking for good contrast + if pixel < min { + min = pixel; + } + if pixel > max { + max = pixel; + } + } + // short-circuit min/max tests once dynamic range is met + if (max - min) as usize > HybridBinarizer::MIN_DYNAMIC_RANGE { + // finish the rest of the rows quickly + offset += width; + yy += 1; + while yy < HybridBinarizer::BLOCK_SIZE { + // for (yy++, offset += width; yy < HybridBinarizer::BLOCK_SIZE; yy++, offset += width) { + for xx in 0..HybridBinarizer::BLOCK_SIZE { + // for (int xx = 0; xx < BLOCK_SIZE; xx++) { + sum += luminances[offset as usize + xx] as u32; + } + yy += 1; + offset += width; + } + break; + } + yy += 1; + offset += width; + } + + // The default estimate is the average of the values in the block. + let mut average = sum >> (HybridBinarizer::BLOCK_SIZE_POWER * 2); + if (max - min) as usize <= HybridBinarizer::MIN_DYNAMIC_RANGE { + // If variation within the block is low, assume this is a block with only light or only + // dark pixels. In that case we do not want to use the average, as it would divide this + // low contrast area into black and white pixels, essentially creating data out of noise. + // + // The default assumption is that the block is light/background. Since no estimate for + // the level of dark pixels exists locally, use half the min for the block. + average = min as u32 / 2; + + if y > 0 && x > 0 { + // Correct the "white background" assumption for blocks that have neighbors by comparing + // the pixels in this block to the previously calculated black points. This is based on + // the fact that dark barcode symbology is always surrounded by some amount of light + // background for which reasonable black point estimates were made. The bp estimated at + // the boundaries is used for the interior. + + // The (min < bp) is arbitrary but works better than other heuristics that were tried. + let average_neighbor_black_point: u32 = (blackPoints[y as usize - 1] + [x as usize] + + (2 * blackPoints[y as usize][x as usize - 1]) + + blackPoints[y as usize - 1][x as usize - 1]) + / 4; + if (min as u32) < average_neighbor_black_point { + average = average_neighbor_black_point; + } + } + } + blackPoints[y as usize][x as usize] = average; + } + } + blackPoints + } +} \ No newline at end of file diff --git a/src/common/input_edge.rs b/src/common/input_edge.rs new file mode 100644 index 0000000..33f5699 --- /dev/null +++ b/src/common/input_edge.rs @@ -0,0 +1,115 @@ +use std::{fmt, rc::Rc}; + +use super::{MinimalECIInput, ECIEncoderSet, ECIInput, COST_PER_ECI}; + +pub struct InputEdge { + pub c: String, + pub encoderIndex: usize, //the encoding of this edge + pub previous: Option>, + pub cachedTotalSize: usize, +} +impl InputEdge { + pub fn new( + c: &str, + encoderSet: &ECIEncoderSet, + encoderIndex: usize, + previous: Option>, + fnc1: u16, + ) -> Self { + let mut size = if c == "\u{1000}" { + 1 + } else { + encoderSet.encode_char(c, encoderIndex).len() + }; + + let fnc1Str = String::from_utf16(&[fnc1]).unwrap(); + + if let Some(prev) = previous { + let previousEncoderIndex = prev.encoderIndex; + if previousEncoderIndex != encoderIndex { + size += COST_PER_ECI; + } + size += prev.cachedTotalSize; + + Self { + c: if c == fnc1Str { + String::from("\u{1000}") + } else { + String::from(c) + }, + encoderIndex, + previous: Some(prev.clone()), + cachedTotalSize: size, + } + } else { + let previousEncoderIndex = 0; + if previousEncoderIndex != encoderIndex { + size += COST_PER_ECI; + } + + Self { + c: if c == fnc1Str { + String::from("\u{1000}") + } else { + String::from(c) + }, + encoderIndex, + previous: None, + cachedTotalSize: size, + } + } + + // int size = this.c == 1000 ? 1 : encoderSet.encode(c, encoderIndex).length; + // let previousEncoderIndex = if previous.is_none() { + // 0 + // } else { + // previous.unwrap().encoderIndex + // }; + // int previousEncoderIndex = previous == null ? 0 : previous.encoderIndex; + // if previousEncoderIndex != encoderIndex { + // size += COST_PER_ECI; + // } + // if prev_is_some { + // size += previous.unwrap().cachedTotalSize; + // } + + // Self { + // c: if c == fnc1 { 1000 as char } else { c }, + // encoderIndex, + // previous: previous, + // cachedTotalSize: size, + // } + // this.c = c == fnc1 ? 1000 : c; + // this.encoderIndex = encoderIndex; + // this.previous = previous; + // this.cachedTotalSize = size; + } + + pub fn isFNC1(&self) -> bool { + self.c == "\u{1000}" + } +} + +impl fmt::Display for MinimalECIInput { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut result = String::new(); + for i in 0..self.length() { + // for (int i = 0; i < length(); i++) { + if i > 0 { + result.push_str(", "); + } + if self.isECI(i as u32).unwrap() { + result.push_str("ECI("); + result.push_str(&self.getECIValue(i).unwrap().to_string()); + result.push(')'); + } else if (self.charAt(i).unwrap() as u8) < 128 { + result.push('\''); + result.push(self.charAt(i).unwrap()); + result.push('\''); + } else { + result.push(self.charAt(i).unwrap()); + } + } + write!(f, "{}", result) + } +} \ No newline at end of file diff --git a/src/common/minimal_eci_input.rs b/src/common/minimal_eci_input.rs new file mode 100644 index 0000000..a72782c --- /dev/null +++ b/src/common/minimal_eci_input.rs @@ -0,0 +1,384 @@ +/* + * Copyright 2021 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// package com.google.zxing.common; + +// import java.nio.charset.Charset; +// import java.util.ArrayList; +// import java.util.List; + +use std::rc::Rc; + +use encoding::EncodingRef; +use unicode_segmentation::UnicodeSegmentation; + +use crate::Exceptions; + +use super::{ECIEncoderSet, InputEdge, ECIInput}; + +//* approximated (latch + 2 codewords) +pub const COST_PER_ECI: usize = 3; + +/** + * Class that converts a character string into a sequence of ECIs and bytes + * + * The implementation uses the Dijkstra algorithm to produce minimal encodings + * + * @author Alex Geller + */ +pub struct MinimalECIInput { + bytes: Vec, + fnc1: u16, +} + +impl ECIInput for MinimalECIInput { + /** + * Returns the length of this input. The length is the number + * of {@code byte}s, FNC1 characters or ECIs in the sequence. + * + * @return the number of {@code char}s in this sequence + */ + fn length(&self) -> usize { + return self.bytes.len(); + } + + /** + * Returns the {@code byte} value at the specified index. An index ranges from zero + * to {@code length() - 1}. The first {@code byte} value of the sequence is at + * index zero, the next at index one, and so on, as for array + * indexing. + * + * @param index the index of the {@code byte} value to be returned + * + * @return the specified {@code byte} value as character or the FNC1 character + * + * @throws IndexOutOfBoundsException + * if the {@code index} argument is negative or not less than + * {@code length()} + * @throws IllegalArgumentException + * if the value at the {@code index} argument is an ECI (@see #isECI) + */ + fn charAt(&self, index: usize) -> Result { + if index >= self.length() { + return Err(Exceptions::IndexOutOfBoundsException(index.to_string())); + } + if self.isECI(index as u32)? { + return Err(Exceptions::IllegalArgumentException(format!( + "value at {} is not a character but an ECI", + index + ))); + } + if self.isFNC1(index)? { + Ok(self.fnc1 as u8 as char) + } else { + Ok(self.bytes[index] as u8 as char) + } + } + + /** + * Returns a {@code CharSequence} that is a subsequence of this sequence. + * The subsequence starts with the {@code char} value at the specified index and + * ends with the {@code char} value at index {@code end - 1}. The length + * (in {@code char}s) of the + * returned sequence is {@code end - start}, so if {@code start == end} + * then an empty sequence is returned. + * + * @param start the start index, inclusive + * @param end the end index, exclusive + * + * @return the specified subsequence + * + * @throws IndexOutOfBoundsException + * if {@code start} or {@code end} are negative, + * if {@code end} is greater than {@code length()}, + * or if {@code start} is greater than {@code end} + * @throws IllegalArgumentException + * if a value in the range {@code start}-{@code end} is an ECI (@see #isECI) + */ + fn subSequence(&self, start: usize, end: usize) -> Result, Exceptions> { + if start > end || end > self.length() { + return Err(Exceptions::IndexOutOfBoundsException(start.to_string())); + } + let mut result = String::new(); + for i in start..end { + // for (int i = start; i < end; i++) { + if self.isECI(i as u32)? { + return Err(Exceptions::IllegalArgumentException(format!( + "value at {} is not a character but an ECI", + i + ))); + } + result.push_str(&self.charAt(i)?.to_string()); + } + Ok(result.chars().collect()) + } + + /** + * Determines if a value is an ECI + * + * @param index the index of the value + * + * @return true if the value at position {@code index} is an ECI + * + * @throws IndexOutOfBoundsException + * if the {@code index} argument is negative or not less than + * {@code length()} + */ + fn isECI(&self, index: u32) -> Result { + if index >= self.length() as u32 { + return Err(Exceptions::IndexOutOfBoundsException(index.to_string())); + } + Ok(self.bytes[index as usize] > 255)// && self.bytes[index as usize] <= u16::MAX) + } + + /** + * Returns the {@code int} ECI value at the specified index. An index ranges from zero + * to {@code length() - 1}. The first {@code byte} value of the sequence is at + * index zero, the next at index one, and so on, as for array + * indexing. + * + * @param index the index of the {@code int} value to be returned + * + * @return the specified {@code int} ECI value. + * The ECI specified the encoding of all bytes with a higher index until the + * next ECI or until the end of the input if no other ECI follows. + * + * @throws IndexOutOfBoundsException + * if the {@code index} argument is negative or not less than + * {@code length()} + * @throws IllegalArgumentException + * if the value at the {@code index} argument is not an ECI (@see #isECI) + */ + fn getECIValue(&self, index: usize) -> Result { + if index >= self.length() { + return Err(Exceptions::IndexOutOfBoundsException(index.to_string())); + } + if !self.isECI(index as u32)? { + return Err(Exceptions::IllegalArgumentException(format!( + "value at {} is not an ECI but a character", + index + ))); + } + Ok((self.bytes[index] as u32 - 256) as u32) + } + + fn haveNCharacters(&self, index: usize, n: usize) -> bool { + if index + n - 1 >= self.bytes.len() { + return false; + } + for i in 0..n { + // for (int i = 0; i < n; i++) { + if self.isECI(index as u32 + i as u32).unwrap() { + return false; + } + } + return true; + } +} +impl MinimalECIInput { + /** + * Constructs a minimal input + * + * @param stringToEncode the character string to encode + * @param priorityCharset The preferred {@link Charset}. When the value of the argument is null, the algorithm + * chooses charsets that leads to a minimal representation. Otherwise the algorithm will use the priority + * charset to encode any character in the input that can be encoded by it if the charset is among the + * supported charsets. + * @param fnc1 denotes the character in the input that represents the FNC1 character or -1 if this is not GS1 + * input. + */ + pub fn new( + stringToEncodeInput: &str, + priorityCharset: Option, + fnc1: Option<&str>, + ) -> Self { + let stringToEncode = stringToEncodeInput.graphemes(true).collect::>(); + let encoderSet = ECIEncoderSet::new(stringToEncodeInput, priorityCharset, fnc1); + let bytes = if encoderSet.len() == 1 { + //optimization for the case when all can be encoded without ECI in ISO-8859-1 + let mut bytes_hld = vec![0; stringToEncode.len()]; + for i in 0..stringToEncode.len() { + // for (int i = 0; i < bytes.length; i++) { + let c = stringToEncode.get(i).unwrap(); + bytes_hld[i] = if fnc1.is_some() && c == fnc1.as_ref().unwrap() { + 1000 + } else { + c.chars().nth(0).unwrap() as u16 + }; + } + bytes_hld + } else { + Self::encodeMinimally( + stringToEncodeInput, + &encoderSet, + fnc1.as_ref().unwrap().chars().nth(0).unwrap() as u16, + ) + }; + + Self { + bytes: bytes, + fnc1: fnc1.as_ref().unwrap().chars().nth(0).unwrap() as u16, + } + } + + pub fn getFNC1Character(&self) -> u16 { + self.fnc1 + } + + /** + * Determines if a value is the FNC1 character + * + * @param index the index of the value + * + * @return true if the value at position {@code index} is the FNC1 character + * + * @throws IndexOutOfBoundsException + * if the {@code index} argument is negative or not less than + * {@code length()} + */ + pub fn isFNC1(&self, index: usize) -> Result { + if index >= self.length() { + return Err(Exceptions::IndexOutOfBoundsException(index.to_string())); + } + Ok(self.bytes[index] == 1000) + } + + fn addEdge(edges: &mut Vec>>>, to: usize, edge: Rc) { + if edges[to][edge.encoderIndex].is_none() + || edges[to][edge.encoderIndex] + .clone() + .unwrap() + .cachedTotalSize + > edge.cachedTotalSize + { + edges[to][edge.encoderIndex] = Some(edge.clone()); + } + } + + fn addEdges( + stringToEncode: &str, + encoderSet: &ECIEncoderSet, + edges: &mut Vec>>>, + from: usize, + previous: Option>, + fnc1: u16, + ) { + // let ch = stringToEncode.chars().nth(from).unwrap() as i16; + let ch = stringToEncode.graphemes(true).nth(from).unwrap(); + + let mut start = 0; + let mut end = encoderSet.len(); + if encoderSet.getPriorityEncoderIndex().is_some() + && (ch.chars().nth(0).unwrap() as u16 == fnc1 + || encoderSet.canEncode(ch, encoderSet.getPriorityEncoderIndex().unwrap())) + { + start = encoderSet.getPriorityEncoderIndex().unwrap(); + end = start + 1; + } + + for i in start..end { + // for (int i = start; i < end; i++) { + if ch.chars().nth(0).unwrap() as u16 == fnc1 || encoderSet.canEncode(ch, i) { + Self::addEdge( + edges, + from + 1, + Rc::new(InputEdge::new(ch, encoderSet, i, previous.clone(), fnc1)), + ); + } + } + } + + pub fn encodeMinimally( + stringToEncode: &str, + encoderSet: &ECIEncoderSet, + fnc1: u16, + ) -> Vec { + let inputLength = stringToEncode.len(); + + // Array that represents vertices. There is a vertex for every character and encoding. + let mut edges = vec![vec![None; encoderSet.len()]; inputLength + 1]; //InputEdge[inputLength + 1][encoderSet.length()]; + Self::addEdges(stringToEncode, encoderSet, &mut edges, 0, None, fnc1); + + for i in 0..=inputLength { + // for (int i = 1; i <= inputLength; i++) { + for j in 0..encoderSet.len() { + // for (int j = 0; j < encoderSet.length(); j++) { + if edges[i][j].is_some() && i < inputLength { + let edg = edges[i][j].clone(); + Self::addEdges(stringToEncode, encoderSet, &mut edges, i, edg, fnc1); + } + } + //optimize memory by removing edges that have been passed. + for j in 0..encoderSet.len() { + // for (int j = 0; j < encoderSet.length(); j++) { + edges[i - 1][j] = None; + } + } + let mut minimalJ: i32 = -1; + let mut minimalSize: i32 = i32::MAX; + for j in 0..encoderSet.len() { + // for (int j = 0; j < encoderSet.length(); j++) { + if edges[inputLength][j].is_some() { + let edge = edges[inputLength][j].clone().unwrap(); + if (edge.cachedTotalSize as i32) < minimalSize { + minimalSize = edge.cachedTotalSize as i32; + minimalJ = j as i32; + } + } + } + if minimalJ < 0 { + panic!("Internal error: failed to encode \"{}\"", stringToEncode); + } + let mut intsAL: Vec = Vec::new(); + let mut current = edges[inputLength][minimalJ as usize].clone(); + while current.is_some() { + let c = current.unwrap().clone(); + if c.isFNC1() { + intsAL.splice(0..0, [1000]); + } else { + let bytes: Vec = encoderSet + .encode_char(&c.c, c.encoderIndex) + .iter() + .map(|x| *x as u16) + .collect(); + let mut i = bytes.len() as i32 - 1; + while i >= 0 { + // for (int i = bytes.length - 1; i >= 0; i--) { + intsAL.splice(0..0, [bytes[i as usize]]); + i = -1; + } + } + let previousEncoderIndex = if c.previous.is_none() { + 0 + } else { + c.previous.clone().unwrap().encoderIndex + }; + if previousEncoderIndex != c.encoderIndex { + intsAL.splice( + 0..0, + [256 as u16 + encoderSet.getECIValue(c.encoderIndex) as u16], + ); + } + current = c.previous.clone(); + } + let mut ints = vec![0; intsAL.len()]; + for i in 0..ints.len() { + // for (int i = 0; i < ints.length; i++) { + ints[i] = *intsAL.get(i).unwrap() as u16; + } + return ints; + } +} \ No newline at end of file diff --git a/src/common/mod.rs b/src/common/mod.rs index c79122b..97a16ca 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -36,676 +36,13 @@ mod BitSourceTestCase; #[cfg(test)] mod PerspectiveTransformTestCase; -/* - * Copyright (C) 2010 ZXing authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -// package com.google.zxing.common; -// import java.nio.charset.Charset; -// import java.nio.charset.StandardCharsets; -// import java.util.Map; +mod string_utils; +pub use string_utils::*; -/** - * Common string-related functions. - * - * @author Sean Owen - * @author Alex Dupre - */ -pub struct StringUtils { - // private static final Charset PLATFORM_DEFAULT_ENCODING = Charset.defaultCharset(); - // public static final Charset SHIFT_JIS_CHARSET = Charset.forName("SJIS"); - // public static final Charset GB2312_CHARSET = Charset.forName("GB2312"); - // private static final Charset EUC_JP = Charset.forName("EUC_JP"); - // private static final boolean ASSUME_SHIFT_JIS = - // SHIFT_JIS_CHARSET.equals(PLATFORM_DEFAULT_ENCODING) || - // EUC_JP.equals(PLATFORM_DEFAULT_ENCODING); - - // // Retained for ABI compatibility with earlier versions - // public static final String SHIFT_JIS = "SJIS"; - // public static final String GB2312 = "GB2312"; -} - -// const PLATFORM_DEFAULT_ENCODING: &dyn Encoding = encoding::all::UTF_8; -// const SHIFT_JIS_CHARSET: &dyn Encoding = -// encoding::label::encoding_from_whatwg_label("SJIS").unwrap(); -// const GB2312_CHARSET: &dyn Encoding = -// encoding::label::encoding_from_whatwg_label("GB2312").unwrap(); -// const EUC_JP: &dyn Encoding = encoding::label::encoding_from_whatwg_label("EUC_JP").unwrap(); -const ASSUME_SHIFT_JIS: bool = false; -static SHIFT_JIS: &'static str = "SJIS"; -static GB2312: &'static str = "GB2312"; -lazy_static! { - pub static ref SHIFT_JIS_CHARSET: EncodingRef = - encoding::label::encoding_from_whatwg_label("SJIS").unwrap(); -} - -// private static final boolean ASSUME_SHIFT_JIS = -// SHIFT_JIS_CHARSET.equals(PLATFORM_DEFAULT_ENCODING) || -// EUC_JP.equals(PLATFORM_DEFAULT_ENCODING); - -impl StringUtils { - /** - * @param bytes bytes encoding a string, whose encoding should be guessed - * @param hints decode hints if applicable - * @return name of guessed encoding; at the moment will only guess one of: - * "SJIS", "UTF8", "ISO8859_1", or the platform default encoding if none - * of these can possibly be correct - */ - pub fn guessEncoding(bytes: &[u8], hints: &DecodingHintDictionary) -> String { - let c = StringUtils::guessCharset(bytes, hints); - if c.name() - == encoding::label::encoding_from_whatwg_label("SJIS") - .unwrap() - .name() - { - return "SJIS".to_owned(); - } else if c.name() == encoding::all::UTF_8.name() { - return "UTF8".to_owned(); - } else if c.name() == encoding::all::ISO_8859_1.name() { - return "ISO8859_1".to_owned(); - } - return c.name().to_owned(); - } - - /** - * @param bytes bytes encoding a string, whose encoding should be guessed - * @param hints decode hints if applicable - * @return Charset of guessed encoding; at the moment will only guess one of: - * {@link #SHIFT_JIS_CHARSET}, {@link StandardCharsets#UTF_8}, - * {@link StandardCharsets#ISO_8859_1}, {@link StandardCharsets#UTF_16}, - * or the platform default encoding if - * none of these can possibly be correct - */ - pub fn guessCharset(bytes: &[u8], hints: &DecodingHintDictionary) -> EncodingRef { - match hints.get(&DecodeHintType::CHARACTER_SET) { - Some(hint) => { - if let DecodeHintValue::CharacterSet(cs_name) = hint { - return encoding::label::encoding_from_whatwg_label(cs_name).unwrap(); - } - } - _ => {} - }; - // if hints.contains_key(&DecodeHintType::CHARACTER_SET) { - // return Charset.forName(hints.get(DecodeHintType.CHARACTER_SET).toString()); - // } - - // First try UTF-16, assuming anything with its BOM is UTF-16 - if bytes.len() > 2 - && ((bytes[0] == 0xFE && bytes[1] == 0xFF) || (bytes[0] == 0xFF && bytes[1] == 0xFE)) - { - if bytes[0] == 0xFE && bytes[1] == 0xFF { - return encoding::all::UTF_16BE; - } else { - return encoding::all::UTF_16LE; - } - } - - // For now, merely tries to distinguish ISO-8859-1, UTF-8 and Shift_JIS, - // which should be by far the most common encodings. - let length = bytes.len(); - let mut can_be_iso88591 = true; - let mut can_be_shift_jis = true; - let mut can_be_utf8 = true; - let mut utf8_bytes_left = 0; - let mut utf2_bytes_chars = 0; - let mut utf3_bytes_chars = 0; - let mut utf4_bytes_chars = 0; - let mut sjis_bytes_left = 0; - let mut sjis_katakana_chars = 0; - let mut sjis_cur_katakana_word_length = 0; - let mut sjis_cur_double_bytes_word_length = 0; - let mut sjis_max_katakana_word_length = 0; - let mut sjis_max_double_bytes_word_length = 0; - let mut iso_high_other = 0; - - let utf8bom = bytes.len() > 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF; - - for i in 0..length { - // for (int i = 0; - // i < length && (canBeISO88591 || canBeShiftJIS || canBeUTF8); - // i++) { - if !(can_be_iso88591 || can_be_shift_jis || can_be_utf8) { - break; - } - - let value = bytes[i]; - - // UTF-8 stuff - if can_be_utf8 { - if utf8_bytes_left > 0 { - if (value & 0x80) == 0 { - can_be_utf8 = false; - } else { - utf8_bytes_left -= 1; - } - } else if (value & 0x80) != 0 { - if (value & 0x40) == 0 { - can_be_utf8 = false; - } else { - utf8_bytes_left += 1; - if (value & 0x20) == 0 { - utf2_bytes_chars += 1; - } else { - utf8_bytes_left += 1; - if (value & 0x10) == 0 { - utf3_bytes_chars += 1; - } else { - utf8_bytes_left += 1; - if (value & 0x08) == 0 { - utf4_bytes_chars += 1; - } else { - can_be_utf8 = false; - } - } - } - } - } - } - - // ISO-8859-1 stuff - if can_be_iso88591 { - if value > 0x7F && value < 0xA0 { - can_be_iso88591 = false; - } else if value > 0x9F && (value < 0xC0 || value == 0xD7 || value == 0xF7) { - iso_high_other += 1; - } - } - - // Shift_JIS stuff - if can_be_shift_jis { - if sjis_bytes_left > 0 { - if value < 0x40 || value == 0x7F || value > 0xFC { - can_be_shift_jis = false; - } else { - sjis_bytes_left -= 1; - } - } else if value == 0x80 || value == 0xA0 || value > 0xEF { - can_be_shift_jis = false; - } else if value > 0xA0 && value < 0xE0 { - sjis_katakana_chars += 1; - sjis_cur_double_bytes_word_length = 0; - sjis_cur_katakana_word_length += 1; - if sjis_cur_katakana_word_length > sjis_max_katakana_word_length { - sjis_max_katakana_word_length = sjis_cur_katakana_word_length; - } - } else if value > 0x7F { - sjis_bytes_left += 1; - //sjisDoubleBytesChars++; - sjis_cur_katakana_word_length = 0; - sjis_cur_double_bytes_word_length += 1; - if sjis_cur_double_bytes_word_length > sjis_max_double_bytes_word_length { - sjis_max_double_bytes_word_length = sjis_cur_double_bytes_word_length; - } - } else { - //sjisLowChars++; - sjis_cur_katakana_word_length = 0; - sjis_cur_double_bytes_word_length = 0; - } - } - } - - if can_be_utf8 && utf8_bytes_left > 0 { - can_be_utf8 = false; - } - if can_be_shift_jis && sjis_bytes_left > 0 { - can_be_shift_jis = false; - } - - // Easy -- if there is BOM or at least 1 valid not-single byte character (and no evidence it can't be UTF-8), done - if can_be_utf8 && (utf8bom || utf2_bytes_chars + utf3_bytes_chars + utf4_bytes_chars > 0) { - return encoding::all::UTF_8; - } - // Easy -- if assuming Shift_JIS or >= 3 valid consecutive not-ascii characters (and no evidence it can't be), done - if can_be_shift_jis - && (ASSUME_SHIFT_JIS - || sjis_max_katakana_word_length >= 3 - || sjis_max_double_bytes_word_length >= 3) - { - return encoding::label::encoding_from_whatwg_label("SJIS").unwrap(); - } - // Distinguishing Shift_JIS and ISO-8859-1 can be a little tough for short words. The crude heuristic is: - // - If we saw - // - only two consecutive katakana chars in the whole text, or - // - at least 10% of bytes that could be "upper" not-alphanumeric Latin1, - // - then we conclude Shift_JIS, else ISO-8859-1 - if can_be_iso88591 && can_be_shift_jis { - return if (sjis_max_katakana_word_length == 2 && sjis_katakana_chars == 2) - || iso_high_other * 10 >= length - { - encoding::label::encoding_from_whatwg_label("SJIS").unwrap() - } else { - encoding::all::ISO_8859_1 - }; - } - - // Otherwise, try in order ISO-8859-1, Shift JIS, UTF-8 and fall back to default platform encoding - if can_be_iso88591 { - return encoding::all::ISO_8859_1; - } - if can_be_shift_jis { - return encoding::label::encoding_from_whatwg_label("SJIS").unwrap(); - } - if can_be_utf8 { - return encoding::all::UTF_8; - } - // Otherwise, we take a wild guess with platform encoding - return encoding::all::UTF_8; - } -} - -/* - * Copyright 2007 ZXing authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// package com.google.zxing.common; - -// import java.util.Arrays; - -static EMPTY_BITS: [u32; 0] = [0; 0]; -static LOAD_FACTOR: f32 = 0.75f32; - -/** - *

A simple, fast array of bits, represented compactly by an array of ints internally.

- * - * @author Sean Owen - */ -#[derive(Debug, PartialEq, Eq, Clone, Hash)] -pub struct BitArray { - bits: Vec, - size: usize, -} - -impl BitArray { - pub fn new() -> Self { - Self { - bits: EMPTY_BITS.to_vec(), - size: 0, - } - } - - pub fn with_size(size: usize) -> Self { - Self { - bits: BitArray::makeArray(size), - size: size, - } - } - - // For testing only - pub fn with_initial_values(bits: Vec, size: usize) -> Self { - Self { - bits: bits, - size: size, - } - } - - pub fn getSize(&self) -> usize { - self.size - } - - pub fn getSizeInBytes(&self) -> usize { - return (self.size + 7) / 8; - } - - fn ensure_capacity(&mut self, newSize: usize) { - if newSize > self.bits.len() * 32 { - let mut newBits = BitArray::makeArray((newSize as f32 / LOAD_FACTOR).ceil() as usize); - //System.arraycopy(bits, 0, newBits, 0, bits.length); - newBits[0..self.bits.len()].clone_from_slice(&self.bits[0..self.bits.len()]); - self.bits = newBits; - } - } - - /** - * @param i bit to get - * @return true iff bit i is set - */ - pub fn get(&self, i: usize) -> bool { - return (self.bits[i / 32] & (1 << (i & 0x1F))) != 0; - } - - /** - * Sets bit i. - * - * @param i bit to set - */ - pub fn set(&mut self, i: usize) { - self.bits[i / 32] |= 1 << (i & 0x1F); - } - - /** - * Flips bit i. - * - * @param i bit to set - */ - pub fn flip(&mut self, i: usize) { - self.bits[i / 32] ^= 1 << (i & 0x1F); - } - - /** - * @param from first bit to check - * @return index of first bit that is set, starting from the given index, or size if none are set - * at or beyond this given index - * @see #getNextUnset(int) - */ - pub fn getNextSet(&self, from: usize) -> usize { - if from >= self.size { - return self.size; - } - let mut bitsOffset = from / 32; - let mut currentBits = self.bits[bitsOffset] as i64; - // mask off lesser bits first - currentBits &= -(1 << (from & 0x1F)); - while currentBits == 0 { - bitsOffset += 1; - if bitsOffset == self.bits.len() { - return self.size; - } - currentBits = self.bits[bitsOffset] as i64; - } - let result = (bitsOffset * 32) + currentBits.trailing_zeros() as usize; - cmp::min(result, self.size) - } - - /** - * @param from index to start looking for unset bit - * @return index of next unset bit, or {@code size} if none are unset until the end - * @see #getNextSet(int) - */ - pub fn getNextUnset(&self, from: usize) -> usize { - if from >= self.size { - return self.size; - } - let mut bitsOffset = from / 32; - let mut currentBits = !self.bits[bitsOffset] as i32; - // mask off lesser bits first - currentBits &= -(1 << (from & 0x1F)); - while currentBits == 0 { - bitsOffset += 1; - if bitsOffset == self.bits.len() { - return self.size; - } - currentBits = !self.bits[bitsOffset] as i32; - } - let result = (bitsOffset * 32) + currentBits.trailing_zeros() as usize; - return cmp::min(result, self.size); - } - - /** - * Sets a block of 32 bits, starting at bit i. - * - * @param i first bit to set - * @param newBits the new value of the next 32 bits. Note again that the least-significant bit - * corresponds to bit i, the next-least-significant to i+1, and so on. - */ - pub fn setBulk(&mut self, i: usize, newBits: u32) { - self.bits[i / 32] = newBits; - } - - /** - * Sets a range of bits. - * - * @param start start of range, inclusive. - * @param end end of range, exclusive - */ - pub fn setRange(&mut self, start: usize, end: usize) -> Result<(), Exceptions> { - let mut end = end; - if end < start || end > self.size { - return Err(Exceptions::IllegalArgumentException( - "end < start || start < 0 || end > self.size".to_owned(), - )); - } - if end == start { - return Ok(()); - } - end -= 1; // will be easier to treat this as the last actually set bit -- inclusive - let firstInt = start / 32; - let lastInt = end / 32; - for i in firstInt..=lastInt { - //for (int i = firstInt; i <= lastInt; i++) { - let firstBit = if i > firstInt { 0 } else { start & 0x1F }; - let lastBit = if i < lastInt { 31 } else { end & 0x1F }; - // Ones from firstBit to lastBit, inclusive - let mask: u64 = (2 << lastBit) - (1 << firstBit); - self.bits[i] |= mask as u32; - } - Ok(()) - } - - /** - * Clears all bits (sets to false). - */ - pub fn clear(&mut self) { - let max = self.bits.len(); - for i in 0..max { - //for (int i = 0; i < max; i++) { - self.bits[i] = 0; - } - } - - /** - * Efficient method to check if a range of bits is set, or not set. - * - * @param start start of range, inclusive. - * @param end end of range, exclusive - * @param value if true, checks that bits in range are set, otherwise checks that they are not set - * @return true iff all bits are set or not set in range, according to value argument - * @throws IllegalArgumentException if end is less than start or the range is not contained in the array - */ - pub fn isRange(&self, start: usize, end: usize, value: bool) -> Result { - let mut end = end; - if end < start || end > self.size { - return Err(Exceptions::IllegalArgumentException( - "end < start || start < 0 || end > self.size".to_owned(), - )); - } - if end == start { - return Ok(true); // empty range matches - } - end -= 1; // will be easier to treat this as the last actually set bit -- inclusive - let firstInt = start / 32; - let lastInt = end / 32; - for i in firstInt..=lastInt { - //for (int i = firstInt; i <= lastInt; i++) { - let firstBit = if i > firstInt { 0 } else { start & 0x1F }; - let lastBit = if i < lastInt { 31 } else { end & 0x1F }; - // Ones from firstBit to lastBit, inclusive - let mask: u64 = (2 << lastBit) - (1 << firstBit); - - // Return false if we're looking for 1s and the masked bits[i] isn't all 1s (that is, - // equals the mask, or we're looking for 0s and the masked portion is not all 0s - if (self.bits[i] & mask as u32) != (if value { mask as u32 } else { 0 }) { - return Ok(false); - } - } - return Ok(true); - } - - pub fn appendBit(&mut self, bit: bool) { - self.ensure_capacity(self.size + 1); - if bit { - self.bits[self.size / 32] |= 1 << (self.size & 0x1F); - } - self.size += 1; - } - - /** - * Appends the least-significant bits, from value, in order from most-significant to - * least-significant. For example, appending 6 bits from 0x000001E will append the bits - * 0, 1, 1, 1, 1, 0 in that order. - * - * @param value {@code int} containing bits to append - * @param numBits bits from value to append - */ - pub fn appendBits(&mut self, value: u32, num_bits: usize) -> Result<(), Exceptions> { - if num_bits > 32 { - return Err(Exceptions::IllegalArgumentException( - "Num bits must be between 0 and 32".to_owned(), - )); - } - - if num_bits == 0 { - return Ok(()); - } - - let mut next_size = self.size; - self.ensure_capacity(next_size + num_bits); - for numBitsLeft in (0..num_bits).rev() { - //for (int numBitsLeft = numBits - 1; numBitsLeft >= 0; numBitsLeft--) { - if (value & (1 << numBitsLeft)) != 0 { - self.bits[next_size / 32] |= 1 << (next_size & 0x1F); - } - next_size += 1; - } - self.size = next_size; - Ok(()) - } - - pub fn appendBitArray(&mut self, other: BitArray) { - let otherSize = other.size; - self.ensure_capacity(self.size + otherSize); - for i in 0..otherSize { - //for (int i = 0; i < otherSize; i++) { - self.appendBit(other.get(i)); - } - } - - pub fn xor(&mut self, other: &BitArray) -> Result<(), Exceptions> { - if self.size != other.size { - return Err(Exceptions::IllegalArgumentException( - "Sizes don't match".to_owned(), - )); - } - for i in 0..self.bits.len() { - //for (int i = 0; i < bits.length; i++) { - // The last int could be incomplete (i.e. not have 32 bits in - // it) but there is no problem since 0 XOR 0 == 0. - self.bits[i] ^= other.bits[i]; - } - Ok(()) - } - - /** - * - * @param bitOffset first bit to start writing - * @param array array to write into. Bytes are written most-significant byte first. This is the opposite - * of the internal representation, which is exposed by {@link #getBitArray()} - * @param offset position in array to start writing - * @param numBytes how many bytes to write - */ - pub fn toBytes(&self, bitOffset: usize, array: &mut [u8], offset: usize, numBytes: usize) { - let mut bitOffset = bitOffset; - for i in 0..numBytes { - //for (int i = 0; i < numBytes; i++) { - let mut theByte = 0; - for j in 0..8 { - //for (int j = 0; j < 8; j++) { - if self.get(bitOffset) { - theByte |= 1 << (7 - j); - } - bitOffset += 1; - } - array[offset + i] = theByte; - } - } - - /** - * @return underlying array of ints. The first element holds the first 32 bits, and the least - * significant bit is bit 0. - */ - pub fn getBitArray(&self) -> &Vec { - return &self.bits; - } - - /** - * Reverses all bits in the array. - */ - pub fn reverse(&mut self) { - let mut newBits = vec![0; self.bits.len()]; - // reverse all int's first - let len = (self.size - 1) / 32; - let oldBitsLen = len + 1; - for i in 0..oldBitsLen { - //for (int i = 0; i < oldBitsLen; i++) { - newBits[len - i] = self.bits[i].reverse_bits(); - } - // now correct the int's if the bit size isn't a multiple of 32 - if self.size != oldBitsLen * 32 { - let leftOffset = oldBitsLen * 32 - self.size; - let mut currentInt = newBits[0] >> leftOffset; - for i in 1..oldBitsLen { - //for (int i = 1; i < oldBitsLen; i++) { - let nextInt = newBits[i]; - currentInt |= nextInt << (32 - leftOffset); - newBits[i - 1] = currentInt; - currentInt = nextInt >> leftOffset; - } - newBits[oldBitsLen - 1] = currentInt; - } - self.bits = newBits; - } - - fn makeArray(size: usize) -> Vec { - return vec![0; (size + 31) / 32]; - } - - // @Override - // public boolean equals(Object o) { - // if (!(o instanceof BitArray)) { - // return false; - // } - // BitArray other = (BitArray) o; - // return size == other.size && Arrays.equals(bits, other.bits); - // } - - // @Override - // public int hashCode() { - // return 31 * size + Arrays.hashCode(bits); - // } - - // @Override - // public BitArray clone() { - // return new BitArray(bits.clone(), size); - // } -} - -impl fmt::Display for BitArray { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut _str = String::with_capacity(self.size + (self.size / 8) + 1); - for i in 0..self.size { - //for (int i = 0; i < size; i++) { - if (i & 0x07) == 0 { - _str.push_str(" "); - } - _str.push_str(if self.get(i) { "X" } else { "." }); - } - write!(f, "{}", _str) - } -} +mod bit_array; +pub use bit_array::*; /* * Copyright 2007 ZXing authors @@ -745,3398 +82,48 @@ pub trait DetectorRXingResult { // points: Vec, // } -/* - * Copyright 2007 ZXing authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +mod bit_matrix; +pub use bit_matrix::*; -// package com.google.zxing.common; +mod eci_input; +pub use eci_input::*; -// import java.util.Arrays; +mod bit_source; +pub use bit_source::*; -/** - *

Represents a 2D matrix of bits. In function arguments below, and throughout the common - * module, x is the column position, and y is the row position. The ordering is always x, y. - * The origin is at the top-left.

- * - *

Internally the bits are represented in a 1-D array of 32-bit ints. However, each row begins - * with a new int. This is done intentionally so that we can copy out a row into a BitArray very - * efficiently.

- * - *

The ordering of bits is row-major. Within each int, the least significant bits are used first, - * meaning they represent lower x values. This is compatible with BitArray's implementation.

- * - * @author Sean Owen - * @author dswitkin@google.com (Daniel Switkin) - */ -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct BitMatrix { - width: u32, - height: u32, - row_size: usize, - bits: Vec, -} +mod perspective_transform; +pub use perspective_transform::*; -impl BitMatrix { - /** - * Creates an empty square {@code BitMatrix}. - * - * @param dimension height and width - */ - pub fn with_single_dimension(dimension: u32) -> Self { - Self::new(dimension, dimension).unwrap() - } +mod decoder_rxing_result; +pub use decoder_rxing_result::*; - /** - * Creates an empty {@code BitMatrix}. - * - * @param width bit matrix width - * @param height bit matrix height - */ - pub fn new(width: u32, height: u32) -> Result { - if width < 1 || height < 1 { - return Err(Exceptions::IllegalArgumentException( - "Both dimensions must be greater than 0".to_owned(), - )); - } - Ok(Self { - width, - height, - row_size: ((width + 31) / 32) as usize, - bits: vec![0; (((width + 31) / 32) * height) as usize], - }) - // this.width = width; - // this.height = height; - // this.rowSize = (width + 31) / 32; - // bits = new int[rowSize * height]; - } +mod bit_source_builder; +pub use bit_source_builder::*; - fn with_all_data(&self, width: u32, height: u32, rowSize: usize, bits: Vec) -> Self { - Self { - width, - height, - row_size: rowSize, - bits, - } - } +mod grid_sampler; +pub use grid_sampler::*; - /** - * Interprets a 2D array of booleans as a {@code BitMatrix}, where "true" means an "on" bit. - * - * @param image bits of the image, as a row-major 2D array. Elements are arrays representing rows - * @return {@code BitMatrix} representation of image - */ - pub fn parse_bools(image: &Vec>) -> Self { - let height: u32 = image.len().try_into().unwrap(); - let width: u32 = image[0].len().try_into().unwrap(); - let mut bits = BitMatrix::new(width, height).unwrap(); - for i in 0..height as usize { - //for (int i = 0; i < height; i++) { - let imageI = &image[i]; - for j in 0..width as usize { - //for (int j = 0; j < width; j++) { - if imageI[j] { - bits.set(j as u32, i as u32); - } - } - } - return bits; - } +mod default_grid_sampler; +pub use default_grid_sampler::*; - pub fn parse_strings( - string_representation: &str, - set_string: &str, - unset_string: &str, - ) -> Result { - // cannot pass nulls in rust - // if (stringRepresentation == null) { - // throw new IllegalArgumentException(); - // } +mod character_set_eci; +pub use character_set_eci::*; - let mut bits = vec![false; string_representation.len()]; - let mut bitsPos = 0; - let mut rowStartPos = 0; - let mut rowLength = 0; //-1; - let mut first_run = true; - let mut nRows = 0; - let mut pos = 0; - while pos < string_representation.len() { - if string_representation.chars().nth(pos).unwrap() == '\n' - || string_representation.chars().nth(pos).unwrap() == '\r' - { - if bitsPos > rowStartPos { - //if rowLength == -1 { - if first_run { - first_run = false; - rowLength = bitsPos - rowStartPos; - } else if bitsPos - rowStartPos != rowLength { - return Err(Exceptions::IllegalArgumentException( - "row lengths do not match".to_owned(), - )); - } - rowStartPos = bitsPos; - nRows += 1; - } - pos += 1; - } else if string_representation[pos..].starts_with(set_string) { - pos += set_string.len(); - bits[bitsPos] = true; - bitsPos += 1; - } else if string_representation[pos..].starts_with(unset_string) { - pos += unset_string.len(); - bits[bitsPos] = false; - bitsPos += 1; - } else { - return Err(Exceptions::IllegalArgumentException(format!( - "illegal character encountered: {}", - string_representation[pos..].to_owned() - ))); - } - } +mod eci_string_builder; +pub use eci_string_builder::*; - // no EOL at end? - if bitsPos > rowStartPos { - //if rowLength == -1 { - if first_run { - first_run = false; - rowLength = bitsPos - rowStartPos; - } else if bitsPos - rowStartPos != rowLength { - return Err(Exceptions::IllegalArgumentException( - "row lengths do not match".to_owned(), - )); - } - nRows += 1; - } +mod eci_encoder_set; +pub use eci_encoder_set::*; - let mut matrix = BitMatrix::new(rowLength.try_into().unwrap(), nRows)?; - for i in 0..bitsPos { - //for (int i = 0; i < bitsPos; i++) { - if bits[i] { - matrix.set( - (i % rowLength).try_into().unwrap(), - (i / rowLength).try_into().unwrap(), - ); - } - } - return Ok(matrix); - } +mod minimal_eci_input; +pub use minimal_eci_input::*; - /** - *

Gets the requested bit, where true means black.

- * - * @param x The horizontal component (i.e. which column) - * @param y The vertical component (i.e. which row) - * @return value of given bit in matrix - */ - pub fn get(&self, x: u32, y: u32) -> bool { - let offset = y as usize * self.row_size + (x as usize / 32); - return ((self.bits[offset] >> (x & 0x1f)) & 1) != 0; - } +mod input_edge; +pub use input_edge::*; - /** - *

Sets the given bit to true.

- * - * @param x The horizontal component (i.e. which column) - * @param y The vertical component (i.e. which row) - */ - pub fn set(&mut self, x: u32, y: u32) { - let offset = y as usize * self.row_size + (x as usize / 32); - self.bits[offset] |= 1 << (x & 0x1f); - } - pub fn unset(&mut self, x: u32, y: u32) { - let offset = y as usize * self.row_size + (x as usize / 32); - self.bits[offset] &= !(1 << (x & 0x1f)); - } +mod global_histogram_binarizer; +pub use global_histogram_binarizer::*; - /** - *

Flips the given bit.

- * - * @param x The horizontal component (i.e. which column) - * @param y The vertical component (i.e. which row) - */ - pub fn flip_coords(&mut self, x: u32, y: u32) { - let offset = y as usize * self.row_size + (x as usize / 32); - self.bits[offset] ^= 1 << (x & 0x1f); - } - - /** - *

Flips every bit in the matrix.

- */ - pub fn flip_self(&mut self) { - let max = self.bits.len(); - for i in 0..max { - //for (int i = 0; i < max; i++) { - self.bits[i] = !self.bits[i]; - } - } - - /** - * Exclusive-or (XOR): Flip the bit in this {@code BitMatrix} if the corresponding - * mask bit is set. - * - * @param mask XOR mask - */ - pub fn xor(&mut self, mask: &BitMatrix) -> Result<(), Exceptions> { - if self.width != mask.width || self.height != mask.height || self.row_size != mask.row_size - { - return Err(Exceptions::IllegalArgumentException( - "input matrix dimensions do not match".to_owned(), - )); - } - let rowArray = BitArray::with_size(self.width as usize); - for y in 0..self.height { - //for (int y = 0; y < height; y++) { - let offset = y as usize * self.row_size; - let tmp = mask.getRow(y, &rowArray); - let row = tmp.getBitArray(); - for x in 0..self.row_size { - //for (int x = 0; x < rowSize; x++) { - self.bits[offset + x] ^= row[x]; - } - } - Ok(()) - } - - /** - * Clears all bits (sets to false). - */ - pub fn clear(&mut self) { - let max = self.bits.len(); - for i in 0..max { - //for (int i = 0; i < max; i++) { - self.bits[i] = 0; - } - } - - /** - *

Sets a square region of the bit matrix to true.

- * - * @param left The horizontal position to begin at (inclusive) - * @param top The vertical position to begin at (inclusive) - * @param width The width of the region - * @param height The height of the region - */ - pub fn setRegion( - &mut self, - left: u32, - top: u32, - width: u32, - height: u32, - ) -> Result<(), Exceptions> { - // if top < 0 || left < 0 { - // return Err(Exceptions::IllegalArgumentException( - // "Left and top must be nonnegative".to_owned(), - // )); - // } - if height < 1 || width < 1 { - return Err(Exceptions::IllegalArgumentException( - "Height and width must be at least 1".to_owned(), - )); - } - let right = left + width; - let bottom = top + height; - if bottom > self.height || right > self.width { - return Err(Exceptions::IllegalArgumentException( - "The region must fit inside the matrix".to_owned(), - )); - } - for y in top..bottom { - //for (int y = top; y < bottom; y++) { - let offset = y as usize * self.row_size; - for x in left..right { - //for (int x = left; x < right; x++) { - self.bits[offset + (x as usize / 32)] |= 1 << (x & 0x1f); - } - } - Ok(()) - } - - /** - * A fast method to retrieve one row of data from the matrix as a BitArray. - * - * @param y The row to retrieve - * @param row An optional caller-allocated BitArray, will be allocated if null or too small - * @return The resulting BitArray - this reference should always be used even when passing - * your own row - */ - pub fn getRow(&self, y: u32, row: &BitArray) -> BitArray { - let mut rw: BitArray = if row.getSize() < self.width as usize { - BitArray::with_size(self.width as usize) - } else { - let mut z = row.clone(); - z.clear(); - z - // row.clear(); - // row.clone() - }; - - let offset = y as usize * self.row_size; - for x in 0..self.row_size { - //for (int x = 0; x < rowSize; x++) { - rw.setBulk(x * 32, self.bits[offset + x]); - } - return rw; - } - - /** - * @param y row to set - * @param row {@link BitArray} to copy from - */ - pub fn setRow(&mut self, y: u32, row: &BitArray) { - return self.bits[y as usize * self.row_size..y as usize * self.row_size + self.row_size] - .clone_from_slice(&row.getBitArray()[0..self.row_size]); - //System.arraycopy(row.getBitArray(), 0, self.bits, y * self.rowSize, self.rowSize); - } - - /** - * Modifies this {@code BitMatrix} to represent the same but rotated the given degrees (0, 90, 180, 270) - * - * @param degrees number of degrees to rotate through counter-clockwise (0, 90, 180, 270) - */ - pub fn rotate(&mut self, degrees: u32) -> Result<(), Exceptions> { - match degrees % 360 { - 0 => Ok(()), - 90 => { - self.rotate90(); - Ok(()) - } - 180 => { - self.rotate180(); - Ok(()) - } - 270 => { - self.rotate90(); - self.rotate180(); - Ok(()) - } - _ => Err(Exceptions::IllegalArgumentException( - "degrees must be a multiple of 0, 90, 180, or 270".to_owned(), - )), - } - } - - /** - * Modifies this {@code BitMatrix} to represent the same but rotated 180 degrees - */ - pub fn rotate180(&mut self) { - let mut topRow = BitArray::with_size(self.width as usize); - let mut bottomRow = BitArray::with_size(self.width as usize); - let maxHeight = (self.height + 1) / 2; - for i in 0..maxHeight { - //for (int i = 0; i < maxHeight; i++) { - topRow = self.getRow(i, &topRow); - let bottomRowIndex = self.height - 1 - i; - bottomRow = self.getRow(bottomRowIndex, &bottomRow); - topRow.reverse(); - bottomRow.reverse(); - self.setRow(i, &bottomRow); - self.setRow(bottomRowIndex, &topRow); - } - } - - /** - * Modifies this {@code BitMatrix} to represent the same but rotated 90 degrees counterclockwise - */ - pub fn rotate90(&mut self) { - let newWidth = self.height; - let newHeight = self.width; - let newRowSize = (newWidth + 31) / 32; - let mut newBits = vec![0; (newRowSize * newHeight).try_into().unwrap()]; - - for y in 0..self.height { - //for (int y = 0; y < height; y++) { - for x in 0..self.width { - //for (int x = 0; x < width; x++) { - let offset = y as usize * self.row_size + (x as usize / 32); - if ((self.bits[offset] >> (x & 0x1f)) & 1) != 0 { - let newOffset: usize = ((newHeight - 1 - x) * newRowSize + (y / 32)) - .try_into() - .unwrap(); - newBits[newOffset] |= 1 << (y & 0x1f); - } - } - } - self.width = newWidth; - self.height = newHeight; - self.row_size = newRowSize.try_into().unwrap(); - self.bits = newBits; - } - - /** - * This is useful in detecting the enclosing rectangle of a 'pure' barcode. - * - * @return {@code left,top,width,height} enclosing rectangle of all 1 bits, or null if it is all white - */ - pub fn getEnclosingRectangle(&self) -> Option> { - let mut left = self.width; - let mut top = self.height; - // let right = -1; - // let bottom = -1; - let mut right: u32 = 0; - let mut bottom = 0; - - for y in 0..self.height { - //for (int y = 0; y < height; y++) { - for x32 in 0..self.row_size { - //for (int x32 = 0; x32 < rowSize; x32++) { - let theBits = self.bits[y as usize * self.row_size + x32]; - if theBits != 0 { - if y < top { - top = y; - } - if y > bottom { - bottom = y; - } - if x32 * 32 < left.try_into().unwrap() { - let mut bit = 0; - while (theBits << (31 - bit)) == 0 { - bit += 1; - } - if (x32 * 32 + bit) < left.try_into().unwrap() { - left = (x32 * 32 + bit).try_into().unwrap(); - } - } - if x32 * 32 + 31 > right.try_into().unwrap() { - let mut bit = 31; - while (theBits >> bit) == 0 { - bit -= 1; - } - if (x32 * 32 + bit) > right.try_into().unwrap() { - right = (x32 * 32 + bit).try_into().unwrap(); - } - } - } - } - } - - if right < left || bottom < top { - return None; - } - - return Some(vec![left, top, right - left + 1, bottom - top + 1]); - } - - /** - * This is useful in detecting a corner of a 'pure' barcode. - * - * @return {@code x,y} coordinate of top-left-most 1 bit, or null if it is all white - */ - pub fn getTopLeftOnBit(&self) -> Option> { - let mut bitsOffset = 0; - while bitsOffset < self.bits.len() && self.bits[bitsOffset] == 0 { - bitsOffset += 1; - } - if bitsOffset == self.bits.len() { - return None; - } - let y = bitsOffset / self.row_size; - let mut x = (bitsOffset % self.row_size) * 32; - - let theBits = self.bits[bitsOffset]; - let mut bit = 0; - while (theBits << (31 - bit)) == 0 { - bit += 1; - } - x += bit; - return Some(vec![x as u32, y as u32]); - } - - pub fn getBottomRightOnBit(&self) -> Option> { - let mut bitsOffset = self.bits.len() as i64 - 1; - while bitsOffset >= 0 && self.bits[bitsOffset as usize] == 0 { - bitsOffset -= 1; - } - if bitsOffset < 0 { - return None; - } - - let y = bitsOffset as usize / self.row_size; - let mut x = (bitsOffset as usize % self.row_size) * 32; - - let theBits = self.bits[bitsOffset as usize]; - let mut bit = 31; - while (theBits >> bit) == 0 { - bit -= 1; - } - x += bit; - - return Some(vec![x as u32, y as u32]); - } - - /** - * @return The width of the matrix - */ - pub fn getWidth(&self) -> u32 { - return self.width; - } - - /** - * @return The height of the matrix - */ - pub fn getHeight(&self) -> u32 { - return self.height; - } - - /** - * @return The row size of the matrix - */ - pub fn getRowSize(&self) -> usize { - return self.row_size; - } - - // @Override - // public boolean equals(Object o) { - // if (!(o instanceof BitMatrix)) { - // return false; - // } - // BitMatrix other = (BitMatrix) o; - // return width == other.width && height == other.height && rowSize == other.rowSize && - // Arrays.equals(bits, other.bits); - // } - - // @Override - // public int hashCode() { - // int hash = width; - // hash = 31 * hash + width; - // hash = 31 * hash + height; - // hash = 31 * hash + rowSize; - // hash = 31 * hash + Arrays.hashCode(bits); - // return hash; - // } - - /** - * @param setString representation of a set bit - * @param unsetString representation of an unset bit - * @return string representation of entire matrix utilizing given strings - */ - pub fn toString(&self, setString: &str, unsetString: &str) -> String { - return self.buildToString(setString, unsetString, "\n"); - } - - /** - * @param setString representation of a set bit - * @param unsetString representation of an unset bit - * @param lineSeparator newline character in string representation - * @return string representation of entire matrix utilizing given strings and line separator - * @deprecated call {@link #toString(String,String)} only, which uses \n line separator always - */ - // @Deprecated - // public String toString(String setString, String unsetString, String lineSeparator) { - // return buildToString(setString, unsetString, lineSeparator); - // } - - fn buildToString(&self, setString: &str, unsetString: &str, lineSeparator: &str) -> String { - let mut result = - String::with_capacity((self.height * (self.width + 1)).try_into().unwrap()); - for y in 0..self.height { - //for (int y = 0; y < height; y++) { - for x in 0..self.width { - //for (int x = 0; x < width; x++) { - result.push_str(if self.get(x, y) { - setString - } else { - unsetString - }); - } - result.push_str(lineSeparator); - } - return result; - } - - // @Override - // public BitMatrix clone() { - // return new BitMatrix(width, height, rowSize, bits.clone()); - // } -} - -impl fmt::Display for BitMatrix { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.toString("X ", " ")) - } -} - -/* - * Copyright 2021 ZXing authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -//package com.google.zxing.common; - -/** - * Interface to navigate a sequence of ECIs and bytes. - * - * @author Alex Geller - */ -pub trait ECIInput { - /** - * Returns the length of this input. The length is the number - * of {@code byte}s in or ECIs in the sequence. - * - * @return the number of {@code char}s in this sequence - */ - fn length(&self) -> usize; - - /** - * Returns the {@code byte} value at the specified index. An index ranges from zero - * to {@code length() - 1}. The first {@code byte} value of the sequence is at - * index zero, the next at index one, and so on, as for array - * indexing. - * - * @param index the index of the {@code byte} value to be returned - * - * @return the specified {@code byte} value as character or the FNC1 character - * - * @throws IndexOutOfBoundsException - * if the {@code index} argument is negative or not less than - * {@code length()} - * @throws IllegalArgumentException - * if the value at the {@code index} argument is an ECI (@see #isECI) - */ - fn charAt(&self, index: usize) -> Result; - - /** - * Returns a {@code CharSequence} that is a subsequence of this sequence. - * The subsequence starts with the {@code char} value at the specified index and - * ends with the {@code char} value at index {@code end - 1}. The length - * (in {@code char}s) of the - * returned sequence is {@code end - start}, so if {@code start == end} - * then an empty sequence is returned. - * - * @param start the start index, inclusive - * @param end the end index, exclusive - * - * @return the specified subsequence - * - * @throws IndexOutOfBoundsException - * if {@code start} or {@code end} are negative, - * if {@code end} is greater than {@code length()}, - * or if {@code start} is greater than {@code end} - * @throws IllegalArgumentException - * if a value in the range {@code start}-{@code end} is an ECI (@see #isECI) - */ - fn subSequence(&self, start: usize, end: usize) -> Result, Exceptions>; - - /** - * Determines if a value is an ECI - * - * @param index the index of the value - * - * @return true if the value at position {@code index} is an ECI - * - * @throws IndexOutOfBoundsException - * if the {@code index} argument is negative or not less than - * {@code length()} - */ - fn isECI(&self, index: u32) -> Result; - - /** - * Returns the {@code int} ECI value at the specified index. An index ranges from zero - * to {@code length() - 1}. The first {@code byte} value of the sequence is at - * index zero, the next at index one, and so on, as for array - * indexing. - * - * @param index the index of the {@code int} value to be returned - * - * @return the specified {@code int} ECI value. - * The ECI specified the encoding of all bytes with a higher index until the - * next ECI or until the end of the input if no other ECI follows. - * - * @throws IndexOutOfBoundsException - * if the {@code index} argument is negative or not less than - * {@code length()} - * @throws IllegalArgumentException - * if the value at the {@code index} argument is not an ECI (@see #isECI) - */ - fn getECIValue(&self, index: usize) -> Result; - fn haveNCharacters(&self, index: usize, n: usize) -> bool; -} - -/* - * Copyright 2007 ZXing authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -//package com.google.zxing.common; - -/** - *

This provides an easy abstraction to read bits at a time from a sequence of bytes, where the - * number of bits read is not often a multiple of 8.

- * - *

This class is thread-safe but not reentrant -- unless the caller modifies the bytes array - * it passed in, in which case all bets are off.

- * - * @author Sean Owen - */ -pub struct BitSource { - bytes: Vec, - byte_offset: usize, - bit_offset: usize, -} - -impl BitSource { - /** - * @param bytes bytes from which this will read bits. Bits will be read from the first byte first. - * Bits are read within a byte from most-significant to least-significant bit. - */ - pub fn new(bytes: Vec) -> Self { - Self { - bytes, - byte_offset: 0, - bit_offset: 0, - } - } - - /** - * @return index of next bit in current byte which would be read by the next call to {@link #readBits(int)}. - */ - pub fn getBitOffset(&self) -> usize { - return self.bit_offset; - } - - /** - * @return index of next byte in input byte array which would be read by the next call to {@link #readBits(int)}. - */ - pub fn getByteOffset(&self) -> usize { - return self.byte_offset; - } - - /** - * @param numBits number of bits to read - * @return int representing the bits read. The bits will appear as the least-significant - * bits of the int - * @throws IllegalArgumentException if numBits isn't in [1,32] or more than is available - */ - pub fn readBits(&mut self, numBits: usize) -> Result { - if numBits < 1 || numBits > 32 || numBits > self.available() { - return Err(Exceptions::IllegalArgumentException(numBits.to_string())); - } - - let mut result: u32 = 0; - - let mut num_bits = numBits; - - // First, read remainder from current byte - if self.bit_offset > 0 { - let bitsLeft = 8 - self.bit_offset; - let toRead = cmp::min(num_bits, bitsLeft); - let bitsToNotRead = bitsLeft - toRead; - let mask = (0xFF >> (8 - toRead)) << bitsToNotRead; - - result = (self.bytes[self.byte_offset] & mask) as u32 >> bitsToNotRead; - num_bits -= toRead; - self.bit_offset += toRead; - if self.bit_offset == 8 { - self.bit_offset = 0; - self.byte_offset += 1; - } - } - - // Next read whole bytes - if num_bits > 0 { - while num_bits >= 8 { - result = (result << 8) | (self.bytes[self.byte_offset] & 0xFF) as u32; - // result = ((result as u16) << 8) as u8 | (self.bytes[self.byte_offset]); - self.byte_offset += 1; - num_bits -= 8; - } - - // Finally read a partial byte - if num_bits > 0 { - let bits_to_not_read = 8 - num_bits; - let mask = (0xFF >> bits_to_not_read) << bits_to_not_read; - result = (result << num_bits) - | ((self.bytes[self.byte_offset] & mask) as u32 >> bits_to_not_read); - self.bit_offset += num_bits; - } - } - - return Ok(result); - } - - /** - * @return number of bits that can be read successfully - */ - pub fn available(&self) -> usize { - return 8 * (self.bytes.len() - self.byte_offset) - self.bit_offset; - } -} - -/* - * Copyright 2007 ZXing authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// package com.google.zxing.common; - -/** - *

This class implements a perspective transform in two dimensions. Given four source and four - * destination points, it will compute the transformation implied between them. The code is based - * directly upon section 3.4.2 of George Wolberg's "Digital Image Warping"; see pages 54-56.

- * - * @author Sean Owen - */ -pub struct PerspectiveTransform { - a11: f32, - a12: f32, - a13: f32, - a21: f32, - a22: f32, - a23: f32, - a31: f32, - a32: f32, - a33: f32, -} - -impl PerspectiveTransform { - fn new( - a11: f32, - a21: f32, - a31: f32, - a12: f32, - a22: f32, - a32: f32, - a13: f32, - a23: f32, - a33: f32, - ) -> Self { - Self { - a11, - a12, - a13, - a21, - a22, - a23, - a31, - a32, - a33, - } - } - - pub fn quadrilateralToQuadrilateral( - x0: f32, - y0: f32, - x1: f32, - y1: f32, - x2: f32, - y2: f32, - x3: f32, - y3: f32, - x0p: f32, - y0p: f32, - x1p: f32, - y1p: f32, - x2p: f32, - y2p: f32, - x3p: f32, - y3p: f32, - ) -> Self { - let q_to_s = PerspectiveTransform::quadrilateralToSquare(x0, y0, x1, y1, x2, y2, x3, y3); - let s_to_q = - PerspectiveTransform::squareToQuadrilateral(x0p, y0p, x1p, y1p, x2p, y2p, x3p, y3p); - return s_to_q.times(&q_to_s); - } - - pub fn transform_points_single(&self, points: &mut [f32]) { - let a11 = self.a11; - let a12 = self.a12; - let a13 = self.a13; - let a21 = self.a21; - let a22 = self.a22; - let a23 = self.a23; - let a31 = self.a31; - let a32 = self.a32; - let a33 = self.a33; - let maxI = points.len() - 1; // points.length must be even - let mut i = 0; - while i < maxI { - // for (int i = 0; i < maxI; i += 2) { - let x = points[i]; - let y = points[i + 1]; - let denominator = a13 * x + a23 * y + a33; - points[i] = (a11 * x + a21 * y + a31) / denominator; - points[i + 1] = (a12 * x + a22 * y + a32) / denominator; - i += 2; - } - } - - pub fn transform_points_double(&self, x_values: &mut [f32], y_valuess: &mut [f32]) { - let n = x_values.len(); - for i in 0..n { - // for (int i = 0; i < n; i++) { - let x = x_values[i]; - let y = y_valuess[i]; - let denominator = self.a13 * x + self.a23 * y + self.a33; - x_values[i] = (self.a11 * x + self.a21 * y + self.a31) / denominator; - y_valuess[i] = (self.a12 * x + self.a22 * y + self.a32) / denominator; - } - } - - pub fn squareToQuadrilateral( - x0: f32, - y0: f32, - x1: f32, - y1: f32, - x2: f32, - y2: f32, - x3: f32, - y3: f32, - ) -> Self { - let dx3 = x0 - x1 + x2 - x3; - let dy3 = y0 - y1 + y2 - y3; - if dx3 == 0.0f32 && dy3 == 0.0f32 { - // Affine - return PerspectiveTransform::new( - x1 - x0, - x2 - x1, - x0, - y1 - y0, - y2 - y1, - y0, - 0.0f32, - 0.0f32, - 1.0f32, - ); - } else { - let dx1 = x1 - x2; - let dx2 = x3 - x2; - let dy1 = y1 - y2; - let dy2 = y3 - y2; - let denominator = dx1 * dy2 - dx2 * dy1; - let a13 = (dx3 * dy2 - dx2 * dy3) / denominator; - let a23 = (dx1 * dy3 - dx3 * dy1) / denominator; - return PerspectiveTransform::new( - x1 - x0 + a13 * x1, - x3 - x0 + a23 * x3, - x0, - y1 - y0 + a13 * y1, - y3 - y0 + a23 * y3, - y0, - a13, - a23, - 1.0f32, - ); - } - } - - pub fn quadrilateralToSquare( - x0: f32, - y0: f32, - x1: f32, - y1: f32, - x2: f32, - y2: f32, - x3: f32, - y3: f32, - ) -> Self { - // Here, the adjoint serves as the inverse - return PerspectiveTransform::squareToQuadrilateral(x0, y0, x1, y1, x2, y2, x3, y3) - .buildAdjoint(); - } - - fn buildAdjoint(&self) -> Self { - // Adjoint is the transpose of the cofactor matrix: - return PerspectiveTransform::new( - self.a22 * self.a33 - self.a23 * self.a32, - self.a23 * self.a31 - self.a21 * self.a33, - self.a21 * self.a32 - self.a22 * self.a31, - self.a13 * self.a32 - self.a12 * self.a33, - self.a11 * self.a33 - self.a13 * self.a31, - self.a12 * self.a31 - self.a11 * self.a32, - self.a12 * self.a23 - self.a13 * self.a22, - self.a13 * self.a21 - self.a11 * self.a23, - self.a11 * self.a22 - self.a12 * self.a21, - ); - } - - fn times(&self, other: &Self) -> Self { - return PerspectiveTransform::new( - self.a11 * other.a11 + self.a21 * other.a12 + self.a31 * other.a13, - self.a11 * other.a21 + self.a21 * other.a22 + self.a31 * other.a23, - self.a11 * other.a31 + self.a21 * other.a32 + self.a31 * other.a33, - self.a12 * other.a11 + self.a22 * other.a12 + self.a32 * other.a13, - self.a12 * other.a21 + self.a22 * other.a22 + self.a32 * other.a23, - self.a12 * other.a31 + self.a22 * other.a32 + self.a32 * other.a33, - self.a13 * other.a11 + self.a23 * other.a12 + self.a33 * other.a13, - self.a13 * other.a21 + self.a23 * other.a22 + self.a33 * other.a23, - self.a13 * other.a31 + self.a23 * other.a32 + self.a33 * other.a33, - ); - } -} - -/* - * Copyright 2007 ZXing authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// package com.google.zxing.common; - -// import java.util.List; - -/** - *

Encapsulates the result of decoding a matrix of bits. This typically - * applies to 2D barcode formats. For now it contains the raw bytes obtained, - * as well as a String interpretation of those bytes, if applicable.

- * - * @author Sean Owen - */ -pub struct DecoderRXingResult { - rawBytes: Vec, - numBits: usize, - text: String, - byteSegments: Vec>, - ecLevel: String, - errorsCorrected: u64, - erasures: u64, - other: Box, - structuredAppendParity: i32, - structuredAppendSequenceNumber: i32, - symbologyModifier: u32, -} - -impl DecoderRXingResult { - pub fn new( - rawBytes: Vec, - text: String, - byteSegments: Vec>, - ecLevel: String, - ) -> Self { - Self::with_all(rawBytes, text, byteSegments, ecLevel, -2, -2, 0) - } - - pub fn with_symbology( - rawBytes: Vec, - text: String, - byteSegments: Vec>, - ecLevel: String, - symbologyModifier: u32, - ) -> Self { - Self::with_all( - rawBytes, - text, - byteSegments, - ecLevel, - -1, - -1, - symbologyModifier, - ) - } - - pub fn with_sa( - rawBytes: Vec, - text: String, - byteSegments: Vec>, - ecLevel: String, - saSequence: i32, - saParity: i32, - ) -> Self { - Self::with_all( - rawBytes, - text, - byteSegments, - ecLevel, - saSequence, - saParity, - 0, - ) - } - - pub fn with_all( - rawBytes: Vec, - text: String, - byteSegments: Vec>, - ecLevel: String, - saSequence: i32, - saParity: i32, - symbologyModifier: u32, - ) -> Self { - let nb = rawBytes.len(); - Self { - rawBytes, - numBits: nb, - text, - byteSegments, - ecLevel, - errorsCorrected: 0, - erasures: 0, - other: Box::new(false), - structuredAppendParity: saParity, - structuredAppendSequenceNumber: saSequence, - symbologyModifier, - } - } - - /** - * @return raw bytes representing the result, or {@code null} if not applicable - */ - pub fn getRawBytes(&self) -> &Vec { - &self.rawBytes - } - - /** - * @return how many bits of {@link #getRawBytes()} are valid; typically 8 times its length - * @since 3.3.0 - */ - pub fn getNumBits(&self) -> usize { - self.numBits - } - - /** - * @param numBits overrides the number of bits that are valid in {@link #getRawBytes()} - * @since 3.3.0 - */ - pub fn setNumBits(&mut self, numBits: usize) { - self.numBits = numBits; - } - - /** - * @return text representation of the result - */ - pub fn getText(&self) -> &str { - &self.text - } - - /** - * @return list of byte segments in the result, or {@code null} if not applicable - */ - pub fn getByteSegments(&self) -> &Vec> { - &self.byteSegments - } - - /** - * @return name of error correction level used, or {@code null} if not applicable - */ - pub fn getECLevel(&self) -> &str { - &self.ecLevel - } - - /** - * @return number of errors corrected, or {@code null} if not applicable - */ - pub fn getErrorsCorrected(&self) -> u64 { - self.errorsCorrected - } - - pub fn setErrorsCorrected(&mut self, errorsCorrected: u64) { - self.errorsCorrected = errorsCorrected; - } - - /** - * @return number of erasures corrected, or {@code null} if not applicable - */ - pub fn getErasures(&self) -> u64 { - self.erasures - } - - pub fn setErasures(&mut self, erasures: u64) { - self.erasures = erasures - } - - /** - * @return arbitrary additional metadata - */ - pub fn getOther(&self) -> &Box { - &self.other - } - - pub fn setOther(&mut self, other: Box) { - self.other = other - } - - pub fn hasStructuredAppend(&self) -> bool { - self.structuredAppendParity >= 0 && self.structuredAppendSequenceNumber >= 0 - } - - pub fn getStructuredAppendParity(&self) -> i32 { - self.structuredAppendParity - } - - pub fn getStructuredAppendSequenceNumber(&self) -> i32 { - self.structuredAppendSequenceNumber - } - - pub fn getSymbologyModifier(&self) -> u32 { - self.symbologyModifier - } -} - -/* - * Copyright 2008 ZXing authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// package com.google.zxing.common; - -// import java.io.ByteArrayOutputStream; - -/** - * Class that lets one easily build an array of bytes by appending bits at a time. - * - * @author Sean Owen - */ -pub struct BitSourceBuilder { - output: Vec, - nextByte: u32, - bitsLeftInNextByte: u32, -} - -impl BitSourceBuilder { - pub fn new() -> Self { - Self { - output: Vec::new(), - nextByte: 0, - bitsLeftInNextByte: 8, - } - } - - pub fn write(&mut self, value: u32, numBits: u32) { - if numBits <= self.bitsLeftInNextByte { - self.nextByte <<= numBits; - self.nextByte |= value; - self.bitsLeftInNextByte -= numBits; - if self.bitsLeftInNextByte == 0 { - self.output.push(self.nextByte as u8); - self.nextByte = 0; - self.bitsLeftInNextByte = 8; - } - } else { - let bitsToWriteNow = self.bitsLeftInNextByte; - let numRestOfBits = numBits - bitsToWriteNow; - let mask = 0xFF >> (8 - bitsToWriteNow); - let valueToWriteNow = (value >> numRestOfBits) & mask; - self.write(valueToWriteNow, bitsToWriteNow); - self.write(value, numRestOfBits); - } - } - - pub fn toByteArray(&mut self) -> &Vec { - if self.bitsLeftInNextByte < 8 { - self.write(0, self.bitsLeftInNextByte); - } - &self.output - } -} - -/* - * Copyright 2007 ZXing authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// package com.google.zxing.common; - -// import com.google.zxing.NotFoundException; - -/** - * Implementations of this class can, given locations of finder patterns for a QR code in an - * image, sample the right points in the image to reconstruct the QR code, accounting for - * perspective distortion. It is abstracted since it is relatively expensive and should be allowed - * to take advantage of platform-specific optimized implementations, like Sun's Java Advanced - * Imaging library, but which may not be available in other environments such as J2ME, and vice - * versa. - * - * The implementation used can be controlled by calling {@link #setGridSampler(GridSampler)} - * with an instance of a class which implements this interface. - * - * @author Sean Owen - */ - -pub trait GridSampler { - // /** - // * Sets the implementation of GridSampler used by the library. One global - // * instance is stored, which may sound problematic. But, the implementation provided - // * ought to be appropriate for the entire platform, and all uses of this library - // * in the whole lifetime of the JVM. For instance, an Android activity can swap in - // * an implementation that takes advantage of native platform libraries. - // * - // * @param newGridSampler The platform-specific object to install. - // */ - // public static void setGridSampler(GridSampler newGridSampler) { - // gridSampler = newGridSampler; - // } - - // /** - // * @return the current implementation of GridSampler - // */ - // public static GridSampler getInstance() { - // return gridSampler; - // } - - /** - * Samples an image for a rectangular matrix of bits of the given dimension. The sampling - * transformation is determined by the coordinates of 4 points, in the original and transformed - * image space. - * - * @param image image to sample - * @param dimensionX width of {@link BitMatrix} to sample from image - * @param dimensionY height of {@link BitMatrix} to sample from image - * @param p1ToX point 1 preimage X - * @param p1ToY point 1 preimage Y - * @param p2ToX point 2 preimage X - * @param p2ToY point 2 preimage Y - * @param p3ToX point 3 preimage X - * @param p3ToY point 3 preimage Y - * @param p4ToX point 4 preimage X - * @param p4ToY point 4 preimage Y - * @param p1FromX point 1 image X - * @param p1FromY point 1 image Y - * @param p2FromX point 2 image X - * @param p2FromY point 2 image Y - * @param p3FromX point 3 image X - * @param p3FromY point 3 image Y - * @param p4FromX point 4 image X - * @param p4FromY point 4 image Y - * @return {@link BitMatrix} representing a grid of points sampled from the image within a region - * defined by the "from" parameters - * @throws NotFoundException if image can't be sampled, for example, if the transformation defined - * by the given points is invalid or results in sampling outside the image boundaries - */ - fn sample_grid_detailed( - &self, - image: &BitMatrix, - dimensionX: u32, - dimensionY: u32, - p1ToX: f32, - p1ToY: f32, - p2ToX: f32, - p2ToY: f32, - p3ToX: f32, - p3ToY: f32, - p4ToX: f32, - p4ToY: f32, - p1FromX: f32, - p1FromY: f32, - p2FromX: f32, - p2FromY: f32, - p3FromX: f32, - p3FromY: f32, - p4FromX: f32, - p4FromY: f32, - ) -> Result; - - fn sample_grid( - &self, - image: &BitMatrix, - dimensionX: u32, - dimensionY: u32, - transform: &PerspectiveTransform, - ) -> Result; - - /** - *

Checks a set of points that have been transformed to sample points on an image against - * the image's dimensions to see if the point are even within the image.

- * - *

This method will actually "nudge" the endpoints back onto the image if they are found to be - * barely (less than 1 pixel) off the image. This accounts for imperfect detection of finder - * patterns in an image where the QR Code runs all the way to the image border.

- * - *

For efficiency, the method will check points from either end of the line until one is found - * to be within the image. Because the set of points are assumed to be linear, this is valid.

- * - * @param image image into which the points should map - * @param points actual points in x1,y1,...,xn,yn form - * @throws NotFoundException if an endpoint is lies outside the image boundaries - */ - fn checkAndNudgePoints(&self, image: &BitMatrix, points: &mut [f32]) -> Result<(), Exceptions> { - let width = image.getWidth(); - let height = image.getHeight(); - // Check and nudge points from start until we see some that are OK: - let mut nudged = true; - let max_offset = points.len() - 1; // points.length must be even - let mut offset = 0; - while offset < max_offset && nudged { - // for (int offset = 0; offset < maxOffset && nudged; offset += 2) { - let x = points[offset] as i32; - let y = points[offset + 1] as i32; - if x < -1 || x > width.try_into().unwrap() || y < -1 || y > height.try_into().unwrap() { - return Err(Exceptions::NotFoundException( - "getNotFoundInstance".to_owned(), - )); - } - nudged = false; - if x == -1 { - points[offset] = 0.0f32; - nudged = true; - } else if x == width.try_into().unwrap() { - points[offset] = width as f32 - 1f32; - nudged = true; - } - if y == -1 { - points[offset + 1] = 0.0f32; - nudged = true; - } else if (y == height.try_into().unwrap()) { - points[offset + 1] = height as f32 - 1f32; - nudged = true; - } - offset += 2; - } - // Check and nudge points from end: - nudged = true; - let mut offset = points.len() as isize - 2; - while offset >= 0 && nudged { - // for (int offset = points.length - 2; offset >= 0 && nudged; offset -= 2) { - let x = points[offset as usize] as i32; - let y = points[offset as usize + 1] as i32; - if x < -1 || x > width.try_into().unwrap() || y < -1 || y > height.try_into().unwrap() { - return Err(Exceptions::NotFoundException( - "getNotFoundInstance".to_owned(), - )); - } - nudged = false; - if x == -1 { - points[offset as usize] = 0.0f32; - nudged = true; - } else if x == width.try_into().unwrap() { - points[offset as usize] = width as f32 - 1f32; - nudged = true; - } - if y == -1 { - points[offset as usize + 1] = 0.0f32; - nudged = true; - } else if y == height.try_into().unwrap() { - points[offset as usize + 1] = height as f32 - 1f32; - nudged = true; - } - offset += -2; - } - Ok(()) - } -} - -/* - * Copyright 2007 ZXing authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// package com.google.zxing.common; - -// import com.google.zxing.NotFoundException; - -/** - * @author Sean Owen - */ -pub struct DefaultGridSampler {} - -impl GridSampler for DefaultGridSampler { - fn sample_grid_detailed( - &self, - image: &BitMatrix, - dimensionX: u32, - dimensionY: u32, - p1ToX: f32, - p1ToY: f32, - p2ToX: f32, - p2ToY: f32, - p3ToX: f32, - p3ToY: f32, - p4ToX: f32, - p4ToY: f32, - p1FromX: f32, - p1FromY: f32, - p2FromX: f32, - p2FromY: f32, - p3FromX: f32, - p3FromY: f32, - p4FromX: f32, - p4FromY: f32, - ) -> Result { - let transform = PerspectiveTransform::quadrilateralToQuadrilateral( - p1ToX, p1ToY, p2ToX, p2ToY, p3ToX, p3ToY, p4ToX, p4ToY, p1FromX, p1FromY, p2FromX, - p2FromY, p3FromX, p3FromY, p4FromX, p4FromY, - ); - - self.sample_grid(image, dimensionX, dimensionY, &transform) - } - - fn sample_grid( - &self, - image: &BitMatrix, - dimensionX: u32, - dimensionY: u32, - transform: &PerspectiveTransform, - ) -> Result { - if dimensionX == 0 || dimensionY == 0 { - return Err(Exceptions::NotFoundException( - "getNotFoundInstance".to_owned(), - )); - } - let mut bits = BitMatrix::new(dimensionX, dimensionY)?; - let mut points = vec![0_f32; 2 * dimensionX as usize]; - for y in 0..dimensionY { - // for (int y = 0; y < dimensionY; y++) { - let max = points.len(); - let i_value = y as f32 + 0.5f32; - let mut x = 0; - while x < max { - // for (int x = 0; x < max; x += 2) { - points[x] = (x as f32 / 2.0) + 0.5f32; - points[x + 1] = i_value; - x += 2; - } - transform.transform_points_single(&mut points); - // Quick check to see if points transformed to something inside the image; - // sufficient to check the endpoints - self.checkAndNudgePoints(image, &mut points)?; - // try { - let mut x = 0; - while x < max { - // for (int x = 0; x < max; x += 2) { - if points[x].floor() as u32 >= image.getWidth() - || points[x + 1].floor() as u32 >= image.getHeight() - { - return Err(Exceptions::NotFoundException( - "index out of bounds, see documentation in file for explanation".to_owned(), - )); - } - if image.get(points[x].floor() as u32, points[x + 1].floor() as u32) { - // Black(-ish) pixel - bits.set(x as u32 / 2, y); - } - x += 2; - } - // } catch (ArrayIndexOutOfBoundsException aioobe) { - // // This feels wrong, but, sometimes if the finder patterns are misidentified, the resulting - // // transform gets "twisted" such that it maps a straight line of points to a set of points - // // whose endpoints are in bounds, but others are not. There is probably some mathematical - // // way to detect this about the transformation that I don't know yet. - // // This results in an ugly runtime exception despite our clever checks above -- can't have - // // that. We could check each point's coordinates but that feels duplicative. We settle for - // // catching and wrapping ArrayIndexOutOfBoundsException. - // throw NotFoundException.getNotFoundInstance(); - // } - } - return Ok(bits); - } -} - -/* - * Copyright 2008 ZXing authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// package com.google.zxing.common; - -// import com.google.zxing.FormatException; - -// import java.nio.charset.Charset; - -// import java.util.HashMap; -// import java.util.Map; - -/** - * Encapsulates a Character Set ECI, according to "Extended Channel Interpretations" 5.3.1.1 - * of ISO 18004. - * - * @author Sean Owen - */ -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub enum CharacterSetECI { - // Enum name is a Java encoding valid for java.lang and java.io - Cp437, //(new int[]{0,2}), - ISO8859_1, //(new int[]{1,3}, "ISO-8859-1"), - ISO8859_2, //(4, "ISO-8859-2"), - ISO8859_3, //(5, "ISO-8859-3"), - ISO8859_4, //(6, "ISO-8859-4"), - ISO8859_5, //(7, "ISO-8859-5"), - ISO8859_6, //(8, "ISO-8859-6"), - ISO8859_7, //(9, "ISO-8859-7"), - ISO8859_8, //(10, "ISO-8859-8"), - ISO8859_9, //(11, "ISO-8859-9"), - ISO8859_10, //(12, "ISO-8859-10"), - ISO8859_11, //(13, "ISO-8859-11"), - ISO8859_13, //(15, "ISO-8859-13"), - ISO8859_14, //(16, "ISO-8859-14"), - ISO8859_15, //(17, "ISO-8859-15"), - ISO8859_16, //(18, "ISO-8859-16"), - SJIS, //(20, "Shift_JIS"), - Cp1250, //(21, "windows-1250"), - Cp1251, //(22, "windows-1251"), - Cp1252, //(23, "windows-1252"), - Cp1256, //(24, "windows-1256"), - UnicodeBigUnmarked, //(25, "UTF-16BE", "UnicodeBig"), - UTF8, //(26, "UTF-8"), - ASCII, //(new int[] {27, 170}, "US-ASCII"), - Big5, //(28), - GB18030, //(29, "GB2312", "EUC_CN", "GBK"), - EUC_KR, //(30, "EUC-KR"); -} -impl CharacterSetECI { - // private static final Map VALUE_TO_ECI = new HashMap<>(); - // private static final Map NAME_TO_ECI = new HashMap<>(); - // static { - // for (CharacterSetECI eci : values()) { - // for (int value : eci.values) { - // VALUE_TO_ECI.put(value, eci); - // } - // NAME_TO_ECI.put(eci.name(), eci); - // for (String name : eci.otherEncodingNames) { - // NAME_TO_ECI.put(name, eci); - // } - // } - // } - - // private final int[] values; - // private final String[] otherEncodingNames; - - // CharacterSetECI(int value) { - // this(new int[] {value}); - // } - - // CharacterSetECI(int value, String... otherEncodingNames) { - // this.values = new int[] {value}; - // this.otherEncodingNames = otherEncodingNames; - // } - - // CharacterSetECI(int[] values, String... otherEncodingNames) { - // this.values = values; - // this.otherEncodingNames = otherEncodingNames; - // } - - pub fn getValueSelf(&self) -> u32 { - Self::getValue(self) - } - - pub fn getValue(cs_eci: &CharacterSetECI) -> u32 { - match cs_eci { - CharacterSetECI::Cp437 => 0, - CharacterSetECI::ISO8859_1 => 1, - CharacterSetECI::ISO8859_2 => 4, - CharacterSetECI::ISO8859_3 => 5, - CharacterSetECI::ISO8859_4 => 6, - CharacterSetECI::ISO8859_5 => 7, - CharacterSetECI::ISO8859_6 => 8, - CharacterSetECI::ISO8859_7 => 9, - CharacterSetECI::ISO8859_8 => 10, - CharacterSetECI::ISO8859_9 => 11, - CharacterSetECI::ISO8859_10 => 12, - CharacterSetECI::ISO8859_11 => 13, - CharacterSetECI::ISO8859_13 => 15, - CharacterSetECI::ISO8859_14 => 16, - CharacterSetECI::ISO8859_15 => 17, - CharacterSetECI::ISO8859_16 => 18, - CharacterSetECI::SJIS => 20, - CharacterSetECI::Cp1250 => 21, - CharacterSetECI::Cp1251 => 22, - CharacterSetECI::Cp1252 => 23, - CharacterSetECI::Cp1256 => 24, - CharacterSetECI::UnicodeBigUnmarked => 25, - CharacterSetECI::UTF8 => 26, - CharacterSetECI::ASCII => 27, - CharacterSetECI::Big5 => 28, - CharacterSetECI::GB18030 => 29, - CharacterSetECI::EUC_KR => 30, - } - } - - pub fn getCharset(cs_eci: &CharacterSetECI) -> EncodingRef { - let name = match cs_eci { - // CharacterSetECI::Cp437 => "CP437", - CharacterSetECI::Cp437 => "UTF-8", - CharacterSetECI::ISO8859_1 => "ISO-8859-1", - CharacterSetECI::ISO8859_2 => "ISO-8859-2", - CharacterSetECI::ISO8859_3 => "ISO-8859-3", - CharacterSetECI::ISO8859_4 => "ISO-8859-4", - CharacterSetECI::ISO8859_5 => "ISO-8859-5", - CharacterSetECI::ISO8859_6 => "ISO-8859-6", - CharacterSetECI::ISO8859_7 => "ISO-8859-7", - CharacterSetECI::ISO8859_8 => "ISO-8859-8", - CharacterSetECI::ISO8859_9 => "ISO-8859-9", - CharacterSetECI::ISO8859_10 => "ISO-8859-10", - CharacterSetECI::ISO8859_11 => "ISO-8859-11", - CharacterSetECI::ISO8859_13 => "ISO-8859-13", - CharacterSetECI::ISO8859_14 => "ISO-8859-14", - CharacterSetECI::ISO8859_15 => "ISO-8859-15", - CharacterSetECI::ISO8859_16 => "ISO-8859-16", - CharacterSetECI::SJIS => "Shift_JIS", - CharacterSetECI::Cp1250 => "windows-1250", - CharacterSetECI::Cp1251 => "windows-1251", - CharacterSetECI::Cp1252 => "windows-1252", - CharacterSetECI::Cp1256 => "windows-1256", - CharacterSetECI::UnicodeBigUnmarked => "UTF-16BE", - CharacterSetECI::UTF8 => "UTF-8", - CharacterSetECI::ASCII => "US-ASCII", - CharacterSetECI::Big5 => "Big5", - CharacterSetECI::GB18030 => "GB2312", - CharacterSetECI::EUC_KR => "EUC-KR", - }; - encoding::label::encoding_from_whatwg_label(name).unwrap() - } - - /** - * @param charset Java character set object - * @return CharacterSetECI representing ECI for character encoding, or null if it is legal - * but unsupported - */ - pub fn getCharacterSetECI(charset: EncodingRef) -> Option { - let name = if let Some(nm) = charset.whatwg_name() { - nm - } else { - charset.name() - }; - match name { - "CP437" => Some(CharacterSetECI::Cp437), - "iso-8859-1" => Some(CharacterSetECI::ISO8859_1), - "iso-8859-2" => Some(CharacterSetECI::ISO8859_2), - "iso-8859-3" => Some(CharacterSetECI::ISO8859_3), - "iso-8859-4" => Some(CharacterSetECI::ISO8859_4), - "iso-8859-5" => Some(CharacterSetECI::ISO8859_5), - "iso-8859-6" => Some(CharacterSetECI::ISO8859_6), - "iso-8859-7" => Some(CharacterSetECI::ISO8859_7), - "iso-8859-8" => Some(CharacterSetECI::ISO8859_8), - "iso-8859-9" => Some(CharacterSetECI::ISO8859_9), - "iso-8859-10" => Some(CharacterSetECI::ISO8859_10), - "iso-8859-11" => Some(CharacterSetECI::ISO8859_11), - "iso-8859-13" => Some(CharacterSetECI::ISO8859_13), - "iso-8859-14" => Some(CharacterSetECI::ISO8859_14), - "iso-8859-15" => Some(CharacterSetECI::ISO8859_15), - "iso-8859-16" => Some(CharacterSetECI::ISO8859_16), - "shift_jis" => Some(CharacterSetECI::SJIS), - "windows-1250" => Some(CharacterSetECI::Cp1250), - "windows-1251" => Some(CharacterSetECI::Cp1251), - "windows-1252" => Some(CharacterSetECI::Cp1252), - "windows-1256" => Some(CharacterSetECI::Cp1256), - "utf-16be" => Some(CharacterSetECI::UnicodeBigUnmarked), - "utf-8" => Some(CharacterSetECI::UTF8), - "us-ascii" => Some(CharacterSetECI::ASCII), - "big5" => Some(CharacterSetECI::Big5), - "gb2312" => Some(CharacterSetECI::GB18030), - "euc-kr" => Some(CharacterSetECI::EUC_KR), - _ => None, - } - } - - /** - * @param value character set ECI value - * @return {@code CharacterSetECI} representing ECI of given value, or null if it is legal but - * unsupported - * @throws FormatException if ECI value is invalid - */ - pub fn getCharacterSetECIByValue(value: u32) -> Result { - match value { - 0 | 2 => Ok(CharacterSetECI::Cp437), - 1 | 3 => Ok(CharacterSetECI::ISO8859_1), - 4 => Ok(CharacterSetECI::ISO8859_2), - 5 => Ok(CharacterSetECI::ISO8859_3), - 6 => Ok(CharacterSetECI::ISO8859_4), - 7 => Ok(CharacterSetECI::ISO8859_5), - 8 => Ok(CharacterSetECI::ISO8859_6), - 9 => Ok(CharacterSetECI::ISO8859_7), - 10 => Ok(CharacterSetECI::ISO8859_8), - 11 => Ok(CharacterSetECI::ISO8859_9), - 12 => Ok(CharacterSetECI::ISO8859_10), - 13 => Ok(CharacterSetECI::ISO8859_11), - 15 => Ok(CharacterSetECI::ISO8859_13), - 16 => Ok(CharacterSetECI::ISO8859_14), - 17 => Ok(CharacterSetECI::ISO8859_15), - 18 => Ok(CharacterSetECI::ISO8859_16), - 20 => Ok(CharacterSetECI::SJIS), - 21 => Ok(CharacterSetECI::Cp1250), - 22 => Ok(CharacterSetECI::Cp1251), - 23 => Ok(CharacterSetECI::Cp1252), - 24 => Ok(CharacterSetECI::Cp1256), - 25 => Ok(CharacterSetECI::UnicodeBigUnmarked), - 26 => Ok(CharacterSetECI::UTF8), - 27 | 170 => Ok(CharacterSetECI::ASCII), - 28 => Ok(CharacterSetECI::Big5), - 29 => Ok(CharacterSetECI::GB18030), - 30 => Ok(CharacterSetECI::EUC_KR), - _ => Err(Exceptions::NotFoundException("Bad ECI Value".to_owned())), - } - } - - /** - * @param name character set ECI encoding name - * @return CharacterSetECI representing ECI for character encoding, or null if it is legal - * but unsupported - */ - pub fn getCharacterSetECIByName(name: &str) -> Option { - match name { - "CP437" => Some(CharacterSetECI::Cp437), - "ISO-8859-1" => Some(CharacterSetECI::ISO8859_1), - "ISO-8859-2" => Some(CharacterSetECI::ISO8859_2), - "ISO-8859-3" => Some(CharacterSetECI::ISO8859_3), - "ISO-8859-4" => Some(CharacterSetECI::ISO8859_4), - "ISO-8859-5" => Some(CharacterSetECI::ISO8859_5), - "ISO-8859-6" => Some(CharacterSetECI::ISO8859_6), - "ISO-8859-7" => Some(CharacterSetECI::ISO8859_7), - "ISO-8859-8" => Some(CharacterSetECI::ISO8859_8), - "ISO-8859-9" => Some(CharacterSetECI::ISO8859_9), - "ISO-8859-10" => Some(CharacterSetECI::ISO8859_10), - "ISO-8859-11" => Some(CharacterSetECI::ISO8859_11), - "ISO-8859-13" => Some(CharacterSetECI::ISO8859_13), - "ISO-8859-14" => Some(CharacterSetECI::ISO8859_14), - "ISO-8859-15" => Some(CharacterSetECI::ISO8859_15), - "ISO-8859-16" => Some(CharacterSetECI::ISO8859_16), - "Shift_JIS" => Some(CharacterSetECI::SJIS), - "windows-1250" => Some(CharacterSetECI::Cp1250), - "windows-1251" => Some(CharacterSetECI::Cp1251), - "windows-1252" => Some(CharacterSetECI::Cp1252), - "windows-1256" => Some(CharacterSetECI::Cp1256), - "UTF-16BE" => Some(CharacterSetECI::UnicodeBigUnmarked), - "UTF-8" => Some(CharacterSetECI::UTF8), - "US-ASCII" => Some(CharacterSetECI::ASCII), - "Big5" => Some(CharacterSetECI::Big5), - "GB2312" => Some(CharacterSetECI::GB18030), - "EUC-KR" => Some(CharacterSetECI::EUC_KR), - _ => None, - } - } -} - -/* - * Copyright 2022 ZXing authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// package com.google.zxing.common; - -// import com.google.zxing.FormatException; - -// import java.nio.charset.Charset; -// import java.nio.charset.StandardCharsets; - -/** - * Class that converts a sequence of ECIs and bytes into a string - * - * @author Alex Geller - */ -pub struct ECIStringBuilder { - current_bytes: Vec, - result: String, - current_charset: EncodingRef, //= StandardCharsets.ISO_8859_1; -} - -impl ECIStringBuilder { - pub fn new() -> Self { - Self { - current_bytes: Vec::new(), - result: String::new(), - current_charset: encoding::all::UTF_8, - } - } - pub fn with_capacity(initial_capacity: usize) -> Self { - Self { - current_bytes: Vec::with_capacity(initial_capacity), - result: String::new(), - current_charset: encoding::all::ISO_8859_1, - } - } - - /** - * Appends {@code value} as a byte value - * - * @param value character whose lowest byte is to be appended - */ - pub fn append_char(&mut self, value: char) { - self.current_bytes.push(value as u8); - } - - /** - * Appends {@code value} as a byte value - * - * @param value byte to append - */ - pub fn append_byte(&mut self, value: u8) { - self.current_bytes.push(value); - } - - /** - * Appends the characters in {@code value} as bytes values - * - * @param value string to append - */ - pub fn append_string(&mut self, value: &str) { - value.as_bytes().iter().map(|b| self.current_bytes.push(*b)); - // self.current_bytes.push(value.as_bytes()); - } - - /** - * Append the string repesentation of {@code value} (short for {@code append(String.valueOf(value))}) - * - * @param value int to append as a string - */ - pub fn append(&mut self, value: i32) { - self.append_string(&format!("{}", value)); - } - - /** - * Appends ECI value to output. - * - * @param value ECI value to append, as an int - * @throws FormatException on invalid ECI value - */ - pub fn appendECI(&mut self, value: u32) -> Result<(), Exceptions> { - self.encodeCurrentBytesIfAny(); - let character_set_eci = CharacterSetECI::getCharacterSetECIByValue(value)?; - // if (character_set_eci == null) { - // throw FormatException.getFormatInstance(); - // } - self.current_charset = CharacterSetECI::getCharset(&character_set_eci); - Ok(()) - } - - pub fn encodeCurrentBytesIfAny(&mut self) { - if self.current_charset.name() == encoding::all::UTF_8.name() { - if !self.current_bytes.is_empty() { - // if result == null { - // result = currentBytes; - // currentBytes = new StringBuilder(); - // } else { - self.result - .push_str(&String::from_utf8(self.current_bytes.clone()).unwrap()); - self.current_bytes.clear(); - // } - } - } else if !self.current_bytes.is_empty() { - let bytes = self.current_bytes.clone(); - self.current_bytes.clear(); - // if (result == null) { - // result = new StringBuilder(new String(bytes, currentCharset)); - // } else { - let encoded_value = self - .current_charset - .decode(&bytes, encoding::DecoderTrap::Replace) - .unwrap(); - self.result.push_str(&encoded_value); - // } - } - } - - /** - * Appends the characters from {@code value} (unlike all other append methods of this class who append bytes) - * - * @param value characters to append - */ - pub fn appendCharacters(&mut self, value: &str) { - self.encodeCurrentBytesIfAny(); - self.result.push_str(value); - } - - /** - * Short for {@code toString().length()} (if possible, use {@link #isEmpty()} instead) - * - * @return length of string representation in characters - */ - pub fn len(&mut self) -> usize { - self.encodeCurrentBytesIfAny(); //return toString().length(); - self.result.len() - } - - /** - * @return true iff nothing has been appended - */ - pub fn is_empty(&self) -> bool { - return self.current_bytes.is_empty() && self.result.is_empty(); - } -} - -impl fmt::Display for ECIStringBuilder { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - //self.encodeCurrentBytesIfAny(); - write!(f, "{}", self.result) - } -} - -/* - * Copyright 2021 ZXing authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// package com.google.zxing.common; - -// import java.nio.charset.Charset; -// import java.nio.charset.CharsetEncoder; -// import java.nio.charset.StandardCharsets; -// import java.nio.charset.UnsupportedCharsetException; -// import java.util.ArrayList; -// import java.util.List; - -lazy_static! { - static ref ENCODERS : Vec = { - let mut enc_vec = Vec::new(); - for name in NAMES { - if let Some(enc) = CharacterSetECI::getCharacterSetECIByName(name) { - // try { - enc_vec.push(CharacterSetECI::getCharset(&enc)); - // } catch (UnsupportedCharsetException e) { - // continue - // } - } - } - enc_vec - }; -} -const NAMES: [&str; 20] = [ - "IBM437", - "ISO-8859-2", - "ISO-8859-3", - "ISO-8859-4", - "ISO-8859-5", - "ISO-8859-6", - "ISO-8859-7", - "ISO-8859-8", - "ISO-8859-9", - "ISO-8859-10", - "ISO-8859-11", - "ISO-8859-13", - "ISO-8859-14", - "ISO-8859-15", - "ISO-8859-16", - "windows-1250", - "windows-1251", - "windows-1252", - "windows-1256", - "Shift_JIS", -]; - -/** - * Set of CharsetEncoders for a given input string - * - * Invariants: - * - The list contains only encoders from CharacterSetECI (list is shorter then the list of encoders available on - * the platform for which ECI values are defined). - * - The list contains encoders at least one encoder for every character in the input. - * - The first encoder in the list is always the ISO-8859-1 encoder even of no character in the input can be encoded - * by it. - * - If the input contains a character that is not in ISO-8859-1 then the last two entries in the list will be the - * UTF-8 encoder and the UTF-16BE encoder. - * - * @author Alex Geller - */ -#[derive(Clone)] -pub struct ECIEncoderSet { - encoders: Vec, - priorityEncoderIndex: Option, -} - -impl ECIEncoderSet { - /** - * Constructs an encoder set - * - * @param stringToEncode the string that needs to be encoded - * @param priorityCharset The preferred {@link Charset} or null. - * @param fnc1 fnc1 denotes the character in the input that represents the FNC1 character or -1 for a non-GS1 bar - * code. When specified, it is considered an error to pass it as argument to the methods canEncode() or encode(). - */ - pub fn new( - stringToEncodeMain: &str, - priorityCharset: Option, - fnc1: Option<&str>, - ) -> Self { - // List of encoders that potentially encode characters not in ISO-8859-1 in one byte. - - let mut encoders: Vec; - let mut priorityEncoderIndexValue = None; - - let mut neededEncoders: Vec = Vec::new(); - - let stringToEncode = stringToEncodeMain.graphemes(true).collect::>(); - - //we always need the ISO-8859-1 encoder. It is the default encoding - neededEncoders.push(encoding::all::ISO_8859_1); - let mut needUnicodeEncoder = if let Some(pc) = priorityCharset { - pc.name().starts_with("UTF") - } else { - false - }; - - //Walk over the input string and see if all characters can be encoded with the list of encoders - for i in 0..stringToEncode.len() { - // for (int i = 0; i < stringToEncode.length(); i++) { - let mut canEncode = false; - for encoder in &neededEncoders { - // for (CharsetEncoder encoder : neededEncoders) { - let c = stringToEncode.get(i).unwrap(); - if (fnc1.is_some() && c == fnc1.as_ref().unwrap()) - || encoder.encode(c, encoding::EncoderTrap::Strict).is_ok() - { - canEncode = true; - break; - } - } - if !canEncode { - //for the character at position i we don't yet have an encoder in the list - for i_encoder in 0..ENCODERS.len() { - // for encoder in ENCODERS { - let encoder = ENCODERS.get(i_encoder).unwrap(); - // for (CharsetEncoder encoder : ENCODERS) { - if encoder - .encode( - &stringToEncode.get(i).unwrap(), - encoding::EncoderTrap::Strict, - ) - .is_ok() - { - //Good, we found an encoder that can encode the character. We add him to the list and continue scanning - //the input - neededEncoders.push(*encoder); - canEncode = true; - break; - } - } - } - - if !canEncode { - //The character is not encodeable by any of the single byte encoders so we remember that we will need a - //Unicode encoder. - needUnicodeEncoder = true; - } - } - - if neededEncoders.len() == 1 && !needUnicodeEncoder { - //the entire input can be encoded by the ISO-8859-1 encoder - encoders = vec![encoding::all::ISO_8859_1]; - } else { - // we need more than one single byte encoder or we need a Unicode encoder. - // In this case we append a UTF-8 and UTF-16 encoder to the list - // encoders = [] new CharsetEncoder[neededEncoders.size() + 2]; - encoders = Vec::new(); - let index = 0; - - for encoder in neededEncoders { - // for (CharsetEncoder encoder : neededEncoders) { - //encoders[index++] = encoder; - encoders.push(encoder); - } - - encoders.push(encoding::all::UTF_8); - encoders.push(encoding::all::UTF_16BE); - } - - //Compute priorityEncoderIndex by looking up priorityCharset in encoders - // if priorityCharset != null { - if priorityCharset.is_some() { - for i in 0..encoders.len() { - // for (int i = 0; i < encoders.length; i++) { - if priorityCharset.as_ref().unwrap().name() == encoders[i].name() { - priorityEncoderIndexValue = Some(i); - break; - } - } - } - // } - //invariants - assert_eq!(encoders[0].name(), encoding::all::ISO_8859_1.name()); - Self { - encoders: encoders, - priorityEncoderIndex: priorityEncoderIndexValue, - } - } - - pub fn len(&self) -> usize { - return self.encoders.len(); - } - - pub fn getCharsetName(&self, index: usize) -> &'static str { - assert!(index < self.len()); - return self.encoders[index].name(); - } - - pub fn getCharset(&self, index: usize) -> EncodingRef { - assert!(index < self.len()); - return self.encoders[index]; - } - - pub fn getECIValue(&self, encoderIndex: usize) -> u32 { - CharacterSetECI::getValue( - &CharacterSetECI::getCharacterSetECI(self.encoders[encoderIndex]).unwrap(), - ) - } - - /* - * returns -1 if no priority charset was defined - */ - pub fn getPriorityEncoderIndex(&self) -> Option { - self.priorityEncoderIndex - } - - pub fn canEncode(&self, c: &str, encoderIndex: usize) -> bool { - assert!(encoderIndex < self.len()); - let encoder = self.encoders[encoderIndex]; - let enc_data = encoder.encode(c, encoding::EncoderTrap::Strict); - - enc_data.is_ok() - } - - pub fn encode_char(&self, c: &str, encoderIndex: usize) -> Vec { - assert!(encoderIndex < self.len()); - let encoder = self.encoders[encoderIndex]; - let enc_data = encoder.encode(&c.to_string(), encoding::EncoderTrap::Strict); - assert!(enc_data.is_ok()); - return enc_data.unwrap(); - } - - pub fn encode_string(&self, s: &str, encoderIndex: usize) -> Vec { - assert!(encoderIndex < self.len()); - let encoder = self.encoders[encoderIndex]; - encoder.encode(s, encoding::EncoderTrap::Replace).unwrap() - } -} - -/* - * Copyright 2021 ZXing authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// package com.google.zxing.common; - -// import java.nio.charset.Charset; -// import java.util.ArrayList; -// import java.util.List; - -//* approximated (latch + 2 codewords) -static COST_PER_ECI: usize = 3; - -/** - * Class that converts a character string into a sequence of ECIs and bytes - * - * The implementation uses the Dijkstra algorithm to produce minimal encodings - * - * @author Alex Geller - */ -pub struct MinimalECIInput { - bytes: Vec, - fnc1: u16, -} - -impl ECIInput for MinimalECIInput { - /** - * Returns the length of this input. The length is the number - * of {@code byte}s, FNC1 characters or ECIs in the sequence. - * - * @return the number of {@code char}s in this sequence - */ - fn length(&self) -> usize { - return self.bytes.len(); - } - - /** - * Returns the {@code byte} value at the specified index. An index ranges from zero - * to {@code length() - 1}. The first {@code byte} value of the sequence is at - * index zero, the next at index one, and so on, as for array - * indexing. - * - * @param index the index of the {@code byte} value to be returned - * - * @return the specified {@code byte} value as character or the FNC1 character - * - * @throws IndexOutOfBoundsException - * if the {@code index} argument is negative or not less than - * {@code length()} - * @throws IllegalArgumentException - * if the value at the {@code index} argument is an ECI (@see #isECI) - */ - fn charAt(&self, index: usize) -> Result { - if index >= self.length() { - return Err(Exceptions::IndexOutOfBoundsException(index.to_string())); - } - if self.isECI(index as u32)? { - return Err(Exceptions::IllegalArgumentException(format!( - "value at {} is not a character but an ECI", - index - ))); - } - if self.isFNC1(index)? { - Ok(self.fnc1 as u8 as char) - } else { - Ok(self.bytes[index] as u8 as char) - } - } - - /** - * Returns a {@code CharSequence} that is a subsequence of this sequence. - * The subsequence starts with the {@code char} value at the specified index and - * ends with the {@code char} value at index {@code end - 1}. The length - * (in {@code char}s) of the - * returned sequence is {@code end - start}, so if {@code start == end} - * then an empty sequence is returned. - * - * @param start the start index, inclusive - * @param end the end index, exclusive - * - * @return the specified subsequence - * - * @throws IndexOutOfBoundsException - * if {@code start} or {@code end} are negative, - * if {@code end} is greater than {@code length()}, - * or if {@code start} is greater than {@code end} - * @throws IllegalArgumentException - * if a value in the range {@code start}-{@code end} is an ECI (@see #isECI) - */ - fn subSequence(&self, start: usize, end: usize) -> Result, Exceptions> { - if start > end || end > self.length() { - return Err(Exceptions::IndexOutOfBoundsException(start.to_string())); - } - let mut result = String::new(); - for i in start..end { - // for (int i = start; i < end; i++) { - if self.isECI(i as u32)? { - return Err(Exceptions::IllegalArgumentException(format!( - "value at {} is not a character but an ECI", - i - ))); - } - result.push_str(&self.charAt(i)?.to_string()); - } - Ok(result.chars().collect()) - } - - /** - * Determines if a value is an ECI - * - * @param index the index of the value - * - * @return true if the value at position {@code index} is an ECI - * - * @throws IndexOutOfBoundsException - * if the {@code index} argument is negative or not less than - * {@code length()} - */ - fn isECI(&self, index: u32) -> Result { - if index >= self.length() as u32 { - return Err(Exceptions::IndexOutOfBoundsException(index.to_string())); - } - Ok(self.bytes[index as usize] > 255)// && self.bytes[index as usize] <= u16::MAX) - } - - /** - * Returns the {@code int} ECI value at the specified index. An index ranges from zero - * to {@code length() - 1}. The first {@code byte} value of the sequence is at - * index zero, the next at index one, and so on, as for array - * indexing. - * - * @param index the index of the {@code int} value to be returned - * - * @return the specified {@code int} ECI value. - * The ECI specified the encoding of all bytes with a higher index until the - * next ECI or until the end of the input if no other ECI follows. - * - * @throws IndexOutOfBoundsException - * if the {@code index} argument is negative or not less than - * {@code length()} - * @throws IllegalArgumentException - * if the value at the {@code index} argument is not an ECI (@see #isECI) - */ - fn getECIValue(&self, index: usize) -> Result { - if index >= self.length() { - return Err(Exceptions::IndexOutOfBoundsException(index.to_string())); - } - if !self.isECI(index as u32)? { - return Err(Exceptions::IllegalArgumentException(format!( - "value at {} is not an ECI but a character", - index - ))); - } - Ok((self.bytes[index] as u32 - 256) as u32) - } - - fn haveNCharacters(&self, index: usize, n: usize) -> bool { - if index + n - 1 >= self.bytes.len() { - return false; - } - for i in 0..n { - // for (int i = 0; i < n; i++) { - if self.isECI(index as u32 + i as u32).unwrap() { - return false; - } - } - return true; - } -} -impl MinimalECIInput { - /** - * Constructs a minimal input - * - * @param stringToEncode the character string to encode - * @param priorityCharset The preferred {@link Charset}. When the value of the argument is null, the algorithm - * chooses charsets that leads to a minimal representation. Otherwise the algorithm will use the priority - * charset to encode any character in the input that can be encoded by it if the charset is among the - * supported charsets. - * @param fnc1 denotes the character in the input that represents the FNC1 character or -1 if this is not GS1 - * input. - */ - pub fn new( - stringToEncodeInput: &str, - priorityCharset: Option, - fnc1: Option<&str>, - ) -> Self { - let stringToEncode = stringToEncodeInput.graphemes(true).collect::>(); - let encoderSet = ECIEncoderSet::new(stringToEncodeInput, priorityCharset, fnc1); - let bytes = if encoderSet.len() == 1 { - //optimization for the case when all can be encoded without ECI in ISO-8859-1 - let mut bytes_hld = vec![0; stringToEncode.len()]; - for i in 0..stringToEncode.len() { - // for (int i = 0; i < bytes.length; i++) { - let c = stringToEncode.get(i).unwrap(); - bytes_hld[i] = if fnc1.is_some() && c == fnc1.as_ref().unwrap() { - 1000 - } else { - c.chars().nth(0).unwrap() as u16 - }; - } - bytes_hld - } else { - Self::encodeMinimally( - stringToEncodeInput, - &encoderSet, - fnc1.as_ref().unwrap().chars().nth(0).unwrap() as u16, - ) - }; - - Self { - bytes: bytes, - fnc1: fnc1.as_ref().unwrap().chars().nth(0).unwrap() as u16, - } - } - - pub fn getFNC1Character(&self) -> u16 { - self.fnc1 - } - - /** - * Determines if a value is the FNC1 character - * - * @param index the index of the value - * - * @return true if the value at position {@code index} is the FNC1 character - * - * @throws IndexOutOfBoundsException - * if the {@code index} argument is negative or not less than - * {@code length()} - */ - pub fn isFNC1(&self, index: usize) -> Result { - if index >= self.length() { - return Err(Exceptions::IndexOutOfBoundsException(index.to_string())); - } - Ok(self.bytes[index] == 1000) - } - - fn addEdge(edges: &mut Vec>>>, to: usize, edge: Rc) { - if edges[to][edge.encoderIndex].is_none() - || edges[to][edge.encoderIndex] - .clone() - .unwrap() - .cachedTotalSize - > edge.cachedTotalSize - { - edges[to][edge.encoderIndex] = Some(edge.clone()); - } - } - - fn addEdges( - stringToEncode: &str, - encoderSet: &ECIEncoderSet, - edges: &mut Vec>>>, - from: usize, - previous: Option>, - fnc1: u16, - ) { - // let ch = stringToEncode.chars().nth(from).unwrap() as i16; - let ch = stringToEncode.graphemes(true).nth(from).unwrap(); - - let mut start = 0; - let mut end = encoderSet.len(); - if encoderSet.getPriorityEncoderIndex().is_some() - && (ch.chars().nth(0).unwrap() as u16 == fnc1 - || encoderSet.canEncode(ch, encoderSet.getPriorityEncoderIndex().unwrap())) - { - start = encoderSet.getPriorityEncoderIndex().unwrap(); - end = start + 1; - } - - for i in start..end { - // for (int i = start; i < end; i++) { - if ch.chars().nth(0).unwrap() as u16 == fnc1 || encoderSet.canEncode(ch, i) { - Self::addEdge( - edges, - from + 1, - Rc::new(InputEdge::new(ch, encoderSet, i, previous.clone(), fnc1)), - ); - } - } - } - - pub fn encodeMinimally( - stringToEncode: &str, - encoderSet: &ECIEncoderSet, - fnc1: u16, - ) -> Vec { - let inputLength = stringToEncode.len(); - - // Array that represents vertices. There is a vertex for every character and encoding. - let mut edges = vec![vec![None; encoderSet.len()]; inputLength + 1]; //InputEdge[inputLength + 1][encoderSet.length()]; - Self::addEdges(stringToEncode, encoderSet, &mut edges, 0, None, fnc1); - - for i in 0..=inputLength { - // for (int i = 1; i <= inputLength; i++) { - for j in 0..encoderSet.len() { - // for (int j = 0; j < encoderSet.length(); j++) { - if edges[i][j].is_some() && i < inputLength { - let edg = edges[i][j].clone(); - Self::addEdges(stringToEncode, encoderSet, &mut edges, i, edg, fnc1); - } - } - //optimize memory by removing edges that have been passed. - for j in 0..encoderSet.len() { - // for (int j = 0; j < encoderSet.length(); j++) { - edges[i - 1][j] = None; - } - } - let mut minimalJ: i32 = -1; - let mut minimalSize: i32 = i32::MAX; - for j in 0..encoderSet.len() { - // for (int j = 0; j < encoderSet.length(); j++) { - if edges[inputLength][j].is_some() { - let edge = edges[inputLength][j].clone().unwrap(); - if (edge.cachedTotalSize as i32) < minimalSize { - minimalSize = edge.cachedTotalSize as i32; - minimalJ = j as i32; - } - } - } - if minimalJ < 0 { - panic!("Internal error: failed to encode \"{}\"", stringToEncode); - } - let mut intsAL: Vec = Vec::new(); - let mut current = edges[inputLength][minimalJ as usize].clone(); - while current.is_some() { - let c = current.unwrap().clone(); - if c.isFNC1() { - intsAL.splice(0..0, [1000]); - } else { - let bytes: Vec = encoderSet - .encode_char(&c.c, c.encoderIndex) - .iter() - .map(|x| *x as u16) - .collect(); - let mut i = bytes.len() as i32 - 1; - while i >= 0 { - // for (int i = bytes.length - 1; i >= 0; i--) { - intsAL.splice(0..0, [bytes[i as usize]]); - i = -1; - } - } - let previousEncoderIndex = if c.previous.is_none() { - 0 - } else { - c.previous.clone().unwrap().encoderIndex - }; - if previousEncoderIndex != c.encoderIndex { - intsAL.splice( - 0..0, - [256 as u16 + encoderSet.getECIValue(c.encoderIndex) as u16], - ); - } - current = c.previous.clone(); - } - let mut ints = vec![0; intsAL.len()]; - for i in 0..ints.len() { - // for (int i = 0; i < ints.length; i++) { - ints[i] = *intsAL.get(i).unwrap() as u16; - } - return ints; - } -} - -struct InputEdge { - c: String, - encoderIndex: usize, //the encoding of this edge - previous: Option>, - cachedTotalSize: usize, -} -impl InputEdge { - pub fn new( - c: &str, - encoderSet: &ECIEncoderSet, - encoderIndex: usize, - previous: Option>, - fnc1: u16, - ) -> Self { - let mut size = if c == "\u{1000}" { - 1 - } else { - encoderSet.encode_char(c, encoderIndex).len() - }; - - let fnc1Str = String::from_utf16(&[fnc1]).unwrap(); - - if let Some(prev) = previous { - let previousEncoderIndex = prev.encoderIndex; - if previousEncoderIndex != encoderIndex { - size += COST_PER_ECI; - } - size += prev.cachedTotalSize; - - Self { - c: if c == fnc1Str { - String::from("\u{1000}") - } else { - String::from(c) - }, - encoderIndex, - previous: Some(prev.clone()), - cachedTotalSize: size, - } - } else { - let previousEncoderIndex = 0; - if previousEncoderIndex != encoderIndex { - size += COST_PER_ECI; - } - - Self { - c: if c == fnc1Str { - String::from("\u{1000}") - } else { - String::from(c) - }, - encoderIndex, - previous: None, - cachedTotalSize: size, - } - } - - // int size = this.c == 1000 ? 1 : encoderSet.encode(c, encoderIndex).length; - // let previousEncoderIndex = if previous.is_none() { - // 0 - // } else { - // previous.unwrap().encoderIndex - // }; - // int previousEncoderIndex = previous == null ? 0 : previous.encoderIndex; - // if previousEncoderIndex != encoderIndex { - // size += COST_PER_ECI; - // } - // if prev_is_some { - // size += previous.unwrap().cachedTotalSize; - // } - - // Self { - // c: if c == fnc1 { 1000 as char } else { c }, - // encoderIndex, - // previous: previous, - // cachedTotalSize: size, - // } - // this.c = c == fnc1 ? 1000 : c; - // this.encoderIndex = encoderIndex; - // this.previous = previous; - // this.cachedTotalSize = size; - } - - pub fn isFNC1(&self) -> bool { - self.c == "\u{1000}" - } -} - -impl fmt::Display for MinimalECIInput { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut result = String::new(); - for i in 0..self.length() { - // for (int i = 0; i < length(); i++) { - if i > 0 { - result.push_str(", "); - } - if self.isECI(i as u32).unwrap() { - result.push_str("ECI("); - result.push_str(&self.getECIValue(i).unwrap().to_string()); - result.push(')'); - } else if (self.charAt(i).unwrap() as u8) < 128 { - result.push('\''); - result.push(self.charAt(i).unwrap()); - result.push('\''); - } else { - result.push(self.charAt(i).unwrap()); - } - } - write!(f, "{}", result) - } -} - -/* - * Copyright 2009 ZXing authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// package com.google.zxing.common; - -// import com.google.zxing.Binarizer; -// import com.google.zxing.LuminanceSource; -// import com.google.zxing.NotFoundException; - -/** - * This Binarizer implementation uses the old ZXing global histogram approach. It is suitable - * for low-end mobile devices which don't have enough CPU or memory to use a local thresholding - * algorithm. However, because it picks a global black point, it cannot handle difficult shadows - * and gradients. - * - * Faster mobile devices and all desktop applications should probably use HybridBinarizer instead. - * - * @author dswitkin@google.com (Daniel Switkin) - * @author Sean Owen - */ -pub struct GlobalHistogramBinarizer { - luminances: Vec, - buckets: Vec, - width: usize, - height: usize, - source: Box, -} - -impl Binarizer for GlobalHistogramBinarizer { - fn getLuminanceSource(&self) -> &Box { - &self.source - } - - // Applies simple sharpening to the row data to improve performance of the 1D Readers. - fn getBlackRow(&self, y: usize, row: &mut BitArray) -> Result { - let source = self.getLuminanceSource(); - let width = source.getWidth(); - let mut row = if row.getSize() < width { - BitArray::with_size(width) - } else { - let mut z = row.clone(); - z.clear(); - z - }; - - // self.initArrays(width); - let localLuminances = source.getRow(y, &self.luminances); - let mut localBuckets = self.buckets.clone(); - for x in 0..width { - // for (int x = 0; x < width; x++) { - localBuckets - [((localLuminances[x]) >> GlobalHistogramBinarizer::LUMINANCE_SHIFT) as usize] += 1; - } - let blackPoint = self.estimateBlackPoint(&localBuckets)?; - - if width < 3 { - // Special case for very small images - for x in 0..width { - // for (int x = 0; x < width; x++) { - if (localLuminances[x] as u32) < blackPoint { - row.set(x); - } - } - } else { - let mut left = localLuminances[0]; // & 0xff; - let mut center = localLuminances[1]; // & 0xff; - for x in 1..width - 1 { - // for (int x = 1; x < width - 1; x++) { - let right = localLuminances[x + 1] & 0xff; - // A simple -1 4 -1 box filter with a weight of 2. - if ((center * 4) - left - right) as u32 / 2 < blackPoint { - row.set(x); - } - left = center; - center = right; - } - } - Ok(row) - } - - // Does not sharpen the data, as this call is intended to only be used by 2D Readers. - fn getBlackMatrix(&self) -> Result { - let source = self.getLuminanceSource(); - let width = source.getWidth(); - let height = source.getHeight(); - 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 - // more robust on the blackbox tests than sampling a diagonal as we used to do. - // self.initArrays(width); - let mut localBuckets = self.buckets.clone(); - for y in 1..5 { - // for (int y = 1; y < 5; y++) { - let row = height * y / 5; - let localLuminances = source.getRow(row, &self.luminances); - let right = (width * 4) / 5; - let mut x = width / 5; - while x < right { - // for (int x = width / 5; x < right; x++) { - let pixel = localLuminances[x]; - localBuckets[(pixel >> GlobalHistogramBinarizer::LUMINANCE_SHIFT) as usize] += 1; - x += 1; - } - } - let blackPoint = self.estimateBlackPoint(&localBuckets)?; - - // 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 localLuminances = source.getMatrix(); - for y in 0..height { - // for (int y = 0; y < height; y++) { - let offset = y * width; - for x in 0..width { - // for (int x = 0; x < width; x++) { - let pixel = localLuminances[offset + x] & 0xff; - if (pixel as u32) < blackPoint { - matrix.set(x as u32, y as u32); - } - } - } - - Ok(matrix) - } - - fn createBinarizer(&self, source: Box) -> Box { - return Box::new(GlobalHistogramBinarizer::new(source)); - } - - fn getWidth(&self) -> usize { - self.width - } - - fn getHeight(&self) -> usize { - self.height - } -} - -impl GlobalHistogramBinarizer { - const LUMINANCE_BITS: usize = 5; - const LUMINANCE_SHIFT: usize = 8 - GlobalHistogramBinarizer::LUMINANCE_BITS; - const LUMINANCE_BUCKETS: usize = 1 << GlobalHistogramBinarizer::LUMINANCE_BITS; - const EMPTY: [u8; 0] = [0; 0]; - - pub fn new(source: Box) -> Self { - Self { - luminances: vec![0; source.getWidth()], - buckets: vec![0; GlobalHistogramBinarizer::LUMINANCE_BUCKETS], - width: source.getWidth(), - height: source.getHeight(), - source: source, - } - } - - // fn initArrays(&mut self, luminanceSize: usize) { - // // if self.luminances.len() < luminanceSize { - // // self.luminances = ; - // // } - // // // for x in 0..GlobalHistogramBinarizer::LUMINANCE_BUCKETS { - // // // for (int x = 0; x < LUMINANCE_BUCKETS; x++) { - // // self.buckets[x] = 0; - // // } - // } - - fn estimateBlackPoint(&self, buckets: &[u32]) -> Result { - // Find the tallest peak in the histogram. - let numBuckets = buckets.len(); - let mut maxBucketCount = 0; - let mut firstPeak = 0; - let mut firstPeakSize = 0; - for x in 0..numBuckets { - // for (int x = 0; x < numBuckets; x++) { - if buckets[x] > firstPeakSize { - firstPeak = x; - firstPeakSize = buckets[x]; - } - if buckets[x] > maxBucketCount { - maxBucketCount = buckets[x]; - } - } - - // Find the second-tallest peak which is somewhat far from the tallest peak. - let mut secondPeak = 0; - let mut secondPeakScore = 0; - for x in 0..numBuckets { - // for (int x = 0; x < numBuckets; x++) { - let distanceToBiggest = x - firstPeak; - // Encourage more distant second peaks by multiplying by square of distance. - let score = buckets[x] * distanceToBiggest as u32 * distanceToBiggest as u32; - if score > secondPeakScore { - secondPeak = x; - secondPeakScore = score; - } - } - - // Make sure firstPeak corresponds to the black peak. - if firstPeak > secondPeak { - let temp = firstPeak; - firstPeak = secondPeak; - secondPeak = temp; - } - - // 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 secondPeak - firstPeak <= numBuckets / 16 { - return Err(Exceptions::NotFoundException( - "secondPeak - firstPeak <= numBuckets / 16 ".to_owned(), - )); - } - - // Find a valley between them that is low and closer to the white peak. - let mut bestValley = secondPeak - 1; - let mut bestValleyScore = -1i32; - let mut x = secondPeak; - while x > firstPeak { - // for (int x = secondPeak - 1; x > firstPeak; x--) { - let fromFirst = x - firstPeak; - let score = - fromFirst * fromFirst * (secondPeak - x) * (maxBucketCount - buckets[x]) as usize; - if score as i32 > bestValleyScore { - bestValley = x; - bestValleyScore = score as i32; - } - x -= 1; - } - - Ok((bestValley as u32) << GlobalHistogramBinarizer::LUMINANCE_SHIFT) - } -} - -/* - * Copyright 2009 ZXing authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// package com.google.zxing.common; - -// import com.google.zxing.Binarizer; -// import com.google.zxing.LuminanceSource; -// import com.google.zxing.NotFoundException; - -/** - * This class implements a local thresholding algorithm, which while slower than the - * GlobalHistogramBinarizer, is fairly efficient for what it does. It is designed for - * high frequency images of barcodes with black data on white backgrounds. For this application, - * it does a much better job than a global blackpoint with severe shadows and gradients. - * However it tends to produce artifacts on lower frequency images and is therefore not - * a good general purpose binarizer for uses outside ZXing. - * - * This class extends GlobalHistogramBinarizer, using the older histogram approach for 1D readers, - * and the newer local approach for 2D readers. 1D decoding using a per-row histogram is already - * inherently local, and only fails for horizontal gradients. We can revisit that problem later, - * but for now it was not a win to use local blocks for 1D. - * - * This Binarizer is the default for the unit tests and the recommended class for library users. - * - * @author dswitkin@google.com (Daniel Switkin) - */ -pub struct HybridBinarizer { - //width: usize, - //height: usize, - //source: Box, - ghb: GlobalHistogramBinarizer, - // matrix :Option, -} -impl Binarizer for HybridBinarizer { - fn getLuminanceSource(&self) -> &Box { - self.ghb.getLuminanceSource() - } - - fn getBlackRow(&self, y: usize, row: &mut BitArray) -> Result { - self.ghb.getBlackRow(y, row) - } - - /** - * 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 getBlackMatrix(&self) -> Result { - // if self.matrix.is_some() { - // return Ok(self.matrix.clone().unwrap()) - // } - let matrix; - let source = self.getLuminanceSource(); - let width = source.getWidth(); - let height = source.getHeight(); - if width >= HybridBinarizer::MINIMUM_DIMENSION - && height >= HybridBinarizer::MINIMUM_DIMENSION - { - let luminances = source.getMatrix(); - let mut sub_width = width >> HybridBinarizer::BLOCK_SIZE_POWER; - if (width & HybridBinarizer::BLOCK_SIZE_MASK) != 0 { - sub_width += 1; - } - let mut sub_height = height >> HybridBinarizer::BLOCK_SIZE_POWER; - if (height & HybridBinarizer::BLOCK_SIZE_MASK) != 0 { - sub_height += 1; - } - let black_points = Self::calculateBlackPoints( - &luminances, - sub_width as u32, - sub_height as u32, - width as u32, - height as u32, - ); - - let mut new_matrix = BitMatrix::new(width as u32, height as u32)?; - Self::calculateThresholdForBlock( - &luminances, - sub_width as u32, - sub_height as u32, - width as u32, - height as u32, - &black_points, - &mut new_matrix, - ); - matrix = new_matrix; - } else { - // If the image is too small, fall back to the global histogram approach. - matrix = self.ghb.getBlackMatrix()?; - } - // dbg!(matrix.to_string()); - Ok(matrix) - } - - fn createBinarizer(&self, source: Box) -> Box { - Box::new(HybridBinarizer::new(source)) - } - - fn getWidth(&self) -> usize { - self.ghb.getWidth() - } - - fn getHeight(&self) -> usize { - self.ghb.getHeight() - } -} -impl HybridBinarizer { - // This class uses 5x5 blocks to compute local luminance, where each block is 8x8 pixels. - // So this is the smallest dimension in each axis we can accept. - const BLOCK_SIZE_POWER: usize = 3; - const BLOCK_SIZE: usize = 1 << HybridBinarizer::BLOCK_SIZE_POWER; // ...0100...00 - const BLOCK_SIZE_MASK: usize = HybridBinarizer::BLOCK_SIZE - 1; // ...0011...11 - const MINIMUM_DIMENSION: usize = HybridBinarizer::BLOCK_SIZE * 5; - const MIN_DYNAMIC_RANGE: usize = 24; - - pub fn new(source: Box) -> Self { - Self { - ghb: GlobalHistogramBinarizer::new(source), - // matrix: None, - } - } - - /** - * For each block in the image, calculate the average black point using a 5x5 grid - * of the blocks around it. Also handles the corner cases (fractional blocks are computed based - * on the last pixels in the row/column which are also used in the previous block). - */ - fn calculateThresholdForBlock( - luminances: &[u8], - sub_width: u32, - sub_height: u32, - width: u32, - height: u32, - black_points: &Vec>, - matrix: &mut BitMatrix, - ) { - let maxYOffset = height - HybridBinarizer::BLOCK_SIZE as u32; - let maxXOffset = width - HybridBinarizer::BLOCK_SIZE as u32; - for y in 0..sub_height { - // for (int y = 0; y < subHeight; y++) { - let mut yoffset = y << HybridBinarizer::BLOCK_SIZE_POWER; - if yoffset > maxYOffset { - yoffset = maxYOffset; - } - let top = Self::cap(y, sub_height - 3); - for x in 0..sub_width { - // for (int x = 0; x < subWidth; x++) { - let mut xoffset = x << HybridBinarizer::BLOCK_SIZE_POWER; - if xoffset > maxXOffset { - xoffset = maxXOffset; - } - let left = Self::cap(x, sub_width - 3); - let mut sum = 0; - for z in -2i32..=2 { - // for (int z = -2; z <= 2; z++) { - let blackRow = &black_points[(top as i32 + z) as usize]; - sum += blackRow[(left - 2) as usize] - + blackRow[(left - 1) as usize] - + blackRow[left as usize] - + blackRow[(left + 1) as usize] - + blackRow[(left + 2) as usize]; - } - let average = sum / 25; - Self::thresholdBlock(luminances, xoffset, yoffset, average, width, matrix); - } - } - } - - fn cap(value: u32, max: u32) -> u32 { - if value < 2 { - 2 - } else { - value.min(max) - } - } - - /** - * Applies a single threshold to a block of pixels. - */ - fn thresholdBlock( - luminances: &[u8], - xoffset: u32, - yoffset: u32, - threshold: u32, - stride: u32, - matrix: &mut BitMatrix, - ) { - let mut offset = yoffset * stride + xoffset; - for y in 0..HybridBinarizer::BLOCK_SIZE { - // for (int y = 0, offset = yoffset * stride + xoffset; y < HybridBinarizer::BLOCK_SIZE; y++, offset += stride) { - for x in 0..HybridBinarizer::BLOCK_SIZE { - // for (int x = 0; x < HybridBinarizer::BLOCK_SIZE; x++) { - // Comparison needs to be <= so that black == 0 pixels are black even if the threshold is 0. - if luminances[offset as usize + x] as u32 <= threshold { - matrix.set(xoffset + x as u32, yoffset + y as u32); - } - } - offset += stride; - } - } - - /** - * Calculates a single black point for each block of pixels and saves it away. - * See the following thread for a discussion of this algorithm: - * http://groups.google.com/group/zxing/browse_thread/thread/d06efa2c35a7ddc0 - */ - fn calculateBlackPoints( - luminances: &[u8], - subWidth: u32, - subHeight: u32, - width: u32, - height: u32, - ) -> Vec> { - let maxYOffset = height as usize - HybridBinarizer::BLOCK_SIZE; - let maxXOffset = width as usize - HybridBinarizer::BLOCK_SIZE; - let mut blackPoints = vec![vec![0; subWidth as usize]; subHeight as usize]; - for y in 0..subHeight { - // for (int y = 0; y < subHeight; y++) { - let mut yoffset = y << HybridBinarizer::BLOCK_SIZE_POWER; - if yoffset > maxYOffset as u32 { - yoffset = maxYOffset as u32; - } - for x in 0..subWidth { - // for (int x = 0; x < subWidth; x++) { - let mut xoffset = x << HybridBinarizer::BLOCK_SIZE_POWER; - if xoffset > maxXOffset as u32 { - xoffset = maxXOffset as u32; - } - let mut sum = 0u32; - let mut min = 0xff; - let mut max = 0; - - let mut offset = yoffset * width + xoffset; - let mut yy = 0; - while yy < HybridBinarizer::BLOCK_SIZE { - // for (int yy = 0, offset = yoffset * width + xoffset; yy < HybridBinarizer::BLOCK_SIZE; yy++, offset += width) { - for xx in 0..HybridBinarizer::BLOCK_SIZE { - // for (int xx = 0; xx < HybridBinarizer::BLOCK_SIZE; xx++) { - let pixel = luminances[offset as usize + xx]; - sum += pixel as u32; - // still looking for good contrast - if pixel < min { - min = pixel; - } - if pixel > max { - max = pixel; - } - } - // short-circuit min/max tests once dynamic range is met - if (max - min) as usize > HybridBinarizer::MIN_DYNAMIC_RANGE { - // finish the rest of the rows quickly - offset += width; - yy += 1; - while yy < HybridBinarizer::BLOCK_SIZE { - // for (yy++, offset += width; yy < HybridBinarizer::BLOCK_SIZE; yy++, offset += width) { - for xx in 0..HybridBinarizer::BLOCK_SIZE { - // for (int xx = 0; xx < BLOCK_SIZE; xx++) { - sum += luminances[offset as usize + xx] as u32; - } - yy += 1; - offset += width; - } - break; - } - yy += 1; - offset += width; - } - - // The default estimate is the average of the values in the block. - let mut average = sum >> (HybridBinarizer::BLOCK_SIZE_POWER * 2); - if (max - min) as usize <= HybridBinarizer::MIN_DYNAMIC_RANGE { - // If variation within the block is low, assume this is a block with only light or only - // dark pixels. In that case we do not want to use the average, as it would divide this - // low contrast area into black and white pixels, essentially creating data out of noise. - // - // The default assumption is that the block is light/background. Since no estimate for - // the level of dark pixels exists locally, use half the min for the block. - average = min as u32 / 2; - - if y > 0 && x > 0 { - // Correct the "white background" assumption for blocks that have neighbors by comparing - // the pixels in this block to the previously calculated black points. This is based on - // the fact that dark barcode symbology is always surrounded by some amount of light - // background for which reasonable black point estimates were made. The bp estimated at - // the boundaries is used for the interior. - - // The (min < bp) is arbitrary but works better than other heuristics that were tried. - let average_neighbor_black_point: u32 = (blackPoints[y as usize - 1] - [x as usize] - + (2 * blackPoints[y as usize][x as usize - 1]) - + blackPoints[y as usize - 1][x as usize - 1]) - / 4; - if (min as u32) < average_neighbor_black_point { - average = average_neighbor_black_point; - } - } - } - blackPoints[y as usize][x as usize] = average; - } - } - blackPoints - } -} +mod hybrid_binarizer; +pub use hybrid_binarizer::*; \ No newline at end of file diff --git a/src/common/perspective_transform.rs b/src/common/perspective_transform.rs new file mode 100644 index 0000000..3584d09 --- /dev/null +++ b/src/common/perspective_transform.rs @@ -0,0 +1,213 @@ + +/* + * Copyright 2007 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// package com.google.zxing.common; + +/** + *

This class implements a perspective transform in two dimensions. Given four source and four + * destination points, it will compute the transformation implied between them. The code is based + * directly upon section 3.4.2 of George Wolberg's "Digital Image Warping"; see pages 54-56.

+ * + * @author Sean Owen + */ +pub struct PerspectiveTransform { + a11: f32, + a12: f32, + a13: f32, + a21: f32, + a22: f32, + a23: f32, + a31: f32, + a32: f32, + a33: f32, +} + +impl PerspectiveTransform { + fn new( + a11: f32, + a21: f32, + a31: f32, + a12: f32, + a22: f32, + a32: f32, + a13: f32, + a23: f32, + a33: f32, + ) -> Self { + Self { + a11, + a12, + a13, + a21, + a22, + a23, + a31, + a32, + a33, + } + } + + pub fn quadrilateralToQuadrilateral( + x0: f32, + y0: f32, + x1: f32, + y1: f32, + x2: f32, + y2: f32, + x3: f32, + y3: f32, + x0p: f32, + y0p: f32, + x1p: f32, + y1p: f32, + x2p: f32, + y2p: f32, + x3p: f32, + y3p: f32, + ) -> Self { + let q_to_s = PerspectiveTransform::quadrilateralToSquare(x0, y0, x1, y1, x2, y2, x3, y3); + let s_to_q = + PerspectiveTransform::squareToQuadrilateral(x0p, y0p, x1p, y1p, x2p, y2p, x3p, y3p); + return s_to_q.times(&q_to_s); + } + + pub fn transform_points_single(&self, points: &mut [f32]) { + let a11 = self.a11; + let a12 = self.a12; + let a13 = self.a13; + let a21 = self.a21; + let a22 = self.a22; + let a23 = self.a23; + let a31 = self.a31; + let a32 = self.a32; + let a33 = self.a33; + let maxI = points.len() - 1; // points.length must be even + let mut i = 0; + while i < maxI { + // for (int i = 0; i < maxI; i += 2) { + let x = points[i]; + let y = points[i + 1]; + let denominator = a13 * x + a23 * y + a33; + points[i] = (a11 * x + a21 * y + a31) / denominator; + points[i + 1] = (a12 * x + a22 * y + a32) / denominator; + i += 2; + } + } + + pub fn transform_points_double(&self, x_values: &mut [f32], y_valuess: &mut [f32]) { + let n = x_values.len(); + for i in 0..n { + // for (int i = 0; i < n; i++) { + let x = x_values[i]; + let y = y_valuess[i]; + let denominator = self.a13 * x + self.a23 * y + self.a33; + x_values[i] = (self.a11 * x + self.a21 * y + self.a31) / denominator; + y_valuess[i] = (self.a12 * x + self.a22 * y + self.a32) / denominator; + } + } + + pub fn squareToQuadrilateral( + x0: f32, + y0: f32, + x1: f32, + y1: f32, + x2: f32, + y2: f32, + x3: f32, + y3: f32, + ) -> Self { + let dx3 = x0 - x1 + x2 - x3; + let dy3 = y0 - y1 + y2 - y3; + if dx3 == 0.0f32 && dy3 == 0.0f32 { + // Affine + return PerspectiveTransform::new( + x1 - x0, + x2 - x1, + x0, + y1 - y0, + y2 - y1, + y0, + 0.0f32, + 0.0f32, + 1.0f32, + ); + } else { + let dx1 = x1 - x2; + let dx2 = x3 - x2; + let dy1 = y1 - y2; + let dy2 = y3 - y2; + let denominator = dx1 * dy2 - dx2 * dy1; + let a13 = (dx3 * dy2 - dx2 * dy3) / denominator; + let a23 = (dx1 * dy3 - dx3 * dy1) / denominator; + return PerspectiveTransform::new( + x1 - x0 + a13 * x1, + x3 - x0 + a23 * x3, + x0, + y1 - y0 + a13 * y1, + y3 - y0 + a23 * y3, + y0, + a13, + a23, + 1.0f32, + ); + } + } + + pub fn quadrilateralToSquare( + x0: f32, + y0: f32, + x1: f32, + y1: f32, + x2: f32, + y2: f32, + x3: f32, + y3: f32, + ) -> Self { + // Here, the adjoint serves as the inverse + return PerspectiveTransform::squareToQuadrilateral(x0, y0, x1, y1, x2, y2, x3, y3) + .buildAdjoint(); + } + + fn buildAdjoint(&self) -> Self { + // Adjoint is the transpose of the cofactor matrix: + return PerspectiveTransform::new( + self.a22 * self.a33 - self.a23 * self.a32, + self.a23 * self.a31 - self.a21 * self.a33, + self.a21 * self.a32 - self.a22 * self.a31, + self.a13 * self.a32 - self.a12 * self.a33, + self.a11 * self.a33 - self.a13 * self.a31, + self.a12 * self.a31 - self.a11 * self.a32, + self.a12 * self.a23 - self.a13 * self.a22, + self.a13 * self.a21 - self.a11 * self.a23, + self.a11 * self.a22 - self.a12 * self.a21, + ); + } + + fn times(&self, other: &Self) -> Self { + return PerspectiveTransform::new( + self.a11 * other.a11 + self.a21 * other.a12 + self.a31 * other.a13, + self.a11 * other.a21 + self.a21 * other.a22 + self.a31 * other.a23, + self.a11 * other.a31 + self.a21 * other.a32 + self.a31 * other.a33, + self.a12 * other.a11 + self.a22 * other.a12 + self.a32 * other.a13, + self.a12 * other.a21 + self.a22 * other.a22 + self.a32 * other.a23, + self.a12 * other.a31 + self.a22 * other.a32 + self.a32 * other.a33, + self.a13 * other.a11 + self.a23 * other.a12 + self.a33 * other.a13, + self.a13 * other.a21 + self.a23 * other.a22 + self.a33 * other.a23, + self.a13 * other.a31 + self.a23 * other.a32 + self.a33 * other.a33, + ); + } +} \ No newline at end of file diff --git a/src/common/reedsolomon/generic_gf.rs b/src/common/reedsolomon/generic_gf.rs new file mode 100644 index 0000000..9bdfdbe --- /dev/null +++ b/src/common/reedsolomon/generic_gf.rs @@ -0,0 +1,178 @@ +use std::fmt; + +use crate::Exceptions; + +use super::{GenericGFRef, GenericGFPoly}; + +/** + *

This class contains utility methods for performing mathematical operations over + * the Galois Fields. Operations use a given primitive polynomial in calculations.

+ * + *

Throughout this package, elements of the GF are represented as an {@code int} + * for convenience and speed (but at the cost of memory). + *

+ * + * @author Sean Owen + * @author David Olivier + */ +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct GenericGF { + expTable: Vec, + logTable: Vec, + // zero: Box, + // one: Box, + size: usize, + primitive: i32, + generatorBase: i32, +} + +impl GenericGF { + /** + * Create a representation of GF(size) using the given primitive polynomial. + * + * @param primitive irreducible polynomial whose coefficients are represented by + * the bits of an int, where the least-significant bit represents the constant + * coefficient + * @param size the size of the field + * @param b the factor b in the generator polynomial can be 0- or 1-based + * (g(x) = (x+a^b)(x+a^(b+1))...(x+a^(b+2t-1))). + * In most cases it should be 1, but for QR code it is 0. + */ + pub fn new(primitive: i32, size: usize, b: i32) -> Self { + let mut expTable = vec![0; size]; + let mut logTable = vec![0; size]; + let mut x = 1; + for i in 0..size { + //for (int i = 0; i < size; i++) { + //expTable.push(x); + expTable[i] = x; + x *= 2; // we're assuming the generator alpha is 2 + if x >= size as i32 { + x ^= primitive; + let sz_m_1: i32 = size as i32 - 1; + x &= sz_m_1; + } + } + for i in 0..size - 1 { + //for (int i = 0; i < size - 1; i++) { + let loc: usize = expTable[i] as usize; + logTable[loc] = i as i32; + } + logTable[0] = 0; + + // let mut p:u32; + // //int i; + // /*Initialize the table of powers of a primtive root, alpha=0x02.*/ + // p = 1; + // for i in 0..size { + // // for (i = 0; i < 256; i++) { + // expTable[i] = expTable[i + size - 1] = p; + // p = ((p << 1) ^ (-(p as i32 >> 7) & primitive) as u32) & 0xFF; + // } + // /*Invert the table to recover the logs.*/ + // for i in 0..size-1 { + // // for (i = 0; i < 255; i++) + // logTable[expTable[i].try_into().unwrap()] = i; + // /*Note that we rely on the fact that _gf->log[0]=0 below.*/ + Self { + expTable, + logTable, + size, + primitive, + generatorBase: b, + } + + // logTable[0] == 0 but this should never be used + // new_ggf.zero = Box::new(GenericGFPoly::new(Box::new(new_ggf), &vec![0]).unwrap()); + // new_ggf.one = Box::new(GenericGFPoly::new(Box::new(new_ggf), &vec![1]).unwrap()); + + //new_ggf + } + + // pub fn getZero(&self) -> Box { + // return self.zero; + // } + + // pub fn getOne(&self) -> Box { + // return self.one; + // } + + /** + * @return the monomial representing coefficient * x^degree + */ + pub fn buildMonomial(source: GenericGFRef, degree: usize, coefficient: i32) -> GenericGFPoly { + if coefficient == 0 { + return GenericGFPoly::new(source, &vec![0]).unwrap(); + } + let mut coefficients = vec![0; degree + 1]; + coefficients[0] = coefficient; + return GenericGFPoly::new(source, &coefficients).unwrap(); + } + + /** + * Implements both addition and subtraction -- they are the same in GF(size). + * + * @return sum/difference of a and b + */ + pub fn addOrSubtract(a: i32, b: i32) -> i32 { + return a ^ b; + } + + /** + * @return 2 to the power of a in GF(size) + */ + pub fn exp(&self, a: i32) -> i32 { + // let pos: usize = a.try_into().unwrap(); + return self.expTable[a as usize]; + } + + /** + * @return base 2 log of a in GF(size) + */ + pub fn log(&self, a: i32) -> Result { + if a == 0 { + return Err(Exceptions::IllegalArgumentException("".to_owned())); + } + // let pos: usize = a.try_into().unwrap(); + return Ok(self.logTable[a as usize]); + } + + /** + * @return multiplicative inverse of a + */ + pub fn inverse(&self, a: i32) -> Result { + if a == 0 { + return Err(Exceptions::ArithmeticException("".to_owned())); + } + let log_t_loc: usize = a as usize; + let loc: usize = ((self.size as i32) - self.logTable[log_t_loc] - 1) as usize; + return Ok(self.expTable[loc]); + } + + /** + * @return product of a and b in GF(size) + */ + pub fn multiply(&self, a: i32, b: i32) -> i32 { + if a == 0 || b == 0 { + return 0; + } + let a_loc: usize = a as usize; //.try_into().unwrap(); + let b_loc: usize = b as usize; //.try_into().unwrap(); + let comb_loc: usize = (self.logTable[a_loc] + self.logTable[b_loc]) as usize; + return self.expTable[comb_loc % (self.size - 1)]; + } + + pub fn getSize(&self) -> usize { + return self.size; + } + + pub fn getGeneratorBase(&self) -> i32 { + return self.generatorBase; + } +} + +impl fmt::Display for GenericGF { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "GF({:#06x},{}", self.primitive, self.size) + } +} \ No newline at end of file diff --git a/src/common/reedsolomon/generic_gf_poly.rs b/src/common/reedsolomon/generic_gf_poly.rs new file mode 100644 index 0000000..1f9f53b --- /dev/null +++ b/src/common/reedsolomon/generic_gf_poly.rs @@ -0,0 +1,340 @@ +/* + * Copyright 2007 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//package com.google.zxing.common.reedsolomon; + +use std::fmt; + +use crate::Exceptions; + +use super::{GenericGFRef, GenericGF}; + +/** + *

Represents a polynomial whose coefficients are elements of a GF. + * Instances of this class are immutable.

+ * + *

Much credit is due to William Rucklidge since portions of this code are an indirect + * port of his C++ Reed-Solomon implementation.

+ * + * @author Sean Owen + */ +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct GenericGFPoly { + field: GenericGFRef, + coefficients: Vec, +} + +impl GenericGFPoly { + /** + * @param field the {@link GenericGF} instance representing the field to use + * to perform computations + * @param coefficients coefficients as ints representing elements of GF(size), arranged + * from most significant (highest-power term) coefficient to least significant + * @throws IllegalArgumentException if argument is null or empty, + * or if leading coefficient is 0 and this is not a + * constant polynomial (that is, it is not the monomial "0") + */ + pub fn new(field: GenericGFRef, coefficients: &Vec) -> Result { + if coefficients.len() == 0 { + return Err(Exceptions::IllegalArgumentException( + "coefficients.len()".to_owned(), + )); + } + Ok(Self { + field: field, + coefficients: { + let coefficients_length = coefficients.len(); + if coefficients_length > 1 && coefficients[0] == 0 { + // Leading term must be non-zero for anything except the constant polynomial "0" + let mut first_non_zero = 1; + while first_non_zero < coefficients_length && coefficients[first_non_zero] == 0 + { + first_non_zero += 1; + } + if first_non_zero == coefficients_length { + vec![0] + } else { + let mut new_coefficients = vec![0; coefficients_length - first_non_zero]; + let l = new_coefficients.len() - 1; + new_coefficients[0..=l].clone_from_slice(&coefficients[first_non_zero..]); + // System.arraycopy(coefficients, + // firstNonZero, + // this.coefficients, + // 0, + // this.coefficients.length); + new_coefficients + } + } else { + coefficients.to_vec() + } + }, + }) + } + + pub fn getCoefficients(&self) -> &Vec { + return &self.coefficients; + } + + /** + * @return degree of this polynomial + */ + pub fn getDegree(&self) -> usize { + return self.coefficients.len() - 1; + } + + /** + * @return true iff this polynomial is the monomial "0" + */ + pub fn isZero(&self) -> bool { + return self.coefficients[0] == 0; + } + + /** + * @return coefficient of x^degree term in this polynomial + */ + pub fn getCoefficient(&self, degree: usize) -> i32 { + return self.coefficients[self.coefficients.len() - 1 - degree]; + } + + /** + * @return evaluation of this polynomial at a given point + */ + pub fn evaluateAt(&self, a: usize) -> i32 { + if a == 0 { + // Just return the x^0 coefficient + return self.getCoefficient(0); + } + if a == 1 { + // Just the sum of the coefficients + let mut result = 0; + for coefficient in &self.coefficients { + //for (int coefficient : coefficients) { + result = GenericGF::addOrSubtract(result, *coefficient); + } + return result; + } + let mut result = self.coefficients[0]; + let size = self.coefficients.len(); + for i in 1..size { + //for (int i = 1; i < size; i++) { + result = GenericGF::addOrSubtract( + self.field.multiply(a as i32, result as i32), + self.coefficients[i], + ); + } + return result; + } + + pub fn addOrSubtract(&self, other: &GenericGFPoly) -> Result { + if self.field != other.field { + return Err(Exceptions::IllegalArgumentException( + "GenericGFPolys do not have same GenericGF field".to_owned(), + )); + } + if self.isZero() { + return Ok(other.clone()); + } + if other.isZero() { + return Ok(self.clone()); + } + + let mut smallerCoefficients = self.coefficients.clone(); + let mut largerCoefficients = other.coefficients.clone(); + if smallerCoefficients.len() > largerCoefficients.len() { + let temp = smallerCoefficients; + smallerCoefficients = largerCoefficients; + largerCoefficients = temp; + } + + let mut sumDiff = vec![0; largerCoefficients.len()]; + let lengthDiff = largerCoefficients.len() - smallerCoefficients.len(); + // Copy high-order terms only found in higher-degree polynomial's coefficients + sumDiff[0..lengthDiff].clone_from_slice(&largerCoefficients[0..lengthDiff]); + //System.arraycopy(largerCoefficients, 0, sumDiff, 0, lengthDiff); + + for i in lengthDiff..largerCoefficients.len() { + //for (int i = lengthDiff; i < largerCoefficients.length; i++) { + sumDiff[i] = GenericGF::addOrSubtract( + smallerCoefficients[i - lengthDiff], + largerCoefficients[i], + ); + } + + return Ok(GenericGFPoly::new(self.field, &sumDiff)?); + } + + pub fn multiply(&self, other: &GenericGFPoly) -> Result { + if self.field != other.field { + //if (!field.equals(other.field)) { + return Err(Exceptions::IllegalArgumentException( + "GenericGFPolys do not have same GenericGF field".to_owned(), + )); + } + if self.isZero() || other.isZero() { + return Ok(self.getZero()); + } + let aCoefficients = self.coefficients.clone(); + let aLength = aCoefficients.len(); + let bCoefficients = other.coefficients.clone(); + let bLength = bCoefficients.len(); + let mut product = vec![0; aLength + bLength - 1]; + for i in 0..aLength { + //for (int i = 0; i < aLength; i++) { + let aCoeff = aCoefficients[i]; + for j in 0..bLength { + //for (int j = 0; j < bLength; j++) { + product[i + j] = GenericGF::addOrSubtract( + product[i + j], + self.field.multiply(aCoeff, bCoefficients[j]), + ); + } + } + return Ok(GenericGFPoly::new(self.field, &product)?); + } + + pub fn multiply_with_scalar(&self, scalar: i32) -> GenericGFPoly { + if scalar == 0 { + return self.getZero(); + } + if scalar == 1 { + return self.clone(); + } + let size = self.coefficients.len(); + + let mut product = vec![0; size]; + for i in 0..size { + //for (int i = 0; i < size; i++) { + product[i] = self.field.multiply(self.coefficients[i], scalar); + } + return GenericGFPoly::new(self.field, &product).unwrap(); + } + + pub fn getZero(&self) -> Self { + GenericGFPoly::new(self.field, &vec![0]).unwrap() + } + + pub fn getOne(&self) -> Self { + GenericGFPoly::new(self.field, &vec![1]).unwrap() + } + + pub fn multiply_by_monomial( + &self, + degree: usize, + coefficient: i32, + ) -> Result { + if coefficient == 0 { + return Ok(self.getZero()); + } + let size = self.coefficients.len(); + let mut product = vec![0; size + degree]; + for i in 0..size { + //for (int i = 0; i < size; i++) { + product[i] = self.field.multiply(self.coefficients[i], coefficient); + } + return Ok(GenericGFPoly::new(self.field, &product)?); + } + + pub fn divide( + &self, + other: &GenericGFPoly, + ) -> Result<(GenericGFPoly, GenericGFPoly), Exceptions> { + if self.field != other.field { + return Err(Exceptions::IllegalArgumentException( + "GenericGFPolys do not have same GenericGF field".to_owned(), + )); + } + if other.isZero() { + return Err(Exceptions::IllegalArgumentException( + "Divide by 0".to_owned(), + )); + } + + let mut quotient = self.getZero(); + let mut remainder = self.clone(); + + let denominator_leading_term = other.getCoefficient(other.getDegree()); + let inverse_denominator_leading_term = match self.field.inverse(denominator_leading_term) { + Ok(val) => val, + Err(_issue) => { + return Err(Exceptions::IllegalArgumentException( + "arithmetic issue".to_owned(), + )) + } + }; + + while remainder.getDegree() >= other.getDegree() && !remainder.isZero() { + let degree_difference = remainder.getDegree() - other.getDegree(); + let scale = self.field.multiply( + remainder.getCoefficient(remainder.getDegree()), + inverse_denominator_leading_term, + ); + let term = other.multiply_by_monomial(degree_difference, scale)?; + let iteration_quotient = GenericGF::buildMonomial(self.field, degree_difference, scale); + quotient = quotient.addOrSubtract(&iteration_quotient)?; + remainder = remainder.addOrSubtract(&term)?; + } + + return Ok((quotient, remainder)); + } +} + +impl fmt::Display for GenericGFPoly { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.isZero() { + return write!(f, "0"); + } + let mut result = String::with_capacity(8 * self.getDegree()); + for degree in (0..=self.getDegree()).rev() { + //for (int degree = getDegree(); degree >= 0; degree--) { + let mut coefficient = self.getCoefficient(degree); + if coefficient != 0 { + if coefficient < 0 { + if degree == self.getDegree() { + result.push_str("-"); + } else { + result.push_str(" - "); + } + coefficient = -coefficient; + } else { + if result.len() > 0 { + result.push_str(" + "); + } + } + if degree == 0 || coefficient != 1 { + if let Ok(alpha_power) = self.field.log(coefficient) { + if alpha_power == 0 { + result.push_str("1"); + } else if alpha_power == 1 { + result.push_str("a"); + } else { + result.push_str("a^"); + result.push_str(&format!("{}", alpha_power)); + } + } + } + if degree != 0 { + if degree == 1 { + result.push_str("x"); + } else { + result.push_str("x^"); + result.push_str(&format!("{}", degree)); + } + } + } + } + write!(f, "{}", result) + } +} \ No newline at end of file diff --git a/src/common/reedsolomon/mod.rs b/src/common/reedsolomon/mod.rs index 3dccd6d..0f71336 100644 --- a/src/common/reedsolomon/mod.rs +++ b/src/common/reedsolomon/mod.rs @@ -73,923 +73,14 @@ pub fn get_predefined_genericgf(request: PredefinedGenericGF) -> GenericGFRef { } } -/** - *

This class contains utility methods for performing mathematical operations over - * the Galois Fields. Operations use a given primitive polynomial in calculations.

- * - *

Throughout this package, elements of the GF are represented as an {@code int} - * for convenience and speed (but at the cost of memory). - *

- * - * @author Sean Owen - * @author David Olivier - */ -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct GenericGF { - expTable: Vec, - logTable: Vec, - // zero: Box, - // one: Box, - size: usize, - primitive: i32, - generatorBase: i32, -} +mod generic_gf; +pub use generic_gf::*; -impl GenericGF { - /** - * Create a representation of GF(size) using the given primitive polynomial. - * - * @param primitive irreducible polynomial whose coefficients are represented by - * the bits of an int, where the least-significant bit represents the constant - * coefficient - * @param size the size of the field - * @param b the factor b in the generator polynomial can be 0- or 1-based - * (g(x) = (x+a^b)(x+a^(b+1))...(x+a^(b+2t-1))). - * In most cases it should be 1, but for QR code it is 0. - */ - pub fn new(primitive: i32, size: usize, b: i32) -> Self { - let mut expTable = vec![0; size]; - let mut logTable = vec![0; size]; - let mut x = 1; - for i in 0..size { - //for (int i = 0; i < size; i++) { - //expTable.push(x); - expTable[i] = x; - x *= 2; // we're assuming the generator alpha is 2 - if x >= size as i32 { - x ^= primitive; - let sz_m_1: i32 = size as i32 - 1; - x &= sz_m_1; - } - } - for i in 0..size - 1 { - //for (int i = 0; i < size - 1; i++) { - let loc: usize = expTable[i] as usize; - logTable[loc] = i as i32; - } - logTable[0] = 0; +mod generic_gf_poly; +pub use generic_gf_poly::*; - // let mut p:u32; - // //int i; - // /*Initialize the table of powers of a primtive root, alpha=0x02.*/ - // p = 1; - // for i in 0..size { - // // for (i = 0; i < 256; i++) { - // expTable[i] = expTable[i + size - 1] = p; - // p = ((p << 1) ^ (-(p as i32 >> 7) & primitive) as u32) & 0xFF; - // } - // /*Invert the table to recover the logs.*/ - // for i in 0..size-1 { - // // for (i = 0; i < 255; i++) - // logTable[expTable[i].try_into().unwrap()] = i; - // /*Note that we rely on the fact that _gf->log[0]=0 below.*/ - Self { - expTable, - logTable, - size, - primitive, - generatorBase: b, - } +mod reedsolomon_decoder; +pub use reedsolomon_decoder::*; - // logTable[0] == 0 but this should never be used - // new_ggf.zero = Box::new(GenericGFPoly::new(Box::new(new_ggf), &vec![0]).unwrap()); - // new_ggf.one = Box::new(GenericGFPoly::new(Box::new(new_ggf), &vec![1]).unwrap()); - - //new_ggf - } - - // pub fn getZero(&self) -> Box { - // return self.zero; - // } - - // pub fn getOne(&self) -> Box { - // return self.one; - // } - - /** - * @return the monomial representing coefficient * x^degree - */ - pub fn buildMonomial(source: GenericGFRef, degree: usize, coefficient: i32) -> GenericGFPoly { - if coefficient == 0 { - return GenericGFPoly::new(source, &vec![0]).unwrap(); - } - let mut coefficients = vec![0; degree + 1]; - coefficients[0] = coefficient; - return GenericGFPoly::new(source, &coefficients).unwrap(); - } - - /** - * Implements both addition and subtraction -- they are the same in GF(size). - * - * @return sum/difference of a and b - */ - pub fn addOrSubtract(a: i32, b: i32) -> i32 { - return a ^ b; - } - - /** - * @return 2 to the power of a in GF(size) - */ - pub fn exp(&self, a: i32) -> i32 { - // let pos: usize = a.try_into().unwrap(); - return self.expTable[a as usize]; - } - - /** - * @return base 2 log of a in GF(size) - */ - pub fn log(&self, a: i32) -> Result { - if a == 0 { - return Err(Exceptions::IllegalArgumentException("".to_owned())); - } - // let pos: usize = a.try_into().unwrap(); - return Ok(self.logTable[a as usize]); - } - - /** - * @return multiplicative inverse of a - */ - pub fn inverse(&self, a: i32) -> Result { - if a == 0 { - return Err(Exceptions::ArithmeticException("".to_owned())); - } - let log_t_loc: usize = a as usize; - let loc: usize = ((self.size as i32) - self.logTable[log_t_loc] - 1) as usize; - return Ok(self.expTable[loc]); - } - - /** - * @return product of a and b in GF(size) - */ - pub fn multiply(&self, a: i32, b: i32) -> i32 { - if a == 0 || b == 0 { - return 0; - } - let a_loc: usize = a as usize; //.try_into().unwrap(); - let b_loc: usize = b as usize; //.try_into().unwrap(); - let comb_loc: usize = (self.logTable[a_loc] + self.logTable[b_loc]) as usize; - return self.expTable[comb_loc % (self.size - 1)]; - } - - pub fn getSize(&self) -> usize { - return self.size; - } - - pub fn getGeneratorBase(&self) -> i32 { - return self.generatorBase; - } -} - -impl fmt::Display for GenericGF { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "GF({:#06x},{}", self.primitive, self.size) - } -} - -/* - * Copyright 2007 ZXing authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -//package com.google.zxing.common.reedsolomon; - -/** - *

Represents a polynomial whose coefficients are elements of a GF. - * Instances of this class are immutable.

- * - *

Much credit is due to William Rucklidge since portions of this code are an indirect - * port of his C++ Reed-Solomon implementation.

- * - * @author Sean Owen - */ -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct GenericGFPoly { - field: GenericGFRef, - coefficients: Vec, -} - -impl GenericGFPoly { - /** - * @param field the {@link GenericGF} instance representing the field to use - * to perform computations - * @param coefficients coefficients as ints representing elements of GF(size), arranged - * from most significant (highest-power term) coefficient to least significant - * @throws IllegalArgumentException if argument is null or empty, - * or if leading coefficient is 0 and this is not a - * constant polynomial (that is, it is not the monomial "0") - */ - pub fn new(field: GenericGFRef, coefficients: &Vec) -> Result { - if coefficients.len() == 0 { - return Err(Exceptions::IllegalArgumentException( - "coefficients.len()".to_owned(), - )); - } - Ok(Self { - field: field, - coefficients: { - let coefficients_length = coefficients.len(); - if coefficients_length > 1 && coefficients[0] == 0 { - // Leading term must be non-zero for anything except the constant polynomial "0" - let mut first_non_zero = 1; - while first_non_zero < coefficients_length && coefficients[first_non_zero] == 0 - { - first_non_zero += 1; - } - if first_non_zero == coefficients_length { - vec![0] - } else { - let mut new_coefficients = vec![0; coefficients_length - first_non_zero]; - let l = new_coefficients.len() - 1; - new_coefficients[0..=l].clone_from_slice(&coefficients[first_non_zero..]); - // System.arraycopy(coefficients, - // firstNonZero, - // this.coefficients, - // 0, - // this.coefficients.length); - new_coefficients - } - } else { - coefficients.to_vec() - } - }, - }) - } - - pub fn getCoefficients(&self) -> &Vec { - return &self.coefficients; - } - - /** - * @return degree of this polynomial - */ - pub fn getDegree(&self) -> usize { - return self.coefficients.len() - 1; - } - - /** - * @return true iff this polynomial is the monomial "0" - */ - pub fn isZero(&self) -> bool { - return self.coefficients[0] == 0; - } - - /** - * @return coefficient of x^degree term in this polynomial - */ - pub fn getCoefficient(&self, degree: usize) -> i32 { - return self.coefficients[self.coefficients.len() - 1 - degree]; - } - - /** - * @return evaluation of this polynomial at a given point - */ - pub fn evaluateAt(&self, a: usize) -> i32 { - if a == 0 { - // Just return the x^0 coefficient - return self.getCoefficient(0); - } - if a == 1 { - // Just the sum of the coefficients - let mut result = 0; - for coefficient in &self.coefficients { - //for (int coefficient : coefficients) { - result = GenericGF::addOrSubtract(result, *coefficient); - } - return result; - } - let mut result = self.coefficients[0]; - let size = self.coefficients.len(); - for i in 1..size { - //for (int i = 1; i < size; i++) { - result = GenericGF::addOrSubtract( - self.field.multiply(a as i32, result as i32), - self.coefficients[i], - ); - } - return result; - } - - pub fn addOrSubtract(&self, other: &GenericGFPoly) -> Result { - if self.field != other.field { - return Err(Exceptions::IllegalArgumentException( - "GenericGFPolys do not have same GenericGF field".to_owned(), - )); - } - if self.isZero() { - return Ok(other.clone()); - } - if other.isZero() { - return Ok(self.clone()); - } - - let mut smallerCoefficients = self.coefficients.clone(); - let mut largerCoefficients = other.coefficients.clone(); - if smallerCoefficients.len() > largerCoefficients.len() { - let temp = smallerCoefficients; - smallerCoefficients = largerCoefficients; - largerCoefficients = temp; - } - - let mut sumDiff = vec![0; largerCoefficients.len()]; - let lengthDiff = largerCoefficients.len() - smallerCoefficients.len(); - // Copy high-order terms only found in higher-degree polynomial's coefficients - sumDiff[0..lengthDiff].clone_from_slice(&largerCoefficients[0..lengthDiff]); - //System.arraycopy(largerCoefficients, 0, sumDiff, 0, lengthDiff); - - for i in lengthDiff..largerCoefficients.len() { - //for (int i = lengthDiff; i < largerCoefficients.length; i++) { - sumDiff[i] = GenericGF::addOrSubtract( - smallerCoefficients[i - lengthDiff], - largerCoefficients[i], - ); - } - - return Ok(GenericGFPoly::new(self.field, &sumDiff)?); - } - - pub fn multiply(&self, other: &GenericGFPoly) -> Result { - if self.field != other.field { - //if (!field.equals(other.field)) { - return Err(Exceptions::IllegalArgumentException( - "GenericGFPolys do not have same GenericGF field".to_owned(), - )); - } - if self.isZero() || other.isZero() { - return Ok(self.getZero()); - } - let aCoefficients = self.coefficients.clone(); - let aLength = aCoefficients.len(); - let bCoefficients = other.coefficients.clone(); - let bLength = bCoefficients.len(); - let mut product = vec![0; aLength + bLength - 1]; - for i in 0..aLength { - //for (int i = 0; i < aLength; i++) { - let aCoeff = aCoefficients[i]; - for j in 0..bLength { - //for (int j = 0; j < bLength; j++) { - product[i + j] = GenericGF::addOrSubtract( - product[i + j], - self.field.multiply(aCoeff, bCoefficients[j]), - ); - } - } - return Ok(GenericGFPoly::new(self.field, &product)?); - } - - pub fn multiply_with_scalar(&self, scalar: i32) -> GenericGFPoly { - if scalar == 0 { - return self.getZero(); - } - if scalar == 1 { - return self.clone(); - } - let size = self.coefficients.len(); - - let mut product = vec![0; size]; - for i in 0..size { - //for (int i = 0; i < size; i++) { - product[i] = self.field.multiply(self.coefficients[i], scalar); - } - return GenericGFPoly::new(self.field, &product).unwrap(); - } - - pub fn getZero(&self) -> Self { - GenericGFPoly::new(self.field, &vec![0]).unwrap() - } - - pub fn getOne(&self) -> Self { - GenericGFPoly::new(self.field, &vec![1]).unwrap() - } - - pub fn multiply_by_monomial( - &self, - degree: usize, - coefficient: i32, - ) -> Result { - if coefficient == 0 { - return Ok(self.getZero()); - } - let size = self.coefficients.len(); - let mut product = vec![0; size + degree]; - for i in 0..size { - //for (int i = 0; i < size; i++) { - product[i] = self.field.multiply(self.coefficients[i], coefficient); - } - return Ok(GenericGFPoly::new(self.field, &product)?); - } - - pub fn divide( - &self, - other: &GenericGFPoly, - ) -> Result<(GenericGFPoly, GenericGFPoly), Exceptions> { - if self.field != other.field { - return Err(Exceptions::IllegalArgumentException( - "GenericGFPolys do not have same GenericGF field".to_owned(), - )); - } - if other.isZero() { - return Err(Exceptions::IllegalArgumentException( - "Divide by 0".to_owned(), - )); - } - - let mut quotient = self.getZero(); - let mut remainder = self.clone(); - - let denominator_leading_term = other.getCoefficient(other.getDegree()); - let inverse_denominator_leading_term = match self.field.inverse(denominator_leading_term) { - Ok(val) => val, - Err(_issue) => { - return Err(Exceptions::IllegalArgumentException( - "arithmetic issue".to_owned(), - )) - } - }; - - while remainder.getDegree() >= other.getDegree() && !remainder.isZero() { - let degree_difference = remainder.getDegree() - other.getDegree(); - let scale = self.field.multiply( - remainder.getCoefficient(remainder.getDegree()), - inverse_denominator_leading_term, - ); - let term = other.multiply_by_monomial(degree_difference, scale)?; - let iteration_quotient = GenericGF::buildMonomial(self.field, degree_difference, scale); - quotient = quotient.addOrSubtract(&iteration_quotient)?; - remainder = remainder.addOrSubtract(&term)?; - } - - return Ok((quotient, remainder)); - } -} - -impl fmt::Display for GenericGFPoly { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if self.isZero() { - return write!(f, "0"); - } - let mut result = String::with_capacity(8 * self.getDegree()); - for degree in (0..=self.getDegree()).rev() { - //for (int degree = getDegree(); degree >= 0; degree--) { - let mut coefficient = self.getCoefficient(degree); - if coefficient != 0 { - if coefficient < 0 { - if degree == self.getDegree() { - result.push_str("-"); - } else { - result.push_str(" - "); - } - coefficient = -coefficient; - } else { - if result.len() > 0 { - result.push_str(" + "); - } - } - if degree == 0 || coefficient != 1 { - if let Ok(alpha_power) = self.field.log(coefficient) { - if alpha_power == 0 { - result.push_str("1"); - } else if alpha_power == 1 { - result.push_str("a"); - } else { - result.push_str("a^"); - result.push_str(&format!("{}", alpha_power)); - } - } - } - if degree != 0 { - if degree == 1 { - result.push_str("x"); - } else { - result.push_str("x^"); - result.push_str(&format!("{}", degree)); - } - } - } - } - write!(f, "{}", result) - } -} -/* - * Copyright 2007 ZXing authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -//package com.google.zxing.common.reedsolomon; - -/** - *

Implements Reed-Solomon decoding, as the name implies.

- * - *

The algorithm will not be explained here, but the following references were helpful - * in creating this implementation:

- * - * - * - *

Much credit is due to William Rucklidge since portions of this code are an indirect - * port of his C++ Reed-Solomon implementation.

- * - * @author Sean Owen - * @author William Rucklidge - * @author sanfordsquires - */ -pub struct ReedSolomonDecoder { - field: GenericGFRef, -} - -impl ReedSolomonDecoder { - pub fn new(field: GenericGFRef) -> Self { - Self { field: field } - } - - /** - *

Decodes given set of received codewords, which include both data and error-correction - * codewords. Really, this means it uses Reed-Solomon to detect and correct errors, in-place, - * in the input.

- * - * @param received data and error-correction codewords - * @param twoS number of error-correction codewords available - * @throws ReedSolomonException if decoding fails for any reason - */ - pub fn decode(&self, received: &mut Vec, twoS: i32) -> Result<(), Exceptions> { - let poly = GenericGFPoly::new(self.field, received).unwrap(); - let mut syndromeCoefficients = vec![0; twoS as usize]; - let mut noError = true; - for i in 0..twoS { - //for (int i = 0; i < twoS; i++) { - let eval = poly.evaluateAt(self.field.exp(i + self.field.getGeneratorBase()) as usize); - let len = syndromeCoefficients.len(); - syndromeCoefficients[len - 1 - i as usize] = eval; - if eval != 0 { - noError = false; - } - } - if noError { - return Ok(()); - } - let syndrome = match GenericGFPoly::new(self.field, &syndromeCoefficients) { - Ok(res) => res, - Err(_fail) => { - return Err(Exceptions::ReedSolomonException( - "IllegalArgumentException".to_owned(), - )) - } - }; - let sigmaOmega = self.runEuclideanAlgorithm( - &GenericGF::buildMonomial(self.field, twoS as usize, 1), - &syndrome, - twoS as usize, - )?; - let sigma = &sigmaOmega[0]; - let omega = &sigmaOmega[1]; - let errorLocations = self.findErrorLocations(&sigma)?; - let errorMagnitudes = self.findErrorMagnitudes(&omega, &errorLocations); - for i in 0..errorLocations.len() { - //for (int i = 0; i < errorLocations.length; i++) { - let log_value = self.field.log(errorLocations[i] as i32)?; - if log_value > received.len() as i32 - 1 { - return Ok(()); - } - let position: isize = received.len() as isize - 1 - log_value as isize; - if position < 0 { - return Err(Exceptions::ReedSolomonException( - "Bad error location".to_owned(), - )); - } - received[position as usize] = - GenericGF::addOrSubtract(received[position as usize], errorMagnitudes[i]); - } - Ok(()) - } - - fn runEuclideanAlgorithm( - &self, - a: &GenericGFPoly, - b: &GenericGFPoly, - R: usize, - ) -> Result, Exceptions> { - // Assume a's degree is >= b's - let mut a = a.clone(); - let mut b = b.clone(); - if a.getDegree() < b.getDegree() { - let temp = a; - a = b; - b = temp; - } - - let mut rLast = a; - let mut r = b; - // let tLast = self.field.getZero(); - // let t = self.field.getOne(); - let mut tLast = rLast.getZero(); - let mut t = rLast.getOne(); - - // Run Euclidean algorithm until r's degree is less than R/2 - while 2 * r.getDegree() >= R { - let rLastLast = rLast; - let tLastLast = tLast; - rLast = r; - tLast = t; - - // Divide rLastLast by rLast, with quotient in q and remainder in r - if rLast.isZero() { - // Oops, Euclidean algorithm already terminated? - return Err(Exceptions::ReedSolomonException( - "r_{i-1} was zero".to_owned(), - )); - } - r = rLastLast; - let mut q = r.getZero(); - let denominatorLeadingTerm = rLast.getCoefficient(rLast.getDegree()); - let dltInverse = match self.field.inverse(denominatorLeadingTerm) { - Ok(inv) => inv, - Err(_err) => { - return Err(Exceptions::ReedSolomonException( - "ArithmetricException".to_owned(), - )) - } - }; - while r.getDegree() >= rLast.getDegree() && !r.isZero() { - let degreeDiff = r.getDegree() - rLast.getDegree(); - let scale = self - .field - .multiply(r.getCoefficient(r.getDegree()), dltInverse); - q = match q.addOrSubtract(&GenericGF::buildMonomial(self.field, degreeDiff, scale)) - { - Ok(res) => res, - Err(_err) => { - return Err(Exceptions::ReedSolomonException( - "IllegalArgumentException".to_owned(), - )) - } - }; - r = match r.addOrSubtract(&match rLast.multiply_by_monomial(degreeDiff, scale) { - Ok(res) => res, - Err(_err) => { - return Err(Exceptions::ReedSolomonException( - "IllegalArgumentException".to_owned(), - )) - } - }) { - Ok(res) => res, - Err(_err) => { - return Err(Exceptions::ReedSolomonException( - "IllegalArgumentException".to_owned(), - )) - } - }; - } - - t = match (match q.multiply(&tLast) { - Ok(res) => res, - Err(_err) => { - return Err(Exceptions::ReedSolomonException( - "IllegalArgumentException".to_owned(), - )) - } - }) - .addOrSubtract(&tLastLast) - { - Ok(res) => res, - Err(_err) => { - return Err(Exceptions::ReedSolomonException( - "IllegalArgumentException".to_owned(), - )) - } - }; - - if r.getDegree() >= rLast.getDegree() { - return Err(Exceptions::ReedSolomonException(format!( - "Division algorithm failed to reduce polynomial? r: {}, rLast: {}", - r, rLast - ))); - } - } - - let sigmaTildeAtZero = t.getCoefficient(0); - if sigmaTildeAtZero == 0 { - return Err(Exceptions::ReedSolomonException( - "sigmaTilde(0) was zero".to_owned(), - )); - } - - let inverse = match self.field.inverse(sigmaTildeAtZero) { - Ok(res) => res, - Err(_err) => { - return Err(Exceptions::ReedSolomonException( - "ArithmetricException".to_owned(), - )) - } - }; - let sigma = t.multiply_with_scalar(inverse); - let omega = r.multiply_with_scalar(inverse); - return Ok(vec![sigma, omega]); - } - - fn findErrorLocations(&self, errorLocator: &GenericGFPoly) -> Result, Exceptions> { - // This is a direct application of Chien's search - let numErrors = errorLocator.getDegree(); - if numErrors == 1 { - // shortcut - return Ok(vec![errorLocator.getCoefficient(1) as usize]); - } - - let mut result: Vec = vec![0; numErrors]; - let mut e = 0; - for i in 1..self.field.getSize() { - //for (int i = 1; i < field.getSize() && e < numErrors; i++) { - if e >= numErrors { - break; - } - if errorLocator.evaluateAt(i) == 0 { - result[e] = match self.field.inverse(i as i32) { - Ok(res) => res as usize, - Err(_err) => { - return Err(Exceptions::ReedSolomonException( - "ArithmetricException".to_owned(), - )) - } - }; - e += 1; - } - } - if e != numErrors { - return Err(Exceptions::ReedSolomonException( - "Error locator degree does not match number of roots".to_owned(), - )); - } - return Ok(result); - } - - fn findErrorMagnitudes( - &self, - errorEvaluator: &GenericGFPoly, - errorLocations: &Vec, - ) -> Vec { - // This is directly applying Forney's Formula - let s = errorLocations.len(); - let mut result = vec![0; s]; - for i in 0..s { - //for (int i = 0; i < s; i++) { - let xiInverse = self.field.inverse(errorLocations[i] as i32).unwrap(); - let mut denominator = 1; - for j in 0..s { - //for (int j = 0; j < s; j++) { - if i != j { - //denominator = field.multiply(denominator, - // GenericGF.addOrSubtract(1, field.multiply(errorLocations[j], xiInverse))); - // Above should work but fails on some Apple and Linux JDKs due to a Hotspot bug. - // Below is a funny-looking workaround from Steven Parkes - let term = self.field.multiply(errorLocations[j] as i32, xiInverse); - let termPlus1 = if (term & 0x1) == 0 { - term | 1 - } else { - term & !1 - }; - denominator = self.field.multiply(denominator, termPlus1); - } - } - result[i] = self.field.multiply( - errorEvaluator.evaluateAt(xiInverse as usize), - self.field.inverse(denominator).unwrap(), - ); - if self.field.getGeneratorBase() != 0 { - result[i] = self.field.multiply(result[i], xiInverse); - } - } - return result; - } -} - -/* - * Copyright 2008 ZXing authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -//package com.google.zxing.common.reedsolomon; - -//import java.util.ArrayList; -//import java.util.List; - -/** - *

Implements Reed-Solomon encoding, as the name implies.

- * - * @author Sean Owen - * @author William Rucklidge - */ -pub struct ReedSolomonEncoder { - field: GenericGFRef, - cachedGenerators: Vec, -} - -impl ReedSolomonEncoder { - pub fn new(field: GenericGFRef) -> Self { - let n = field; - Self { - cachedGenerators: vec![GenericGFPoly::new(n, &vec![1]).unwrap()], - field: n, - } - } - - fn buildGenerator(&mut self, degree: usize) -> &GenericGFPoly { - if degree >= self.cachedGenerators.len() { - let mut lastGenerator = self - .cachedGenerators - .get(self.cachedGenerators.len() - 1) - .unwrap(); - let cg_len = self.cachedGenerators.len(); - let mut nextGenerator; - for d in cg_len..=degree { - //for (int d = cachedGenerators.size(); d <= degree; d++) { - nextGenerator = lastGenerator - .multiply( - &GenericGFPoly::new( - self.field, - &vec![ - 1, - self.field.exp(d as i32 - 1 + self.field.getGeneratorBase()), - ], - ) - .unwrap(), - ) - .unwrap(); - self.cachedGenerators.push(nextGenerator); - lastGenerator = self.cachedGenerators.get(d).unwrap(); - //lastGenerator = &nextGenerator; - } - } - let rv = self.cachedGenerators.get(degree).unwrap(); - return rv; - } - - pub fn encode(&mut self, to_encode: &mut Vec, ec_bytes: usize) -> Result<(), Exceptions> { - if ec_bytes == 0 { - return Err(Exceptions::IllegalArgumentException( - "No error correction bytes".to_owned(), - )); - } - let data_bytes = to_encode.len() - ec_bytes; - if data_bytes == 0 { - return Err(Exceptions::IllegalArgumentException( - "No data bytes provided".to_owned(), - )); - } - let fld = self.field; - let generator = self.buildGenerator(ec_bytes); - let mut info_coefficients: Vec = vec![0; data_bytes]; - info_coefficients[0..data_bytes].clone_from_slice(&to_encode[0..data_bytes]); - //System.arraycopy(toEncode, 0, infoCoefficients, 0, dataBytes); - let mut info = GenericGFPoly::new(fld, &info_coefficients)?; - info = info.multiply_by_monomial(ec_bytes, 1)?; - let remainder = &info.divide(&generator)?.1; - let coefficients = remainder.getCoefficients(); - let num_zero_coefficients = ec_bytes - coefficients.len(); - for i in 0..num_zero_coefficients { - //for (int i = 0; i < numZeroCoefficients; i++) { - to_encode[data_bytes + i] = 0; - } - to_encode[data_bytes + num_zero_coefficients - ..(coefficients.len() + data_bytes + num_zero_coefficients)] - .clone_from_slice(&coefficients[0..coefficients.len()]); - //System.arraycopy(coefficients, 0, toEncode, dataBytes + numZeroCoefficients, coefficients.length); - Ok(()) - } -} +mod reedsolomon_encoder; +pub use reedsolomon_encoder::*; \ No newline at end of file diff --git a/src/common/reedsolomon/reedsolomon_decoder.rs b/src/common/reedsolomon/reedsolomon_decoder.rs new file mode 100644 index 0000000..a38bdc4 --- /dev/null +++ b/src/common/reedsolomon/reedsolomon_decoder.rs @@ -0,0 +1,311 @@ +/* + * Copyright 2007 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//package com.google.zxing.common.reedsolomon; + +use crate::Exceptions; + +use super::{GenericGFRef, GenericGFPoly, GenericGF}; + +/** + *

Implements Reed-Solomon decoding, as the name implies.

+ * + *

The algorithm will not be explained here, but the following references were helpful + * in creating this implementation:

+ * + * + * + *

Much credit is due to William Rucklidge since portions of this code are an indirect + * port of his C++ Reed-Solomon implementation.

+ * + * @author Sean Owen + * @author William Rucklidge + * @author sanfordsquires + */ +pub struct ReedSolomonDecoder { + field: GenericGFRef, +} + +impl ReedSolomonDecoder { + pub fn new(field: GenericGFRef) -> Self { + Self { field: field } + } + + /** + *

Decodes given set of received codewords, which include both data and error-correction + * codewords. Really, this means it uses Reed-Solomon to detect and correct errors, in-place, + * in the input.

+ * + * @param received data and error-correction codewords + * @param twoS number of error-correction codewords available + * @throws ReedSolomonException if decoding fails for any reason + */ + pub fn decode(&self, received: &mut Vec, twoS: i32) -> Result<(), Exceptions> { + let poly = GenericGFPoly::new(self.field, received).unwrap(); + let mut syndromeCoefficients = vec![0; twoS as usize]; + let mut noError = true; + for i in 0..twoS { + //for (int i = 0; i < twoS; i++) { + let eval = poly.evaluateAt(self.field.exp(i + self.field.getGeneratorBase()) as usize); + let len = syndromeCoefficients.len(); + syndromeCoefficients[len - 1 - i as usize] = eval; + if eval != 0 { + noError = false; + } + } + if noError { + return Ok(()); + } + let syndrome = match GenericGFPoly::new(self.field, &syndromeCoefficients) { + Ok(res) => res, + Err(_fail) => { + return Err(Exceptions::ReedSolomonException( + "IllegalArgumentException".to_owned(), + )) + } + }; + let sigmaOmega = self.runEuclideanAlgorithm( + &GenericGF::buildMonomial(self.field, twoS as usize, 1), + &syndrome, + twoS as usize, + )?; + let sigma = &sigmaOmega[0]; + let omega = &sigmaOmega[1]; + let errorLocations = self.findErrorLocations(&sigma)?; + let errorMagnitudes = self.findErrorMagnitudes(&omega, &errorLocations); + for i in 0..errorLocations.len() { + //for (int i = 0; i < errorLocations.length; i++) { + let log_value = self.field.log(errorLocations[i] as i32)?; + if log_value > received.len() as i32 - 1 { + return Ok(()); + } + let position: isize = received.len() as isize - 1 - log_value as isize; + if position < 0 { + return Err(Exceptions::ReedSolomonException( + "Bad error location".to_owned(), + )); + } + received[position as usize] = + GenericGF::addOrSubtract(received[position as usize], errorMagnitudes[i]); + } + Ok(()) + } + + fn runEuclideanAlgorithm( + &self, + a: &GenericGFPoly, + b: &GenericGFPoly, + R: usize, + ) -> Result, Exceptions> { + // Assume a's degree is >= b's + let mut a = a.clone(); + let mut b = b.clone(); + if a.getDegree() < b.getDegree() { + let temp = a; + a = b; + b = temp; + } + + let mut rLast = a; + let mut r = b; + // let tLast = self.field.getZero(); + // let t = self.field.getOne(); + let mut tLast = rLast.getZero(); + let mut t = rLast.getOne(); + + // Run Euclidean algorithm until r's degree is less than R/2 + while 2 * r.getDegree() >= R { + let rLastLast = rLast; + let tLastLast = tLast; + rLast = r; + tLast = t; + + // Divide rLastLast by rLast, with quotient in q and remainder in r + if rLast.isZero() { + // Oops, Euclidean algorithm already terminated? + return Err(Exceptions::ReedSolomonException( + "r_{i-1} was zero".to_owned(), + )); + } + r = rLastLast; + let mut q = r.getZero(); + let denominatorLeadingTerm = rLast.getCoefficient(rLast.getDegree()); + let dltInverse = match self.field.inverse(denominatorLeadingTerm) { + Ok(inv) => inv, + Err(_err) => { + return Err(Exceptions::ReedSolomonException( + "ArithmetricException".to_owned(), + )) + } + }; + while r.getDegree() >= rLast.getDegree() && !r.isZero() { + let degreeDiff = r.getDegree() - rLast.getDegree(); + let scale = self + .field + .multiply(r.getCoefficient(r.getDegree()), dltInverse); + q = match q.addOrSubtract(&GenericGF::buildMonomial(self.field, degreeDiff, scale)) + { + Ok(res) => res, + Err(_err) => { + return Err(Exceptions::ReedSolomonException( + "IllegalArgumentException".to_owned(), + )) + } + }; + r = match r.addOrSubtract(&match rLast.multiply_by_monomial(degreeDiff, scale) { + Ok(res) => res, + Err(_err) => { + return Err(Exceptions::ReedSolomonException( + "IllegalArgumentException".to_owned(), + )) + } + }) { + Ok(res) => res, + Err(_err) => { + return Err(Exceptions::ReedSolomonException( + "IllegalArgumentException".to_owned(), + )) + } + }; + } + + t = match (match q.multiply(&tLast) { + Ok(res) => res, + Err(_err) => { + return Err(Exceptions::ReedSolomonException( + "IllegalArgumentException".to_owned(), + )) + } + }) + .addOrSubtract(&tLastLast) + { + Ok(res) => res, + Err(_err) => { + return Err(Exceptions::ReedSolomonException( + "IllegalArgumentException".to_owned(), + )) + } + }; + + if r.getDegree() >= rLast.getDegree() { + return Err(Exceptions::ReedSolomonException(format!( + "Division algorithm failed to reduce polynomial? r: {}, rLast: {}", + r, rLast + ))); + } + } + + let sigmaTildeAtZero = t.getCoefficient(0); + if sigmaTildeAtZero == 0 { + return Err(Exceptions::ReedSolomonException( + "sigmaTilde(0) was zero".to_owned(), + )); + } + + let inverse = match self.field.inverse(sigmaTildeAtZero) { + Ok(res) => res, + Err(_err) => { + return Err(Exceptions::ReedSolomonException( + "ArithmetricException".to_owned(), + )) + } + }; + let sigma = t.multiply_with_scalar(inverse); + let omega = r.multiply_with_scalar(inverse); + return Ok(vec![sigma, omega]); + } + + fn findErrorLocations(&self, errorLocator: &GenericGFPoly) -> Result, Exceptions> { + // This is a direct application of Chien's search + let numErrors = errorLocator.getDegree(); + if numErrors == 1 { + // shortcut + return Ok(vec![errorLocator.getCoefficient(1) as usize]); + } + + let mut result: Vec = vec![0; numErrors]; + let mut e = 0; + for i in 1..self.field.getSize() { + //for (int i = 1; i < field.getSize() && e < numErrors; i++) { + if e >= numErrors { + break; + } + if errorLocator.evaluateAt(i) == 0 { + result[e] = match self.field.inverse(i as i32) { + Ok(res) => res as usize, + Err(_err) => { + return Err(Exceptions::ReedSolomonException( + "ArithmetricException".to_owned(), + )) + } + }; + e += 1; + } + } + if e != numErrors { + return Err(Exceptions::ReedSolomonException( + "Error locator degree does not match number of roots".to_owned(), + )); + } + return Ok(result); + } + + fn findErrorMagnitudes( + &self, + errorEvaluator: &GenericGFPoly, + errorLocations: &Vec, + ) -> Vec { + // This is directly applying Forney's Formula + let s = errorLocations.len(); + let mut result = vec![0; s]; + for i in 0..s { + //for (int i = 0; i < s; i++) { + let xiInverse = self.field.inverse(errorLocations[i] as i32).unwrap(); + let mut denominator = 1; + for j in 0..s { + //for (int j = 0; j < s; j++) { + if i != j { + //denominator = field.multiply(denominator, + // GenericGF.addOrSubtract(1, field.multiply(errorLocations[j], xiInverse))); + // Above should work but fails on some Apple and Linux JDKs due to a Hotspot bug. + // Below is a funny-looking workaround from Steven Parkes + let term = self.field.multiply(errorLocations[j] as i32, xiInverse); + let termPlus1 = if (term & 0x1) == 0 { + term | 1 + } else { + term & !1 + }; + denominator = self.field.multiply(denominator, termPlus1); + } + } + result[i] = self.field.multiply( + errorEvaluator.evaluateAt(xiInverse as usize), + self.field.inverse(denominator).unwrap(), + ); + if self.field.getGeneratorBase() != 0 { + result[i] = self.field.multiply(result[i], xiInverse); + } + } + return result; + } +} \ No newline at end of file diff --git a/src/common/reedsolomon/reedsolomon_encoder.rs b/src/common/reedsolomon/reedsolomon_encoder.rs new file mode 100644 index 0000000..6b7c5c6 --- /dev/null +++ b/src/common/reedsolomon/reedsolomon_encoder.rs @@ -0,0 +1,109 @@ +/* + * Copyright 2008 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//package com.google.zxing.common.reedsolomon; + +//import java.util.ArrayList; +//import java.util.List; + +use crate::Exceptions; + +use super::{GenericGFPoly, GenericGFRef}; + +/** + *

Implements Reed-Solomon encoding, as the name implies.

+ * + * @author Sean Owen + * @author William Rucklidge + */ +pub struct ReedSolomonEncoder { + field: GenericGFRef, + cachedGenerators: Vec, +} + +impl ReedSolomonEncoder { + pub fn new(field: GenericGFRef) -> Self { + let n = field; + Self { + cachedGenerators: vec![GenericGFPoly::new(n, &vec![1]).unwrap()], + field: n, + } + } + + fn buildGenerator(&mut self, degree: usize) -> &GenericGFPoly { + if degree >= self.cachedGenerators.len() { + let mut lastGenerator = self + .cachedGenerators + .get(self.cachedGenerators.len() - 1) + .unwrap(); + let cg_len = self.cachedGenerators.len(); + let mut nextGenerator; + for d in cg_len..=degree { + //for (int d = cachedGenerators.size(); d <= degree; d++) { + nextGenerator = lastGenerator + .multiply( + &GenericGFPoly::new( + self.field, + &vec![ + 1, + self.field.exp(d as i32 - 1 + self.field.getGeneratorBase()), + ], + ) + .unwrap(), + ) + .unwrap(); + self.cachedGenerators.push(nextGenerator); + lastGenerator = self.cachedGenerators.get(d).unwrap(); + //lastGenerator = &nextGenerator; + } + } + let rv = self.cachedGenerators.get(degree).unwrap(); + return rv; + } + + pub fn encode(&mut self, to_encode: &mut Vec, ec_bytes: usize) -> Result<(), Exceptions> { + if ec_bytes == 0 { + return Err(Exceptions::IllegalArgumentException( + "No error correction bytes".to_owned(), + )); + } + let data_bytes = to_encode.len() - ec_bytes; + if data_bytes == 0 { + return Err(Exceptions::IllegalArgumentException( + "No data bytes provided".to_owned(), + )); + } + let fld = self.field; + let generator = self.buildGenerator(ec_bytes); + let mut info_coefficients: Vec = vec![0; data_bytes]; + info_coefficients[0..data_bytes].clone_from_slice(&to_encode[0..data_bytes]); + //System.arraycopy(toEncode, 0, infoCoefficients, 0, dataBytes); + let mut info = GenericGFPoly::new(fld, &info_coefficients)?; + info = info.multiply_by_monomial(ec_bytes, 1)?; + let remainder = &info.divide(&generator)?.1; + let coefficients = remainder.getCoefficients(); + let num_zero_coefficients = ec_bytes - coefficients.len(); + for i in 0..num_zero_coefficients { + //for (int i = 0; i < numZeroCoefficients; i++) { + to_encode[data_bytes + i] = 0; + } + to_encode[data_bytes + num_zero_coefficients + ..(coefficients.len() + data_bytes + num_zero_coefficients)] + .clone_from_slice(&coefficients[0..coefficients.len()]); + //System.arraycopy(coefficients, 0, toEncode, dataBytes + numZeroCoefficients, coefficients.length); + Ok(()) + } +} diff --git a/src/common/string_utils.rs b/src/common/string_utils.rs new file mode 100644 index 0000000..9017de7 --- /dev/null +++ b/src/common/string_utils.rs @@ -0,0 +1,275 @@ +/* + * Copyright (C) 2010 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// package com.google.zxing.common; + +// import java.nio.charset.Charset; +// import java.nio.charset.StandardCharsets; +// import java.util.Map; + +use encoding::{EncodingRef, Encoding}; + +use crate::{DecodingHintDictionary, DecodeHintType, DecodeHintValue}; + +use lazy_static::lazy_static; + +/** + * Common string-related functions. + * + * @author Sean Owen + * @author Alex Dupre + */ +pub struct StringUtils { + // private static final Charset PLATFORM_DEFAULT_ENCODING = Charset.defaultCharset(); + // public static final Charset SHIFT_JIS_CHARSET = Charset.forName("SJIS"); + // public static final Charset GB2312_CHARSET = Charset.forName("GB2312"); + // private static final Charset EUC_JP = Charset.forName("EUC_JP"); + // private static final boolean ASSUME_SHIFT_JIS = + // SHIFT_JIS_CHARSET.equals(PLATFORM_DEFAULT_ENCODING) || + // EUC_JP.equals(PLATFORM_DEFAULT_ENCODING); + + // // Retained for ABI compatibility with earlier versions + // public static final String SHIFT_JIS = "SJIS"; + // public static final String GB2312 = "GB2312"; +} + +// const PLATFORM_DEFAULT_ENCODING: &dyn Encoding = encoding::all::UTF_8; +// const SHIFT_JIS_CHARSET: &dyn Encoding = +// encoding::label::encoding_from_whatwg_label("SJIS").unwrap(); +// const GB2312_CHARSET: &dyn Encoding = +// encoding::label::encoding_from_whatwg_label("GB2312").unwrap(); +// const EUC_JP: &dyn Encoding = encoding::label::encoding_from_whatwg_label("EUC_JP").unwrap(); +const ASSUME_SHIFT_JIS: bool = false; +static SHIFT_JIS: &'static str = "SJIS"; +static GB2312: &'static str = "GB2312"; +lazy_static! { + pub static ref SHIFT_JIS_CHARSET: EncodingRef = + encoding::label::encoding_from_whatwg_label("SJIS").unwrap(); +} + +// private static final boolean ASSUME_SHIFT_JIS = +// SHIFT_JIS_CHARSET.equals(PLATFORM_DEFAULT_ENCODING) || +// EUC_JP.equals(PLATFORM_DEFAULT_ENCODING); + +impl StringUtils { + /** + * @param bytes bytes encoding a string, whose encoding should be guessed + * @param hints decode hints if applicable + * @return name of guessed encoding; at the moment will only guess one of: + * "SJIS", "UTF8", "ISO8859_1", or the platform default encoding if none + * of these can possibly be correct + */ + pub fn guessEncoding(bytes: &[u8], hints: &DecodingHintDictionary) -> String { + let c = StringUtils::guessCharset(bytes, hints); + if c.name() + == encoding::label::encoding_from_whatwg_label("SJIS") + .unwrap() + .name() + { + return "SJIS".to_owned(); + } else if c.name() == encoding::all::UTF_8.name() { + return "UTF8".to_owned(); + } else if c.name() == encoding::all::ISO_8859_1.name() { + return "ISO8859_1".to_owned(); + } + return c.name().to_owned(); + } + + /** + * @param bytes bytes encoding a string, whose encoding should be guessed + * @param hints decode hints if applicable + * @return Charset of guessed encoding; at the moment will only guess one of: + * {@link #SHIFT_JIS_CHARSET}, {@link StandardCharsets#UTF_8}, + * {@link StandardCharsets#ISO_8859_1}, {@link StandardCharsets#UTF_16}, + * or the platform default encoding if + * none of these can possibly be correct + */ + pub fn guessCharset(bytes: &[u8], hints: &DecodingHintDictionary) -> EncodingRef { + match hints.get(&DecodeHintType::CHARACTER_SET) { + Some(hint) => { + if let DecodeHintValue::CharacterSet(cs_name) = hint { + return encoding::label::encoding_from_whatwg_label(cs_name).unwrap(); + } + } + _ => {} + }; + // if hints.contains_key(&DecodeHintType::CHARACTER_SET) { + // return Charset.forName(hints.get(DecodeHintType.CHARACTER_SET).toString()); + // } + + // First try UTF-16, assuming anything with its BOM is UTF-16 + if bytes.len() > 2 + && ((bytes[0] == 0xFE && bytes[1] == 0xFF) || (bytes[0] == 0xFF && bytes[1] == 0xFE)) + { + if bytes[0] == 0xFE && bytes[1] == 0xFF { + return encoding::all::UTF_16BE; + } else { + return encoding::all::UTF_16LE; + } + } + + // For now, merely tries to distinguish ISO-8859-1, UTF-8 and Shift_JIS, + // which should be by far the most common encodings. + let length = bytes.len(); + let mut can_be_iso88591 = true; + let mut can_be_shift_jis = true; + let mut can_be_utf8 = true; + let mut utf8_bytes_left = 0; + let mut utf2_bytes_chars = 0; + let mut utf3_bytes_chars = 0; + let mut utf4_bytes_chars = 0; + let mut sjis_bytes_left = 0; + let mut sjis_katakana_chars = 0; + let mut sjis_cur_katakana_word_length = 0; + let mut sjis_cur_double_bytes_word_length = 0; + let mut sjis_max_katakana_word_length = 0; + let mut sjis_max_double_bytes_word_length = 0; + let mut iso_high_other = 0; + + let utf8bom = bytes.len() > 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF; + + for i in 0..length { + // for (int i = 0; + // i < length && (canBeISO88591 || canBeShiftJIS || canBeUTF8); + // i++) { + if !(can_be_iso88591 || can_be_shift_jis || can_be_utf8) { + break; + } + + let value = bytes[i]; + + // UTF-8 stuff + if can_be_utf8 { + if utf8_bytes_left > 0 { + if (value & 0x80) == 0 { + can_be_utf8 = false; + } else { + utf8_bytes_left -= 1; + } + } else if (value & 0x80) != 0 { + if (value & 0x40) == 0 { + can_be_utf8 = false; + } else { + utf8_bytes_left += 1; + if (value & 0x20) == 0 { + utf2_bytes_chars += 1; + } else { + utf8_bytes_left += 1; + if (value & 0x10) == 0 { + utf3_bytes_chars += 1; + } else { + utf8_bytes_left += 1; + if (value & 0x08) == 0 { + utf4_bytes_chars += 1; + } else { + can_be_utf8 = false; + } + } + } + } + } + } + + // ISO-8859-1 stuff + if can_be_iso88591 { + if value > 0x7F && value < 0xA0 { + can_be_iso88591 = false; + } else if value > 0x9F && (value < 0xC0 || value == 0xD7 || value == 0xF7) { + iso_high_other += 1; + } + } + + // Shift_JIS stuff + if can_be_shift_jis { + if sjis_bytes_left > 0 { + if value < 0x40 || value == 0x7F || value > 0xFC { + can_be_shift_jis = false; + } else { + sjis_bytes_left -= 1; + } + } else if value == 0x80 || value == 0xA0 || value > 0xEF { + can_be_shift_jis = false; + } else if value > 0xA0 && value < 0xE0 { + sjis_katakana_chars += 1; + sjis_cur_double_bytes_word_length = 0; + sjis_cur_katakana_word_length += 1; + if sjis_cur_katakana_word_length > sjis_max_katakana_word_length { + sjis_max_katakana_word_length = sjis_cur_katakana_word_length; + } + } else if value > 0x7F { + sjis_bytes_left += 1; + //sjisDoubleBytesChars++; + sjis_cur_katakana_word_length = 0; + sjis_cur_double_bytes_word_length += 1; + if sjis_cur_double_bytes_word_length > sjis_max_double_bytes_word_length { + sjis_max_double_bytes_word_length = sjis_cur_double_bytes_word_length; + } + } else { + //sjisLowChars++; + sjis_cur_katakana_word_length = 0; + sjis_cur_double_bytes_word_length = 0; + } + } + } + + if can_be_utf8 && utf8_bytes_left > 0 { + can_be_utf8 = false; + } + if can_be_shift_jis && sjis_bytes_left > 0 { + can_be_shift_jis = false; + } + + // Easy -- if there is BOM or at least 1 valid not-single byte character (and no evidence it can't be UTF-8), done + if can_be_utf8 && (utf8bom || utf2_bytes_chars + utf3_bytes_chars + utf4_bytes_chars > 0) { + return encoding::all::UTF_8; + } + // Easy -- if assuming Shift_JIS or >= 3 valid consecutive not-ascii characters (and no evidence it can't be), done + if can_be_shift_jis + && (ASSUME_SHIFT_JIS + || sjis_max_katakana_word_length >= 3 + || sjis_max_double_bytes_word_length >= 3) + { + return encoding::label::encoding_from_whatwg_label("SJIS").unwrap(); + } + // Distinguishing Shift_JIS and ISO-8859-1 can be a little tough for short words. The crude heuristic is: + // - If we saw + // - only two consecutive katakana chars in the whole text, or + // - at least 10% of bytes that could be "upper" not-alphanumeric Latin1, + // - then we conclude Shift_JIS, else ISO-8859-1 + if can_be_iso88591 && can_be_shift_jis { + return if (sjis_max_katakana_word_length == 2 && sjis_katakana_chars == 2) + || iso_high_other * 10 >= length + { + encoding::label::encoding_from_whatwg_label("SJIS").unwrap() + } else { + encoding::all::ISO_8859_1 + }; + } + + // Otherwise, try in order ISO-8859-1, Shift JIS, UTF-8 and fall back to default platform encoding + if can_be_iso88591 { + return encoding::all::ISO_8859_1; + } + if can_be_shift_jis { + return encoding::label::encoding_from_whatwg_label("SJIS").unwrap(); + } + if can_be_utf8 { + return encoding::all::UTF_8; + } + // Otherwise, we take a wild guess with platform encoding + return encoding::all::UTF_8; + } +} \ No newline at end of file