mirror of
https://github.com/starovoid/rxing.git
synced 2026-07-26 04:12:34 +00:00
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:
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>,
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -32,6 +32,7 @@ pub fn ReadVersion(bitMatrix: &BitMatrix, qr_type: Type) -> Result<VersionRef> {
|
||||
Type::Model1 => Version::Model1(number),
|
||||
Type::Micro => Version::Micro(number),
|
||||
Type::Model2 => Version::Model2(number),
|
||||
Type::RectMicro => Version::rMQR(number),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +52,47 @@ pub fn ReadFormatInformation(bitMatrix: &BitMatrix) -> Result<FormatInformation>
|
||||
return Ok(FormatInformation::DecodeMQR(formatInfoBits as u32));
|
||||
}
|
||||
|
||||
if (Version::HasRMQRSize(bitMatrix)) {
|
||||
// Read top-left format info bits
|
||||
let mut formatInfoBits1 = 0;
|
||||
for y in (1..=3).rev() {
|
||||
// for (int y = 3; y >= 1; y--){
|
||||
AppendBit(&mut formatInfoBits1, getBit(bitMatrix, 11, y, None));
|
||||
}
|
||||
for x in (8..=10).rev() {
|
||||
// for (int x = 10; x >= 8; x--){
|
||||
for y in (1..=5).rev() {
|
||||
// for (int y = 5; y >= 1; y--){
|
||||
AppendBit(&mut formatInfoBits1, getBit(bitMatrix, x, y, None));
|
||||
}
|
||||
}
|
||||
// Read bottom-right format info bits
|
||||
let mut formatInfoBits2 = 0;
|
||||
let width = bitMatrix.width();
|
||||
let height = bitMatrix.height();
|
||||
for x in 3..=5 {
|
||||
// for (int x = 3; x <= 5; x++){
|
||||
AppendBit(
|
||||
&mut formatInfoBits2,
|
||||
getBit(bitMatrix, width - x, height - 6, None),
|
||||
);
|
||||
}
|
||||
for x in 6..=8 {
|
||||
// for (int x = 6; x <= 8; x++){
|
||||
for y in 2..=6 {
|
||||
// for (int y = 2; y <= 6; y++){
|
||||
AppendBit(
|
||||
&mut formatInfoBits2,
|
||||
getBit(bitMatrix, width - x, height - y, None),
|
||||
);
|
||||
}
|
||||
}
|
||||
return Ok(FormatInformation::DecodeRMQR(
|
||||
formatInfoBits1 as u32,
|
||||
formatInfoBits2 as u32,
|
||||
));
|
||||
}
|
||||
|
||||
// Read top-left format info bits
|
||||
let mut formatInfoBits1 = 0;
|
||||
for x in 0..6 {
|
||||
@@ -311,6 +353,59 @@ pub fn ReadQRCodewordsModel1(
|
||||
Ok(result.iter().copied().map(|x| x as u8).collect())
|
||||
}
|
||||
|
||||
pub fn ReadRMQRCodewords(
|
||||
bitMatrix: &BitMatrix,
|
||||
version: VersionRef,
|
||||
formatInfo: &FormatInformation,
|
||||
) -> Result<Vec<u8>> {
|
||||
let functionPattern = version.buildFunctionPattern()?;
|
||||
|
||||
let mut result = Vec::with_capacity(version.getTotalCodewords() as usize);
|
||||
let mut currentByte = 0;
|
||||
let mut readingUp = true;
|
||||
let mut bitsRead = 0;
|
||||
let width = bitMatrix.width();
|
||||
let height = bitMatrix.height();
|
||||
// Read columns in pairs, from right to left
|
||||
let mut x = width as i32 - 1 - 1;
|
||||
while x > 0 {
|
||||
// for (int x = width - 1 - 1; x > 0; x -= 2) { // Skip right edge alignment
|
||||
// Read alternatingly from bottom to top then top to bottom
|
||||
for row in 0..height {
|
||||
// for (int row = 0; row < height; row++) {
|
||||
let y = if readingUp { height - 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 as u32, y)) {
|
||||
// Read a bit
|
||||
AppendBit(
|
||||
&mut currentByte,
|
||||
GetDataMaskBit(formatInfo.data_mask as u32, xx as u32, y, None)?
|
||||
!= getBit(bitMatrix, xx as u32, y, Some(formatInfo.isMirrored)),
|
||||
);
|
||||
// If we've made a whole byte, save it off
|
||||
bitsRead += 1;
|
||||
if bitsRead % 8 == 0 {
|
||||
// if (++bitsRead % 8 == 0){
|
||||
result.push(currentByte);
|
||||
currentByte = 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,
|
||||
@@ -320,5 +415,6 @@ pub fn ReadCodewords(
|
||||
Type::Model1 => ReadQRCodewordsModel1(bitMatrix, version, formatInfo),
|
||||
Type::Model2 => ReadQRCodewords(bitMatrix, version, formatInfo),
|
||||
Type::Micro => ReadMQRCodewords(bitMatrix, version, formatInfo),
|
||||
Type::RectMicro => ReadRMQRCodewords(bitMatrix, version, formatInfo),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -310,7 +310,7 @@ pub fn DecodeBitStream(
|
||||
} else {
|
||||
Mode::CodecModeForBits(
|
||||
bits.readBits(modeBitLength as usize)?,
|
||||
Some(version.isMicro()),
|
||||
Some(version.qr_type),
|
||||
)?
|
||||
};
|
||||
|
||||
|
||||
@@ -5,14 +5,15 @@ use crate::{
|
||||
},
|
||||
DefaultGridSampler, GridSampler, Result, SamplerControl,
|
||||
},
|
||||
point_i,
|
||||
point, point_i,
|
||||
qrcode::{
|
||||
decoder::{FormatInformation, Version, VersionRef},
|
||||
detector::QRCodeDetectorResult,
|
||||
},
|
||||
Exceptions,
|
||||
Exceptions, PointF,
|
||||
};
|
||||
use multimap::MultiMap;
|
||||
use num::Integer;
|
||||
|
||||
use crate::{
|
||||
common::{
|
||||
@@ -23,7 +24,7 @@ use crate::{
|
||||
},
|
||||
BitMatrix, PerspectiveTransform, Quadrilateral,
|
||||
},
|
||||
point_f, Point,
|
||||
point_f, Point, PointI,
|
||||
};
|
||||
|
||||
#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
|
||||
@@ -39,6 +40,8 @@ pub type FinderPatternSets = Vec<FinderPatternSet>;
|
||||
const LEN: usize = 5;
|
||||
const SUM: usize = 7;
|
||||
const PATTERN: FixedPattern<LEN, SUM, false> = FixedPattern::new([1, 1, 3, 1, 1]);
|
||||
const SUBPATTERN_RMQR: FixedPattern<5, 5, false> = FixedPattern::new([1, 1, 1, 1, 1]);
|
||||
const CORNER_EDGE_RMQR: FixedPattern<2, 4, false> = FixedPattern::new([3, 1]);
|
||||
const E2E: bool = true;
|
||||
|
||||
fn FindPattern(view: PatternView<'_>) -> Result<PatternView<'_>> {
|
||||
@@ -885,8 +888,8 @@ pub fn DetectPureQR(image: &BitMatrix) -> Result<QRCodeDetectorResult> {
|
||||
pub fn DetectPureMQR(image: &BitMatrix) -> Result<QRCodeDetectorResult> {
|
||||
type Pattern = [PatternType; 5];
|
||||
|
||||
let MIN_MODULES = Version::DimensionOfVersion(1, true);
|
||||
let MAX_MODULES = Version::DimensionOfVersion(4, true);
|
||||
const MIN_MODULES: u32 = Version::DimensionOfVersion(1, true);
|
||||
const MAX_MODULES: u32 = Version::DimensionOfVersion(4, true);
|
||||
|
||||
let (found, left, top, width, height) = image.findBoundingBox(0, 0, 0, 0, MIN_MODULES);
|
||||
|
||||
@@ -949,6 +952,139 @@ pub fn DetectPureMQR(image: &BitMatrix) -> Result<QRCodeDetectorResult> {
|
||||
// {{left, top}, {right, top}, {right, bottom}, {left, bottom}}};
|
||||
}
|
||||
|
||||
pub fn DetectPureRMQR(image: &BitMatrix) -> Result<QRCodeDetectorResult> {
|
||||
type Pattern = [PatternType; 5]; //std::array<PatternView::value_type, PATTERN.size()>;
|
||||
type SubPattern = [PatternType; 5]; //std::array<PatternView::value_type, SUBPATTERN_RMQR.size()>;
|
||||
type CornerEdgePattern = [PatternType; 2]; //std::array<PatternView::value_type, CORNER_EDGE_RMQR.size()>;
|
||||
|
||||
// #ifdef PRINT_DEBUG
|
||||
// SaveAsPBM(image, "weg.pbm");
|
||||
// #endif
|
||||
|
||||
const MIN_MODULES: u32 = 7;
|
||||
const MIN_MODULES_W: u32 = 27;
|
||||
const MIN_MODULES_H: u32 = 7;
|
||||
const MAX_MODULES_W: u32 = 139;
|
||||
const MAX_MODULES_H: u32 = 17;
|
||||
|
||||
let (found, left, top, width, height) = image.findBoundingBox(0, 0, 0, 0, MIN_MODULES);
|
||||
|
||||
if (!found) {
|
||||
return Err(Exceptions::NOT_FOUND);
|
||||
}
|
||||
let right = left + width - 1;
|
||||
let bottom = top + height - 1;
|
||||
|
||||
let tl = point_i(left, top);
|
||||
let tr = point_i(right, top);
|
||||
let br = point_i(right, bottom);
|
||||
let bl = point_i(left, bottom);
|
||||
|
||||
// allow corners be moved one pixel inside to accommodate for possible aliasing artifacts
|
||||
let diagonal: Pattern = EdgeTracer::new(image, tl, point_i(1, 1))
|
||||
.readPatternFromBlack(1, None)
|
||||
.ok_or(Exceptions::ILLEGAL_STATE)?;
|
||||
let diag_hld = diagonal.to_vec().into();
|
||||
let view = PatternView::new(&diag_hld);
|
||||
if !(IsPattern::<E2E, 5, 7, false>(&view, &PATTERN, None, 0.0, 0.0) != 0.0) {
|
||||
return Err(Exceptions::NOT_FOUND);
|
||||
}
|
||||
|
||||
// Finder sub pattern
|
||||
let mut subdiagonal: SubPattern = EdgeTracer::new(image, br, point_i(-1, -1))
|
||||
.readPatternFromBlack(1, None)
|
||||
.ok_or(Exceptions::ILLEGAL_STATE)?;
|
||||
if (subdiagonal.len() == 5 && subdiagonal[4] > subdiagonal[3]) {
|
||||
// Sub pattern has no separator so can run off along the diagonal
|
||||
subdiagonal[4] = subdiagonal[3]; // Hack it back to previous
|
||||
}
|
||||
let subdiagonal_hld = subdiagonal.to_vec().into();
|
||||
let view = PatternView::new(&subdiagonal_hld);
|
||||
if !(IsPattern::<E2E, 5, 5, false>(&view, &SUBPATTERN_RMQR, None, 0.0, 0.0) != 0.0) {
|
||||
return Err(Exceptions::NOT_FOUND);
|
||||
}
|
||||
|
||||
// Horizontal corner finder patterns (for vertical ones see below)
|
||||
for (p, d) in [(tr, point_i(-1, 0)), (bl, point_i(1, 0))] {
|
||||
// for (auto [p, d] : {std::pair(tr, PointI{-1, 0}), {bl, {1, 0}}}) {
|
||||
let corner: CornerEdgePattern = EdgeTracer::new(image, p, d)
|
||||
.readPatternFromBlack(1, None)
|
||||
.ok_or(Exceptions::ILLEGAL_STATE)?;
|
||||
let corner_hld = corner.to_vec().into();
|
||||
let view = PatternView::new(&corner_hld);
|
||||
if !(IsPattern::<E2E, 2, 4, false>(&view, &CORNER_EDGE_RMQR, None, 0.0, 0.0) != 0.0) {
|
||||
{
|
||||
return Err(Exceptions::NOT_FOUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let fpWidth = (diagonal).into_iter().sum::<u16>();
|
||||
let moduleSize = (fpWidth as f32) / 7.0;
|
||||
let dimW = (width as f32 / moduleSize as f32).floor() as u32;
|
||||
let dimH = (height as f32 / moduleSize as f32).floor() as u32;
|
||||
|
||||
if (dimW == dimH
|
||||
|| dimW.is_even()
|
||||
|| dimH.is_even()
|
||||
|| dimW < MIN_MODULES_W
|
||||
|| dimW > MAX_MODULES_W
|
||||
|| dimH < MIN_MODULES_H
|
||||
|| dimH > MAX_MODULES_H
|
||||
|| !image.is_in(point_f(
|
||||
left as f32 + moduleSize / 2.0 + (dimW as f32 - 1.0) * moduleSize,
|
||||
top as f32 + moduleSize / 2.0 + (dimH as f32 - 1.0) * moduleSize,
|
||||
)))
|
||||
{
|
||||
return Err(Exceptions::NOT_FOUND);
|
||||
}
|
||||
|
||||
// Vertical corner finder patterns
|
||||
if (dimH > 7) {
|
||||
// None for R7
|
||||
let corner: CornerEdgePattern = EdgeTracer::new(image, tr, point_i(0, 1))
|
||||
.readPatternFromBlack(1, None)
|
||||
.ok_or(Exceptions::ILLEGAL_STATE)?;
|
||||
let corner_hld = corner.to_vec().into();
|
||||
let view = PatternView::new(&corner_hld);
|
||||
if !(IsPattern::<E2E, 2, 4, false>(&view, &CORNER_EDGE_RMQR, None, 0.0, 0.0) != 0.0) {
|
||||
return Err(Exceptions::NOT_FOUND);
|
||||
}
|
||||
if (dimH > 9) {
|
||||
// No bottom left for R9
|
||||
let corner: CornerEdgePattern = EdgeTracer::new(image, bl, point_i(0, -1))
|
||||
.readPatternFromBlack(1, None)
|
||||
.ok_or(Exceptions::ILLEGAL_STATE)?;
|
||||
let corner_hld = corner.to_vec().into();
|
||||
let view = PatternView::new(&corner_hld);
|
||||
if !(IsPattern::<E2E, 2, 4, false>(&view, &CORNER_EDGE_RMQR, None, 0.0, 0.0) != 0.0) {
|
||||
return Err(Exceptions::NOT_FOUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// #ifdef PRINT_DEBUG
|
||||
// LogMatrix log;
|
||||
// LogMatrixWriter lmw(log, image, 5, "grid2.pnm");
|
||||
// for (int y = 0; y < dimH; y++)
|
||||
// for (int x = 0; x < dimW; x++)
|
||||
// log(PointF(left + (x + .5f) * moduleSize, top + (y + .5f) * moduleSize));
|
||||
// #endif
|
||||
|
||||
// Now just read off the bits (this is a crop + subsample)
|
||||
Ok(QRCodeDetectorResult::new(
|
||||
image.Deflate(
|
||||
dimW,
|
||||
dimH,
|
||||
top as f32 + moduleSize / 2.0,
|
||||
left as f32 + moduleSize / 2.0,
|
||||
moduleSize,
|
||||
)?,
|
||||
vec![tl, tr, br, bl],
|
||||
))
|
||||
// return {Deflate(image, dimW, dimH, top + moduleSize / 2, left + moduleSize / 2, moduleSize), {tl, tr, br, bl}};
|
||||
}
|
||||
|
||||
pub fn SampleMQR(image: &BitMatrix, fp: ConcentricPattern) -> Result<QRCodeDetectorResult> {
|
||||
let Some(fpQuad) = FindConcentricPatternCorners(image, fp.p, fp.size, 2) else {
|
||||
return Err(Exceptions::NOT_FOUND);
|
||||
@@ -1057,3 +1193,91 @@ pub fn SampleMQR(image: &BitMatrix, fp: ConcentricPattern) -> Result<QRCodeDetec
|
||||
|
||||
// SampleGrid(image, dim, dim, bestPT)
|
||||
}
|
||||
|
||||
pub fn SampleRMQR(image: &BitMatrix, fp: ConcentricPattern) -> Result<QRCodeDetectorResult> {
|
||||
// TODO proper
|
||||
let Some(fpQuad) = FindConcentricPatternCorners(image, fp.p, fp.size, 2) else {
|
||||
return Err(Exceptions::NOT_FOUND);
|
||||
};
|
||||
|
||||
let srcQuad = Quadrilateral::rectangle(7, 7, Some(0.5));
|
||||
|
||||
let FORMAT_INFO_EDGE_COORDS: [Point; 4] =
|
||||
[point_i(8, 0), point_i(9, 0), point_i(10, 0), point_i(11, 0)];
|
||||
let FORMAT_INFO_COORDS: [Point; 18] = [
|
||||
point_i(8, 1),
|
||||
point_i(8, 2),
|
||||
point_i(8, 3),
|
||||
point_i(8, 4),
|
||||
point_i(8, 5),
|
||||
point_i(9, 1),
|
||||
point_i(9, 2),
|
||||
point_i(9, 3),
|
||||
point_i(9, 4),
|
||||
point_i(9, 5),
|
||||
point_i(10, 1),
|
||||
point_i(10, 2),
|
||||
point_i(10, 3),
|
||||
point_i(10, 4),
|
||||
point_i(10, 5),
|
||||
point_i(11, 1),
|
||||
point_i(11, 2),
|
||||
point_i(11, 3),
|
||||
];
|
||||
|
||||
let mut bestFI: FormatInformation = FormatInformation::default();
|
||||
let mut bestPT: PerspectiveTransform = PerspectiveTransform::default();
|
||||
|
||||
for i in 0..4 {
|
||||
// for (int i = 0; i < 4; ++i) {
|
||||
let mod2Pix = PerspectiveTransform::quadrilateralToQuadrilateral(
|
||||
srcQuad,
|
||||
fpQuad.rotated_corners(Some(i), None),
|
||||
)?;
|
||||
|
||||
let check = |i: usize, on: bool| {
|
||||
let p = mod2Pix.transform_point(Point::centered(FORMAT_INFO_EDGE_COORDS[i]));
|
||||
image.is_in(p) && image.get_point(p) == on
|
||||
};
|
||||
|
||||
// check that we see top edge timing pattern modules
|
||||
if (!check(0, true) || !check(1, false) || !check(2, true) || !check(3, false)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut formatInfoBits = 0;
|
||||
for i in 0..FORMAT_INFO_COORDS.len() {
|
||||
// for (int i = 0; i < Size(FORMAT_INFO_COORDS); ++i)
|
||||
AppendBit(
|
||||
&mut formatInfoBits,
|
||||
image.get_point(mod2Pix.transform_point(Point::centered(FORMAT_INFO_COORDS[i]))),
|
||||
);
|
||||
}
|
||||
|
||||
let fi = FormatInformation::DecodeRMQR(formatInfoBits as u32, 0 /*formatInfoBits2*/);
|
||||
if (fi.hammingDistance < bestFI.hammingDistance) {
|
||||
bestFI = fi;
|
||||
bestPT = mod2Pix;
|
||||
}
|
||||
}
|
||||
|
||||
if (!bestFI.isValid()) {
|
||||
return Err(Exceptions::NOT_FOUND);
|
||||
}
|
||||
|
||||
let dim = Version::DimensionOfVersionRMQR(bestFI.rMQRVersion as u32 + 1);
|
||||
|
||||
let grid_sampler = DefaultGridSampler;
|
||||
let (sample, rps) = grid_sampler.sample_grid(
|
||||
image,
|
||||
dim.x as u32,
|
||||
dim.y as u32,
|
||||
&[SamplerControl {
|
||||
p1: point_i(dim.x, dim.y),
|
||||
p0: point_i(0, 0),
|
||||
transform: bestPT,
|
||||
}],
|
||||
)?;
|
||||
Ok(QRCodeDetectorResult::new(sample, rps.to_vec()))
|
||||
// SampleGrid(image, dim.x, dim.y, bestPT)
|
||||
}
|
||||
|
||||
@@ -127,8 +127,8 @@ use crate::{
|
||||
use super::{
|
||||
decoder::Decode,
|
||||
detector::{
|
||||
DetectPureMQR, DetectPureQR, FindFinderPatterns, GenerateFinderPatternSets, SampleMQR,
|
||||
SampleQR,
|
||||
DetectPureMQR, DetectPureQR, DetectPureRMQR, FindFinderPatterns, GenerateFinderPatternSets,
|
||||
SampleMQR, SampleQR, SampleRMQR,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -179,10 +179,15 @@ impl Reader for QrReader {
|
||||
if formats.contains(&BarcodeFormat::MICRO_QR_CODE) && detectorResult.is_err() {
|
||||
detectorResult = DetectPureMQR(binImg);
|
||||
}
|
||||
if formats.contains(&BarcodeFormat::RECTANGULAR_MICRO_QR_CODE)
|
||||
&& detectorResult.is_err()
|
||||
{
|
||||
detectorResult = DetectPureRMQR(binImg);
|
||||
}
|
||||
}
|
||||
|
||||
if detectorResult.is_err() {
|
||||
for decode_function in [DetectPureQR, DetectPureMQR] {
|
||||
for decode_function in [DetectPureQR, DetectPureMQR, DetectPureRMQR] {
|
||||
detectorResult = decode_function(binImg);
|
||||
if detectorResult.is_ok() {
|
||||
break;
|
||||
@@ -208,25 +213,16 @@ impl Reader for QrReader {
|
||||
Ok(RXingResult::with_decoder_result(
|
||||
decoderResult,
|
||||
position,
|
||||
if detectorResult.getBits().width() < 21 {
|
||||
BarcodeFormat::MICRO_QR_CODE
|
||||
if detectorResult.getBits().width() != detectorResult.getBits().height() {
|
||||
BarcodeFormat::RECTANGULAR_MICRO_QR_CODE
|
||||
} else {
|
||||
BarcodeFormat::QR_CODE
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,16 +272,18 @@ impl QrReader {
|
||||
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)
|
||||
};
|
||||
let (check_qr, check_mqr, check_rmqr) =
|
||||
if let Some(DecodeHintValue::PossibleFormats(formats)) =
|
||||
hints.get(&DecodeHintType::POSSIBLE_FORMATS)
|
||||
{
|
||||
(
|
||||
formats.contains(&BarcodeFormat::QR_CODE),
|
||||
formats.contains(&BarcodeFormat::MICRO_QR_CODE),
|
||||
formats.contains(&BarcodeFormat::RECTANGULAR_MICRO_QR_CODE),
|
||||
)
|
||||
} else {
|
||||
(true, true, true)
|
||||
};
|
||||
|
||||
if check_qr {
|
||||
// if (_hints.hasFormat(BarcodeFormat::QRCode)) {
|
||||
@@ -314,18 +312,11 @@ impl QrReader {
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -337,13 +328,13 @@ impl QrReader {
|
||||
}
|
||||
if check_mqr && !(maxSymbols != 0 && (results.len() as u32) == maxSymbols) {
|
||||
// if (_hints.hasFormat(BarcodeFormat::MicroQRCode) && !(maxSymbols && Size(results) == maxSymbols)) {
|
||||
for fp in allFPs {
|
||||
for fp in &allFPs {
|
||||
// for (const auto& fp : allFPs) {
|
||||
if usedFPs.contains(&fp) {
|
||||
if usedFPs.contains(fp) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let detectorResult = SampleMQR(binImg, fp);
|
||||
let detectorResult = SampleMQR(binImg, *fp);
|
||||
if let Ok(detectorResult) = detectorResult {
|
||||
// if (detectorResult.is_ok()) {
|
||||
let decoderResult = Decode(detectorResult.getBits());
|
||||
@@ -355,13 +346,34 @@ impl QrReader {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if check_rmqr && !(maxSymbols != 0 && (results.len() as u32) == maxSymbols) {
|
||||
for fp in &allFPs {
|
||||
// for (const auto& fp : allFPs) {
|
||||
if usedFPs.contains(fp) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let detectorResult = SampleRMQR(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::RECTANGULAR_MICRO_QR_CODE,
|
||||
));
|
||||
|
||||
if maxSymbols != 0 && (results.len() as u32) == maxSymbols {
|
||||
break;
|
||||
|
||||
@@ -3,6 +3,7 @@ pub enum Type {
|
||||
Model1,
|
||||
Model2,
|
||||
Micro,
|
||||
RectMicro,
|
||||
}
|
||||
|
||||
impl Type {
|
||||
|
||||
@@ -15,6 +15,13 @@ const MASKED_TEST_FORMAT_INFO2: u32 =
|
||||
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;
|
||||
const RMQR_MASKED_TEST_FORMAT_INFO: u32 = 0x20137;
|
||||
const RMQR_MASKED_TEST_FORMAT_INFO_SUB: u32 = 0x1F1FE;
|
||||
|
||||
const FORMAT_INFO_MASK_QR_MODEL1: u32 = 0x2825;
|
||||
const FORMAT_INFO_MASK_MICRO: u32 = 0x4445;
|
||||
const FORMAT_INFO_MASK_RMQR: u32 = 0x1FAB2; // Finder pattern side
|
||||
const FORMAT_INFO_MASK_RMQR_SUB: u32 = 0x20A7B; // Finder sub pattern side
|
||||
|
||||
fn DoFormatInformationTest(formatInfo: u32, expectedMask: u8, expectedECL: ErrorCorrectionLevel) {
|
||||
let parsedFormat = FormatInformation::DecodeMQR(formatInfo);
|
||||
@@ -23,6 +30,27 @@ fn DoFormatInformationTest(formatInfo: u32, expectedMask: u8, expectedECL: Error
|
||||
assert_eq!(expectedECL, parsedFormat.error_correction_level);
|
||||
}
|
||||
|
||||
// Helper for rMQR to unset `numBits` number of bits
|
||||
fn RMQRUnsetBits(formatInfoBits: u32, numBits: u32) -> u32 {
|
||||
let mut formatInfoBits = formatInfoBits;
|
||||
let mut numBits = numBits as i32;
|
||||
for i in 0..18 {
|
||||
// for (int i = 0; i < 18 && numBits; i++) {
|
||||
if (formatInfoBits & (1 << i)) != 0 {
|
||||
formatInfoBits ^= 1 << i;
|
||||
numBits -= 1;
|
||||
}
|
||||
if numBits == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
formatInfoBits
|
||||
}
|
||||
|
||||
fn cpp_eq(rhs: &FormatInformation, lhs: &FormatInformation) {
|
||||
assert!(rhs.cpp_eq(lhs))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn Decode() {
|
||||
// Normal case
|
||||
@@ -131,6 +159,85 @@ fn DecodeMicroWithBitDifference() {
|
||||
// FormatInformation::DecodeFormatInformation(MICRO_MASKED_TEST_FORMAT_INFO ^ 0x3f).errorCorrectionLevel());
|
||||
}
|
||||
|
||||
fn cpp_eq(rhs: &FormatInformation, lhs: &FormatInformation) {
|
||||
assert!(rhs.cpp_eq(lhs))
|
||||
#[test]
|
||||
fn DecodeRMQR() {
|
||||
// Normal case
|
||||
let expected = FormatInformation::DecodeRMQR(
|
||||
RMQR_MASKED_TEST_FORMAT_INFO,
|
||||
RMQR_MASKED_TEST_FORMAT_INFO_SUB,
|
||||
);
|
||||
assert!(expected.isValid());
|
||||
assert_eq!(4, expected.data_mask);
|
||||
assert_eq!(ErrorCorrectionLevel::H, expected.error_correction_level);
|
||||
assert_eq!(FORMAT_INFO_MASK_RMQR, expected.mask);
|
||||
// Not catered for: where the code forgot the mask!
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn DecodeRMQRWithBitDifference() {
|
||||
let expected = FormatInformation::DecodeRMQR(
|
||||
RMQR_MASKED_TEST_FORMAT_INFO,
|
||||
RMQR_MASKED_TEST_FORMAT_INFO_SUB,
|
||||
);
|
||||
assert_eq!(expected.error_correction_level, ErrorCorrectionLevel::H);
|
||||
// 1,2,3,4,5 bits difference
|
||||
cpp_eq(
|
||||
&expected,
|
||||
&FormatInformation::DecodeRMQR(
|
||||
RMQRUnsetBits(RMQR_MASKED_TEST_FORMAT_INFO, 1),
|
||||
RMQRUnsetBits(RMQR_MASKED_TEST_FORMAT_INFO_SUB, 1),
|
||||
),
|
||||
);
|
||||
cpp_eq(
|
||||
&expected,
|
||||
&FormatInformation::DecodeRMQR(
|
||||
RMQRUnsetBits(RMQR_MASKED_TEST_FORMAT_INFO, 2),
|
||||
RMQRUnsetBits(RMQR_MASKED_TEST_FORMAT_INFO_SUB, 2),
|
||||
),
|
||||
);
|
||||
cpp_eq(
|
||||
&expected,
|
||||
&FormatInformation::DecodeRMQR(
|
||||
RMQRUnsetBits(RMQR_MASKED_TEST_FORMAT_INFO, 3),
|
||||
RMQRUnsetBits(RMQR_MASKED_TEST_FORMAT_INFO_SUB, 3),
|
||||
),
|
||||
);
|
||||
cpp_eq(
|
||||
&expected,
|
||||
&FormatInformation::DecodeRMQR(
|
||||
RMQRUnsetBits(RMQR_MASKED_TEST_FORMAT_INFO, 4),
|
||||
RMQRUnsetBits(RMQR_MASKED_TEST_FORMAT_INFO_SUB, 4),
|
||||
),
|
||||
);
|
||||
let unexpected = FormatInformation::DecodeRMQR(
|
||||
RMQRUnsetBits(RMQR_MASKED_TEST_FORMAT_INFO, 5),
|
||||
RMQRUnsetBits(RMQR_MASKED_TEST_FORMAT_INFO_SUB, 5),
|
||||
);
|
||||
assert!(!(expected == unexpected));
|
||||
assert!(!(unexpected.isValid()));
|
||||
assert!(unexpected.qr_type() == Type::RectMicro); // Note `mask` (used to determine type) set regardless
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn DecodeRMQRWithMisread() {
|
||||
let expected = FormatInformation::DecodeRMQR(
|
||||
RMQR_MASKED_TEST_FORMAT_INFO,
|
||||
RMQR_MASKED_TEST_FORMAT_INFO_SUB,
|
||||
);
|
||||
{
|
||||
let actual = FormatInformation::DecodeRMQR(
|
||||
RMQRUnsetBits(RMQR_MASKED_TEST_FORMAT_INFO, 2),
|
||||
RMQRUnsetBits(RMQR_MASKED_TEST_FORMAT_INFO_SUB, 4),
|
||||
);
|
||||
cpp_eq(&expected, &actual);
|
||||
assert_eq!(actual.mask, FORMAT_INFO_MASK_RMQR);
|
||||
}
|
||||
{
|
||||
let actual = FormatInformation::DecodeRMQR(
|
||||
RMQRUnsetBits(RMQR_MASKED_TEST_FORMAT_INFO, 5),
|
||||
RMQRUnsetBits(RMQR_MASKED_TEST_FORMAT_INFO_SUB, 4),
|
||||
);
|
||||
cpp_eq(&expected, &actual);
|
||||
assert_eq!(actual.mask, FORMAT_INFO_MASK_RMQR_SUB);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,22 +4,34 @@
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::qrcode::decoder::{Mode, Version};
|
||||
use crate::qrcode::{
|
||||
cpp_port::Type,
|
||||
decoder::{Mode, Version},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn ForBits() {
|
||||
assert_eq!(
|
||||
Mode::TERMINATOR,
|
||||
Mode::CodecModeForBits(0x00, None).unwrap()
|
||||
Mode::CodecModeForBits(0x00, Some(Type::Model2)).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
Mode::NUMERIC,
|
||||
Mode::CodecModeForBits(0x01, Some(Type::Model2)).unwrap()
|
||||
);
|
||||
assert_eq!(Mode::NUMERIC, Mode::CodecModeForBits(0x01, None).unwrap());
|
||||
assert_eq!(
|
||||
Mode::ALPHANUMERIC,
|
||||
Mode::CodecModeForBits(0x02, None).unwrap()
|
||||
Mode::CodecModeForBits(0x02, Some(Type::Model2)).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());
|
||||
assert_eq!(
|
||||
Mode::BYTE,
|
||||
Mode::CodecModeForBits(0x04, Some(Type::Model2)).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
Mode::KANJI,
|
||||
Mode::CodecModeForBits(0x08, Some(Type::Model2)).unwrap()
|
||||
);
|
||||
assert!(Mode::CodecModeForBits(0x10, Some(Type::Model2)).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -56,53 +68,53 @@ fn MicroForBits() {
|
||||
// M1
|
||||
assert_eq!(
|
||||
Mode::NUMERIC,
|
||||
Mode::CodecModeForBits(0x00, Some(true)).unwrap()
|
||||
Mode::CodecModeForBits(0x00, Some(Type::Micro)).unwrap()
|
||||
);
|
||||
// M2
|
||||
assert_eq!(
|
||||
Mode::NUMERIC,
|
||||
Mode::CodecModeForBits(0x00, Some(true)).unwrap()
|
||||
Mode::CodecModeForBits(0x00, Some(Type::Micro)).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
Mode::ALPHANUMERIC,
|
||||
Mode::CodecModeForBits(0x01, Some(true)).unwrap()
|
||||
Mode::CodecModeForBits(0x01, Some(Type::Micro)).unwrap()
|
||||
);
|
||||
// M3
|
||||
assert_eq!(
|
||||
Mode::NUMERIC,
|
||||
Mode::CodecModeForBits(0x00, Some(true)).unwrap()
|
||||
Mode::CodecModeForBits(0x00, Some(Type::Micro)).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
Mode::ALPHANUMERIC,
|
||||
Mode::CodecModeForBits(0x01, Some(true)).unwrap()
|
||||
Mode::CodecModeForBits(0x01, Some(Type::Micro)).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
Mode::BYTE,
|
||||
Mode::CodecModeForBits(0x02, Some(true)).unwrap()
|
||||
Mode::CodecModeForBits(0x02, Some(Type::Micro)).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
Mode::KANJI,
|
||||
Mode::CodecModeForBits(0x03, Some(true)).unwrap()
|
||||
Mode::CodecModeForBits(0x03, Some(Type::Micro)).unwrap()
|
||||
);
|
||||
// M4
|
||||
assert_eq!(
|
||||
Mode::NUMERIC,
|
||||
Mode::CodecModeForBits(0x00, Some(true)).unwrap()
|
||||
Mode::CodecModeForBits(0x00, Some(Type::Micro)).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
Mode::ALPHANUMERIC,
|
||||
Mode::CodecModeForBits(0x01, Some(true)).unwrap()
|
||||
Mode::CodecModeForBits(0x01, Some(Type::Micro)).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
Mode::BYTE,
|
||||
Mode::CodecModeForBits(0x02, Some(true)).unwrap()
|
||||
Mode::CodecModeForBits(0x02, Some(Type::Micro)).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
Mode::KANJI,
|
||||
Mode::CodecModeForBits(0x03, Some(true)).unwrap()
|
||||
Mode::CodecModeForBits(0x03, Some(Type::Micro)).unwrap()
|
||||
);
|
||||
|
||||
assert!(Mode::CodecModeForBits(0x04, Some(true)).is_err());
|
||||
assert!(Mode::CodecModeForBits(0x04, Some(Type::Micro)).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -133,3 +145,87 @@ fn MicroCharacterCount() {
|
||||
Mode::CharacterCountBits(&Mode::KANJI, Version::Micro(4).unwrap())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn RMQRForBits() {
|
||||
assert_eq!(
|
||||
Mode::TERMINATOR,
|
||||
Mode::CodecModeForBits(0x00, Some(Type::RectMicro)).expect("could not decode 0x00")
|
||||
);
|
||||
assert_eq!(
|
||||
Mode::NUMERIC,
|
||||
Mode::CodecModeForBits(0x01, Some(Type::RectMicro)).expect("could not decode 0x01")
|
||||
);
|
||||
assert_eq!(
|
||||
Mode::ALPHANUMERIC,
|
||||
Mode::CodecModeForBits(0x02, Some(Type::RectMicro)).expect("could not decode 0x02")
|
||||
);
|
||||
assert_eq!(
|
||||
Mode::BYTE,
|
||||
Mode::CodecModeForBits(0x03, Some(Type::RectMicro)).expect("could not decode 0x03")
|
||||
);
|
||||
assert_eq!(
|
||||
Mode::KANJI,
|
||||
Mode::CodecModeForBits(0x04, Some(Type::RectMicro)).expect("could not decode 0x04")
|
||||
);
|
||||
assert_eq!(
|
||||
Mode::FNC1_FIRST_POSITION,
|
||||
Mode::CodecModeForBits(0x05, Some(Type::RectMicro)).expect("could not decode 0x05")
|
||||
);
|
||||
assert_eq!(
|
||||
Mode::FNC1_SECOND_POSITION,
|
||||
Mode::CodecModeForBits(0x06, Some(Type::RectMicro)).expect("could not decode 0x06")
|
||||
);
|
||||
assert_eq!(
|
||||
Mode::ECI,
|
||||
Mode::CodecModeForBits(0x07, Some(Type::RectMicro)).expect("could not decode 0x07")
|
||||
);
|
||||
assert!(Mode::CodecModeForBits(0x08, Some(Type::RectMicro)).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn RMQRCharacterCount() {
|
||||
// Spot check a few values
|
||||
assert_eq!(
|
||||
7,
|
||||
Mode::CharacterCountBits(
|
||||
&Mode::NUMERIC,
|
||||
Version::rMQR(5).expect("should return version")
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
8,
|
||||
Mode::CharacterCountBits(
|
||||
&Mode::NUMERIC,
|
||||
Version::rMQR(26).expect("should return version")
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
9,
|
||||
Mode::CharacterCountBits(
|
||||
&Mode::NUMERIC,
|
||||
Version::rMQR(32).expect("should return version")
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
5,
|
||||
Mode::CharacterCountBits(
|
||||
&Mode::ALPHANUMERIC,
|
||||
Version::rMQR(6).expect("should return version")
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
5,
|
||||
Mode::CharacterCountBits(
|
||||
&Mode::BYTE,
|
||||
Version::rMQR(7).expect("should return version")
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
5,
|
||||
Mode::CharacterCountBits(
|
||||
&Mode::KANJI,
|
||||
Version::rMQR(8).expect("should return version")
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -125,3 +125,130 @@ fn FunctionPattern() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn CheckRMQRVersion(version: VersionRef, number: u32) {
|
||||
assert_eq!(number, version.getVersionNumber());
|
||||
assert_eq!(
|
||||
Version::DimensionOfVersionRMQR(number).x == 27,
|
||||
version.getAlignmentPatternCenters().is_empty()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn RMQRVersionForNumber() {
|
||||
let version = Version::rMQR(0);
|
||||
assert!(version.is_err(), "There is version with number 0");
|
||||
|
||||
for i in 1..=32 {
|
||||
// for (int i = 1; i <= 32; i++) {
|
||||
CheckRMQRVersion(Version::rMQR(i).expect("version {i} should exist"), i);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn RMQRFunctionPattern1() {
|
||||
{
|
||||
let expected = BitMatrix::parse_strings(
|
||||
r"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
||||
XXXXXXXXXXXX XXX XXXXXXXX
|
||||
XXXXXXXXXXXX XXX XXXXXXXX
|
||||
XXXXXXXXXXXX X XXXXXXXX
|
||||
XXXXXXXXXXX XXX XXXXXXXX
|
||||
XXXXXXXXXXX XXX XXXXXXXX
|
||||
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
||||
",
|
||||
"X",
|
||||
" ",
|
||||
)
|
||||
.unwrap();
|
||||
let version = Version::rMQR(1).unwrap(); // R7x43
|
||||
let functionPattern = version.buildFunctionPattern().unwrap();
|
||||
assert_eq!(expected, functionPattern);
|
||||
}
|
||||
{
|
||||
let expected = BitMatrix::parse_strings(
|
||||
r"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
||||
XXXXXXXXXXXX XXX XX
|
||||
XXXXXXXXXXXX XXX X
|
||||
XXXXXXXXXXXX X XXXXXX X
|
||||
XXXXXXXXXXX X XXXXXXXX
|
||||
XXXXXXXXXXX X XXXXXXXX
|
||||
XXXXXXXX XXX XXXXXXXX
|
||||
XXXXXXXX XXX XXXXXXXX
|
||||
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
||||
",
|
||||
"X",
|
||||
" ",
|
||||
)
|
||||
.unwrap();
|
||||
let version = Version::rMQR(6).unwrap(); // R9x43
|
||||
let functionPattern = version.buildFunctionPattern().unwrap();
|
||||
assert_eq!(expected, functionPattern);
|
||||
}
|
||||
{
|
||||
let expected = BitMatrix::parse_strings(
|
||||
r"XXXXXXXXXXXXXXXXXXXXXXXXXXX
|
||||
XXXXXXXXXXXX XX
|
||||
XXXXXXXXXXXX X
|
||||
XXXXXXXXXXXX X
|
||||
XXXXXXXXXXX X
|
||||
XXXXXXXXXXX XXXXXX X
|
||||
XXXXXXXX XXXXXXXX
|
||||
XXXXXXXX XXXXXXXX
|
||||
X XXXXXXXX
|
||||
XX XXXXXXXX
|
||||
XXXXXXXXXXXXXXXXXXXXXXXXXXX
|
||||
",
|
||||
"X",
|
||||
" ",
|
||||
)
|
||||
.unwrap();
|
||||
let version = Version::rMQR(11).unwrap(); // R11x27
|
||||
let functionPattern = version.buildFunctionPattern().unwrap();
|
||||
assert_eq!(expected, functionPattern);
|
||||
}
|
||||
{
|
||||
let expected = BitMatrix::parse_strings(
|
||||
r"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
||||
XXXXXXXXXXXX XXX XX
|
||||
XXXXXXXXXXXX XXX X
|
||||
XXXXXXXXXXXX X X
|
||||
XXXXXXXXXXX X X
|
||||
XXXXXXXXXXX X XXXXXX X
|
||||
XXXXXXXX X XXXXXXXX
|
||||
XXXXXXXX X XXXXXXXX
|
||||
X XXX XXXXXXXX
|
||||
XX XXX XXXXXXXX
|
||||
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
||||
",
|
||||
"X",
|
||||
" ",
|
||||
)
|
||||
.unwrap();
|
||||
let version = Version::rMQR(12).unwrap(); // R11x43
|
||||
let functionPattern = version.buildFunctionPattern().unwrap();
|
||||
assert_eq!(expected, functionPattern);
|
||||
}
|
||||
{
|
||||
let expected = BitMatrix::parse_strings(
|
||||
"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
||||
XXXXXXXXXXXX XXX XXX XX
|
||||
XXXXXXXXXXXX XXX XXX X
|
||||
XXXXXXXXXXXX X X X
|
||||
XXXXXXXXXXX X X X
|
||||
XXXXXXXXXXX X X XXXXXX X
|
||||
XXXXXXXX X X XXXXXXXX
|
||||
XXXXXXXX X X XXXXXXXX
|
||||
X XXX XXX XXXXXXXX
|
||||
XX XXX XXX XXXXXXXX
|
||||
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
||||
",
|
||||
"X",
|
||||
" ",
|
||||
)
|
||||
.unwrap();
|
||||
let version = Version::rMQR(13).unwrap(); // R11x59
|
||||
let functionPattern = version.buildFunctionPattern().unwrap();
|
||||
assert_eq!(expected, functionPattern);
|
||||
}
|
||||
}
|
||||
|
||||
250
src/qrcode/cpp_port/test/RMQRDecoderTest.rs
Normal file
250
src/qrcode/cpp_port/test/RMQRDecoderTest.rs
Normal file
@@ -0,0 +1,250 @@
|
||||
/*
|
||||
* Copyright 2023 gitlost
|
||||
*/
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::{
|
||||
common::{BitMatrix, Eci},
|
||||
qrcode::cpp_port::decoder::Decode,
|
||||
Exceptions,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn RMQRCodeR7x43M() {
|
||||
let bitMatrix = BitMatrix::parse_strings(
|
||||
r"XXXXXXX X X X X X X XXX X X X X X X X X XXX
|
||||
X X X XXX XXXXX XXX X X XX X X
|
||||
X XXX X X XXX X X X XXXX XXXX X X XXXXXXXX
|
||||
X XXX X XX XXXXX XXXXXX X X X X
|
||||
X XXX X XX XXX XXXXXXX X X XX X X X
|
||||
X X XXXXX XXX XXX XXXXX XXXXXX X X
|
||||
XXXXXXX X X X X X X XXX X X X X X X X XXXXX
|
||||
",
|
||||
"X",
|
||||
" ",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let result = Decode(&bitMatrix).unwrap();
|
||||
// assert!(result.isValid());
|
||||
|
||||
assert_eq!(result.text(), "ABCDEFG");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn RMQRCodeR7x43MError6Bits() {
|
||||
let bitMatrix = BitMatrix::parse_strings(
|
||||
r"XXXXXXX X X X X X X XXX X X X X X X X X XXX
|
||||
X X X XXX XXXXX XXX X X XX X X
|
||||
X XXX X X XXX X X XXXX XXXX XX X XXXXXXXX
|
||||
X XXX X XX XXXXX X XXXXXX X X X X
|
||||
X XXX X XX XXX XXXXXXX X X XXX X X X
|
||||
X X XXXXX XXX XXX XXXX X XXXXXX X X
|
||||
XXXXXXX X X X X X X XXX X X X X X X X XXXXX
|
||||
",
|
||||
"X",
|
||||
" ",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let result = Decode(&bitMatrix);
|
||||
|
||||
assert!(matches!(
|
||||
result.err(),
|
||||
Some(Exceptions::ReedSolomonException(_))
|
||||
));
|
||||
// assert_eq!(Error::Checksum, result.error());
|
||||
// assert!(result.text().empty());
|
||||
// assert!(result.content().text(TextMode::Plain).empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn RMQRCodeR7x139H() {
|
||||
let bitMatrix = BitMatrix::parse_strings(
|
||||
r"XXXXXXX X X X X X X X X X XXX X X X X X X X X X X X X XXX X X X X X X X X X X X X XXX X X X X X X X X X X X X XXX X X X X X X X X X X X XXX
|
||||
X X XX XXX X X X X X XX XX X X X XXX XX XXXX XXX XX XX XX X XX X X X XXX X XX XX XX X X XX X XX XXXX X X X
|
||||
X XXX X X XXXXX X XXXXX X X XXX XX X XXX X XX XXX XX X XXX X X XXXX X XXXXXXX X XX XXX X X X XXX X XXXXX
|
||||
X XXX X XXXX X XX X X XX XX X XX XX X XXX XX X XX X XX X X XX X X XXX X X X X X X X XX X XX XX X X X
|
||||
X XXX X XXXX XXXXX X X XXXXXX XX X XXXX X XXXX X XXX XXXX X XXXXXXX XXX XXXXXX X X XX X XXX X XXXXXXXXX X XXXX X X X X X
|
||||
X X X XX XX X X XX X X X XXXX X X X XX X XXX X X X X X XXX XX XXX X X XX XXXX XX X X X X XXXXX XXX XX X XX X
|
||||
XXXXXXX X X X X X X X X X XXX X X X X X X X X X X X X XXX X X X X X X X X X X X X XXX X X X X X X X X X X X X XXX X X X X X X X X X X XXXXX
|
||||
",
|
||||
"X",
|
||||
" ",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let result = Decode(&bitMatrix).unwrap();
|
||||
// assert!(result.isValid());
|
||||
assert_eq!(result.text(), "1234567890,ABCDEFGHIJKLMOPQRSTUVW");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn RMQRCodeR9x59H() {
|
||||
let bitMatrix = BitMatrix::parse_strings(
|
||||
r"XXXXXXX X X X X X XXX X X X X X X X X XXX X X X X X X X XXX
|
||||
X X X XXXXX XXX X X XXXXXXXX X X X X XXXX X X
|
||||
X XXX X XX XXX X XXX XXXX X XXXXXXX X XXXXX X X
|
||||
X XXX X XXXX X XX X XX XXXX XX XX X X X XXX X
|
||||
X XXX X X X XX XXXXXX X X XX X XX X X XXXX XXXXX
|
||||
X X X X X X XXX X X X XX X XXXX XX X X X X
|
||||
XXXXXXX XXXXX XXXXXX X XX XXX X XXXX X X X XX X X
|
||||
XXX XXXX XX XXX X XXXXXXX X XX XXX XX XX X
|
||||
XXX X X X X X X X XXX X X X X X X X X XXX X X X X X X XXXXX
|
||||
",
|
||||
"X",
|
||||
" ",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let result = Decode(&bitMatrix).unwrap();
|
||||
// assert!(result.isValid());
|
||||
assert_eq!(result.text(), "ABCDEFGHIJKLMN");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn RMQRCodeR9x77M() {
|
||||
let bitMatrix = BitMatrix::parse_strings(
|
||||
r"XXXXXXX X X X X X X X X XXX X X X X X X X X X X X XXX X X X X X X X X X X XXX
|
||||
X X XXX XX XXX XXX XXXX XXX XX X XXXXXXXXX X XXX XXXX X XXXX XX XXX X
|
||||
X XXX X X X X XXX X XXXX XX XX X XX XX XXX XXXX X X XX X X XX X
|
||||
X XXX X X X XXXXXX X XX XXXX X XXX X XX X XX XX XX X XXX X X XXX XX
|
||||
X XXX X XXXX X X XXXX XXXX XX XXX X XX XXXXXX X X XXX XX XXXXX
|
||||
X X X X XX XXX X X XX X X XX XXX X X X X X XX XXXXX X
|
||||
XXXXXXX X XX XX X XXXX X X X X X XX XXX X XX X XXX XX X X
|
||||
X XXXXX XX X XXXXXX XX XXXXX X XX XX XXXXX XXX X
|
||||
XXX X X X X X X X X X X XXX X X X X X X X X X X X XXX X X X X X X X X X XXXXX
|
||||
",
|
||||
"X",
|
||||
" ",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let result = Decode(&bitMatrix).unwrap();
|
||||
// assert!(result.isValid());
|
||||
assert_eq!(result.text(), "__ABCDEFGH__1234567890___ABCDEFGHIJK");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn RMQRCodeR11x27H() {
|
||||
let bitMatrix = BitMatrix::parse_strings(
|
||||
r"XXXXXXX X X X X X X X X XXX
|
||||
X X XX X X X X
|
||||
X XXX X X XX X X XX
|
||||
X XXX X XXXX XX X XXXXXX
|
||||
X XXX X X X XX XX XXX X
|
||||
X X XXX X XX XXXX X
|
||||
XXXXXXX X XX X XXXXX
|
||||
X X X X X
|
||||
XXXX X X X XX XXXXXX X X
|
||||
X XX XXXXXX XXX XXXX X X
|
||||
XXX X X X X X X X X X XXXXX
|
||||
",
|
||||
"X",
|
||||
" ",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let result = Decode(&bitMatrix).unwrap();
|
||||
// assert!(result.isValid());
|
||||
assert_eq!(result.text(), "ABCDEF");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn RMQRCodeR13x27M_ECI() {
|
||||
let bitMatrix = BitMatrix::parse_strings(
|
||||
r"XXXXXXX X X X X X X X X XXX
|
||||
X X XX XX XXX XX X
|
||||
X XXX X XX X XX XX XXX X
|
||||
X XXX X XX X XX X X XX
|
||||
X XXX X XXXXXXX X X XX
|
||||
X X XX X XXX XX XX
|
||||
XXXXXXX X X X X XXX
|
||||
XXX XX X XX XXX
|
||||
XXX XX XX X X XX XX XXXXX
|
||||
XXX X X X X X X
|
||||
X XX X X XX X XX X X X X
|
||||
X X X X X X X X X
|
||||
XXX X X X X X X X X X XXXXX
|
||||
",
|
||||
"X",
|
||||
" ",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let result = Decode(&bitMatrix).unwrap();
|
||||
// assert!(result.isValid());
|
||||
assert_eq!(result.text(), "AB貫12345AB");
|
||||
assert!(result.content().has_eci);
|
||||
assert_eq!(result.content().eci_positions[0].0, Eci::Shift_JIS);
|
||||
// assert_eq!(result.symbologyIdentifier(), "]Q2"); // Shouldn't this be Q1 TODO: Fix
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn RMQRCodeR15x59H_GS1() {
|
||||
let bitMatrix = BitMatrix::parse_strings(
|
||||
r"XXXXXXX X X X X X XXX X X X X X X X X XXX X X X X X X X XXX
|
||||
X X XXX XXX X XXXXX XX XXX X X X X X X XXX X
|
||||
X XXX X XXX XX X XXX XXX X X XXX XXXXX XX XXX XX
|
||||
X XXX X X X XX X X XXX X X X XXXXX XX XXX
|
||||
X XXX X XX XXX XX X X X XX XX XX XXX XXXX X XXXX
|
||||
X X X X X X X XXX XXX XXXX X XXX XX X X
|
||||
XXXXXXX X XXX XXXX X XX XXXX X X XX XXX XXXXX X
|
||||
X XXX X XXXXX X XX XXXX XX X
|
||||
XX XX X X X XXXXX XX X X XX XX X XX X X XX X
|
||||
XX XX X XXXXXX XXX XX X X XX XXX X X XXX
|
||||
X X XX XXXXXXXXXX XX X X XX XX XX X XXXX XX XXXXXX
|
||||
XX X XX X XXX X X X XXX X XXX X X XXX XXXX X
|
||||
XXXX X X XX XXX X X X XX XXXXX XX X XX XXX X X
|
||||
X X X XX XXX XXXXXXX XXX X XXX XX X X X XX X
|
||||
XXX X X X X X X X XXX X X X X X X X X XXX X X X X X X XXXXX
|
||||
",
|
||||
"X",
|
||||
" ",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let result = Decode(&bitMatrix).unwrap();
|
||||
// assert!(result.isValid());
|
||||
// assert!(result.content().type() == ContentType::GS1);
|
||||
// assert_eq!(
|
||||
// result.text(),
|
||||
// "(01)09524000059109(21)12345678p901(10)1234567p(17)231120"
|
||||
// );
|
||||
// TODO: Right now we aren't properly handling the parsing of this, but we can just check that
|
||||
// the data comes back as valid, it's not perfect, but it works ok.
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn RMQRCodeR17x99H() {
|
||||
let bitMatrix = BitMatrix::parse_strings(
|
||||
r"XXXXXXX X X X X X X X XXX X X X X X X X X X X X XXX X X X X X X X X X X X XXX X X X X X X X X X XXX
|
||||
X X X XXXXX XXX X X X XX X X XX XXXXX X XX X XX XXX X X XX X X XXX X X XX X X X
|
||||
X XXX X X X XXX XXX X XXX XXX X X XX XXXX X X X X XXX XXXXX X X XX X XX X X
|
||||
X XXX X XX X XX X X XX X XXXX X XXXXX X X XX X XXX XX X X X X XXXXXX X
|
||||
X XXX X X XX X X X X X X X X XXX XX XXXXXX X X XXX X XXXXXX X X X X X X X XX X
|
||||
X X XX X X XXXXX XX X XXX X XX X X XXX X XXX XXX X XXXX XX X X X XX XXXX
|
||||
XXXXXXX X XX X XX X X XXX XX X XXXX X X XXX X X XX X XXXX XX X X X XX X XXXX
|
||||
XX XX XX XX X XX X X X XXX XX X X XXX XXXX XX X X X X XX XX XXX
|
||||
XX X XXX X X XXXX XXX XXXXX XXX XXX X X X X X XXX X XX XX X X X X XX X XXX
|
||||
X XXXXX X X XXXXX X XX X XX XXXX X X XXXXX X XX X XX X XX X XX XX
|
||||
X XX XX X XX XXX XX XXXXXX X XXXXX XX XXXX X X X X XXXX XX X X XXXXXX XX X X
|
||||
XXX XX XXX XX XX X X X XX X X X X XX XXX XXXX X XX XXX X X X XXXX XXXXX X XXX
|
||||
X X XX X XX XX XX X X XX X X X XX XXXXXXXX X XX XX X X X X X XX X X XXXXXXXXXXX
|
||||
X X X XX X X X XX XXXX X XXX X XX X X X X X XXX XXXXX XX X X X XXXXX X X X
|
||||
XXXX XX XX X XXXX XXXX X XX X XX XX XX XXXX XXX X X XX XX X XXXX X XXX XX X XX X X
|
||||
X XXX XX XXX X X X XXX X XXX X XXXX XX X X XXXXX X XX X X X X X X X X XXXX XXXX X
|
||||
XXX X X X X X X X X X XXX X X X X X X X X X X X XXX X X X X X X X X X X X XXX X X X X X X X X XXXXX
|
||||
",
|
||||
"X",
|
||||
" ",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let result = Decode(&bitMatrix).unwrap();
|
||||
// assert!(result.isValid());
|
||||
assert_eq!(
|
||||
result.text(),
|
||||
"1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890________________________"
|
||||
);
|
||||
}
|
||||
@@ -12,3 +12,5 @@ mod QRDataMaskTest;
|
||||
mod QRBitMatrixParserTest;
|
||||
|
||||
mod MQRDecoderTest;
|
||||
|
||||
mod RMQRDecoderTest;
|
||||
|
||||
@@ -75,9 +75,10 @@ pub struct FormatInformation {
|
||||
pub microVersion: u32,
|
||||
pub isMirrored: bool,
|
||||
|
||||
pub mask: u32, // = 0
|
||||
pub data: u32, // = 255
|
||||
pub bitsIndex: u8, // = 255;
|
||||
pub mask: u32, // = 0
|
||||
pub data: u32, // = 255
|
||||
pub bitsIndex: u8, // = 255;
|
||||
pub rMQRVersion: u8, //= 0;
|
||||
}
|
||||
|
||||
impl Default for FormatInformation {
|
||||
@@ -91,6 +92,7 @@ impl Default for FormatInformation {
|
||||
mask: 0,
|
||||
data: 255,
|
||||
bitsIndex: 255,
|
||||
rMQRVersion: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -110,6 +112,7 @@ impl FormatInformation {
|
||||
mask: 0,
|
||||
bitsIndex: 255,
|
||||
data: 255,
|
||||
rMQRVersion: 0,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ mod mode;
|
||||
mod qr_code_decoder_meta_data;
|
||||
pub mod qrcode_decoder;
|
||||
mod version;
|
||||
mod version_build_versions_arrays;
|
||||
|
||||
#[cfg(test)]
|
||||
mod DecodedBitStreamParserTestCase;
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
use crate::common::Result;
|
||||
use crate::qrcode::cpp_port::Type;
|
||||
use crate::Exceptions;
|
||||
|
||||
use super::Version;
|
||||
@@ -82,6 +83,7 @@ impl Mode {
|
||||
*/
|
||||
pub fn getCharacterCountBits(&self, version: &Version) -> u8 {
|
||||
let number = version.getVersionNumber();
|
||||
|
||||
let offset = if number <= 9 {
|
||||
0
|
||||
} else if number <= 26 {
|
||||
@@ -122,18 +124,19 @@ impl Mode {
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn get_terminator_bit_length(version: &Version) -> u8 {
|
||||
pub fn get_terminator_bit_length(version: &Version) -> u8 {
|
||||
(if version.isMicro() {
|
||||
version.getVersionNumber() * 2 + 1
|
||||
} else {
|
||||
4
|
||||
4 - u32::from(version.isRMQR())
|
||||
}) as u8
|
||||
}
|
||||
pub const fn get_codec_mode_bits_length(version: &Version) -> u8 {
|
||||
|
||||
pub fn get_codec_mode_bits_length(version: &Version) -> u8 {
|
||||
(if version.isMicro() {
|
||||
version.getVersionNumber() - 1
|
||||
} else {
|
||||
4
|
||||
4 - u32::from(version.isRMQR())
|
||||
}) as u8
|
||||
}
|
||||
/**
|
||||
@@ -142,19 +145,33 @@ impl Mode {
|
||||
* @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;
|
||||
pub fn CodecModeForBits(bits: u32, qr_type: Option<Type>) -> Result<Self> {
|
||||
let qr_type = qr_type.unwrap_or(Type::Model2);
|
||||
let bits = bits as usize;
|
||||
|
||||
if !isMicro {
|
||||
if (0x00..=0x05).contains(&bits) || (0x07..=0x09).contains(&bits) || bits == 0x0d {
|
||||
return Mode::try_from(bits);
|
||||
if (qr_type == Type::Micro) {
|
||||
const Bits2Mode: [Mode; 4] =
|
||||
[Mode::NUMERIC, Mode::ALPHANUMERIC, Mode::BYTE, Mode::KANJI];
|
||||
if (bits < (Bits2Mode.len())) {
|
||||
return Ok(Bits2Mode[bits]);
|
||||
}
|
||||
} else if (qr_type == Type::RectMicro) {
|
||||
const Bits2Mode: [Mode; 8] = [
|
||||
Mode::TERMINATOR,
|
||||
Mode::NUMERIC,
|
||||
Mode::ALPHANUMERIC,
|
||||
Mode::BYTE,
|
||||
Mode::KANJI,
|
||||
Mode::FNC1_FIRST_POSITION,
|
||||
Mode::FNC1_SECOND_POSITION,
|
||||
Mode::ECI,
|
||||
];
|
||||
if (bits < (Bits2Mode.len())) {
|
||||
return Ok(Bits2Mode[bits]);
|
||||
}
|
||||
} else {
|
||||
const BITS_2_MODE: [Mode; BITS_2_MODE_LEN] =
|
||||
[Mode::NUMERIC, Mode::ALPHANUMERIC, Mode::BYTE, Mode::KANJI];
|
||||
if (bits as usize) < BITS_2_MODE_LEN {
|
||||
return Ok(BITS_2_MODE[bits as usize]);
|
||||
if ((bits >= 0x00 && bits <= 0x05) || (bits >= 0x07 && bits <= 0x09) || bits == 0x0d) {
|
||||
return Mode::try_from(bits as u32);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,6 +196,33 @@ impl Mode {
|
||||
}
|
||||
}
|
||||
|
||||
if (version.isRMQR()) {
|
||||
// See ISO/IEC 23941:2022 7.4.1, Table 3 - Number of bits of character count indicator
|
||||
const numeric: [u32; 32] = [
|
||||
4, 5, 6, 7, 7, 5, 6, 7, 7, 8, 4, 6, 7, 7, 8, 8, 5, 6, 7, 7, 8, 8, 7, 7, 8, 8, 9, 7,
|
||||
8, 8, 8, 9,
|
||||
];
|
||||
const alphanum: [u32; 32] = [
|
||||
3, 5, 5, 6, 6, 5, 5, 6, 6, 7, 4, 5, 6, 6, 7, 7, 5, 6, 6, 7, 7, 8, 6, 7, 7, 7, 8, 6,
|
||||
7, 7, 8, 8,
|
||||
];
|
||||
const byte: [u32; 32] = [
|
||||
3, 4, 5, 5, 6, 4, 5, 5, 6, 6, 3, 5, 5, 6, 6, 7, 4, 5, 6, 6, 7, 7, 6, 6, 7, 7, 7, 6,
|
||||
6, 7, 7, 8,
|
||||
];
|
||||
const kanji: [u32; 32] = [
|
||||
2, 3, 4, 5, 5, 3, 4, 5, 5, 6, 2, 4, 5, 5, 6, 6, 3, 5, 5, 6, 6, 7, 5, 5, 6, 6, 7, 5,
|
||||
6, 6, 6, 7,
|
||||
];
|
||||
match (self) {
|
||||
Mode::NUMERIC => return numeric[number - 1],
|
||||
Mode::ALPHANUMERIC => return alphanum[number - 1],
|
||||
Mode::BYTE => return byte[number - 1],
|
||||
Mode::KANJI => return kanji[number - 1],
|
||||
_ => return 0,
|
||||
}
|
||||
}
|
||||
|
||||
let i = if number <= 9 {
|
||||
0
|
||||
} else if number <= 26 {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
1547
src/qrcode/decoder/version_build_versions_arrays.rs
Normal file
1547
src/qrcode/decoder/version_build_versions_arrays.rs
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user