feat: initial port for Rectangular Micro QR

This is the intial port for rMQR support. Directly ported from 7a294f2f3c (diff-5d6a0ddd024f3102876492f502a41becde594de81f46d942b87b64cbfdc1985b)

subsequent improvements will be incorporated in future patches.
This commit is contained in:
Henry Schimke
2024-01-13 13:48:13 -06:00
parent cf47d8a826
commit d31da2f858
29 changed files with 2876 additions and 1093 deletions

View File

@@ -636,7 +636,7 @@ impl BitMatrix {
self.width()
}
pub fn width(&self) -> u32 {
pub const fn width(&self) -> u32 {
self.width
}
@@ -647,7 +647,7 @@ impl BitMatrix {
self.height()
}
pub fn height(&self) -> u32 {
pub const fn height(&self) -> u32 {
self.height
}

View File

@@ -7,6 +7,8 @@ use crate::qrcode::{
pub const FORMAT_INFO_MASK_QR_MODEL1: u32 = 0x2825;
pub const FORMAT_INFO_MASK_MICRO: u32 = 0x4445;
pub const FORMAT_INFO_MASK_RMQR: u32 = 0x1FAB2; // Finder pattern side
pub const FORMAT_INFO_MASK_RMQR_SUB: u32 = 0x20A7B; // Finder sub pattern side
// pub const FORMAT_INFO_DECODE_LOOKUP_MICRO: [u32 ;32] = [
// 0x4445,
@@ -94,6 +96,33 @@ impl FormatInformation {
fi
}
/**
* @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 DecodeRMQR(formatInfoBits1: u32, formatInfoBits2: u32) -> Self {
//FormatInformation fi;
let mirror18Bits = |bits: u32| bits.reverse_bits() >> 14;
let mut fi = if (formatInfoBits2 != 0) {
Self::FindBestFormatInfoRMQR(
&[formatInfoBits1, mirror18Bits(formatInfoBits1)],
&[formatInfoBits2, mirror18Bits(formatInfoBits2)],
)
} else {
// TODO probably remove if `sampleRMQR()` done properly
Self::FindBestFormatInfoRMQR(&[formatInfoBits1, mirror18Bits(formatInfoBits1)], &[])
};
// Bit 6 is error correction (M/H), and bits 0-5 version.
fi.error_correction_level =
ErrorCorrectionLevel::ECLevelFromBits(((fi.data >> 5) as u8 & 1) << 1, false); // Shift to match QRCode M/H
fi.data_mask = 4; // ((y / 2) + (x / 3)) % 2 == 0
fi.rMQRVersion = fi.data as u8 & 0x1F;
fi.isMirrored = fi.bitsIndex > 1;
fi
}
#[inline(always)]
pub fn MirrorBits(bits: u32) -> u32 {
(bits.reverse_bits()) >> 17
@@ -153,16 +182,73 @@ impl FormatInformation {
fi
}
pub fn qr_type(&self) -> Type {
pub fn FindBestFormatInfoRMQR(bits: &[u32], subbits: &[u32]) -> Self {
// See ISO/IEC 23941:2022, Annex C, Table C.1 - Valid format information sequences
const MASKED_PATTERNS: [u32; 64] = [
// Finder pattern side
0x1FAB2, 0x1E597, 0x1DBDD, 0x1C4F8, 0x1B86C, 0x1A749, 0x19903, 0x18626, 0x17F0E,
0x1602B, 0x15E61, 0x14144, 0x13DD0, 0x122F5, 0x11CBF, 0x1039A, 0x0F1CA, 0x0EEEF,
0x0D0A5, 0x0CF80, 0x0B314, 0x0AC31, 0x0927B, 0x08D5E, 0x07476, 0x06B53, 0x05519,
0x04A3C, 0x036A8, 0x0298D, 0x017C7, 0x008E2, 0x3F367, 0x3EC42, 0x3D208, 0x3CD2D,
0x3B1B9, 0x3AE9C, 0x390D6, 0x38FF3, 0x376DB, 0x369FE, 0x357B4, 0x34891, 0x33405,
0x32B20, 0x3156A, 0x30A4F, 0x2F81F, 0x2E73A, 0x2D970, 0x2C655, 0x2BAC1, 0x2A5E4,
0x29BAE, 0x2848B, 0x27DA3, 0x26286, 0x25CCC, 0x243E9, 0x23F7D, 0x22058, 0x21E12,
0x20137,
];
const MASKED_PATTERNS_SUB: [u32; 64] = [
// Finder sub pattern side
0x20A7B, 0x2155E, 0x22B14, 0x23431, 0x248A5, 0x25780, 0x269CA, 0x276EF, 0x28FC7,
0x290E2, 0x2AEA8, 0x2B18D, 0x2CD19, 0x2D23C, 0x2EC76, 0x2F353, 0x30103, 0x31E26,
0x3206C, 0x33F49, 0x343DD, 0x35CF8, 0x362B2, 0x37D97, 0x384BF, 0x39B9A, 0x3A5D0,
0x3BAF5, 0x3C661, 0x3D944, 0x3E70E, 0x3F82B, 0x003AE, 0x01C8B, 0x022C1, 0x03DE4,
0x04170, 0x05E55, 0x0601F, 0x07F3A, 0x08612, 0x09937, 0x0A77D, 0x0B858, 0x0C4CC,
0x0DBE9, 0x0E5A3, 0x0FA86, 0x108D6, 0x117F3, 0x129B9, 0x1369C, 0x14A08, 0x1552D,
0x16B67, 0x17442, 0x18D6A, 0x1924F, 0x1AC05, 0x1B320, 0x1CFB4, 0x1D091, 0x1EEDB,
0x1F1FE,
];
let mut fi = FormatInformation::default();
let mut best = |bits: &[u32], &patterns: &[u32; 64], mask: u32| {
for bitsIndex in 0..bits.len() {
// for (int bitsIndex = 0; bitsIndex < Size(bits); ++bitsIndex) {
for l_pattern in patterns {
// for (uint32_t pattern : patterns) {
// 'unmask' the pattern first to get the original 6-data bits + 12-ec bits back
let pattern = l_pattern ^ mask;
// Find the pattern with fewest bits differing
let hammingDist = ((bits[bitsIndex] ^ mask) ^ pattern).count_ones();
if (hammingDist < fi.hammingDistance) {
fi.mask = mask; // store the used mask to discriminate between types/models
fi.data = pattern >> 12; // drop the 12 BCH error correction bits
fi.hammingDistance = hammingDist;
fi.bitsIndex = bitsIndex as u8;
}
}
}
};
best(bits, &MASKED_PATTERNS, FORMAT_INFO_MASK_RMQR);
if (!subbits.is_empty())
// TODO probably remove if `sampleRMQR()` done properly
{
best(subbits, &MASKED_PATTERNS_SUB, FORMAT_INFO_MASK_RMQR_SUB);
}
fi
}
pub const fn qr_type(&self) -> Type {
match self.mask {
FORMAT_INFO_MASK_QR_MODEL1 => Type::Model1,
FORMAT_INFO_MASK_MICRO => Type::Micro,
FORMAT_INFO_MASK_RMQR | FORMAT_INFO_MASK_RMQR_SUB => Type::RectMicro,
_ => Type::Model2,
}
}
// 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 {
// Hamming distance of the 32 masked codes is 7 (64 and 8 for rMQR), by construction, so <= 3 bits differing means we found a match
pub const fn isValid(&self) -> bool {
self.hammingDistance <= 3
}

View File

@@ -8,9 +8,45 @@
use crate::common::{BitMatrix, Result};
use crate::qrcode::cpp_port::Type;
use crate::qrcode::decoder::{
Version, VersionRef, MICRO_VERSIONS, MODEL1_VERSIONS, VERSIONS, VERSION_DECODE_INFO,
Version, VersionRef, MICRO_VERSIONS, MODEL1_VERSIONS, RMQR_VERSIONS, VERSIONS,
VERSION_DECODE_INFO,
};
use crate::Exceptions;
use crate::{point, Exceptions, PointI};
const dimsVersionRMQR: [PointI; 32] = [
point(43, 7),
point(59, 7),
point(77, 7),
point(99, 7),
point(139, 7),
point(43, 9),
point(59, 9),
point(77, 9),
point(99, 9),
point(139, 9),
point(27, 11),
point(43, 11),
point(59, 11),
point(77, 11),
point(99, 11),
point(139, 11),
point(27, 13),
point(43, 13),
point(59, 13),
point(77, 13),
point(99, 13),
point(139, 13),
point(43, 15),
point(59, 15),
point(77, 15),
point(99, 15),
point(139, 15),
point(43, 17),
point(59, 17),
point(77, 17),
point(99, 17),
point(139, 17),
];
impl Version {
pub fn Model1(version_number: u32) -> Result<VersionRef> {
@@ -37,11 +73,20 @@ impl Version {
}
}
pub fn DimensionOfVersion(version: u32, is_micro: bool) -> u32 {
pub fn rMQR(version_number: u32) -> Result<VersionRef> {
let version_number = version_number as usize;
if (version_number < 1 || version_number > (RMQR_VERSIONS.len())) {
Err(Exceptions::ILLEGAL_ARGUMENT)
} else {
Ok(&RMQR_VERSIONS[version_number as usize - 1])
}
}
pub const fn DimensionOfVersion(version: u32, is_micro: bool) -> u32 {
Self::DimensionOffset(is_micro) + Self::DimensionStep(is_micro) * version
}
pub fn DimensionOffset(is_micro: bool) -> u32 {
pub const fn DimensionOffset(is_micro: bool) -> u32 {
match is_micro {
true => 9,
false => 17,
@@ -49,7 +94,7 @@ impl Version {
// return std::array{17, 9}[isMicro];
}
pub fn DimensionStep(is_micro: bool) -> u32 {
pub const fn DimensionStep(is_micro: bool) -> u32 {
match is_micro {
true => 2,
false => 4,
@@ -93,22 +138,65 @@ impl Version {
Type::const_eq(self.qr_type, Type::Model2)
}
pub const fn isRMQR(&self) -> bool {
Type::const_eq(self.qr_type, Type::RectMicro)
}
pub fn HasMicroSize(bitMatrix: &BitMatrix) -> bool {
let size = bitMatrix.height();
(11..=17).contains(&size) && (size % 2) == 1
size == bitMatrix.width() && size >= 11 && size <= 17 && (size % 2) == 1
}
pub fn HasRMQRSize(bitMatrix: &BitMatrix) -> bool {
Self::getVersionRMQR(bitMatrix) != -1
}
pub fn HasValidSize(bitMatrix: &BitMatrix) -> bool {
let size = bitMatrix.height();
Self::HasMicroSize(bitMatrix) || ((21..=177).contains(&size) && (size % 4) == 1)
if bitMatrix.width() != size {
Self::HasRMQRSize(bitMatrix)
} else {
Self::HasMicroSize(bitMatrix) || ((21..=177).contains(&size) && (size % 4) == 1)
}
}
pub fn Number(bitMatrix: &BitMatrix) -> u32 {
if !Self::HasValidSize(bitMatrix) {
if bitMatrix.width() != bitMatrix.height() {
Self::getVersionRMQR(bitMatrix) as u32 + 1
} else if !Self::HasValidSize(bitMatrix) {
0
} else {
let isMicro = Self::HasMicroSize(bitMatrix);
(bitMatrix.height() - Self::DimensionOffset(isMicro)) / Self::DimensionStep(isMicro)
}
}
pub fn DimensionOfVersionRMQR(version_number: u32) -> PointI {
if version_number < 1 || version_number as usize > dimsVersionRMQR.len() {
point(0, 0)
} else {
dimsVersionRMQR[version_number as usize - 1]
}
}
fn getVersionRMQR(bitMatrix: &BitMatrix) -> i32 {
let width = bitMatrix.width() as i32;
let height = bitMatrix.height() as i32;
if width != height
&& (width & 1 != 0)
&& (height & 1 != 0)
&& width >= 27
&& width <= 139
&& height >= 7
&& height <= 17
{
for i in 0..dimsVersionRMQR.len() {
// for (int i = 0; i < Size(dimsVersionRMQR); i++){
if width == dimsVersionRMQR[i].x && height == dimsVersionRMQR[i].y {
return i as i32;
}
}
}
return -1;
}
}

View File

@@ -38,7 +38,7 @@ pub struct ECIStringBuilder {
pub has_eci: bool,
eci_result: Option<String>,
bytes: Vec<u8>,
eci_positions: Vec<(Eci, usize, usize)>, // (Eci, start, end)
pub(crate) eci_positions: Vec<(Eci, usize, usize)>, // (Eci, start, end)
pub symbology: SymbologyIdentifier,
eci_list: HashSet<Eci>,
}

View File

@@ -29,7 +29,7 @@ use super::Quadrilateral;
*
* @author Sean Owen
*/
#[derive(Debug, Copy, Clone, PartialEq)]
#[derive(Debug, Copy, Clone, PartialEq, Default)]
pub struct PerspectiveTransform {
a11: f32,
a12: f32,