mirror of
https://github.com/starovoid/rxing.git
synced 2026-07-26 04:12:34 +00:00
break out many mods
This commit is contained in:
405
src/common/bit_array.rs
Normal file
405
src/common/bit_array.rs
Normal file
@@ -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;
|
||||
|
||||
/**
|
||||
* <p>A simple, fast array of bits, represented compactly by an array of ints internally.</p>
|
||||
*
|
||||
* @author Sean Owen
|
||||
*/
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
|
||||
pub struct BitArray {
|
||||
bits: Vec<u32>,
|
||||
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<u32>, 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<bool, 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(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<u32> {
|
||||
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<u32> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
628
src/common/bit_matrix.rs
Normal file
628
src/common/bit_matrix.rs
Normal file
@@ -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;
|
||||
|
||||
/**
|
||||
* <p>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.</p>
|
||||
*
|
||||
* <p>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.</p>
|
||||
*
|
||||
* <p>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.</p>
|
||||
*
|
||||
* @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<u32>,
|
||||
}
|
||||
|
||||
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<Self, Exceptions> {
|
||||
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<u32>) -> 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<Vec<bool>>) -> 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<Self, Exceptions> {
|
||||
// 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Gets the requested bit, where true means black.</p>
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Sets the given bit to true.</p>
|
||||
*
|
||||
* @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));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Flips the given bit.</p>
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Flips every bit in the matrix.</p>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Sets a square region of the bit matrix to true.</p>
|
||||
*
|
||||
* @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<Vec<u32>> {
|
||||
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<Vec<u32>> {
|
||||
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<Vec<u32>> {
|
||||
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 ", " "))
|
||||
}
|
||||
}
|
||||
125
src/common/bit_source.rs
Normal file
125
src/common/bit_source.rs
Normal file
@@ -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;
|
||||
|
||||
/**
|
||||
* <p>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.</p>
|
||||
*
|
||||
* <p>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.</p>
|
||||
*
|
||||
* @author Sean Owen
|
||||
*/
|
||||
pub struct BitSource {
|
||||
bytes: Vec<u8>,
|
||||
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<u8>) -> 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<u32, Exceptions> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
68
src/common/bit_source_builder.rs
Normal file
68
src/common/bit_source_builder.rs
Normal file
@@ -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<u8>,
|
||||
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<u8> {
|
||||
if self.bitsLeftInNextByte < 8 {
|
||||
self.write(0, self.bitsLeftInNextByte);
|
||||
}
|
||||
&self.output
|
||||
}
|
||||
}
|
||||
289
src/common/character_set_eci.rs
Normal file
289
src/common/character_set_eci.rs
Normal file
@@ -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<Integer,CharacterSetECI> VALUE_TO_ECI = new HashMap<>();
|
||||
// private static final Map<String,CharacterSetECI> 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<CharacterSetECI> {
|
||||
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<CharacterSetECI, Exceptions> {
|
||||
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<CharacterSetECI> {
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
209
src/common/decoder_rxing_result.rs
Normal file
209
src/common/decoder_rxing_result.rs
Normal file
@@ -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;
|
||||
|
||||
/**
|
||||
* <p>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.</p>
|
||||
*
|
||||
* @author Sean Owen
|
||||
*/
|
||||
pub struct DecoderRXingResult {
|
||||
rawBytes: Vec<u8>,
|
||||
numBits: usize,
|
||||
text: String,
|
||||
byteSegments: Vec<Vec<u8>>,
|
||||
ecLevel: String,
|
||||
errorsCorrected: u64,
|
||||
erasures: u64,
|
||||
other: Box<dyn Any>,
|
||||
structuredAppendParity: i32,
|
||||
structuredAppendSequenceNumber: i32,
|
||||
symbologyModifier: u32,
|
||||
}
|
||||
|
||||
impl DecoderRXingResult {
|
||||
pub fn new(
|
||||
rawBytes: Vec<u8>,
|
||||
text: String,
|
||||
byteSegments: Vec<Vec<u8>>,
|
||||
ecLevel: String,
|
||||
) -> Self {
|
||||
Self::with_all(rawBytes, text, byteSegments, ecLevel, -2, -2, 0)
|
||||
}
|
||||
|
||||
pub fn with_symbology(
|
||||
rawBytes: Vec<u8>,
|
||||
text: String,
|
||||
byteSegments: Vec<Vec<u8>>,
|
||||
ecLevel: String,
|
||||
symbologyModifier: u32,
|
||||
) -> Self {
|
||||
Self::with_all(
|
||||
rawBytes,
|
||||
text,
|
||||
byteSegments,
|
||||
ecLevel,
|
||||
-1,
|
||||
-1,
|
||||
symbologyModifier,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn with_sa(
|
||||
rawBytes: Vec<u8>,
|
||||
text: String,
|
||||
byteSegments: Vec<Vec<u8>>,
|
||||
ecLevel: String,
|
||||
saSequence: i32,
|
||||
saParity: i32,
|
||||
) -> Self {
|
||||
Self::with_all(
|
||||
rawBytes,
|
||||
text,
|
||||
byteSegments,
|
||||
ecLevel,
|
||||
saSequence,
|
||||
saParity,
|
||||
0,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn with_all(
|
||||
rawBytes: Vec<u8>,
|
||||
text: String,
|
||||
byteSegments: Vec<Vec<u8>>,
|
||||
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<u8> {
|
||||
&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<Vec<u8>> {
|
||||
&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<dyn Any> {
|
||||
&self.other
|
||||
}
|
||||
|
||||
pub fn setOther(&mut self, other: Box<dyn Any>) {
|
||||
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
|
||||
}
|
||||
}
|
||||
121
src/common/default_grid_sampler.rs
Normal file
121
src/common/default_grid_sampler.rs
Normal file
@@ -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<BitMatrix, Exceptions> {
|
||||
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<BitMatrix, Exceptions> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
* <p>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.</p>
|
||||
*
|
||||
* @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() }
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Detects a rectangular region of black and white -- mostly black -- with a region of mostly
|
||||
* white, in an image.</p>
|
||||
*
|
||||
* @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<Vec<RXingResultPoint>, 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<RXingResultPoint, Exceptions> {
|
||||
let mut lastRange_z: Option<Vec<i32>> = None;
|
||||
let mut y: i32 = centerY;
|
||||
let mut x: i32 = centerX;
|
||||
while y < bottom && y >= top && x < right && x >= left {
|
||||
let range: Option<Vec<i32>>;
|
||||
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<Vec<i32>> {
|
||||
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;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 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.
|
||||
* </p>
|
||||
*
|
||||
* @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, Exceptions> {
|
||||
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<Self, Exceptions> {
|
||||
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 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.
|
||||
* </p>
|
||||
*
|
||||
* @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<Vec<RXingResultPoint>, 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<RXingResultPoint> = 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<RXingResultPoint> = 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<RXingResultPoint> = 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<RXingResultPoint> = 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<RXingResultPoint> {
|
||||
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<RXingResultPoint> {
|
||||
//
|
||||
// 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::*;
|
||||
312
src/common/detector/monochrome_rectangle_detector.rs
Normal file
312
src/common/detector/monochrome_rectangle_detector.rs
Normal file
@@ -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};
|
||||
|
||||
/**
|
||||
* <p>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.</p>
|
||||
*
|
||||
* @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() }
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Detects a rectangular region of black and white -- mostly black -- with a region of mostly
|
||||
* white, in an image.</p>
|
||||
*
|
||||
* @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<Vec<RXingResultPoint>, 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<RXingResultPoint, Exceptions> {
|
||||
let mut lastRange_z: Option<Vec<i32>> = None;
|
||||
let mut y: i32 = centerY;
|
||||
let mut x: i32 = centerX;
|
||||
while y < bottom && y >= top && x < right && x >= left {
|
||||
let range: Option<Vec<i32>>;
|
||||
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<Vec<i32>> {
|
||||
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
|
||||
};
|
||||
}
|
||||
}
|
||||
387
src/common/detector/white_rectangle_detector.rs
Normal file
387
src/common/detector/white_rectangle_detector.rs
Normal file
@@ -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;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 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.
|
||||
* </p>
|
||||
*
|
||||
* @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, Exceptions> {
|
||||
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<Self, Exceptions> {
|
||||
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 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.
|
||||
* </p>
|
||||
*
|
||||
* @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<Vec<RXingResultPoint>, 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<RXingResultPoint> = 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<RXingResultPoint> = 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<RXingResultPoint> = 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<RXingResultPoint> = 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<RXingResultPoint> {
|
||||
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<RXingResultPoint> {
|
||||
//
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
254
src/common/eci_encoder_set.rs
Normal file
254
src/common/eci_encoder_set.rs
Normal file
@@ -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<EncodingRef> = {
|
||||
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<EncodingRef>,
|
||||
priorityEncoderIndex: Option<usize>,
|
||||
}
|
||||
|
||||
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<EncodingRef>,
|
||||
fnc1: Option<&str>,
|
||||
) -> Self {
|
||||
// List of encoders that potentially encode characters not in ISO-8859-1 in one byte.
|
||||
|
||||
let mut encoders: Vec<EncodingRef>;
|
||||
let mut priorityEncoderIndexValue = None;
|
||||
|
||||
let mut neededEncoders: Vec<EncodingRef> = Vec::new();
|
||||
|
||||
let stringToEncode = stringToEncodeMain.graphemes(true).collect::<Vec<&str>>();
|
||||
|
||||
//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<usize> {
|
||||
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<u8> {
|
||||
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<u8> {
|
||||
assert!(encoderIndex < self.len());
|
||||
let encoder = self.encoders[encoderIndex];
|
||||
encoder.encode(s, encoding::EncoderTrap::Replace).unwrap()
|
||||
}
|
||||
}
|
||||
109
src/common/eci_input.rs
Normal file
109
src/common/eci_input.rs
Normal file
@@ -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<char, Exceptions>;
|
||||
|
||||
/**
|
||||
* 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<Vec<char>, 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<bool, Exceptions>;
|
||||
|
||||
/**
|
||||
* 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<u32, Exceptions>;
|
||||
fn haveNCharacters(&self, index: usize, n: usize) -> bool;
|
||||
}
|
||||
173
src/common/eci_string_builder.rs
Normal file
173
src/common/eci_string_builder.rs
Normal file
@@ -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<u8>,
|
||||
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)
|
||||
}
|
||||
}
|
||||
247
src/common/global_histogram_binarizer.rs
Normal file
247
src/common/global_histogram_binarizer.rs
Normal file
@@ -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<u8>,
|
||||
buckets: Vec<u32>,
|
||||
width: usize,
|
||||
height: usize,
|
||||
source: Box<dyn LuminanceSource>,
|
||||
}
|
||||
|
||||
impl Binarizer for GlobalHistogramBinarizer {
|
||||
fn getLuminanceSource(&self) -> &Box<dyn LuminanceSource> {
|
||||
&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<BitArray, Exceptions> {
|
||||
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<BitMatrix, Exceptions> {
|
||||
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<dyn crate::LuminanceSource>) -> Box<dyn Binarizer> {
|
||||
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<dyn LuminanceSource>) -> 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<u32, Exceptions> {
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
200
src/common/grid_sampler.rs
Normal file
200
src/common/grid_sampler.rs
Normal file
@@ -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<BitMatrix, Exceptions>;
|
||||
|
||||
fn sample_grid(
|
||||
&self,
|
||||
image: &BitMatrix,
|
||||
dimensionX: u32,
|
||||
dimensionY: u32,
|
||||
transform: &PerspectiveTransform,
|
||||
) -> Result<BitMatrix, Exceptions>;
|
||||
|
||||
/**
|
||||
* <p>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.</p>
|
||||
*
|
||||
* <p>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.</p>
|
||||
*
|
||||
* <p>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.</p>
|
||||
*
|
||||
* @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(())
|
||||
}
|
||||
}
|
||||
320
src/common/hybrid_binarizer.rs
Normal file
320
src/common/hybrid_binarizer.rs
Normal file
@@ -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<dyn LuminanceSource>,
|
||||
ghb: GlobalHistogramBinarizer,
|
||||
// matrix :Option<BitMatrix>,
|
||||
}
|
||||
impl Binarizer for HybridBinarizer {
|
||||
fn getLuminanceSource(&self) -> &Box<dyn LuminanceSource> {
|
||||
self.ghb.getLuminanceSource()
|
||||
}
|
||||
|
||||
fn getBlackRow(&self, y: usize, row: &mut BitArray) -> Result<BitArray, Exceptions> {
|
||||
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<BitMatrix, Exceptions> {
|
||||
// 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<dyn LuminanceSource>) -> Box<dyn Binarizer> {
|
||||
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<dyn LuminanceSource>) -> 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<Vec<u32>>,
|
||||
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<Vec<u32>> {
|
||||
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
|
||||
}
|
||||
}
|
||||
115
src/common/input_edge.rs
Normal file
115
src/common/input_edge.rs
Normal file
@@ -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<Rc<InputEdge>>,
|
||||
pub cachedTotalSize: usize,
|
||||
}
|
||||
impl InputEdge {
|
||||
pub fn new(
|
||||
c: &str,
|
||||
encoderSet: &ECIEncoderSet,
|
||||
encoderIndex: usize,
|
||||
previous: Option<Rc<InputEdge>>,
|
||||
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)
|
||||
}
|
||||
}
|
||||
384
src/common/minimal_eci_input.rs
Normal file
384
src/common/minimal_eci_input.rs
Normal file
@@ -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<u16>,
|
||||
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<char, Exceptions> {
|
||||
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<Vec<char>, 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<bool, Exceptions> {
|
||||
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<u32, Exceptions> {
|
||||
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<EncodingRef>,
|
||||
fnc1: Option<&str>,
|
||||
) -> Self {
|
||||
let stringToEncode = stringToEncodeInput.graphemes(true).collect::<Vec<&str>>();
|
||||
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<bool, Exceptions> {
|
||||
if index >= self.length() {
|
||||
return Err(Exceptions::IndexOutOfBoundsException(index.to_string()));
|
||||
}
|
||||
Ok(self.bytes[index] == 1000)
|
||||
}
|
||||
|
||||
fn addEdge(edges: &mut Vec<Vec<Option<Rc<InputEdge>>>>, to: usize, edge: Rc<InputEdge>) {
|
||||
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<Vec<Option<Rc<InputEdge>>>>,
|
||||
from: usize,
|
||||
previous: Option<Rc<InputEdge>>,
|
||||
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<u16> {
|
||||
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<u16> = 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<u16> = 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;
|
||||
}
|
||||
}
|
||||
4081
src/common/mod.rs
4081
src/common/mod.rs
File diff suppressed because it is too large
Load Diff
213
src/common/perspective_transform.rs
Normal file
213
src/common/perspective_transform.rs
Normal file
@@ -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;
|
||||
|
||||
/**
|
||||
* <p>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.</p>
|
||||
*
|
||||
* @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,
|
||||
);
|
||||
}
|
||||
}
|
||||
178
src/common/reedsolomon/generic_gf.rs
Normal file
178
src/common/reedsolomon/generic_gf.rs
Normal file
@@ -0,0 +1,178 @@
|
||||
use std::fmt;
|
||||
|
||||
use crate::Exceptions;
|
||||
|
||||
use super::{GenericGFRef, GenericGFPoly};
|
||||
|
||||
/**
|
||||
* <p>This class contains utility methods for performing mathematical operations over
|
||||
* the Galois Fields. Operations use a given primitive polynomial in calculations.</p>
|
||||
*
|
||||
* <p>Throughout this package, elements of the GF are represented as an {@code int}
|
||||
* for convenience and speed (but at the cost of memory).
|
||||
* </p>
|
||||
*
|
||||
* @author Sean Owen
|
||||
* @author David Olivier
|
||||
*/
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct GenericGF {
|
||||
expTable: Vec<i32>,
|
||||
logTable: Vec<i32>,
|
||||
// zero: Box<GenericGFPoly>,
|
||||
// one: Box<GenericGFPoly>,
|
||||
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<GenericGFPoly> {
|
||||
// return self.zero;
|
||||
// }
|
||||
|
||||
// pub fn getOne(&self) -> Box<GenericGFPoly> {
|
||||
// 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<i32, Exceptions> {
|
||||
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<i32, Exceptions> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
340
src/common/reedsolomon/generic_gf_poly.rs
Normal file
340
src/common/reedsolomon/generic_gf_poly.rs
Normal file
@@ -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};
|
||||
|
||||
/**
|
||||
* <p>Represents a polynomial whose coefficients are elements of a GF.
|
||||
* Instances of this class are immutable.</p>
|
||||
*
|
||||
* <p>Much credit is due to William Rucklidge since portions of this code are an indirect
|
||||
* port of his C++ Reed-Solomon implementation.</p>
|
||||
*
|
||||
* @author Sean Owen
|
||||
*/
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct GenericGFPoly {
|
||||
field: GenericGFRef,
|
||||
coefficients: Vec<i32>,
|
||||
}
|
||||
|
||||
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<i32>) -> Result<Self, Exceptions> {
|
||||
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<i32> {
|
||||
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<GenericGFPoly, Exceptions> {
|
||||
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<GenericGFPoly, Exceptions> {
|
||||
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<GenericGFPoly, Exceptions> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -73,923 +73,14 @@ pub fn get_predefined_genericgf(request: PredefinedGenericGF) -> GenericGFRef {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>This class contains utility methods for performing mathematical operations over
|
||||
* the Galois Fields. Operations use a given primitive polynomial in calculations.</p>
|
||||
*
|
||||
* <p>Throughout this package, elements of the GF are represented as an {@code int}
|
||||
* for convenience and speed (but at the cost of memory).
|
||||
* </p>
|
||||
*
|
||||
* @author Sean Owen
|
||||
* @author David Olivier
|
||||
*/
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct GenericGF {
|
||||
expTable: Vec<i32>,
|
||||
logTable: Vec<i32>,
|
||||
// zero: Box<GenericGFPoly>,
|
||||
// one: Box<GenericGFPoly>,
|
||||
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<GenericGFPoly> {
|
||||
// return self.zero;
|
||||
// }
|
||||
|
||||
// pub fn getOne(&self) -> Box<GenericGFPoly> {
|
||||
// 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<i32, Exceptions> {
|
||||
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<i32, Exceptions> {
|
||||
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;
|
||||
|
||||
/**
|
||||
* <p>Represents a polynomial whose coefficients are elements of a GF.
|
||||
* Instances of this class are immutable.</p>
|
||||
*
|
||||
* <p>Much credit is due to William Rucklidge since portions of this code are an indirect
|
||||
* port of his C++ Reed-Solomon implementation.</p>
|
||||
*
|
||||
* @author Sean Owen
|
||||
*/
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct GenericGFPoly {
|
||||
field: GenericGFRef,
|
||||
coefficients: Vec<i32>,
|
||||
}
|
||||
|
||||
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<i32>) -> Result<Self, Exceptions> {
|
||||
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<i32> {
|
||||
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<GenericGFPoly, Exceptions> {
|
||||
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<GenericGFPoly, Exceptions> {
|
||||
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<GenericGFPoly, Exceptions> {
|
||||
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;
|
||||
|
||||
/**
|
||||
* <p>Implements Reed-Solomon decoding, as the name implies.</p>
|
||||
*
|
||||
* <p>The algorithm will not be explained here, but the following references were helpful
|
||||
* in creating this implementation:</p>
|
||||
*
|
||||
* <ul>
|
||||
* <li>Bruce Maggs.
|
||||
* <a href="http://www.cs.cmu.edu/afs/cs.cmu.edu/project/pscico-guyb/realworld/www/rs_decode.ps">
|
||||
* "Decoding Reed-Solomon Codes"</a> (see discussion of Forney's Formula)</li>
|
||||
* <li>J.I. Hall. <a href="www.mth.msu.edu/~jhall/classes/codenotes/GRS.pdf">
|
||||
* "Chapter 5. Generalized Reed-Solomon Codes"</a>
|
||||
* (see discussion of Euclidean algorithm)</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Much credit is due to William Rucklidge since portions of this code are an indirect
|
||||
* port of his C++ Reed-Solomon implementation.</p>
|
||||
*
|
||||
* @author Sean Owen
|
||||
* @author William Rucklidge
|
||||
* @author sanfordsquires
|
||||
*/
|
||||
pub struct ReedSolomonDecoder {
|
||||
field: GenericGFRef,
|
||||
}
|
||||
|
||||
impl ReedSolomonDecoder {
|
||||
pub fn new(field: GenericGFRef) -> Self {
|
||||
Self { field: field }
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>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.</p>
|
||||
*
|
||||
* @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<i32>, 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<Vec<GenericGFPoly>, 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<Vec<usize>, 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<usize> = 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<usize>,
|
||||
) -> Vec<i32> {
|
||||
// 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;
|
||||
|
||||
/**
|
||||
* <p>Implements Reed-Solomon encoding, as the name implies.</p>
|
||||
*
|
||||
* @author Sean Owen
|
||||
* @author William Rucklidge
|
||||
*/
|
||||
pub struct ReedSolomonEncoder {
|
||||
field: GenericGFRef,
|
||||
cachedGenerators: Vec<GenericGFPoly>,
|
||||
}
|
||||
|
||||
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<i32>, 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<i32> = 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::*;
|
||||
311
src/common/reedsolomon/reedsolomon_decoder.rs
Normal file
311
src/common/reedsolomon/reedsolomon_decoder.rs
Normal file
@@ -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};
|
||||
|
||||
/**
|
||||
* <p>Implements Reed-Solomon decoding, as the name implies.</p>
|
||||
*
|
||||
* <p>The algorithm will not be explained here, but the following references were helpful
|
||||
* in creating this implementation:</p>
|
||||
*
|
||||
* <ul>
|
||||
* <li>Bruce Maggs.
|
||||
* <a href="http://www.cs.cmu.edu/afs/cs.cmu.edu/project/pscico-guyb/realworld/www/rs_decode.ps">
|
||||
* "Decoding Reed-Solomon Codes"</a> (see discussion of Forney's Formula)</li>
|
||||
* <li>J.I. Hall. <a href="www.mth.msu.edu/~jhall/classes/codenotes/GRS.pdf">
|
||||
* "Chapter 5. Generalized Reed-Solomon Codes"</a>
|
||||
* (see discussion of Euclidean algorithm)</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Much credit is due to William Rucklidge since portions of this code are an indirect
|
||||
* port of his C++ Reed-Solomon implementation.</p>
|
||||
*
|
||||
* @author Sean Owen
|
||||
* @author William Rucklidge
|
||||
* @author sanfordsquires
|
||||
*/
|
||||
pub struct ReedSolomonDecoder {
|
||||
field: GenericGFRef,
|
||||
}
|
||||
|
||||
impl ReedSolomonDecoder {
|
||||
pub fn new(field: GenericGFRef) -> Self {
|
||||
Self { field: field }
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>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.</p>
|
||||
*
|
||||
* @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<i32>, 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<Vec<GenericGFPoly>, 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<Vec<usize>, 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<usize> = 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<usize>,
|
||||
) -> Vec<i32> {
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
109
src/common/reedsolomon/reedsolomon_encoder.rs
Normal file
109
src/common/reedsolomon/reedsolomon_encoder.rs
Normal file
@@ -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};
|
||||
|
||||
/**
|
||||
* <p>Implements Reed-Solomon encoding, as the name implies.</p>
|
||||
*
|
||||
* @author Sean Owen
|
||||
* @author William Rucklidge
|
||||
*/
|
||||
pub struct ReedSolomonEncoder {
|
||||
field: GenericGFRef,
|
||||
cachedGenerators: Vec<GenericGFPoly>,
|
||||
}
|
||||
|
||||
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<i32>, 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<i32> = 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(())
|
||||
}
|
||||
}
|
||||
275
src/common/string_utils.rs
Normal file
275
src/common/string_utils.rs
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user