Skip to content

Commit

Permalink
Merge pull request #43 from rxing-core/rmqr_port
Browse files Browse the repository at this point in the history
rMQR port from zxing-cpp
  • Loading branch information
hschimke authored Jan 16, 2024
2 parents d2b3bf6 + 9261c40 commit 12eedd1
Show file tree
Hide file tree
Showing 41 changed files with 3,445 additions and 1,110 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "rxing"
version = "0.5.3"
version = "0.5.4"
description="A rust port of the zxing barcode library."
license="Apache-2.0"
repository="https://github.com/rxing-core/rxing"
Expand Down
6 changes: 6 additions & 0 deletions src/barcode_format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ pub enum BarcodeFormat {

MICRO_QR_CODE,

RECTANGULAR_MICRO_QR_CODE,

/** RSS 14 */
RSS_14,

Expand Down Expand Up @@ -108,6 +110,7 @@ impl Display for BarcodeFormat {
BarcodeFormat::PDF_417 => "pdf 417",
BarcodeFormat::QR_CODE => "qrcode",
BarcodeFormat::MICRO_QR_CODE => "mqr",
BarcodeFormat::RECTANGULAR_MICRO_QR_CODE => "rmqr",
BarcodeFormat::RSS_14 => "rss 14",
BarcodeFormat::RSS_EXPANDED => "rss expanded",
BarcodeFormat::TELEPEN => "telepen",
Expand Down Expand Up @@ -150,6 +153,9 @@ impl From<&str> for BarcodeFormat {
"mqr" | "microqr" | "micro_qr" | "micro_qrcode" | "micro_qr_code" | "mqr_code" => {
BarcodeFormat::MICRO_QR_CODE
}
"rmqr" | "rectangular_mqr" | "rectangular_micro_qr" | "rmqr_code" => {
BarcodeFormat::RECTANGULAR_MICRO_QR_CODE
}
"rss 14" | "rss_14" | "rss14" | "gs1 databar" | "gs1 databar coupon"
| "gs1_databar_coupon" => BarcodeFormat::RSS_14,
"rss expanded" | "expanded rss" | "rss_expanded" => BarcodeFormat::RSS_EXPANDED,
Expand Down
4 changes: 2 additions & 2 deletions src/common/bit_matrix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -636,7 +636,7 @@ impl BitMatrix {
self.width()
}

pub fn width(&self) -> u32 {
pub const fn width(&self) -> u32 {
self.width
}

Expand All @@ -647,7 +647,7 @@ impl BitMatrix {
self.height()
}

pub fn height(&self) -> u32 {
pub const fn height(&self) -> u32 {
self.height
}

Expand Down
242 changes: 184 additions & 58 deletions src/common/cpp_essentials/base_extentions/qr_formatinformation.rs
Original file line number Diff line number Diff line change
@@ -1,41 +1,49 @@
use crate::qrcode::decoder::{
ErrorCorrectionLevel, FormatInformation, FORMAT_INFO_DECODE_LOOKUP, FORMAT_INFO_MASK_QR,
use crate::qrcode::{
cpp_port::Type,
decoder::{
ErrorCorrectionLevel, FormatInformation, FORMAT_INFO_MASK_MODEL2, 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],
];
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,
// 0x4172,
// 0x4E2B,
// 0x4B1C,
// 0x55AE,
// 0x5099,
// 0x5FC0,
// 0x5AF7,
// 0x6793,
// 0x62A4,
// 0x6DFD,
// 0x68CA,
// 0x7678,
// 0x734F,
// 0x7C16,
// 0x7921,
// 0x06DE,
// 0x03E9,
// 0x0CB0,
// 0x0987,
// 0x1735,
// 0x1202,
// 0x1D5B,
// 0x186C,
// 0x2508,
// 0x203F,
// 0x2F66,
// 0x2A51,
// 0x34E3,
// 0x31D4,
// 0x3E8D,
// 0x3BBA,
// ];

impl FormatInformation {
/**
Expand All @@ -49,9 +57,9 @@ impl FormatInformation {
);
let formatInfoBits2 =
((formatInfoBits2 >> 1) & 0b111111100000000) | (formatInfoBits2 & 0b11111111);
// Some (Model2) QR codes apparently do not apply the XOR mask. Try with (standard) and without (quirk) masking.
let mut fi = Self::FindBestFormatInfo(
FORMAT_INFO_MASK_QR,
FORMAT_INFO_DECODE_LOOKUP,
&[FORMAT_INFO_MASK_QR, 0, FORMAT_INFO_MASK_QR_MODEL1],
&[
formatInfoBits1,
formatInfoBits2,
Expand All @@ -62,8 +70,8 @@ impl FormatInformation {

// 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;
ErrorCorrectionLevel::ECLevelFromBits((fi.data >> 3) as u8 & 0x03, false);
fi.data_mask = fi.data as u8 & 0x07;
fi.isMirrored = fi.bitsIndex > 1;

fi
Expand All @@ -72,60 +80,178 @@ impl FormatInformation {
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,
&[FORMAT_INFO_MASK_MICRO, 0],
&[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;
ErrorCorrectionLevel::ECLevelFromBits((fi.data >> 2) as u8 & 0x07, true);
fi.data_mask = fi.data as u8 & 0x03;
fi.microVersion = BITS_TO_VERSION[((fi.data >> 2) as u8 & 0x07) as usize] as u32;
fi.isMirrored = fi.bitsIndex == 1;

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 mut fi = if formatInfoBits2 != 0 {
Self::FindBestFormatInfoRMQR(&[formatInfoBits1], &[formatInfoBits2])
} else {
// TODO probably remove if `sampleRMQR()` done properly
Self::FindBestFormatInfoRMQR(&[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.microVersion = (fi.data & 0x1F) + 1;
fi.isMirrored = false; // TODO: implement mirrored format bit reading

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 {
pub fn FindBestFormatInfo(masks: &[u32], 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, bit_set) in bits.iter().enumerate() {
// See ISO 18004:2015, Annex C, Table C.1
const MODEL2_MASKED_PATTERNS: [u32; 32] = [
0x5412, 0x5125, 0x5E7C, 0x5B4B, 0x45F9, 0x40CE, 0x4F97, 0x4AA0, 0x77C4, 0x72F3, 0x7DAA,
0x789D, 0x662F, 0x6318, 0x6C41, 0x6976, 0x1689, 0x13BE, 0x1CE7, 0x19D0, 0x0762, 0x0255,
0x0D0C, 0x083B, 0x355F, 0x3068, 0x3F31, 0x3A06, 0x24B4, 0x2183, 0x2EDA, 0x2BED,
];

for mask in masks {
// for (auto mask : masks)
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 = ((bit_set ^ mask) ^ pattern).count_ones();
for ref_pattern in MODEL2_MASKED_PATTERNS {
// for (uint32_t pattern : MODEL2_MASKED_PATTERNS) {
// 'unmask' the pattern first to get the original 5-data bits + 10-ec bits back
let pattern = ref_pattern ^ FORMAT_INFO_MASK_MODEL2;
// Find the pattern with fewest bits differing
let hammingDist = ((bits[bitsIndex] ^ mask) ^ pattern).count_ones();
// if (int hammingDist = BitHacks::CountBitsSet((bits[bitsIndex] ^ mask) ^ pattern);
if hammingDist < fi.hammingDistance {
fi.mask = *mask; // store the used mask to discriminate between types/models
fi.data = pattern >> 10; // drop the 10 BCH error correction bits
fi.hammingDistance = hammingDist;
fi.bitsIndex = bitsIndex as u8;
}
}
}
}

// // Some QR codes apparently do not apply the XOR mask. Try without and with additional masking.
// for mask in masks {
// // for (auto mask : {0, mask})
// for (bitsIndex, bit_set) in bits.iter().enumerate() {
// // for (int bitsIndex = 0; bitsIndex < Size(bits); ++bitsIndex)
// for [pattern, _index] in FORMAT_INFO_DECODE_LOOKUP {
// // for (const auto& [pattern, index] : lookup) {
// // Find the int in lookup with fewest bits differing
// let hammingDist = ((bit_set ^ mask) ^ pattern).count_ones();
// if hammingDist < fi.hammingDistance {
// // if (int hammingDist = BitHacks::CountBitsSet((bits[bitsIndex] ^ mask) ^ pattern); hammingDist < fi.hammingDistance) {
// fi.mask = *mask; // store the used mask to discriminate between types/models
// fi.data = pattern >> 10; // drop the 10 BCH error correction bits
// fi.hammingDistance = hammingDist;
// fi.bitsIndex = bitsIndex as u8;
// }
// }
// }
// }

fi
}

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 {
// if (int hammingDist = BitHacks::CountBitsSet((bits[bitsIndex] ^ mask) ^ pattern); hammingDist < fi.hammingDistance) {
fi.index = index as u8;
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
}

// 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 {
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 (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
}

pub fn cpp_eq(&self, other: &Self) -> bool {
self.data_mask == other.data_mask
&& self.error_correction_level == other.error_correction_level
&& self.qr_type() == other.qr_type()
}
}
Loading

0 comments on commit 12eedd1

Please sign in to comment.