Merge pull request #30 from rxing-core/port_cpp_qrcode

Port cpp qrcode
This commit is contained in:
Henry A Schimke
2023-04-28 18:14:42 -05:00
committed by GitHub
449 changed files with 8517 additions and 381 deletions

View File

@@ -13,7 +13,7 @@ exclude = [
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
regex = "1.7.1" regex = "1.8.0"
fancy-regex = "0.11" fancy-regex = "0.11"
once_cell = "1.17.1" once_cell = "1.17.1"
encoding = "0.2" encoding = "0.2"
@@ -31,6 +31,7 @@ svg = {version = "0.13", optional = true}
resvg = {version = "0.28.0", optional = true, default-features=false} resvg = {version = "0.28.0", optional = true, default-features=false}
serde = { version = "1.0", features = ["derive", "rc"], optional = true } serde = { version = "1.0", features = ["derive", "rc"], optional = true }
thiserror = "1.0.38" thiserror = "1.0.38"
multimap = "0.8.3"
[dev-dependencies] [dev-dependencies]
java-properties = "1.4.1" java-properties = "1.4.1"

View File

@@ -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 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [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"] } rxing = {path = "../../", version = "~0.4.0", features = ["image", "svg_read", "svg_write"] }

View File

@@ -115,12 +115,18 @@ fn test_error_in_parameter_locator(data: &str) {
// dbg!(copy.to_string()); // dbg!(copy.to_string());
// dbg!(make_larger(&copy, 3).to_string()); // dbg!(make_larger(&copy, 3).to_string());
// The detector doesn't seem to work when matrix bits are only 1x1. So magnify. // 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(&copy, 3)).detect(isMirror); let r = Detector::new(&make_larger(&copy, 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"); let r = r.expect("result already tested as ok");
assert_eq!(r.getNbLayers(), layers); assert_eq!(r.getNbLayers(), layers);
assert_eq!(r.isCompact(), compact); 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()); assert_eq!(data, res.getText());
} }
} }

View File

@@ -469,7 +469,9 @@ impl<'a> Detector<'_> {
point(low, high), 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)
} }
/** /**

View File

@@ -65,6 +65,8 @@ pub enum BarcodeFormat {
/** QR Code 2D barcode format. */ /** QR Code 2D barcode format. */
QR_CODE, QR_CODE,
MICRO_QR_CODE,
/** RSS 14 */ /** RSS 14 */
RSS_14, RSS_14,
@@ -102,6 +104,7 @@ impl Display for BarcodeFormat {
BarcodeFormat::MAXICODE => "maxicode", BarcodeFormat::MAXICODE => "maxicode",
BarcodeFormat::PDF_417 => "pdf 417", BarcodeFormat::PDF_417 => "pdf 417",
BarcodeFormat::QR_CODE => "qrcode", BarcodeFormat::QR_CODE => "qrcode",
BarcodeFormat::MICRO_QR_CODE => "mqr",
BarcodeFormat::RSS_14 => "rss 14", BarcodeFormat::RSS_14 => "rss 14",
BarcodeFormat::RSS_EXPANDED => "rss expanded", BarcodeFormat::RSS_EXPANDED => "rss expanded",
BarcodeFormat::UPC_A => "upc a", BarcodeFormat::UPC_A => "upc a",
@@ -140,6 +143,9 @@ impl From<&str> for BarcodeFormat {
"maxicode" | "maxi_code" => BarcodeFormat::MAXICODE, "maxicode" | "maxi_code" => BarcodeFormat::MAXICODE,
"pdf 417" | "pdf_417" | "pdf417" | "iso 15438" | "iso_15438" => BarcodeFormat::PDF_417, "pdf 417" | "pdf_417" | "pdf417" | "iso 15438" | "iso_15438" => BarcodeFormat::PDF_417,
"qrcode" | "qr_code" | "qr code" => BarcodeFormat::QR_CODE, "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" "rss 14" | "rss_14" | "rss14" | "gs1 databar" | "gs1 databar coupon"
| "gs1_databar_coupon" => BarcodeFormat::RSS_14, | "gs1_databar_coupon" => BarcodeFormat::RSS_14,
"rss expanded" | "expanded rss" | "rss_expanded" => BarcodeFormat::RSS_EXPANDED, "rss expanded" | "expanded rss" | "rss_expanded" => BarcodeFormat::RSS_EXPANDED,

View File

@@ -18,6 +18,7 @@
// import java.util.Arrays; // import java.util.Arrays;
use std::ops::Index;
use std::{cmp, fmt}; use std::{cmp, fmt};
use crate::common::Result; use crate::common::Result;
@@ -82,6 +83,14 @@ impl BitArray {
(self.bits[i / 32] & (1 << (i & 0x1F))) != 0 (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. * Sets bit i.
* *
@@ -401,3 +410,30 @@ impl Default for BitArray {
Self::new() 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
}
}

View File

@@ -21,7 +21,7 @@
use std::fmt; use std::fmt;
use crate::common::Result; use crate::common::Result;
use crate::{point, Exceptions, Point}; use crate::{point, point_i, Exceptions, Point};
use super::BitArray; use super::BitArray;
@@ -222,6 +222,17 @@ impl BitMatrix {
// ((self.bits[offset] >> (x & 0x1f)) & 1) != 0 // ((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)] #[inline(always)]
fn get_offset(&self, y: u32, x: u32) -> usize { fn get_offset(&self, y: u32, x: u32) -> usize {
y as usize * self.row_size + (x as usize / 32) y as usize * self.row_size + (x as usize / 32)
@@ -420,6 +431,21 @@ impl BitMatrix {
rw 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 y row to set
* @param row {@link BitArray} to copy from * @param row {@link BitArray} to copy from
@@ -607,6 +633,10 @@ impl BitMatrix {
* @return The width of the matrix * @return The width of the matrix
*/ */
pub fn getWidth(&self) -> u32 { pub fn getWidth(&self) -> u32 {
self.width()
}
pub fn width(&self) -> u32 {
self.width self.width
} }
@@ -614,6 +644,10 @@ impl BitMatrix {
* @return The height of the matrix * @return The height of the matrix
*/ */
pub fn getHeight(&self) -> u32 { pub fn getHeight(&self) -> u32 {
self.height()
}
pub fn height(&self) -> u32 {
self.height self.height
} }
@@ -707,6 +741,10 @@ impl BitMatrix {
new_bm new_bm
} }
pub fn is_in(&self, p: Point) -> bool {
self.isIn(p, 0)
}
pub fn isIn(&self, p: Point, b: i32) -> bool { pub fn isIn(&self, p: Point, b: i32) -> bool {
b as f32 <= p.x b as f32 <= p.x
&& p.x < self.getWidth() as f32 - b as f32 && 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")] #[cfg(feature = "image")]
/// This should only be used if you *know* that the `DynamicImage` is binary. /// This should only be used if you *know* that the `DynamicImage` is binary.
impl TryFrom<image::DynamicImage> for BitMatrix { impl TryFrom<image::DynamicImage> for BitMatrix {

View File

@@ -116,6 +116,56 @@ impl BitSource {
Ok(result) 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 * @return number of bits that can be read successfully
*/ */

View 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,
)
}
}

View File

@@ -0,0 +1,4 @@
mod bitmatrix;
mod qr_ec_level;
mod qr_formatinformation;
mod qrcode_version;

View 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)
}
}

View File

@@ -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
}
}

View 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
}
}

View File

@@ -0,0 +1 @@

View File

@@ -1,4 +1,4 @@
use crate::Point; use crate::{common::BitMatrix, Point};
use super::{util::opposite, Direction, Value}; 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 * 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)). * 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; // const BitMatrix* img;
// POINT p; // current position // POINT p; // current position
@@ -77,7 +77,8 @@ pub trait BitMatrixCursor {
// return isIn(p); // return isIn(p);
// } // }
fn movedBy<T: BitMatrixCursor>(self, d: Point) -> Self; fn movedBy<T: BitMatrixCursorTrait>(self, d: Point) -> Self;
fn turnedBack(&self) -> Self; // { return {*img, p, back()}; }
// { // {
// auto res = *this; // auto res = *this;
// res.p += d; // res.p += d;
@@ -160,20 +161,40 @@ pub trait BitMatrixCursor {
res res
} }
// template<typename ARRAY> fn p(&self) -> Point;
// ARRAY readPattern(int range = 0)
// {
// ARRAY res;
// for (auto& i : res)
// i = stepToEdge(1, range);
// return res;
// }
// template<typename ARRAY> fn d(&self) -> Point;
// ARRAY readPatternFromBlack(int maxWhitePrefix, int range = 0)
// { fn img(&self) -> &BitMatrix;
// if (maxWhitePrefix && isWhite() && !stepToEdge(1, maxWhitePrefix))
// return {}; fn readPattern<const LEN: usize, T: TryFrom<i32> + Default + Copy + Clone>(
// return readPattern<ARRAY>(range); &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))
}
} }

View 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
}

View 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()
}
}
}

View File

@@ -1,10 +1,7 @@
use crate::common::Result; use crate::common::Result;
use crate::{Exceptions, Point}; use crate::{Exceptions, Point};
use super::{ use super::RegressionLineTrait;
util::{float_max, float_min},
RegressionLine,
};
#[derive(Clone)] #[derive(Clone)]
pub struct DMRegressionLine { pub struct DMRegressionLine {
@@ -30,7 +27,7 @@ impl Default for DMRegressionLine {
} }
} }
impl RegressionLine for DMRegressionLine { impl RegressionLineTrait for DMRegressionLine {
fn points(&self) -> &[Point] { fn points(&self) -> &[Point] {
&self.points &self.points
} }
@@ -136,14 +133,14 @@ impl RegressionLine for DMRegressionLine {
let Some(mut min) = self.points.first().copied() else { return false }; let Some(mut min) = self.points.first().copied() else { return false };
let Some(mut max) = self.points.first().copied() else { return false }; let Some(mut max) = self.points.first().copied() else { return false };
for p in &self.points { for p in &self.points {
min.x = float_min(min.x, p.x); min.x = f32::min(min.x, p.x);
min.y = float_min(min.y, p.y); min.y = f32::min(min.y, p.y);
max.x = float_max(max.x, p.x); max.x = f32::max(max.x, p.x);
max.y = float_max(max.y, p.y); max.y = f32::max(max.y, p.y);
} }
let diff = max - min; let diff = max - min;
let len = diff.maxAbsComponent(); 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 // due to aliasing we get bad extrapolations if the line is short and too close to vertical/horizontal
steps > 2.0 || len > 50.0 steps > 2.0 || len > 50.0
} }
@@ -211,9 +208,27 @@ impl RegressionLine for DMRegressionLine {
Point::dot(self.direction_inward, self.normal()) > 0.5 Point::dot(self.direction_inward, self.normal()) > 0.5
// angle between original and new direction is at most 60 degree // 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 { 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> // template <typename Container, typename Filter>
fn average<T>(c: &[f64], f: T) -> f64 fn average<T>(c: &[f64], f: T) -> f64
where where

View File

@@ -6,13 +6,13 @@ use crate::{
Exceptions, Point, Exceptions, Point,
}; };
use super::{BitMatrixCursor, Direction, RegressionLine, StepResult, Value}; use super::{BitMatrixCursorTrait, Direction, RegressionLineTrait, StepResult, Value};
#[derive(Clone)] #[derive(Clone)]
pub struct EdgeTracer<'a> { 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 d: Point, // current direction
// pub history: Option<&'a mut ByteMatrix>, // = nullptr; // 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 { fn testAt(&self, p: Point) -> Value {
if self.img.isIn(p, 0) { if self.img.isIn(p, 0) {
Value::from(self.img.get_point(p)) Value::from(self.img.get_point(p))
@@ -119,13 +119,20 @@ impl BitMatrixCursor for EdgeTracer<'_> {
self.isIn(self.p) self.isIn(self.p)
} }
fn movedBy<T: BitMatrixCursor>(self, d: Point) -> Self { fn movedBy<T: BitMatrixCursorTrait>(self, d: Point) -> Self {
let mut res = self; let mut res = self;
res.p += d; res.p += d;
res 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. * @brief stepToEdge advances cursor to one step behind the next (or n-th) edge.
* @param nth number of edges to pass * @param nth number of edges to pass
@@ -134,9 +141,9 @@ impl BitMatrixCursor for EdgeTracer<'_> {
* @return number of steps taken or 0 if moved outside of range/image * @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 { 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 mut nth = nth.unwrap_or(1); //if let Some(nth) = nth { nth } else { 1 };
let range = if let Some(r) = range { r } else { 0 }; let range = range.unwrap_or(0); //if let Some(r) = range { r } else { 0 };
let backup = if let Some(b) = backup { b } else { false }; 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() // TODO: provide an alternative and faster out-of-bounds check than isIn() inside testAt()
let mut steps = 0; let mut steps = 0;
let mut lv = self.testAt(self.p); let mut lv = self.testAt(self.p);
@@ -155,6 +162,18 @@ impl BitMatrixCursor for EdgeTracer<'_> {
self.p += self.d * steps; self.p += self.d * steps;
steps * i32::from(nth == 0) 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<'_> { impl<'a> EdgeTracer<'_> {
@@ -163,7 +182,7 @@ impl<'a> EdgeTracer<'_> {
EdgeTracer { EdgeTracer {
img: image, img: image,
p, p,
d, d: Point::bresenhamDirection(d), //d,
history: None, history: None,
state: 0, state: 0,
} }
@@ -259,7 +278,11 @@ impl<'a> EdgeTracer<'_> {
true 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); line.setDirectionInward(dEdge);
loop { loop {
// log(self.p); // log(self.p);
@@ -286,7 +309,7 @@ impl<'a> EdgeTracer<'_> {
} // while (true); } // while (true);
} }
pub fn traceGaps<T: RegressionLine>( pub fn traceGaps<T: RegressionLineTrait>(
&mut self, &mut self,
dEdge: Point, dEdge: Point,
line: &mut T, line: &mut T,

View 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
}
}

View 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)
}
}

View 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::*;

View 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);
}
}

View 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
}
}

View File

@@ -1,7 +1,7 @@
use crate::common::Result; use crate::common::Result;
use crate::Point; use crate::{point, Point};
pub trait RegressionLine { pub trait RegressionLineTrait {
// points: Vec<Point>, // points: Vec<Point>,
// direction_inward: Point, // direction_inward: Point,
@@ -11,8 +11,20 @@ pub trait RegressionLine {
// PointF _directionInward; // PointF _directionInward;
// PointF::value_t a = NAN, b = NAN, c = NAN; // PointF::value_t a = NAN, b = NAN, c = NAN;
// fn intersect<T: RegressionLine, T2: RegressionLine>(&self, l1: &T, l2: &T2) fn intersect<T: RegressionLineTrait, T2: RegressionLineTrait>(
// -> Point; 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;// { // 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 // // due to aliasing we get bad extrapolations if the line is short and too close to vertical/horizontal
// return steps > 2 || len > 50; // return steps > 2 || len > 50;
// } // }
fn a(&self) -> f32;
fn b(&self) -> f32;
fn c(&self) -> f32;
} }

View 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(),
}
}
}

View 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())
}

View File

@@ -19,9 +19,9 @@
// import com.google.zxing.NotFoundException; // import com.google.zxing.NotFoundException;
use crate::common::Result; 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 * @author Sean Owen
@@ -30,87 +30,70 @@ use super::{BitMatrix, GridSampler, PerspectiveTransform, Quadrilateral, Sampler
pub struct DefaultGridSampler; pub struct DefaultGridSampler;
impl GridSampler for 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( fn sample_grid(
&self, &self,
image: &BitMatrix, image: &BitMatrix,
dimensionX: u32, dimensionX: u32,
dimensionY: u32, dimensionY: u32,
controls: &[SamplerControl], controls: &[SamplerControl],
) -> Result<BitMatrix> { ) -> Result<(BitMatrix, [Point; 4])> {
if dimensionX == 0 || dimensionY == 0 { if dimensionX <= 0 || dimensionY <= 0 {
return Err(Exceptions::NOT_FOUND); return Err(Exceptions::NOT_FOUND);
} }
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);
}
}
}
let mut bits = BitMatrix::new(dimensionX, dimensionY)?; let mut bits = BitMatrix::new(dimensionX, dimensionY)?;
let mut points = vec![Point::default(); dimensionX as usize]; for SamplerControl { p0, p1, transform } in controls {
for y in 0..dimensionY { // for (auto&& [x0, x1, y0, y1, mod2Pix] : rois) {
// for (int y = 0; y < dimensionY; y++) { for y in (p0.y as i32)..(p1.y as i32) {
let max = points.len(); // for (int y = y0; y < y1; ++y)
let i_value = y as f32 + 0.5; for x in (p0.x as i32)..(p1.x as i32) {
let mut x = 0; // for (int x = x0; x < x1; ++x) {
while x < max { let p = transform.transform_point(Point::from((x, y)).centered()); //mod2Pix(centered(PointI{x, y}));
// for (int x = 0; x < max; x += 2) {
points[x].x = (x as f32) + 0.5; if image.get_point(p) {
points[x].y = i_value; bits.set(x as u32, y as u32);
x += 1; }
} }
controls }
.first() }
.unwrap()
.transform // dbg!(image.to_string());
.transform_points_single(&mut points); // dbg!(bits.to_string());
// Quick check to see if points transformed to something inside the image;
// sufficient to check the endpoints let projectCorner = |p: Point| -> Point {
self.checkAndNudgePoints(image, &mut points)?; for SamplerControl { p0, p1, transform } in controls {
// try { if p0.x <= p.x && p.x <= p1.x && p0.y <= p.y && p.y <= p1.y {
let mut x = 0; return transform.transform_point(p) + point(0.5, 0.5);
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() Point::default()
// { };
// return Err(Exceptions::notFound(
// "index out of bounds, see documentation in file for explanation".to_owned(), let tl = projectCorner(Point::default());
// )); let tr = projectCorner(Point::from((dimensionX, 0)));
// } let bl = projectCorner(Point::from((dimensionX, dimensionY)));
if image let br = projectCorner(Point::from((0, dimensionX)));
.try_get(points[x].x as u32, points[x].y as u32)
.ok_or(Exceptions::not_found_with( Ok((bits, [tl, tr, bl, br]))
"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();
// }
}
Ok(bits)
} }
} }

View File

@@ -1,8 +1,12 @@
use std::fmt::Display; use std::fmt::Display;
use crate::Exceptions;
use super::CharacterSet; use super::CharacterSet;
#[derive(Copy, Clone, Debug, PartialEq, Eq)] use crate::common::Result;
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum Eci { pub enum Eci {
Unknown = -1, Unknown = -1,
Cp437 = 2, // obsolete Cp437 = 2, // obsolete
@@ -44,6 +48,19 @@ impl Eci {
pub fn can_encode(self) -> bool { pub fn can_encode(self) -> bool {
(self as i32) >= 899 (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 { impl From<u32> for Eci {

View File

@@ -21,21 +21,28 @@
// import java.nio.charset.Charset; // import java.nio.charset.Charset;
// import java.nio.charset.StandardCharsets; // 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 * Class that converts a sequence of ECIs and bytes into a string
* *
* @author Alex Geller * @author Alex Geller
*/ */
#[derive(Default)] #[derive(Default, PartialEq, Eq, Debug, Clone)]
pub struct ECIStringBuilder { pub struct ECIStringBuilder {
is_eci: bool, pub has_eci: bool,
eci_result: Option<String>, eci_result: Option<String>,
bytes: Vec<u8>, bytes: Vec<u8>,
eci_positions: Vec<(Eci, usize, usize)>, // (Eci, start, end) eci_positions: Vec<(Eci, usize, usize)>, // (Eci, start, end)
pub symbology: SymbologyIdentifier,
eci_list: HashSet<Eci>,
} }
impl ECIStringBuilder { impl ECIStringBuilder {
@@ -44,7 +51,9 @@ impl ECIStringBuilder {
eci_result: None, eci_result: None,
bytes: Vec::with_capacity(initial_capacity), bytes: Vec::with_capacity(initial_capacity),
eci_positions: Vec::default(), eci_positions: Vec::default(),
is_eci: false, has_eci: false,
symbology: SymbologyIdentifier::default(),
eci_list: HashSet::default(),
} }
} }
@@ -107,18 +116,47 @@ impl ECIStringBuilder {
*/ */
pub fn append_eci(&mut self, eci: Eci) { pub fn append_eci(&mut self, eci: Eci) {
self.eci_result = None; 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() { if let Some(last) = self.eci_positions.last_mut() {
last.2 = self.bytes.len() last.2 = self.bytes.len()
} }
self.eci_positions.push((eci, self.bytes.len(), 0)); 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. /// Finishes encoding anything in the buffer using the current ECI and resets.
/// ///
@@ -126,7 +164,7 @@ impl ECIStringBuilder {
pub fn encodeCurrentBytesIfAny(&self) -> String { pub fn encodeCurrentBytesIfAny(&self) -> String {
let mut encoded_string = String::with_capacity(self.bytes.len()); let mut encoded_string = String::with_capacity(self.bytes.len());
// First encode the first set // First encode the first set
let (_, end, _) = let (_eci, end, _) =
*self *self
.eci_positions .eci_positions
.first() .first()
@@ -136,6 +174,10 @@ impl ECIStringBuilder {
&Self::encode_segment(&self.bytes[0..end], Eci::ISO8859_1).unwrap_or_default(), &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 // If there are more sets, encode each of them in turn
for (eci, eci_start, eci_end) in &self.eci_positions { for (eci, eci_start, eci_end) in &self.eci_positions {
// let (_,end) = *self.eci_positions.first().unwrap_or(&(*eci, self.bytes.len())); // 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> { fn encode_segment(bytes: &[u8], eci: Eci) -> Option<String> {
let mut not_encoded_yet = true;
let mut encoded_string = String::with_capacity(bytes.len()); let mut encoded_string = String::with_capacity(bytes.len());
if ![Eci::Binary, Eci::Unknown].contains(&eci) { if ![Eci::Binary, Eci::Unknown].contains(&eci) {
if eci == Eci::UTF8 { if eci == Eci::UTF8 {
if !bytes.is_empty() { if !bytes.is_empty() {
encoded_string.push_str(&CharacterSet::UTF8.decode(bytes).ok()?); encoded_string.push_str(&CharacterSet::UTF8.decode(bytes).ok()?);
not_encoded_yet = false;
} else { } else {
return None; return None;
} }
} else if !bytes.is_empty() { } else if !bytes.is_empty() {
encoded_string.push_str(&CharacterSet::from(eci).decode(bytes).ok()?); encoded_string.push_str(&CharacterSet::from(eci).decode(bytes).ok()?);
not_encoded_yet = false;
} else { } else {
return None; 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 { for byte in bytes {
encoded_string.push(char::from(*byte)) encoded_string.push(char::from(*byte))
} }
@@ -198,6 +261,11 @@ impl ECIStringBuilder {
self.bytes.len() 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 * @return true iff nothing has been appended
*/ */
@@ -210,6 +278,14 @@ impl ECIStringBuilder {
self 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 { 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,
}
}
}

View File

@@ -95,7 +95,16 @@ pub trait GridSampler {
dimensionY: u32, dimensionY: u32,
dst: Quadrilateral, dst: Quadrilateral,
src: 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( fn sample_grid(
&self, &self,
@@ -103,7 +112,76 @@ pub trait GridSampler {
dimensionX: u32, dimensionX: u32,
dimensionY: u32, dimensionY: u32,
controls: &[SamplerControl], 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 * <p>Checks a set of points that have been transformed to sample points on an image against

View File

@@ -112,6 +112,8 @@ pub use eci::*;
mod quad; mod quad;
pub use quad::*; pub use quad::*;
pub mod cpp_essentials;
#[cfg(feature = "otsu_level")] #[cfg(feature = "otsu_level")]
mod otsu_level_binarizer; mod otsu_level_binarizer;
#[cfg(feature = "otsu_level")] #[cfg(feature = "otsu_level")]

View File

@@ -29,6 +29,7 @@ use super::Quadrilateral;
* *
* @author Sean Owen * @author Sean Owen
*/ */
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct PerspectiveTransform { pub struct PerspectiveTransform {
a11: f32, a11: f32,
a12: f32, a12: f32,
@@ -81,15 +82,28 @@ impl PerspectiveTransform {
Ok(s_to_q * q_to_s) Ok(s_to_q * q_to_s)
} }
pub fn transform_points_single(&self, points: &mut [Point]) { pub fn transform_point(&self, point: Point) -> Point {
for point in points.iter_mut() {
// for (int i = 0; i < maxI; i += 2) {
let x = point.x; let x = point.x;
let y = point.y; let y = point.y;
let denominator = self.a13 * x + self.a23 * y + self.a33; let denominator = self.a13 * x + self.a23 * y + self.a33;
point.x = (self.a11 * x + self.a21 * y + self.a31) / denominator; Point::new(
point.y = (self.a12 * x + self.a22 * y + self.a32) / denominator; (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() {
*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]) { pub fn transform_points_double(&self, x_values: &mut [f32], y_valuess: &mut [f32]) {

View File

@@ -1,4 +1,6 @@
use crate::Point; use crate::{point, Point};
use super::PerspectiveTransform;
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct Quadrilateral(pub [Point; 4]); pub struct Quadrilateral(pub [Point; 4]);
@@ -51,8 +53,8 @@ impl Quadrilateral {
impl Quadrilateral { impl Quadrilateral {
#[allow(dead_code)] #[allow(dead_code)]
pub fn rectangle(width: i32, height: i32, margin: Option<i32>) -> Quadrilateral { pub fn rectangle(width: i32, height: i32, margin: Option<f32>) -> Quadrilateral {
let margin = margin.unwrap_or(0); let margin = margin.unwrap_or(0.0);
Quadrilateral([ Quadrilateral([
Point { 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)] #[allow(dead_code)]
pub fn centered_square(size: i32) -> Quadrilateral { pub fn centered_square(size: i32) -> Quadrilateral {
Self::scale( Self::scale(
@@ -171,7 +183,7 @@ impl Quadrilateral {
let mirror = if let Some(m) = mirror { m } else { false }; 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); res.0.rotate_left(((n + 4) % 4) as usize);
// std::rotate_copy(q.begin(), q.begin() + ((n + 4) % 4), q.end(), res.begin()); // std::rotate_copy(q.begin(), q.begin() + ((n + 4) % 4), q.end(), res.begin());
if mirror { if mirror {
@@ -208,6 +220,33 @@ impl Quadrilateral {
!(x || y) !(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 { impl Default for Quadrilateral {
@@ -215,3 +254,23 @@ impl Default for Quadrilateral {
Self([Point { x: 0.0, y: 0.0 }; 4]) 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)
}
}

View File

@@ -343,7 +343,8 @@ impl<'a> Detector<'_> {
); );
let src = Quadrilateral::new(topRight, topLeft, bottomRight, bottomLeft); 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)
} }
/** /**

View File

@@ -14,9 +14,12 @@ macro_rules! CHECK {
use std::{cell::RefCell, rc::Rc}; use std::{cell::RefCell, rc::Rc};
use crate::{ use crate::{
common::{BitMatrix, DefaultGridSampler, GridSampler, Quadrilateral, Result}, common::{
cpp_essentials::RegressionLineTrait, BitMatrix, DefaultGridSampler, GridSampler,
Quadrilateral, Result,
},
datamatrix::detector::{ datamatrix::detector::{
zxing_cpp_detector::{util::intersect, BitMatrixCursor, RegressionLine}, zxing_cpp_detector::{util::intersect, BitMatrixCursorTrait},
DatamatrixDetectorResult, DatamatrixDetectorResult,
}, },
point, point,
@@ -230,8 +233,10 @@ fn Scan(
CHECK!(res.is_ok()); CHECK!(res.is_ok());
let (res, _) = res?;
return Ok(DatamatrixDetectorResult::new( return Ok(DatamatrixDetectorResult::new(
res?, res,
sourcePoints.points().to_vec(), sourcePoints.points().to_vec(),
)); ));
} }

View File

@@ -1,18 +1,11 @@
mod bitmatrix_cursor;
mod cpp_new_detector; 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 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::*;

View File

@@ -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
}
}

View File

@@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
use thiserror::Error; use thiserror::Error;
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Error, Debug, PartialEq, Eq)] #[derive(Error, Debug, PartialEq, Eq, Clone)]
pub enum Exceptions { pub enum Exceptions {
#[error("IllegalArgumentException{}", if .0.is_empty() { String::new() } else { format!(" - {}", .0) })] #[error("IllegalArgumentException{}", if .0.is_empty() { String::new() } else { format!(" - {}", .0) })]
IllegalArgumentException(String), IllegalArgumentException(String),

View File

@@ -356,7 +356,7 @@ pub fn detect(image: &BitMatrix, try_harder: bool) -> Result<MaxicodeDetectionRe
); );
let src = Quadrilateral::new(tl, tr, br, bl); 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, image,
target_width.round() as u32, target_width.round() as u32,
target_height.round() as u32, target_height.round() as u32,

View File

@@ -17,6 +17,7 @@
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use crate::common::Result; use crate::common::Result;
use crate::qrcode::cpp_port::QrReader;
use crate::{ use crate::{
aztec::AztecReader, datamatrix::DataMatrixReader, maxicode::MaxiCodeReader, aztec::AztecReader, datamatrix::DataMatrixReader, maxicode::MaxiCodeReader,
oned::MultiFormatOneDReader, pdf417::PDF417Reader, qrcode::QRCodeReader, BarcodeFormat, oned::MultiFormatOneDReader, pdf417::PDF417Reader, qrcode::QRCodeReader, BarcodeFormat,
@@ -136,9 +137,18 @@ impl MultiFormatReader {
// Calling all readers again with inverted image // Calling all readers again with inverted image
image.get_black_matrix_mut().flip_self(); image.get_black_matrix_mut().flip_self();
let res = self.decode_formats(image); let res = self.decode_formats(image);
// if let Ok(r) = res.as_mut() {
if res.is_ok() { 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) Err(Exceptions::NOT_FOUND)
} }
@@ -164,8 +174,16 @@ impl MultiFormatReader {
for possible_format in self.possible_formats.iter() { for possible_format in self.possible_formats.iter() {
let res = match possible_format { let res = match possible_format {
BarcodeFormat::QR_CODE => { BarcodeFormat::QR_CODE => {
let cpp = QrReader::default().decode_with_hints(image, &self.hints);
if cpp.is_ok() {
cpp
} else {
QRCodeReader::default().decode_with_hints(image, &self.hints) QRCodeReader::default().decode_with_hints(image, &self.hints)
} }
}
BarcodeFormat::MICRO_QR_CODE => {
QrReader::default().decode_with_hints(image, &self.hints)
}
BarcodeFormat::DATA_MATRIX => { BarcodeFormat::DATA_MATRIX => {
DataMatrixReader::default().decode_with_hints(image, &self.hints) 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) { if let Ok(res) = QRCodeReader::default().decode_with_hints(image, &self.hints) {
return Ok(res); return Ok(res);
} }

View File

@@ -17,6 +17,7 @@
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use crate::common::Result; use crate::common::Result;
use crate::qrcode::cpp_port::QrReader;
use crate::{ use crate::{
aztec::AztecReader, datamatrix::DataMatrixReader, maxicode::MaxiCodeReader, aztec::AztecReader, datamatrix::DataMatrixReader, maxicode::MaxiCodeReader,
oned::MultiFormatOneDReader, pdf417::PDF417Reader, qrcode::QRCodeReader, BarcodeFormat, oned::MultiFormatOneDReader, pdf417::PDF417Reader, qrcode::QRCodeReader, BarcodeFormat,
@@ -43,6 +44,7 @@ pub struct MultiUseMultiFormatReader {
aztec_reader: AztecReader, aztec_reader: AztecReader,
pdf417_reader: PDF417Reader, pdf417_reader: PDF417Reader,
maxicode_reader: MaxiCodeReader, maxicode_reader: MaxiCodeReader,
cpp_qrcode_reader: QrReader,
} }
impl Reader for MultiUseMultiFormatReader { impl Reader for MultiUseMultiFormatReader {
@@ -84,6 +86,7 @@ impl Reader for MultiUseMultiFormatReader {
self.aztec_reader.reset(); self.aztec_reader.reset();
self.pdf417_reader.reset(); self.pdf417_reader.reset();
self.maxicode_reader.reset(); self.maxicode_reader.reset();
self.cpp_qrcode_reader.reset();
} }
} }
@@ -147,8 +150,16 @@ impl MultiUseMultiFormatReader {
image.get_black_matrix_mut().flip_self(); image.get_black_matrix_mut().flip_self();
let res = self.decode_formats(image); let res = self.decode_formats(image);
if res.is_ok() { 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) Err(Exceptions::NOT_FOUND)
} }
@@ -174,8 +185,16 @@ impl MultiUseMultiFormatReader {
for possible_format in self.possible_formats.iter() { for possible_format in self.possible_formats.iter() {
let res = match possible_format { let res = match possible_format {
BarcodeFormat::QR_CODE => { BarcodeFormat::QR_CODE => {
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) 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 BarcodeFormat::DATA_MATRIX => self
.data_matrix_reader .data_matrix_reader
.decode_with_hints(image, &self.hints), .decode_with_hints(image, &self.hints),
@@ -203,7 +222,9 @@ impl MultiUseMultiFormatReader {
return Ok(res); 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) { if let Ok(res) = self.qr_code_reader.decode_with_hints(image, &self.hints) {
return Ok(res); return Ok(res);
} }

View File

@@ -195,6 +195,6 @@ impl Writer for L {
impl OneDimensionalCodeWriter for L { impl OneDimensionalCodeWriter for L {
fn encode_oned(&self, _contents: &str) -> Result<Vec<bool>> { fn encode_oned(&self, _contents: &str) -> Result<Vec<bool>> {
todo!() unimplemented!()
} }
} }

View File

@@ -490,7 +490,7 @@ pub trait UPCEANReader: OneDReader {
pub(crate) struct StandInStruct; pub(crate) struct StandInStruct;
impl UPCEANReader for StandInStruct { impl UPCEANReader for StandInStruct {
fn getBarcodeFormat(&self) -> BarcodeFormat { fn getBarcodeFormat(&self) -> BarcodeFormat {
todo!() unimplemented!()
} }
fn decodeMiddle( fn decodeMiddle(
@@ -499,7 +499,7 @@ impl UPCEANReader for StandInStruct {
_startRange: &[usize; 2], _startRange: &[usize; 2],
_resultString: &mut String, _resultString: &mut String,
) -> Result<usize> { ) -> Result<usize> {
todo!() unimplemented!()
} }
} }
impl OneDReader for StandInStruct { impl OneDReader for StandInStruct {
@@ -509,13 +509,13 @@ impl OneDReader for StandInStruct {
_row: &BitArray, _row: &BitArray,
_hints: &crate::DecodingHintDictionary, _hints: &crate::DecodingHintDictionary,
) -> Result<RXingResult> { ) -> Result<RXingResult> {
todo!() unimplemented!()
} }
} }
impl Reader for StandInStruct { impl Reader for StandInStruct {
fn decode<B: Binarizer>(&mut self, _image: &mut crate::BinaryBitmap<B>) -> Result<RXingResult> { fn decode<B: Binarizer>(&mut self, _image: &mut crate::BinaryBitmap<B>) -> Result<RXingResult> {
todo!() unimplemented!()
} }
fn decode_with_hints<B: Binarizer>( fn decode_with_hints<B: Binarizer>(
@@ -523,7 +523,7 @@ impl Reader for StandInStruct {
_image: &mut crate::BinaryBitmap<B>, _image: &mut crate::BinaryBitmap<B>,
_hints: &crate::DecodingHintDictionary, _hints: &crate::DecodingHintDictionary,
) -> Result<RXingResult> { ) -> Result<RXingResult> {
todo!() unimplemented!()
} }
} }

View 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)
}
}

View 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))
}

View 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

File diff suppressed because it is too large Load Diff

View 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;

View 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);
}
}

View 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());
}

View 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]);
}

View 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);
}

View 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());
}

View 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);
}

View 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());
}

View 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))
}

View 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())
);
}

View 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));
}
}
}

View File

@@ -0,0 +1,14 @@
mod QRModeTest;
mod QRVersionTest;
mod QRFormatInformationTest;
mod QRErrorCorrectionLevelTest;
mod QRDecodedBitStreamParserTest;
mod QRDataMaskTest;
mod QRBitMatrixParserTest;
mod MQRDecoderTest;

View File

@@ -14,6 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
use std::fmt::{self, Display};
use std::str::FromStr; use std::str::FromStr;
use crate::common::Result; use crate::common::Result;
@@ -35,6 +36,7 @@ pub enum ErrorCorrectionLevel {
Q, //0x03 Q, //0x03
/** H = ~30% correction */ /** H = ~30% correction */
H, //0x02 H, //0x02
Invalid,
} }
impl ErrorCorrectionLevel { impl ErrorCorrectionLevel {
@@ -60,6 +62,7 @@ impl ErrorCorrectionLevel {
ErrorCorrectionLevel::M => 0x00, ErrorCorrectionLevel::M => 0x00,
ErrorCorrectionLevel::Q => 0x03, ErrorCorrectionLevel::Q => 0x03,
ErrorCorrectionLevel::H => 0x02, ErrorCorrectionLevel::H => 0x02,
ErrorCorrectionLevel::Invalid => 0x00,
} }
} }
@@ -69,6 +72,7 @@ impl ErrorCorrectionLevel {
ErrorCorrectionLevel::M => 1, ErrorCorrectionLevel::M => 1,
ErrorCorrectionLevel::Q => 2, ErrorCorrectionLevel::Q => 2,
ErrorCorrectionLevel::H => 3, 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 => "_",
})
}
}

View File

@@ -18,12 +18,12 @@ use crate::common::Result;
use super::ErrorCorrectionLevel; 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 * 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], [0x5412, 0x00],
[0x5125, 0x01], [0x5125, 0x01],
[0x5E7C, 0x02], [0x5E7C, 0x02],
@@ -68,8 +68,28 @@ const FORMAT_INFO_DECODE_LOOKUP: [[u32; 2]; 32] = [
*/ */
#[derive(Hash, Eq, PartialEq, Debug)] #[derive(Hash, Eq, PartialEq, Debug)]
pub struct FormatInformation { pub struct FormatInformation {
error_correction_level: ErrorCorrectionLevel, pub hammingDistance: u32,
data_mask: u8, 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 { impl FormatInformation {
@@ -79,8 +99,13 @@ impl FormatInformation {
// Bottom 3 bits // Bottom 3 bits
let dataMask = format_info & 0x07; let dataMask = format_info & 0x07;
Ok(Self { Ok(Self {
hammingDistance: 255,
microVersion: 0,
error_correction_level: errorCorrectionLevel, error_correction_level: errorCorrectionLevel,
data_mask: dataMask, data_mask: dataMask,
isMirrored: false,
index: 255,
bitsIndex: 255,
}) })
} }

View File

@@ -121,6 +121,81 @@ impl Mode {
Mode::HANZI => 0x0D, 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 { impl From<Mode> for u8 {
@@ -136,3 +211,11 @@ impl TryFrom<u8> for Mode {
Self::forBits(value) 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)
}
}

View File

@@ -27,13 +27,14 @@ use once_cell::sync::Lazy;
pub type VersionRef = &'static Version; 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. * See ISO 18004:2006 Annex D.
* Element i represents the raw version bits that specify version i + 7 * 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, 0x07C94, 0x085BC, 0x09A99, 0x0A4D3, 0x0BBF6, 0x0C762, 0x0D847, 0x0E60D, 0x0F928, 0x10B78,
0x1145D, 0x12A17, 0x13532, 0x149A6, 0x15683, 0x168C9, 0x177EC, 0x18EC4, 0x191E1, 0x1AFAB, 0x1145D, 0x12A17, 0x13532, 0x149A6, 0x15683, 0x168C9, 0x177EC, 0x18EC4, 0x191E1, 0x1AFAB,
0x1B08E, 0x1CC1A, 0x1D33F, 0x1ED75, 0x1F250, 0x209D5, 0x216F0, 0x228BA, 0x2379F, 0x24B0B, 0x1B08E, 0x1CC1A, 0x1D33F, 0x1ED75, 0x1F250, 0x209D5, 0x216F0, 0x228BA, 0x2379F, 0x24B0B,
@@ -53,9 +54,10 @@ pub struct Version {
alignmentPatternCenters: Vec<u32>, alignmentPatternCenters: Vec<u32>,
ecBlocks: Vec<ECBlocks>, ecBlocks: Vec<ECBlocks>,
totalCodewords: u32, totalCodewords: u32,
pub(crate) is_micro: bool,
} }
impl Version { 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 mut total = 0;
let ecCodewords = ecBlocks[0].getECCodewordsPerBlock(); let ecCodewords = ecBlocks[0].getECCodewordsPerBlock();
let ecbArray = ecBlocks[0].getECBlocks(); let ecbArray = ecBlocks[0].getECBlocks();
@@ -68,12 +70,32 @@ impl Version {
Self { Self {
versionNumber, versionNumber,
alignmentPatternCenters, alignmentPatternCenters,
ecBlocks, ecBlocks: ecBlocks.to_vec(),
totalCodewords: total, 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 self.versionNumber
} }
@@ -86,10 +108,14 @@ impl Version {
} }
pub fn getDimensionForVersion(&self) -> u32 { 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 { 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] &self.ecBlocks[ecLevel.get_ordinal() as usize]
} }
@@ -100,21 +126,21 @@ impl Version {
* @return Version for a QR Code of that dimension * @return Version for a QR Code of that dimension
* @throws FormatException if dimension is not 1 mod 4 * @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 { if dimension % 4 != 1 {
return Err(Exceptions::format_with("dimension incorrect")); return Err(Exceptions::format_with("dimension incorrect"));
} }
Self::getVersionForNumber((dimension - 17) / 4) 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) { if !(1..=40).contains(&versionNumber) {
return Err(Exceptions::illegal_argument_with("version out of spec")); return Err(Exceptions::illegal_argument_with("version out of spec"));
} }
Ok(&VERSIONS[versionNumber as usize - 1]) 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 bestDifference = u32::MAX;
let mut bestVersion = 0; let mut bestVersion = 0;
for i in 0..VERSION_DECODE_INFO.len() as u32 { for i in 0..VERSION_DECODE_INFO.len() as u32 {
@@ -149,6 +175,8 @@ impl Version {
// Top left finder pattern + separator + format // Top left finder pattern + separator + format
bitMatrix.setRegion(0, 0, 9, 9)?; bitMatrix.setRegion(0, 0, 9, 9)?;
if !self.is_micro {
// Top right finder pattern + separator + format // Top right finder pattern + separator + format
bitMatrix.setRegion(dimension - 8, 0, 8, 9)?; bitMatrix.setRegion(dimension - 8, 0, 8, 9)?;
// Bottom left finder pattern + separator + format // Bottom left finder pattern + separator + format
@@ -177,10 +205,50 @@ impl Version {
// Version info, bottom left // Version info, bottom left
bitMatrix.setRegion(0, dimension - 11, 6, 3)?; 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) 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 * See ISO 18004:2006 6.5.1 Table 9
*/ */
@@ -189,402 +257,402 @@ impl Version {
Version::new( Version::new(
1, 1,
Vec::from([]), Vec::from([]),
Vec::from([ [
ECBlocks::new(7, Vec::from([ECB::new(1, 19)])), ECBlocks::new(7, Vec::from([ECB::new(1, 19)])),
ECBlocks::new(10, Vec::from([ECB::new(1, 16)])), ECBlocks::new(10, Vec::from([ECB::new(1, 16)])),
ECBlocks::new(13, Vec::from([ECB::new(1, 13)])), ECBlocks::new(13, Vec::from([ECB::new(1, 13)])),
ECBlocks::new(17, Vec::from([ECB::new(1, 9)])), ECBlocks::new(17, Vec::from([ECB::new(1, 9)])),
]), ],
), ),
Version::new( Version::new(
2, 2,
Vec::from([6, 18]), Vec::from([6, 18]),
Vec::from([ [
ECBlocks::new(10, Vec::from([ECB::new(1, 34)])), ECBlocks::new(10, Vec::from([ECB::new(1, 34)])),
ECBlocks::new(16, Vec::from([ECB::new(1, 28)])), ECBlocks::new(16, Vec::from([ECB::new(1, 28)])),
ECBlocks::new(22, Vec::from([ECB::new(1, 22)])), ECBlocks::new(22, Vec::from([ECB::new(1, 22)])),
ECBlocks::new(28, Vec::from([ECB::new(1, 16)])), ECBlocks::new(28, Vec::from([ECB::new(1, 16)])),
]), ],
), ),
Version::new( Version::new(
3, 3,
Vec::from([6, 22]), Vec::from([6, 22]),
Vec::from([ [
ECBlocks::new(15, Vec::from([ECB::new(1, 55)])), ECBlocks::new(15, Vec::from([ECB::new(1, 55)])),
ECBlocks::new(26, Vec::from([ECB::new(1, 44)])), ECBlocks::new(26, Vec::from([ECB::new(1, 44)])),
ECBlocks::new(18, Vec::from([ECB::new(2, 17)])), ECBlocks::new(18, Vec::from([ECB::new(2, 17)])),
ECBlocks::new(22, Vec::from([ECB::new(2, 13)])), ECBlocks::new(22, Vec::from([ECB::new(2, 13)])),
]), ],
), ),
Version::new( Version::new(
4, 4,
Vec::from([6, 26]), Vec::from([6, 26]),
Vec::from([ [
ECBlocks::new(20, Vec::from([ECB::new(1, 80)])), ECBlocks::new(20, Vec::from([ECB::new(1, 80)])),
ECBlocks::new(18, Vec::from([ECB::new(2, 32)])), ECBlocks::new(18, Vec::from([ECB::new(2, 32)])),
ECBlocks::new(26, Vec::from([ECB::new(2, 24)])), ECBlocks::new(26, Vec::from([ECB::new(2, 24)])),
ECBlocks::new(16, Vec::from([ECB::new(4, 9)])), ECBlocks::new(16, Vec::from([ECB::new(4, 9)])),
]), ],
), ),
Version::new( Version::new(
5, 5,
Vec::from([6, 30]), Vec::from([6, 30]),
Vec::from([ [
ECBlocks::new(26, Vec::from([ECB::new(1, 108)])), ECBlocks::new(26, Vec::from([ECB::new(1, 108)])),
ECBlocks::new(24, Vec::from([ECB::new(2, 43)])), ECBlocks::new(24, Vec::from([ECB::new(2, 43)])),
ECBlocks::new(18, Vec::from([ECB::new(2, 15), ECB::new(2, 16)])), 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)])), ECBlocks::new(22, Vec::from([ECB::new(2, 11), ECB::new(2, 12)])),
]), ],
), ),
Version::new( Version::new(
6, 6,
Vec::from([6, 34]), Vec::from([6, 34]),
Vec::from([ [
ECBlocks::new(18, Vec::from([ECB::new(2, 68)])), ECBlocks::new(18, Vec::from([ECB::new(2, 68)])),
ECBlocks::new(16, Vec::from([ECB::new(4, 27)])), ECBlocks::new(16, Vec::from([ECB::new(4, 27)])),
ECBlocks::new(24, Vec::from([ECB::new(4, 19)])), ECBlocks::new(24, Vec::from([ECB::new(4, 19)])),
ECBlocks::new(28, Vec::from([ECB::new(4, 15)])), ECBlocks::new(28, Vec::from([ECB::new(4, 15)])),
]), ],
), ),
Version::new( Version::new(
7, 7,
Vec::from([6, 22, 38]), Vec::from([6, 22, 38]),
Vec::from([ [
ECBlocks::new(20, Vec::from([ECB::new(2, 78)])), 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(4, 31)])),
ECBlocks::new(18, Vec::from([ECB::new(2, 14), ECB::new(4, 15)])), 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)])), ECBlocks::new(26, Vec::from([ECB::new(4, 13), ECB::new(1, 14)])),
]), ],
), ),
Version::new( Version::new(
8, 8,
Vec::from([6, 24, 42]), Vec::from([6, 24, 42]),
Vec::from([ [
ECBlocks::new(24, Vec::from([ECB::new(2, 97)])), 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(2, 38), ECB::new(2, 39)])),
ECBlocks::new(22, Vec::from([ECB::new(4, 18), ECB::new(2, 19)])), 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)])), ECBlocks::new(26, Vec::from([ECB::new(4, 14), ECB::new(2, 15)])),
]), ],
), ),
Version::new( Version::new(
9, 9,
Vec::from([6, 26, 46]), Vec::from([6, 26, 46]),
Vec::from([ [
ECBlocks::new(30, Vec::from([ECB::new(2, 116)])), ECBlocks::new(30, Vec::from([ECB::new(2, 116)])),
ECBlocks::new(22, Vec::from([ECB::new(3, 36), ECB::new(2, 37)])), 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(20, Vec::from([ECB::new(4, 16), ECB::new(4, 17)])),
ECBlocks::new(24, Vec::from([ECB::new(4, 12), ECB::new(4, 13)])), ECBlocks::new(24, Vec::from([ECB::new(4, 12), ECB::new(4, 13)])),
]), ],
), ),
Version::new( Version::new(
10, 10,
Vec::from([6, 28, 50]), Vec::from([6, 28, 50]),
Vec::from([ [
ECBlocks::new(18, Vec::from([ECB::new(2, 68), ECB::new(2, 69)])), 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(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(24, Vec::from([ECB::new(6, 19), ECB::new(2, 20)])),
ECBlocks::new(28, Vec::from([ECB::new(6, 15), ECB::new(2, 16)])), ECBlocks::new(28, Vec::from([ECB::new(6, 15), ECB::new(2, 16)])),
]), ],
), ),
Version::new( Version::new(
11, 11,
Vec::from([6, 30, 54]), Vec::from([6, 30, 54]),
Vec::from([ [
ECBlocks::new(20, Vec::from([ECB::new(4, 81)])), ECBlocks::new(20, Vec::from([ECB::new(4, 81)])),
ECBlocks::new(30, Vec::from([ECB::new(1, 50), ECB::new(4, 51)])), 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(28, Vec::from([ECB::new(4, 22), ECB::new(4, 23)])),
ECBlocks::new(24, Vec::from([ECB::new(3, 12), ECB::new(8, 13)])), ECBlocks::new(24, Vec::from([ECB::new(3, 12), ECB::new(8, 13)])),
]), ],
), ),
Version::new( Version::new(
12, 12,
Vec::from([6, 32, 58]), Vec::from([6, 32, 58]),
Vec::from([ [
ECBlocks::new(24, Vec::from([ECB::new(2, 92), ECB::new(2, 93)])), 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(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(26, Vec::from([ECB::new(4, 20), ECB::new(6, 21)])),
ECBlocks::new(28, Vec::from([ECB::new(7, 14), ECB::new(4, 15)])), ECBlocks::new(28, Vec::from([ECB::new(7, 14), ECB::new(4, 15)])),
]), ],
), ),
Version::new( Version::new(
13, 13,
Vec::from([6, 34, 62]), Vec::from([6, 34, 62]),
Vec::from([ [
ECBlocks::new(26, Vec::from([ECB::new(4, 107)])), ECBlocks::new(26, Vec::from([ECB::new(4, 107)])),
ECBlocks::new(22, Vec::from([ECB::new(8, 37), ECB::new(1, 38)])), 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(24, Vec::from([ECB::new(8, 20), ECB::new(4, 21)])),
ECBlocks::new(22, Vec::from([ECB::new(12, 11), ECB::new(4, 12)])), ECBlocks::new(22, Vec::from([ECB::new(12, 11), ECB::new(4, 12)])),
]), ],
), ),
Version::new( Version::new(
14, 14,
Vec::from([6, 26, 46, 66]), Vec::from([6, 26, 46, 66]),
Vec::from([ [
ECBlocks::new(30, Vec::from([ECB::new(3, 115), ECB::new(1, 116)])), 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(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(20, Vec::from([ECB::new(11, 16), ECB::new(5, 17)])),
ECBlocks::new(24, Vec::from([ECB::new(11, 12), ECB::new(5, 13)])), ECBlocks::new(24, Vec::from([ECB::new(11, 12), ECB::new(5, 13)])),
]), ],
), ),
Version::new( Version::new(
15, 15,
Vec::from([6, 26, 48, 70]), Vec::from([6, 26, 48, 70]),
Vec::from([ [
ECBlocks::new(22, Vec::from([ECB::new(5, 87), ECB::new(1, 88)])), 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(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(30, Vec::from([ECB::new(5, 24), ECB::new(7, 25)])),
ECBlocks::new(24, Vec::from([ECB::new(11, 12), ECB::new(7, 13)])), ECBlocks::new(24, Vec::from([ECB::new(11, 12), ECB::new(7, 13)])),
]), ],
), ),
Version::new( Version::new(
16, 16,
Vec::from([6, 26, 50, 74]), Vec::from([6, 26, 50, 74]),
Vec::from([ [
ECBlocks::new(24, Vec::from([ECB::new(5, 98), ECB::new(1, 99)])), 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(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(24, Vec::from([ECB::new(15, 19), ECB::new(2, 20)])),
ECBlocks::new(30, Vec::from([ECB::new(3, 15), ECB::new(13, 16)])), ECBlocks::new(30, Vec::from([ECB::new(3, 15), ECB::new(13, 16)])),
]), ],
), ),
Version::new( Version::new(
17, 17,
Vec::from([6, 30, 54, 78]), 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(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(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(1, 22), ECB::new(15, 23)])),
ECBlocks::new(28, Vec::from([ECB::new(2, 14), ECB::new(17, 15)])), ECBlocks::new(28, Vec::from([ECB::new(2, 14), ECB::new(17, 15)])),
]), ],
), ),
Version::new( Version::new(
18, 18,
Vec::from([6, 30, 56, 82]), Vec::from([6, 30, 56, 82]),
Vec::from([ [
ECBlocks::new(30, Vec::from([ECB::new(5, 120), ECB::new(1, 121)])), 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(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(17, 22), ECB::new(1, 23)])),
ECBlocks::new(28, Vec::from([ECB::new(2, 14), ECB::new(19, 15)])), ECBlocks::new(28, Vec::from([ECB::new(2, 14), ECB::new(19, 15)])),
]), ],
), ),
Version::new( Version::new(
19, 19,
Vec::from([6, 30, 58, 86]), Vec::from([6, 30, 58, 86]),
Vec::from([ [
ECBlocks::new(28, Vec::from([ECB::new(3, 113), ECB::new(4, 114)])), 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(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(17, 21), ECB::new(4, 22)])),
ECBlocks::new(26, Vec::from([ECB::new(9, 13), ECB::new(16, 14)])), ECBlocks::new(26, Vec::from([ECB::new(9, 13), ECB::new(16, 14)])),
]), ],
), ),
Version::new( Version::new(
20, 20,
Vec::from([6, 34, 62, 90]), Vec::from([6, 34, 62, 90]),
Vec::from([ [
ECBlocks::new(28, Vec::from([ECB::new(3, 107), ECB::new(5, 108)])), 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(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(30, Vec::from([ECB::new(15, 24), ECB::new(5, 25)])),
ECBlocks::new(28, Vec::from([ECB::new(15, 15), ECB::new(10, 16)])), ECBlocks::new(28, Vec::from([ECB::new(15, 15), ECB::new(10, 16)])),
]), ],
), ),
Version::new( Version::new(
21, 21,
Vec::from([6, 28, 50, 72, 94]), Vec::from([6, 28, 50, 72, 94]),
Vec::from([ [
ECBlocks::new(28, Vec::from([ECB::new(4, 116), ECB::new(4, 117)])), ECBlocks::new(28, Vec::from([ECB::new(4, 116), ECB::new(4, 117)])),
ECBlocks::new(26, Vec::from([ECB::new(17, 42)])), ECBlocks::new(26, Vec::from([ECB::new(17, 42)])),
ECBlocks::new(28, Vec::from([ECB::new(17, 22), ECB::new(6, 23)])), 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)])), ECBlocks::new(30, Vec::from([ECB::new(19, 16), ECB::new(6, 17)])),
]), ],
), ),
Version::new( Version::new(
22, 22,
Vec::from([6, 26, 50, 74, 98]), 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(2, 111), ECB::new(7, 112)])),
ECBlocks::new(28, Vec::from([ECB::new(17, 46)])), ECBlocks::new(28, Vec::from([ECB::new(17, 46)])),
ECBlocks::new(30, Vec::from([ECB::new(7, 24), ECB::new(16, 25)])), ECBlocks::new(30, Vec::from([ECB::new(7, 24), ECB::new(16, 25)])),
ECBlocks::new(24, Vec::from([ECB::new(34, 13)])), ECBlocks::new(24, Vec::from([ECB::new(34, 13)])),
]), ],
), ),
Version::new( Version::new(
23, 23,
Vec::from([6, 30, 54, 78, 102]), Vec::from([6, 30, 54, 78, 102]),
Vec::from([ [
ECBlocks::new(30, Vec::from([ECB::new(4, 121), ECB::new(5, 122)])), 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(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(11, 24), ECB::new(14, 25)])),
ECBlocks::new(30, Vec::from([ECB::new(16, 15), ECB::new(14, 16)])), ECBlocks::new(30, Vec::from([ECB::new(16, 15), ECB::new(14, 16)])),
]), ],
), ),
Version::new( Version::new(
24, 24,
Vec::from([6, 28, 54, 80, 106]), Vec::from([6, 28, 54, 80, 106]),
Vec::from([ [
ECBlocks::new(30, Vec::from([ECB::new(6, 117), ECB::new(4, 118)])), 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(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(11, 24), ECB::new(16, 25)])),
ECBlocks::new(30, Vec::from([ECB::new(30, 16), ECB::new(2, 17)])), ECBlocks::new(30, Vec::from([ECB::new(30, 16), ECB::new(2, 17)])),
]), ],
), ),
Version::new( Version::new(
25, 25,
Vec::from([6, 32, 58, 84, 110]), Vec::from([6, 32, 58, 84, 110]),
Vec::from([ [
ECBlocks::new(26, Vec::from([ECB::new(8, 106), ECB::new(4, 107)])), 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(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(7, 24), ECB::new(22, 25)])),
ECBlocks::new(30, Vec::from([ECB::new(22, 15), ECB::new(13, 16)])), ECBlocks::new(30, Vec::from([ECB::new(22, 15), ECB::new(13, 16)])),
]), ],
), ),
Version::new( Version::new(
26, 26,
Vec::from([6, 30, 58, 86, 114]), 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(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(19, 46), ECB::new(4, 47)])),
ECBlocks::new(28, Vec::from([ECB::new(28, 22), ECB::new(6, 23)])), 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)])), ECBlocks::new(30, Vec::from([ECB::new(33, 16), ECB::new(4, 17)])),
]), ],
), ),
Version::new( Version::new(
27, 27,
Vec::from([6, 34, 62, 90, 118]), Vec::from([6, 34, 62, 90, 118]),
Vec::from([ [
ECBlocks::new(30, Vec::from([ECB::new(8, 122), ECB::new(4, 123)])), 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(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(8, 23), ECB::new(26, 24)])),
ECBlocks::new(30, Vec::from([ECB::new(12, 15), ECB::new(28, 16)])), ECBlocks::new(30, Vec::from([ECB::new(12, 15), ECB::new(28, 16)])),
]), ],
), ),
Version::new( Version::new(
28, 28,
Vec::from([6, 26, 50, 74, 98, 122]), 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(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(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(4, 24), ECB::new(31, 25)])),
ECBlocks::new(30, Vec::from([ECB::new(11, 15), ECB::new(31, 16)])), ECBlocks::new(30, Vec::from([ECB::new(11, 15), ECB::new(31, 16)])),
]), ],
), ),
Version::new( Version::new(
29, 29,
Vec::from([6, 30, 54, 78, 102, 126]), 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(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(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(1, 23), ECB::new(37, 24)])),
ECBlocks::new(30, Vec::from([ECB::new(19, 15), ECB::new(26, 16)])), ECBlocks::new(30, Vec::from([ECB::new(19, 15), ECB::new(26, 16)])),
]), ],
), ),
Version::new( Version::new(
30, 30,
Vec::from([6, 26, 52, 78, 104, 130]), 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(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(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(15, 24), ECB::new(25, 25)])),
ECBlocks::new(30, Vec::from([ECB::new(23, 15), ECB::new(25, 16)])), ECBlocks::new(30, Vec::from([ECB::new(23, 15), ECB::new(25, 16)])),
]), ],
), ),
Version::new( Version::new(
31, 31,
Vec::from([6, 30, 56, 82, 108, 134]), 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(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(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(42, 24), ECB::new(1, 25)])),
ECBlocks::new(30, Vec::from([ECB::new(23, 15), ECB::new(28, 16)])), ECBlocks::new(30, Vec::from([ECB::new(23, 15), ECB::new(28, 16)])),
]), ],
), ),
Version::new( Version::new(
32, 32,
Vec::from([6, 34, 60, 86, 112, 138]), Vec::from([6, 34, 60, 86, 112, 138]),
Vec::from([ [
ECBlocks::new(30, Vec::from([ECB::new(17, 115)])), ECBlocks::new(30, Vec::from([ECB::new(17, 115)])),
ECBlocks::new(28, Vec::from([ECB::new(10, 46), ECB::new(23, 47)])), 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(10, 24), ECB::new(35, 25)])),
ECBlocks::new(30, Vec::from([ECB::new(19, 15), ECB::new(35, 16)])), ECBlocks::new(30, Vec::from([ECB::new(19, 15), ECB::new(35, 16)])),
]), ],
), ),
Version::new( Version::new(
33, 33,
Vec::from([6, 30, 58, 86, 114, 142]), 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(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(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(29, 24), ECB::new(19, 25)])),
ECBlocks::new(30, Vec::from([ECB::new(11, 15), ECB::new(46, 16)])), ECBlocks::new(30, Vec::from([ECB::new(11, 15), ECB::new(46, 16)])),
]), ],
), ),
Version::new( Version::new(
34, 34,
Vec::from([6, 34, 62, 90, 118, 146]), 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(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(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(44, 24), ECB::new(7, 25)])),
ECBlocks::new(30, Vec::from([ECB::new(59, 16), ECB::new(1, 17)])), ECBlocks::new(30, Vec::from([ECB::new(59, 16), ECB::new(1, 17)])),
]), ],
), ),
Version::new( Version::new(
35, 35,
Vec::from([6, 30, 54, 78, 102, 126, 150]), 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(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(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(39, 24), ECB::new(14, 25)])),
ECBlocks::new(30, Vec::from([ECB::new(22, 15), ECB::new(41, 16)])), ECBlocks::new(30, Vec::from([ECB::new(22, 15), ECB::new(41, 16)])),
]), ],
), ),
Version::new( Version::new(
36, 36,
Vec::from([6, 24, 50, 76, 102, 128, 154]), 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(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(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(46, 24), ECB::new(10, 25)])),
ECBlocks::new(30, Vec::from([ECB::new(2, 15), ECB::new(64, 16)])), ECBlocks::new(30, Vec::from([ECB::new(2, 15), ECB::new(64, 16)])),
]), ],
), ),
Version::new( Version::new(
37, 37,
Vec::from([6, 28, 54, 80, 106, 132, 158]), 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(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(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(49, 24), ECB::new(10, 25)])),
ECBlocks::new(30, Vec::from([ECB::new(24, 15), ECB::new(46, 16)])), ECBlocks::new(30, Vec::from([ECB::new(24, 15), ECB::new(46, 16)])),
]), ],
), ),
Version::new( Version::new(
38, 38,
Vec::from([6, 32, 58, 84, 110, 136, 162]), 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(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(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(48, 24), ECB::new(14, 25)])),
ECBlocks::new(30, Vec::from([ECB::new(42, 15), ECB::new(32, 16)])), ECBlocks::new(30, Vec::from([ECB::new(42, 15), ECB::new(32, 16)])),
]), ],
), ),
Version::new( Version::new(
39, 39,
Vec::from([6, 26, 54, 82, 110, 138, 166]), 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(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(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(43, 24), ECB::new(22, 25)])),
ECBlocks::new(30, Vec::from([ECB::new(10, 15), ECB::new(67, 16)])), ECBlocks::new(30, Vec::from([ECB::new(10, 15), ECB::new(67, 16)])),
]), ],
), ),
Version::new( Version::new(
40, 40,
Vec::from([6, 30, 58, 86, 114, 142, 170]), 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(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(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(34, 24), ECB::new(34, 25)])),
ECBlocks::new(30, Vec::from([ECB::new(20, 15), ECB::new(61, 16)])), ECBlocks::new(30, Vec::from([ECB::new(20, 15), ECB::new(61, 16)])),
]), ],
), ),
]) /* ]) /*
new Version(4, new int[]{6, 26}, 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 * 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> * will be the same across all blocks within one version.</p>
*/ */
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct ECBlocks { pub struct ECBlocks {
ecCodewordsPerBlock: u32, ecCodewordsPerBlock: u32,
ecBlocks: Vec<ECB>, ecBlocks: Vec<ECB>,
@@ -930,7 +998,7 @@ impl ECBlocks {
} }
} }
pub fn getECCodewordsPerBlock(&self) -> u32 { pub const fn getECCodewordsPerBlock(&self) -> u32 {
self.ecCodewordsPerBlock self.ecCodewordsPerBlock
} }
@@ -956,7 +1024,7 @@ impl ECBlocks {
* This includes the number of data codewords, and the number of times a block with these * 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> * parameters is used consecutively in the QR code version's format.</p>
*/ */
#[derive(Debug)] #[derive(Debug, Clone, Copy)]
pub struct ECB { pub struct ECB {
count: u32, count: u32,
dataCodewords: u32, dataCodewords: u32,
@@ -970,11 +1038,11 @@ impl ECB {
} }
} }
pub fn getCount(&self) -> u32 { pub const fn getCount(&self) -> u32 {
self.count self.count
} }
pub fn getDataCodewords(&self) -> u32 { pub const fn getDataCodewords(&self) -> u32 {
self.dataCodewords self.dataCodewords
} }
} }

View File

@@ -218,12 +218,13 @@ impl<'a> Detector<'_> {
dimension: u32, dimension: u32,
) -> Result<BitMatrix> { ) -> Result<BitMatrix> {
let sampler = DefaultGridSampler::default(); let sampler = DefaultGridSampler::default();
sampler.sample_grid( let (res, _) = sampler.sample_grid(
image, image,
dimension, dimension,
dimension, dimension,
&[SamplerControl::new(dimension, dimension, transform)], &[SamplerControl::new(dimension, dimension, transform)],
) )?;
Ok(res)
} }
/** /**

View File

@@ -8,6 +8,8 @@ pub use qr_code_reader::*;
mod qr_code_writer; mod qr_code_writer;
pub use qr_code_writer::*; pub use qr_code_writer::*;
pub mod cpp_port;
#[cfg(test)] #[cfg(test)]
#[cfg(feature = "image")] #[cfg(feature = "image")]
mod QRCodeWriterTestCase; mod QRCodeWriterTestCase;

View File

@@ -16,7 +16,10 @@
use std::{collections::HashMap, fmt}; 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")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; 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 * @return raw text encoded by the barcode
*/ */

View File

@@ -112,6 +112,8 @@ pub enum RXingResultMetadataType {
IS_MIRRORED, IS_MIRRORED,
CONTENT_TYPE, CONTENT_TYPE,
IS_INVERTED,
} }
impl From<String> for RXingResultMetadataType { impl From<String> for RXingResultMetadataType {
@@ -120,7 +122,7 @@ impl From<String> for RXingResultMetadataType {
"OTHER" => RXingResultMetadataType::OTHER, "OTHER" => RXingResultMetadataType::OTHER,
"ORIENTATION" => RXingResultMetadataType::ORIENTATION, "ORIENTATION" => RXingResultMetadataType::ORIENTATION,
"BYTE_SEGMENTS" | "BYTESEGMENTS" => RXingResultMetadataType::BYTE_SEGMENTS, "BYTE_SEGMENTS" | "BYTESEGMENTS" => RXingResultMetadataType::BYTE_SEGMENTS,
"ERROR_CORRECTION_LEVEL" | "ERRORCORRECTIONLEVEL" => { "ERROR_CORRECTION_LEVEL" | "ERRORCORRECTIONLEVEL" | "ECLEVEL" => {
RXingResultMetadataType::ERROR_CORRECTION_LEVEL RXingResultMetadataType::ERROR_CORRECTION_LEVEL
} }
"ISSUE_NUMBER" | "ISSUENUMBER" => RXingResultMetadataType::ISSUE_NUMBER, "ISSUE_NUMBER" | "ISSUENUMBER" => RXingResultMetadataType::ISSUE_NUMBER,
@@ -141,6 +143,7 @@ impl From<String> for RXingResultMetadataType {
} }
"IS_MIRRORED" | "ISMIRRORED" => RXingResultMetadataType::IS_MIRRORED, "IS_MIRRORED" | "ISMIRRORED" => RXingResultMetadataType::IS_MIRRORED,
"CONTENT_TYPE" | "CONTENTTYPE" => RXingResultMetadataType::CONTENT_TYPE, "CONTENT_TYPE" | "CONTENTTYPE" => RXingResultMetadataType::CONTENT_TYPE,
"ISINVERTED" => RXingResultMetadataType::IS_INVERTED,
_ => RXingResultMetadataType::OTHER, _ => RXingResultMetadataType::OTHER,
} }
} }
@@ -229,4 +232,6 @@ pub enum RXingResultMetadataValue {
IsMirrored(bool), IsMirrored(bool),
ContentType(String), ContentType(String),
IsInverted(bool),
} }

View File

@@ -25,6 +25,14 @@ pub fn point(x: f32, y: f32) -> Point {
Point::new(x, y) 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. */ /** Currently necessary because the external OneDReader proc macro uses it. */
pub type RXingResultPoint = Point; 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 { impl std::ops::Mul for Point {
type Output = Self; 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 { impl std::ops::Mul<Point> for i32 {
type Output = Point; 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 { impl Point {
pub fn dot(self, p: Self) -> f32 { pub fn dot(self, p: Self) -> f32 {
self.x * p.x + self.y * p.y self.x * p.x + self.y * p.y
@@ -240,6 +280,13 @@ impl Point {
y: self.y.round(), y: self.y.round(),
} }
} }
pub fn floor(self) -> Self {
Self {
x: self.x.floor(),
y: self.y.floor(),
}
}
} }
impl From<&(f32, f32)> for Point { 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)] #[cfg(test)]
mod tests { mod tests {
use super::Point; use super::Point;

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

@@ -0,0 +1 @@
Wikipedia

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@@ -0,0 +1 @@
Loser

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -0,0 +1 @@
SN888623311

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -0,0 +1 @@
malgajninto

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -0,0 +1 @@
Victus

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

@@ -0,0 +1 @@
ezik

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 B

View File

@@ -0,0 +1 @@
12345

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 B

View File

@@ -0,0 +1 @@
ABC

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 B

View File

@@ -0,0 +1 @@
1234567890

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 B

View File

@@ -0,0 +1 @@
E=mc²

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 B

View File

@@ -0,0 +1 @@

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 B

View File

@@ -0,0 +1 @@
1234 ABCD

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 B

View File

@@ -0,0 +1 @@
!"§$%&/()=?`

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 B

View File

@@ -0,0 +1 @@
1234 ABCD abcd

Some files were not shown because too many files have changed in this diff Show More