@@ -13,7 +13,7 @@ exclude = [
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
regex = "1.7.1"
|
||||
regex = "1.8.0"
|
||||
fancy-regex = "0.11"
|
||||
once_cell = "1.17.1"
|
||||
encoding = "0.2"
|
||||
@@ -31,6 +31,7 @@ svg = {version = "0.13", optional = true}
|
||||
resvg = {version = "0.28.0", optional = true, default-features=false}
|
||||
serde = { version = "1.0", features = ["derive", "rc"], optional = true }
|
||||
thiserror = "1.0.38"
|
||||
multimap = "0.8.3"
|
||||
|
||||
[dev-dependencies]
|
||||
java-properties = "1.4.1"
|
||||
|
||||
@@ -10,5 +10,5 @@ keywords = ["barcode", "2d_barcode", "1d_barcode", "barcode_reader", "barcode_wr
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.1.1", features = ["derive"] }
|
||||
clap = { version = "4.2.0", features = ["derive"] }
|
||||
rxing = {path = "../../", version = "~0.4.0", features = ["image", "svg_read", "svg_write"] }
|
||||
|
||||
@@ -115,12 +115,18 @@ fn test_error_in_parameter_locator(data: &str) {
|
||||
// dbg!(copy.to_string());
|
||||
// dbg!(make_larger(©, 3).to_string());
|
||||
// The detector doesn't seem to work when matrix bits are only 1x1. So magnify.
|
||||
// dbg!(isMirror, error1, error2);
|
||||
let r = Detector::new(&make_larger(©, 3)).detect(isMirror);
|
||||
assert!(r.is_ok());
|
||||
assert!(
|
||||
r.is_ok(),
|
||||
"decode should be ok: {isMirror:?}, {error1}, {error2}"
|
||||
);
|
||||
let r = r.expect("result already tested as ok");
|
||||
assert_eq!(r.getNbLayers(), layers);
|
||||
assert_eq!(r.isCompact(), compact);
|
||||
let res = decoder::decode(&r).expect("decode should be ok");
|
||||
let res = decoder::decode(&r).unwrap_or_else(|_| {
|
||||
panic!("decode should be ok: {isMirror}, {error1}, {error2}")
|
||||
});
|
||||
assert_eq!(data, res.getText());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -469,7 +469,9 @@ impl<'a> Detector<'_> {
|
||||
point(low, high),
|
||||
);
|
||||
|
||||
sampler.sample_grid_detailed(image, dimension, dimension, dst, quad)
|
||||
let (res, _) = sampler.sample_grid_detailed(image, dimension, dimension, dst, quad)?;
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -65,6 +65,8 @@ pub enum BarcodeFormat {
|
||||
/** QR Code 2D barcode format. */
|
||||
QR_CODE,
|
||||
|
||||
MICRO_QR_CODE,
|
||||
|
||||
/** RSS 14 */
|
||||
RSS_14,
|
||||
|
||||
@@ -102,6 +104,7 @@ impl Display for BarcodeFormat {
|
||||
BarcodeFormat::MAXICODE => "maxicode",
|
||||
BarcodeFormat::PDF_417 => "pdf 417",
|
||||
BarcodeFormat::QR_CODE => "qrcode",
|
||||
BarcodeFormat::MICRO_QR_CODE => "mqr",
|
||||
BarcodeFormat::RSS_14 => "rss 14",
|
||||
BarcodeFormat::RSS_EXPANDED => "rss expanded",
|
||||
BarcodeFormat::UPC_A => "upc a",
|
||||
@@ -140,6 +143,9 @@ impl From<&str> for BarcodeFormat {
|
||||
"maxicode" | "maxi_code" => BarcodeFormat::MAXICODE,
|
||||
"pdf 417" | "pdf_417" | "pdf417" | "iso 15438" | "iso_15438" => BarcodeFormat::PDF_417,
|
||||
"qrcode" | "qr_code" | "qr code" => BarcodeFormat::QR_CODE,
|
||||
"mqr" | "microqr" | "micro_qr" | "micro_qrcode" | "micro_qr_code" | "mqr_code" => {
|
||||
BarcodeFormat::MICRO_QR_CODE
|
||||
}
|
||||
"rss 14" | "rss_14" | "rss14" | "gs1 databar" | "gs1 databar coupon"
|
||||
| "gs1_databar_coupon" => BarcodeFormat::RSS_14,
|
||||
"rss expanded" | "expanded rss" | "rss_expanded" => BarcodeFormat::RSS_EXPANDED,
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
// import java.util.Arrays;
|
||||
|
||||
use std::ops::Index;
|
||||
use std::{cmp, fmt};
|
||||
|
||||
use crate::common::Result;
|
||||
@@ -82,6 +83,14 @@ impl BitArray {
|
||||
(self.bits[i / 32] & (1 << (i & 0x1F))) != 0
|
||||
}
|
||||
|
||||
pub fn try_get(&self, i: usize) -> Option<bool> {
|
||||
if (i / 32) >= self.bits.len() {
|
||||
None
|
||||
} else {
|
||||
Some(self.get(i))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets bit i.
|
||||
*
|
||||
@@ -401,3 +410,30 @@ impl Default for BitArray {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Vec<u8>> for BitArray {
|
||||
fn into(self) -> Vec<u8> {
|
||||
let mut array = vec![0; self.getSizeInBytes()];
|
||||
self.toBytes(0, &mut array, 0, self.getSizeInBytes());
|
||||
array
|
||||
// let mut arr = vec![0; self.get_size()];
|
||||
// for x in 0..self.get_size() {
|
||||
// if self.get(x) {
|
||||
// arr[x] = 1;
|
||||
// }
|
||||
// }
|
||||
// arr
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Vec<bool>> for BitArray {
|
||||
fn into(self) -> Vec<bool> {
|
||||
let mut array = vec![false; self.size];
|
||||
|
||||
for pixel in 0..self.size {
|
||||
array[pixel] = bool::from(self.get(pixel));
|
||||
}
|
||||
|
||||
array
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
use std::fmt;
|
||||
|
||||
use crate::common::Result;
|
||||
use crate::{point, Exceptions, Point};
|
||||
use crate::{point, point_i, Exceptions, Point};
|
||||
|
||||
use super::BitArray;
|
||||
|
||||
@@ -222,6 +222,17 @@ impl BitMatrix {
|
||||
// ((self.bits[offset] >> (x & 0x1f)) & 1) != 0
|
||||
}
|
||||
|
||||
pub fn get_index<T: Into<usize>>(&self, index: T) -> bool {
|
||||
self.get_point(self.calculate_point_from_index(index.into()))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn calculate_point_from_index(&self, index: usize) -> Point {
|
||||
let row = index / (self.getWidth() as usize);
|
||||
let column = index % (self.getWidth() as usize);
|
||||
point_i(column as u32, row as u32)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn get_offset(&self, y: u32, x: u32) -> usize {
|
||||
y as usize * self.row_size + (x as usize / 32)
|
||||
@@ -420,6 +431,21 @@ impl BitMatrix {
|
||||
rw
|
||||
}
|
||||
|
||||
/// This method returns a column of the bitmatrix.
|
||||
///
|
||||
/// The current implementation may be very slow.
|
||||
pub fn getCol(&self, x: u32) -> BitArray {
|
||||
let mut cw = BitArray::with_size(self.height as usize);
|
||||
|
||||
for y in 0..self.height {
|
||||
if self.get(x, y) {
|
||||
cw.set(y as usize)
|
||||
}
|
||||
}
|
||||
|
||||
cw
|
||||
}
|
||||
|
||||
/**
|
||||
* @param y row to set
|
||||
* @param row {@link BitArray} to copy from
|
||||
@@ -607,6 +633,10 @@ impl BitMatrix {
|
||||
* @return The width of the matrix
|
||||
*/
|
||||
pub fn getWidth(&self) -> u32 {
|
||||
self.width()
|
||||
}
|
||||
|
||||
pub fn width(&self) -> u32 {
|
||||
self.width
|
||||
}
|
||||
|
||||
@@ -614,6 +644,10 @@ impl BitMatrix {
|
||||
* @return The height of the matrix
|
||||
*/
|
||||
pub fn getHeight(&self) -> u32 {
|
||||
self.height()
|
||||
}
|
||||
|
||||
pub fn height(&self) -> u32 {
|
||||
self.height
|
||||
}
|
||||
|
||||
@@ -707,6 +741,10 @@ impl BitMatrix {
|
||||
new_bm
|
||||
}
|
||||
|
||||
pub fn is_in(&self, p: Point) -> bool {
|
||||
self.isIn(p, 0)
|
||||
}
|
||||
|
||||
pub fn isIn(&self, p: Point, b: i32) -> bool {
|
||||
b as f32 <= p.x
|
||||
&& p.x < self.getWidth() as f32 - b as f32
|
||||
@@ -721,6 +759,19 @@ impl fmt::Display for BitMatrix {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&BitMatrix> for Vec<bool> {
|
||||
fn from(value: &BitMatrix) -> Self {
|
||||
let mut arr = vec![false; (value.width * value.height) as usize];
|
||||
for x in 0..value.width {
|
||||
for y in 0..value.height {
|
||||
let insert_pos = ((y * value.width) + x) as usize;
|
||||
arr[insert_pos] = value.get(x, y);
|
||||
}
|
||||
}
|
||||
arr
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "image")]
|
||||
/// This should only be used if you *know* that the `DynamicImage` is binary.
|
||||
impl TryFrom<image::DynamicImage> for BitMatrix {
|
||||
|
||||
@@ -116,6 +116,56 @@ impl BitSource {
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn peak_bits(&self, numBits: usize) -> Result<u32> {
|
||||
if !(1..=32).contains(&numBits) || numBits > self.available() {
|
||||
return Err(Exceptions::illegal_argument_with(numBits.to_string()));
|
||||
}
|
||||
|
||||
let mut bit_offset = self.bit_offset;
|
||||
let mut byte_offset = self.byte_offset;
|
||||
|
||||
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;
|
||||
bit_offset += toRead;
|
||||
if bit_offset == 8 {
|
||||
bit_offset = 0;
|
||||
byte_offset += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Next read whole bytes
|
||||
if num_bits > 0 {
|
||||
while num_bits >= 8 {
|
||||
result = (result << 8) | self.bytes[byte_offset] as u32;
|
||||
// result = ((result as u16) << 8) as u8 | (self.bytes[self.byte_offset]);
|
||||
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[byte_offset] & mask) as u32 >> bits_to_not_read);
|
||||
bit_offset += num_bits;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/**
|
||||
* @return number of bits that can be read successfully
|
||||
*/
|
||||
|
||||
102
src/common/cpp_essentials/base_extentions/bitmatrix.rs
Normal file
@@ -0,0 +1,102 @@
|
||||
use crate::common::BitMatrix;
|
||||
use crate::common::Result;
|
||||
use crate::point;
|
||||
use crate::Point;
|
||||
|
||||
impl BitMatrix {
|
||||
pub fn Deflate(
|
||||
&self,
|
||||
width: u32,
|
||||
height: u32,
|
||||
top: f32,
|
||||
left: f32,
|
||||
subSampling: f32,
|
||||
) -> Result<Self> {
|
||||
let mut result = BitMatrix::new(width, height)?;
|
||||
|
||||
for y in 0..result.height() {
|
||||
// for (int y = 0; y < result.height(); y++) {
|
||||
let yOffset = top + y as f32 * subSampling;
|
||||
for x in 0..result.width() {
|
||||
// for (int x = 0; x < result.width(); x++) {
|
||||
if self.get_point(point(left + x as f32 * subSampling, yOffset)) {
|
||||
result.set(x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn getTopLeftOnBitWithPosition(&self, left: &mut u32, top: &mut u32) -> bool {
|
||||
let Some(Point{x,y}) = self.getTopLeftOnBit() else {
|
||||
return false;
|
||||
};
|
||||
*left = x as u32;
|
||||
*top = y as u32;
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub fn getBottomRightOnBitWithPosition(&self, right: &mut u32, bottom: &mut u32) -> bool {
|
||||
let Some(Point{x,y}) = self.getBottomRightOnBit() else {
|
||||
return false;
|
||||
};
|
||||
*right = x as u32;
|
||||
*bottom = y as u32;
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub fn findBoundingBox(
|
||||
&self,
|
||||
left: u32,
|
||||
top: u32,
|
||||
width: u32,
|
||||
height: u32,
|
||||
minSize: u32,
|
||||
) -> (bool, u32, u32, u32, u32) {
|
||||
let mut left = left;
|
||||
let mut top = top;
|
||||
let mut width = width;
|
||||
let mut height = height;
|
||||
|
||||
let mut right = 0;
|
||||
let mut bottom = 0;
|
||||
if (!self.getTopLeftOnBitWithPosition(&mut left, &mut top)
|
||||
|| !self.getBottomRightOnBitWithPosition(&mut right, &mut bottom)
|
||||
|| bottom - top + 1 < minSize)
|
||||
{
|
||||
return (false, left, top, width, height);
|
||||
}
|
||||
|
||||
for y in top..=bottom {
|
||||
// for (int y = top; y <= bottom; y++ ) {
|
||||
for x in 0..left {
|
||||
// for (int x = 0; x < left; ++x){
|
||||
if (self.get(x, y)) {
|
||||
left = x;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for x in (right..(self.width() - 1)).rev() {
|
||||
// for (int x = _width-1; x > right; x--){
|
||||
if (self.get(x, y)) {
|
||||
right = x;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
width = right - left + 1;
|
||||
height = bottom - top + 1;
|
||||
|
||||
(
|
||||
width >= minSize && height >= minSize,
|
||||
left,
|
||||
top,
|
||||
width,
|
||||
height,
|
||||
)
|
||||
}
|
||||
}
|
||||
4
src/common/cpp_essentials/base_extentions/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
mod bitmatrix;
|
||||
mod qr_ec_level;
|
||||
mod qr_formatinformation;
|
||||
mod qrcode_version;
|
||||
31
src/common/cpp_essentials/base_extentions/qr_ec_level.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
use crate::common::Result;
|
||||
use crate::qrcode::decoder::ErrorCorrectionLevel;
|
||||
|
||||
impl ErrorCorrectionLevel {
|
||||
pub fn ECLevelFromBitsSigned(bits: i8, isMicro: bool) -> Self {
|
||||
if (isMicro) {
|
||||
let LEVEL_FOR_BITS: [ErrorCorrectionLevel; 8] = [
|
||||
ErrorCorrectionLevel::L,
|
||||
ErrorCorrectionLevel::L,
|
||||
ErrorCorrectionLevel::M,
|
||||
ErrorCorrectionLevel::L,
|
||||
ErrorCorrectionLevel::M,
|
||||
ErrorCorrectionLevel::L,
|
||||
ErrorCorrectionLevel::M,
|
||||
ErrorCorrectionLevel::Q,
|
||||
];
|
||||
return LEVEL_FOR_BITS[bits as usize & 0x07];
|
||||
}
|
||||
let LEVEL_FOR_BITS: [ErrorCorrectionLevel; 4] = [
|
||||
ErrorCorrectionLevel::M,
|
||||
ErrorCorrectionLevel::L,
|
||||
ErrorCorrectionLevel::H,
|
||||
ErrorCorrectionLevel::Q,
|
||||
];
|
||||
return LEVEL_FOR_BITS[bits as usize & 0x3];
|
||||
}
|
||||
|
||||
pub fn ECLevelFromBits(bits: u8, isMicro: bool) -> Self {
|
||||
Self::ECLevelFromBitsSigned(bits as i8, isMicro)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
use crate::common::Result;
|
||||
|
||||
use crate::qrcode::decoder::{
|
||||
ErrorCorrectionLevel, FormatInformation, FORMAT_INFO_DECODE_LOOKUP, FORMAT_INFO_MASK_QR,
|
||||
};
|
||||
|
||||
pub const FORMAT_INFO_DECODE_LOOKUP_MICRO: [[u32; 2]; 32] = [
|
||||
[0x4445, 0x00],
|
||||
[0x4172, 0x01],
|
||||
[0x4E2B, 0x02],
|
||||
[0x4B1C, 0x03],
|
||||
[0x55AE, 0x04],
|
||||
[0x5099, 0x05],
|
||||
[0x5FC0, 0x06],
|
||||
[0x5AF7, 0x07],
|
||||
[0x6793, 0x08],
|
||||
[0x62A4, 0x09],
|
||||
[0x6DFD, 0x0A],
|
||||
[0x68CA, 0x0B],
|
||||
[0x7678, 0x0C],
|
||||
[0x734F, 0x0D],
|
||||
[0x7C16, 0x0E],
|
||||
[0x7921, 0x0F],
|
||||
[0x06DE, 0x10],
|
||||
[0x03E9, 0x11],
|
||||
[0x0CB0, 0x12],
|
||||
[0x0987, 0x13],
|
||||
[0x1735, 0x14],
|
||||
[0x1202, 0x15],
|
||||
[0x1D5B, 0x16],
|
||||
[0x186C, 0x17],
|
||||
[0x2508, 0x18],
|
||||
[0x203F, 0x19],
|
||||
[0x2F66, 0x1A],
|
||||
[0x2A51, 0x1B],
|
||||
[0x34E3, 0x1C],
|
||||
[0x31D4, 0x1D],
|
||||
[0x3E8D, 0x1E],
|
||||
[0x3BBA, 0x1F],
|
||||
];
|
||||
|
||||
impl FormatInformation {
|
||||
/**
|
||||
* @param formatInfoBits1 format info indicator, with mask still applied
|
||||
* @param formatInfoBits2 second copy of same info; both are checked at the same time to establish best match
|
||||
*/
|
||||
pub fn DecodeQR(formatInfoBits1: u32, formatInfoBits2: u32) -> Self {
|
||||
// maks out the 'Dark Module' for mirrored and non-mirrored case (see Figure 25 in ISO/IEC 18004:2015)
|
||||
let mirroredFormatInfoBits2 = Self::MirrorBits(
|
||||
((formatInfoBits2 >> 1) & 0b111111110000000) | (formatInfoBits2 & 0b1111111),
|
||||
);
|
||||
let formatInfoBits2 =
|
||||
((formatInfoBits2 >> 1) & 0b111111100000000) | (formatInfoBits2 & 0b11111111);
|
||||
let mut fi = Self::FindBestFormatInfo(
|
||||
FORMAT_INFO_MASK_QR,
|
||||
FORMAT_INFO_DECODE_LOOKUP,
|
||||
&[
|
||||
formatInfoBits1,
|
||||
formatInfoBits2,
|
||||
Self::MirrorBits(formatInfoBits1),
|
||||
mirroredFormatInfoBits2,
|
||||
],
|
||||
);
|
||||
|
||||
// Use bits 3/4 for error correction, and 0-2 for mask.
|
||||
fi.error_correction_level =
|
||||
ErrorCorrectionLevel::ECLevelFromBits((fi.index >> 3) & 0x03, false);
|
||||
fi.data_mask = fi.index & 0x07;
|
||||
fi.isMirrored = fi.bitsIndex > 1;
|
||||
|
||||
fi
|
||||
}
|
||||
|
||||
pub fn DecodeMQR(formatInfoBits: u32) -> Self {
|
||||
// We don't use the additional masking (with 0x4445) to work around potentially non complying MicroQRCode encoders
|
||||
let mut fi = Self::FindBestFormatInfo(
|
||||
0,
|
||||
FORMAT_INFO_DECODE_LOOKUP_MICRO,
|
||||
&[formatInfoBits, Self::MirrorBits(formatInfoBits)],
|
||||
);
|
||||
|
||||
const BITS_TO_VERSION: [u8; 8] = [1, 2, 2, 3, 3, 4, 4, 4];
|
||||
|
||||
// Bits 2/3/4 contain both error correction level and version, 0/1 contain mask.
|
||||
fi.error_correction_level =
|
||||
ErrorCorrectionLevel::ECLevelFromBits((fi.index >> 2) & 0x07, true);
|
||||
fi.data_mask = (fi.index & 0x03);
|
||||
fi.microVersion = BITS_TO_VERSION[((fi.index >> 2) & 0x07) as usize] as u32;
|
||||
fi.isMirrored = fi.bitsIndex == 1;
|
||||
|
||||
fi
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn MirrorBits(bits: u32) -> u32 {
|
||||
(bits.reverse_bits()) >> 17
|
||||
}
|
||||
|
||||
pub fn FindBestFormatInfo(mask: u32, lookup: [[u32; 2]; 32], bits: &[u32]) -> Self {
|
||||
let mut fi = FormatInformation::default();
|
||||
|
||||
// Some QR codes apparently do not apply the XOR mask. Try without and with additional masking.
|
||||
for mask in [0, mask] {
|
||||
// for (auto mask : {0, mask})
|
||||
for bitsIndex in 0..bits.len() {
|
||||
// for (int bitsIndex = 0; bitsIndex < Size(bits); ++bitsIndex)
|
||||
for [pattern, index] in lookup {
|
||||
// for (const auto& [pattern, index] : lookup) {
|
||||
// Find the int in lookup with fewest bits differing
|
||||
let hammingDist = ((bits[bitsIndex] ^ mask) ^ pattern).count_ones();
|
||||
if hammingDist < fi.hammingDistance {
|
||||
// if (int hammingDist = BitHacks::CountBitsSet((bits[bitsIndex] ^ mask) ^ pattern); hammingDist < fi.hammingDistance) {
|
||||
fi.index = index as u8;
|
||||
fi.hammingDistance = hammingDist;
|
||||
fi.bitsIndex = bitsIndex as u8;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fi
|
||||
}
|
||||
|
||||
// Hamming distance of the 32 masked codes is 7, by construction, so <= 3 bits differing means we found a match
|
||||
pub fn isValid(&self) -> bool {
|
||||
self.hammingDistance <= 3
|
||||
}
|
||||
|
||||
pub fn cpp_eq(&self, other: &Self) -> bool {
|
||||
self.data_mask == other.data_mask
|
||||
&& self.error_correction_level == other.error_correction_level
|
||||
}
|
||||
}
|
||||
97
src/common/cpp_essentials/base_extentions/qrcode_version.rs
Normal file
@@ -0,0 +1,97 @@
|
||||
use crate::common::Result;
|
||||
use crate::qrcode::decoder::{Version, VersionRef, MICRO_VERSIONS, VERSIONS, VERSION_DECODE_INFO};
|
||||
use crate::Exceptions;
|
||||
|
||||
// const Version* Version::AllMicroVersions()
|
||||
// {
|
||||
// /**
|
||||
// * See ISO 18004:2006 6.5.1 Table 9
|
||||
// */
|
||||
// static const Version allVersions[] = {
|
||||
// {1, {2, 1, 3, 0, 0}},
|
||||
// {2, {5, 1, 5, 0, 0, 6, 1, 4, 0, 0}},
|
||||
// {3, {6, 1, 11, 0, 0, 8, 1, 9, 0, 0}},
|
||||
// {4, {8, 1, 16, 0, 0, 10, 1, 14, 0, 0, 14, 1, 10, 0, 0}}};
|
||||
// return allVersions;
|
||||
// }
|
||||
|
||||
impl Version {
|
||||
pub fn FromDimension(dimension: u32) -> Result<VersionRef> {
|
||||
let isMicro = dimension < 21;
|
||||
if (dimension % Self::DimensionStep(isMicro) != 1) {
|
||||
//throw std::invalid_argument("Unexpected dimension");
|
||||
return Err(Exceptions::ILLEGAL_ARGUMENT);
|
||||
}
|
||||
return Self::FromNumber(
|
||||
(dimension - Self::DimensionOffset(isMicro)) / Self::DimensionStep(isMicro),
|
||||
isMicro,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn FromNumber(versionNumber: u32, is_micro: bool) -> Result<VersionRef> {
|
||||
if (versionNumber < 1 || versionNumber > (if is_micro { 4 } else { 40 })) {
|
||||
//throw std::invalid_argument("Version should be in range [1-40].");
|
||||
return Err(Exceptions::ILLEGAL_ARGUMENT);
|
||||
}
|
||||
|
||||
Ok(if is_micro {
|
||||
&MICRO_VERSIONS[versionNumber as usize - 1]
|
||||
} else {
|
||||
&VERSIONS[versionNumber as usize - 1]
|
||||
})
|
||||
}
|
||||
|
||||
pub fn DimensionOfVersion(version: u32, is_micro: bool) -> u32 {
|
||||
Self::DimensionOffset(is_micro) + Self::DimensionStep(is_micro) * version
|
||||
}
|
||||
|
||||
pub fn DimensionOffset(is_micro: bool) -> u32 {
|
||||
match is_micro {
|
||||
true => 9,
|
||||
false => 17,
|
||||
}
|
||||
// return std::array{17, 9}[isMicro];
|
||||
}
|
||||
|
||||
pub fn DimensionStep(is_micro: bool) -> u32 {
|
||||
match is_micro {
|
||||
true => 2,
|
||||
false => 4,
|
||||
}
|
||||
// return std::array{4, 2}[isMicro];
|
||||
}
|
||||
pub fn DecodeVersionInformation(versionBitsA: i32, versionBitsB: i32) -> Result<VersionRef> {
|
||||
let mut bestDifference = u32::MAX;
|
||||
let mut bestVersion = 0;
|
||||
let mut i = 0;
|
||||
for targetVersion in VERSION_DECODE_INFO {
|
||||
// for (int targetVersion : VERSION_DECODE_INFO) {
|
||||
// Do the version info bits match exactly? done.
|
||||
if targetVersion == versionBitsA as u32 || targetVersion == versionBitsB as u32 {
|
||||
return Self::getVersionForNumber(i + 7);
|
||||
}
|
||||
// Otherwise see if this is the closest to a real version info bit string
|
||||
// we have seen so far
|
||||
for bits in [versionBitsA, versionBitsB] {
|
||||
// for (int bits : {versionBitsA, versionBitsB}) {
|
||||
let bitsDifference = ((bits as u32) ^ targetVersion).count_ones(); //BitHacks::CountBitsSet(bits ^ targetVersion);
|
||||
if bitsDifference < bestDifference {
|
||||
bestVersion = i + 7;
|
||||
bestDifference = bitsDifference;
|
||||
}
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
// We can tolerate up to 3 bits of error since no two version info codewords will
|
||||
// differ in less than 8 bits.
|
||||
if bestDifference <= 3 {
|
||||
return Self::getVersionForNumber(bestVersion);
|
||||
}
|
||||
// If we didn't find a close enough match, fail
|
||||
return Err(Exceptions::ILLEGAL_STATE);
|
||||
}
|
||||
|
||||
pub const fn isMicroQRCode(&self) -> bool {
|
||||
self.is_micro
|
||||
}
|
||||
}
|
||||
1
src/common/cpp_essentials/bitmatrix_cursor.rs
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::Point;
|
||||
use crate::{common::BitMatrix, Point};
|
||||
|
||||
use super::{util::opposite, Direction, Value};
|
||||
|
||||
@@ -8,7 +8,7 @@ use super::{util::opposite, Direction, Value};
|
||||
* The current position and direction is a PointT<T>. So depending on the type it can be used to traverse the image
|
||||
* in a Bresenham style (PointF) or in a discrete way (step only horizontal/vertical/diagonal (PointI)).
|
||||
*/
|
||||
pub trait BitMatrixCursor {
|
||||
pub trait BitMatrixCursorTrait {
|
||||
// const BitMatrix* img;
|
||||
|
||||
// POINT p; // current position
|
||||
@@ -77,12 +77,13 @@ pub trait BitMatrixCursor {
|
||||
// return isIn(p);
|
||||
// }
|
||||
|
||||
fn movedBy<T: BitMatrixCursor>(self, d: Point) -> Self;
|
||||
// {
|
||||
// auto res = *this;
|
||||
// res.p += d;
|
||||
// return res;
|
||||
// }
|
||||
fn movedBy<T: BitMatrixCursorTrait>(self, d: Point) -> Self;
|
||||
fn turnedBack(&self) -> Self; // { return {*img, p, back()}; }
|
||||
// {
|
||||
// auto res = *this;
|
||||
// res.p += d;
|
||||
// return res;
|
||||
// }
|
||||
|
||||
/**
|
||||
* @brief stepToEdge advances cursor to one step behind the next (or n-th) edge.
|
||||
@@ -160,20 +161,40 @@ pub trait BitMatrixCursor {
|
||||
res
|
||||
}
|
||||
|
||||
// template<typename ARRAY>
|
||||
// ARRAY readPattern(int range = 0)
|
||||
// {
|
||||
// ARRAY res;
|
||||
// for (auto& i : res)
|
||||
// i = stepToEdge(1, range);
|
||||
// return res;
|
||||
// }
|
||||
fn p(&self) -> Point;
|
||||
|
||||
// template<typename ARRAY>
|
||||
// ARRAY readPatternFromBlack(int maxWhitePrefix, int range = 0)
|
||||
// {
|
||||
// if (maxWhitePrefix && isWhite() && !stepToEdge(1, maxWhitePrefix))
|
||||
// return {};
|
||||
// return readPattern<ARRAY>(range);
|
||||
// }
|
||||
fn d(&self) -> Point;
|
||||
|
||||
fn img(&self) -> &BitMatrix;
|
||||
|
||||
fn readPattern<const LEN: usize, T: TryFrom<i32> + Default + Copy + Clone>(
|
||||
&mut self,
|
||||
range: Option<i32>,
|
||||
) -> Option<[T; LEN]> {
|
||||
let range = range.unwrap_or(0);
|
||||
let mut res = [T::default(); LEN];
|
||||
for i in res.iter_mut() {
|
||||
*i = self
|
||||
.stepToEdge(Some(1), Some(range), None)
|
||||
.try_into()
|
||||
.ok()?;
|
||||
}
|
||||
Some(res)
|
||||
}
|
||||
|
||||
fn readPatternFromBlack<const LEN: usize, T: TryFrom<i32> + Default + Copy + Clone>(
|
||||
&mut self,
|
||||
maxWhitePrefix: i32,
|
||||
range: Option<i32>,
|
||||
) -> Option<[T; LEN]> {
|
||||
let range = range.unwrap_or(0);
|
||||
if (maxWhitePrefix != 0
|
||||
&& self.isWhite()
|
||||
&& !self.stepToEdge(Some(1), Some(maxWhitePrefix), None) > 0)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
// return readPattern<ARRAY>(range);
|
||||
self.readPattern::<LEN, _>(Some(range))
|
||||
}
|
||||
}
|
||||
682
src/common/cpp_essentials/concentric_finder.rs
Normal file
@@ -0,0 +1,682 @@
|
||||
use crate::{
|
||||
common::{
|
||||
cpp_essentials::{
|
||||
Direction, FixedPattern, IsPattern, PatternRow, PatternType, PatternView,
|
||||
},
|
||||
BitMatrix, Quadrilateral,
|
||||
},
|
||||
point, Exceptions, Point,
|
||||
};
|
||||
|
||||
use crate::common::Result;
|
||||
|
||||
use super::{
|
||||
BitMatrixCursorTrait, EdgeTracer, FastEdgeToEdgeCounter, Pattern, RegressionLine,
|
||||
RegressionLineTrait, UpdateMinMax, UpdateMinMaxFloat,
|
||||
};
|
||||
|
||||
pub fn CenterFromEnd<const N: usize, T: Into<f32> + std::iter::Sum<T> + Copy>(
|
||||
pattern: &[T; N],
|
||||
end: f32,
|
||||
) -> f32 {
|
||||
if N == 5 {
|
||||
let a: f32 = pattern[4].into() + pattern[3].into() + pattern[2].into() / 2.0;
|
||||
let b: f32 =
|
||||
pattern[4].into() + (pattern[3].into() + pattern[2].into() + pattern[1].into()) / 2.0;
|
||||
let c: f32 = (pattern[4].into()
|
||||
+ pattern[3].into()
|
||||
+ pattern[2].into()
|
||||
+ pattern[1].into()
|
||||
+ pattern[0].into())
|
||||
/ 2.0;
|
||||
end - (2.0 * a + b + c) / 4.0
|
||||
} else if N == 3 {
|
||||
let a: f32 = pattern[2].into() + pattern[1].into() / 2.0;
|
||||
let b: f32 = (pattern[2].into() + pattern[1].into() + pattern[0].into()) / 2.0;
|
||||
end - (2.0 * a + b) / 3.0
|
||||
} else {
|
||||
// aztec
|
||||
let a: f32 =
|
||||
pattern.iter().skip(N / 2 + 1).copied().sum::<T>().into() + pattern[N / 2].into() / 2.0;
|
||||
// let a = std::accumulate(pattern.begin() + (N/2 + 1), pattern.end(), pattern[N/2] / 2.0);
|
||||
end - a
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ReadSymmetricPattern<const N: usize, Cursor: BitMatrixCursorTrait>(
|
||||
cur: &mut Cursor,
|
||||
range: i32,
|
||||
) -> Option<Pattern<N>> {
|
||||
assert!(N % 2 == 1);
|
||||
|
||||
assert!(range > 0);
|
||||
|
||||
let mut range = range;
|
||||
|
||||
let mut res: Pattern<N> = [0; N];
|
||||
let s_2 = res.len() as isize / 2;
|
||||
let mut cuo = cur.turnedBack();
|
||||
|
||||
let mut next = |cur: &mut Cursor, i: isize| {
|
||||
let v = cur.stepToEdge(Some(1), Some(range), None);
|
||||
res[(s_2 + i) as usize] = (res[(s_2 + i) as usize] as i32 + v) as u16;
|
||||
// res[(s_2 + i) as usize] += v;
|
||||
if range != 0 {
|
||||
range -= v;
|
||||
}
|
||||
|
||||
v
|
||||
};
|
||||
|
||||
for i in 0..=s_2 {
|
||||
// for (int i = 0; i <= s_2; ++i) {
|
||||
if !next(cur, i) == 0 || !next(&mut cuo, -i) == 0 {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
res[s_2 as usize] -= 1; // the starting pixel has been counted twice, fix this
|
||||
|
||||
Some(res)
|
||||
}
|
||||
|
||||
// default for RELAXED_THRESHOLD should be false
|
||||
pub fn CheckSymmetricPattern<
|
||||
const E2E: bool,
|
||||
const LEN: usize,
|
||||
const SUM: usize,
|
||||
T: BitMatrixCursorTrait,
|
||||
>(
|
||||
cur: &mut T,
|
||||
pattern: &Pattern<LEN>,
|
||||
range: i32,
|
||||
updatePosition: bool,
|
||||
) -> i32 {
|
||||
let mut range = range;
|
||||
|
||||
let mut curFwd: FastEdgeToEdgeCounter = FastEdgeToEdgeCounter::new(cur);
|
||||
let binding = cur.turnedBack();
|
||||
let mut curBwd: FastEdgeToEdgeCounter = FastEdgeToEdgeCounter::new(&binding);
|
||||
|
||||
let centerFwd = curFwd.stepToNextEdge(range as u32) as i32;
|
||||
if centerFwd == 0 {
|
||||
return 0;
|
||||
}
|
||||
let centerBwd = curBwd.stepToNextEdge(range as u32) as i32;
|
||||
if centerBwd == 0 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
assert!(range > 0);
|
||||
let mut res: PatternRow = PatternRow::new(vec![0; LEN]);
|
||||
let s_2 = (res.len()) / 2;
|
||||
res[s_2] = (centerFwd + centerBwd - 1) as u16; // -1 because the starting pixel is counted twice
|
||||
range -= res[s_2] as i32;
|
||||
|
||||
let mut next = |cur: &mut FastEdgeToEdgeCounter, i: isize| {
|
||||
let v = cur.stepToNextEdge(range as u32) as i32;
|
||||
res[(s_2 as isize + i) as usize] = v as u16;
|
||||
range -= v;
|
||||
|
||||
v
|
||||
};
|
||||
|
||||
for i in 1..=s_2 {
|
||||
// for (int i = 1; i <= s_2; ++i) {
|
||||
if next(&mut curFwd, i as isize) == 0 || next(&mut curBwd, -(i as isize)) == 0 {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if IsPattern::<E2E, LEN, SUM, false>(
|
||||
&PatternView::new(&res),
|
||||
&FixedPattern::<LEN, SUM, false>::with_reference(pattern),
|
||||
None,
|
||||
0.0,
|
||||
0.0,
|
||||
) == 0.0
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if updatePosition {
|
||||
cur.step(Some((res[s_2] as i32 / 2 - (centerBwd - 1)) as f32));
|
||||
}
|
||||
|
||||
res.into_iter().sum::<PatternType>() as i32
|
||||
}
|
||||
|
||||
pub fn AverageEdgePixels<T: BitMatrixCursorTrait>(
|
||||
cur: &mut T,
|
||||
range: i32,
|
||||
numOfEdges: u32,
|
||||
) -> Option<Point> {
|
||||
let mut sum = Point::default();
|
||||
|
||||
for _i in 0..numOfEdges {
|
||||
// for (int i = 0; i < numOfEdges; ++i) {
|
||||
if !cur.isInSelf() {
|
||||
return None;
|
||||
}
|
||||
cur.stepToEdge(Some(1), Some(range), None);
|
||||
sum += cur.p().centered() + (cur.p() + cur.back()).centered()
|
||||
// sum += centered(cur.p) + centered(cur.p + cur.back());
|
||||
// log(cur.p + cur.back(), 2);
|
||||
}
|
||||
Some(sum / (2 * numOfEdges) as f32)
|
||||
}
|
||||
|
||||
pub fn CenterOfDoubleCross(
|
||||
image: &BitMatrix,
|
||||
center: Point,
|
||||
range: i32,
|
||||
numOfEdges: u32,
|
||||
) -> Option<Point> {
|
||||
let mut sum = Point::default();
|
||||
|
||||
for d in [
|
||||
point(0.0, 1.0),
|
||||
point(1.0, 0.0),
|
||||
point(1.0, 1.0),
|
||||
point(1.0, -1.0),
|
||||
] {
|
||||
// for (auto d : {PointI{0, 1}, {1, 0}, {1, 1}, {1, -1}}) {
|
||||
let avr1 = AverageEdgePixels(&mut EdgeTracer::new(image, center, d), range, numOfEdges)?;
|
||||
let avr2 = AverageEdgePixels(&mut EdgeTracer::new(image, center, -d), range, numOfEdges)?;
|
||||
|
||||
sum += avr1 + avr2;
|
||||
}
|
||||
Some(sum / 8.0)
|
||||
}
|
||||
|
||||
pub fn CenterOfRing(
|
||||
image: &BitMatrix,
|
||||
center: Point,
|
||||
range: i32,
|
||||
nth: i32,
|
||||
requireCircle: bool,
|
||||
) -> Option<Point> {
|
||||
// range is the approximate width/height of the nth ring, if nth>1 then it would be plausible to limit the search radius
|
||||
// to approximately range / 2 * sqrt(2) == range * 0.75 but it turned out to be too limiting with realworld/noisy data.
|
||||
let radius = range;
|
||||
let inner = nth < 0;
|
||||
let nth = nth.abs();
|
||||
// log(center, 3);
|
||||
let mut cur = EdgeTracer::new(image, center, point(0.0, 1.0));
|
||||
if cur.stepToEdge(Some(nth), Some(radius), Some(inner)) == 0 {
|
||||
return None;
|
||||
}
|
||||
cur.turnRight(); // move clock wise and keep edge on the right/left depending on backup
|
||||
let edgeDir = if inner {
|
||||
Direction::Left
|
||||
} else {
|
||||
Direction::Right
|
||||
};
|
||||
|
||||
let mut neighbourMask = 0;
|
||||
let start = cur.p();
|
||||
let mut sum = Point::default();
|
||||
let mut n = 0;
|
||||
loop {
|
||||
// log(cur.p, 4);
|
||||
sum += cur.p().centered();
|
||||
n += 1;
|
||||
|
||||
// find out if we come full circle around the center. 8 bits have to be set in the end.
|
||||
neighbourMask |= 1
|
||||
<< (4.0
|
||||
+ Point::dot(
|
||||
Point::floor(Point::bresenhamDirection(cur.p() - center)),
|
||||
point(1.0, 3.0),
|
||||
)) as u32;
|
||||
|
||||
if !cur.stepAlongEdge(edgeDir, None) {
|
||||
return None;
|
||||
}
|
||||
|
||||
// use L-inf norm, simply because it is a lot faster than L2-norm and sufficiently accurate
|
||||
if Point::maxAbsComponent(cur.p - center) > radius as f32
|
||||
|| center == cur.p
|
||||
|| n > 4 * 2 * range
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
if !(cur.p != start) {
|
||||
break;
|
||||
}
|
||||
} //while (cur.p != start);
|
||||
|
||||
if requireCircle && neighbourMask != 0b111101111 {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(sum / n as f32)
|
||||
}
|
||||
|
||||
pub fn CenterOfRings(
|
||||
image: &BitMatrix,
|
||||
center: Point,
|
||||
range: i32,
|
||||
numOfRings: u32,
|
||||
) -> Option<Point> {
|
||||
let mut n = 1;
|
||||
let mut sum = center;
|
||||
for i in 2..(numOfRings + 1) {
|
||||
// for (int i = 1; i < numOfRings; ++i) {
|
||||
let c = CenterOfRing(image, center.floor(), range, i as i32, true)?;
|
||||
|
||||
if (c == Point::default()) {
|
||||
if n == 1 {
|
||||
return None;
|
||||
} else {
|
||||
return Some(sum / n as f32);
|
||||
}
|
||||
} else if Point::distance(c, center) > range as f32 / numOfRings as f32 / 2.0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
sum += c;
|
||||
n += 1;
|
||||
}
|
||||
Some(sum / n as f32)
|
||||
}
|
||||
|
||||
pub fn CollectRingPoints(
|
||||
image: &BitMatrix,
|
||||
center: Point,
|
||||
range: i32,
|
||||
edgeIndex: i32,
|
||||
backup: bool,
|
||||
) -> Vec<Point> {
|
||||
let centerI = center.floor();
|
||||
let radius = range;
|
||||
let mut cur = EdgeTracer::new(image, centerI, point(0.0, 1.0));
|
||||
if cur.stepToEdge(Some(edgeIndex), Some(radius), Some(backup)) == 0 {
|
||||
return Vec::default();
|
||||
}
|
||||
cur.turnRight(); // move clock wise and keep edge on the right/left depending on backup
|
||||
let edgeDir = if backup {
|
||||
Direction::Left
|
||||
} else {
|
||||
Direction::Right
|
||||
};
|
||||
|
||||
let mut neighbourMask = 0;
|
||||
let start = cur.p();
|
||||
let mut points = Vec::<Point>::with_capacity(4 * range as usize);
|
||||
|
||||
loop {
|
||||
// log(cur.p, 4);
|
||||
points.push(cur.p().centered());
|
||||
|
||||
// find out if we come full circle around the center. 8 bits have to be set in the end.
|
||||
neighbourMask |= 1
|
||||
<< (4.0
|
||||
+ Point::dot(
|
||||
Point::round(Point::bresenhamDirection(cur.p - centerI)),
|
||||
point(1.0, 3.0),
|
||||
)) as u32;
|
||||
|
||||
if !cur.stepAlongEdge(edgeDir, None) {
|
||||
return Vec::default();
|
||||
}
|
||||
|
||||
// use L-inf norm, simply because it is a lot faster than L2-norm and sufficiently accurate
|
||||
if Point::maxAbsComponent(cur.p - centerI) > radius as f32
|
||||
|| centerI == cur.p
|
||||
|| (points).len() > 4 * 2 * range as usize
|
||||
{
|
||||
return Vec::default();
|
||||
}
|
||||
|
||||
if !(cur.p != start) {
|
||||
break;
|
||||
}
|
||||
} //while (cur.p != start);
|
||||
|
||||
if neighbourMask != 0b111101111 {
|
||||
return Vec::default();
|
||||
}
|
||||
|
||||
points
|
||||
}
|
||||
|
||||
pub fn FitQadrilateralToPoints(center: Point, points: &mut [Point]) -> Option<Quadrilateral> {
|
||||
let dist2Center = |a, b| Point::distance(a, center) < Point::distance(b, center);
|
||||
// rotate points such that the first one is the furthest away from the center (hence, a corner)
|
||||
|
||||
let max_by_pred = |a: &&Point, b: &&Point| {
|
||||
let da = Point::distance(**a, center);
|
||||
let db = Point::distance(**b, center);
|
||||
da.partial_cmp(&db).unwrap()
|
||||
// if dist2Center(**a, **b) {
|
||||
// std::cmp::Ordering::Greater
|
||||
// } else {
|
||||
// std::cmp::Ordering::Less
|
||||
// }
|
||||
};
|
||||
|
||||
let max = points.iter().max_by(max_by_pred)?;
|
||||
|
||||
let pos = points.iter().position(|e| e == max)?;
|
||||
|
||||
points.rotate_left(pos);
|
||||
// std::rotate(points.begin(), std::max_element(points.begin(), points.end(), dist2Center), points.end());
|
||||
|
||||
let mut corners = [Point::default(); 4];
|
||||
corners[0] = points[0];
|
||||
// find the oposite corner by looking for the farthest point near the oposite point
|
||||
corners[2] = *points[(points.len() * 3 / 8)..=(points.len() * 5 / 8)]
|
||||
.iter()
|
||||
.max_by(max_by_pred)?;
|
||||
// corners[2] = std::max_element(&points[Size(points) * 3 / 8], &points[Size(points) * 5 / 8], dist2Center);
|
||||
// find the two in between corners by looking for the points farthest from the long diagonal
|
||||
let l = RegressionLine::with_two_points(corners[0], corners[2]);
|
||||
let dist2Diagonal = /*[l = RegressionLine(*corners[0], *corners[2])]*/| a, b| { l.distance_single(a) < l.distance_single(b) };
|
||||
|
||||
let diagonal_max_by_pred = |p1: &Point, p2: &Point| {
|
||||
let d1 = l.distance_single(*p1);
|
||||
let d2 = l.distance_single(*p2);
|
||||
d1.partial_cmp(&d2).unwrap()
|
||||
// if dist2Diagonal(*p1, *p2) {
|
||||
// std::cmp::Ordering::Greater
|
||||
// } else {
|
||||
// std::cmp::Ordering::Less
|
||||
// }
|
||||
};
|
||||
corners[1] = points[(points.len() / 8)..=(points.len() * 3 / 8)]
|
||||
.iter()
|
||||
.copied()
|
||||
.max_by(diagonal_max_by_pred)?;
|
||||
// corners[1] = std::max_element(&points[Size(points) * 1 / 8], &points[Size(points) * 3 / 8], dist2Diagonal);
|
||||
corners[3] = points[(points.len() * 5 / 8)..=(points.len() * 7 / 8)]
|
||||
.iter()
|
||||
.copied()
|
||||
.max_by(diagonal_max_by_pred)?;
|
||||
// corners[3] = std::max_element(&points[Size(points) * 5 / 8], &points[Size(points) * 7 / 8], dist2Diagonal);
|
||||
|
||||
let corner_positions = [
|
||||
0,
|
||||
points.iter().position(|p| *p == corners[1])?,
|
||||
points.iter().position(|p| *p == corners[2])?,
|
||||
points.iter().position(|p| *p == corners[3])?,
|
||||
];
|
||||
|
||||
let try_get_range = |a: usize, b: usize| -> Option<&[Point]> {
|
||||
if a > b {
|
||||
None
|
||||
} else {
|
||||
Some(&points[a + 1..b])
|
||||
}
|
||||
};
|
||||
|
||||
let lines = [
|
||||
RegressionLine::with_point_slice(try_get_range(corner_positions[0], corner_positions[1])?),
|
||||
RegressionLine::with_point_slice(try_get_range(corner_positions[1], corner_positions[2])?),
|
||||
RegressionLine::with_point_slice(try_get_range(corner_positions[2], corner_positions[3])?),
|
||||
RegressionLine::with_point_slice(try_get_range(corner_positions[3], points.len())?),
|
||||
];
|
||||
|
||||
// let lines = [
|
||||
// RegressionLine::with_point_slice(&points[corner_positions[0] + 1..corner_positions[1]]),
|
||||
// RegressionLine::with_point_slice(&points[corner_positions[1] + 1..corner_positions[2]]),
|
||||
// RegressionLine::with_point_slice(&points[corner_positions[2] + 1..corner_positions[3]]),
|
||||
// RegressionLine::with_point_slice(&points[corner_positions[3] + 1..points.len()]),
|
||||
// ];
|
||||
// std::array lines{RegressionLine{corners[0] + 1, corners[1]}, RegressionLine{corners[1] + 1, corners[2]},
|
||||
// RegressionLine{corners[2] + 1, corners[3]}, RegressionLine{corners[3] + 1, &points.back() + 1}};
|
||||
if lines.iter().any(|line| !line.isValid()) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let beg: [usize; 4] = [
|
||||
corner_positions[0] + 1,
|
||||
corner_positions[1] + 1,
|
||||
corner_positions[2] + 1,
|
||||
corner_positions[3] + 1,
|
||||
];
|
||||
let end: [usize; 4] = [
|
||||
corner_positions[1],
|
||||
corner_positions[2],
|
||||
corner_positions[3],
|
||||
points.len(),
|
||||
];
|
||||
|
||||
// check if all points belonging to each line segment are sufficiently close to that line
|
||||
for i in 0..4 {
|
||||
// for (int i = 0; i < 4; ++i){
|
||||
for p in &points[beg[i]..end[i]] {
|
||||
// for (const PointF* p = beg[i]; p != end[i]; ++p) {
|
||||
let len = (end[i] - beg[i]) as f64; //std::distance(beg[i], end[i]);
|
||||
if (len > 3.0
|
||||
&& (lines[i].distance_single(*p) as f64) > f64::max(1.0, f64::min(8.0, len / 8.0)))
|
||||
{
|
||||
// #ifdef PRINT_DEBUG
|
||||
// printf("%d: %.2f > %.2f @ %.fx%.f\n", i, lines[i].distance(*p), std::distance(beg[i], end[i]) / 1., p->x, p->y);
|
||||
// #endif
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut res = Quadrilateral::default();
|
||||
for i in 0..4 {
|
||||
// for (int i = 0; i < 4; ++i) {
|
||||
res[i] = RegressionLine::intersect(&lines[i], &lines[(i + 1) % 4])?;
|
||||
}
|
||||
|
||||
Some(res)
|
||||
}
|
||||
|
||||
pub fn QuadrilateralIsPlausibleSquare(q: &Quadrilateral, lineIndex: usize) -> bool {
|
||||
let mut m;
|
||||
|
||||
m = Point::distance(q[0], q[3]) as f64; //M = distance(q[0], q[3]);
|
||||
let mut M = m;
|
||||
|
||||
for i in 1..4 {
|
||||
// for (int i = 1; i < 4; ++i)
|
||||
|
||||
UpdateMinMaxFloat(&mut m, &mut M, Point::distance(q[i - 1], q[i]) as f64);
|
||||
}
|
||||
|
||||
m >= (lineIndex * 2) as f64 && m > M / 3.0
|
||||
}
|
||||
|
||||
pub fn FitSquareToPoints(
|
||||
image: &BitMatrix,
|
||||
center: Point,
|
||||
range: i32,
|
||||
lineIndex: i32,
|
||||
backup: bool,
|
||||
) -> Option<Quadrilateral> {
|
||||
let mut points = CollectRingPoints(image, center, range, lineIndex, backup);
|
||||
if points.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let res = FitQadrilateralToPoints(center, &mut points)?;
|
||||
if !QuadrilateralIsPlausibleSquare(&res, (lineIndex - i32::from(backup)) as usize) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(res)
|
||||
}
|
||||
|
||||
pub fn FindConcentricPatternCorners(
|
||||
image: &BitMatrix,
|
||||
center: Point,
|
||||
range: i32,
|
||||
lineIndex: i32,
|
||||
) -> Option<Quadrilateral> {
|
||||
let innerCorners = FitSquareToPoints(image, center, range, lineIndex, false)?;
|
||||
|
||||
let outerCorners = FitSquareToPoints(image, center, range, lineIndex + 1, true)?;
|
||||
|
||||
let res = Quadrilateral::blend(&innerCorners, &outerCorners);
|
||||
|
||||
// for p in innerCorners{
|
||||
// log(p, 3);}
|
||||
|
||||
// for p in outerCorners{
|
||||
// log(p, 3);}
|
||||
|
||||
// for p in res{
|
||||
// log(p, 3);}
|
||||
|
||||
Some(res)
|
||||
}
|
||||
|
||||
#[derive(Default, Copy, Clone, Eq, PartialEq, Debug)]
|
||||
pub struct ConcentricPattern {
|
||||
pub p: Point,
|
||||
pub size: i32,
|
||||
}
|
||||
|
||||
impl std::ops::Sub for ConcentricPattern {
|
||||
type Output = Self;
|
||||
|
||||
fn sub(self, rhs: Self) -> Self::Output {
|
||||
let new_p = self.p - rhs.p;
|
||||
Self {
|
||||
p: new_p,
|
||||
size: self.size,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Add for ConcentricPattern {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
let new_p = self.p + rhs.p;
|
||||
Self {
|
||||
p: new_p,
|
||||
size: self.size,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Point> for ConcentricPattern {
|
||||
fn from(value: Point) -> Self {
|
||||
Self { p: value, size: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl ConcentricPattern {
|
||||
pub fn dot(self, other: ConcentricPattern) -> f32 {
|
||||
Point::dot(self.p, other.p)
|
||||
}
|
||||
|
||||
pub fn cross(self, other: ConcentricPattern) -> f32 {
|
||||
Point::cross(self.p, other.p)
|
||||
}
|
||||
|
||||
pub fn distance(self, other: ConcentricPattern) -> f32 {
|
||||
Point::distance(self.p, other.p)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn LocateConcentricPattern<const E2E: bool, const LEN: usize, const SUM: usize>(
|
||||
image: &BitMatrix,
|
||||
pattern: &Pattern<LEN>,
|
||||
center: Point,
|
||||
range: i32,
|
||||
) -> Option<ConcentricPattern> {
|
||||
let mut cur = EdgeTracer::new(image, center.floor(), Point::default());
|
||||
let mut minSpread = image.getWidth() as i32;
|
||||
let mut maxSpread = 0_i32;
|
||||
|
||||
// TODO: setting maxError to 1 can subtantially help with detecting symbols with low print quality resulting in damaged
|
||||
// finder patterns, but it sutantially increases the runtime (approx. 20% slower for the falsepositive images).
|
||||
let mut maxError = 0;
|
||||
for d in [point(0.0, 1.0), point(1.0, 0.0)] {
|
||||
// for (auto d : {PointI{0, 1}, {1, 0}}) {
|
||||
cur.setDirection(d); // THIS COULD POSSIBLY BE WRONG, WE MIGHT MEAN TO CLONE cur EACH RUN?
|
||||
|
||||
let spread = CheckSymmetricPattern::<E2E, LEN, SUM, _>(&mut cur, pattern, range, true);
|
||||
if spread != 0 {
|
||||
UpdateMinMax(&mut minSpread, &mut maxSpread, spread);
|
||||
} else {
|
||||
maxError -= 1;
|
||||
if maxError < 0 {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//#if 1
|
||||
for d in [point(1.0, 1.0), point(1.0, -1.0)] {
|
||||
// for (auto d : {PointI{1, 1}, {1, -1}}) {
|
||||
cur.setDirection(d); // THIS COULD POSSIBLY BE WRONG, WE MIGHT MEAN TO CLONE cur EACH RUN?
|
||||
let spread = CheckSymmetricPattern::<E2E, LEN, SUM, _>(&mut cur, pattern, range * 2, false);
|
||||
if spread != 0 {
|
||||
UpdateMinMax(&mut minSpread, &mut maxSpread, spread);
|
||||
} else {
|
||||
maxError -= 1;
|
||||
if maxError < 0 {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
//#endif
|
||||
|
||||
if maxSpread > 5 * minSpread {
|
||||
return None;
|
||||
}
|
||||
|
||||
let newCenter = FinetuneConcentricPatternCenter(image, cur.p(), range, pattern.len() as u32)?;
|
||||
|
||||
Some(ConcentricPattern {
|
||||
p: newCenter,
|
||||
size: (maxSpread + minSpread) / 2,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn FinetuneConcentricPatternCenter(
|
||||
image: &BitMatrix,
|
||||
center: Point,
|
||||
range: i32,
|
||||
finderPatternSize: u32,
|
||||
) -> Option<Point> {
|
||||
// make sure we have at least one path of white around the center
|
||||
if let Some(res1) = CenterOfRing(image, center.floor(), range, 1, true) {
|
||||
if !image.get_point(res1) {
|
||||
return None;
|
||||
}
|
||||
// and then either at least one more ring around that
|
||||
if let Some(res2) = CenterOfRings(image, res1, range, finderPatternSize / 2) {
|
||||
return Some(res2);
|
||||
}
|
||||
// or the center can be approximated by a square
|
||||
if (FitSquareToPoints(image, res1, range, 1, false).is_some()) {
|
||||
return Some(res1);
|
||||
}
|
||||
// TODO: this is currently only keeping #258 alive, evaluate if still worth it
|
||||
if let Some(res2) =
|
||||
CenterOfDoubleCross(image, res1.floor(), range, finderPatternSize / 2 + 1)
|
||||
{
|
||||
return Some(res2);
|
||||
}
|
||||
}
|
||||
None
|
||||
|
||||
// // make sure we have at least one path of white around the center
|
||||
// let res = CenterOfRing(image, center, range, 1, false)?;
|
||||
|
||||
// let center = res;
|
||||
|
||||
// let mut res = CenterOfRings(image, center, range, finderPatternSize / 2);
|
||||
|
||||
// if res.is_none() || !image.get_point(res?) {
|
||||
// res = CenterOfDoubleCross(image, center, range, finderPatternSize / 2 + 1);
|
||||
// }
|
||||
// if res.is_none() || !image.get_point(res?) {
|
||||
// res = Some(center);
|
||||
// }
|
||||
// if res.is_none() || !image.get_point(res?) {
|
||||
// return None;
|
||||
// }
|
||||
|
||||
// res
|
||||
}
|
||||
192
src/common/cpp_essentials/decoder_result.rs
Normal file
@@ -0,0 +1,192 @@
|
||||
use std::{any::Any, rc::Rc};
|
||||
|
||||
use crate::{common::ECIStringBuilder, Exceptions, RXingResult};
|
||||
|
||||
use super::StructuredAppendInfo;
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Clone)]
|
||||
pub struct DecoderResult<T>
|
||||
where
|
||||
T: Copy + Clone + Default + Eq + PartialEq,
|
||||
{
|
||||
content: ECIStringBuilder,
|
||||
ecLevel: String,
|
||||
lineCount: u32, // = 0;
|
||||
versionNumber: u32, // = 0;
|
||||
structuredAppend: StructuredAppendInfo,
|
||||
isMirrored: bool, // = false;
|
||||
readerInit: bool, // = false;
|
||||
//Error _error;
|
||||
//std::shared_ptr<CustomData> _extra;
|
||||
error: Option<Exceptions>,
|
||||
extra: Rc<T>,
|
||||
}
|
||||
|
||||
impl<T> Default for DecoderResult<T>
|
||||
where
|
||||
T: Copy + Clone + Default + Eq + PartialEq,
|
||||
{
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
content: Default::default(),
|
||||
ecLevel: Default::default(),
|
||||
lineCount: 0,
|
||||
versionNumber: 0,
|
||||
structuredAppend: Default::default(),
|
||||
isMirrored: false,
|
||||
readerInit: false,
|
||||
error: None,
|
||||
extra: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DecoderResult<T>
|
||||
where
|
||||
T: Copy + Clone + Default + Eq + PartialEq,
|
||||
{
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
pub fn with_eci_string_builder(src: ECIStringBuilder) -> Self {
|
||||
let mut new_self = Self::default();
|
||||
new_self.content = src;
|
||||
|
||||
new_self
|
||||
}
|
||||
|
||||
pub fn isValid(&self) -> bool {
|
||||
self.content.symbology.code != 0 && self.error.is_none()
|
||||
//return includeErrors || (_content.symbology.code != 0 && !_error);
|
||||
}
|
||||
|
||||
pub fn content(&self) -> &ECIStringBuilder {
|
||||
&self.content
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DecoderResult<T>
|
||||
where
|
||||
T: Copy + Clone + Default + Eq + PartialEq,
|
||||
{
|
||||
pub fn ecLevel(&self) -> &str {
|
||||
&self.ecLevel
|
||||
}
|
||||
pub fn setEcLevel(&mut self, ecLevel: String) {
|
||||
self.ecLevel = ecLevel
|
||||
}
|
||||
pub fn withEcLevel(mut self, ecLevel: String) -> DecoderResult<T> {
|
||||
self.setEcLevel(ecLevel);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn lineCount(&self) -> u32 {
|
||||
self.lineCount
|
||||
}
|
||||
pub fn setLineCount(&mut self, lc: u32) {
|
||||
self.lineCount = lc
|
||||
}
|
||||
pub fn withLineCount(mut self, lc: u32) -> DecoderResult<T> {
|
||||
self.setLineCount(lc);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn versionNumber(&self) -> u32 {
|
||||
self.versionNumber
|
||||
}
|
||||
pub fn setVersionNumber(&mut self, vn: u32) {
|
||||
self.versionNumber = vn
|
||||
}
|
||||
pub fn withVersionNumber(mut self, vn: u32) -> DecoderResult<T> {
|
||||
self.setVersionNumber(vn);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn structuredAppend(&self) -> &StructuredAppendInfo {
|
||||
&self.structuredAppend
|
||||
}
|
||||
pub fn setStructuredAppend(&mut self, sai: StructuredAppendInfo) {
|
||||
self.structuredAppend = sai
|
||||
}
|
||||
pub fn withStructuredAppend(mut self, sai: StructuredAppendInfo) -> DecoderResult<T> {
|
||||
self.setStructuredAppend(sai);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn isMirrored(&self) -> bool {
|
||||
self.isMirrored
|
||||
}
|
||||
pub fn setIsMirrored(&mut self, is_mirrored: bool) {
|
||||
self.isMirrored = is_mirrored
|
||||
}
|
||||
pub fn withIsMirrored(mut self, is_mirrored: bool) -> DecoderResult<T> {
|
||||
self.setIsMirrored(is_mirrored);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn readerInit(&self) -> bool {
|
||||
self.readerInit
|
||||
}
|
||||
pub fn setReaderInit(&mut self, reader_init: bool) {
|
||||
self.readerInit = reader_init
|
||||
}
|
||||
pub fn withReaderInit(mut self, reader_init: bool) -> DecoderResult<T> {
|
||||
self.setReaderInit(reader_init);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn extra(&self) -> Rc<T> {
|
||||
self.extra.clone()
|
||||
}
|
||||
pub fn setExtra(&mut self, extra: Rc<T>) {
|
||||
self.extra = extra
|
||||
}
|
||||
pub fn withExtra(mut self, extra: Rc<T>) -> DecoderResult<T> {
|
||||
self.setExtra(extra);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn error(&self) -> &Option<Exceptions> {
|
||||
&self.error
|
||||
}
|
||||
pub fn setError(&mut self, error: Option<Exceptions>) {
|
||||
self.error = error
|
||||
}
|
||||
pub fn withError(mut self, error: Option<Exceptions>) -> DecoderResult<T> {
|
||||
self.setError(error);
|
||||
self
|
||||
}
|
||||
|
||||
// pub fn build(self) -> DecoderResult<T> {
|
||||
|
||||
// }
|
||||
}
|
||||
|
||||
impl<T> DecoderResult<T>
|
||||
where
|
||||
T: Copy + Clone + Default + Eq + PartialEq,
|
||||
{
|
||||
pub fn text(&self) -> String {
|
||||
self.content.to_string()
|
||||
}
|
||||
|
||||
pub fn symbologyIdentifier(&self) -> String {
|
||||
let s = self.content.symbology;
|
||||
if s.code > 0 {
|
||||
format!(
|
||||
"]{}{}",
|
||||
char::from(s.code),
|
||||
char::from(
|
||||
s.modifier
|
||||
+ if self.content.has_eci {
|
||||
s.eciModifierOffset
|
||||
} else {
|
||||
0
|
||||
}
|
||||
)
|
||||
)
|
||||
} else {
|
||||
String::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,7 @@
|
||||
use crate::common::Result;
|
||||
use crate::{Exceptions, Point};
|
||||
|
||||
use super::{
|
||||
util::{float_max, float_min},
|
||||
RegressionLine,
|
||||
};
|
||||
use super::RegressionLineTrait;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DMRegressionLine {
|
||||
@@ -30,7 +27,7 @@ impl Default for DMRegressionLine {
|
||||
}
|
||||
}
|
||||
|
||||
impl RegressionLine for DMRegressionLine {
|
||||
impl RegressionLineTrait for DMRegressionLine {
|
||||
fn points(&self) -> &[Point] {
|
||||
&self.points
|
||||
}
|
||||
@@ -136,14 +133,14 @@ impl RegressionLine for DMRegressionLine {
|
||||
let Some(mut min) = self.points.first().copied() else { return false };
|
||||
let Some(mut max) = self.points.first().copied() else { return false };
|
||||
for p in &self.points {
|
||||
min.x = float_min(min.x, p.x);
|
||||
min.y = float_min(min.y, p.y);
|
||||
max.x = float_max(max.x, p.x);
|
||||
max.y = float_max(max.y, p.y);
|
||||
min.x = f32::min(min.x, p.x);
|
||||
min.y = f32::min(min.y, p.y);
|
||||
max.x = f32::max(max.x, p.x);
|
||||
max.y = f32::max(max.y, p.y);
|
||||
}
|
||||
let diff = max - min;
|
||||
let len = diff.maxAbsComponent();
|
||||
let steps = float_min(diff.x.abs(), diff.y.abs());
|
||||
let steps = f32::min(diff.x.abs(), diff.y.abs());
|
||||
// due to aliasing we get bad extrapolations if the line is short and too close to vertical/horizontal
|
||||
steps > 2.0 || len > 50.0
|
||||
}
|
||||
@@ -211,9 +208,27 @@ impl RegressionLine for DMRegressionLine {
|
||||
Point::dot(self.direction_inward, self.normal()) > 0.5
|
||||
// angle between original and new direction is at most 60 degree
|
||||
}
|
||||
|
||||
fn a(&self) -> f32 {
|
||||
self.a
|
||||
}
|
||||
|
||||
fn b(&self) -> f32 {
|
||||
self.b
|
||||
}
|
||||
|
||||
fn c(&self) -> f32 {
|
||||
self.c
|
||||
}
|
||||
}
|
||||
|
||||
impl DMRegressionLine {
|
||||
pub fn new(point_1: Point, point_2: Point) -> Self {
|
||||
let mut new = Self::default();
|
||||
RegressionLineTrait::evaluate(&mut new, &[point_1, point_2]);
|
||||
new
|
||||
}
|
||||
|
||||
// template <typename Container, typename Filter>
|
||||
fn average<T>(c: &[f64], f: T) -> f64
|
||||
where
|
||||
@@ -6,13 +6,13 @@ use crate::{
|
||||
Exceptions, Point,
|
||||
};
|
||||
|
||||
use super::{BitMatrixCursor, Direction, RegressionLine, StepResult, Value};
|
||||
use super::{BitMatrixCursorTrait, Direction, RegressionLineTrait, StepResult, Value};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct EdgeTracer<'a> {
|
||||
pub(super) img: &'a BitMatrix,
|
||||
pub(crate) img: &'a BitMatrix,
|
||||
|
||||
pub(super) p: Point, // current position
|
||||
pub(crate) p: Point, // current position
|
||||
d: Point, // current direction
|
||||
|
||||
// pub history: Option<&'a mut ByteMatrix>, // = nullptr;
|
||||
@@ -34,7 +34,7 @@ pub struct EdgeTracer<'a> {
|
||||
// }
|
||||
// }
|
||||
|
||||
impl BitMatrixCursor for EdgeTracer<'_> {
|
||||
impl BitMatrixCursorTrait for EdgeTracer<'_> {
|
||||
fn testAt(&self, p: Point) -> Value {
|
||||
if self.img.isIn(p, 0) {
|
||||
Value::from(self.img.get_point(p))
|
||||
@@ -119,13 +119,20 @@ impl BitMatrixCursor for EdgeTracer<'_> {
|
||||
self.isIn(self.p)
|
||||
}
|
||||
|
||||
fn movedBy<T: BitMatrixCursor>(self, d: Point) -> Self {
|
||||
fn movedBy<T: BitMatrixCursorTrait>(self, d: Point) -> Self {
|
||||
let mut res = self;
|
||||
res.p += d;
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
fn turnedBack(&self) -> Self {
|
||||
let mut res = self.clone();
|
||||
res.d = res.back();
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief stepToEdge advances cursor to one step behind the next (or n-th) edge.
|
||||
* @param nth number of edges to pass
|
||||
@@ -134,10 +141,10 @@ impl BitMatrixCursor for EdgeTracer<'_> {
|
||||
* @return number of steps taken or 0 if moved outside of range/image
|
||||
*/
|
||||
fn stepToEdge(&mut self, nth: Option<i32>, range: Option<i32>, backup: Option<bool>) -> i32 {
|
||||
let mut nth = if let Some(nth) = nth { nth } else { 1 };
|
||||
let range = if let Some(r) = range { r } else { 0 };
|
||||
let backup = if let Some(b) = backup { b } else { false };
|
||||
// TODO: provide an alternative and faster out-of-bounds check than isIn() inside testAt()
|
||||
let mut nth = nth.unwrap_or(1); //if let Some(nth) = nth { nth } else { 1 };
|
||||
let range = range.unwrap_or(0); //if let Some(r) = range { r } else { 0 };
|
||||
let backup = backup.unwrap_or(false); //if let Some(b) = backup { b } else { false };
|
||||
// TODO: provide an alternative and faster out-of-bounds check than isIn() inside testAt()
|
||||
let mut steps = 0;
|
||||
let mut lv = self.testAt(self.p);
|
||||
|
||||
@@ -155,6 +162,18 @@ impl BitMatrixCursor for EdgeTracer<'_> {
|
||||
self.p += self.d * steps;
|
||||
steps * i32::from(nth == 0)
|
||||
}
|
||||
|
||||
fn p(&self) -> Point {
|
||||
self.p
|
||||
}
|
||||
|
||||
fn d(&self) -> Point {
|
||||
self.d
|
||||
}
|
||||
|
||||
fn img(&self) -> &BitMatrix {
|
||||
self.img
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> EdgeTracer<'_> {
|
||||
@@ -163,7 +182,7 @@ impl<'a> EdgeTracer<'_> {
|
||||
EdgeTracer {
|
||||
img: image,
|
||||
p,
|
||||
d,
|
||||
d: Point::bresenhamDirection(d), //d,
|
||||
history: None,
|
||||
state: 0,
|
||||
}
|
||||
@@ -259,7 +278,11 @@ impl<'a> EdgeTracer<'_> {
|
||||
true
|
||||
}
|
||||
|
||||
pub fn traceLine<T: RegressionLine>(&mut self, dEdge: Point, line: &mut T) -> Result<bool> {
|
||||
pub fn traceLine<T: RegressionLineTrait>(
|
||||
&mut self,
|
||||
dEdge: Point,
|
||||
line: &mut T,
|
||||
) -> Result<bool> {
|
||||
line.setDirectionInward(dEdge);
|
||||
loop {
|
||||
// log(self.p);
|
||||
@@ -286,7 +309,7 @@ impl<'a> EdgeTracer<'_> {
|
||||
} // while (true);
|
||||
}
|
||||
|
||||
pub fn traceGaps<T: RegressionLine>(
|
||||
pub fn traceGaps<T: RegressionLineTrait>(
|
||||
&mut self,
|
||||
dEdge: Point,
|
||||
line: &mut T,
|
||||
84
src/common/cpp_essentials/fast_edge_to_edge_counter.rs
Normal file
@@ -0,0 +1,84 @@
|
||||
use crate::common::BitMatrix;
|
||||
|
||||
use super::BitMatrixCursorTrait;
|
||||
|
||||
pub struct FastEdgeToEdgeCounter<'a> {
|
||||
// const uint8_t* p = nullptr;
|
||||
// int stride = 0;
|
||||
// int stepsToBorder = 0;
|
||||
p: u32, // = nullptr;
|
||||
stride: isize, // = 0;
|
||||
stepsToBorder: i32, // = 0;
|
||||
//arr: BitArray,
|
||||
_arr: isize,
|
||||
under_arry: &'a BitMatrix, //,Vec<bool>
|
||||
}
|
||||
|
||||
impl<'a> FastEdgeToEdgeCounter<'a> {
|
||||
pub fn new<T: BitMatrixCursorTrait>(cur: &T) -> FastEdgeToEdgeCounter {
|
||||
let stride = cur.d().y as isize * cur.img().width() as isize + cur.d().x as isize;
|
||||
let p = ((cur.p().y as isize * cur.img().width() as isize).abs() as i32 + cur.p().x as i32)
|
||||
as u32; // P IS SET WRONG IN REVERSE
|
||||
|
||||
let maxStepsX: i32 = if cur.d().x != 0.0 {
|
||||
if cur.d().x > 0.0 {
|
||||
cur.img().width() as i32 - 1 - cur.p().x as i32
|
||||
} else {
|
||||
cur.p().x as i32
|
||||
}
|
||||
} else {
|
||||
i32::MAX
|
||||
};
|
||||
let maxStepsY: i32 = if cur.d().y != 0.0 {
|
||||
if cur.d().y > 0.0 {
|
||||
cur.img().height() as i32 - 1 - cur.p().y as i32
|
||||
} else {
|
||||
cur.p().y as i32
|
||||
}
|
||||
} else {
|
||||
i32::MAX
|
||||
};
|
||||
let stepsToBorder = std::cmp::min(maxStepsX, maxStepsY) as i32;
|
||||
|
||||
FastEdgeToEdgeCounter {
|
||||
p,
|
||||
stride,
|
||||
stepsToBorder,
|
||||
_arr: cur.p().y as isize * stride as isize, //cur.img().getRow(cur.p().y as u32),
|
||||
under_arry: cur.img(), //.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stepToNextEdge(&mut self, range: u32) -> u32 {
|
||||
let maxSteps = std::cmp::min(self.stepsToBorder, range as i32);
|
||||
let mut steps = 0;
|
||||
loop {
|
||||
steps += 1;
|
||||
if steps > maxSteps {
|
||||
if maxSteps == self.stepsToBorder {
|
||||
break;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
let idx_pt = self.get_array_check_index(steps);
|
||||
|
||||
// if !(self.under_arry[idx_pt]
|
||||
// == self.under_arry[self.p as usize])
|
||||
if !(self.under_arry.get_index(idx_pt) == self.under_arry.get_index(self.p as usize)) {
|
||||
break;
|
||||
}
|
||||
} // while (p[steps * stride] == p[0]);
|
||||
|
||||
self.p = (self.p as isize + (steps as isize * self.stride)).abs() as u32;
|
||||
self.stepsToBorder -= steps;
|
||||
|
||||
return steps as u32;
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn get_array_check_index(&self, steps: i32) -> usize {
|
||||
(self.p as isize + (steps as isize * self.stride)) as usize
|
||||
}
|
||||
}
|
||||
108
src/common/cpp_essentials/matrix.rs
Normal file
@@ -0,0 +1,108 @@
|
||||
use crate::common::Result;
|
||||
use crate::{Exceptions, Point};
|
||||
|
||||
#[derive(Default, Clone, PartialEq, Eq)]
|
||||
pub struct Matrix<T: Default + Clone + Copy> {
|
||||
width: usize,
|
||||
height: usize,
|
||||
data: Vec<Option<T>>,
|
||||
}
|
||||
|
||||
impl<T: Default + Clone + Copy> Matrix<T> {
|
||||
pub fn with_data(width: usize, height: usize, data: Vec<Option<T>>) -> Result<Matrix<T>> {
|
||||
if width != 0 && data.len() / width as usize != height as usize {
|
||||
return Err(Exceptions::illegal_argument_with(
|
||||
"invalid size: width * height is too big",
|
||||
));
|
||||
}
|
||||
Ok(Self {
|
||||
width,
|
||||
height,
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new(width: usize, height: usize) -> Result<Matrix<T>> {
|
||||
if (width != 0 && (width * height) / width as usize != height as usize) {
|
||||
return Err(Exceptions::illegal_argument_with(
|
||||
"invalid size: width * height is too big",
|
||||
));
|
||||
}
|
||||
Ok(Self {
|
||||
width,
|
||||
height,
|
||||
data: vec![None; width * height],
|
||||
})
|
||||
}
|
||||
|
||||
pub fn height(&self) -> usize {
|
||||
self.height
|
||||
}
|
||||
|
||||
pub fn width(&self) -> usize {
|
||||
self.width
|
||||
}
|
||||
|
||||
pub fn size(&self) -> usize {
|
||||
self.data.len()
|
||||
}
|
||||
|
||||
// value_t& operator()(int x, int y)
|
||||
// {
|
||||
// assert(x >= 0 && x < _width && y >= 0 && y < _height);
|
||||
// return _data[y * _width + x];
|
||||
// }
|
||||
|
||||
// const T& operator()(int x, int y) const
|
||||
// {
|
||||
// assert(x >= 0 && x < _width && y >= 0 && y < _height);
|
||||
// return _data[y * _width + x];
|
||||
// }
|
||||
|
||||
fn get_offset(x: usize, y: usize, width: usize) -> usize {
|
||||
(y * width + x) as usize
|
||||
}
|
||||
|
||||
pub fn get(&self, x: usize, y: usize) -> Option<T> {
|
||||
if x >= self.width || y >= self.height {
|
||||
None
|
||||
} else if let Some(Some(d)) = self.data.get(Self::get_offset(x, y, self.width)) {
|
||||
Some(*d)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set(&mut self, x: usize, y: usize, value: T) -> T {
|
||||
self.data[Self::get_offset(x, y, self.width)] = Some(value);
|
||||
self.get(x, y).unwrap()
|
||||
}
|
||||
|
||||
pub fn get_point(&self, p: Point) -> Option<T> {
|
||||
self.get(p.x as usize, p.y as usize)
|
||||
}
|
||||
|
||||
pub fn set_point(&mut self, p: Point, value: T) -> T {
|
||||
self.set(p.x as usize, p.y as usize, value)
|
||||
}
|
||||
|
||||
pub fn data(&self) -> &[Option<T>] {
|
||||
&self.data
|
||||
}
|
||||
|
||||
// const value_t* begin() const {
|
||||
// return _data.data();
|
||||
// }
|
||||
|
||||
// const value_t* end() const {
|
||||
// return _data.data() + _width * _height;
|
||||
// }
|
||||
|
||||
pub fn clear_with(&mut self, value: T) {
|
||||
self.data.fill(Some(value))
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.data.fill(None)
|
||||
}
|
||||
}
|
||||
35
src/common/cpp_essentials/mod.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
mod base_extentions;
|
||||
|
||||
pub mod bitmatrix_cursor;
|
||||
pub mod bitmatrix_cursor_trait;
|
||||
pub mod concentric_finder;
|
||||
pub mod decoder_result;
|
||||
pub mod direction;
|
||||
pub mod dm_regression_line;
|
||||
pub mod edge_tracer;
|
||||
pub mod fast_edge_to_edge_counter;
|
||||
pub mod matrix;
|
||||
pub mod pattern;
|
||||
pub mod regression_line;
|
||||
pub mod regression_line_trait;
|
||||
pub mod step_result;
|
||||
pub mod structured_append;
|
||||
pub mod util;
|
||||
pub mod value;
|
||||
|
||||
pub use bitmatrix_cursor::*;
|
||||
pub use bitmatrix_cursor_trait::*;
|
||||
pub use concentric_finder::*;
|
||||
pub use decoder_result::*;
|
||||
pub use direction::*;
|
||||
pub use dm_regression_line::*;
|
||||
pub use edge_tracer::*;
|
||||
pub use fast_edge_to_edge_counter::*;
|
||||
pub use matrix::*;
|
||||
pub use pattern::*;
|
||||
pub use regression_line::*;
|
||||
pub use regression_line_trait::*;
|
||||
pub use step_result::*;
|
||||
pub use structured_append::*;
|
||||
pub use util::*;
|
||||
pub use value::*;
|
||||
855
src/common/cpp_essentials/pattern.rs
Normal file
@@ -0,0 +1,855 @@
|
||||
/*
|
||||
* Copyright 2020 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::{
|
||||
common::{BitMatrix, Result},
|
||||
Exceptions,
|
||||
};
|
||||
|
||||
pub type PatternType = u16;
|
||||
pub type Pattern<const N: usize> = [PatternType; N];
|
||||
|
||||
fn BarAndSpaceSum<
|
||||
const LEN: usize,
|
||||
T: Into<RT> + Copy,
|
||||
RT: Default + std::cmp::PartialEq + std::ops::AddAssign,
|
||||
>(
|
||||
view: &[T],
|
||||
) -> BarAndSpace<RT> {
|
||||
let mut res = BarAndSpace::default();
|
||||
for i in 0..LEN {
|
||||
// for (int i = 0; i < LEN; ++i)
|
||||
res[i] += view[i].into();
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct PatternRow(Vec<PatternType>);
|
||||
|
||||
// pub struct PatternRow<T: std::iter::Sum + Into<f32> + Into<usize> + Copy>(Vec<T>);
|
||||
|
||||
impl PatternRow {
|
||||
pub fn new(v: Vec<PatternType>) -> Self {
|
||||
Self(v)
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
|
||||
pub fn into_pattern_view(&self) -> PatternView {
|
||||
PatternView::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for PatternRow {
|
||||
type Item = PatternType;
|
||||
|
||||
type IntoIter = std::vec::IntoIter<PatternType>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.0.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Index<usize> for PatternRow {
|
||||
type Output = PatternType;
|
||||
|
||||
fn index(&self, index: usize) -> &Self::Output {
|
||||
&self.0[index]
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::IndexMut<usize> for PatternRow {
|
||||
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
|
||||
&mut self.0[index]
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<PatternType>> for PatternRow {
|
||||
fn from(value: Vec<PatternType>) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PatternViewIterator<'a> {
|
||||
pattern_view: &'a PatternView<'a>,
|
||||
current_position: usize,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for PatternViewIterator<'_> {
|
||||
type Item = PatternType;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.current_position + 1 > self.pattern_view.count {
|
||||
return None;
|
||||
}
|
||||
|
||||
self.current_position += 1;
|
||||
|
||||
Some(
|
||||
*self.pattern_view.data.0.get(
|
||||
self.current_position - 1 + self.pattern_view.start + self.pattern_view.current,
|
||||
)?,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct PatternView<'a> {
|
||||
data: &'a PatternRow,
|
||||
start: usize,
|
||||
count: usize,
|
||||
current: usize,
|
||||
}
|
||||
|
||||
impl<'a> PatternView<'a> {
|
||||
// A PatternRow always starts with the width of whitespace in front of the first black bar.
|
||||
// The first element of the PatternView is the first bar.
|
||||
pub fn new(bars: &'a PatternRow) -> PatternView<'a> {
|
||||
PatternView {
|
||||
data: bars,
|
||||
start: 1,
|
||||
count: bars.0.len(),
|
||||
current: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_config(
|
||||
bars: &'a PatternRow,
|
||||
start: usize,
|
||||
size: usize,
|
||||
base: usize,
|
||||
_end: usize,
|
||||
) -> PatternView<'a> {
|
||||
PatternView {
|
||||
data: bars,
|
||||
start,
|
||||
count: size,
|
||||
current: base,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn data(&self) -> &PatternRow {
|
||||
self.data
|
||||
}
|
||||
pub fn begin(&self) -> Option<PatternType> {
|
||||
Some(*self.data.0.get(self.start)?)
|
||||
}
|
||||
pub fn end(&self) -> Option<PatternType> {
|
||||
// if self.start + self.count < self.data.0.len() {
|
||||
// Some(self.data.0[self.start + self.count])
|
||||
// } else {
|
||||
// None
|
||||
// }
|
||||
Some(self.data.0.len() as PatternType)
|
||||
}
|
||||
|
||||
// int sum(int n = 0) const { return std::accumulate(_data, _data + (n == 0 ? _size : n), 0); }
|
||||
pub fn sum(&self, n: Option<usize>) -> PatternType {
|
||||
if self.count == self.data.len() {
|
||||
return self.data.0.iter().sum::<PatternType>();
|
||||
}
|
||||
|
||||
let n = n.unwrap_or(self.count);
|
||||
|
||||
self.data
|
||||
.0
|
||||
.iter()
|
||||
.skip(self.start + self.current)
|
||||
.take(n)
|
||||
.copied()
|
||||
.sum::<PatternType>()
|
||||
}
|
||||
|
||||
pub fn iter(&'a self) -> PatternViewIterator<'a> {
|
||||
PatternViewIterator {
|
||||
pattern_view: self,
|
||||
current_position: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn size(&self) -> usize {
|
||||
self.count
|
||||
}
|
||||
|
||||
// index is the number of bars and spaces from the first bar to the current position
|
||||
pub fn index(&self) -> usize {
|
||||
self.current /*return narrow_cast<int>(_data - _base) - 1;*/
|
||||
}
|
||||
pub fn pixelsInFront(&self) -> PatternType {
|
||||
self.data
|
||||
.0
|
||||
.iter()
|
||||
.take(self.start + self.current)
|
||||
.copied()
|
||||
.sum::<PatternType>() /*return std::accumulate(_base, _data, 0);*/
|
||||
}
|
||||
pub fn pixelsTillEnd(&self) -> PatternType {
|
||||
self.data
|
||||
.0
|
||||
.iter()
|
||||
.skip(self.start + self.current)
|
||||
.copied()
|
||||
.sum::<PatternType>() /*return std::accumulate(_base, _data + _size, 0) - 1;*/
|
||||
}
|
||||
pub fn isAtFirstBar(&self) -> bool {
|
||||
self.start == (self.current + 1) /*return _data == _base + 1;*/
|
||||
}
|
||||
pub fn isAtLastBar(&self) -> bool {
|
||||
self.current == self.start + self.count - 1 /*return _data + _size == _end - 1;*/
|
||||
}
|
||||
pub fn isValidWithN(&self, n: usize) -> bool {
|
||||
!self.data.0.is_empty()
|
||||
&& self.start <= self.current + self.start
|
||||
&& self.current + n <= (self.data.0.len())
|
||||
/*return _data && _data >= _base && _data + n <= _end;*/
|
||||
}
|
||||
pub fn isValid(&self) -> bool {
|
||||
self.isValidWithN(self.size())
|
||||
}
|
||||
|
||||
pub fn has_quiet_zone_before(&self, scale: f32, acceptIfAtFirstBar: Option<bool>) -> bool {
|
||||
(acceptIfAtFirstBar.unwrap_or(false) && self.isAtLastBar())
|
||||
|| Into::<f32>::into(self.data.0[self.count])
|
||||
>= Into::<f32>::into(self.sum(None)) * scale
|
||||
}
|
||||
// template<bool acceptIfAtFirstBar = false>
|
||||
// bool hasQuietZoneBefore(float scale) const
|
||||
// {
|
||||
// return (acceptIfAtFirstBar && isAtFirstBar()) || _data[-1] >= sum() * scale;
|
||||
// }
|
||||
|
||||
pub fn hasQuietZoneAfter(&self, scale: f32, acceptIfAtLastBar: Option<bool>) -> bool {
|
||||
(acceptIfAtLastBar.unwrap_or(true) && self.isAtLastBar())
|
||||
|| Into::<f32>::into(self.data.0[self.count])
|
||||
>= Into::<f32>::into(self.sum(None)) * scale
|
||||
}
|
||||
|
||||
// template<bool acceptIfAtLastBar = true>
|
||||
// bool hasQuietZoneAfter(float scale) const
|
||||
// {
|
||||
// return (acceptIfAtLastBar && isAtLastBar()) || _data[_size] >= sum() * scale;
|
||||
// }
|
||||
|
||||
pub fn subView(&self, offset: usize, size: Option<usize>) -> PatternView<'a> {
|
||||
let mut size = size.unwrap_or(0);
|
||||
if size == 0 {
|
||||
size = self.count - offset;
|
||||
} else if size < 0 {
|
||||
size += self.count - offset;
|
||||
}
|
||||
|
||||
PatternView {
|
||||
data: self.data,
|
||||
start: self.start + offset,
|
||||
count: size,
|
||||
current: self.current,
|
||||
}
|
||||
}
|
||||
|
||||
// PatternView subView(int offset, int size = 0) const
|
||||
// {
|
||||
// // if(std::abs(size) > count())
|
||||
// // printf("%d > %d\n", std::abs(size), _count);
|
||||
// // assert(std::abs(size) <= count());
|
||||
// if (size == 0)
|
||||
// size = _size - offset;
|
||||
// else if (size < 0)
|
||||
// size = _size - offset + size;
|
||||
// return {begin() + offset, std::max(size, 0), _base, _end};
|
||||
// }
|
||||
|
||||
pub fn shift(&mut self, n: usize) -> bool {
|
||||
self.current += n;
|
||||
!self.data.0.is_empty() && self.start + self.count <= (self.start + self.count)
|
||||
}
|
||||
|
||||
// bool shift(int n)
|
||||
// {
|
||||
// return _data && ((_data += n) + _size <= _end);
|
||||
// }
|
||||
|
||||
pub fn skipPair(&mut self) -> bool {
|
||||
self.shift(2)
|
||||
}
|
||||
|
||||
pub fn skipSymbol(&mut self) -> bool {
|
||||
self.shift(self.count)
|
||||
}
|
||||
|
||||
pub fn skipSingle(&mut self /* maxWidth: usize */) -> bool {
|
||||
self.shift(1) //&& _data[-1] <= maxWidth;
|
||||
}
|
||||
|
||||
pub fn extend(&mut self) {
|
||||
self.count = std::cmp::max(0, self.data.len() - (self.current + self.start))
|
||||
}
|
||||
|
||||
fn try_get_index(&self, index: isize) -> Option<PatternType> {
|
||||
if index.abs() > self.data.0.len() as isize {
|
||||
return None;
|
||||
}
|
||||
if index >= 0 {
|
||||
let fetch_spot = ((self.start + self.current) as isize + index) as usize;
|
||||
return Some(self.data.0[fetch_spot]);
|
||||
}
|
||||
if index.abs() > (self.start + self.current) as isize {
|
||||
return None;
|
||||
}
|
||||
let fetch_spot = ((self.start + self.current) as isize + index) as usize;
|
||||
Some(self.data.0[fetch_spot])
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> std::ops::Index<isize> for PatternView<'_> {
|
||||
type Output = PatternType;
|
||||
|
||||
fn index(&self, index: isize) -> &Self::Output {
|
||||
if self.count == self.data.len() {
|
||||
return &self.data[index.abs() as usize];
|
||||
}
|
||||
|
||||
if index > self.data.0.len() as isize {
|
||||
panic!("array index out of bounds")
|
||||
}
|
||||
if index >= 0 {
|
||||
let fetch_spot = ((self.start + self.current) as isize + index) as usize;
|
||||
return &self.data.0[fetch_spot];
|
||||
}
|
||||
if index.abs() > self.start as isize {
|
||||
panic!("array index out of bounds")
|
||||
}
|
||||
let fetch_spot = ((self.start + self.current) as isize + index) as usize;
|
||||
&self.data.0[fetch_spot]
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> std::ops::Index<usize> for PatternView<'_> {
|
||||
type Output = PatternType;
|
||||
|
||||
fn index(&self, index: usize) -> &Self::Output {
|
||||
if self.count == self.data.len() {
|
||||
return &self.data[index];
|
||||
}
|
||||
|
||||
if index > self.data.0.len() {
|
||||
panic!("array index out of bounds")
|
||||
}
|
||||
self.data.0.get(self.start + self.current + index).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> std::ops::Index<i32> for PatternView<'_> {
|
||||
type Output = PatternType;
|
||||
|
||||
fn index(&self, index: i32) -> &Self::Output {
|
||||
std::ops::Index::<isize>::index(self, index as isize)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Into<Vec<PatternType>> for &PatternView<'a> {
|
||||
fn into(self) -> Vec<PatternType> {
|
||||
let mut v = vec![PatternType::default(); self.count as usize];
|
||||
for i in 0..self.count {
|
||||
v[i] = self[i];
|
||||
}
|
||||
v
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief The BarAndSpace struct is a simple 2 element data structure to hold information about bar(s) and space(s).
|
||||
*
|
||||
* The operator[](int) can be used in combination with a PatternView
|
||||
*/
|
||||
#[derive(Default)]
|
||||
struct BarAndSpace<T: Default + std::cmp::PartialEq> {
|
||||
bar: T,
|
||||
space: T,
|
||||
}
|
||||
impl<T: Default + std::cmp::PartialEq> BarAndSpace<T> {
|
||||
pub fn isValid(&self) -> bool {
|
||||
self.bar != T::default() && self.space != T::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Default + std::cmp::PartialEq> std::ops::Index<usize> for BarAndSpace<T> {
|
||||
type Output = T;
|
||||
|
||||
fn index(&self, index: usize) -> &Self::Output {
|
||||
match index & 1 {
|
||||
0 => &self.bar,
|
||||
1 => &self.space,
|
||||
_ => panic!("Index out of range for BarAndSpace"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Default + std::cmp::PartialEq> std::ops::IndexMut<usize> for BarAndSpace<T> {
|
||||
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
|
||||
match index & 1 {
|
||||
0 => &mut self.bar,
|
||||
1 => &mut self.space,
|
||||
_ => panic!("Index out of range for BarAndSpace"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// using value_type = T;
|
||||
// T bar = {}, space = {};
|
||||
// // even index -> bar, odd index -> space
|
||||
// T& operator[](int i) { return reinterpret_cast<T*>(this)[i & 1]; }
|
||||
// T operator[](int i) const { return reinterpret_cast<const T*>(this)[i & 1]; }
|
||||
// bool isValid() const { return bar != T{} && space != T{}; }
|
||||
// };
|
||||
|
||||
type BarAndSpaceI = BarAndSpace<PatternType>;
|
||||
|
||||
/**
|
||||
* @brief FixedPattern describes a compile-time constant (start/stop) pattern.
|
||||
*
|
||||
* @param N number of bars/spaces
|
||||
* @param SUM sum over all N elements (size of pattern in modules)
|
||||
* @param IS_SPARCE whether or not the pattern contains '0's denoting 'wide' bars/spaces
|
||||
*/
|
||||
pub struct FixedPattern<const N: usize, const SUM: usize, const IS_SPARCE: bool = false> {
|
||||
data: [PatternType; N],
|
||||
}
|
||||
|
||||
impl<const N: usize, const SUM: usize, const IS_SPARCE: bool> Into<Pattern<N>>
|
||||
for FixedPattern<N, SUM, IS_SPARCE>
|
||||
{
|
||||
fn into(self) -> Pattern<N> {
|
||||
self.data
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize, const SUM: usize, const IS_SPARCE: bool> FixedPattern<N, SUM, IS_SPARCE> {
|
||||
pub const fn new(data: [PatternType; N]) -> Self {
|
||||
FixedPattern { data }
|
||||
}
|
||||
|
||||
pub fn with_reference(data: &[PatternType; N]) -> Self {
|
||||
FixedPattern { data: *data }
|
||||
}
|
||||
|
||||
fn as_slice(&self) -> &[PatternType] {
|
||||
&self.data
|
||||
}
|
||||
|
||||
fn size(&self) -> usize {
|
||||
N
|
||||
}
|
||||
|
||||
fn sums(&self) -> BarAndSpace<PatternType> {
|
||||
return BarAndSpaceSum::<N, PatternType, PatternType>(&self.data);
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize, const SUM: usize, const IS_SPARCE: bool> std::ops::Index<usize>
|
||||
for FixedPattern<N, SUM, IS_SPARCE>
|
||||
{
|
||||
type Output = PatternType;
|
||||
|
||||
fn index(&self, index: usize) -> &Self::Output {
|
||||
&self.data[index]
|
||||
}
|
||||
}
|
||||
|
||||
pub type FixedSparcePattern<const N: usize, const SUM: usize> = FixedPattern<N, SUM, true>;
|
||||
|
||||
// template <int N, int SUM, bool IS_SPARCE = false>
|
||||
// struct FixedPattern
|
||||
// {
|
||||
// using value_type = PatternRow::value_type;
|
||||
// value_type _data[N];
|
||||
// constexpr value_type operator[](int i) const noexcept { return _data[i]; }
|
||||
// constexpr const value_type* data() const noexcept { return _data; }
|
||||
// constexpr int size() const noexcept { return N; }
|
||||
// };
|
||||
|
||||
// template <int N, int SUM>
|
||||
// using FixedSparcePattern = FixedPattern<N, SUM, true>;
|
||||
|
||||
pub fn IsPattern<const E2E: bool, const LEN: usize, const SUM: usize, const SPARSE: bool>(
|
||||
view: &PatternView,
|
||||
pattern: &FixedPattern<LEN, SUM, SPARSE>,
|
||||
space_in_pixel: Option<f32>,
|
||||
min_quiet_zone: f32,
|
||||
module_size_ref: f32,
|
||||
// e2e: Option<bool>,
|
||||
) -> f32 {
|
||||
//let e2e = E2E; //e2e.unwrap_or(false);
|
||||
let mut module_size_ref = module_size_ref;
|
||||
|
||||
if E2E {
|
||||
//using float_t = double;
|
||||
let v_src: Vec<PatternType> = view.into();
|
||||
let widths = BarAndSpaceSum::<LEN, PatternType, f64>(&v_src);
|
||||
let sums = pattern.sums();
|
||||
let modSize: BarAndSpace<f64> = BarAndSpace {
|
||||
bar: widths[0] / sums[0] as f64,
|
||||
space: widths[1] / sums[1] as f64,
|
||||
};
|
||||
|
||||
let [m, M] = [
|
||||
f64::min(modSize[0], modSize[1]),
|
||||
f64::max(modSize[0], modSize[1]),
|
||||
];
|
||||
if (M > 4.0 * m) {
|
||||
// make sure module sizes of bars and spaces are not too far away from each other
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
if (min_quiet_zone != 0.0
|
||||
&& (space_in_pixel.unwrap_or_default()) < min_quiet_zone * modSize.space as f32)
|
||||
{
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let thr: BarAndSpace<f64> = BarAndSpace {
|
||||
bar: modSize[0] * 0.75 + 0.5,
|
||||
space: modSize[1] / (2.0 + f64::from(LEN < 6)) + 0.5,
|
||||
};
|
||||
|
||||
for x in 0..LEN {
|
||||
// for (int x = 0; x < LEN; ++x){
|
||||
if (view[x] as f64 - pattern[x] as f64 * modSize[x]).abs() > thr[x] {
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
let moduleSize: f64 = (modSize[0] + modSize[1]) / 2.0;
|
||||
return moduleSize as f32;
|
||||
}
|
||||
|
||||
let width = view.sum(Some(LEN));
|
||||
if SUM > LEN && Into::<usize>::into(width) < SUM {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let module_size: f32 = (Into::<f32>::into(width)) / (SUM as f32);
|
||||
|
||||
if min_quiet_zone != 0.0
|
||||
&& (space_in_pixel.unwrap_or(f32::MAX)) < min_quiet_zone * module_size - 1.0
|
||||
{
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
if module_size_ref == 0.0 {
|
||||
module_size_ref = module_size;
|
||||
}
|
||||
|
||||
let threshold = module_size_ref * (0.5 + (E2E as u8) as f32 * 0.25) + 0.5;
|
||||
|
||||
// the offset of 0.5 is to make the code less sensitive to quantization errors for small (near 1) module sizes.
|
||||
// TODO: review once we have upsampling in the binarizer in place.
|
||||
|
||||
for x in 0..LEN {
|
||||
if (Into::<f32>::into(view[x]) - Into::<f32>::into(pattern[x]) * module_size_ref).abs()
|
||||
> threshold
|
||||
{
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
module_size
|
||||
}
|
||||
|
||||
pub fn IsRightGuard<const N: usize, const SUM: usize, const IS_SPARCE: bool>(
|
||||
view: &PatternView,
|
||||
pattern: &FixedPattern<N, SUM, IS_SPARCE>,
|
||||
minQuietZone: f32,
|
||||
moduleSizeRef: f32,
|
||||
) -> bool {
|
||||
let spaceInPixel = if view.isAtLastBar() {
|
||||
None
|
||||
} else {
|
||||
Some(view.end().unwrap().into())
|
||||
};
|
||||
|
||||
const E2E: bool = false;
|
||||
|
||||
IsPattern::<E2E, N, SUM, IS_SPARCE>(
|
||||
view,
|
||||
pattern,
|
||||
spaceInPixel,
|
||||
minQuietZone,
|
||||
moduleSizeRef,
|
||||
// None,
|
||||
) != 0.0
|
||||
}
|
||||
|
||||
pub fn FindLeftGuardBy<'a, const LEN: usize, Pred: Fn(&PatternView, Option<f32>) -> bool>(
|
||||
view: PatternView<'a>,
|
||||
minSize: usize,
|
||||
isGuard: Pred,
|
||||
) -> Result<PatternView<'a>> {
|
||||
const PREV_IDX: isize = -1;
|
||||
|
||||
if view.size() < minSize {
|
||||
return Err(Exceptions::ILLEGAL_STATE);
|
||||
}
|
||||
|
||||
let mut window = view.subView(0, Some(LEN));
|
||||
if window.isAtFirstBar() && isGuard(&window, Some(f32::MAX)) {
|
||||
return Ok(window);
|
||||
}
|
||||
let end = Into::<usize>::into(view.end().ok_or(Exceptions::INDEX_OUT_OF_BOUNDS)?) - minSize;
|
||||
while (window.start + window.current) < end {
|
||||
let prev = if let Some(v) = window.try_get_index(PREV_IDX) {
|
||||
Some(v as f32)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if isGuard(&window, prev) {
|
||||
return Ok(window);
|
||||
}
|
||||
|
||||
window.skipPair();
|
||||
}
|
||||
|
||||
Err(Exceptions::ILLEGAL_STATE)
|
||||
}
|
||||
|
||||
pub fn FindLeftGuard<'a, const LEN: usize, const SUM: usize, const IS_SPARCE: bool>(
|
||||
view: PatternView<'a>,
|
||||
minSize: usize,
|
||||
pattern: &FixedPattern<LEN, SUM, IS_SPARCE>,
|
||||
minQuietZone: f32,
|
||||
) -> Result<PatternView<'a>> {
|
||||
FindLeftGuardBy::<LEN, _>(view, std::cmp::max(minSize, LEN), |window, spaceInPixel| {
|
||||
// perform a fast plausability test for 1:1:3:1:1 pattern
|
||||
// dbg!(window[2], 2 as PatternType * std::cmp::max(window[0], window[4]));
|
||||
// dbg!(window[2] < std::cmp::max(window[1], window[3]));
|
||||
if window[2] < 2 as PatternType * std::cmp::max(window[0], window[4])
|
||||
|| window[2] < std::cmp::max(window[1], window[3])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return IsPattern::<false, LEN, SUM, IS_SPARCE>(
|
||||
window,
|
||||
pattern,
|
||||
spaceInPixel,
|
||||
minQuietZone,
|
||||
0.0,
|
||||
) != 0.0;
|
||||
})
|
||||
}
|
||||
|
||||
pub fn NormalizedE2EPattern<'a, const LEN: usize, const LEN_MINUS_2: usize, const SUM: usize>(
|
||||
view: &'a PatternView,
|
||||
) -> [PatternType; LEN_MINUS_2] {
|
||||
let moduleSize: f32 = Into::<f32>::into(view.sum(Some(LEN))) / SUM as f32;
|
||||
|
||||
let mut e2e = [PatternType::default(); LEN_MINUS_2];
|
||||
|
||||
for i in 0..LEN_MINUS_2 {
|
||||
let v: f32 = (Into::<f32>::into(view[i]) + Into::<f32>::into(view[i + 1])) / moduleSize;
|
||||
e2e[i] = (v + 0.5) as PatternType;
|
||||
}
|
||||
|
||||
e2e
|
||||
}
|
||||
|
||||
pub fn NormalizedPattern<'a, const LEN: usize, const SUM: usize>(
|
||||
view: &'a PatternView,
|
||||
) -> Result<[PatternType; LEN]> {
|
||||
let moduleSize: f32 = (Into::<usize>::into(view.sum(Some(LEN))) / SUM) as f32;
|
||||
let mut err = SUM as isize;
|
||||
let mut is = [PatternType::default(); LEN];
|
||||
let mut rs = [0.0; LEN];
|
||||
for i in 0..LEN {
|
||||
// for (int i = 0; i < LEN; i++) {
|
||||
let v: f32 = Into::<f32>::into(view[i]) / moduleSize;
|
||||
is[i] = (v + 0.5) as PatternType;
|
||||
rs[i] = v - Into::<f32>::into(is[i]);
|
||||
err -= Into::<usize>::into(is[i]) as isize;
|
||||
}
|
||||
|
||||
if err.abs() > 1 {
|
||||
return Err(Exceptions::NOT_FOUND);
|
||||
}
|
||||
|
||||
if err != 0 {
|
||||
// let mi =if err > 0 { std::max_element(std::begin(rs), std::end(rs)) - std::begin(rs)}
|
||||
// else {std::min_element(std::begin(rs), std::end(rs)) - std::begin(rs)};
|
||||
let mi = if err > 0 {
|
||||
rs.iter()
|
||||
.max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
|
||||
} else {
|
||||
rs.iter()
|
||||
.min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
|
||||
};
|
||||
let mi = mi.ok_or(Exceptions::ILLEGAL_STATE)?;
|
||||
is[*mi as usize] += err as PatternType;
|
||||
rs[*mi as usize] -= err as f32;
|
||||
}
|
||||
|
||||
Ok(is)
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
enum Color {
|
||||
White = 0,
|
||||
Black = 1,
|
||||
}
|
||||
|
||||
impl<T: Into<PatternType>> From<T> for Color {
|
||||
fn from(value: T) -> Self {
|
||||
match value.into() {
|
||||
0 => Color::White,
|
||||
_ => Color::Black,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn GetPatternRowTP(matrix: &BitMatrix, r: u32, pr: &mut PatternRow, transpose: bool) {
|
||||
let row = if transpose {
|
||||
matrix.getCol(r)
|
||||
} else {
|
||||
matrix.getRow(r)
|
||||
};
|
||||
|
||||
let pixel_states: Vec<bool> = row.into();
|
||||
|
||||
GetPatternRow(&pixel_states, pr)
|
||||
}
|
||||
|
||||
pub fn GetPatternRow<T: Into<PatternType> + Copy + Default + From<T>>(
|
||||
b_row: &[T],
|
||||
p_row: &mut PatternRow,
|
||||
) {
|
||||
p_row.0.clear();
|
||||
|
||||
if Color::from(p_row.0.first().copied().unwrap_or_default()) == Color::Black {
|
||||
// first
|
||||
p_row.0.push(0);
|
||||
}
|
||||
|
||||
let mut current_color = Color::from(p_row.0.first().copied().unwrap_or_default()); //if p_row.0.first().copied().unwrap_or_default() == 1 {Color::Black} else { Color::White};
|
||||
let mut count = 0;
|
||||
|
||||
for bit in b_row.iter() {
|
||||
let this_color = Color::from(*bit);
|
||||
|
||||
if current_color != this_color {
|
||||
p_row.0.push(count);
|
||||
count = 0;
|
||||
|
||||
current_color = this_color;
|
||||
}
|
||||
|
||||
count += 1;
|
||||
}
|
||||
|
||||
// dbg!(&p_row.0);
|
||||
|
||||
if count != 0 {
|
||||
p_row.0.push(count);
|
||||
}
|
||||
|
||||
if current_color == Color::Black {
|
||||
p_row.0.push(0);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::common::cpp_essentials::PatternType;
|
||||
|
||||
use super::{GetPatternRow, PatternRow, PatternView};
|
||||
const N: usize = 33;
|
||||
|
||||
#[test]
|
||||
fn all_white() {
|
||||
for s in 1..=N {
|
||||
// for (int s = 1; s <= N; ++s) {
|
||||
let t_in: Vec<PatternType> = vec![0; s];
|
||||
// std::vector<uint8_t> in(s, 0);
|
||||
let mut pr = PatternRow::default();
|
||||
GetPatternRow(&t_in, &mut pr);
|
||||
|
||||
assert_eq!(pr.0.len(), 1);
|
||||
assert_eq!(pr.0[0], s as PatternType);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_black() {
|
||||
for s in 1..=N {
|
||||
// for (int s = 1; s <= N; ++s) {
|
||||
let t_in: Vec<PatternType> = vec![0xff; s];
|
||||
let mut pr = PatternRow::default();
|
||||
GetPatternRow(&t_in, &mut pr);
|
||||
|
||||
assert_eq!(pr.0.len(), 3);
|
||||
assert_eq!(pr.0[0], 0);
|
||||
assert_eq!(pr.0[1], s as PatternType);
|
||||
assert_eq!(pr.0[2], 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn black_white() {
|
||||
for s in 1..=N {
|
||||
// for (int s = 1; s <= N; ++s) {
|
||||
let mut t_in: Vec<PatternType> = vec![0; N];
|
||||
t_in[..s].copy_from_slice(&vec![1; s]);
|
||||
// std::fill_n(in.data(), s, 0xff);
|
||||
let mut pr = PatternRow::default();
|
||||
GetPatternRow(&t_in, &mut pr);
|
||||
|
||||
assert_eq!(pr.0.len(), 3);
|
||||
assert_eq!(pr.0[0], 0);
|
||||
assert_eq!(pr.0[1], s as PatternType);
|
||||
assert_eq!(pr.0[2], (N - s) as PatternType);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn white_black() {
|
||||
for s in 0..N {
|
||||
// for (int s = 0; s < N; ++s) {
|
||||
let mut t_in: Vec<PatternType> = vec![0xff; N];
|
||||
t_in[..s].copy_from_slice(&vec![0; s]);
|
||||
let mut pr = PatternRow::default();
|
||||
GetPatternRow(&t_in, &mut pr);
|
||||
|
||||
assert_eq!(pr.0.len(), 3);
|
||||
assert_eq!(pr.0[0], s as PatternType);
|
||||
assert_eq!(pr.0[1], (N - s) as PatternType);
|
||||
assert_eq!(pr.0[2], 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_pattern_view() {
|
||||
let mut p_row = PatternRow::default();
|
||||
GetPatternRow(
|
||||
&[
|
||||
0_u16, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,
|
||||
],
|
||||
&mut p_row,
|
||||
);
|
||||
|
||||
let mut pv = PatternView::new(&p_row);
|
||||
|
||||
assert_eq!(pv.data().0, p_row.0);
|
||||
|
||||
assert_eq!(pv[0], 1_u16);
|
||||
assert_eq!(pv[1], 1_u16);
|
||||
assert_eq!(pv[4], 2_u16);
|
||||
assert_eq!(pv[7], 6_u16);
|
||||
|
||||
assert_eq!(pv.index(), 0);
|
||||
assert!(pv.shift(1));
|
||||
assert_eq!(pv.index(), 1);
|
||||
assert!(pv.skipPair());
|
||||
assert_eq!(pv.index(), 3);
|
||||
}
|
||||
}
|
||||
236
src/common/cpp_essentials/regression_line.rs
Normal file
@@ -0,0 +1,236 @@
|
||||
use crate::common::Result;
|
||||
use crate::{Exceptions, Point};
|
||||
|
||||
use super::RegressionLineTrait;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RegressionLine {
|
||||
points: Vec<Point>,
|
||||
direction_inward: Point,
|
||||
pub(super) a: f32,
|
||||
pub(super) b: f32,
|
||||
pub(super) c: f32,
|
||||
// std::vector<PointF> _points;
|
||||
// PointF _directionInward;
|
||||
// PointF::value_t a = NAN, b = NAN, c = NAN;
|
||||
}
|
||||
|
||||
impl Default for RegressionLine {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
points: Default::default(),
|
||||
direction_inward: Default::default(),
|
||||
a: f32::NAN,
|
||||
b: f32::NAN,
|
||||
c: f32::NAN,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RegressionLineTrait for RegressionLine {
|
||||
fn points(&self) -> &[Point] {
|
||||
&self.points
|
||||
}
|
||||
|
||||
fn length(&self) -> u32 {
|
||||
if self.points.len() >= 2 {
|
||||
Point::distance(*self.points.first().unwrap(), *self.points.last().unwrap()) as u32
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
fn isValid(&self) -> bool {
|
||||
!self.a.is_nan()
|
||||
}
|
||||
|
||||
fn normal(&self) -> Point {
|
||||
if self.isValid() {
|
||||
Point {
|
||||
x: self.a,
|
||||
y: self.b,
|
||||
}
|
||||
} else {
|
||||
self.direction_inward
|
||||
}
|
||||
}
|
||||
|
||||
fn signedDistance(&self, p: Point) -> f32 {
|
||||
Point::dot(self.normal(), p) - self.c
|
||||
}
|
||||
|
||||
fn distance_single(&self, p: Point) -> f32 {
|
||||
(self.signedDistance(p)).abs()
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.points.clear();
|
||||
self.direction_inward = Point { x: 0.0, y: 0.0 };
|
||||
self.a = f32::NAN;
|
||||
self.b = f32::NAN;
|
||||
self.c = f32::NAN;
|
||||
}
|
||||
|
||||
fn add(&mut self, p: Point) -> Result<()> {
|
||||
if self.direction_inward == Point::default() {
|
||||
return Err(Exceptions::ILLEGAL_STATE);
|
||||
}
|
||||
self.points.push(p);
|
||||
if self.points.len() == 1 {
|
||||
self.c = Point::dot(self.normal(), p);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn pop_back(&mut self) {
|
||||
self.points.pop();
|
||||
}
|
||||
|
||||
fn setDirectionInward(&mut self, d: Point) {
|
||||
self.direction_inward = Point::normalized(d);
|
||||
}
|
||||
|
||||
fn evaluate_max_distance(
|
||||
&mut self,
|
||||
maxSignedDist: Option<f64>,
|
||||
updatePoints: Option<bool>,
|
||||
) -> bool {
|
||||
let maxSignedDist = if let Some(m) = maxSignedDist { m } else { -1.0 };
|
||||
let updatePoints = if let Some(u) = updatePoints { u } else { false };
|
||||
|
||||
let mut ret = self.evaluateSelf();
|
||||
if maxSignedDist > 0.0 {
|
||||
let mut points = self.points.clone();
|
||||
loop {
|
||||
let old_points_size = points.len();
|
||||
// remove points that are further 'inside' than maxSignedDist or further 'outside' than 2 x maxSignedDist
|
||||
// auto end = std::remove_if(points.begin(), points.end(), [this, maxSignedDist](auto p) {
|
||||
// auto sd = this->signedDistance(p);
|
||||
// return sd > maxSignedDist || sd < -2 * maxSignedDist;
|
||||
// });
|
||||
// points.erase(end, points.end());
|
||||
points.retain(|&p| {
|
||||
let sd = self.signedDistance(p) as f64;
|
||||
!(sd > maxSignedDist || sd < -2.0 * maxSignedDist)
|
||||
});
|
||||
if old_points_size == points.len() {
|
||||
break;
|
||||
}
|
||||
// #ifdef PRINT_DEBUG
|
||||
// printf("removed %zu points\n", old_points_size - points.size());
|
||||
// #endif
|
||||
ret = self.evaluate(&points);
|
||||
}
|
||||
|
||||
if updatePoints {
|
||||
self.points = points;
|
||||
}
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
fn isHighRes(&self) -> bool {
|
||||
let Some(mut min) = self.points.first().copied() else { return false };
|
||||
let Some(mut max) = self.points.first().copied() else { return false };
|
||||
for p in &self.points {
|
||||
min.x = f32::min(min.x, p.x);
|
||||
min.y = f32::min(min.y, p.y);
|
||||
max.x = f32::max(max.x, p.x);
|
||||
max.y = f32::max(max.y, p.y);
|
||||
}
|
||||
let diff = max - min;
|
||||
let len = diff.maxAbsComponent();
|
||||
let steps = f32::min(diff.x.abs(), diff.y.abs());
|
||||
// due to aliasing we get bad extrapolations if the line is short and too close to vertical/horizontal
|
||||
steps > 2.0 || len > 50.0
|
||||
}
|
||||
|
||||
fn evaluate(&mut self, points: &[Point]) -> bool {
|
||||
let mean = points.iter().sum::<Point>() / points.len() as f32;
|
||||
|
||||
let mut sumXX = 0.0;
|
||||
let mut sumYY = 0.0;
|
||||
let mut sumXY = 0.0;
|
||||
for p in points {
|
||||
// for (auto p = begin; p != end; ++p) {
|
||||
let d = *p - mean;
|
||||
sumXX += d.x * d.x;
|
||||
sumYY += d.y * d.y;
|
||||
sumXY += d.x * d.y;
|
||||
}
|
||||
if sumYY >= sumXX {
|
||||
let l = (sumYY * sumYY + sumXY * sumXY).sqrt();
|
||||
self.a = sumYY / l;
|
||||
self.b = -sumXY / l;
|
||||
} else {
|
||||
let l = (sumXX * sumXX + sumXY * sumXY).sqrt();
|
||||
self.a = sumXY / l;
|
||||
self.b = -sumXX / l;
|
||||
}
|
||||
if Point::dot(self.direction_inward, self.normal()) < 0.0 {
|
||||
// if (dot(_directionInward, normal()) < 0) {
|
||||
self.a = -self.a;
|
||||
self.b = -self.b;
|
||||
}
|
||||
self.c = Point::dot(self.normal(), mean); // (a*mean.x + b*mean.y);
|
||||
Point::dot(self.direction_inward, self.normal()) > 0.5
|
||||
// angle between original and new direction is at most 60 degree
|
||||
}
|
||||
|
||||
fn evaluateSelf(&mut self) -> bool {
|
||||
let mean = self.points.iter().sum::<Point>() / self.points.len() as f32;
|
||||
|
||||
let mut sumXX = 0.0;
|
||||
let mut sumYY = 0.0;
|
||||
let mut sumXY = 0.0;
|
||||
for p in &self.points {
|
||||
// for (auto p = begin; p != end; ++p) {
|
||||
let d = *p - mean;
|
||||
sumXX += d.x * d.x;
|
||||
sumYY += d.y * d.y;
|
||||
sumXY += d.x * d.y;
|
||||
}
|
||||
if sumYY >= sumXX {
|
||||
let l = (sumYY * sumYY + sumXY * sumXY).sqrt();
|
||||
self.a = sumYY / l;
|
||||
self.b = -sumXY / l;
|
||||
} else {
|
||||
let l = (sumXX * sumXX + sumXY * sumXY).sqrt();
|
||||
self.a = sumXY / l;
|
||||
self.b = -sumXX / l;
|
||||
}
|
||||
if Point::dot(self.direction_inward, self.normal()) < 0.0 {
|
||||
// if (dot(_directionInward, normal()) < 0) {
|
||||
self.a = -self.a;
|
||||
self.b = -self.b;
|
||||
}
|
||||
self.c = Point::dot(self.normal(), mean); // (a*mean.x + b*mean.y);
|
||||
Point::dot(self.direction_inward, self.normal()) > 0.5
|
||||
// angle between original and new direction is at most 60 degree
|
||||
}
|
||||
|
||||
fn a(&self) -> f32 {
|
||||
self.a
|
||||
}
|
||||
|
||||
fn b(&self) -> f32 {
|
||||
self.b
|
||||
}
|
||||
|
||||
fn c(&self) -> f32 {
|
||||
self.c
|
||||
}
|
||||
}
|
||||
|
||||
impl RegressionLine {
|
||||
pub fn with_two_points(point1: Point, point2: Point) -> Self {
|
||||
let mut new_rl = RegressionLine::default();
|
||||
new_rl.evaluate(&[point1, point2]);
|
||||
new_rl
|
||||
}
|
||||
pub fn with_point_slice(points: &[Point]) -> Self {
|
||||
let mut new_rl = RegressionLine::default();
|
||||
new_rl.evaluate(points);
|
||||
new_rl
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::common::Result;
|
||||
use crate::Point;
|
||||
use crate::{point, Point};
|
||||
|
||||
pub trait RegressionLine {
|
||||
pub trait RegressionLineTrait {
|
||||
// points: Vec<Point>,
|
||||
// direction_inward: Point,
|
||||
|
||||
@@ -11,8 +11,20 @@ pub trait RegressionLine {
|
||||
// PointF _directionInward;
|
||||
// PointF::value_t a = NAN, b = NAN, c = NAN;
|
||||
|
||||
// fn intersect<T: RegressionLine, T2: RegressionLine>(&self, l1: &T, l2: &T2)
|
||||
// -> Point;
|
||||
fn intersect<T: RegressionLineTrait, T2: RegressionLineTrait>(
|
||||
l1: &T,
|
||||
l2: &T2,
|
||||
) -> Option<Point> {
|
||||
if !(l1.isValid() && l2.isValid()) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let d = l1.a() * l2.b() - l1.b() * l2.a();
|
||||
let x = (l1.c() * l2.b() - l1.b() * l2.c()) / d;
|
||||
let y = (l1.a() * l2.c() - l1.c() * l2.a()) / d;
|
||||
|
||||
Some(point(x, y))
|
||||
}
|
||||
|
||||
// fn evaluate_begin_end(&self, begin: Point, end: Point) -> bool;// {
|
||||
// {
|
||||
@@ -131,4 +143,7 @@ pub trait RegressionLine {
|
||||
// // due to aliasing we get bad extrapolations if the line is short and too close to vertical/horizontal
|
||||
// return steps > 2 || len > 50;
|
||||
// }
|
||||
fn a(&self) -> f32;
|
||||
fn b(&self) -> f32;
|
||||
fn c(&self) -> f32;
|
||||
}
|
||||
16
src/common/cpp_essentials/structured_append.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct StructuredAppendInfo {
|
||||
pub index: i32, // -1;
|
||||
pub count: i32, // = -1;
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
impl Default for StructuredAppendInfo {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
index: -1,
|
||||
count: -1,
|
||||
id: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
68
src/common/cpp_essentials/util.rs
Normal file
@@ -0,0 +1,68 @@
|
||||
use crate::common::Result;
|
||||
use crate::{Exceptions, Point};
|
||||
|
||||
use super::{Direction, RegressionLineTrait};
|
||||
|
||||
#[inline(always)]
|
||||
pub fn intersect<T: RegressionLineTrait, T2: RegressionLineTrait>(
|
||||
l1: &T,
|
||||
l2: &T2,
|
||||
) -> Result<Point> {
|
||||
if !(l1.isValid() && l2.isValid()) {
|
||||
return Err(Exceptions::ILLEGAL_STATE);
|
||||
}
|
||||
let d = l1.a() * l2.b() - l1.b() * l2.a();
|
||||
let x = (l1.c() * l2.b() - l1.b() * l2.c()) / d;
|
||||
let y = (l1.a() * l2.c() - l1.c() * l2.a()) / d;
|
||||
Ok(Point { x, y })
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[inline(always)]
|
||||
pub fn opposite(dir: Direction) -> Direction {
|
||||
if dir == Direction::Left {
|
||||
Direction::Right
|
||||
} else {
|
||||
Direction::Left
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn UpdateMinMax<T: Ord + Copy>(min: &mut T, max: &mut T, val: T) {
|
||||
*min = std::cmp::min(*min, val);
|
||||
*max = std::cmp::max(*max, val);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn UpdateMinMaxFloat(min: &mut f64, max: &mut f64, val: f64) {
|
||||
*min = f64::min(*min, val);
|
||||
*max = f64::max(*max, val);
|
||||
}
|
||||
|
||||
// template<typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
|
||||
pub fn ToString<T: Into<usize>>(val: T, len: usize) -> Result<String> {
|
||||
let mut len = len as isize;
|
||||
let val = val.into();
|
||||
let mut val = val as isize;
|
||||
|
||||
let mut result = vec!['0'; len as usize];
|
||||
len -= 1;
|
||||
// std::string result(len--, '0');
|
||||
if val < 0 {
|
||||
return Err(Exceptions::format_with("Invalid value"));
|
||||
}
|
||||
while len >= 0 && val != 0 {
|
||||
result[len as usize] = char::from(b'0' + (val % 10) as u8);
|
||||
// result.replace_range((len as usize)..(len as usize), &char::from(b'0' + (val % 10) as u8).to_string());
|
||||
|
||||
len -= 1;
|
||||
val /= 10;
|
||||
}
|
||||
// for (; len >= 0 && val != 0; --len, val /= 10) {
|
||||
// result[len] = '0' + val % 10;}
|
||||
if val != 0 {
|
||||
return Err(Exceptions::format_with("Invalid value"));
|
||||
}
|
||||
|
||||
Ok(result.iter().collect())
|
||||
}
|
||||
@@ -19,9 +19,9 @@
|
||||
// import com.google.zxing.NotFoundException;
|
||||
|
||||
use crate::common::Result;
|
||||
use crate::{Exceptions, Point};
|
||||
use crate::{point, Exceptions, Point};
|
||||
|
||||
use super::{BitMatrix, GridSampler, PerspectiveTransform, Quadrilateral, SamplerControl};
|
||||
use super::{BitMatrix, GridSampler, SamplerControl};
|
||||
|
||||
/**
|
||||
* @author Sean Owen
|
||||
@@ -30,87 +30,70 @@ use super::{BitMatrix, GridSampler, PerspectiveTransform, Quadrilateral, Sampler
|
||||
pub struct DefaultGridSampler;
|
||||
|
||||
impl GridSampler for DefaultGridSampler {
|
||||
fn sample_grid_detailed(
|
||||
&self,
|
||||
image: &BitMatrix,
|
||||
dimensionX: u32,
|
||||
dimensionY: u32,
|
||||
dst: Quadrilateral,
|
||||
src: Quadrilateral,
|
||||
) -> Result<BitMatrix> {
|
||||
let transform = PerspectiveTransform::quadrilateralToQuadrilateral(dst, src)?;
|
||||
|
||||
self.sample_grid(
|
||||
image,
|
||||
dimensionX,
|
||||
dimensionY,
|
||||
&[SamplerControl::new(dimensionX, dimensionY, transform)],
|
||||
)
|
||||
}
|
||||
|
||||
fn sample_grid(
|
||||
&self,
|
||||
image: &BitMatrix,
|
||||
dimensionX: u32,
|
||||
dimensionY: u32,
|
||||
controls: &[SamplerControl],
|
||||
) -> Result<BitMatrix> {
|
||||
if dimensionX == 0 || dimensionY == 0 {
|
||||
) -> Result<(BitMatrix, [Point; 4])> {
|
||||
if dimensionX <= 0 || dimensionY <= 0 {
|
||||
return Err(Exceptions::NOT_FOUND);
|
||||
}
|
||||
let mut bits = BitMatrix::new(dimensionX, dimensionY)?;
|
||||
let mut points = vec![Point::default(); 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.5;
|
||||
let mut x = 0;
|
||||
while x < max {
|
||||
// for (int x = 0; x < max; x += 2) {
|
||||
points[x].x = (x as f32) + 0.5;
|
||||
points[x].y = i_value;
|
||||
x += 1;
|
||||
}
|
||||
controls
|
||||
.first()
|
||||
.unwrap()
|
||||
.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] as u32 >= image.getWidth() || points[x + 1] as u32 >= image.getHeight()
|
||||
// {
|
||||
// return Err(Exceptions::notFound(
|
||||
// "index out of bounds, see documentation in file for explanation".to_owned(),
|
||||
// ));
|
||||
// }
|
||||
if image
|
||||
.try_get(points[x].x as u32, points[x].y as u32)
|
||||
.ok_or(Exceptions::not_found_with(
|
||||
"index out of bounds, see documentation in file for explanation",
|
||||
))?
|
||||
{
|
||||
// Black(-ish) pixel
|
||||
bits.set(x as u32, y);
|
||||
|
||||
for SamplerControl { p0, p1, transform } in controls {
|
||||
// To deal with remaining examples (see #251 and #267) of "numercial instabilities" that have not been
|
||||
// prevented with the Quadrilateral.h:IsConvex() check, we check for all boundary points of the grid to
|
||||
// be inside.
|
||||
let isInside =
|
||||
|p: Point| -> bool { image.is_in(transform.transform_point(p.centered())) };
|
||||
for y in (p0.y as i32)..(p1.y as i32) {
|
||||
// for (int y = y0; y < y1; ++y)
|
||||
if !isInside(point(p0.x, y as f32)) || !isInside(point(p1.x - 1.0, y as f32)) {
|
||||
return Err(Exceptions::NOT_FOUND);
|
||||
}
|
||||
}
|
||||
for x in (p0.x as i32)..(p1.x as i32) {
|
||||
// for (int x = x0; x < x1; ++x)
|
||||
if !isInside(point(x as f32, p0.y)) || !isInside(point(x as f32, p1.y - 1.0)) {
|
||||
return Err(Exceptions::NOT_FOUND);
|
||||
}
|
||||
x += 1;
|
||||
}
|
||||
// } 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();
|
||||
// }
|
||||
}
|
||||
Ok(bits)
|
||||
|
||||
let mut bits = BitMatrix::new(dimensionX, dimensionY)?;
|
||||
for SamplerControl { p0, p1, transform } in controls {
|
||||
// for (auto&& [x0, x1, y0, y1, mod2Pix] : rois) {
|
||||
for y in (p0.y as i32)..(p1.y as i32) {
|
||||
// for (int y = y0; y < y1; ++y)
|
||||
for x in (p0.x as i32)..(p1.x as i32) {
|
||||
// for (int x = x0; x < x1; ++x) {
|
||||
let p = transform.transform_point(Point::from((x, y)).centered()); //mod2Pix(centered(PointI{x, y}));
|
||||
|
||||
if image.get_point(p) {
|
||||
bits.set(x as u32, y as u32);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// dbg!(image.to_string());
|
||||
// dbg!(bits.to_string());
|
||||
|
||||
let projectCorner = |p: Point| -> Point {
|
||||
for SamplerControl { p0, p1, transform } in controls {
|
||||
if p0.x <= p.x && p.x <= p1.x && p0.y <= p.y && p.y <= p1.y {
|
||||
return transform.transform_point(p) + point(0.5, 0.5);
|
||||
}
|
||||
}
|
||||
Point::default()
|
||||
};
|
||||
|
||||
let tl = projectCorner(Point::default());
|
||||
let tr = projectCorner(Point::from((dimensionX, 0)));
|
||||
let bl = projectCorner(Point::from((dimensionX, dimensionY)));
|
||||
let br = projectCorner(Point::from((0, dimensionX)));
|
||||
|
||||
Ok((bits, [tl, tr, bl, br]))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use crate::Exceptions;
|
||||
|
||||
use super::CharacterSet;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
use crate::common::Result;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum Eci {
|
||||
Unknown = -1,
|
||||
Cp437 = 2, // obsolete
|
||||
@@ -44,6 +48,19 @@ impl Eci {
|
||||
pub fn can_encode(self) -> bool {
|
||||
(self as i32) >= 899
|
||||
}
|
||||
|
||||
pub fn try_from_i32(value: i32) -> Result<Self> {
|
||||
let v = Self::from(value);
|
||||
if v == Eci::Unknown {
|
||||
Err(Exceptions::ILLEGAL_ARGUMENT)
|
||||
} else {
|
||||
Ok(v)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_from_u32(value: u32) -> Result<Self> {
|
||||
Self::try_from_i32(value as i32)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for Eci {
|
||||
|
||||
@@ -21,21 +21,28 @@
|
||||
// import java.nio.charset.Charset;
|
||||
// import java.nio.charset.StandardCharsets;
|
||||
|
||||
use std::fmt;
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
fmt::{self, Display},
|
||||
};
|
||||
|
||||
use super::{CharacterSet, Eci};
|
||||
use crate::{pdf417::decoder::ec, BarcodeFormat};
|
||||
|
||||
use super::{CharacterSet, Eci, StringUtils};
|
||||
|
||||
/**
|
||||
* Class that converts a sequence of ECIs and bytes into a string
|
||||
*
|
||||
* @author Alex Geller
|
||||
*/
|
||||
#[derive(Default)]
|
||||
#[derive(Default, PartialEq, Eq, Debug, Clone)]
|
||||
pub struct ECIStringBuilder {
|
||||
is_eci: bool,
|
||||
pub has_eci: bool,
|
||||
eci_result: Option<String>,
|
||||
bytes: Vec<u8>,
|
||||
eci_positions: Vec<(Eci, usize, usize)>, // (Eci, start, end)
|
||||
pub symbology: SymbologyIdentifier,
|
||||
eci_list: HashSet<Eci>,
|
||||
}
|
||||
|
||||
impl ECIStringBuilder {
|
||||
@@ -44,7 +51,9 @@ impl ECIStringBuilder {
|
||||
eci_result: None,
|
||||
bytes: Vec::with_capacity(initial_capacity),
|
||||
eci_positions: Vec::default(),
|
||||
is_eci: false,
|
||||
has_eci: false,
|
||||
symbology: SymbologyIdentifier::default(),
|
||||
eci_list: HashSet::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,26 +116,55 @@ impl ECIStringBuilder {
|
||||
*/
|
||||
pub fn append_eci(&mut self, eci: Eci) {
|
||||
self.eci_result = None;
|
||||
if !self.is_eci && eci != Eci::ISO8859_1 {
|
||||
self.is_eci = true;
|
||||
|
||||
if !self.has_eci && eci != Eci::ISO8859_1 {
|
||||
self.has_eci = true;
|
||||
}
|
||||
|
||||
if self.is_eci {
|
||||
if self.has_eci {
|
||||
if let Some(last) = self.eci_positions.last_mut() {
|
||||
last.2 = self.bytes.len()
|
||||
}
|
||||
|
||||
self.eci_positions.push((eci, self.bytes.len(), 0));
|
||||
|
||||
self.eci_list.insert(eci);
|
||||
|
||||
if self.eci_list.len() == 1 && (self.eci_list.contains(&Eci::Unknown)) {
|
||||
self.has_eci = false;
|
||||
self.eci_positions.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Change the current encoding characterset, finding an eci to do so
|
||||
pub fn switch_encoding(&mut self, charset: CharacterSet, is_eci: bool) {
|
||||
//self.append_eci(Eci::from(charset))
|
||||
if is_eci && !self.has_eci {
|
||||
self.eci_positions.clear();
|
||||
}
|
||||
if is_eci || !self.has_eci
|
||||
//{self.eci_positions.push_back({eci, Size(bytes)});}
|
||||
{
|
||||
// self.append_eci(Eci::from(charset))
|
||||
if let Some(last) = self.eci_positions.last_mut() {
|
||||
last.2 = self.bytes.len()
|
||||
}
|
||||
|
||||
self.eci_positions
|
||||
.push((Eci::from(charset), self.bytes.len(), 0));
|
||||
}
|
||||
|
||||
self.has_eci |= is_eci;
|
||||
}
|
||||
|
||||
/// Finishes encoding anything in the buffer using the current ECI and resets.
|
||||
///
|
||||
/// This function can panic
|
||||
pub fn encodeCurrentBytesIfAny(&self) -> String {
|
||||
let mut encoded_string = String::with_capacity(self.bytes.len());
|
||||
// First encode the first set
|
||||
let (_, end, _) =
|
||||
let (_eci, end, _) =
|
||||
*self
|
||||
.eci_positions
|
||||
.first()
|
||||
@@ -136,6 +174,10 @@ impl ECIStringBuilder {
|
||||
&Self::encode_segment(&self.bytes[0..end], Eci::ISO8859_1).unwrap_or_default(),
|
||||
);
|
||||
|
||||
if end == self.bytes.len() {
|
||||
return encoded_string;
|
||||
}
|
||||
|
||||
// If there are more sets, encode each of them in turn
|
||||
for (eci, eci_start, eci_end) in &self.eci_positions {
|
||||
// let (_,end) = *self.eci_positions.first().unwrap_or(&(*eci, self.bytes.len()));
|
||||
@@ -154,20 +196,41 @@ impl ECIStringBuilder {
|
||||
}
|
||||
|
||||
fn encode_segment(bytes: &[u8], eci: Eci) -> Option<String> {
|
||||
let mut not_encoded_yet = true;
|
||||
let mut encoded_string = String::with_capacity(bytes.len());
|
||||
if ![Eci::Binary, Eci::Unknown].contains(&eci) {
|
||||
if eci == Eci::UTF8 {
|
||||
if !bytes.is_empty() {
|
||||
encoded_string.push_str(&CharacterSet::UTF8.decode(bytes).ok()?);
|
||||
not_encoded_yet = false;
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
} else if !bytes.is_empty() {
|
||||
encoded_string.push_str(&CharacterSet::from(eci).decode(bytes).ok()?);
|
||||
not_encoded_yet = false;
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
} else {
|
||||
} else if eci == Eci::Unknown {
|
||||
/* // This probably should never be used, it's here just in case I don't understand what's
|
||||
// going on.
|
||||
let cs = CharacterSet::from(Eci::ISO8859_1);
|
||||
if let Ok(enc_str) = cs.decode(bytes) {
|
||||
encoded_string.push_str(&enc_str);
|
||||
not_encoded_yet = false;
|
||||
}
|
||||
|
||||
else */
|
||||
if let Some(found_encoding) = StringUtils::guessCharset(bytes, &HashMap::default()) {
|
||||
if let Ok(found_encoded_str) = found_encoding.decode(bytes) {
|
||||
encoded_string.push_str(&found_encoded_str);
|
||||
not_encoded_yet = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if not_encoded_yet {
|
||||
for byte in bytes {
|
||||
encoded_string.push(char::from(*byte))
|
||||
}
|
||||
@@ -198,6 +261,11 @@ impl ECIStringBuilder {
|
||||
self.bytes.len()
|
||||
}
|
||||
|
||||
/// Reserve an additional number of bytes for storage
|
||||
pub fn reserve(&mut self, additional: usize) {
|
||||
self.bytes.reserve(additional);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true iff nothing has been appended
|
||||
*/
|
||||
@@ -210,6 +278,14 @@ impl ECIStringBuilder {
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
// pub fn list_ecis(&self) -> HashSet<Eci> {
|
||||
// let mut hs = HashSet::new();
|
||||
// self.eci_positions.iter().for_each(|pos| {
|
||||
// hs.insert(pos.0);
|
||||
// });
|
||||
// hs
|
||||
// }
|
||||
}
|
||||
|
||||
impl fmt::Display for ECIStringBuilder {
|
||||
@@ -221,3 +297,57 @@ impl fmt::Display for ECIStringBuilder {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::AddAssign<u8> for ECIStringBuilder {
|
||||
fn add_assign(&mut self, rhs: u8) {
|
||||
self.append_byte(rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::AddAssign<String> for ECIStringBuilder {
|
||||
fn add_assign(&mut self, rhs: String) {
|
||||
self.append_string(&rhs)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum ContentType {
|
||||
Text,
|
||||
Binary,
|
||||
Mixed,
|
||||
GS1,
|
||||
ISO15434,
|
||||
UnknownECI,
|
||||
}
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum AIFlag {
|
||||
None,
|
||||
GS1,
|
||||
AIM,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub struct SymbologyIdentifier {
|
||||
//char code = 0, modifier = 0, eciModifierOffset = 0;
|
||||
pub code: u8,
|
||||
pub modifier: u8,
|
||||
pub eciModifierOffset: u8,
|
||||
pub aiFlag: AIFlag,
|
||||
// AIFlag aiFlag = AIFlag::None;
|
||||
|
||||
// std::string toString(bool hasECI = false) const
|
||||
// {
|
||||
// return code ? ']' + std::string(1, code) + static_cast<char>(modifier + eciModifierOffset * hasECI) : std::string();
|
||||
// }
|
||||
}
|
||||
|
||||
impl Default for SymbologyIdentifier {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
code: 0,
|
||||
modifier: 0,
|
||||
eciModifierOffset: 0,
|
||||
aiFlag: AIFlag::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,7 +95,16 @@ pub trait GridSampler {
|
||||
dimensionY: u32,
|
||||
dst: Quadrilateral,
|
||||
src: Quadrilateral,
|
||||
) -> Result<BitMatrix>;
|
||||
) -> Result<(BitMatrix, [Point; 4])> {
|
||||
let transform = PerspectiveTransform::quadrilateralToQuadrilateral(dst, src)?;
|
||||
|
||||
self.sample_grid(
|
||||
image,
|
||||
dimensionX,
|
||||
dimensionY,
|
||||
&[SamplerControl::new(dimensionX, dimensionY, transform)],
|
||||
)
|
||||
}
|
||||
|
||||
fn sample_grid(
|
||||
&self,
|
||||
@@ -103,7 +112,76 @@ pub trait GridSampler {
|
||||
dimensionX: u32,
|
||||
dimensionY: u32,
|
||||
controls: &[SamplerControl],
|
||||
) -> Result<BitMatrix>;
|
||||
) -> Result<(BitMatrix, [Point; 4])> {
|
||||
if dimensionX == 0 || dimensionY == 0 {
|
||||
return Err(Exceptions::NOT_FOUND);
|
||||
}
|
||||
let mut bits = BitMatrix::new(dimensionX, dimensionY)?;
|
||||
let mut points = vec![Point::default(); 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.5;
|
||||
let mut x = 0;
|
||||
while x < max {
|
||||
// for (int x = 0; x < max; x += 2) {
|
||||
points[x].x = (x as f32) + 0.5;
|
||||
points[x].y = i_value;
|
||||
x += 1;
|
||||
}
|
||||
|
||||
controls
|
||||
.first()
|
||||
.unwrap()
|
||||
.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] as u32 >= image.getWidth() || points[x + 1] as u32 >= image.getHeight()
|
||||
// {
|
||||
// return Err(Exceptions::notFound(
|
||||
// "index out of bounds, see documentation in file for explanation".to_owned(),
|
||||
// ));
|
||||
// }
|
||||
if image
|
||||
.try_get(points[x].x as u32, points[x].y as u32)
|
||||
.ok_or(Exceptions::not_found_with(
|
||||
"index out of bounds, see documentation in file for explanation",
|
||||
))?
|
||||
{
|
||||
// Black(-ish) pixel
|
||||
bits.set(x as u32, y);
|
||||
}
|
||||
x += 1;
|
||||
}
|
||||
// } 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();
|
||||
// }
|
||||
}
|
||||
// dbg!(bits.to_string());
|
||||
|
||||
Ok((
|
||||
bits,
|
||||
[
|
||||
Point::default(),
|
||||
Point::default(),
|
||||
Point::default(),
|
||||
Point::default(),
|
||||
],
|
||||
))
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Checks a set of points that have been transformed to sample points on an image against
|
||||
|
||||
@@ -112,6 +112,8 @@ pub use eci::*;
|
||||
mod quad;
|
||||
pub use quad::*;
|
||||
|
||||
pub mod cpp_essentials;
|
||||
|
||||
#[cfg(feature = "otsu_level")]
|
||||
mod otsu_level_binarizer;
|
||||
#[cfg(feature = "otsu_level")]
|
||||
|
||||
@@ -29,6 +29,7 @@ use super::Quadrilateral;
|
||||
*
|
||||
* @author Sean Owen
|
||||
*/
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub struct PerspectiveTransform {
|
||||
a11: f32,
|
||||
a12: f32,
|
||||
@@ -81,15 +82,28 @@ impl PerspectiveTransform {
|
||||
Ok(s_to_q * q_to_s)
|
||||
}
|
||||
|
||||
pub fn transform_point(&self, point: Point) -> Point {
|
||||
let x = point.x;
|
||||
let y = point.y;
|
||||
let denominator = self.a13 * x + self.a23 * y + self.a33;
|
||||
Point::new(
|
||||
(self.a11 * x + self.a21 * y + self.a31) / denominator,
|
||||
(self.a12 * x + self.a22 * y + self.a32) / denominator,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn transform_points_single(&self, points: &mut [Point]) {
|
||||
for point in points.iter_mut() {
|
||||
// for (int i = 0; i < maxI; i += 2) {
|
||||
let x = point.x;
|
||||
let y = point.y;
|
||||
let denominator = self.a13 * x + self.a23 * y + self.a33;
|
||||
point.x = (self.a11 * x + self.a21 * y + self.a31) / denominator;
|
||||
point.y = (self.a12 * x + self.a22 * y + self.a32) / denominator;
|
||||
*point = self.transform_point(Point::new(point.x, point.y));
|
||||
}
|
||||
// for point in points.iter_mut() {
|
||||
// // for (int i = 0; i < maxI; i += 2) {
|
||||
// let x = point.x;
|
||||
// let y = point.y;
|
||||
// let denominator = self.a13 * x + self.a23 * y + self.a33;
|
||||
// point.x = (self.a11 * x + self.a21 * y + self.a31) / denominator;
|
||||
// point.y = (self.a12 * x + self.a22 * y + self.a32) / denominator;
|
||||
// }
|
||||
}
|
||||
|
||||
pub fn transform_points_double(&self, x_values: &mut [f32], y_valuess: &mut [f32]) {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use crate::Point;
|
||||
use crate::{point, Point};
|
||||
|
||||
use super::PerspectiveTransform;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Quadrilateral(pub [Point; 4]);
|
||||
@@ -51,8 +53,8 @@ impl Quadrilateral {
|
||||
|
||||
impl Quadrilateral {
|
||||
#[allow(dead_code)]
|
||||
pub fn rectangle(width: i32, height: i32, margin: Option<i32>) -> Quadrilateral {
|
||||
let margin = margin.unwrap_or(0);
|
||||
pub fn rectangle(width: i32, height: i32, margin: Option<f32>) -> Quadrilateral {
|
||||
let margin = margin.unwrap_or(0.0);
|
||||
|
||||
Quadrilateral([
|
||||
Point {
|
||||
@@ -74,6 +76,16 @@ impl Quadrilateral {
|
||||
])
|
||||
}
|
||||
|
||||
pub fn rectangle_from_xy(x0: f32, x1: f32, y0: f32, y1: f32, o: Option<f32>) -> Self {
|
||||
let o = o.unwrap_or(0.5);
|
||||
Quadrilateral::from([
|
||||
point(x0 + o, y0 + o),
|
||||
point(x1 + o, y0 + o),
|
||||
point(x1 + o, y1 + o),
|
||||
point(x0 + o, y1 + o),
|
||||
])
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn centered_square(size: i32) -> Quadrilateral {
|
||||
Self::scale(
|
||||
@@ -171,7 +183,7 @@ impl Quadrilateral {
|
||||
|
||||
let mirror = if let Some(m) = mirror { m } else { false };
|
||||
|
||||
let mut res = self.clone();
|
||||
let mut res = *self;
|
||||
res.0.rotate_left(((n + 4) % 4) as usize);
|
||||
// std::rotate_copy(q.begin(), q.begin() + ((n + 4) % 4), q.end(), res.begin());
|
||||
if mirror {
|
||||
@@ -208,6 +220,33 @@ impl Quadrilateral {
|
||||
|
||||
!(x || y)
|
||||
}
|
||||
|
||||
pub fn blend(a: &Quadrilateral, b: &Quadrilateral) -> Self {
|
||||
let c = a[0];
|
||||
let dist2First = |a, b| Point::distance(a, c) < Point::distance(b, c);
|
||||
// rotate points such that the the two topLeft points are closest to each other
|
||||
let min_element =
|
||||
b.0.iter()
|
||||
.copied()
|
||||
.min_by(|a, b| match dist2First(*a, *b) {
|
||||
true => std::cmp::Ordering::Less,
|
||||
false => std::cmp::Ordering::Greater,
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let offset =
|
||||
b.0.iter()
|
||||
.position(|v| *v == min_element)
|
||||
.unwrap_or_default();
|
||||
// let offset = std::min_element(b.begin(), b.end(), dist2First) - b.begin();
|
||||
|
||||
let mut res = Quadrilateral::default();
|
||||
for i in 0..4 {
|
||||
// for (int i = 0; i < 4; ++i){
|
||||
res[i] = (a[i] + b[(i + offset) % 4]) / 2.0;
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Quadrilateral {
|
||||
@@ -215,3 +254,23 @@ impl Default for Quadrilateral {
|
||||
Self([Point { x: 0.0, y: 0.0 }; 4])
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Index<usize> for Quadrilateral {
|
||||
type Output = Point;
|
||||
|
||||
fn index(&self, index: usize) -> &Self::Output {
|
||||
&self.0[index]
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::IndexMut<usize> for Quadrilateral {
|
||||
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
|
||||
&mut self.0[index]
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[Point; 4]> for Quadrilateral {
|
||||
fn from(value: [Point; 4]) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -343,7 +343,8 @@ impl<'a> Detector<'_> {
|
||||
);
|
||||
let src = Quadrilateral::new(topRight, topLeft, bottomRight, bottomLeft);
|
||||
|
||||
sampler.sample_grid_detailed(image, dimensionX, dimensionY, dst, src)
|
||||
let (res, _) = sampler.sample_grid_detailed(image, dimensionX, dimensionY, dst, src)?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -14,9 +14,12 @@ macro_rules! CHECK {
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use crate::{
|
||||
common::{BitMatrix, DefaultGridSampler, GridSampler, Quadrilateral, Result},
|
||||
common::{
|
||||
cpp_essentials::RegressionLineTrait, BitMatrix, DefaultGridSampler, GridSampler,
|
||||
Quadrilateral, Result,
|
||||
},
|
||||
datamatrix::detector::{
|
||||
zxing_cpp_detector::{util::intersect, BitMatrixCursor, RegressionLine},
|
||||
zxing_cpp_detector::{util::intersect, BitMatrixCursorTrait},
|
||||
DatamatrixDetectorResult,
|
||||
},
|
||||
point,
|
||||
@@ -230,8 +233,10 @@ fn Scan(
|
||||
|
||||
CHECK!(res.is_ok());
|
||||
|
||||
let (res, _) = res?;
|
||||
|
||||
return Ok(DatamatrixDetectorResult::new(
|
||||
res?,
|
||||
res,
|
||||
sourcePoints.points().to_vec(),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -1,18 +1,11 @@
|
||||
mod bitmatrix_cursor;
|
||||
mod cpp_new_detector;
|
||||
mod direction;
|
||||
mod dm_regression_line;
|
||||
mod edge_tracer;
|
||||
mod regression_line;
|
||||
mod step_result;
|
||||
pub(self) mod util;
|
||||
mod value;
|
||||
|
||||
pub(self) use bitmatrix_cursor::*;
|
||||
pub(self) use crate::common::cpp_essentials::bitmatrix_cursor_trait::*;
|
||||
pub(self) use crate::common::cpp_essentials::direction::*;
|
||||
pub(self) use crate::common::cpp_essentials::dm_regression_line::*;
|
||||
pub(self) use crate::common::cpp_essentials::edge_tracer::*;
|
||||
pub(self) use crate::common::cpp_essentials::regression_line::*;
|
||||
pub(self) use crate::common::cpp_essentials::step_result::*;
|
||||
pub(self) use crate::common::cpp_essentials::util;
|
||||
pub(self) use crate::common::cpp_essentials::value::*;
|
||||
pub use cpp_new_detector::detect;
|
||||
pub(self) use direction::*;
|
||||
pub(self) use dm_regression_line::*;
|
||||
pub(self) use edge_tracer::*;
|
||||
pub(self) use regression_line::*;
|
||||
pub(self) use step_result::*;
|
||||
pub(self) use value::*;
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
use crate::common::Result;
|
||||
use crate::{Exceptions, Point};
|
||||
|
||||
use super::{DMRegressionLine, Direction, RegressionLine};
|
||||
|
||||
#[inline(always)]
|
||||
pub fn float_min<T: PartialOrd>(a: T, b: T) -> T {
|
||||
if a > b {
|
||||
b
|
||||
} else {
|
||||
a
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn float_max<T: PartialOrd>(a: T, b: T) -> T {
|
||||
if a < b {
|
||||
b
|
||||
} else {
|
||||
a
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn intersect(l1: &DMRegressionLine, l2: &DMRegressionLine) -> Result<Point> {
|
||||
if !(l1.isValid() && l2.isValid()) {
|
||||
return Err(Exceptions::ILLEGAL_STATE);
|
||||
}
|
||||
let d = l1.a * l2.b - l1.b * l2.a;
|
||||
let x = (l1.c * l2.b - l1.b * l2.c) / d;
|
||||
let y = (l1.a * l2.c - l1.c * l2.a) / d;
|
||||
Ok(Point { x, y })
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[inline(always)]
|
||||
pub fn opposite(dir: Direction) -> Direction {
|
||||
if dir == Direction::Left {
|
||||
Direction::Right
|
||||
} else {
|
||||
Direction::Left
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[derive(Error, Debug, PartialEq, Eq)]
|
||||
#[derive(Error, Debug, PartialEq, Eq, Clone)]
|
||||
pub enum Exceptions {
|
||||
#[error("IllegalArgumentException{}", if .0.is_empty() { String::new() } else { format!(" - {}", .0) })]
|
||||
IllegalArgumentException(String),
|
||||
|
||||
@@ -356,7 +356,7 @@ pub fn detect(image: &BitMatrix, try_harder: bool) -> Result<MaxicodeDetectionRe
|
||||
);
|
||||
let src = Quadrilateral::new(tl, tr, br, bl);
|
||||
|
||||
let Ok(bits) = grid_sampler.sample_grid_detailed(
|
||||
let Ok((bits,_)) = grid_sampler.sample_grid_detailed(
|
||||
image,
|
||||
target_width.round() as u32,
|
||||
target_height.round() as u32,
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use crate::common::Result;
|
||||
use crate::qrcode::cpp_port::QrReader;
|
||||
use crate::{
|
||||
aztec::AztecReader, datamatrix::DataMatrixReader, maxicode::MaxiCodeReader,
|
||||
oned::MultiFormatOneDReader, pdf417::PDF417Reader, qrcode::QRCodeReader, BarcodeFormat,
|
||||
@@ -136,9 +137,18 @@ impl MultiFormatReader {
|
||||
// Calling all readers again with inverted image
|
||||
image.get_black_matrix_mut().flip_self();
|
||||
let res = self.decode_formats(image);
|
||||
// if let Ok(r) = res.as_mut() {
|
||||
if res.is_ok() {
|
||||
return res;
|
||||
let mut r = res.unwrap();
|
||||
r.putMetadata(
|
||||
crate::RXingResultMetadataType::IS_INVERTED,
|
||||
crate::RXingResultMetadataValue::IsInverted(true),
|
||||
);
|
||||
return Ok(r);
|
||||
}
|
||||
// if res.is_ok() {
|
||||
// return res;
|
||||
// }
|
||||
}
|
||||
Err(Exceptions::NOT_FOUND)
|
||||
}
|
||||
@@ -164,7 +174,15 @@ impl MultiFormatReader {
|
||||
for possible_format in self.possible_formats.iter() {
|
||||
let res = match possible_format {
|
||||
BarcodeFormat::QR_CODE => {
|
||||
QRCodeReader::default().decode_with_hints(image, &self.hints)
|
||||
let cpp = QrReader::default().decode_with_hints(image, &self.hints);
|
||||
if cpp.is_ok() {
|
||||
cpp
|
||||
} else {
|
||||
QRCodeReader::default().decode_with_hints(image, &self.hints)
|
||||
}
|
||||
}
|
||||
BarcodeFormat::MICRO_QR_CODE => {
|
||||
QrReader::default().decode_with_hints(image, &self.hints)
|
||||
}
|
||||
BarcodeFormat::DATA_MATRIX => {
|
||||
DataMatrixReader::default().decode_with_hints(image, &self.hints)
|
||||
@@ -196,6 +214,9 @@ impl MultiFormatReader {
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(res) = QrReader::default().decode_with_hints(image, &self.hints) {
|
||||
return Ok(res);
|
||||
}
|
||||
if let Ok(res) = QRCodeReader::default().decode_with_hints(image, &self.hints) {
|
||||
return Ok(res);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use crate::common::Result;
|
||||
use crate::qrcode::cpp_port::QrReader;
|
||||
use crate::{
|
||||
aztec::AztecReader, datamatrix::DataMatrixReader, maxicode::MaxiCodeReader,
|
||||
oned::MultiFormatOneDReader, pdf417::PDF417Reader, qrcode::QRCodeReader, BarcodeFormat,
|
||||
@@ -43,6 +44,7 @@ pub struct MultiUseMultiFormatReader {
|
||||
aztec_reader: AztecReader,
|
||||
pdf417_reader: PDF417Reader,
|
||||
maxicode_reader: MaxiCodeReader,
|
||||
cpp_qrcode_reader: QrReader,
|
||||
}
|
||||
|
||||
impl Reader for MultiUseMultiFormatReader {
|
||||
@@ -84,6 +86,7 @@ impl Reader for MultiUseMultiFormatReader {
|
||||
self.aztec_reader.reset();
|
||||
self.pdf417_reader.reset();
|
||||
self.maxicode_reader.reset();
|
||||
self.cpp_qrcode_reader.reset();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,8 +150,16 @@ impl MultiUseMultiFormatReader {
|
||||
image.get_black_matrix_mut().flip_self();
|
||||
let res = self.decode_formats(image);
|
||||
if res.is_ok() {
|
||||
return res;
|
||||
let mut r = res.unwrap();
|
||||
r.putMetadata(
|
||||
crate::RXingResultMetadataType::IS_INVERTED,
|
||||
crate::RXingResultMetadataValue::IsInverted(true),
|
||||
);
|
||||
return Ok(r);
|
||||
}
|
||||
// if res.is_ok() {
|
||||
// return res;
|
||||
// }
|
||||
}
|
||||
Err(Exceptions::NOT_FOUND)
|
||||
}
|
||||
@@ -174,7 +185,15 @@ impl MultiUseMultiFormatReader {
|
||||
for possible_format in self.possible_formats.iter() {
|
||||
let res = match possible_format {
|
||||
BarcodeFormat::QR_CODE => {
|
||||
self.qr_code_reader.decode_with_hints(image, &self.hints)
|
||||
let a = self.cpp_qrcode_reader.decode_with_hints(image, &self.hints);
|
||||
if a.is_ok() {
|
||||
a
|
||||
} else {
|
||||
self.qr_code_reader.decode_with_hints(image, &self.hints)
|
||||
}
|
||||
}
|
||||
BarcodeFormat::MICRO_QR_CODE => {
|
||||
self.cpp_qrcode_reader.decode_with_hints(image, &self.hints)
|
||||
}
|
||||
BarcodeFormat::DATA_MATRIX => self
|
||||
.data_matrix_reader
|
||||
@@ -203,7 +222,9 @@ impl MultiUseMultiFormatReader {
|
||||
return Ok(res);
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(res) = self.cpp_qrcode_reader.decode_with_hints(image, &self.hints) {
|
||||
return Ok(res);
|
||||
}
|
||||
if let Ok(res) = self.qr_code_reader.decode_with_hints(image, &self.hints) {
|
||||
return Ok(res);
|
||||
}
|
||||
|
||||
@@ -195,6 +195,6 @@ impl Writer for L {
|
||||
|
||||
impl OneDimensionalCodeWriter for L {
|
||||
fn encode_oned(&self, _contents: &str) -> Result<Vec<bool>> {
|
||||
todo!()
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -490,7 +490,7 @@ pub trait UPCEANReader: OneDReader {
|
||||
pub(crate) struct StandInStruct;
|
||||
impl UPCEANReader for StandInStruct {
|
||||
fn getBarcodeFormat(&self) -> BarcodeFormat {
|
||||
todo!()
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn decodeMiddle(
|
||||
@@ -499,7 +499,7 @@ impl UPCEANReader for StandInStruct {
|
||||
_startRange: &[usize; 2],
|
||||
_resultString: &mut String,
|
||||
) -> Result<usize> {
|
||||
todo!()
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
impl OneDReader for StandInStruct {
|
||||
@@ -509,13 +509,13 @@ impl OneDReader for StandInStruct {
|
||||
_row: &BitArray,
|
||||
_hints: &crate::DecodingHintDictionary,
|
||||
) -> Result<RXingResult> {
|
||||
todo!()
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
impl Reader for StandInStruct {
|
||||
fn decode<B: Binarizer>(&mut self, _image: &mut crate::BinaryBitmap<B>) -> Result<RXingResult> {
|
||||
todo!()
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn decode_with_hints<B: Binarizer>(
|
||||
@@ -523,7 +523,7 @@ impl Reader for StandInStruct {
|
||||
_image: &mut crate::BinaryBitmap<B>,
|
||||
_hints: &crate::DecodingHintDictionary,
|
||||
) -> Result<RXingResult> {
|
||||
todo!()
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
256
src/qrcode/cpp_port/bitmatrix_parser.rs
Normal file
@@ -0,0 +1,256 @@
|
||||
// /*
|
||||
// * Copyright 2016 Nu-book Inc.
|
||||
// * Copyright 2016 ZXing authors
|
||||
// */
|
||||
// // SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::{
|
||||
common::{BitMatrix, Result},
|
||||
qrcode::decoder::{ErrorCorrectionLevel, FormatInformation, Version, VersionRef},
|
||||
Exceptions,
|
||||
};
|
||||
|
||||
use super::{data_mask::GetDataMaskBit, detector::AppendBit};
|
||||
|
||||
pub fn getBit(bitMatrix: &BitMatrix, x: u32, y: u32, mirrored: Option<bool>) -> bool {
|
||||
let mirrored = mirrored.unwrap_or(false);
|
||||
if mirrored {
|
||||
bitMatrix.get(y, x)
|
||||
} else {
|
||||
bitMatrix.get(x, y)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hasValidDimension(bitMatrix: &BitMatrix, isMicro: bool) -> bool {
|
||||
let dimension = bitMatrix.height();
|
||||
if (isMicro) {
|
||||
dimension >= 11 && dimension <= 17 && (dimension % 2) == 1
|
||||
} else {
|
||||
dimension >= 21 && dimension <= 177 && (dimension % 4) == 1
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ReadVersion(bitMatrix: &BitMatrix) -> Result<VersionRef> {
|
||||
let dimension = bitMatrix.height();
|
||||
|
||||
let mut version = Version::FromDimension(dimension)?;
|
||||
|
||||
if (version.getVersionNumber() < 7) {
|
||||
return Ok(version);
|
||||
}
|
||||
|
||||
for mirror in [false, true] {
|
||||
// for (bool mirror : {false, true}) {
|
||||
// Read top-right/bottom-left version info: 3 wide by 6 tall (depending on mirrored)
|
||||
let mut versionBits = 0;
|
||||
for y in (0..=5).rev() {
|
||||
// for (int y = 5; y >= 0; --y) {
|
||||
for x in ((dimension - 11)..=(dimension - 9)).rev() {
|
||||
// for (int x = dimension - 9; x >= dimension - 11; --x) {
|
||||
AppendBit(&mut versionBits, getBit(bitMatrix, x, y, Some(mirror)));
|
||||
}
|
||||
}
|
||||
|
||||
version = Version::DecodeVersionInformation(versionBits, 0)?; // THIS MIGHT BE WRONG todo!()
|
||||
if (version.getDimensionForVersion() == dimension) {
|
||||
return Ok(version);
|
||||
}
|
||||
}
|
||||
|
||||
return Err(Exceptions::FORMAT);
|
||||
}
|
||||
|
||||
pub fn ReadFormatInformation(bitMatrix: &BitMatrix, isMicro: bool) -> Result<FormatInformation> {
|
||||
if (!hasValidDimension(bitMatrix, isMicro)) {
|
||||
return Err(Exceptions::FORMAT);
|
||||
}
|
||||
|
||||
if (isMicro) {
|
||||
// Read top-left format info bits
|
||||
let mut formatInfoBits = 0;
|
||||
for x in 1..9 {
|
||||
// for (int x = 1; x < 9; x++)
|
||||
AppendBit(&mut formatInfoBits, getBit(bitMatrix, x, 8, None));
|
||||
}
|
||||
for y in (1..=7).rev() {
|
||||
// for (int y = 7; y >= 1; y--)
|
||||
AppendBit(&mut formatInfoBits, getBit(bitMatrix, 8, y, None));
|
||||
}
|
||||
|
||||
return Ok(FormatInformation::DecodeMQR(formatInfoBits as u32));
|
||||
}
|
||||
|
||||
// Read top-left format info bits
|
||||
let mut formatInfoBits1 = 0;
|
||||
for x in 0..6 {
|
||||
// for (int x = 0; x < 6; x++)
|
||||
AppendBit(&mut formatInfoBits1, getBit(bitMatrix, x, 8, None));
|
||||
}
|
||||
// .. and skip a bit in the timing pattern ...
|
||||
AppendBit(&mut formatInfoBits1, getBit(bitMatrix, 7, 8, None));
|
||||
AppendBit(&mut formatInfoBits1, getBit(bitMatrix, 8, 8, None));
|
||||
AppendBit(&mut formatInfoBits1, getBit(bitMatrix, 8, 7, None));
|
||||
// .. and skip a bit in the timing pattern ...
|
||||
for y in (0..=5).rev() {
|
||||
// for (int y = 5; y >= 0; y--)
|
||||
AppendBit(&mut formatInfoBits1, getBit(bitMatrix, 8, y, None));
|
||||
}
|
||||
|
||||
// Read the top-right/bottom-left pattern including the 'Dark Module' from the bottom-left
|
||||
// part that has to be considered separately when looking for mirrored symbols.
|
||||
// See also FormatInformation::DecodeQR
|
||||
let dimension = bitMatrix.height();
|
||||
let mut formatInfoBits2 = 0;
|
||||
for y in ((dimension - 8)..=(dimension - 1)).rev() {
|
||||
// for (int y = dimension - 1; y >= dimension - 8; y--)
|
||||
AppendBit(&mut formatInfoBits2, getBit(bitMatrix, 8, y, None));
|
||||
}
|
||||
for x in (dimension - 8)..dimension {
|
||||
// for (int x = dimension - 8; x < dimension; x++)
|
||||
AppendBit(&mut formatInfoBits2, getBit(bitMatrix, x, 8, None));
|
||||
}
|
||||
|
||||
return Ok(FormatInformation::DecodeQR(
|
||||
formatInfoBits1 as u32,
|
||||
formatInfoBits2 as u32,
|
||||
));
|
||||
}
|
||||
|
||||
pub fn ReadQRCodewords(
|
||||
bitMatrix: &BitMatrix,
|
||||
version: VersionRef,
|
||||
formatInfo: &FormatInformation,
|
||||
) -> Result<Vec<u8>> {
|
||||
let functionPattern: BitMatrix = version.buildFunctionPattern()?;
|
||||
|
||||
let mut result = Vec::new();
|
||||
result.reserve(version.getTotalCodewords() as usize);
|
||||
let mut currentByte = 0;
|
||||
let mut readingUp = true;
|
||||
let mut bitsRead = 0;
|
||||
let dimension = bitMatrix.height();
|
||||
// Read columns in pairs, from right to left
|
||||
let mut x = (dimension as i32) - 1;
|
||||
while x > 0 {
|
||||
// for (int x = dimension - 1; x > 0; x -= 2) {
|
||||
// Skip whole column with vertical timing pattern.
|
||||
if (x == 6) {
|
||||
x -= 1;
|
||||
}
|
||||
// Read alternatingly from bottom to top then top to bottom
|
||||
for row in 0..dimension {
|
||||
// for (int row = 0; row < dimension; row++) {
|
||||
let y = if readingUp { dimension - 1 - row } else { row };
|
||||
for col in 0..2 {
|
||||
// for (int col = 0; col < 2; col++) {
|
||||
let xx = (x - col) as u32;
|
||||
// Ignore bits covered by the function pattern
|
||||
if (!functionPattern.get(xx, y)) {
|
||||
// Read a bit
|
||||
AppendBit(
|
||||
&mut currentByte,
|
||||
GetDataMaskBit(formatInfo.data_mask as u32, xx, y, None)?
|
||||
!= getBit(bitMatrix, xx, y, Some(formatInfo.isMirrored)),
|
||||
);
|
||||
// If we've made a whole byte, save it off
|
||||
bitsRead += 1;
|
||||
if (bitsRead % 8 == 0) {
|
||||
result.push(std::mem::take(&mut currentByte));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
readingUp = !readingUp; // switch directions
|
||||
|
||||
x -= 2;
|
||||
}
|
||||
if ((result.len()) != version.getTotalCodewords() as usize) {
|
||||
return Err(Exceptions::FORMAT);
|
||||
}
|
||||
|
||||
Ok(result.iter().copied().map(|x| x as u8).collect())
|
||||
}
|
||||
|
||||
pub fn ReadMQRCodewords(
|
||||
bitMatrix: &BitMatrix,
|
||||
version: VersionRef,
|
||||
formatInfo: &FormatInformation,
|
||||
) -> Result<Vec<u8>> {
|
||||
let functionPattern = version.buildFunctionPattern()?;
|
||||
|
||||
// D3 in a Version M1 symbol, D11 in a Version M3-L symbol and D9
|
||||
// in a Version M3-M symbol is a 2x2 square 4-module block.
|
||||
// See ISO 18004:2006 6.7.3.
|
||||
let hasD4mBlock = version.getVersionNumber() % 2 == 1;
|
||||
let d4mBlockIndex = if version.getVersionNumber() == 1 {
|
||||
3
|
||||
} else {
|
||||
(if formatInfo.error_correction_level == ErrorCorrectionLevel::L {
|
||||
11
|
||||
} else {
|
||||
9
|
||||
})
|
||||
};
|
||||
|
||||
let mut result = Vec::new();
|
||||
result.reserve(version.getTotalCodewords() as usize);
|
||||
let mut currentByte = 0;
|
||||
let mut readingUp = true;
|
||||
let mut bitsRead = 0;
|
||||
let dimension = bitMatrix.height();
|
||||
// Read columns in pairs, from right to left
|
||||
let mut x = dimension - 1;
|
||||
while x > 0 {
|
||||
// for (int x = dimension - 1; x > 0; x -= 2) {
|
||||
// Read alternatingly from bottom to top then top to bottom
|
||||
for row in 0..dimension {
|
||||
// for (int row = 0; row < dimension; row++) {
|
||||
let y = if readingUp { dimension - 1 - row } else { row };
|
||||
for col in 0..2 {
|
||||
// for (int col = 0; col < 2; col++) {
|
||||
let xx = x - col;
|
||||
// Ignore bits covered by the function pattern
|
||||
if (!functionPattern.get(xx, y)) {
|
||||
// Read a bit
|
||||
AppendBit(
|
||||
&mut currentByte,
|
||||
GetDataMaskBit(formatInfo.data_mask as u32, xx, y, Some(true))?
|
||||
!= getBit(bitMatrix, xx, y, Some(formatInfo.isMirrored)),
|
||||
);
|
||||
bitsRead += 1;
|
||||
// If we've made a whole byte, save it off; save early if 2x2 data block.
|
||||
if (bitsRead == 8
|
||||
|| (bitsRead == 4 && hasD4mBlock && (result.len()) == d4mBlockIndex - 1))
|
||||
{
|
||||
result.push(std::mem::take(&mut currentByte));
|
||||
bitsRead = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
readingUp = !readingUp; // switch directions
|
||||
|
||||
x -= 2;
|
||||
}
|
||||
if ((result.len()) != version.getTotalCodewords() as usize) {
|
||||
return Err(Exceptions::FORMAT);
|
||||
}
|
||||
|
||||
Ok(result.iter().copied().map(|x| x as u8).collect())
|
||||
}
|
||||
|
||||
pub fn ReadCodewords(
|
||||
bitMatrix: &BitMatrix,
|
||||
version: VersionRef,
|
||||
formatInfo: &FormatInformation,
|
||||
) -> Result<Vec<u8>> {
|
||||
if (!hasValidDimension(bitMatrix, version.isMicroQRCode())) {
|
||||
return Err(Exceptions::FORMAT);
|
||||
}
|
||||
|
||||
if version.isMicroQRCode() {
|
||||
ReadMQRCodewords(bitMatrix, version, formatInfo)
|
||||
} else {
|
||||
ReadQRCodewords(bitMatrix, version, formatInfo)
|
||||
}
|
||||
}
|
||||
55
src/qrcode/cpp_port/data_mask.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2016 Nu-book Inc.
|
||||
* Copyright 2016 ZXing authors
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::common::BitMatrix;
|
||||
use crate::common::Result;
|
||||
use crate::Exceptions;
|
||||
|
||||
/**
|
||||
* <p>Encapsulates data masks for the data bits in a QR and micro QR code, per ISO 18004:2006 6.8.</p>
|
||||
*
|
||||
* <p>Note that the diagram in section 6.8.1 is misleading since it indicates that i is column position
|
||||
* and j is row position. In fact, as the text says, i is row position and j is column position.</p>
|
||||
*/
|
||||
|
||||
pub fn GetDataMaskBit(maskIndex: u32, x: u32, y: u32, isMicro: Option<bool>) -> Result<bool> {
|
||||
let isMicro = isMicro.unwrap_or(false);
|
||||
let mut maskIndex = maskIndex;
|
||||
if (isMicro) {
|
||||
if (maskIndex < 0 || maskIndex >= 4) {
|
||||
return Err(Exceptions::illegal_argument_with(
|
||||
"QRCode maskIndex out of range",
|
||||
));
|
||||
}
|
||||
maskIndex = [1, 4, 6, 7][maskIndex as usize]; // map from MQR to QR indices
|
||||
}
|
||||
|
||||
match (maskIndex) {
|
||||
0 => return Ok((y + x) % 2 == 0),
|
||||
1 => return Ok(y % 2 == 0),
|
||||
2 => return Ok(x % 3 == 0),
|
||||
3 => return Ok((y + x) % 3 == 0),
|
||||
4 => return Ok(((y / 2) + (x / 3)) % 2 == 0),
|
||||
5 => return Ok((y * x) % 6 == 0),
|
||||
6 => return Ok(((y * x) % 6) < 3),
|
||||
7 => return Ok((y + x + ((y * x) % 3)) % 2 == 0),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Err(Exceptions::illegal_argument_with(
|
||||
"QRCode maskIndex out of range",
|
||||
))
|
||||
}
|
||||
|
||||
pub fn GetMaskedBit(
|
||||
bits: &BitMatrix,
|
||||
x: u32,
|
||||
y: u32,
|
||||
maskIndex: u32,
|
||||
isMicro: Option<bool>,
|
||||
) -> Result<bool> {
|
||||
Ok(GetDataMaskBit(maskIndex, x, y, isMicro)? != bits.get(x, y))
|
||||
}
|
||||
446
src/qrcode/cpp_port/decoder.rs
Normal file
@@ -0,0 +1,446 @@
|
||||
// /*
|
||||
// * Copyright 2016 Nu-book Inc.
|
||||
// * Copyright 2016 ZXing authors
|
||||
// */
|
||||
// // SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::common::cpp_essentials::{DecoderResult, StructuredAppendInfo};
|
||||
use crate::common::reedsolomon::{
|
||||
get_predefined_genericgf, PredefinedGenericGF, ReedSolomonDecoder,
|
||||
};
|
||||
use crate::common::{
|
||||
AIFlag, BitMatrix, BitSource, CharacterSet, DecoderRXingResult, ECIStringBuilder, Eci, Result,
|
||||
SymbologyIdentifier,
|
||||
};
|
||||
use crate::qrcode::cpp_port::bitmatrix_parser::{
|
||||
ReadCodewords, ReadFormatInformation, ReadVersion,
|
||||
};
|
||||
use crate::qrcode::decoder::{DataBlock, ErrorCorrectionLevel, Mode, Version};
|
||||
use crate::Exceptions;
|
||||
|
||||
/**
|
||||
* <p>Given data and error-correction codewords received, possibly corrupted by errors, attempts to
|
||||
* correct the errors in-place using Reed-Solomon error correction.</p>
|
||||
*
|
||||
* @param codewordBytes data and error correction codewords
|
||||
* @param numDataCodewords number of codewords that are data bytes
|
||||
* @return false if error correction fails
|
||||
*/
|
||||
pub fn CorrectErrors(codewordBytes: &mut [u8], numDataCodewords: u32) -> Result<bool> {
|
||||
// First read into an array of ints
|
||||
// std::vector<int> codewordsInts(codewordBytes.begin(), codewordBytes.end());
|
||||
let mut codewordsInts = codewordBytes.iter().copied().map(|b| b as i32).collect();
|
||||
|
||||
let numECCodewords = ((codewordBytes.len() as u32) - numDataCodewords) as i32;
|
||||
let rs = ReedSolomonDecoder::new(get_predefined_genericgf(
|
||||
PredefinedGenericGF::QrCodeField256,
|
||||
));
|
||||
|
||||
rs.decode(&mut codewordsInts, numECCodewords)?;
|
||||
|
||||
// if rs.decode(&mut codewordsInts, numECCodewords)? != 0
|
||||
// // if (!ReedSolomonDecode(GenericGF::QRCodeField256(), codewordsInts, numECCodewords))
|
||||
// {
|
||||
// return Ok(false);
|
||||
// }
|
||||
|
||||
// Copy back into array of bytes -- only need to worry about the bytes that were data
|
||||
// We don't care about errors in the error-correction codewords
|
||||
codewordBytes[..numDataCodewords as usize].copy_from_slice(
|
||||
&codewordsInts[..numDataCodewords as usize]
|
||||
.into_iter()
|
||||
.copied()
|
||||
.map(|i| i as u8)
|
||||
.collect::<Vec<u8>>(),
|
||||
);
|
||||
// std::copy_n(codewordsInts.begin(), numDataCodewords, codewordBytes.begin());
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/**
|
||||
* See specification GBT 18284-2000
|
||||
*/
|
||||
pub fn DecodeHanziSegment(
|
||||
bits: &mut BitSource,
|
||||
count: u32,
|
||||
result: &mut ECIStringBuilder,
|
||||
) -> Result<()> {
|
||||
let mut count = count;
|
||||
|
||||
// Each character will require 2 bytes, decode as GB2312
|
||||
// There is no ECI value for GB2312, use GB18030 which is a superset
|
||||
result.switch_encoding(CharacterSet::GB18030, false);
|
||||
result.reserve(2 * count as usize);
|
||||
|
||||
while (count > 0) {
|
||||
// Each 13 bits encodes a 2-byte character
|
||||
let twoBytes = bits.readBits(13)?;
|
||||
let mut assembledTwoBytes = ((twoBytes / 0x060) << 8) | (twoBytes % 0x060);
|
||||
if (assembledTwoBytes < 0x00A00) {
|
||||
// In the 0xA1A1 to 0xAAFE range
|
||||
assembledTwoBytes += 0x0A1A1;
|
||||
} else {
|
||||
// In the 0xB0A1 to 0xFAFE range
|
||||
assembledTwoBytes += 0x0A6A1;
|
||||
}
|
||||
*result += ((assembledTwoBytes >> 8) & 0xFF) as u8;
|
||||
*result += (assembledTwoBytes & 0xFF) as u8;
|
||||
count -= 1;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn DecodeKanjiSegment(
|
||||
bits: &mut BitSource,
|
||||
count: u32,
|
||||
result: &mut ECIStringBuilder,
|
||||
) -> Result<()> {
|
||||
let mut count = count;
|
||||
// Each character will require 2 bytes. Read the characters as 2-byte pairs
|
||||
// and decode as Shift_JIS afterwards
|
||||
result.switch_encoding(CharacterSet::Shift_JIS, false);
|
||||
result.reserve(2 * count as usize);
|
||||
|
||||
while (count > 0) {
|
||||
// Each 13 bits encodes a 2-byte character
|
||||
let twoBytes = bits.readBits(13)?;
|
||||
let mut assembledTwoBytes = ((twoBytes / 0x0C0) << 8) | (twoBytes % 0x0C0);
|
||||
if (assembledTwoBytes < 0x01F00) {
|
||||
// In the 0x8140 to 0x9FFC range
|
||||
assembledTwoBytes += 0x08140;
|
||||
} else {
|
||||
// In the 0xE040 to 0xEBBF range
|
||||
assembledTwoBytes += 0x0C140;
|
||||
}
|
||||
*result += (assembledTwoBytes >> 8) as u8;
|
||||
*result += (assembledTwoBytes) as u8;
|
||||
count -= 1;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn DecodeByteSegment(
|
||||
bits: &mut BitSource,
|
||||
count: u32,
|
||||
result: &mut ECIStringBuilder,
|
||||
) -> Result<()> {
|
||||
result.switch_encoding(CharacterSet::Unknown, false);
|
||||
result.reserve(count as usize);
|
||||
|
||||
for _i in 0..count {
|
||||
// for (int i = 0; i < count; i++)
|
||||
*result += (bits.readBits(8)?) as u8;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn ToAlphaNumericChar(value: u32) -> Result<char> {
|
||||
let value = value as usize;
|
||||
/**
|
||||
* See ISO 18004:2006, 6.4.4 Table 5
|
||||
*/
|
||||
const ALPHANUMERIC_CHARS: [char; 45] = [
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
|
||||
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
|
||||
' ', '$', '%', '*', '+', '-', '.', '/', ':',
|
||||
];
|
||||
|
||||
if (value < 0 || value >= (ALPHANUMERIC_CHARS.len())) {
|
||||
return Err(Exceptions::index_out_of_bounds_with(
|
||||
"oAlphaNumericChar: out of range",
|
||||
));
|
||||
}
|
||||
|
||||
Ok(ALPHANUMERIC_CHARS[value])
|
||||
}
|
||||
|
||||
pub fn DecodeAlphanumericSegment(
|
||||
bits: &mut BitSource,
|
||||
count: u32,
|
||||
result: &mut ECIStringBuilder,
|
||||
) -> Result<()> {
|
||||
let mut count = count;
|
||||
|
||||
// Read two characters at a time
|
||||
let mut buffer = String::new();
|
||||
|
||||
while count > 1 {
|
||||
let nextTwoCharsBits = bits.readBits(11)?;
|
||||
buffer.push(ToAlphaNumericChar(nextTwoCharsBits / 45)?);
|
||||
buffer.push(ToAlphaNumericChar(nextTwoCharsBits % 45)?);
|
||||
count -= 2;
|
||||
}
|
||||
if (count == 1) {
|
||||
// special case: one character left
|
||||
buffer.push(ToAlphaNumericChar(bits.readBits(6)?)?);
|
||||
}
|
||||
// See section 6.4.8.1, 6.4.8.2
|
||||
if (result.symbology.aiFlag != AIFlag::None) {
|
||||
// We need to massage the result a bit if in an FNC1 mode:
|
||||
for i in 0..buffer.len() {
|
||||
// for (size_t i = 0; i < buffer.length(); i++) {
|
||||
if (buffer
|
||||
.chars()
|
||||
.nth(i)
|
||||
.ok_or(Exceptions::INDEX_OUT_OF_BOUNDS)?
|
||||
== '%')
|
||||
{
|
||||
if (i < buffer.len() - 1
|
||||
&& buffer
|
||||
.chars()
|
||||
.nth(i + 1)
|
||||
.ok_or(Exceptions::INDEX_OUT_OF_BOUNDS)?
|
||||
== '%')
|
||||
{
|
||||
// %% is rendered as %
|
||||
buffer.remove(i + 1);
|
||||
// buffer.erase(i + 1);
|
||||
} else {
|
||||
// In alpha mode, % should be converted to FNC1 separator 0x1D
|
||||
buffer.replace_range(i..i, &char::from(0x1D).to_string());
|
||||
// buffer[i] = static_cast<char>(0x1D);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.switch_encoding(CharacterSet::ISO8859_1, false);
|
||||
*result += buffer;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn DecodeNumericSegment(
|
||||
bits: &mut BitSource,
|
||||
count: u32,
|
||||
result: &mut ECIStringBuilder,
|
||||
) -> Result<()> {
|
||||
let mut count = count;
|
||||
|
||||
result.switch_encoding(CharacterSet::ISO8859_1, false);
|
||||
result.reserve(count as usize);
|
||||
|
||||
while (count > 0) {
|
||||
let n = std::cmp::min(count, 3);
|
||||
let nDigits = bits.readBits(1 + 3 * n as usize)?; // read 4, 7 or 10 bits into 1, 2 or 3 digits
|
||||
result.append_string(&crate::common::cpp_essentials::util::ToString(
|
||||
nDigits as usize,
|
||||
n as usize,
|
||||
)?);
|
||||
count -= n;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn ParseECIValue(bits: &mut BitSource) -> Result<Eci> {
|
||||
let firstByte = bits.readBits(8)?;
|
||||
if ((firstByte & 0x80) == 0) {
|
||||
// just one byte
|
||||
return Ok(Eci::from(firstByte & 0x7F));
|
||||
}
|
||||
if ((firstByte & 0xC0) == 0x80) {
|
||||
// two bytes
|
||||
let secondByte = bits.readBits(8)?;
|
||||
return Ok(Eci::from(((firstByte & 0x3F) << 8) | secondByte));
|
||||
}
|
||||
if ((firstByte & 0xE0) == 0xC0) {
|
||||
// three bytes
|
||||
let secondThirdBytes = bits.readBits(16)?;
|
||||
return Ok(Eci::from(((firstByte & 0x1F) << 16) | secondThirdBytes));
|
||||
}
|
||||
Err(Exceptions::format_with("ParseECIValue: invalid value"))
|
||||
}
|
||||
|
||||
/**
|
||||
* QR codes encode mode indicators and terminator codes into a constant bit length of 4.
|
||||
* Micro QR codes have terminator codes that vary in bit length but are always longer than
|
||||
* the mode indicators.
|
||||
* M1 - 0 length mode code, 3 bits terminator code
|
||||
* M2 - 1 bit mode code, 5 bits terminator code
|
||||
* M3 - 2 bit mode code, 7 bits terminator code
|
||||
* M4 - 3 bit mode code, 9 bits terminator code
|
||||
* IsTerminator peaks into the bit stream to see if the current position is at the start of
|
||||
* a terminator code. If true, then the decoding can finish. If false, then the decoding
|
||||
* can read off the next mode code.
|
||||
*
|
||||
* See ISO 18004:2015, 7.4.1 Table 2
|
||||
*
|
||||
* @param bits the stream of bits that might have a terminator code
|
||||
* @param version the QR or micro QR code version
|
||||
*/
|
||||
pub fn IsEndOfStream(bits: &mut BitSource, version: &Version) -> Result<bool> {
|
||||
let bitsRequired = Mode::get_terminator_bit_length(version); //super::qr_codec_mode::TerminatorBitsLength(version);
|
||||
let bitsAvailable = std::cmp::min(bits.available(), bitsRequired as usize);
|
||||
Ok(bitsAvailable == 0 || bits.peak_bits(bitsAvailable)? == 0)
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>QR Codes can encode text as bits in one of several modes, and can use multiple modes
|
||||
* in one QR Code. This method decodes the bits back into text.</p>
|
||||
*
|
||||
* <p>See ISO 18004:2006, 6.4.3 - 6.4.7</p>
|
||||
*/
|
||||
// ZXING_EXPORT_TEST_ONLY
|
||||
pub fn DecodeBitStream(
|
||||
bytes: &[u8],
|
||||
version: &Version,
|
||||
ecLevel: ErrorCorrectionLevel,
|
||||
) -> Result<DecoderResult<bool>> {
|
||||
let mut bits = BitSource::new(bytes.to_vec());
|
||||
let mut result = ECIStringBuilder::default();
|
||||
// Error error;
|
||||
result.symbology = SymbologyIdentifier {
|
||||
code: b'Q',
|
||||
modifier: b'1',
|
||||
eciModifierOffset: 1,
|
||||
aiFlag: AIFlag::None,
|
||||
}; //{'Q', '1', 1};
|
||||
let mut structuredAppend = StructuredAppendInfo::default();
|
||||
let modeBitLength = Mode::get_codec_mode_bits_length(version);
|
||||
|
||||
let res = (|| {
|
||||
while !IsEndOfStream(&mut bits, version)? {
|
||||
let mode: Mode;
|
||||
if modeBitLength == 0 {
|
||||
mode = Mode::NUMERIC; // MicroQRCode version 1 is always NUMERIC and modeBitLength is 0
|
||||
} else {
|
||||
mode = Mode::CodecModeForBits(
|
||||
bits.readBits(modeBitLength as usize)?,
|
||||
Some(version.isMicroQRCode()),
|
||||
)?;
|
||||
}
|
||||
|
||||
match mode {
|
||||
Mode::FNC1_FIRST_POSITION => {
|
||||
// if (!result.empty()) // uncomment to enforce specification
|
||||
// throw FormatError("GS1 Indicator (FNC1 in first position) at illegal position");
|
||||
result.symbology.modifier = b'3';
|
||||
result.symbology.aiFlag = AIFlag::GS1; // In Alphanumeric mode undouble doubled '%' and treat single '%' as <GS>
|
||||
}
|
||||
Mode::FNC1_SECOND_POSITION => {
|
||||
if !result.is_empty() {
|
||||
return Err(Exceptions::format_with("AIM Application Indicator (FNC1 in second position) at illegal position"));
|
||||
// throw FormatError("AIM Application Indicator (FNC1 in second position) at illegal position");
|
||||
}
|
||||
result.symbology.modifier = b'5'; // As above
|
||||
// ISO/IEC 18004:2015 7.4.8.3 AIM Application Indicator (FNC1 in second position), "00-99" or "A-Za-z"
|
||||
let appInd = bits.readBits(8)?;
|
||||
if appInd < 100
|
||||
// "00-09"
|
||||
{
|
||||
result +=
|
||||
crate::common::cpp_essentials::util::ToString(appInd as usize, 2)?;
|
||||
} else if ((appInd >= 165 && appInd <= 190) || (appInd >= 197 && appInd <= 222))
|
||||
// "A-Za-z"
|
||||
{
|
||||
result += (appInd - 100) as u8;
|
||||
} else {
|
||||
return Err(Exceptions::format_with("Invalid AIM Application Indicator"));
|
||||
// throw FormatError("Invalid AIM Application Indicator");
|
||||
}
|
||||
result.symbology.aiFlag = AIFlag::AIM; // see also above
|
||||
}
|
||||
Mode::STRUCTURED_APPEND => {
|
||||
// sequence number and parity is added later to the result metadata
|
||||
// Read next 4 bits of index, 4 bits of symbol count, and 8 bits of parity data, then continue
|
||||
structuredAppend.index = bits.readBits(4)? as i32;
|
||||
structuredAppend.count = bits.readBits(4)? as i32 + 1;
|
||||
structuredAppend.id = (bits.readBits(8)?).to_string(); //std::to_string(bits.readBits(8));
|
||||
}
|
||||
Mode::ECI => {
|
||||
// Count doesn't apply to ECI
|
||||
result.switch_encoding(ParseECIValue(&mut bits)?.into(), true);
|
||||
}
|
||||
Mode::HANZI => {
|
||||
// First handle Hanzi mode which does not start with character count
|
||||
// chinese mode contains a sub set indicator right after mode indicator
|
||||
let subset = bits.readBits(4)?;
|
||||
if (subset != 1)
|
||||
// GB2312_SUBSET is the only supported one right now
|
||||
{
|
||||
return Err(Exceptions::format_with("Unsupported HANZI subset"));
|
||||
// throw FormatError("Unsupported HANZI subset");
|
||||
}
|
||||
let count = bits.readBits(mode.CharacterCountBits(version) as usize)?;
|
||||
DecodeHanziSegment(&mut bits, count, &mut result)?;
|
||||
}
|
||||
_ => {
|
||||
// "Normal" QR code modes:
|
||||
// How many characters will follow, encoded in this mode?
|
||||
let count = bits.readBits(mode.CharacterCountBits(version) as usize)?;
|
||||
match mode {
|
||||
Mode::NUMERIC => DecodeNumericSegment(&mut bits, count, &mut result)?,
|
||||
Mode::ALPHANUMERIC => {
|
||||
DecodeAlphanumericSegment(&mut bits, count, &mut result)?
|
||||
}
|
||||
Mode::BYTE => DecodeByteSegment(&mut bits, count, &mut result)?,
|
||||
Mode::KANJI => DecodeKanjiSegment(&mut bits, count, &mut result)?,
|
||||
_ => return Err(Exceptions::format_with("Invalid CodecMode")), //throw FormatError("Invalid CodecMode");
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
})();
|
||||
|
||||
Ok(DecoderResult::with_eci_string_builder(result)
|
||||
.withError(res.err())
|
||||
.withEcLevel(ecLevel.to_string())
|
||||
.withVersionNumber(version.getVersionNumber())
|
||||
.withStructuredAppend(structuredAppend))
|
||||
}
|
||||
|
||||
pub fn Decode(bits: &BitMatrix) -> Result<DecoderResult<bool>> {
|
||||
let Ok(pversion) = ReadVersion(bits) else {
|
||||
return Err(Exceptions::format_with("Invalid version"))
|
||||
};
|
||||
let version = pversion;
|
||||
|
||||
let Ok(formatInfo) = ReadFormatInformation(bits, version.isMicroQRCode()) else {
|
||||
return Err(Exceptions::format_with("Invalid format information"))
|
||||
};
|
||||
|
||||
// Read codewords
|
||||
let codewords = ReadCodewords(bits, &version, &formatInfo)?;
|
||||
if (codewords.is_empty()) {
|
||||
return Err(Exceptions::format_with("Failed to read codewords"));
|
||||
}
|
||||
|
||||
// Separate into data blocks
|
||||
let dataBlocks: Vec<DataBlock> =
|
||||
DataBlock::getDataBlocks(&codewords, &version, formatInfo.error_correction_level)?;
|
||||
if (dataBlocks.is_empty()) {
|
||||
return Err(Exceptions::format_with("Failed to get data blocks"));
|
||||
}
|
||||
|
||||
// Count total number of data bytes
|
||||
let op = |totalBytes, dataBlock: &DataBlock| totalBytes + dataBlock.getNumDataCodewords();
|
||||
let totalBytes = dataBlocks.iter().fold(0, op); // std::accumulate(std::begin(dataBlocks), std::end(dataBlocks), int{}, op);
|
||||
let mut resultBytes = vec![0u8; totalBytes as usize];
|
||||
let mut resultIterator = 0; //resultBytes.begin();
|
||||
|
||||
// Error-correct and copy data blocks together into a stream of bytes
|
||||
for dataBlock in dataBlocks.iter() {
|
||||
let mut codewordBytes = dataBlock.getCodewords().to_vec();
|
||||
let numDataCodewords = dataBlock.getNumDataCodewords() as usize;
|
||||
|
||||
if !CorrectErrors(&mut codewordBytes, numDataCodewords as u32)? {
|
||||
return Err(Exceptions::CHECKSUM);
|
||||
}
|
||||
|
||||
// resultIterator = std::copy_n(codewordBytes.begin(), numDataCodewords, resultIterator);
|
||||
resultBytes[resultIterator..(resultIterator + numDataCodewords)]
|
||||
.copy_from_slice(&codewordBytes[..numDataCodewords]);
|
||||
resultIterator += numDataCodewords;
|
||||
}
|
||||
|
||||
// Decode the contents of that stream of bytes
|
||||
Ok(
|
||||
DecodeBitStream(&resultBytes, &version, formatInfo.error_correction_level)?
|
||||
.withIsMirrored(formatInfo.isMirrored),
|
||||
)
|
||||
}
|
||||
|
||||
// } // namespace ZXing::QRCode
|
||||
1060
src/qrcode/cpp_port/detector.rs
Normal file
11
src/qrcode/cpp_port/mod.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
mod data_mask;
|
||||
pub mod decoder;
|
||||
pub mod detector;
|
||||
mod qr_cpp_reader;
|
||||
|
||||
pub use qr_cpp_reader::QrReader;
|
||||
|
||||
mod bitmatrix_parser;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
379
src/qrcode/cpp_port/qr_cpp_reader.rs
Normal file
@@ -0,0 +1,379 @@
|
||||
/*
|
||||
* Copyright 2016 Nu-book Inc.
|
||||
* Copyright 2016 ZXing authors
|
||||
* Copyright 2022 Axel Waggershauser
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Result Reader::decode(const BinaryBitmap& image) const
|
||||
// {
|
||||
// #if 1
|
||||
// if (!_hints.isPure())
|
||||
// return FirstOrDefault(decode(image, 1));
|
||||
// #endif
|
||||
|
||||
// auto binImg = image.getBitMatrix();
|
||||
// if (binImg == nullptr)
|
||||
// return {};
|
||||
|
||||
// DetectorResult detectorResult;
|
||||
// if (_hints.hasFormat(BarcodeFormat::QRCode))
|
||||
// detectorResult = DetectPureQR(*binImg);
|
||||
// if (_hints.hasFormat(BarcodeFormat::MicroQRCode) && !detectorResult.isValid())
|
||||
// detectorResult = DetectPureMQR(*binImg);
|
||||
|
||||
// if (!detectorResult.isValid())
|
||||
// return {};
|
||||
|
||||
// auto decoderResult = Decode(detectorResult.bits());
|
||||
// auto position = detectorResult.position();
|
||||
|
||||
// return Result(std::move(decoderResult), std::move(position),
|
||||
// detectorResult.bits().width() < 21 ? BarcodeFormat::MicroQRCode : BarcodeFormat::QRCode);
|
||||
// }
|
||||
|
||||
// void logFPSet(const FinderPatternSet& fps [[maybe_unused]])
|
||||
// {
|
||||
// #ifdef PRINT_DEBUG
|
||||
// auto drawLine = [](PointF a, PointF b) {
|
||||
// int steps = maxAbsComponent(b - a);
|
||||
// PointF dir = bresenhamDirection(PointF(b - a));
|
||||
// for (int i = 0; i < steps; ++i)
|
||||
// log(centered(a + i * dir), 2);
|
||||
// };
|
||||
|
||||
// drawLine(fps.bl, fps.tl);
|
||||
// drawLine(fps.tl, fps.tr);
|
||||
// drawLine(fps.tr, fps.bl);
|
||||
// #endif
|
||||
// }
|
||||
|
||||
// Results Reader::decode(const BinaryBitmap& image, int maxSymbols) const
|
||||
// {
|
||||
// auto binImg = image.getBitMatrix();
|
||||
// if (binImg == nullptr)
|
||||
// return {};
|
||||
|
||||
// #ifdef PRINT_DEBUG
|
||||
// LogMatrixWriter lmw(log, *binImg, 5, "qr-log.pnm");
|
||||
// #endif
|
||||
|
||||
// auto allFPs = FindFinderPatterns(*binImg, _hints.tryHarder());
|
||||
|
||||
// #ifdef PRINT_DEBUG
|
||||
// printf("allFPs: %d\n", Size(allFPs));
|
||||
// #endif
|
||||
|
||||
// std::vector<ConcentricPattern> usedFPs;
|
||||
// Results results;
|
||||
|
||||
// if (_hints.hasFormat(BarcodeFormat::QRCode)) {
|
||||
// auto allFPSets = GenerateFinderPatternSets(allFPs);
|
||||
// for (const auto& fpSet : allFPSets) {
|
||||
// if (Contains(usedFPs, fpSet.bl) || Contains(usedFPs, fpSet.tl) || Contains(usedFPs, fpSet.tr))
|
||||
// continue;
|
||||
|
||||
// logFPSet(fpSet);
|
||||
|
||||
// auto detectorResult = SampleQR(*binImg, fpSet);
|
||||
// if (detectorResult.isValid()) {
|
||||
// auto decoderResult = Decode(detectorResult.bits());
|
||||
// auto position = detectorResult.position();
|
||||
// if (decoderResult.isValid()) {
|
||||
// usedFPs.push_back(fpSet.bl);
|
||||
// usedFPs.push_back(fpSet.tl);
|
||||
// usedFPs.push_back(fpSet.tr);
|
||||
// }
|
||||
// if (decoderResult.isValid(_hints.returnErrors())) {
|
||||
// results.emplace_back(std::move(decoderResult), std::move(position), BarcodeFormat::QRCode);
|
||||
// if (maxSymbols && Size(results) == maxSymbols)
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// if (_hints.hasFormat(BarcodeFormat::MicroQRCode) && !(maxSymbols && Size(results) == maxSymbols)) {
|
||||
// for (const auto& fp : allFPs) {
|
||||
// if (Contains(usedFPs, fp))
|
||||
// continue;
|
||||
|
||||
// auto detectorResult = SampleMQR(*binImg, fp);
|
||||
// if (detectorResult.isValid()) {
|
||||
// auto decoderResult = Decode(detectorResult.bits());
|
||||
// auto position = detectorResult.position();
|
||||
// if (decoderResult.isValid(_hints.returnErrors())) {
|
||||
// results.emplace_back(std::move(decoderResult), std::move(position), BarcodeFormat::MicroQRCode);
|
||||
// if (maxSymbols && Size(results) == maxSymbols)
|
||||
// break;
|
||||
// }
|
||||
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// return results;
|
||||
// }
|
||||
|
||||
// } // namespace ZXing::QRCode
|
||||
|
||||
use crate::{
|
||||
common::{cpp_essentials::ConcentricPattern, DetectorRXingResult, HybridBinarizer},
|
||||
multi::MultipleBarcodeReader,
|
||||
BarcodeFormat, BinaryBitmap, DecodeHintType, DecodeHintValue, DecodingHintDictionary,
|
||||
Exceptions, RXingResult, Reader,
|
||||
};
|
||||
|
||||
use super::{
|
||||
decoder::Decode,
|
||||
detector::{
|
||||
DetectPureMQR, DetectPureQR, FindFinderPatterns, GenerateFinderPatternSets, SampleMQR,
|
||||
SampleQR,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::qrcode::detector::QRCodeDetectorResult as DetectorResult;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct QrReader;
|
||||
|
||||
impl Reader for QrReader {
|
||||
fn decode<B: crate::Binarizer>(
|
||||
&mut self,
|
||||
image: &mut crate::BinaryBitmap<B>,
|
||||
) -> crate::common::Result<crate::RXingResult> {
|
||||
self.decode_with_hints(image, &DecodingHintDictionary::new())
|
||||
}
|
||||
|
||||
fn decode_with_hints<B: crate::Binarizer>(
|
||||
&mut self,
|
||||
image: &mut crate::BinaryBitmap<B>,
|
||||
hints: &crate::DecodingHintDictionary,
|
||||
) -> crate::common::Result<RXingResult> {
|
||||
// #if 1
|
||||
if !matches!(
|
||||
hints.get(&DecodeHintType::PURE_BARCODE),
|
||||
Some(DecodeHintValue::PureBarcode(true))
|
||||
)
|
||||
// if !matches!(Some(hints.get(&DecodeHintType::PURE_BARCODE)))
|
||||
// if (!_hints.isPure())
|
||||
{
|
||||
return Ok(self
|
||||
.decode_set_number_with_hints(image, hints, 1)?
|
||||
.first()
|
||||
.ok_or(Exceptions::NOT_FOUND)?
|
||||
.clone());
|
||||
// return FirstOrDefault(decode(image, 1));
|
||||
}
|
||||
// #endif
|
||||
|
||||
let binImg = image.get_black_matrix(); //image.getBitMatrix();
|
||||
// if (binImg == nullptr)
|
||||
// {return {};}
|
||||
|
||||
let mut detectorResult = Err(Exceptions::NOT_FOUND);
|
||||
if let Some(DecodeHintValue::PossibleFormats(formats)) =
|
||||
hints.get(&DecodeHintType::POSSIBLE_FORMATS)
|
||||
{
|
||||
if formats.contains(&BarcodeFormat::QR_CODE) {
|
||||
detectorResult = DetectPureQR(binImg);
|
||||
}
|
||||
if formats.contains(&BarcodeFormat::MICRO_QR_CODE) && detectorResult.is_err() {
|
||||
detectorResult = DetectPureMQR(binImg);
|
||||
}
|
||||
}
|
||||
|
||||
if detectorResult.is_err() {
|
||||
for decode_function in [DetectPureQR, DetectPureMQR] {
|
||||
detectorResult = decode_function(binImg);
|
||||
if detectorResult.is_ok() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let detectorResult = detectorResult?;
|
||||
|
||||
// let detectorResult: DetectorResult;
|
||||
// if (_hints.hasFormat(BarcodeFormat::QR_CODE))
|
||||
// {detectorResult = DetectPureQR(binImg);}
|
||||
|
||||
// if (_hints.hasFormat(BarcodeFormat::MICRO_QR_CODE) && !detectorResult.isValid())
|
||||
// {detectorResult = DetectPureMQR(binImg);}
|
||||
|
||||
// if (!detectorResult.isValid())
|
||||
// {return {};}
|
||||
|
||||
let decoderResult = Decode(detectorResult.getBits())?;
|
||||
let position = detectorResult.getPoints();
|
||||
|
||||
Ok(RXingResult::with_decoder_result(
|
||||
decoderResult,
|
||||
position,
|
||||
if detectorResult.getBits().width() < 21 {
|
||||
BarcodeFormat::MICRO_QR_CODE
|
||||
} else {
|
||||
BarcodeFormat::QR_CODE
|
||||
},
|
||||
))
|
||||
|
||||
// Ok(RXingResult::new(
|
||||
// &decoderResult.content().to_string(),
|
||||
// decoderResult.content().bytes().to_vec(),
|
||||
// position.to_vec(),
|
||||
// if detectorResult.getBits().width() < 21 {
|
||||
// BarcodeFormat::MICRO_QR_CODE
|
||||
// } else {
|
||||
// BarcodeFormat::QR_CODE
|
||||
// },
|
||||
// ))
|
||||
// return Result(std::move(decoderResult), std::move(position),
|
||||
// detectorResult.bits().width() < 21 ? BarcodeFormat::MICRO_QR_CODE : BarcodeFormat::QR_CODE);
|
||||
}
|
||||
}
|
||||
|
||||
impl MultipleBarcodeReader for QrReader {
|
||||
fn decode_multiple<B: crate::Binarizer>(
|
||||
&mut self,
|
||||
image: &mut crate::BinaryBitmap<B>,
|
||||
) -> crate::common::Result<Vec<crate::RXingResult>> {
|
||||
self.decode_multiple_with_hints(image, &DecodingHintDictionary::new())
|
||||
}
|
||||
|
||||
fn decode_multiple_with_hints<B: crate::Binarizer>(
|
||||
&mut self,
|
||||
image: &mut crate::BinaryBitmap<B>,
|
||||
hints: &DecodingHintDictionary,
|
||||
) -> crate::common::Result<Vec<crate::RXingResult>> {
|
||||
self.decode_set_number_with_hints(image, hints, u32::MAX)
|
||||
}
|
||||
}
|
||||
|
||||
impl QrReader {
|
||||
fn decode_set_number_with_hints<B: crate::Binarizer>(
|
||||
&mut self,
|
||||
image: &mut crate::BinaryBitmap<B>,
|
||||
hints: &DecodingHintDictionary,
|
||||
count: u32,
|
||||
) -> crate::common::Result<Vec<RXingResult>> {
|
||||
let binImg = image.get_black_matrix(); //image.getBitMatrix();
|
||||
let maxSymbols = count;
|
||||
// if (binImg == nullptr)
|
||||
// {return {};}
|
||||
|
||||
// #ifdef PRINT_DEBUG
|
||||
// LogMatrixWriter lmw(log, *binImg, 5, "qr-log.pnm");
|
||||
// #endif
|
||||
let try_harder = matches!(
|
||||
hints.get(&DecodeHintType::TRY_HARDER),
|
||||
Some(DecodeHintValue::TryHarder(true))
|
||||
);
|
||||
|
||||
let mut allFPs = FindFinderPatterns(binImg, try_harder);
|
||||
|
||||
// #ifdef PRINT_DEBUG
|
||||
// printf("allFPs: %d\n", Size(allFPs));
|
||||
// #endif
|
||||
|
||||
let mut usedFPs: Vec<ConcentricPattern> = Vec::new();
|
||||
let mut results: Vec<RXingResult> = Vec::new();
|
||||
|
||||
let (check_qr, check_mqr) = if let Some(DecodeHintValue::PossibleFormats(formats)) =
|
||||
hints.get(&DecodeHintType::POSSIBLE_FORMATS)
|
||||
{
|
||||
(
|
||||
formats.contains(&BarcodeFormat::QR_CODE),
|
||||
formats.contains(&BarcodeFormat::MICRO_QR_CODE),
|
||||
)
|
||||
} else {
|
||||
(true, true)
|
||||
};
|
||||
|
||||
if check_qr {
|
||||
// if (_hints.hasFormat(BarcodeFormat::QRCode)) {
|
||||
let allFPSets = GenerateFinderPatternSets(&mut allFPs);
|
||||
for fpSet in allFPSets {
|
||||
// for (const auto& fpSet : allFPSets) {
|
||||
if (usedFPs.contains(&fpSet.bl)
|
||||
|| usedFPs.contains(&fpSet.tl)
|
||||
|| usedFPs.contains(&fpSet.tr))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// logFPSet(fpSet);
|
||||
|
||||
let detectorResult = SampleQR(binImg, &fpSet);
|
||||
if let Ok(detectorResult) = detectorResult {
|
||||
// if (detectorResult.is_ok()) {
|
||||
let decoderResult = Decode(detectorResult.getBits());
|
||||
let position = detectorResult.getPoints();
|
||||
if let Ok(decoderResult) = decoderResult {
|
||||
if (decoderResult.isValid()) {
|
||||
usedFPs.push(fpSet.bl);
|
||||
usedFPs.push(fpSet.tl);
|
||||
usedFPs.push(fpSet.tr);
|
||||
}
|
||||
|
||||
if (decoderResult.isValid()) {
|
||||
// results.push(RXingResult::new(
|
||||
// &decoderResult.content().to_string(),
|
||||
// decoderResult.content().bytes().to_vec(),
|
||||
// position.to_vec(),
|
||||
// BarcodeFormat::QR_CODE,
|
||||
// ));
|
||||
results.push(RXingResult::with_decoder_result(
|
||||
decoderResult,
|
||||
position,
|
||||
BarcodeFormat::QR_CODE,
|
||||
));
|
||||
// results.emplace_back(std::move(decoderResult), std::move(position), BarcodeFormat::QR_CODE);
|
||||
|
||||
if (maxSymbols != 0 && (results.len() as u32) == maxSymbols) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (check_mqr && !(maxSymbols != 0 && (results.len() as u32) == maxSymbols)) {
|
||||
// if (_hints.hasFormat(BarcodeFormat::MicroQRCode) && !(maxSymbols && Size(results) == maxSymbols)) {
|
||||
for fp in allFPs {
|
||||
// for (const auto& fp : allFPs) {
|
||||
if (usedFPs.contains(&fp)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let detectorResult = SampleMQR(binImg, fp);
|
||||
if let Ok(detectorResult) = detectorResult {
|
||||
// if (detectorResult.is_ok()) {
|
||||
let decoderResult = Decode(detectorResult.getBits());
|
||||
let position = detectorResult.getPoints();
|
||||
if let Ok(decoderResult) = decoderResult {
|
||||
if (decoderResult.isValid()) {
|
||||
results.push(RXingResult::with_decoder_result(
|
||||
decoderResult,
|
||||
position,
|
||||
BarcodeFormat::MICRO_QR_CODE,
|
||||
));
|
||||
// results.push(RXingResult::new(
|
||||
// &decoderResult.content().to_string(),
|
||||
// decoderResult.content().bytes().to_vec(),
|
||||
// position.to_vec(),
|
||||
// BarcodeFormat::MICRO_QR_CODE,
|
||||
// ));
|
||||
// results.emplace_back(std::move(decoderResult), std::move(position), BarcodeFormat::MICRO_QR_CODE);
|
||||
|
||||
if (maxSymbols != 0 && (results.len() as u32) == maxSymbols) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(results);
|
||||
}
|
||||
}
|
||||
125
src/qrcode/cpp_port/test/MQRDecoderTest.rs
Normal file
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright 2017 Huy Cuong Nguyen
|
||||
* Copyright 2008 ZXing authors
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::{common::BitMatrix, qrcode::cpp_port::decoder::Decode, Exceptions};
|
||||
|
||||
#[test]
|
||||
fn MQRCodeM3L() {
|
||||
const CODE_STR: &str = r"XXXXXXX X X X X
|
||||
X X X X
|
||||
X XXX X XXXXXXX
|
||||
X XXX X X X XX
|
||||
X XXX X X XX
|
||||
X X X X X X
|
||||
XXXXXXX X XX
|
||||
X X X
|
||||
XXXXXX X X X
|
||||
X XX XXX
|
||||
XXX XX XXXX XXX
|
||||
X X XXX X
|
||||
X XXXXX XXX X X
|
||||
X X X XXX
|
||||
XXX XX X X XXXX
|
||||
";
|
||||
let bitMatrix = BitMatrix::parse_strings(CODE_STR, "X", " ").unwrap();
|
||||
|
||||
let result = Decode(&bitMatrix).unwrap();
|
||||
assert!(result.isValid());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn MQRCodeM3M() {
|
||||
const CODE_STR: &str = r"XXXXXXX X X X X
|
||||
X X XX
|
||||
X XXX X X XX XX
|
||||
X XXX X X X
|
||||
X XXX X XX XXXX
|
||||
X X XX
|
||||
XXXXXXX X XXXX
|
||||
X XXX
|
||||
X XX XX X X
|
||||
X X XX
|
||||
XX XX XXXXXXX
|
||||
X X X
|
||||
XX X X X
|
||||
X X X
|
||||
X X XXXX XXX
|
||||
";
|
||||
let bitMatrix = BitMatrix::parse_strings(CODE_STR, "X", " ").unwrap();
|
||||
|
||||
let result = Decode(&bitMatrix).unwrap();
|
||||
assert!(result.isValid());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn MQRCodeM1() {
|
||||
const CODE_STR: &str = r"XXXXXXX X X
|
||||
X X
|
||||
X XXX X XXX
|
||||
X XXX X XX
|
||||
X XXX X X
|
||||
X X XX
|
||||
XXXXXXX X
|
||||
X
|
||||
XX X
|
||||
X XXXXX X
|
||||
X XXXXXX X
|
||||
";
|
||||
let bitMatrix = BitMatrix::parse_strings(CODE_STR, "X", " ").unwrap();
|
||||
let result = Decode(&bitMatrix).unwrap();
|
||||
assert!(result.isValid());
|
||||
assert_eq!("123", result.text());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn MQRCodeM1Error4Bits() {
|
||||
const CODE_STR: &str = r"XXXXXXX X X
|
||||
X X XX
|
||||
X XXX X X
|
||||
X XXX X XX
|
||||
X XXX X X
|
||||
X X XX
|
||||
XXXXXXX X
|
||||
X
|
||||
XX X
|
||||
X XXXXXX
|
||||
X XXXXXXX
|
||||
";
|
||||
let bitMatrix = BitMatrix::parse_strings(CODE_STR, "X", " ").unwrap();
|
||||
let result = Decode(&bitMatrix);
|
||||
dbg!(&result);
|
||||
assert!(matches!(
|
||||
result.err(),
|
||||
Some(Exceptions::ReedSolomonException(_))
|
||||
));
|
||||
// assert_eq!(Error::Checksum, result.error());
|
||||
// assert!(result.text().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn MQRCodeM4() {
|
||||
const CODE_STR: &str = r"XXXXXXX X X X X X
|
||||
X X XX X XX
|
||||
X XXX X X X XX
|
||||
X XXX X XX XX XX
|
||||
X XXX X X XXXXX
|
||||
X X XX X
|
||||
XXXXXXX XX X XX
|
||||
X XX XX
|
||||
X X XXX X XXX
|
||||
XX X XX XX X
|
||||
XX XXXX X XX XX
|
||||
XX XX X XX XX
|
||||
XXX XXX XXX XX XX
|
||||
X X X XX X
|
||||
X X XX XXXXX
|
||||
X X X X X
|
||||
X XXXXXXX X X X
|
||||
";
|
||||
let bitMatrix = BitMatrix::parse_strings(CODE_STR, "X", " ").unwrap();
|
||||
let result = Decode(&bitMatrix).unwrap();
|
||||
assert!(result.isValid());
|
||||
}
|
||||
78
src/qrcode/cpp_port/test/QRBitMatrixParserTest.rs
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2017 Huy Cuong Nguyen
|
||||
* Copyright 2008 ZXing authors
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::{
|
||||
common::BitMatrix,
|
||||
qrcode::cpp_port::bitmatrix_parser::{ReadCodewords, ReadFormatInformation, ReadVersion},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn MQRCodeM3L() {
|
||||
let bitMatrix = BitMatrix::parse_strings(
|
||||
r"XXXXXXX X X X X
|
||||
X X X X
|
||||
X XXX X XXXXXXX
|
||||
X XXX X X X XX
|
||||
X XXX X X XX
|
||||
X X X X X X
|
||||
XXXXXXX X XX
|
||||
X X X
|
||||
XXXXXX X X X
|
||||
X XX XXX
|
||||
XXX XX XXXX XXX
|
||||
X X XXX X
|
||||
X XXXXX XXX X X
|
||||
X X X XXX
|
||||
XXX XX X X XXXX
|
||||
",
|
||||
"X",
|
||||
" ",
|
||||
)
|
||||
.expect("parse must parse");
|
||||
|
||||
let version = ReadVersion(&bitMatrix).unwrap();
|
||||
assert_eq!(3, version.getVersionNumber());
|
||||
let format =
|
||||
ReadFormatInformation(&bitMatrix, true).expect("could not read format information");
|
||||
let codewords = ReadCodewords(&bitMatrix, version, &format).expect("could not read codewords");
|
||||
assert_eq!(17, codewords.len());
|
||||
assert_eq!(0x0, codewords[10]);
|
||||
assert_eq!(0xd1, codewords[11]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn MQRCodeM3M() {
|
||||
let bitMatrix = BitMatrix::parse_strings(
|
||||
r"XXXXXXX X X X X
|
||||
X X XX
|
||||
X XXX X X XX XX
|
||||
X XXX X X X
|
||||
X XXX X XX XXXX
|
||||
X X XX
|
||||
XXXXXXX X XXXX
|
||||
X XXX
|
||||
X XX XX X X
|
||||
X X XX
|
||||
XX XX XXXXXXX
|
||||
X X X
|
||||
XX X X X
|
||||
X X X
|
||||
X X XXXX XXX
|
||||
",
|
||||
"X",
|
||||
" ",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let version = ReadVersion(&bitMatrix).unwrap();
|
||||
assert_eq!(3, version.getVersionNumber());
|
||||
let format =
|
||||
ReadFormatInformation(&bitMatrix, true).expect("could not read format information");
|
||||
let codewords = ReadCodewords(&bitMatrix, version, &format).expect("could not read codewords");
|
||||
assert_eq!(17, codewords.len());
|
||||
assert_eq!(0x0, codewords[8]);
|
||||
assert_eq!(0x89, codewords[9]);
|
||||
}
|
||||
135
src/qrcode/cpp_port/test/QRDataMaskTest.rs
Normal file
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright 2017 Huy Cuong Nguyen
|
||||
* Copyright 2007 ZXing authors
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::{common::BitMatrix, qrcode::cpp_port::data_mask::GetMaskedBit};
|
||||
|
||||
#[test]
|
||||
fn Mask0() {
|
||||
TestMaskAcrossDimensions(0, |i, j| {
|
||||
return (i + j) % 2 == 0;
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn Mask1() {
|
||||
TestMaskAcrossDimensions(1, |i, _| {
|
||||
return i % 2 == 0;
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn Mask2() {
|
||||
TestMaskAcrossDimensions(2, |_, j| {
|
||||
return j % 3 == 0;
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn Mask3() {
|
||||
TestMaskAcrossDimensions(3, |i, j| {
|
||||
return (i + j) % 3 == 0;
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn Mask4() {
|
||||
TestMaskAcrossDimensions(4, |i, j| {
|
||||
return (i / 2 + j / 3) % 2 == 0;
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn Mask5() {
|
||||
TestMaskAcrossDimensions(5, |i, j| {
|
||||
return (i * j) % 2 + (i * j) % 3 == 0;
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn Mask6() {
|
||||
TestMaskAcrossDimensions(6, |i, j| {
|
||||
return ((i * j) % 2 + (i * j) % 3) % 2 == 0;
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn Mask7() {
|
||||
TestMaskAcrossDimensions(7, |i, j| {
|
||||
return ((i + j) % 2 + (i * j) % 3) % 2 == 0;
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn MicroMask0() {
|
||||
TestMicroMaskAcrossDimensions(0, |i, _| {
|
||||
return i % 2 == 0;
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn MicroMask1() {
|
||||
TestMicroMaskAcrossDimensions(1, |i, j| {
|
||||
return (i / 2 + j / 3) % 2 == 0;
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn MicroMask2() {
|
||||
TestMicroMaskAcrossDimensions(2, |i, j| {
|
||||
return ((i * j) % 2 + (i * j) % 3) % 2 == 0;
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn MicroMask3() {
|
||||
TestMicroMaskAcrossDimensions(3, |i, j| {
|
||||
return ((i + j) % 2 + (i * j) % 3) % 2 == 0;
|
||||
});
|
||||
}
|
||||
|
||||
fn TestMaskAcrossDimensionsImpl<F>(
|
||||
maskIndex: u32,
|
||||
isMicro: bool,
|
||||
versionMax: u32,
|
||||
dimensionStart: u32,
|
||||
dimensionStep: u32,
|
||||
condition: F,
|
||||
) where
|
||||
F: Fn(u32, u32) -> bool,
|
||||
{
|
||||
for version in 1..=versionMax {
|
||||
// for (int version = 1; version <= versionMax; version++) {
|
||||
let dimension = dimensionStart + dimensionStep * version;
|
||||
let bits = BitMatrix::with_single_dimension(dimension).expect("couldn't create bitmatrix");
|
||||
|
||||
for i in 0..dimension {
|
||||
// for (int i = 0; i < dimension; i++)
|
||||
for j in 0..dimension {
|
||||
// for (int j = 0; j < dimension; j++)
|
||||
assert_eq!(
|
||||
GetMaskedBit(&bits, j, i, maskIndex, Some(isMicro))
|
||||
.expect("could not get mask bit"),
|
||||
condition(i, j),
|
||||
"({i},{j})"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn TestMaskAcrossDimensions<F>(maskIndex: u32, condition: F)
|
||||
where
|
||||
F: Fn(u32, u32) -> bool,
|
||||
{
|
||||
TestMaskAcrossDimensionsImpl(maskIndex, false, 40, 17, 4, condition);
|
||||
}
|
||||
|
||||
fn TestMicroMaskAcrossDimensions<F>(maskIndex: u32, condition: F)
|
||||
where
|
||||
F: Fn(u32, u32) -> bool,
|
||||
{
|
||||
TestMaskAcrossDimensionsImpl(maskIndex, true, 4, 9, 2, condition);
|
||||
}
|
||||
190
src/qrcode/cpp_port/test/QRDecodedBitStreamParserTest.rs
Normal file
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* Copyright 2017 Huy Cuong Nguyen
|
||||
* Copyright 2008 ZXing authors
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// #include "BitArray.h"
|
||||
// #include "ByteArray.h"
|
||||
// #include "DecoderResult.h"
|
||||
// #include "qrcode/QRDataMask.h"
|
||||
// #include "qrcode/QRDecoder.h"
|
||||
// #include "qrcode/QRErrorCorrectionLevel.h"
|
||||
// #include "qrcode/QRVersion.h"
|
||||
|
||||
// #include "gtest/gtest.h"
|
||||
|
||||
// namespace ZXing {
|
||||
// namespace QRCode {
|
||||
// DecoderResult DecodeBitStream(ByteArray&& bytes, const Version& version, ErrorCorrectionLevel ecLevel);
|
||||
// }
|
||||
// }
|
||||
|
||||
// using namespace ZXing;
|
||||
// using namespace ZXing::QRCode;
|
||||
|
||||
use crate::common::Result;
|
||||
|
||||
use crate::{
|
||||
common::{cpp_essentials::DecoderResult, BitArray},
|
||||
qrcode::{
|
||||
cpp_port::decoder::DecodeBitStream,
|
||||
decoder::{ErrorCorrectionLevel, Version},
|
||||
},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn SimpleByteMode() {
|
||||
let mut ba = BitArray::new();
|
||||
ba.appendBits(0x04, 4).unwrap(); // Byte mode
|
||||
ba.appendBits(0x03, 8).unwrap(); // 3 bytes
|
||||
ba.appendBits(0xF1, 8).unwrap();
|
||||
ba.appendBits(0xF2, 8).unwrap();
|
||||
ba.appendBits(0xF3, 8).unwrap();
|
||||
let bytes: Vec<u8> = ba.into();
|
||||
let result = DecodeBitStream(
|
||||
&bytes,
|
||||
Version::FromNumber(1, false).expect("find_version"),
|
||||
ErrorCorrectionLevel::M,
|
||||
)
|
||||
.expect("Decode")
|
||||
.content()
|
||||
.to_string();
|
||||
let str = String::from_utf16(&[0xF1, 0xF2, 0xF3]).unwrap();
|
||||
assert_eq!(str, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn SimpleSJIS() {
|
||||
let mut ba = BitArray::new();
|
||||
ba.appendBits(0x04, 4).expect("append"); // Byte mode
|
||||
ba.appendBits(0x04, 8).expect("append"); // 4 bytes
|
||||
ba.appendBits(0xA1, 8).expect("append");
|
||||
ba.appendBits(0xA2, 8).expect("append");
|
||||
ba.appendBits(0xA3, 8).expect("append");
|
||||
ba.appendBits(0xD0, 8).expect("append");
|
||||
let bytes: Vec<u8> = ba.into();
|
||||
let result = DecodeBitStream(
|
||||
&bytes,
|
||||
Version::FromNumber(1, false).unwrap(),
|
||||
ErrorCorrectionLevel::M,
|
||||
)
|
||||
.unwrap()
|
||||
.content()
|
||||
.to_string();
|
||||
assert_eq!("\u{ff61}\u{ff62}\u{ff63}\u{ff90}", result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ECI() {
|
||||
let mut ba = BitArray::new();
|
||||
ba.appendBits(0x07, 4).expect("append"); // ECI mode
|
||||
ba.appendBits(0x02, 8).expect("append"); // ECI 2 = CP437 encoding
|
||||
ba.appendBits(0x04, 4).expect("append"); // Byte mode
|
||||
ba.appendBits(0x03, 8).expect("append"); // 3 bytes
|
||||
ba.appendBits(0xA1, 8).expect("append");
|
||||
ba.appendBits(0xA2, 8).expect("append");
|
||||
ba.appendBits(0xA3, 8).expect("append");
|
||||
let bytes: Vec<u8> = ba.into();
|
||||
let result = DecodeBitStream(
|
||||
&bytes,
|
||||
Version::FromNumber(1, false).unwrap(),
|
||||
ErrorCorrectionLevel::M,
|
||||
)
|
||||
.unwrap()
|
||||
.content()
|
||||
.to_string();
|
||||
assert_eq!("\u{ED}\u{F3}\u{FA}", result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn Hanzi() {
|
||||
let mut ba = BitArray::new();
|
||||
ba.appendBits(0x0D, 4).expect("append"); // Hanzi mode
|
||||
ba.appendBits(0x01, 4).expect("append"); // Subset 1 = GB2312 encoding
|
||||
ba.appendBits(0x01, 8).expect("append"); // 1 characters
|
||||
ba.appendBits(0x03C1, 13).expect("append");
|
||||
let bytes: Vec<u8> = ba.into();
|
||||
let result = DecodeBitStream(
|
||||
&bytes,
|
||||
Version::FromNumber(1, false).unwrap(),
|
||||
ErrorCorrectionLevel::M,
|
||||
)
|
||||
.unwrap()
|
||||
.content()
|
||||
.to_string();
|
||||
assert_eq!("\u{963f}", result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn HanziLevel1() {
|
||||
let mut ba = BitArray::new();
|
||||
ba.appendBits(0x0D, 4).expect("append"); // Hanzi mode
|
||||
ba.appendBits(0x01, 4).expect("append"); // Subset 1 = GB2312 encoding
|
||||
ba.appendBits(0x01, 8).expect("append"); // 1 characters
|
||||
// A5A2 (U+30A2) => A5A2 - A1A1 = 401, 4*60 + 01 = 0181
|
||||
ba.appendBits(0x0181, 13).expect("append");
|
||||
|
||||
let bytes: Vec<u8> = ba.into();
|
||||
|
||||
let result = DecodeBitStream(
|
||||
&bytes,
|
||||
Version::FromNumber(1, false).unwrap(),
|
||||
ErrorCorrectionLevel::M,
|
||||
)
|
||||
.unwrap()
|
||||
.content()
|
||||
.to_string();
|
||||
assert_eq!("\u{30a2}", result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn SymbologyIdentifier() {
|
||||
let version = Version::FromNumber(1, false).unwrap();
|
||||
let ecLevel = ErrorCorrectionLevel::M;
|
||||
|
||||
// Plain "ANUM(1) A"
|
||||
let result = DecodeBitStream(&[0x20, 0x09, 0x40], version, ecLevel).unwrap();
|
||||
assert_eq!(result.symbologyIdentifier(), "]Q1");
|
||||
assert_eq!(result.text(), "A");
|
||||
|
||||
// GS1 "FNC1(1st) NUM(4) 2001"
|
||||
let result = DecodeBitStream(&[0x51, 0x01, 0x0C, 0x81, 0x00], version, ecLevel).unwrap();
|
||||
assert_eq!(result.symbologyIdentifier(), "]Q3");
|
||||
assert_eq!(result.text(), "2001"); // "(20)01"
|
||||
|
||||
// GS1 "NUM(4) 2001 FNC1(1st) 301" - FNC1(1st) can occur anywhere (this actually violates the specification)
|
||||
let result = DecodeBitStream(
|
||||
&[0x10, 0x10, 0xC8, 0x15, 0x10, 0x0D, 0x2D, 0x00],
|
||||
version,
|
||||
ecLevel,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(result.symbologyIdentifier(), "]Q3");
|
||||
assert_eq!(result.text(), "2001301"); // "(20)01(30)1"
|
||||
|
||||
// AIM "FNC1(2nd) 99 (0x63) ANUM(1) A"
|
||||
let result = DecodeBitStream(&[0x96, 0x32, 0x00, 0x94, 0x00], version, ecLevel).unwrap();
|
||||
assert_eq!(result.symbologyIdentifier(), "]Q5");
|
||||
assert_eq!(result.text(), "99A");
|
||||
|
||||
// AIM "BYTE(1) A FNC1(2nd) 99 (0x63) BYTE(1) B" - FNC1(2nd) can occur anywhere
|
||||
// Disabled this test, since this violates the specification and the code does support it anymore
|
||||
// result = DecodeBitStream({0x40, 0x14, 0x19, 0x63, 0x40, 0x14, 0x20, 0x00}, version, ecLevel, "");
|
||||
// assert_eq!(result.symbologyIdentifier(), "]Q5");
|
||||
// assert_eq!(result.text(), L"99AB"); // Application Indicator prefixed to data
|
||||
|
||||
// AIM "FNC1(2nd) A (100 + 61 = 0xA5) ANUM(1) B"
|
||||
let result = DecodeBitStream(&[0x9A, 0x52, 0x00, 0x96, 0x00], version, ecLevel).unwrap();
|
||||
assert_eq!(result.symbologyIdentifier(), "]Q5");
|
||||
assert_eq!(result.text(), "AB");
|
||||
|
||||
// AIM "FNC1(2nd) a (100 + 97 = 0xC5) ANUM(1) B"
|
||||
let result = DecodeBitStream(&[0x9C, 0x52, 0x00, 0x96, 0x00], version, ecLevel).unwrap();
|
||||
assert_eq!(result.symbologyIdentifier(), "]Q5");
|
||||
assert_eq!(result.text(), "aB");
|
||||
|
||||
// Bad AIM Application Indicator "FNC1(2nd) @ (0xA4) ANUM(1) B"
|
||||
let result = DecodeBitStream(&[0x9A, 0x42, 0x00, 0x96, 0x00], version, ecLevel).unwrap();
|
||||
assert!(!result.isValid());
|
||||
}
|
||||
657
src/qrcode/cpp_port/test/QREncoderTest.cpp
Normal file
@@ -0,0 +1,657 @@
|
||||
/*
|
||||
* Copyright 2008 ZXing authors
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "BitArray.h"
|
||||
#include "BitArrayUtility.h"
|
||||
#include "BitMatrixIO.h"
|
||||
#include "CharacterSet.h"
|
||||
#include "TextDecoder.h"
|
||||
#include "Utf.h"
|
||||
#include "qrcode/QREncoder.h"
|
||||
#include "qrcode/QRCodecMode.h"
|
||||
#include "qrcode/QREncodeResult.h"
|
||||
#include "qrcode/QRErrorCorrectionLevel.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace ZXing {
|
||||
namespace QRCode {
|
||||
int GetAlphanumericCode(int code);
|
||||
CodecMode ChooseMode(const std::wstring& content, CharacterSet encoding);
|
||||
void AppendModeInfo(CodecMode mode, BitArray& bits);
|
||||
void AppendLengthInfo(int numLetters, const Version& version, CodecMode mode, BitArray& bits);
|
||||
void AppendNumericBytes(const std::wstring& content, BitArray& bits);
|
||||
void AppendAlphanumericBytes(const std::wstring& content, BitArray& bits);
|
||||
void Append8BitBytes(const std::wstring& content, CharacterSet encoding, BitArray& bits);
|
||||
void AppendKanjiBytes(const std::wstring& content, BitArray& bits);
|
||||
void AppendBytes(const std::wstring& content, CodecMode mode, CharacterSet encoding, BitArray& bits);
|
||||
void TerminateBits(int numDataBytes, BitArray& bits);
|
||||
void GetNumDataBytesAndNumECBytesForBlockID(int numTotalBytes, int numDataBytes, int numRSBlocks, int blockID, int& numDataBytesInBlock, int& numECBytesInBlock);
|
||||
void GenerateECBytes(const ByteArray& dataBytes, int numEcBytesInBlock, ByteArray& ecBytes);
|
||||
BitArray InterleaveWithECBytes(const BitArray& bits, int numTotalBytes, int numDataBytes, int numRSBlocks);
|
||||
}
|
||||
}
|
||||
|
||||
using namespace ZXing;
|
||||
using namespace ZXing::QRCode;
|
||||
using namespace ZXing::Utility;
|
||||
|
||||
namespace {
|
||||
std::wstring ShiftJISString(const std::vector<uint8_t>& bytes)
|
||||
{
|
||||
std::string str;
|
||||
TextDecoder::Append(str, bytes.data(), bytes.size(), CharacterSet::Shift_JIS);
|
||||
return FromUtf8(str);
|
||||
}
|
||||
|
||||
std::string RemoveSpace(std::string s)
|
||||
{
|
||||
s.erase(std::remove(s.begin(), s.end(), ' '), s.end());
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(QREncoderTest, GetAlphanumericCode)
|
||||
{
|
||||
// The first ten code points are numbers.
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
EXPECT_EQ(i, GetAlphanumericCode('0' + i));
|
||||
}
|
||||
|
||||
// The next 26 code points are capital alphabet letters.
|
||||
for (int i = 10; i < 36; ++i) {
|
||||
EXPECT_EQ(i, GetAlphanumericCode('A' + i - 10));
|
||||
}
|
||||
|
||||
// Others are symbol letters
|
||||
EXPECT_EQ(36, GetAlphanumericCode(' '));
|
||||
EXPECT_EQ(37, GetAlphanumericCode('$'));
|
||||
EXPECT_EQ(38, GetAlphanumericCode('%'));
|
||||
EXPECT_EQ(39, GetAlphanumericCode('*'));
|
||||
EXPECT_EQ(40, GetAlphanumericCode('+'));
|
||||
EXPECT_EQ(41, GetAlphanumericCode('-'));
|
||||
EXPECT_EQ(42, GetAlphanumericCode('.'));
|
||||
EXPECT_EQ(43, GetAlphanumericCode('/'));
|
||||
EXPECT_EQ(44, GetAlphanumericCode(':'));
|
||||
|
||||
// Should return -1 for other letters;
|
||||
EXPECT_EQ(-1, GetAlphanumericCode('a'));
|
||||
EXPECT_EQ(-1, GetAlphanumericCode('#'));
|
||||
EXPECT_EQ(-1, GetAlphanumericCode('\0'));
|
||||
}
|
||||
|
||||
TEST(QREncoderTest, ChooseMode)
|
||||
{
|
||||
// Numeric mode.
|
||||
EXPECT_EQ(CodecMode::NUMERIC, ChooseMode(L"0", CharacterSet::Unknown));
|
||||
EXPECT_EQ(CodecMode::NUMERIC, ChooseMode(L"0123456789", CharacterSet::Unknown));
|
||||
// Alphanumeric mode.
|
||||
EXPECT_EQ(CodecMode::ALPHANUMERIC, ChooseMode(L"A", CharacterSet::Unknown));
|
||||
EXPECT_EQ(CodecMode::ALPHANUMERIC,
|
||||
ChooseMode(L"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:", CharacterSet::Unknown));
|
||||
// 8-bit byte mode.
|
||||
EXPECT_EQ(CodecMode::BYTE, ChooseMode(L"a", CharacterSet::Unknown));
|
||||
EXPECT_EQ(CodecMode::BYTE, ChooseMode(L"#", CharacterSet::Unknown));
|
||||
EXPECT_EQ(CodecMode::BYTE, ChooseMode(L"", CharacterSet::Unknown));
|
||||
// Kanji mode. We used to use MODE_KANJI for these, but we stopped
|
||||
// doing that as we cannot distinguish Shift_JIS from other encodings
|
||||
// from data bytes alone. See also comments in qrcode_encoder.h.
|
||||
|
||||
// AIUE in Hiragana in Shift_JIS
|
||||
EXPECT_EQ(CodecMode::BYTE,
|
||||
ChooseMode(ShiftJISString({0x8, 0xa, 0x8, 0xa, 0x8, 0xa, 0x8, 0xa6}), CharacterSet::Unknown));
|
||||
|
||||
// Nihon in Kanji in Shift_JIS.
|
||||
EXPECT_EQ(CodecMode::BYTE, ChooseMode(ShiftJISString({0x9, 0xf, 0x9, 0x7b}), CharacterSet::Unknown));
|
||||
|
||||
// Sou-Utsu-Byou in Kanji in Shift_JIS.
|
||||
EXPECT_EQ(CodecMode::BYTE, ChooseMode(ShiftJISString({0xe, 0x4, 0x9, 0x5, 0x9, 0x61}), CharacterSet::Unknown));
|
||||
}
|
||||
|
||||
TEST(QREncoderTest, Encode)
|
||||
{
|
||||
auto qrCode = Encode(L"ABCDEF", ErrorCorrectionLevel::High, CharacterSet::Unknown, 0, false, -1);
|
||||
EXPECT_EQ(qrCode.mode, CodecMode::ALPHANUMERIC);
|
||||
EXPECT_EQ(qrCode.ecLevel, ErrorCorrectionLevel::High);
|
||||
ASSERT_NE(qrCode.version, nullptr);
|
||||
EXPECT_EQ(qrCode.version->versionNumber(), 1);
|
||||
EXPECT_EQ(qrCode.maskPattern, 4);
|
||||
EXPECT_EQ(ToString(qrCode.matrix),
|
||||
"X X X X X X X X X X X X X X X X \n"
|
||||
"X X X X X X X \n"
|
||||
"X X X X X X X X X X \n"
|
||||
"X X X X X X X X X X X X \n"
|
||||
"X X X X X X X X X X X X \n"
|
||||
"X X X X X X X \n"
|
||||
"X X X X X X X X X X X X X X X X X \n"
|
||||
" X X \n"
|
||||
" X X X X X X X X X X \n"
|
||||
" X X X X X X X X X X X X \n"
|
||||
"X X X X X X X X X X \n"
|
||||
"X X X X X X X X X \n"
|
||||
" X X X X X X X X X X X X X \n"
|
||||
" X X X X X X \n"
|
||||
"X X X X X X X X X X X X X \n"
|
||||
"X X X X X X X X X X \n"
|
||||
"X X X X X X X X X X X \n"
|
||||
"X X X X X X X X X X X \n"
|
||||
"X X X X X X X X X \n"
|
||||
"X X X X X X X \n"
|
||||
"X X X X X X X X X X X X \n");
|
||||
}
|
||||
|
||||
TEST(QREncoderTest, EncodeWithVersion)
|
||||
{
|
||||
auto qrCode = Encode(L"ABCDEF", ErrorCorrectionLevel::High, CharacterSet::Unknown, 7, false, -1);
|
||||
ASSERT_NE(qrCode.version, nullptr);
|
||||
EXPECT_EQ(qrCode.version->versionNumber(), 7);
|
||||
}
|
||||
|
||||
TEST(QREncoderTest, EncodeWithVersionTooSmall)
|
||||
{
|
||||
EXPECT_THROW(
|
||||
Encode(L"THISMESSAGEISTOOLONGFORAQRCODEVERSION3", ErrorCorrectionLevel::High, CharacterSet::Unknown, 3, false, -1)
|
||||
, std::invalid_argument);
|
||||
}
|
||||
|
||||
TEST(QREncoderTest, SimpleUTF8ECI)
|
||||
{
|
||||
auto qrCode = Encode(L"hello", ErrorCorrectionLevel::High, CharacterSet::UTF8, 0, false, -1);
|
||||
EXPECT_EQ(qrCode.mode, CodecMode::BYTE);
|
||||
EXPECT_EQ(qrCode.ecLevel, ErrorCorrectionLevel::High);
|
||||
ASSERT_NE(qrCode.version, nullptr);
|
||||
EXPECT_EQ(qrCode.version->versionNumber(), 1);
|
||||
EXPECT_EQ(qrCode.maskPattern, 6);
|
||||
EXPECT_EQ(ToString(qrCode.matrix), // break the line comment
|
||||
"X X X X X X X X X X X X X X X X \n"
|
||||
"X X X X X X \n"
|
||||
"X X X X X X X X X X X X X \n"
|
||||
"X X X X X X X X X X X X \n"
|
||||
"X X X X X X X X X X X X \n"
|
||||
"X X X X X \n"
|
||||
"X X X X X X X X X X X X X X X X X \n"
|
||||
" X X X X \n"
|
||||
" X X X X X X X \n"
|
||||
" X X X X X X X X X \n"
|
||||
"X X X X X X X X X X X \n"
|
||||
" X X X X X X \n"
|
||||
" X X X X X X X X X X X X X X X \n"
|
||||
" X X X X X X X X X X X X \n"
|
||||
"X X X X X X X X X X \n"
|
||||
"X X X X X X \n"
|
||||
"X X X X X X X X X \n"
|
||||
"X X X X X X X X X X X X X \n"
|
||||
"X X X X X X X X X X X X \n"
|
||||
"X X X X X X \n"
|
||||
"X X X X X X X X X X X \n");
|
||||
}
|
||||
|
||||
TEST(QREncoderTest, SimpleBINARYECI)
|
||||
{
|
||||
auto qrCode = Encode(L"\u00E9", ErrorCorrectionLevel::High, CharacterSet::BINARY, 0, false, -1);
|
||||
EXPECT_EQ(qrCode.mode, CodecMode::BYTE);
|
||||
EXPECT_EQ(qrCode.ecLevel, ErrorCorrectionLevel::High);
|
||||
ASSERT_NE(qrCode.version, nullptr);
|
||||
EXPECT_EQ(qrCode.version->versionNumber(), 1);
|
||||
EXPECT_EQ(qrCode.maskPattern, 6);
|
||||
EXPECT_EQ(ToString(qrCode.matrix),
|
||||
"X X X X X X X X X X X X X X X X X X \n"
|
||||
"X X X X X \n"
|
||||
"X X X X X X X X X X X X X \n"
|
||||
"X X X X X X X X X X X X X X X \n"
|
||||
"X X X X X X X X X X X \n"
|
||||
"X X X X X X \n"
|
||||
"X X X X X X X X X X X X X X X X X \n"
|
||||
" X X X \n"
|
||||
" X X X X X X X \n"
|
||||
"X X X X X X X X \n"
|
||||
"X X X X X X X X X X X X X X X \n"
|
||||
"X X X X X X X X X X X X \n"
|
||||
" X X X X X X X X X X \n"
|
||||
" X X X X X X X X \n"
|
||||
"X X X X X X X X X X X X X X X \n"
|
||||
"X X X X X X X \n"
|
||||
"X X X X X X X X X X X X X \n"
|
||||
"X X X X X X X X X X X X X \n"
|
||||
"X X X X X X X X X X \n"
|
||||
"X X X X X X X \n"
|
||||
"X X X X X X X X X X X X X \n");
|
||||
}
|
||||
|
||||
TEST(QREncoderTest, EncodeKanjiMode)
|
||||
{
|
||||
auto qrCode = Encode(L"\u65e5\u672c", ErrorCorrectionLevel::Medium, CharacterSet::Shift_JIS, 0, false, -1);
|
||||
EXPECT_EQ(qrCode.mode, CodecMode::KANJI);
|
||||
EXPECT_EQ(qrCode.ecLevel, ErrorCorrectionLevel::Medium);
|
||||
ASSERT_NE(qrCode.version, nullptr);
|
||||
EXPECT_EQ(qrCode.version->versionNumber(), 1);
|
||||
EXPECT_EQ(qrCode.maskPattern, 0);
|
||||
EXPECT_EQ(ToString(qrCode.matrix),
|
||||
"X X X X X X X X X X X X X X X X \n"
|
||||
"X X X X X X \n"
|
||||
"X X X X X X X X X X X X X X \n"
|
||||
"X X X X X X X X X X X \n"
|
||||
"X X X X X X X X X X X X X X X \n"
|
||||
"X X X X X X X \n"
|
||||
"X X X X X X X X X X X X X X X X X \n"
|
||||
" X \n"
|
||||
"X X X X X X X X \n"
|
||||
"X X X X X X X X X X \n"
|
||||
" X X X X X X X X X X X X \n"
|
||||
"X X X X X X X X X X X \n"
|
||||
" X X X X X X X X X X X X \n"
|
||||
" X X X X X \n"
|
||||
"X X X X X X X X X X X \n"
|
||||
"X X X X X X X \n"
|
||||
"X X X X X X X X X X X \n"
|
||||
"X X X X X X X X X X \n"
|
||||
"X X X X X X X X X X X X X \n"
|
||||
"X X X X X X X X X \n"
|
||||
"X X X X X X X X X X X X X X \n");
|
||||
}
|
||||
|
||||
TEST(QREncoderTest, EncodeShiftjisNumeric)
|
||||
{
|
||||
auto qrCode = Encode(L"0123", ErrorCorrectionLevel::Medium, CharacterSet::Shift_JIS, 0, false, -1);
|
||||
EXPECT_EQ(qrCode.mode, CodecMode::NUMERIC);
|
||||
EXPECT_EQ(qrCode.ecLevel, ErrorCorrectionLevel::Medium);
|
||||
ASSERT_NE(qrCode.version, nullptr);
|
||||
EXPECT_EQ(qrCode.version->versionNumber(), 1);
|
||||
EXPECT_EQ(qrCode.maskPattern, 2);
|
||||
EXPECT_EQ(ToString(qrCode.matrix),
|
||||
"X X X X X X X X X X X X X X X X X \n"
|
||||
"X X X X X X \n"
|
||||
"X X X X X X X X X X X \n"
|
||||
"X X X X X X X X X X X X X X \n"
|
||||
"X X X X X X X X X X X X X X \n"
|
||||
"X X X X X X X \n"
|
||||
"X X X X X X X X X X X X X X X X X \n"
|
||||
" X X X X X \n"
|
||||
"X X X X X X X X X X X X X X \n"
|
||||
"X X X X X X X X \n"
|
||||
" X X X X X X X X X X X X X X \n"
|
||||
"X X X X X X X X X \n"
|
||||
" X X X X X X X X \n"
|
||||
" X X X X X X X \n"
|
||||
"X X X X X X X X X X X \n"
|
||||
"X X X X X X X X X X \n"
|
||||
"X X X X X X X X X X \n"
|
||||
"X X X X X X X X X X X \n"
|
||||
"X X X X X X X X X X X X \n"
|
||||
"X X X X X X X \n"
|
||||
"X X X X X X X X X X X X X X \n");
|
||||
}
|
||||
|
||||
TEST(QREncoderTest, EncodeGS1)
|
||||
{
|
||||
auto qrCode = Encode(L"100001%11171218", ErrorCorrectionLevel::High, CharacterSet::Unknown, 0, true, -1);
|
||||
EXPECT_EQ(qrCode.mode, CodecMode::ALPHANUMERIC);
|
||||
EXPECT_EQ(qrCode.ecLevel, ErrorCorrectionLevel::High);
|
||||
ASSERT_NE(qrCode.version, nullptr);
|
||||
EXPECT_EQ(qrCode.version->versionNumber(), 2);
|
||||
EXPECT_EQ(qrCode.maskPattern, 4);
|
||||
EXPECT_EQ(ToString(qrCode.matrix),
|
||||
"X X X X X X X X X X X X X X X X X X X X \n"
|
||||
"X X X X X X X X \n"
|
||||
"X X X X X X X X X X X X X X \n"
|
||||
"X X X X X X X X X X X X X X \n"
|
||||
"X X X X X X X X X X X X X X \n"
|
||||
"X X X X X X X X X X \n"
|
||||
"X X X X X X X X X X X X X X X X X X X \n"
|
||||
" X X X X X X \n"
|
||||
" X X X X X X X X X X X \n"
|
||||
" X X X X X X X X X X X X X X X \n"
|
||||
" X X X X X X X X X X X X X X \n"
|
||||
"X X X X X X X X X X X X X X X X X \n"
|
||||
" X X X X X X X X X X X X \n"
|
||||
"X X X X X X X X X X X \n"
|
||||
" X X X X X X X X X X X X X X X X \n"
|
||||
" X X X X X X X X X X \n"
|
||||
"X X X X X X X X X X X X X X \n"
|
||||
" X X X X X X X X \n"
|
||||
"X X X X X X X X X X X X X X X \n"
|
||||
"X X X X X X X X X X \n"
|
||||
"X X X X X X X X X X X X X X X \n"
|
||||
"X X X X X X X X X X X \n"
|
||||
"X X X X X X X X X X X X X X \n"
|
||||
"X X X X X X X X X X X X \n"
|
||||
"X X X X X X X X X X X X X X \n");
|
||||
}
|
||||
|
||||
TEST(QREncoderTest, EncodeGS1ModeHeaderWithECI)
|
||||
{
|
||||
auto qrCode = Encode(L"hello", ErrorCorrectionLevel::High, CharacterSet::UTF8, 0, true, -1);
|
||||
EXPECT_EQ(qrCode.mode, CodecMode::BYTE);
|
||||
EXPECT_EQ(qrCode.ecLevel, ErrorCorrectionLevel::High);
|
||||
ASSERT_NE(qrCode.version, nullptr);
|
||||
EXPECT_EQ(qrCode.version->versionNumber(), 1);
|
||||
EXPECT_EQ(qrCode.maskPattern, 5);
|
||||
EXPECT_EQ(ToString(qrCode.matrix),
|
||||
"X X X X X X X X X X X X X X X X X \n"
|
||||
"X X X X X X \n"
|
||||
"X X X X X X X X X X X X X \n"
|
||||
"X X X X X X X X X X X X \n"
|
||||
"X X X X X X X X X X X X \n"
|
||||
"X X X X X X X X \n"
|
||||
"X X X X X X X X X X X X X X X X X \n"
|
||||
" X X X X \n"
|
||||
" X X X X X X X X \n"
|
||||
" X X X X X X X X X X X X X X \n"
|
||||
" X X X X X X X X X X X \n"
|
||||
"X X X X X X X X X X X X \n"
|
||||
"X X X X X X X X X X \n"
|
||||
" X X X X X X X X \n"
|
||||
"X X X X X X X X X X X X \n"
|
||||
"X X X X X X X X \n"
|
||||
"X X X X X X X X X X \n"
|
||||
"X X X X X X X X X X X X \n"
|
||||
"X X X X X X X X X X X \n"
|
||||
"X X X X X X X X X \n"
|
||||
"X X X X X X X X X X X X X X \n");
|
||||
}
|
||||
|
||||
TEST(QREncoderTest, AppendModeInfo)
|
||||
{
|
||||
BitArray bits;
|
||||
AppendModeInfo(CodecMode::NUMERIC, bits);
|
||||
EXPECT_EQ(ToString(bits), "...X");
|
||||
}
|
||||
|
||||
TEST(QREncoderTest, AppendLengthInfo)
|
||||
{
|
||||
BitArray bits;
|
||||
AppendLengthInfo(1, // 1 letter (1/1).
|
||||
*Version::FromNumber(1), CodecMode::NUMERIC, bits);
|
||||
EXPECT_EQ(ToString(bits), RemoveSpace("........ .X")); // 10 bits.
|
||||
|
||||
bits = BitArray();
|
||||
AppendLengthInfo(2, // 2 letters (2/1).
|
||||
*Version::FromNumber(10), CodecMode::ALPHANUMERIC, bits);
|
||||
EXPECT_EQ(ToString(bits), RemoveSpace("........ .X.")); // 11 bits.
|
||||
|
||||
bits = BitArray();
|
||||
AppendLengthInfo(255, // 255 letter (255/1).
|
||||
*Version::FromNumber(27), CodecMode::BYTE, bits);
|
||||
EXPECT_EQ(ToString(bits), RemoveSpace("........ XXXXXXXX")); // 16 bits.
|
||||
|
||||
bits = BitArray();
|
||||
AppendLengthInfo(512, // 512 letters (1024/2).
|
||||
*Version::FromNumber(40), CodecMode::KANJI, bits);
|
||||
EXPECT_EQ(ToString(bits), RemoveSpace("..X..... ....")); // 12 bits.
|
||||
}
|
||||
|
||||
TEST(QREncoderTest, AppendNumericBytes)
|
||||
{
|
||||
// 1 = 01 = 0001 in 4 bits.
|
||||
BitArray bits;
|
||||
AppendNumericBytes(L"1", bits);
|
||||
EXPECT_EQ(ToString(bits), RemoveSpace("...X"));
|
||||
|
||||
// 12 = 0xc = 0001100 in 7 bits.
|
||||
bits = BitArray();
|
||||
AppendNumericBytes(L"12", bits);
|
||||
EXPECT_EQ(ToString(bits), RemoveSpace("...XX.."));
|
||||
|
||||
// 123 = 0x7b = 0001111011 in 10 bits.
|
||||
bits = BitArray();
|
||||
AppendNumericBytes(L"123", bits);
|
||||
EXPECT_EQ(ToString(bits), RemoveSpace("...XXXX. XX"));
|
||||
|
||||
// 1234 = "123" + "4" = 0001111011 + 0100
|
||||
bits = BitArray();
|
||||
AppendNumericBytes(L"1234", bits);
|
||||
EXPECT_EQ(ToString(bits), RemoveSpace("...XXXX. XX.X.."));
|
||||
|
||||
// Empty.
|
||||
bits = BitArray();
|
||||
AppendNumericBytes(L"", bits);
|
||||
EXPECT_EQ(ToString(bits), RemoveSpace(""));
|
||||
}
|
||||
|
||||
TEST(QREncoderTest, AppendAlphanumericBytes)
|
||||
{
|
||||
// A = 10 = 0xa = 001010 in 6 bits
|
||||
BitArray bits;
|
||||
AppendAlphanumericBytes(L"A", bits);
|
||||
EXPECT_EQ(ToString(bits), RemoveSpace("..X.X."));
|
||||
|
||||
// AB = 10 * 45 + 11 = 461 = 0x1cd = 00111001101 in 11 bits
|
||||
bits = BitArray();
|
||||
AppendAlphanumericBytes(L"AB", bits);
|
||||
EXPECT_EQ(ToString(bits), RemoveSpace("..XXX..X X.X"));
|
||||
|
||||
// ABC = "AB" + "C" = 00111001101 + 001100
|
||||
bits = BitArray();
|
||||
AppendAlphanumericBytes(L"ABC", bits);
|
||||
EXPECT_EQ(ToString(bits), RemoveSpace("..XXX..X X.X..XX. ."));
|
||||
|
||||
// Empty.
|
||||
bits = BitArray();
|
||||
AppendAlphanumericBytes(L"", bits);
|
||||
EXPECT_EQ(ToString(bits), RemoveSpace(""));
|
||||
|
||||
// Invalid data.
|
||||
EXPECT_THROW(AppendAlphanumericBytes(L"abc", bits), std::invalid_argument);
|
||||
}
|
||||
|
||||
TEST(QREncoderTest, Append8BitBytes)
|
||||
{
|
||||
// 0x61, 0x62, 0x63
|
||||
BitArray bits;
|
||||
Append8BitBytes(L"abc", CharacterSet::Unknown, bits);
|
||||
EXPECT_EQ(ToString(bits), RemoveSpace(".XX....X .XX...X. .XX...XX"));
|
||||
|
||||
// Empty.
|
||||
bits = BitArray();
|
||||
Append8BitBytes(L"", CharacterSet::Unknown, bits);
|
||||
EXPECT_EQ(ToString(bits), RemoveSpace(""));
|
||||
}
|
||||
|
||||
// Numbers are from page 21 of JISX0510:2004
|
||||
TEST(QREncoderTest, AppendKanjiBytes)
|
||||
{
|
||||
BitArray bits;
|
||||
AppendKanjiBytes(ShiftJISString({ 0x93, 0x5f }), bits);
|
||||
EXPECT_EQ(ToString(bits), RemoveSpace(".XX.XX.. XXXXX"));
|
||||
|
||||
AppendKanjiBytes(ShiftJISString({ 0xe4, 0xaa }), bits);
|
||||
EXPECT_EQ(ToString(bits), RemoveSpace(".XX.XX.. XXXXXXX. X.X.X.X. X."));
|
||||
}
|
||||
|
||||
TEST(QREncoderTest, AppendBytes)
|
||||
{
|
||||
// Should use appendNumericBytes.
|
||||
// 1 = 01 = 0001 in 4 bits.
|
||||
BitArray bits;
|
||||
AppendBytes(L"1", CodecMode::NUMERIC, CharacterSet::Unknown, bits);
|
||||
EXPECT_EQ(ToString(bits), RemoveSpace("...X"));
|
||||
|
||||
// Should use appendAlphanumericBytes.
|
||||
// A = 10 = 0xa = 001010 in 6 bits
|
||||
bits = BitArray();
|
||||
AppendBytes(L"A", CodecMode::ALPHANUMERIC, CharacterSet::Unknown, bits);
|
||||
EXPECT_EQ(ToString(bits), RemoveSpace("..X.X."));
|
||||
|
||||
// Lower letters such as 'a' cannot be encoded in MODE_ALPHANUMERIC.
|
||||
bits = BitArray();
|
||||
EXPECT_THROW(AppendBytes(L"a", CodecMode::ALPHANUMERIC, CharacterSet::Unknown, bits), std::invalid_argument);
|
||||
|
||||
// Should use append8BitBytes.
|
||||
// 0x61, 0x62, 0x63
|
||||
bits = BitArray();
|
||||
AppendBytes(L"abc", CodecMode::BYTE, CharacterSet::Unknown, bits);
|
||||
EXPECT_EQ(ToString(bits), RemoveSpace(".XX....X .XX...X. .XX...XX"));
|
||||
|
||||
// Anything can be encoded in QRCode.MODE_8BIT_BYTE.
|
||||
AppendBytes(L"\0", CodecMode::BYTE, CharacterSet::Unknown, bits);
|
||||
|
||||
// Should use appendKanjiBytes.
|
||||
// 0x93, 0x5f
|
||||
bits = BitArray();
|
||||
AppendBytes(ShiftJISString({0x93, 0x5f}), CodecMode::KANJI, CharacterSet::Unknown, bits);
|
||||
EXPECT_EQ(ToString(bits), RemoveSpace(".XX.XX.. XXXXX"));
|
||||
}
|
||||
|
||||
TEST(QREncoderTest, TerminateBits)
|
||||
{
|
||||
BitArray v;
|
||||
TerminateBits(0, v);
|
||||
EXPECT_EQ(ToString(v), RemoveSpace(""));
|
||||
|
||||
v = BitArray();
|
||||
TerminateBits(1, v);
|
||||
EXPECT_EQ(ToString(v), RemoveSpace("........"));
|
||||
|
||||
v = BitArray();
|
||||
v.appendBits(0, 3);
|
||||
TerminateBits(1, v);
|
||||
EXPECT_EQ(ToString(v), RemoveSpace("........"));
|
||||
|
||||
v = BitArray();
|
||||
v.appendBits(0, 5);
|
||||
TerminateBits(1, v);
|
||||
EXPECT_EQ(ToString(v), RemoveSpace("........"));
|
||||
|
||||
v = BitArray();
|
||||
v.appendBits(0, 8);
|
||||
TerminateBits(1, v);
|
||||
EXPECT_EQ(ToString(v), RemoveSpace("........"));
|
||||
|
||||
v = BitArray();
|
||||
TerminateBits(2, v);
|
||||
EXPECT_EQ(ToString(v), RemoveSpace("........ XXX.XX.."));
|
||||
|
||||
v = BitArray();
|
||||
v.appendBits(0, 1);
|
||||
TerminateBits(3, v);
|
||||
EXPECT_EQ(ToString(v), RemoveSpace("........ XXX.XX.. ...X...X"));
|
||||
}
|
||||
|
||||
TEST(QREncoderTest, GetNumDataBytesAndNumECBytesForBlockID)
|
||||
{
|
||||
int numDataBytes;
|
||||
int numEcBytes;
|
||||
// Version 1-H.
|
||||
GetNumDataBytesAndNumECBytesForBlockID(26, 9, 1, 0, numDataBytes, numEcBytes);
|
||||
EXPECT_EQ(9, numDataBytes);
|
||||
EXPECT_EQ(17, numEcBytes);
|
||||
|
||||
// Version 3-H. 2 blocks.
|
||||
GetNumDataBytesAndNumECBytesForBlockID(70, 26, 2, 0, numDataBytes, numEcBytes);
|
||||
EXPECT_EQ(13, numDataBytes);
|
||||
EXPECT_EQ(22, numEcBytes);
|
||||
GetNumDataBytesAndNumECBytesForBlockID(70, 26, 2, 1, numDataBytes, numEcBytes);
|
||||
EXPECT_EQ(13, numDataBytes);
|
||||
EXPECT_EQ(22, numEcBytes);
|
||||
|
||||
// Version 7-H. (4 + 1) blocks.
|
||||
GetNumDataBytesAndNumECBytesForBlockID(196, 66, 5, 0, numDataBytes, numEcBytes);
|
||||
EXPECT_EQ(13, numDataBytes);
|
||||
EXPECT_EQ(26, numEcBytes);
|
||||
GetNumDataBytesAndNumECBytesForBlockID(196, 66, 5, 4, numDataBytes, numEcBytes);
|
||||
EXPECT_EQ(14, numDataBytes);
|
||||
EXPECT_EQ(26, numEcBytes);
|
||||
|
||||
// Version 40-H. (20 + 61) blocks.
|
||||
GetNumDataBytesAndNumECBytesForBlockID(3706, 1276, 81, 0, numDataBytes, numEcBytes);
|
||||
EXPECT_EQ(15, numDataBytes);
|
||||
EXPECT_EQ(30, numEcBytes);
|
||||
GetNumDataBytesAndNumECBytesForBlockID(3706, 1276, 81, 20, numDataBytes, numEcBytes);
|
||||
EXPECT_EQ(16, numDataBytes);
|
||||
EXPECT_EQ(30, numEcBytes);
|
||||
GetNumDataBytesAndNumECBytesForBlockID(3706, 1276, 81, 80, numDataBytes, numEcBytes);
|
||||
EXPECT_EQ(16, numDataBytes);
|
||||
EXPECT_EQ(30, numEcBytes);
|
||||
}
|
||||
|
||||
// Numbers are from http://www.swetake.com/qr/qr3.html and
|
||||
// http://www.swetake.com/qr/qr9.html
|
||||
TEST(QREncoderTest, GenerateECBytes)
|
||||
{
|
||||
ByteArray ecBytes;
|
||||
GenerateECBytes({ 32, 65, 205, 69, 41, 220, 46, 128, 236 }, 17, ecBytes);
|
||||
ByteArray expected = { 42, 159, 74, 221, 244, 169, 239, 150, 138, 70, 237, 85, 224, 96, 74, 219, 61 };
|
||||
EXPECT_EQ(ecBytes, expected);
|
||||
|
||||
GenerateECBytes({ 67, 70, 22, 38, 54, 70, 86, 102, 118, 134, 150, 166, 182, 198, 214 }, 18, ecBytes);
|
||||
expected = { 175, 80, 155, 64, 178, 45, 214, 233, 65, 209, 12, 155, 117, 31, 140, 214, 27, 187 };
|
||||
EXPECT_EQ(ecBytes, expected);
|
||||
|
||||
// High-order zero coefficient case.
|
||||
GenerateECBytes({ 32, 49, 205, 69, 42, 20, 0, 236, 17 }, 17, ecBytes);
|
||||
expected = { 0, 3, 130, 179, 194, 0, 55, 211, 110, 79, 98, 72, 170, 96, 211, 137, 213 };
|
||||
EXPECT_EQ(ecBytes, expected);
|
||||
}
|
||||
|
||||
TEST(QREncoderTest, InterleaveWithECBytes)
|
||||
{
|
||||
BitArray in;
|
||||
for (int dataByte : {32, 65, 205, 69, 41, 220, 46, 128, 236})
|
||||
in.appendBits(dataByte, 8);
|
||||
|
||||
BitArray out = InterleaveWithECBytes(in, 26, 9, 1);
|
||||
std::vector<uint8_t> expected = {
|
||||
32, 65, 205, 69, 41, 220, 46, 128, 236,
|
||||
|
||||
42, 159, 74, 221, 244, 169, 239, 150, 138, 70, 237, 85, 224, 96, 74, 219, 61,
|
||||
}; // Error correction bytes in second block
|
||||
ASSERT_EQ(Size(expected), out.sizeInBytes());
|
||||
EXPECT_EQ(expected, out.toBytes());
|
||||
|
||||
// Numbers are from http://www.swetake.com/qr/qr8.html
|
||||
in = BitArray();
|
||||
for (int dataByte :
|
||||
{67, 70, 22, 38, 54, 70, 86, 102, 118, 134, 150, 166, 182, 198, 214, 230, 247, 7, 23, 39, 55,
|
||||
71, 87, 103, 119, 135, 151, 166, 22, 38, 54, 70, 86, 102, 118, 134, 150, 166, 182, 198, 214, 230,
|
||||
247, 7, 23, 39, 55, 71, 87, 103, 119, 135, 151, 160, 236, 17, 236, 17, 236, 17, 236, 17})
|
||||
in.appendBits(dataByte, 8);
|
||||
|
||||
out = InterleaveWithECBytes(in, 134, 62, 4);
|
||||
expected = {
|
||||
67, 230, 54, 55, 70, 247, 70, 71, 22, 7, 86, 87, 38, 23, 102, 103, 54, 39, 118, 119, 70,
|
||||
55, 134, 135, 86, 71, 150, 151, 102, 87, 166, 160, 118, 103, 182, 236, 134, 119, 198, 17, 150, 135,
|
||||
214, 236, 166, 151, 230, 17, 182, 166, 247, 236, 198, 22, 7, 17, 214, 38, 23, 236, 39, 17,
|
||||
|
||||
175, 155, 245, 236, 80, 146, 56, 74, 155, 165, 133, 142, 64, 183, 132, 13, 178, 54, 132, 108, 45,
|
||||
113, 53, 50, 214, 98, 193, 152, 233, 147, 50, 71, 65, 190, 82, 51, 209, 199, 171, 54, 12, 112,
|
||||
57, 113, 155, 117, 211, 164, 117, 30, 158, 225, 31, 190, 242, 38, 140, 61, 179, 154, 214, 138, 147,
|
||||
87, 27, 96, 77, 47, 187, 49, 156, 214,
|
||||
}; // Error correction bytes in second block
|
||||
EXPECT_EQ(Size(expected), out.sizeInBytes());
|
||||
EXPECT_EQ(expected, out.toBytes());
|
||||
}
|
||||
|
||||
TEST(QREncoderTest, BugInBitVectorNumBytes)
|
||||
{
|
||||
// There was a bug in BitVector.sizeInBytes() that caused it to return a
|
||||
// smaller-by-one value (ex. 1465 instead of 1466) if the number of bits
|
||||
// in the vector is not 8-bit aligned. In QRCodeEncoder::InitQRCode(),
|
||||
// BitVector::sizeInBytes() is used for finding the smallest QR Code
|
||||
// version that can fit the given data. Hence there were corner cases
|
||||
// where we chose a wrong QR Code version that cannot fit the given
|
||||
// data. Note that the issue did not occur with MODE_8BIT_BYTE, as the
|
||||
// bits in the bit vector are always 8-bit aligned.
|
||||
//
|
||||
// Before the bug was fixed, the following test didn't pass, because:
|
||||
//
|
||||
// - MODE_NUMERIC is chosen as all bytes in the data are '0'
|
||||
// - The 3518-byte numeric data needs 1466 bytes
|
||||
// - 3518 / 3 * 10 + 7 = 11727 bits = 1465.875 bytes
|
||||
// - 3 numeric bytes are encoded in 10 bits, hence the first
|
||||
// 3516 bytes are encoded in 3516 / 3 * 10 = 11720 bits.
|
||||
// - 2 numeric bytes can be encoded in 7 bits, hence the last
|
||||
// 2 bytes are encoded in 7 bits.
|
||||
// - The version 27 QR Code with the EC level L has 1468 bytes for data.
|
||||
// - 1828 - 360 = 1468
|
||||
// - In InitQRCode(), 3 bytes are reserved for a header. Hence 1465 bytes
|
||||
// (1468 -3) are left for data.
|
||||
// - Because of the bug in BitVector::sizeInBytes(), InitQRCode() determines
|
||||
// the given data can fit in 1465 bytes, despite it needs 1466 bytes.
|
||||
// - Hence QRCodeEncoder.encode() failed and returned false.
|
||||
// - To be precise, it needs 11727 + 4 (getMode info) + 14 (length info) =
|
||||
// 11745 bits = 1468.125 bytes are needed (i.e. cannot fit in 1468
|
||||
// bytes).
|
||||
Encode(std::wstring(3518, L'0'), ErrorCorrectionLevel::Low, CharacterSet::Unknown, 0, false, -1);
|
||||
}
|
||||
89
src/qrcode/cpp_port/test/QRErrorCorrectionLevelTest.rs
Normal file
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 2017 Huy Cuong Nguyen
|
||||
* Copyright 2008 ZXing authors
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// #include "qrcode/QRErrorCorrectionLevel.h"
|
||||
|
||||
// #include "gtest/gtest.h"
|
||||
|
||||
// using namespace ZXing;
|
||||
// using namespace ZXing::QRCode;
|
||||
|
||||
use crate::qrcode::decoder::ErrorCorrectionLevel;
|
||||
|
||||
#[test]
|
||||
fn ForBits() {
|
||||
assert_eq!(
|
||||
ErrorCorrectionLevel::M,
|
||||
ErrorCorrectionLevel::ECLevelFromBits(0, false)
|
||||
);
|
||||
assert_eq!(
|
||||
ErrorCorrectionLevel::L,
|
||||
ErrorCorrectionLevel::ECLevelFromBits(1, false)
|
||||
);
|
||||
assert_eq!(
|
||||
ErrorCorrectionLevel::H,
|
||||
ErrorCorrectionLevel::ECLevelFromBits(2, false)
|
||||
);
|
||||
assert_eq!(
|
||||
ErrorCorrectionLevel::Q,
|
||||
ErrorCorrectionLevel::ECLevelFromBits(3, false)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ForMicroBits() {
|
||||
assert_eq!(
|
||||
ErrorCorrectionLevel::L,
|
||||
ErrorCorrectionLevel::ECLevelFromBits(0, true)
|
||||
);
|
||||
assert_eq!(
|
||||
ErrorCorrectionLevel::L,
|
||||
ErrorCorrectionLevel::ECLevelFromBits(1, true)
|
||||
);
|
||||
assert_eq!(
|
||||
ErrorCorrectionLevel::M,
|
||||
ErrorCorrectionLevel::ECLevelFromBits(2, true)
|
||||
);
|
||||
assert_eq!(
|
||||
ErrorCorrectionLevel::L,
|
||||
ErrorCorrectionLevel::ECLevelFromBits(3, true)
|
||||
);
|
||||
assert_eq!(
|
||||
ErrorCorrectionLevel::M,
|
||||
ErrorCorrectionLevel::ECLevelFromBits(4, true)
|
||||
);
|
||||
assert_eq!(
|
||||
ErrorCorrectionLevel::L,
|
||||
ErrorCorrectionLevel::ECLevelFromBits(5, true)
|
||||
);
|
||||
assert_eq!(
|
||||
ErrorCorrectionLevel::M,
|
||||
ErrorCorrectionLevel::ECLevelFromBits(6, true)
|
||||
);
|
||||
assert_eq!(
|
||||
ErrorCorrectionLevel::Q,
|
||||
ErrorCorrectionLevel::ECLevelFromBits(7, true)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
ErrorCorrectionLevel::Q,
|
||||
ErrorCorrectionLevel::ECLevelFromBitsSigned(-1, true)
|
||||
);
|
||||
assert_eq!(
|
||||
ErrorCorrectionLevel::L,
|
||||
ErrorCorrectionLevel::ECLevelFromBits(8, true)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ToString() {
|
||||
// using namespace std::literals;
|
||||
|
||||
assert_eq!("L", ErrorCorrectionLevel::L.to_string());
|
||||
assert_eq!("M", ErrorCorrectionLevel::M.to_string());
|
||||
assert_eq!("Q", ErrorCorrectionLevel::Q.to_string());
|
||||
assert_eq!("H", ErrorCorrectionLevel::H.to_string());
|
||||
}
|
||||
132
src/qrcode/cpp_port/test/QRFormatInformationTest.rs
Normal file
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Copyright 2017 Huy Cuong Nguyen
|
||||
* Copyright 2007 ZXing authors
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::qrcode::decoder::{ErrorCorrectionLevel, FormatInformation};
|
||||
|
||||
const MASKED_TEST_FORMAT_INFO: u32 = 0x2BED;
|
||||
const MASKED_TEST_FORMAT_INFO2: u32 =
|
||||
((0x2BED << 1) & 0b1111111000000000) | 0b100000000 | (0x2BED & 0b11111111); // insert the 'Dark Module'
|
||||
const UNMASKED_TEST_FORMAT_INFO: u32 = MASKED_TEST_FORMAT_INFO ^ 0x5412;
|
||||
const MICRO_MASKED_TEST_FORMAT_INFO: u32 = 0x3BBA;
|
||||
// const MICRO_UNMASKED_TEST_FORMAT_INFO: u32 = MICRO_MASKED_TEST_FORMAT_INFO ^ 0x4445;
|
||||
|
||||
fn DoFormatInformationTest(formatInfo: u32, expectedMask: u8, expectedECL: ErrorCorrectionLevel) {
|
||||
let parsedFormat = FormatInformation::DecodeMQR(formatInfo);
|
||||
assert!(parsedFormat.isValid());
|
||||
assert_eq!(expectedMask, parsedFormat.data_mask);
|
||||
assert_eq!(expectedECL, parsedFormat.error_correction_level);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn Decode() {
|
||||
// Normal case
|
||||
let expected = FormatInformation::DecodeQR(MASKED_TEST_FORMAT_INFO, MASKED_TEST_FORMAT_INFO2);
|
||||
assert!(expected.isValid());
|
||||
assert_eq!(0x07, expected.data_mask);
|
||||
assert_eq!(ErrorCorrectionLevel::Q, expected.error_correction_level);
|
||||
// where the code forgot the mask!
|
||||
// assert_eq!(
|
||||
// expected,
|
||||
// FormatInformation::DecodeQR(UNMASKED_TEST_FORMAT_INFO, MASKED_TEST_FORMAT_INFO2)
|
||||
// );
|
||||
cpp_eq(
|
||||
&expected,
|
||||
&FormatInformation::DecodeQR(UNMASKED_TEST_FORMAT_INFO, MASKED_TEST_FORMAT_INFO2),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn DecodeWithBitDifference() {
|
||||
let expected = FormatInformation::DecodeQR(MASKED_TEST_FORMAT_INFO, MASKED_TEST_FORMAT_INFO2);
|
||||
// 1,2,3,4 bits difference
|
||||
cpp_eq(
|
||||
&expected,
|
||||
&FormatInformation::DecodeQR(
|
||||
MASKED_TEST_FORMAT_INFO ^ 0x01,
|
||||
MASKED_TEST_FORMAT_INFO2 ^ 0x01,
|
||||
),
|
||||
);
|
||||
cpp_eq(
|
||||
&expected,
|
||||
&FormatInformation::DecodeQR(
|
||||
MASKED_TEST_FORMAT_INFO ^ 0x03,
|
||||
MASKED_TEST_FORMAT_INFO2 ^ 0x03,
|
||||
),
|
||||
);
|
||||
cpp_eq(
|
||||
&expected,
|
||||
&FormatInformation::DecodeQR(
|
||||
MASKED_TEST_FORMAT_INFO ^ 0x07,
|
||||
MASKED_TEST_FORMAT_INFO2 ^ 0x07,
|
||||
),
|
||||
);
|
||||
assert!(!FormatInformation::DecodeQR(
|
||||
MASKED_TEST_FORMAT_INFO ^ 0x0F,
|
||||
MASKED_TEST_FORMAT_INFO2 ^ 0x0F
|
||||
)
|
||||
.isValid());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn DecodeWithMisread() {
|
||||
let expected = FormatInformation::DecodeQR(MASKED_TEST_FORMAT_INFO, MASKED_TEST_FORMAT_INFO2);
|
||||
cpp_eq(
|
||||
&expected,
|
||||
&FormatInformation::DecodeQR(
|
||||
MASKED_TEST_FORMAT_INFO ^ 0x03,
|
||||
MASKED_TEST_FORMAT_INFO2 ^ 0x0F,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn DecodeMicro() {
|
||||
// Normal cases.
|
||||
DoFormatInformationTest(0x4445, 0x0, ErrorCorrectionLevel::L);
|
||||
DoFormatInformationTest(0x4172, 0x1, ErrorCorrectionLevel::L);
|
||||
DoFormatInformationTest(0x5fc0, 0x2, ErrorCorrectionLevel::L);
|
||||
DoFormatInformationTest(0x5af7, 0x3, ErrorCorrectionLevel::L);
|
||||
DoFormatInformationTest(0x6793, 0x0, ErrorCorrectionLevel::M);
|
||||
DoFormatInformationTest(0x62a4, 0x1, ErrorCorrectionLevel::M);
|
||||
DoFormatInformationTest(0x3e8d, 0x2, ErrorCorrectionLevel::Q);
|
||||
DoFormatInformationTest(MICRO_MASKED_TEST_FORMAT_INFO, 0x3, ErrorCorrectionLevel::Q);
|
||||
|
||||
// where the code forgot the mask!
|
||||
// DoFormatInformationTest(MICRO_UNMASKED_TEST_FORMAT_INFO, 0x3, ErrorCorrectionLevel::Quality);
|
||||
}
|
||||
|
||||
// This doesn't work as expected because the implementation of the decode tries with
|
||||
// and without the mask (0x4445). This effectively adds a tolerance of 5 bits to the Hamming
|
||||
// distance calculation.
|
||||
#[test]
|
||||
fn DecodeMicroWithBitDifference() {
|
||||
let expected = FormatInformation::DecodeMQR(MICRO_MASKED_TEST_FORMAT_INFO);
|
||||
|
||||
// 1,2,3 bits difference
|
||||
cpp_eq(
|
||||
&expected,
|
||||
&FormatInformation::DecodeMQR(MICRO_MASKED_TEST_FORMAT_INFO ^ 0x01),
|
||||
);
|
||||
cpp_eq(
|
||||
&expected,
|
||||
&FormatInformation::DecodeMQR(MICRO_MASKED_TEST_FORMAT_INFO ^ 0x03),
|
||||
);
|
||||
cpp_eq(
|
||||
&expected,
|
||||
&FormatInformation::DecodeMQR(MICRO_MASKED_TEST_FORMAT_INFO ^ 0x07),
|
||||
);
|
||||
|
||||
// Bigger bit differences can return valid FormatInformation objects but the data mask and error
|
||||
// correction levels do not match.
|
||||
// EXPECT_TRUE(FormatInformation::DecodeFormatInformation(MICRO_MASKED_TEST_FORMAT_INFO ^ 0x3f).isValid());
|
||||
// EXPECT_NE(expected.dataMask(), FormatInformation::DecodeFormatInformation(MICRO_MASKED_TEST_FORMAT_INFO ^ 0x3f).dataMask());
|
||||
// EXPECT_NE(expected.errorCorrectionLevel(),
|
||||
// FormatInformation::DecodeFormatInformation(MICRO_MASKED_TEST_FORMAT_INFO ^ 0x3f).errorCorrectionLevel());
|
||||
}
|
||||
|
||||
fn cpp_eq(rhs: &FormatInformation, lhs: &FormatInformation) {
|
||||
assert!(rhs.cpp_eq(lhs))
|
||||
}
|
||||
135
src/qrcode/cpp_port/test/QRModeTest.rs
Normal file
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright 2017 Huy Cuong Nguyen
|
||||
* Copyright 2008 ZXing authors
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::qrcode::decoder::{Mode, Version};
|
||||
|
||||
#[test]
|
||||
fn ForBits() {
|
||||
assert_eq!(
|
||||
Mode::TERMINATOR,
|
||||
Mode::CodecModeForBits(0x00, None).unwrap()
|
||||
);
|
||||
assert_eq!(Mode::NUMERIC, Mode::CodecModeForBits(0x01, None).unwrap());
|
||||
assert_eq!(
|
||||
Mode::ALPHANUMERIC,
|
||||
Mode::CodecModeForBits(0x02, None).unwrap()
|
||||
);
|
||||
assert_eq!(Mode::BYTE, Mode::CodecModeForBits(0x04, None).unwrap());
|
||||
assert_eq!(Mode::KANJI, Mode::CodecModeForBits(0x08, None).unwrap());
|
||||
assert!(Mode::CodecModeForBits(0x10, None).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn CharacterCount() {
|
||||
// Spot check a few values
|
||||
assert_eq!(
|
||||
10,
|
||||
Mode::CharacterCountBits(&Mode::NUMERIC, Version::FromNumber(5, false).unwrap())
|
||||
);
|
||||
assert_eq!(
|
||||
12,
|
||||
Mode::CharacterCountBits(&Mode::NUMERIC, Version::FromNumber(26, false).unwrap())
|
||||
);
|
||||
assert_eq!(
|
||||
14,
|
||||
Mode::CharacterCountBits(&Mode::NUMERIC, Version::FromNumber(40, false).unwrap())
|
||||
);
|
||||
assert_eq!(
|
||||
9,
|
||||
Mode::CharacterCountBits(&Mode::ALPHANUMERIC, Version::FromNumber(6, false).unwrap())
|
||||
);
|
||||
assert_eq!(
|
||||
8,
|
||||
Mode::CharacterCountBits(&Mode::BYTE, Version::FromNumber(7, false).unwrap())
|
||||
);
|
||||
assert_eq!(
|
||||
8,
|
||||
Mode::CharacterCountBits(&Mode::KANJI, Version::FromNumber(8, false).unwrap())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn MicroForBits() {
|
||||
// M1
|
||||
assert_eq!(
|
||||
Mode::NUMERIC,
|
||||
Mode::CodecModeForBits(0x00, Some(true)).unwrap()
|
||||
);
|
||||
// M2
|
||||
assert_eq!(
|
||||
Mode::NUMERIC,
|
||||
Mode::CodecModeForBits(0x00, Some(true)).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
Mode::ALPHANUMERIC,
|
||||
Mode::CodecModeForBits(0x01, Some(true)).unwrap()
|
||||
);
|
||||
// M3
|
||||
assert_eq!(
|
||||
Mode::NUMERIC,
|
||||
Mode::CodecModeForBits(0x00, Some(true)).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
Mode::ALPHANUMERIC,
|
||||
Mode::CodecModeForBits(0x01, Some(true)).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
Mode::BYTE,
|
||||
Mode::CodecModeForBits(0x02, Some(true)).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
Mode::KANJI,
|
||||
Mode::CodecModeForBits(0x03, Some(true)).unwrap()
|
||||
);
|
||||
// M4
|
||||
assert_eq!(
|
||||
Mode::NUMERIC,
|
||||
Mode::CodecModeForBits(0x00, Some(true)).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
Mode::ALPHANUMERIC,
|
||||
Mode::CodecModeForBits(0x01, Some(true)).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
Mode::BYTE,
|
||||
Mode::CodecModeForBits(0x02, Some(true)).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
Mode::KANJI,
|
||||
Mode::CodecModeForBits(0x03, Some(true)).unwrap()
|
||||
);
|
||||
|
||||
assert!(Mode::CodecModeForBits(0x04, Some(true)).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn MicroCharacterCount() {
|
||||
// Spot check a few values
|
||||
assert_eq!(
|
||||
3,
|
||||
Mode::CharacterCountBits(&Mode::NUMERIC, Version::FromNumber(1, true).unwrap())
|
||||
);
|
||||
assert_eq!(
|
||||
4,
|
||||
Mode::CharacterCountBits(&Mode::NUMERIC, Version::FromNumber(2, true).unwrap())
|
||||
);
|
||||
assert_eq!(
|
||||
6,
|
||||
Mode::CharacterCountBits(&Mode::NUMERIC, Version::FromNumber(4, true).unwrap())
|
||||
);
|
||||
assert_eq!(
|
||||
3,
|
||||
Mode::CharacterCountBits(&Mode::ALPHANUMERIC, Version::FromNumber(2, true).unwrap())
|
||||
);
|
||||
assert_eq!(
|
||||
4,
|
||||
Mode::CharacterCountBits(&Mode::BYTE, Version::FromNumber(3, true).unwrap())
|
||||
);
|
||||
assert_eq!(
|
||||
4,
|
||||
Mode::CharacterCountBits(&Mode::KANJI, Version::FromNumber(4, true).unwrap())
|
||||
);
|
||||
}
|
||||
122
src/qrcode/cpp_port/test/QRVersionTest.rs
Normal file
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright 2017 Huy Cuong Nguyen
|
||||
* Copyright 2008 ZXing authors
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::{
|
||||
common::BitMatrix,
|
||||
qrcode::decoder::{Version, VersionRef},
|
||||
};
|
||||
|
||||
fn CheckVersion(version: VersionRef, number: u32, dimension: u32) {
|
||||
// assert_ne!(version, nullptr);
|
||||
assert_eq!(number, version.getVersionNumber());
|
||||
if (number > 1 && !version.isMicroQRCode()) {
|
||||
assert!(!version.getAlignmentPatternCenters().is_empty());
|
||||
}
|
||||
assert_eq!(dimension, version.getDimensionForVersion());
|
||||
}
|
||||
|
||||
fn DoTestVersion(expectedVersion: u32, mask: i32) {
|
||||
let version = Version::DecodeVersionInformation(mask, 0).expect("should exist");
|
||||
// assert_ne!(version, nullptr);
|
||||
assert_eq!(expectedVersion, version.getVersionNumber());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn VersionForNumber() {
|
||||
let version = Version::FromNumber(0, false);
|
||||
assert!(version.is_err(), "There is version with number 0");
|
||||
|
||||
for i in 1..=40 {
|
||||
// for (int i = 1; i <= 40; i++) {
|
||||
CheckVersion(
|
||||
Version::FromNumber(i, false).expect("version number found"),
|
||||
i,
|
||||
4 * i + 17,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn GetProvisionalVersionForDimension() {
|
||||
for i in 1..=40 {
|
||||
// for (int i = 1; i <= 40; i++) {
|
||||
let prov =
|
||||
Version::FromDimension(4 * i + 17).expect(&format!("version should exist for {i}"));
|
||||
// assert_ne!(prov, nullptr);
|
||||
assert_eq!(i, prov.getVersionNumber());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn DecodeVersionInformation() {
|
||||
// Spot check
|
||||
DoTestVersion(7, 0x07C94);
|
||||
DoTestVersion(12, 0x0C762);
|
||||
DoTestVersion(17, 0x1145D);
|
||||
DoTestVersion(22, 0x168C9);
|
||||
DoTestVersion(27, 0x1B08E);
|
||||
DoTestVersion(32, 0x209D5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn MicroVersionForNumber() {
|
||||
let version = Version::FromNumber(0, true);
|
||||
assert!(version.is_err(), "There is version with number 0");
|
||||
|
||||
for i in 1..=4 {
|
||||
// for (int i = 1; i <= 4; i++) {
|
||||
CheckVersion(
|
||||
Version::FromNumber(i, true).expect(&format!("version for {i} should exist")),
|
||||
i,
|
||||
2 * i + 9,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn GetProvisionalMicroVersionForDimension() {
|
||||
for i in 1..=4 {
|
||||
// for (int i = 1; i <= 4; i++) {
|
||||
let prov = Version::FromDimension(2 * i + 9)
|
||||
.expect(&format!("version for micro {i} should exist"));
|
||||
// assert_ne!(prov, nullptr);
|
||||
assert_eq!(i, prov.getVersionNumber());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn FunctionPattern() {
|
||||
let testFinderPatternRegion = |bitMatrix: &BitMatrix| {
|
||||
for row in 0..9 {
|
||||
// for (int row = 0; row < 9; row++){
|
||||
for col in 0..9 {
|
||||
// for (int col = 0; col < 9; col++) {
|
||||
assert!(bitMatrix.get(col, row));
|
||||
}
|
||||
}
|
||||
};
|
||||
for i in 1..=4 {
|
||||
// for (int i = 1; i <= 4; i++) {
|
||||
let version = Version::FromNumber(i, true).expect("version must be found");
|
||||
let functionPattern = version
|
||||
.buildFunctionPattern()
|
||||
.expect("function pattern must be found");
|
||||
testFinderPatternRegion(&functionPattern);
|
||||
|
||||
// Check timing pattern areas.
|
||||
let dimension = version.getDimensionForVersion();
|
||||
for row in dimension..functionPattern.height()
|
||||
// for (int row = dimension; row < functionPattern.height(); row++)
|
||||
{
|
||||
assert!(functionPattern.get(0, row));
|
||||
}
|
||||
for col in dimension..functionPattern.width()
|
||||
// for (int col = dimension; col < functionPattern.width(); col++)
|
||||
{
|
||||
assert!(functionPattern.get(col, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
14
src/qrcode/cpp_port/test/mod.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
mod QRModeTest;
|
||||
mod QRVersionTest;
|
||||
|
||||
mod QRFormatInformationTest;
|
||||
|
||||
mod QRErrorCorrectionLevelTest;
|
||||
|
||||
mod QRDecodedBitStreamParserTest;
|
||||
|
||||
mod QRDataMaskTest;
|
||||
|
||||
mod QRBitMatrixParserTest;
|
||||
|
||||
mod MQRDecoderTest;
|
||||
@@ -14,6 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use std::fmt::{self, Display};
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::common::Result;
|
||||
@@ -35,6 +36,7 @@ pub enum ErrorCorrectionLevel {
|
||||
Q, //0x03
|
||||
/** H = ~30% correction */
|
||||
H, //0x02
|
||||
Invalid,
|
||||
}
|
||||
|
||||
impl ErrorCorrectionLevel {
|
||||
@@ -60,6 +62,7 @@ impl ErrorCorrectionLevel {
|
||||
ErrorCorrectionLevel::M => 0x00,
|
||||
ErrorCorrectionLevel::Q => 0x03,
|
||||
ErrorCorrectionLevel::H => 0x02,
|
||||
ErrorCorrectionLevel::Invalid => 0x00,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,6 +72,7 @@ impl ErrorCorrectionLevel {
|
||||
ErrorCorrectionLevel::M => 1,
|
||||
ErrorCorrectionLevel::Q => 2,
|
||||
ErrorCorrectionLevel::H => 3,
|
||||
ErrorCorrectionLevel::Invalid => 100,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -115,3 +119,15 @@ impl FromStr for ErrorCorrectionLevel {
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ErrorCorrectionLevel {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str(match self {
|
||||
ErrorCorrectionLevel::L => "L",
|
||||
ErrorCorrectionLevel::M => "M",
|
||||
ErrorCorrectionLevel::Q => "Q",
|
||||
ErrorCorrectionLevel::H => "H",
|
||||
ErrorCorrectionLevel::Invalid => "_",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,12 +18,12 @@ use crate::common::Result;
|
||||
|
||||
use super::ErrorCorrectionLevel;
|
||||
|
||||
const FORMAT_INFO_MASK_QR: u32 = 0x5412;
|
||||
pub const FORMAT_INFO_MASK_QR: u32 = 0x5412;
|
||||
|
||||
/**
|
||||
* See ISO 18004:2006, Annex C, Table C.1
|
||||
*/
|
||||
const FORMAT_INFO_DECODE_LOOKUP: [[u32; 2]; 32] = [
|
||||
pub const FORMAT_INFO_DECODE_LOOKUP: [[u32; 2]; 32] = [
|
||||
[0x5412, 0x00],
|
||||
[0x5125, 0x01],
|
||||
[0x5E7C, 0x02],
|
||||
@@ -68,8 +68,28 @@ const FORMAT_INFO_DECODE_LOOKUP: [[u32; 2]; 32] = [
|
||||
*/
|
||||
#[derive(Hash, Eq, PartialEq, Debug)]
|
||||
pub struct FormatInformation {
|
||||
error_correction_level: ErrorCorrectionLevel,
|
||||
data_mask: u8,
|
||||
pub hammingDistance: u32,
|
||||
pub error_correction_level: ErrorCorrectionLevel,
|
||||
pub data_mask: u8,
|
||||
pub microVersion: u32,
|
||||
pub isMirrored: bool,
|
||||
|
||||
pub index: u8, // = 255;
|
||||
pub bitsIndex: u8, // = 255;
|
||||
}
|
||||
|
||||
impl Default for FormatInformation {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
hammingDistance: 255,
|
||||
error_correction_level: ErrorCorrectionLevel::Invalid,
|
||||
data_mask: Default::default(),
|
||||
microVersion: 0,
|
||||
isMirrored: false,
|
||||
index: 255,
|
||||
bitsIndex: 255,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FormatInformation {
|
||||
@@ -79,8 +99,13 @@ impl FormatInformation {
|
||||
// Bottom 3 bits
|
||||
let dataMask = format_info & 0x07;
|
||||
Ok(Self {
|
||||
hammingDistance: 255,
|
||||
microVersion: 0,
|
||||
error_correction_level: errorCorrectionLevel,
|
||||
data_mask: dataMask,
|
||||
isMirrored: false,
|
||||
index: 255,
|
||||
bitsIndex: 255,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -121,6 +121,81 @@ impl Mode {
|
||||
Mode::HANZI => 0x0D,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn get_terminator_bit_length(version: &Version) -> u8 {
|
||||
(if version.isMicroQRCode() {
|
||||
version.getVersionNumber() * 2 + 1
|
||||
} else {
|
||||
4
|
||||
}) as u8
|
||||
}
|
||||
pub const fn get_codec_mode_bits_length(version: &Version) -> u8 {
|
||||
(if version.isMicroQRCode() {
|
||||
version.getVersionNumber() - 1
|
||||
} else {
|
||||
4
|
||||
}) as u8
|
||||
}
|
||||
/**
|
||||
* @param bits variable number of bits encoding a QR Code data mode
|
||||
* @param isMicro is this a MicroQRCode
|
||||
* @return Mode encoded by these bits
|
||||
* @throws FormatError if bits do not correspond to a known mode
|
||||
*/
|
||||
pub fn CodecModeForBits(bits: u32, isMicro: Option<bool>) -> Result<Self> {
|
||||
let isMicro = isMicro.unwrap_or(false);
|
||||
const BITS_2_MODE_LEN: usize = 4;
|
||||
|
||||
if (!isMicro) {
|
||||
if ((bits >= 0x00 && bits <= 0x05) || (bits >= 0x07 && bits <= 0x09) || bits == 0x0d) {
|
||||
return Mode::try_from(bits);
|
||||
}
|
||||
} else {
|
||||
const Bits2Mode: [Mode; BITS_2_MODE_LEN] =
|
||||
[Mode::NUMERIC, Mode::ALPHANUMERIC, Mode::BYTE, Mode::KANJI];
|
||||
if ((bits as usize) < BITS_2_MODE_LEN) {
|
||||
return Ok(Bits2Mode[bits as usize]);
|
||||
}
|
||||
}
|
||||
|
||||
Err(Exceptions::format_with("Invalid codec mode"))
|
||||
}
|
||||
|
||||
/**
|
||||
* @param version version in question
|
||||
* @return number of bits used, in this QR Code symbol {@link Version}, to encode the
|
||||
* count of characters that will follow encoded in this Mode
|
||||
*/
|
||||
pub fn CharacterCountBits(&self, version: &Version) -> u32 {
|
||||
let number = version.getVersionNumber() as usize;
|
||||
if (version.isMicroQRCode()) {
|
||||
match (self) {
|
||||
Mode::NUMERIC=> return [3, 4, 5, 6][number - 1],
|
||||
Mode::ALPHANUMERIC=> return [3, 4, 5][number - 2],
|
||||
Mode::BYTE=> return [4, 5][number - 3],
|
||||
Mode::KANJI | //=> [[fallthrough]],
|
||||
Mode::HANZI=> return [3, 4][number - 3],
|
||||
_=> return 0,
|
||||
}
|
||||
}
|
||||
|
||||
let i = if (number <= 9) {
|
||||
0
|
||||
} else if (number <= 26) {
|
||||
1
|
||||
} else {
|
||||
2
|
||||
};
|
||||
|
||||
match (self) {
|
||||
Mode::NUMERIC=> return [10, 12, 14][i],
|
||||
Mode::ALPHANUMERIC=> return [9, 11, 13][i],
|
||||
Mode::BYTE=> return [8, 16, 16][i],
|
||||
Mode::KANJI| // [[fallthrough]];
|
||||
Mode::HANZI=> return [8, 10, 12][i],
|
||||
_=> return 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Mode> for u8 {
|
||||
@@ -136,3 +211,11 @@ impl TryFrom<u8> for Mode {
|
||||
Self::forBits(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u32> for Mode {
|
||||
type Error = Exceptions;
|
||||
|
||||
fn try_from(value: u32) -> Result<Self, Self::Error> {
|
||||
Self::forBits(value as u8)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,13 +27,14 @@ use once_cell::sync::Lazy;
|
||||
|
||||
pub type VersionRef = &'static Version;
|
||||
|
||||
static VERSIONS: Lazy<Vec<Version>> = Lazy::new(Version::buildVersions);
|
||||
pub static VERSIONS: Lazy<Vec<Version>> = Lazy::new(Version::buildVersions);
|
||||
pub static MICRO_VERSIONS: Lazy<Vec<Version>> = Lazy::new(Version::build_micro_versions);
|
||||
|
||||
/**
|
||||
* See ISO 18004:2006 Annex D.
|
||||
* Element i represents the raw version bits that specify version i + 7
|
||||
*/
|
||||
const VERSION_DECODE_INFO: [u32; 34] = [
|
||||
pub const VERSION_DECODE_INFO: [u32; 34] = [
|
||||
0x07C94, 0x085BC, 0x09A99, 0x0A4D3, 0x0BBF6, 0x0C762, 0x0D847, 0x0E60D, 0x0F928, 0x10B78,
|
||||
0x1145D, 0x12A17, 0x13532, 0x149A6, 0x15683, 0x168C9, 0x177EC, 0x18EC4, 0x191E1, 0x1AFAB,
|
||||
0x1B08E, 0x1CC1A, 0x1D33F, 0x1ED75, 0x1F250, 0x209D5, 0x216F0, 0x228BA, 0x2379F, 0x24B0B,
|
||||
@@ -53,9 +54,10 @@ pub struct Version {
|
||||
alignmentPatternCenters: Vec<u32>,
|
||||
ecBlocks: Vec<ECBlocks>,
|
||||
totalCodewords: u32,
|
||||
pub(crate) is_micro: bool,
|
||||
}
|
||||
impl Version {
|
||||
fn new(versionNumber: u32, alignmentPatternCenters: Vec<u32>, ecBlocks: Vec<ECBlocks>) -> Self {
|
||||
fn new(versionNumber: u32, alignmentPatternCenters: Vec<u32>, ecBlocks: [ECBlocks; 4]) -> Self {
|
||||
let mut total = 0;
|
||||
let ecCodewords = ecBlocks[0].getECCodewordsPerBlock();
|
||||
let ecbArray = ecBlocks[0].getECBlocks();
|
||||
@@ -68,12 +70,32 @@ impl Version {
|
||||
Self {
|
||||
versionNumber,
|
||||
alignmentPatternCenters,
|
||||
ecBlocks,
|
||||
ecBlocks: ecBlocks.to_vec(),
|
||||
totalCodewords: total,
|
||||
is_micro: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getVersionNumber(&self) -> u32 {
|
||||
fn new_micro(versionNumber: u32, ecBlocks: Vec<ECBlocks>) -> Self {
|
||||
let mut total = 0;
|
||||
let ecCodewords = ecBlocks[0].getECCodewordsPerBlock();
|
||||
let ecbArray = ecBlocks[0].getECBlocks();
|
||||
let mut i = 0;
|
||||
while i < ecbArray.len() {
|
||||
total += ecbArray[i].getCount() * (ecbArray[i].getDataCodewords() + ecCodewords);
|
||||
i += 1;
|
||||
}
|
||||
|
||||
Self {
|
||||
versionNumber,
|
||||
alignmentPatternCenters: Vec::default(),
|
||||
ecBlocks,
|
||||
totalCodewords: total,
|
||||
is_micro: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn getVersionNumber(&self) -> u32 {
|
||||
self.versionNumber
|
||||
}
|
||||
|
||||
@@ -86,10 +108,14 @@ impl Version {
|
||||
}
|
||||
|
||||
pub fn getDimensionForVersion(&self) -> u32 {
|
||||
17 + 4 * self.versionNumber
|
||||
Self::DimensionOfVersion(self.versionNumber, self.is_micro)
|
||||
// 17 + 4 * self.versionNumber
|
||||
}
|
||||
|
||||
pub fn getECBlocksForLevel(&self, ecLevel: ErrorCorrectionLevel) -> &ECBlocks {
|
||||
if ecLevel.get_ordinal() as usize >= self.ecBlocks.len() {
|
||||
return &self.ecBlocks[ecLevel.get_ordinal() as usize % self.ecBlocks.len()];
|
||||
}
|
||||
&self.ecBlocks[ecLevel.get_ordinal() as usize]
|
||||
}
|
||||
|
||||
@@ -100,21 +126,21 @@ impl Version {
|
||||
* @return Version for a QR Code of that dimension
|
||||
* @throws FormatException if dimension is not 1 mod 4
|
||||
*/
|
||||
pub fn getProvisionalVersionForDimension(dimension: u32) -> Result<&'static Version> {
|
||||
pub fn getProvisionalVersionForDimension(dimension: u32) -> Result<VersionRef> {
|
||||
if dimension % 4 != 1 {
|
||||
return Err(Exceptions::format_with("dimension incorrect"));
|
||||
}
|
||||
Self::getVersionForNumber((dimension - 17) / 4)
|
||||
}
|
||||
|
||||
pub fn getVersionForNumber(versionNumber: u32) -> Result<&'static Version> {
|
||||
pub fn getVersionForNumber(versionNumber: u32) -> Result<VersionRef> {
|
||||
if !(1..=40).contains(&versionNumber) {
|
||||
return Err(Exceptions::illegal_argument_with("version out of spec"));
|
||||
}
|
||||
Ok(&VERSIONS[versionNumber as usize - 1])
|
||||
}
|
||||
|
||||
pub fn decodeVersionInformation(versionBits: u32) -> Result<&'static Version> {
|
||||
pub fn decodeVersionInformation(versionBits: u32) -> Result<VersionRef> {
|
||||
let mut bestDifference = u32::MAX;
|
||||
let mut bestVersion = 0;
|
||||
for i in 0..VERSION_DECODE_INFO.len() as u32 {
|
||||
@@ -149,38 +175,80 @@ impl Version {
|
||||
|
||||
// Top left finder pattern + separator + format
|
||||
bitMatrix.setRegion(0, 0, 9, 9)?;
|
||||
// Top right finder pattern + separator + format
|
||||
bitMatrix.setRegion(dimension - 8, 0, 8, 9)?;
|
||||
// Bottom left finder pattern + separator + format
|
||||
bitMatrix.setRegion(0, dimension - 8, 9, 8)?;
|
||||
|
||||
// Alignment patterns
|
||||
let max = self.alignmentPatternCenters.len();
|
||||
for x in 0..max {
|
||||
let i = self.alignmentPatternCenters[x] - 2;
|
||||
for y in 0..max {
|
||||
if (x != 0 || (y != 0 && y != max - 1)) && (x != max - 1 || y != 0) {
|
||||
bitMatrix.setRegion(self.alignmentPatternCenters[y] - 2, i, 5, 5)?;
|
||||
if !self.is_micro {
|
||||
// Top right finder pattern + separator + format
|
||||
bitMatrix.setRegion(dimension - 8, 0, 8, 9)?;
|
||||
// Bottom left finder pattern + separator + format
|
||||
bitMatrix.setRegion(0, dimension - 8, 9, 8)?;
|
||||
|
||||
// Alignment patterns
|
||||
let max = self.alignmentPatternCenters.len();
|
||||
for x in 0..max {
|
||||
let i = self.alignmentPatternCenters[x] - 2;
|
||||
for y in 0..max {
|
||||
if (x != 0 || (y != 0 && y != max - 1)) && (x != max - 1 || y != 0) {
|
||||
bitMatrix.setRegion(self.alignmentPatternCenters[y] - 2, i, 5, 5)?;
|
||||
}
|
||||
// else no o alignment patterns near the three finder patterns
|
||||
}
|
||||
// else no o alignment patterns near the three finder patterns
|
||||
}
|
||||
}
|
||||
|
||||
// Vertical timing pattern
|
||||
bitMatrix.setRegion(6, 9, 1, dimension - 17)?;
|
||||
// Horizontal timing pattern
|
||||
bitMatrix.setRegion(9, 6, dimension - 17, 1)?;
|
||||
// Vertical timing pattern
|
||||
bitMatrix.setRegion(6, 9, 1, dimension - 17)?;
|
||||
// Horizontal timing pattern
|
||||
bitMatrix.setRegion(9, 6, dimension - 17, 1)?;
|
||||
|
||||
if self.versionNumber > 6 {
|
||||
// Version info, top right
|
||||
bitMatrix.setRegion(dimension - 11, 0, 3, 6)?;
|
||||
// Version info, bottom left
|
||||
bitMatrix.setRegion(0, dimension - 11, 6, 3)?;
|
||||
if self.versionNumber > 6 {
|
||||
// Version info, top right
|
||||
bitMatrix.setRegion(dimension - 11, 0, 3, 6)?;
|
||||
// Version info, bottom left
|
||||
bitMatrix.setRegion(0, dimension - 11, 6, 3)?;
|
||||
}
|
||||
} else {
|
||||
// Vertical timing pattern
|
||||
bitMatrix.setRegion(9, 0, dimension - 9, 1)?;
|
||||
|
||||
// Horizontal timing pattern
|
||||
bitMatrix.setRegion(0, 9, 1, dimension - 9)?;
|
||||
}
|
||||
|
||||
Ok(bitMatrix)
|
||||
}
|
||||
|
||||
pub fn build_micro_versions() -> Vec<Version> {
|
||||
vec![
|
||||
Version::new_micro(1, vec![ECBlocks::new(2, vec![ECB::new(1, 3)])]),
|
||||
Version::new_micro(
|
||||
2,
|
||||
vec![
|
||||
ECBlocks::new(5, vec![ECB::new(1, 5)]),
|
||||
ECBlocks::new(6, vec![ECB::new(1, 4)]),
|
||||
],
|
||||
),
|
||||
Version::new_micro(
|
||||
3,
|
||||
vec![
|
||||
ECBlocks::new(6, vec![ECB::new(1, 11)]),
|
||||
ECBlocks::new(8, vec![ECB::new(1, 9)]),
|
||||
],
|
||||
),
|
||||
Version::new_micro(
|
||||
4,
|
||||
vec![
|
||||
ECBlocks::new(8, vec![ECB::new(1, 16)]),
|
||||
ECBlocks::new(10, vec![ECB::new(1, 14)]),
|
||||
ECBlocks::new(14, vec![ECB::new(1, 10)]),
|
||||
],
|
||||
),
|
||||
]
|
||||
// static const Version allVersions[] = {
|
||||
// {1, {2, 1, 3, 0, 0}},
|
||||
// {2, {5, 1, 5, 0, 0, 6, 1, 4, 0, 0}},
|
||||
// {3, {6, 1, 11, 0, 0, 8, 1, 9, 0, 0}},
|
||||
// {4, {8, 1, 16, 0, 0, 10, 1, 14, 0, 0, 14, 1, 10, 0, 0}}};
|
||||
}
|
||||
|
||||
/**
|
||||
* See ISO 18004:2006 6.5.1 Table 9
|
||||
*/
|
||||
@@ -189,402 +257,402 @@ impl Version {
|
||||
Version::new(
|
||||
1,
|
||||
Vec::from([]),
|
||||
Vec::from([
|
||||
[
|
||||
ECBlocks::new(7, Vec::from([ECB::new(1, 19)])),
|
||||
ECBlocks::new(10, Vec::from([ECB::new(1, 16)])),
|
||||
ECBlocks::new(13, Vec::from([ECB::new(1, 13)])),
|
||||
ECBlocks::new(17, Vec::from([ECB::new(1, 9)])),
|
||||
]),
|
||||
],
|
||||
),
|
||||
Version::new(
|
||||
2,
|
||||
Vec::from([6, 18]),
|
||||
Vec::from([
|
||||
[
|
||||
ECBlocks::new(10, Vec::from([ECB::new(1, 34)])),
|
||||
ECBlocks::new(16, Vec::from([ECB::new(1, 28)])),
|
||||
ECBlocks::new(22, Vec::from([ECB::new(1, 22)])),
|
||||
ECBlocks::new(28, Vec::from([ECB::new(1, 16)])),
|
||||
]),
|
||||
],
|
||||
),
|
||||
Version::new(
|
||||
3,
|
||||
Vec::from([6, 22]),
|
||||
Vec::from([
|
||||
[
|
||||
ECBlocks::new(15, Vec::from([ECB::new(1, 55)])),
|
||||
ECBlocks::new(26, Vec::from([ECB::new(1, 44)])),
|
||||
ECBlocks::new(18, Vec::from([ECB::new(2, 17)])),
|
||||
ECBlocks::new(22, Vec::from([ECB::new(2, 13)])),
|
||||
]),
|
||||
],
|
||||
),
|
||||
Version::new(
|
||||
4,
|
||||
Vec::from([6, 26]),
|
||||
Vec::from([
|
||||
[
|
||||
ECBlocks::new(20, Vec::from([ECB::new(1, 80)])),
|
||||
ECBlocks::new(18, Vec::from([ECB::new(2, 32)])),
|
||||
ECBlocks::new(26, Vec::from([ECB::new(2, 24)])),
|
||||
ECBlocks::new(16, Vec::from([ECB::new(4, 9)])),
|
||||
]),
|
||||
],
|
||||
),
|
||||
Version::new(
|
||||
5,
|
||||
Vec::from([6, 30]),
|
||||
Vec::from([
|
||||
[
|
||||
ECBlocks::new(26, Vec::from([ECB::new(1, 108)])),
|
||||
ECBlocks::new(24, Vec::from([ECB::new(2, 43)])),
|
||||
ECBlocks::new(18, Vec::from([ECB::new(2, 15), ECB::new(2, 16)])),
|
||||
ECBlocks::new(22, Vec::from([ECB::new(2, 11), ECB::new(2, 12)])),
|
||||
]),
|
||||
],
|
||||
),
|
||||
Version::new(
|
||||
6,
|
||||
Vec::from([6, 34]),
|
||||
Vec::from([
|
||||
[
|
||||
ECBlocks::new(18, Vec::from([ECB::new(2, 68)])),
|
||||
ECBlocks::new(16, Vec::from([ECB::new(4, 27)])),
|
||||
ECBlocks::new(24, Vec::from([ECB::new(4, 19)])),
|
||||
ECBlocks::new(28, Vec::from([ECB::new(4, 15)])),
|
||||
]),
|
||||
],
|
||||
),
|
||||
Version::new(
|
||||
7,
|
||||
Vec::from([6, 22, 38]),
|
||||
Vec::from([
|
||||
[
|
||||
ECBlocks::new(20, Vec::from([ECB::new(2, 78)])),
|
||||
ECBlocks::new(18, Vec::from([ECB::new(4, 31)])),
|
||||
ECBlocks::new(18, Vec::from([ECB::new(2, 14), ECB::new(4, 15)])),
|
||||
ECBlocks::new(26, Vec::from([ECB::new(4, 13), ECB::new(1, 14)])),
|
||||
]),
|
||||
],
|
||||
),
|
||||
Version::new(
|
||||
8,
|
||||
Vec::from([6, 24, 42]),
|
||||
Vec::from([
|
||||
[
|
||||
ECBlocks::new(24, Vec::from([ECB::new(2, 97)])),
|
||||
ECBlocks::new(22, Vec::from([ECB::new(2, 38), ECB::new(2, 39)])),
|
||||
ECBlocks::new(22, Vec::from([ECB::new(4, 18), ECB::new(2, 19)])),
|
||||
ECBlocks::new(26, Vec::from([ECB::new(4, 14), ECB::new(2, 15)])),
|
||||
]),
|
||||
],
|
||||
),
|
||||
Version::new(
|
||||
9,
|
||||
Vec::from([6, 26, 46]),
|
||||
Vec::from([
|
||||
[
|
||||
ECBlocks::new(30, Vec::from([ECB::new(2, 116)])),
|
||||
ECBlocks::new(22, Vec::from([ECB::new(3, 36), ECB::new(2, 37)])),
|
||||
ECBlocks::new(20, Vec::from([ECB::new(4, 16), ECB::new(4, 17)])),
|
||||
ECBlocks::new(24, Vec::from([ECB::new(4, 12), ECB::new(4, 13)])),
|
||||
]),
|
||||
],
|
||||
),
|
||||
Version::new(
|
||||
10,
|
||||
Vec::from([6, 28, 50]),
|
||||
Vec::from([
|
||||
[
|
||||
ECBlocks::new(18, Vec::from([ECB::new(2, 68), ECB::new(2, 69)])),
|
||||
ECBlocks::new(26, Vec::from([ECB::new(4, 43), ECB::new(1, 44)])),
|
||||
ECBlocks::new(24, Vec::from([ECB::new(6, 19), ECB::new(2, 20)])),
|
||||
ECBlocks::new(28, Vec::from([ECB::new(6, 15), ECB::new(2, 16)])),
|
||||
]),
|
||||
],
|
||||
),
|
||||
Version::new(
|
||||
11,
|
||||
Vec::from([6, 30, 54]),
|
||||
Vec::from([
|
||||
[
|
||||
ECBlocks::new(20, Vec::from([ECB::new(4, 81)])),
|
||||
ECBlocks::new(30, Vec::from([ECB::new(1, 50), ECB::new(4, 51)])),
|
||||
ECBlocks::new(28, Vec::from([ECB::new(4, 22), ECB::new(4, 23)])),
|
||||
ECBlocks::new(24, Vec::from([ECB::new(3, 12), ECB::new(8, 13)])),
|
||||
]),
|
||||
],
|
||||
),
|
||||
Version::new(
|
||||
12,
|
||||
Vec::from([6, 32, 58]),
|
||||
Vec::from([
|
||||
[
|
||||
ECBlocks::new(24, Vec::from([ECB::new(2, 92), ECB::new(2, 93)])),
|
||||
ECBlocks::new(22, Vec::from([ECB::new(6, 36), ECB::new(2, 37)])),
|
||||
ECBlocks::new(26, Vec::from([ECB::new(4, 20), ECB::new(6, 21)])),
|
||||
ECBlocks::new(28, Vec::from([ECB::new(7, 14), ECB::new(4, 15)])),
|
||||
]),
|
||||
],
|
||||
),
|
||||
Version::new(
|
||||
13,
|
||||
Vec::from([6, 34, 62]),
|
||||
Vec::from([
|
||||
[
|
||||
ECBlocks::new(26, Vec::from([ECB::new(4, 107)])),
|
||||
ECBlocks::new(22, Vec::from([ECB::new(8, 37), ECB::new(1, 38)])),
|
||||
ECBlocks::new(24, Vec::from([ECB::new(8, 20), ECB::new(4, 21)])),
|
||||
ECBlocks::new(22, Vec::from([ECB::new(12, 11), ECB::new(4, 12)])),
|
||||
]),
|
||||
],
|
||||
),
|
||||
Version::new(
|
||||
14,
|
||||
Vec::from([6, 26, 46, 66]),
|
||||
Vec::from([
|
||||
[
|
||||
ECBlocks::new(30, Vec::from([ECB::new(3, 115), ECB::new(1, 116)])),
|
||||
ECBlocks::new(24, Vec::from([ECB::new(4, 40), ECB::new(5, 41)])),
|
||||
ECBlocks::new(20, Vec::from([ECB::new(11, 16), ECB::new(5, 17)])),
|
||||
ECBlocks::new(24, Vec::from([ECB::new(11, 12), ECB::new(5, 13)])),
|
||||
]),
|
||||
],
|
||||
),
|
||||
Version::new(
|
||||
15,
|
||||
Vec::from([6, 26, 48, 70]),
|
||||
Vec::from([
|
||||
[
|
||||
ECBlocks::new(22, Vec::from([ECB::new(5, 87), ECB::new(1, 88)])),
|
||||
ECBlocks::new(24, Vec::from([ECB::new(5, 41), ECB::new(5, 42)])),
|
||||
ECBlocks::new(30, Vec::from([ECB::new(5, 24), ECB::new(7, 25)])),
|
||||
ECBlocks::new(24, Vec::from([ECB::new(11, 12), ECB::new(7, 13)])),
|
||||
]),
|
||||
],
|
||||
),
|
||||
Version::new(
|
||||
16,
|
||||
Vec::from([6, 26, 50, 74]),
|
||||
Vec::from([
|
||||
[
|
||||
ECBlocks::new(24, Vec::from([ECB::new(5, 98), ECB::new(1, 99)])),
|
||||
ECBlocks::new(28, Vec::from([ECB::new(7, 45), ECB::new(3, 46)])),
|
||||
ECBlocks::new(24, Vec::from([ECB::new(15, 19), ECB::new(2, 20)])),
|
||||
ECBlocks::new(30, Vec::from([ECB::new(3, 15), ECB::new(13, 16)])),
|
||||
]),
|
||||
],
|
||||
),
|
||||
Version::new(
|
||||
17,
|
||||
Vec::from([6, 30, 54, 78]),
|
||||
Vec::from([
|
||||
[
|
||||
ECBlocks::new(28, Vec::from([ECB::new(1, 107), ECB::new(5, 108)])),
|
||||
ECBlocks::new(28, Vec::from([ECB::new(10, 46), ECB::new(1, 47)])),
|
||||
ECBlocks::new(28, Vec::from([ECB::new(1, 22), ECB::new(15, 23)])),
|
||||
ECBlocks::new(28, Vec::from([ECB::new(2, 14), ECB::new(17, 15)])),
|
||||
]),
|
||||
],
|
||||
),
|
||||
Version::new(
|
||||
18,
|
||||
Vec::from([6, 30, 56, 82]),
|
||||
Vec::from([
|
||||
[
|
||||
ECBlocks::new(30, Vec::from([ECB::new(5, 120), ECB::new(1, 121)])),
|
||||
ECBlocks::new(26, Vec::from([ECB::new(9, 43), ECB::new(4, 44)])),
|
||||
ECBlocks::new(28, Vec::from([ECB::new(17, 22), ECB::new(1, 23)])),
|
||||
ECBlocks::new(28, Vec::from([ECB::new(2, 14), ECB::new(19, 15)])),
|
||||
]),
|
||||
],
|
||||
),
|
||||
Version::new(
|
||||
19,
|
||||
Vec::from([6, 30, 58, 86]),
|
||||
Vec::from([
|
||||
[
|
||||
ECBlocks::new(28, Vec::from([ECB::new(3, 113), ECB::new(4, 114)])),
|
||||
ECBlocks::new(26, Vec::from([ECB::new(3, 44), ECB::new(11, 45)])),
|
||||
ECBlocks::new(26, Vec::from([ECB::new(17, 21), ECB::new(4, 22)])),
|
||||
ECBlocks::new(26, Vec::from([ECB::new(9, 13), ECB::new(16, 14)])),
|
||||
]),
|
||||
],
|
||||
),
|
||||
Version::new(
|
||||
20,
|
||||
Vec::from([6, 34, 62, 90]),
|
||||
Vec::from([
|
||||
[
|
||||
ECBlocks::new(28, Vec::from([ECB::new(3, 107), ECB::new(5, 108)])),
|
||||
ECBlocks::new(26, Vec::from([ECB::new(3, 41), ECB::new(13, 42)])),
|
||||
ECBlocks::new(30, Vec::from([ECB::new(15, 24), ECB::new(5, 25)])),
|
||||
ECBlocks::new(28, Vec::from([ECB::new(15, 15), ECB::new(10, 16)])),
|
||||
]),
|
||||
],
|
||||
),
|
||||
Version::new(
|
||||
21,
|
||||
Vec::from([6, 28, 50, 72, 94]),
|
||||
Vec::from([
|
||||
[
|
||||
ECBlocks::new(28, Vec::from([ECB::new(4, 116), ECB::new(4, 117)])),
|
||||
ECBlocks::new(26, Vec::from([ECB::new(17, 42)])),
|
||||
ECBlocks::new(28, Vec::from([ECB::new(17, 22), ECB::new(6, 23)])),
|
||||
ECBlocks::new(30, Vec::from([ECB::new(19, 16), ECB::new(6, 17)])),
|
||||
]),
|
||||
],
|
||||
),
|
||||
Version::new(
|
||||
22,
|
||||
Vec::from([6, 26, 50, 74, 98]),
|
||||
Vec::from([
|
||||
[
|
||||
ECBlocks::new(28, Vec::from([ECB::new(2, 111), ECB::new(7, 112)])),
|
||||
ECBlocks::new(28, Vec::from([ECB::new(17, 46)])),
|
||||
ECBlocks::new(30, Vec::from([ECB::new(7, 24), ECB::new(16, 25)])),
|
||||
ECBlocks::new(24, Vec::from([ECB::new(34, 13)])),
|
||||
]),
|
||||
],
|
||||
),
|
||||
Version::new(
|
||||
23,
|
||||
Vec::from([6, 30, 54, 78, 102]),
|
||||
Vec::from([
|
||||
[
|
||||
ECBlocks::new(30, Vec::from([ECB::new(4, 121), ECB::new(5, 122)])),
|
||||
ECBlocks::new(28, Vec::from([ECB::new(4, 47), ECB::new(14, 48)])),
|
||||
ECBlocks::new(30, Vec::from([ECB::new(11, 24), ECB::new(14, 25)])),
|
||||
ECBlocks::new(30, Vec::from([ECB::new(16, 15), ECB::new(14, 16)])),
|
||||
]),
|
||||
],
|
||||
),
|
||||
Version::new(
|
||||
24,
|
||||
Vec::from([6, 28, 54, 80, 106]),
|
||||
Vec::from([
|
||||
[
|
||||
ECBlocks::new(30, Vec::from([ECB::new(6, 117), ECB::new(4, 118)])),
|
||||
ECBlocks::new(28, Vec::from([ECB::new(6, 45), ECB::new(14, 46)])),
|
||||
ECBlocks::new(30, Vec::from([ECB::new(11, 24), ECB::new(16, 25)])),
|
||||
ECBlocks::new(30, Vec::from([ECB::new(30, 16), ECB::new(2, 17)])),
|
||||
]),
|
||||
],
|
||||
),
|
||||
Version::new(
|
||||
25,
|
||||
Vec::from([6, 32, 58, 84, 110]),
|
||||
Vec::from([
|
||||
[
|
||||
ECBlocks::new(26, Vec::from([ECB::new(8, 106), ECB::new(4, 107)])),
|
||||
ECBlocks::new(28, Vec::from([ECB::new(8, 47), ECB::new(13, 48)])),
|
||||
ECBlocks::new(30, Vec::from([ECB::new(7, 24), ECB::new(22, 25)])),
|
||||
ECBlocks::new(30, Vec::from([ECB::new(22, 15), ECB::new(13, 16)])),
|
||||
]),
|
||||
],
|
||||
),
|
||||
Version::new(
|
||||
26,
|
||||
Vec::from([6, 30, 58, 86, 114]),
|
||||
Vec::from([
|
||||
[
|
||||
ECBlocks::new(28, Vec::from([ECB::new(10, 114), ECB::new(2, 115)])),
|
||||
ECBlocks::new(28, Vec::from([ECB::new(19, 46), ECB::new(4, 47)])),
|
||||
ECBlocks::new(28, Vec::from([ECB::new(28, 22), ECB::new(6, 23)])),
|
||||
ECBlocks::new(30, Vec::from([ECB::new(33, 16), ECB::new(4, 17)])),
|
||||
]),
|
||||
],
|
||||
),
|
||||
Version::new(
|
||||
27,
|
||||
Vec::from([6, 34, 62, 90, 118]),
|
||||
Vec::from([
|
||||
[
|
||||
ECBlocks::new(30, Vec::from([ECB::new(8, 122), ECB::new(4, 123)])),
|
||||
ECBlocks::new(28, Vec::from([ECB::new(22, 45), ECB::new(3, 46)])),
|
||||
ECBlocks::new(30, Vec::from([ECB::new(8, 23), ECB::new(26, 24)])),
|
||||
ECBlocks::new(30, Vec::from([ECB::new(12, 15), ECB::new(28, 16)])),
|
||||
]),
|
||||
],
|
||||
),
|
||||
Version::new(
|
||||
28,
|
||||
Vec::from([6, 26, 50, 74, 98, 122]),
|
||||
Vec::from([
|
||||
[
|
||||
ECBlocks::new(30, Vec::from([ECB::new(3, 117), ECB::new(10, 118)])),
|
||||
ECBlocks::new(28, Vec::from([ECB::new(3, 45), ECB::new(23, 46)])),
|
||||
ECBlocks::new(30, Vec::from([ECB::new(4, 24), ECB::new(31, 25)])),
|
||||
ECBlocks::new(30, Vec::from([ECB::new(11, 15), ECB::new(31, 16)])),
|
||||
]),
|
||||
],
|
||||
),
|
||||
Version::new(
|
||||
29,
|
||||
Vec::from([6, 30, 54, 78, 102, 126]),
|
||||
Vec::from([
|
||||
[
|
||||
ECBlocks::new(30, Vec::from([ECB::new(7, 116), ECB::new(7, 117)])),
|
||||
ECBlocks::new(28, Vec::from([ECB::new(21, 45), ECB::new(7, 46)])),
|
||||
ECBlocks::new(30, Vec::from([ECB::new(1, 23), ECB::new(37, 24)])),
|
||||
ECBlocks::new(30, Vec::from([ECB::new(19, 15), ECB::new(26, 16)])),
|
||||
]),
|
||||
],
|
||||
),
|
||||
Version::new(
|
||||
30,
|
||||
Vec::from([6, 26, 52, 78, 104, 130]),
|
||||
Vec::from([
|
||||
[
|
||||
ECBlocks::new(30, Vec::from([ECB::new(5, 115), ECB::new(10, 116)])),
|
||||
ECBlocks::new(28, Vec::from([ECB::new(19, 47), ECB::new(10, 48)])),
|
||||
ECBlocks::new(30, Vec::from([ECB::new(15, 24), ECB::new(25, 25)])),
|
||||
ECBlocks::new(30, Vec::from([ECB::new(23, 15), ECB::new(25, 16)])),
|
||||
]),
|
||||
],
|
||||
),
|
||||
Version::new(
|
||||
31,
|
||||
Vec::from([6, 30, 56, 82, 108, 134]),
|
||||
Vec::from([
|
||||
[
|
||||
ECBlocks::new(30, Vec::from([ECB::new(13, 115), ECB::new(3, 116)])),
|
||||
ECBlocks::new(28, Vec::from([ECB::new(2, 46), ECB::new(29, 47)])),
|
||||
ECBlocks::new(30, Vec::from([ECB::new(42, 24), ECB::new(1, 25)])),
|
||||
ECBlocks::new(30, Vec::from([ECB::new(23, 15), ECB::new(28, 16)])),
|
||||
]),
|
||||
],
|
||||
),
|
||||
Version::new(
|
||||
32,
|
||||
Vec::from([6, 34, 60, 86, 112, 138]),
|
||||
Vec::from([
|
||||
[
|
||||
ECBlocks::new(30, Vec::from([ECB::new(17, 115)])),
|
||||
ECBlocks::new(28, Vec::from([ECB::new(10, 46), ECB::new(23, 47)])),
|
||||
ECBlocks::new(30, Vec::from([ECB::new(10, 24), ECB::new(35, 25)])),
|
||||
ECBlocks::new(30, Vec::from([ECB::new(19, 15), ECB::new(35, 16)])),
|
||||
]),
|
||||
],
|
||||
),
|
||||
Version::new(
|
||||
33,
|
||||
Vec::from([6, 30, 58, 86, 114, 142]),
|
||||
Vec::from([
|
||||
[
|
||||
ECBlocks::new(30, Vec::from([ECB::new(17, 115), ECB::new(1, 116)])),
|
||||
ECBlocks::new(28, Vec::from([ECB::new(14, 46), ECB::new(21, 47)])),
|
||||
ECBlocks::new(30, Vec::from([ECB::new(29, 24), ECB::new(19, 25)])),
|
||||
ECBlocks::new(30, Vec::from([ECB::new(11, 15), ECB::new(46, 16)])),
|
||||
]),
|
||||
],
|
||||
),
|
||||
Version::new(
|
||||
34,
|
||||
Vec::from([6, 34, 62, 90, 118, 146]),
|
||||
Vec::from([
|
||||
[
|
||||
ECBlocks::new(30, Vec::from([ECB::new(13, 115), ECB::new(6, 116)])),
|
||||
ECBlocks::new(28, Vec::from([ECB::new(14, 46), ECB::new(23, 47)])),
|
||||
ECBlocks::new(30, Vec::from([ECB::new(44, 24), ECB::new(7, 25)])),
|
||||
ECBlocks::new(30, Vec::from([ECB::new(59, 16), ECB::new(1, 17)])),
|
||||
]),
|
||||
],
|
||||
),
|
||||
Version::new(
|
||||
35,
|
||||
Vec::from([6, 30, 54, 78, 102, 126, 150]),
|
||||
Vec::from([
|
||||
[
|
||||
ECBlocks::new(30, Vec::from([ECB::new(12, 121), ECB::new(7, 122)])),
|
||||
ECBlocks::new(28, Vec::from([ECB::new(12, 47), ECB::new(26, 48)])),
|
||||
ECBlocks::new(30, Vec::from([ECB::new(39, 24), ECB::new(14, 25)])),
|
||||
ECBlocks::new(30, Vec::from([ECB::new(22, 15), ECB::new(41, 16)])),
|
||||
]),
|
||||
],
|
||||
),
|
||||
Version::new(
|
||||
36,
|
||||
Vec::from([6, 24, 50, 76, 102, 128, 154]),
|
||||
Vec::from([
|
||||
[
|
||||
ECBlocks::new(30, Vec::from([ECB::new(6, 121), ECB::new(14, 122)])),
|
||||
ECBlocks::new(28, Vec::from([ECB::new(6, 47), ECB::new(34, 48)])),
|
||||
ECBlocks::new(30, Vec::from([ECB::new(46, 24), ECB::new(10, 25)])),
|
||||
ECBlocks::new(30, Vec::from([ECB::new(2, 15), ECB::new(64, 16)])),
|
||||
]),
|
||||
],
|
||||
),
|
||||
Version::new(
|
||||
37,
|
||||
Vec::from([6, 28, 54, 80, 106, 132, 158]),
|
||||
Vec::from([
|
||||
[
|
||||
ECBlocks::new(30, Vec::from([ECB::new(17, 122), ECB::new(4, 123)])),
|
||||
ECBlocks::new(28, Vec::from([ECB::new(29, 46), ECB::new(14, 47)])),
|
||||
ECBlocks::new(30, Vec::from([ECB::new(49, 24), ECB::new(10, 25)])),
|
||||
ECBlocks::new(30, Vec::from([ECB::new(24, 15), ECB::new(46, 16)])),
|
||||
]),
|
||||
],
|
||||
),
|
||||
Version::new(
|
||||
38,
|
||||
Vec::from([6, 32, 58, 84, 110, 136, 162]),
|
||||
Vec::from([
|
||||
[
|
||||
ECBlocks::new(30, Vec::from([ECB::new(4, 122), ECB::new(18, 123)])),
|
||||
ECBlocks::new(28, Vec::from([ECB::new(13, 46), ECB::new(32, 47)])),
|
||||
ECBlocks::new(30, Vec::from([ECB::new(48, 24), ECB::new(14, 25)])),
|
||||
ECBlocks::new(30, Vec::from([ECB::new(42, 15), ECB::new(32, 16)])),
|
||||
]),
|
||||
],
|
||||
),
|
||||
Version::new(
|
||||
39,
|
||||
Vec::from([6, 26, 54, 82, 110, 138, 166]),
|
||||
Vec::from([
|
||||
[
|
||||
ECBlocks::new(30, Vec::from([ECB::new(20, 117), ECB::new(4, 118)])),
|
||||
ECBlocks::new(28, Vec::from([ECB::new(40, 47), ECB::new(7, 48)])),
|
||||
ECBlocks::new(30, Vec::from([ECB::new(43, 24), ECB::new(22, 25)])),
|
||||
ECBlocks::new(30, Vec::from([ECB::new(10, 15), ECB::new(67, 16)])),
|
||||
]),
|
||||
],
|
||||
),
|
||||
Version::new(
|
||||
40,
|
||||
Vec::from([6, 30, 58, 86, 114, 142, 170]),
|
||||
Vec::from([
|
||||
[
|
||||
ECBlocks::new(30, Vec::from([ECB::new(19, 118), ECB::new(6, 119)])),
|
||||
ECBlocks::new(28, Vec::from([ECB::new(18, 47), ECB::new(31, 48)])),
|
||||
ECBlocks::new(30, Vec::from([ECB::new(34, 24), ECB::new(34, 25)])),
|
||||
ECBlocks::new(30, Vec::from([ECB::new(20, 15), ECB::new(61, 16)])),
|
||||
]),
|
||||
],
|
||||
),
|
||||
]) /*
|
||||
new Version(4, new int[]{6, 26},
|
||||
@@ -916,7 +984,7 @@ impl fmt::Display for Version {
|
||||
* each set of blocks. It also holds the number of error-correction codewords per block since it
|
||||
* will be the same across all blocks within one version.</p>
|
||||
*/
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ECBlocks {
|
||||
ecCodewordsPerBlock: u32,
|
||||
ecBlocks: Vec<ECB>,
|
||||
@@ -930,7 +998,7 @@ impl ECBlocks {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getECCodewordsPerBlock(&self) -> u32 {
|
||||
pub const fn getECCodewordsPerBlock(&self) -> u32 {
|
||||
self.ecCodewordsPerBlock
|
||||
}
|
||||
|
||||
@@ -956,7 +1024,7 @@ impl ECBlocks {
|
||||
* This includes the number of data codewords, and the number of times a block with these
|
||||
* parameters is used consecutively in the QR code version's format.</p>
|
||||
*/
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct ECB {
|
||||
count: u32,
|
||||
dataCodewords: u32,
|
||||
@@ -970,11 +1038,11 @@ impl ECB {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getCount(&self) -> u32 {
|
||||
pub const fn getCount(&self) -> u32 {
|
||||
self.count
|
||||
}
|
||||
|
||||
pub fn getDataCodewords(&self) -> u32 {
|
||||
pub const fn getDataCodewords(&self) -> u32 {
|
||||
self.dataCodewords
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,12 +218,13 @@ impl<'a> Detector<'_> {
|
||||
dimension: u32,
|
||||
) -> Result<BitMatrix> {
|
||||
let sampler = DefaultGridSampler::default();
|
||||
sampler.sample_grid(
|
||||
let (res, _) = sampler.sample_grid(
|
||||
image,
|
||||
dimension,
|
||||
dimension,
|
||||
&[SamplerControl::new(dimension, dimension, transform)],
|
||||
)
|
||||
)?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,6 +8,8 @@ pub use qr_code_reader::*;
|
||||
mod qr_code_writer;
|
||||
pub use qr_code_writer::*;
|
||||
|
||||
pub mod cpp_port;
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(feature = "image")]
|
||||
mod QRCodeWriterTestCase;
|
||||
|
||||
@@ -16,7 +16,10 @@
|
||||
|
||||
use std::{collections::HashMap, fmt};
|
||||
|
||||
use crate::{BarcodeFormat, Point, RXingResultMetadataType, RXingResultMetadataValue};
|
||||
use crate::{
|
||||
common::cpp_essentials::DecoderResult, BarcodeFormat, MetadataDictionary, Point,
|
||||
RXingResultMetadataType, RXingResultMetadataValue,
|
||||
};
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -95,6 +98,44 @@ impl RXingResult {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_decoder_result<T>(
|
||||
res: DecoderResult<T>,
|
||||
resultPoints: &[Point],
|
||||
format: BarcodeFormat,
|
||||
) -> Self
|
||||
where
|
||||
T: Copy + Clone + Default + Eq + PartialEq,
|
||||
{
|
||||
let mut new_res = Self::new(
|
||||
&res.text(),
|
||||
res.content().bytes().to_vec(),
|
||||
resultPoints.to_vec(),
|
||||
format,
|
||||
);
|
||||
|
||||
let mut meta_data = MetadataDictionary::new();
|
||||
meta_data.insert(
|
||||
RXingResultMetadataType::ERROR_CORRECTION_LEVEL,
|
||||
RXingResultMetadataValue::ErrorCorrectionLevel(res.ecLevel().to_owned()),
|
||||
);
|
||||
meta_data.insert(
|
||||
RXingResultMetadataType::STRUCTURED_APPEND_PARITY,
|
||||
RXingResultMetadataValue::StructuredAppendParity(res.structuredAppend().count),
|
||||
);
|
||||
meta_data.insert(
|
||||
RXingResultMetadataType::STRUCTURED_APPEND_SEQUENCE,
|
||||
RXingResultMetadataValue::StructuredAppendSequence(res.structuredAppend().index),
|
||||
);
|
||||
meta_data.insert(
|
||||
RXingResultMetadataType::SYMBOLOGY_IDENTIFIER,
|
||||
RXingResultMetadataValue::SymbologyIdentifier(res.symbologyIdentifier()),
|
||||
);
|
||||
|
||||
new_res.putAllMetadata(meta_data);
|
||||
|
||||
new_res
|
||||
}
|
||||
|
||||
/**
|
||||
* @return raw text encoded by the barcode
|
||||
*/
|
||||
|
||||
@@ -112,6 +112,8 @@ pub enum RXingResultMetadataType {
|
||||
IS_MIRRORED,
|
||||
|
||||
CONTENT_TYPE,
|
||||
|
||||
IS_INVERTED,
|
||||
}
|
||||
|
||||
impl From<String> for RXingResultMetadataType {
|
||||
@@ -120,7 +122,7 @@ impl From<String> for RXingResultMetadataType {
|
||||
"OTHER" => RXingResultMetadataType::OTHER,
|
||||
"ORIENTATION" => RXingResultMetadataType::ORIENTATION,
|
||||
"BYTE_SEGMENTS" | "BYTESEGMENTS" => RXingResultMetadataType::BYTE_SEGMENTS,
|
||||
"ERROR_CORRECTION_LEVEL" | "ERRORCORRECTIONLEVEL" => {
|
||||
"ERROR_CORRECTION_LEVEL" | "ERRORCORRECTIONLEVEL" | "ECLEVEL" => {
|
||||
RXingResultMetadataType::ERROR_CORRECTION_LEVEL
|
||||
}
|
||||
"ISSUE_NUMBER" | "ISSUENUMBER" => RXingResultMetadataType::ISSUE_NUMBER,
|
||||
@@ -141,6 +143,7 @@ impl From<String> for RXingResultMetadataType {
|
||||
}
|
||||
"IS_MIRRORED" | "ISMIRRORED" => RXingResultMetadataType::IS_MIRRORED,
|
||||
"CONTENT_TYPE" | "CONTENTTYPE" => RXingResultMetadataType::CONTENT_TYPE,
|
||||
"ISINVERTED" => RXingResultMetadataType::IS_INVERTED,
|
||||
_ => RXingResultMetadataType::OTHER,
|
||||
}
|
||||
}
|
||||
@@ -229,4 +232,6 @@ pub enum RXingResultMetadataValue {
|
||||
IsMirrored(bool),
|
||||
|
||||
ContentType(String),
|
||||
|
||||
IsInverted(bool),
|
||||
}
|
||||
|
||||
@@ -25,6 +25,14 @@ pub fn point(x: f32, y: f32) -> Point {
|
||||
Point::new(x, y)
|
||||
}
|
||||
|
||||
pub fn point_g<T: TryInto<f32>>(x: T, y: T) -> Option<Point> {
|
||||
Some(Point::new(x.try_into().ok()?, y.try_into().ok()?))
|
||||
}
|
||||
|
||||
pub fn point_i<T: Into<i64>>(x: T, y: T) -> Point {
|
||||
Point::new(x.into() as f32, y.into() as f32)
|
||||
}
|
||||
|
||||
/** Currently necessary because the external OneDReader proc macro uses it. */
|
||||
pub type RXingResultPoint = Point;
|
||||
|
||||
@@ -117,6 +125,22 @@ impl std::ops::Add for Point {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Add<f32> for Point {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, rhs: f32) -> Self::Output {
|
||||
Self::new(self.x + rhs, self.y + rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Add<Point> for f32 {
|
||||
type Output = Point;
|
||||
|
||||
fn add(self, rhs: Point) -> Self::Output {
|
||||
Point::new(rhs.x + self, rhs.y + self)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Mul for Point {
|
||||
type Output = Self;
|
||||
|
||||
@@ -141,6 +165,14 @@ impl std::ops::Mul<i32> for Point {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Mul<u32> for Point {
|
||||
type Output = Self;
|
||||
|
||||
fn mul(self, rhs: u32) -> Self::Output {
|
||||
Self::new(self.x * rhs as f32, self.y * rhs as f32)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Mul<Point> for i32 {
|
||||
type Output = Point;
|
||||
|
||||
@@ -165,6 +197,14 @@ impl std::ops::Div<f32> for Point {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Mul<Point> for u32 {
|
||||
type Output = Point;
|
||||
|
||||
fn mul(self, rhs: Point) -> Self::Output {
|
||||
Self::Output::new(rhs.x * self as f32, rhs.y * self as f32)
|
||||
}
|
||||
}
|
||||
|
||||
impl Point {
|
||||
pub fn dot(self, p: Self) -> f32 {
|
||||
self.x * p.x + self.y * p.y
|
||||
@@ -240,6 +280,13 @@ impl Point {
|
||||
y: self.y.round(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn floor(self) -> Self {
|
||||
Self {
|
||||
x: self.x.floor(),
|
||||
y: self.y.floor(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&(f32, f32)> for Point {
|
||||
@@ -260,6 +307,12 @@ impl From<(i32, i32)> for Point {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(u32, u32)> for Point {
|
||||
fn from(value: (u32, u32)) -> Self {
|
||||
Self::new(value.0 as f32, value.1 as f32)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Point;
|
||||
|
||||
BIN
test_resources/blackbox/cpp/microqrcode-1/1.png
Normal file
|
After Width: | Height: | Size: 8.3 KiB |
1
test_resources/blackbox/cpp/microqrcode-1/1.txt
Normal file
@@ -0,0 +1 @@
|
||||
Wikipedia
|
||||
BIN
test_resources/blackbox/cpp/microqrcode-1/11.jpg
Normal file
|
After Width: | Height: | Size: 29 KiB |
1
test_resources/blackbox/cpp/microqrcode-1/11.txt
Normal file
@@ -0,0 +1 @@
|
||||
Loser
|
||||
BIN
test_resources/blackbox/cpp/microqrcode-1/12.jpg
Normal file
|
After Width: | Height: | Size: 19 KiB |
1
test_resources/blackbox/cpp/microqrcode-1/12.txt
Normal file
@@ -0,0 +1 @@
|
||||
SN888623311
|
||||
BIN
test_resources/blackbox/cpp/microqrcode-1/3.jpg
Normal file
|
After Width: | Height: | Size: 27 KiB |
1
test_resources/blackbox/cpp/microqrcode-1/3.txt
Normal file
@@ -0,0 +1 @@
|
||||
malgajninto
|
||||
BIN
test_resources/blackbox/cpp/microqrcode-1/7.jpg
Normal file
|
After Width: | Height: | Size: 27 KiB |
1
test_resources/blackbox/cpp/microqrcode-1/7.txt
Normal file
@@ -0,0 +1 @@
|
||||
Victus
|
||||
BIN
test_resources/blackbox/cpp/microqrcode-1/9.jpg
Normal file
|
After Width: | Height: | Size: 9.0 KiB |
1
test_resources/blackbox/cpp/microqrcode-1/9.txt
Normal file
@@ -0,0 +1 @@
|
||||
ezik
|
||||
BIN
test_resources/blackbox/cpp/microqrcode-1/M1-Numeric.png
Normal file
|
After Width: | Height: | Size: 131 B |
1
test_resources/blackbox/cpp/microqrcode-1/M1-Numeric.txt
Normal file
@@ -0,0 +1 @@
|
||||
12345
|
||||
BIN
test_resources/blackbox/cpp/microqrcode-1/M2-Alpha.png
Normal file
|
After Width: | Height: | Size: 146 B |
1
test_resources/blackbox/cpp/microqrcode-1/M2-Alpha.txt
Normal file
@@ -0,0 +1 @@
|
||||
ABC
|
||||
BIN
test_resources/blackbox/cpp/microqrcode-1/M2-Numeric.png
Normal file
|
After Width: | Height: | Size: 144 B |
1
test_resources/blackbox/cpp/microqrcode-1/M2-Numeric.txt
Normal file
@@ -0,0 +1 @@
|
||||
1234567890
|
||||
BIN
test_resources/blackbox/cpp/microqrcode-1/M3-Binary.png
Normal file
|
After Width: | Height: | Size: 152 B |
1
test_resources/blackbox/cpp/microqrcode-1/M3-Binary.txt
Normal file
@@ -0,0 +1 @@
|
||||
E=mc²
|
||||
BIN
test_resources/blackbox/cpp/microqrcode-1/M3-Kanji.png
Normal file
|
After Width: | Height: | Size: 153 B |
1
test_resources/blackbox/cpp/microqrcode-1/M3-Kanji.txt
Normal file
@@ -0,0 +1 @@
|
||||
誠
|
||||
BIN
test_resources/blackbox/cpp/microqrcode-1/M3-Mixed.png
Normal file
|
After Width: | Height: | Size: 150 B |
1
test_resources/blackbox/cpp/microqrcode-1/M3-Mixed.txt
Normal file
@@ -0,0 +1 @@
|
||||
1234 ABCD
|
||||
BIN
test_resources/blackbox/cpp/microqrcode-1/M4-Binary.png
Normal file
|
After Width: | Height: | Size: 168 B |
1
test_resources/blackbox/cpp/microqrcode-1/M4-Binary.txt
Normal file
@@ -0,0 +1 @@
|
||||
!"§$%&/()=?`
|
||||
BIN
test_resources/blackbox/cpp/microqrcode-1/M4-Mixed.png
Normal file
|
After Width: | Height: | Size: 167 B |
1
test_resources/blackbox/cpp/microqrcode-1/M4-Mixed.txt
Normal file
@@ -0,0 +1 @@
|
||||
1234 ABCD abcd
|
||||