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 415001f622
commit 86689a87d9
29 changed files with 2876 additions and 1093 deletions

View File

@@ -636,7 +636,7 @@ impl BitMatrix {
self.width() self.width()
} }
pub fn width(&self) -> u32 { pub const fn width(&self) -> u32 {
self.width self.width
} }
@@ -647,7 +647,7 @@ impl BitMatrix {
self.height() self.height()
} }
pub fn height(&self) -> u32 { pub const fn height(&self) -> u32 {
self.height 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_QR_MODEL1: u32 = 0x2825;
pub const FORMAT_INFO_MASK_MICRO: u32 = 0x4445; 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] = [ // pub const FORMAT_INFO_DECODE_LOOKUP_MICRO: [u32 ;32] = [
// 0x4445, // 0x4445,
@@ -94,6 +96,33 @@ impl FormatInformation {
fi 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)] #[inline(always)]
pub fn MirrorBits(bits: u32) -> u32 { pub fn MirrorBits(bits: u32) -> u32 {
(bits.reverse_bits()) >> 17 (bits.reverse_bits()) >> 17
@@ -153,16 +182,73 @@ impl FormatInformation {
fi 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 { match self.mask {
FORMAT_INFO_MASK_QR_MODEL1 => Type::Model1, FORMAT_INFO_MASK_QR_MODEL1 => Type::Model1,
FORMAT_INFO_MASK_MICRO => Type::Micro, FORMAT_INFO_MASK_MICRO => Type::Micro,
FORMAT_INFO_MASK_RMQR | FORMAT_INFO_MASK_RMQR_SUB => Type::RectMicro,
_ => Type::Model2, _ => Type::Model2,
} }
} }
// Hamming distance of the 32 masked codes is 7, by construction, so <= 3 bits differing means we found a match // 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 fn isValid(&self) -> bool { pub const fn isValid(&self) -> bool {
self.hammingDistance <= 3 self.hammingDistance <= 3
} }

View File

@@ -8,9 +8,45 @@
use crate::common::{BitMatrix, Result}; use crate::common::{BitMatrix, Result};
use crate::qrcode::cpp_port::Type; use crate::qrcode::cpp_port::Type;
use crate::qrcode::decoder::{ 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 { impl Version {
pub fn Model1(version_number: u32) -> Result<VersionRef> { 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 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 { match is_micro {
true => 9, true => 9,
false => 17, false => 17,
@@ -49,7 +94,7 @@ impl Version {
// return std::array{17, 9}[isMicro]; // return std::array{17, 9}[isMicro];
} }
pub fn DimensionStep(is_micro: bool) -> u32 { pub const fn DimensionStep(is_micro: bool) -> u32 {
match is_micro { match is_micro {
true => 2, true => 2,
false => 4, false => 4,
@@ -93,22 +138,65 @@ impl Version {
Type::const_eq(self.qr_type, Type::Model2) 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 { pub fn HasMicroSize(bitMatrix: &BitMatrix) -> bool {
let size = bitMatrix.height(); 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 { pub fn HasValidSize(bitMatrix: &BitMatrix) -> bool {
let size = bitMatrix.height(); let size = bitMatrix.height();
if bitMatrix.width() != size {
Self::HasRMQRSize(bitMatrix)
} else {
Self::HasMicroSize(bitMatrix) || ((21..=177).contains(&size) && (size % 4) == 1) Self::HasMicroSize(bitMatrix) || ((21..=177).contains(&size) && (size % 4) == 1)
} }
}
pub fn Number(bitMatrix: &BitMatrix) -> u32 { 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 0
} else { } else {
let isMicro = Self::HasMicroSize(bitMatrix); let isMicro = Self::HasMicroSize(bitMatrix);
(bitMatrix.height() - Self::DimensionOffset(isMicro)) / Self::DimensionStep(isMicro) (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, 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) pub(crate) eci_positions: Vec<(Eci, usize, usize)>, // (Eci, start, end)
pub symbology: SymbologyIdentifier, pub symbology: SymbologyIdentifier,
eci_list: HashSet<Eci>, eci_list: HashSet<Eci>,
} }

View File

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

View File

@@ -32,6 +32,7 @@ pub fn ReadVersion(bitMatrix: &BitMatrix, qr_type: Type) -> Result<VersionRef> {
Type::Model1 => Version::Model1(number), Type::Model1 => Version::Model1(number),
Type::Micro => Version::Micro(number), Type::Micro => Version::Micro(number),
Type::Model2 => Version::Model2(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)); 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 // Read top-left format info bits
let mut formatInfoBits1 = 0; let mut formatInfoBits1 = 0;
for x in 0..6 { for x in 0..6 {
@@ -311,6 +353,59 @@ pub fn ReadQRCodewordsModel1(
Ok(result.iter().copied().map(|x| x as u8).collect()) 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( pub fn ReadCodewords(
bitMatrix: &BitMatrix, bitMatrix: &BitMatrix,
version: VersionRef, version: VersionRef,
@@ -320,5 +415,6 @@ pub fn ReadCodewords(
Type::Model1 => ReadQRCodewordsModel1(bitMatrix, version, formatInfo), Type::Model1 => ReadQRCodewordsModel1(bitMatrix, version, formatInfo),
Type::Model2 => ReadQRCodewords(bitMatrix, version, formatInfo), Type::Model2 => ReadQRCodewords(bitMatrix, version, formatInfo),
Type::Micro => ReadMQRCodewords(bitMatrix, version, formatInfo), Type::Micro => ReadMQRCodewords(bitMatrix, version, formatInfo),
Type::RectMicro => ReadRMQRCodewords(bitMatrix, version, formatInfo),
} }
} }

View File

@@ -310,7 +310,7 @@ pub fn DecodeBitStream(
} else { } else {
Mode::CodecModeForBits( Mode::CodecModeForBits(
bits.readBits(modeBitLength as usize)?, bits.readBits(modeBitLength as usize)?,
Some(version.isMicro()), Some(version.qr_type),
)? )?
}; };

View File

@@ -5,14 +5,15 @@ use crate::{
}, },
DefaultGridSampler, GridSampler, Result, SamplerControl, DefaultGridSampler, GridSampler, Result, SamplerControl,
}, },
point_i, point, point_i,
qrcode::{ qrcode::{
decoder::{FormatInformation, Version, VersionRef}, decoder::{FormatInformation, Version, VersionRef},
detector::QRCodeDetectorResult, detector::QRCodeDetectorResult,
}, },
Exceptions, Exceptions, PointF,
}; };
use multimap::MultiMap; use multimap::MultiMap;
use num::Integer;
use crate::{ use crate::{
common::{ common::{
@@ -23,7 +24,7 @@ use crate::{
}, },
BitMatrix, PerspectiveTransform, Quadrilateral, BitMatrix, PerspectiveTransform, Quadrilateral,
}, },
point_f, Point, point_f, Point, PointI,
}; };
#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
@@ -39,6 +40,8 @@ pub type FinderPatternSets = Vec<FinderPatternSet>;
const LEN: usize = 5; const LEN: usize = 5;
const SUM: usize = 7; const SUM: usize = 7;
const PATTERN: FixedPattern<LEN, SUM, false> = FixedPattern::new([1, 1, 3, 1, 1]); 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; const E2E: bool = true;
fn FindPattern(view: PatternView<'_>) -> Result<PatternView<'_>> { fn FindPattern(view: PatternView<'_>) -> Result<PatternView<'_>> {
@@ -885,8 +888,8 @@ pub fn DetectPureQR(image: &BitMatrix) -> Result<QRCodeDetectorResult> {
pub fn DetectPureMQR(image: &BitMatrix) -> Result<QRCodeDetectorResult> { pub fn DetectPureMQR(image: &BitMatrix) -> Result<QRCodeDetectorResult> {
type Pattern = [PatternType; 5]; type Pattern = [PatternType; 5];
let MIN_MODULES = Version::DimensionOfVersion(1, true); const MIN_MODULES: u32 = Version::DimensionOfVersion(1, true);
let MAX_MODULES = Version::DimensionOfVersion(4, true); const MAX_MODULES: u32 = Version::DimensionOfVersion(4, true);
let (found, left, top, width, height) = image.findBoundingBox(0, 0, 0, 0, MIN_MODULES); 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}}}; // {{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> { pub fn SampleMQR(image: &BitMatrix, fp: ConcentricPattern) -> Result<QRCodeDetectorResult> {
let Some(fpQuad) = FindConcentricPatternCorners(image, fp.p, fp.size, 2) else { let Some(fpQuad) = FindConcentricPatternCorners(image, fp.p, fp.size, 2) else {
return Err(Exceptions::NOT_FOUND); return Err(Exceptions::NOT_FOUND);
@@ -1057,3 +1193,91 @@ pub fn SampleMQR(image: &BitMatrix, fp: ConcentricPattern) -> Result<QRCodeDetec
// SampleGrid(image, dim, dim, bestPT) // 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)
}

View File

@@ -127,8 +127,8 @@ use crate::{
use super::{ use super::{
decoder::Decode, decoder::Decode,
detector::{ detector::{
DetectPureMQR, DetectPureQR, FindFinderPatterns, GenerateFinderPatternSets, SampleMQR, DetectPureMQR, DetectPureQR, DetectPureRMQR, FindFinderPatterns, GenerateFinderPatternSets,
SampleQR, SampleMQR, SampleQR, SampleRMQR,
}, },
}; };
@@ -179,10 +179,15 @@ impl Reader for QrReader {
if formats.contains(&BarcodeFormat::MICRO_QR_CODE) && detectorResult.is_err() { if formats.contains(&BarcodeFormat::MICRO_QR_CODE) && detectorResult.is_err() {
detectorResult = DetectPureMQR(binImg); detectorResult = DetectPureMQR(binImg);
} }
if formats.contains(&BarcodeFormat::RECTANGULAR_MICRO_QR_CODE)
&& detectorResult.is_err()
{
detectorResult = DetectPureRMQR(binImg);
}
} }
if detectorResult.is_err() { if detectorResult.is_err() {
for decode_function in [DetectPureQR, DetectPureMQR] { for decode_function in [DetectPureQR, DetectPureMQR, DetectPureRMQR] {
detectorResult = decode_function(binImg); detectorResult = decode_function(binImg);
if detectorResult.is_ok() { if detectorResult.is_ok() {
break; break;
@@ -208,25 +213,16 @@ impl Reader for QrReader {
Ok(RXingResult::with_decoder_result( Ok(RXingResult::with_decoder_result(
decoderResult, decoderResult,
position, position,
if detectorResult.getBits().width() != detectorResult.getBits().height() {
BarcodeFormat::RECTANGULAR_MICRO_QR_CODE
} else {
if detectorResult.getBits().width() < 21 { if detectorResult.getBits().width() < 21 {
BarcodeFormat::MICRO_QR_CODE BarcodeFormat::MICRO_QR_CODE
} else { } else {
BarcodeFormat::QR_CODE 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,15 +272,17 @@ impl QrReader {
let mut usedFPs: Vec<ConcentricPattern> = Vec::new(); let mut usedFPs: Vec<ConcentricPattern> = Vec::new();
let mut results: Vec<RXingResult> = Vec::new(); let mut results: Vec<RXingResult> = Vec::new();
let (check_qr, check_mqr) = if let Some(DecodeHintValue::PossibleFormats(formats)) = let (check_qr, check_mqr, check_rmqr) =
if let Some(DecodeHintValue::PossibleFormats(formats)) =
hints.get(&DecodeHintType::POSSIBLE_FORMATS) hints.get(&DecodeHintType::POSSIBLE_FORMATS)
{ {
( (
formats.contains(&BarcodeFormat::QR_CODE), formats.contains(&BarcodeFormat::QR_CODE),
formats.contains(&BarcodeFormat::MICRO_QR_CODE), formats.contains(&BarcodeFormat::MICRO_QR_CODE),
formats.contains(&BarcodeFormat::RECTANGULAR_MICRO_QR_CODE),
) )
} else { } else {
(true, true) (true, true, true)
}; };
if check_qr { if check_qr {
@@ -314,18 +312,11 @@ impl QrReader {
} }
if decoderResult.isValid() { 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( results.push(RXingResult::with_decoder_result(
decoderResult, decoderResult,
position, position,
BarcodeFormat::QR_CODE, BarcodeFormat::QR_CODE,
)); ));
// results.emplace_back(std::move(decoderResult), std::move(position), BarcodeFormat::QR_CODE);
if maxSymbols != 0 && (results.len() as u32) == maxSymbols { if maxSymbols != 0 && (results.len() as u32) == maxSymbols {
break; break;
@@ -337,13 +328,13 @@ impl QrReader {
} }
if check_mqr && !(maxSymbols != 0 && (results.len() as u32) == maxSymbols) { if check_mqr && !(maxSymbols != 0 && (results.len() as u32) == maxSymbols) {
// if (_hints.hasFormat(BarcodeFormat::MicroQRCode) && !(maxSymbols && Size(results) == maxSymbols)) { // if (_hints.hasFormat(BarcodeFormat::MicroQRCode) && !(maxSymbols && Size(results) == maxSymbols)) {
for fp in allFPs { for fp in &allFPs {
// for (const auto& fp : allFPs) { // for (const auto& fp : allFPs) {
if usedFPs.contains(&fp) { if usedFPs.contains(fp) {
continue; continue;
} }
let detectorResult = SampleMQR(binImg, fp); let detectorResult = SampleMQR(binImg, *fp);
if let Ok(detectorResult) = detectorResult { if let Ok(detectorResult) = detectorResult {
// if (detectorResult.is_ok()) { // if (detectorResult.is_ok()) {
let decoderResult = Decode(detectorResult.getBits()); let decoderResult = Decode(detectorResult.getBits());
@@ -355,13 +346,34 @@ impl QrReader {
position, position,
BarcodeFormat::MICRO_QR_CODE, BarcodeFormat::MICRO_QR_CODE,
)); ));
// results.push(RXingResult::new(
// &decoderResult.content().to_string(), if maxSymbols != 0 && (results.len() as u32) == maxSymbols {
// decoderResult.content().bytes().to_vec(), break;
// position.to_vec(), }
// BarcodeFormat::MICRO_QR_CODE, }
// )); }
// results.emplace_back(std::move(decoderResult), std::move(position), BarcodeFormat::MICRO_QR_CODE); }
}
}
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 { if maxSymbols != 0 && (results.len() as u32) == maxSymbols {
break; break;

View File

@@ -3,6 +3,7 @@ pub enum Type {
Model1, Model1,
Model2, Model2,
Micro, Micro,
RectMicro,
} }
impl Type { impl Type {

View File

@@ -15,6 +15,13 @@ const MASKED_TEST_FORMAT_INFO2: u32 =
const UNMASKED_TEST_FORMAT_INFO: u32 = MASKED_TEST_FORMAT_INFO ^ 0x5412; const UNMASKED_TEST_FORMAT_INFO: u32 = MASKED_TEST_FORMAT_INFO ^ 0x5412;
const MICRO_MASKED_TEST_FORMAT_INFO: u32 = 0x3BBA; const MICRO_MASKED_TEST_FORMAT_INFO: u32 = 0x3BBA;
// const MICRO_UNMASKED_TEST_FORMAT_INFO: u32 = MICRO_MASKED_TEST_FORMAT_INFO ^ 0x4445; // 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) { fn DoFormatInformationTest(formatInfo: u32, expectedMask: u8, expectedECL: ErrorCorrectionLevel) {
let parsedFormat = FormatInformation::DecodeMQR(formatInfo); let parsedFormat = FormatInformation::DecodeMQR(formatInfo);
@@ -23,6 +30,27 @@ fn DoFormatInformationTest(formatInfo: u32, expectedMask: u8, expectedECL: Error
assert_eq!(expectedECL, parsedFormat.error_correction_level); 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] #[test]
fn Decode() { fn Decode() {
// Normal case // Normal case
@@ -131,6 +159,85 @@ fn DecodeMicroWithBitDifference() {
// FormatInformation::DecodeFormatInformation(MICRO_MASKED_TEST_FORMAT_INFO ^ 0x3f).errorCorrectionLevel()); // FormatInformation::DecodeFormatInformation(MICRO_MASKED_TEST_FORMAT_INFO ^ 0x3f).errorCorrectionLevel());
} }
fn cpp_eq(rhs: &FormatInformation, lhs: &FormatInformation) { #[test]
assert!(rhs.cpp_eq(lhs)) 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);
}
} }

View File

@@ -4,22 +4,34 @@
*/ */
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
use crate::qrcode::decoder::{Mode, Version}; use crate::qrcode::{
cpp_port::Type,
decoder::{Mode, Version},
};
#[test] #[test]
fn ForBits() { fn ForBits() {
assert_eq!( assert_eq!(
Mode::TERMINATOR, 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!( assert_eq!(
Mode::ALPHANUMERIC, 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!(
assert_eq!(Mode::KANJI, Mode::CodecModeForBits(0x08, None).unwrap()); Mode::BYTE,
assert!(Mode::CodecModeForBits(0x10, None).is_err()); 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] #[test]
@@ -56,53 +68,53 @@ fn MicroForBits() {
// M1 // M1
assert_eq!( assert_eq!(
Mode::NUMERIC, Mode::NUMERIC,
Mode::CodecModeForBits(0x00, Some(true)).unwrap() Mode::CodecModeForBits(0x00, Some(Type::Micro)).unwrap()
); );
// M2 // M2
assert_eq!( assert_eq!(
Mode::NUMERIC, Mode::NUMERIC,
Mode::CodecModeForBits(0x00, Some(true)).unwrap() Mode::CodecModeForBits(0x00, Some(Type::Micro)).unwrap()
); );
assert_eq!( assert_eq!(
Mode::ALPHANUMERIC, Mode::ALPHANUMERIC,
Mode::CodecModeForBits(0x01, Some(true)).unwrap() Mode::CodecModeForBits(0x01, Some(Type::Micro)).unwrap()
); );
// M3 // M3
assert_eq!( assert_eq!(
Mode::NUMERIC, Mode::NUMERIC,
Mode::CodecModeForBits(0x00, Some(true)).unwrap() Mode::CodecModeForBits(0x00, Some(Type::Micro)).unwrap()
); );
assert_eq!( assert_eq!(
Mode::ALPHANUMERIC, Mode::ALPHANUMERIC,
Mode::CodecModeForBits(0x01, Some(true)).unwrap() Mode::CodecModeForBits(0x01, Some(Type::Micro)).unwrap()
); );
assert_eq!( assert_eq!(
Mode::BYTE, Mode::BYTE,
Mode::CodecModeForBits(0x02, Some(true)).unwrap() Mode::CodecModeForBits(0x02, Some(Type::Micro)).unwrap()
); );
assert_eq!( assert_eq!(
Mode::KANJI, Mode::KANJI,
Mode::CodecModeForBits(0x03, Some(true)).unwrap() Mode::CodecModeForBits(0x03, Some(Type::Micro)).unwrap()
); );
// M4 // M4
assert_eq!( assert_eq!(
Mode::NUMERIC, Mode::NUMERIC,
Mode::CodecModeForBits(0x00, Some(true)).unwrap() Mode::CodecModeForBits(0x00, Some(Type::Micro)).unwrap()
); );
assert_eq!( assert_eq!(
Mode::ALPHANUMERIC, Mode::ALPHANUMERIC,
Mode::CodecModeForBits(0x01, Some(true)).unwrap() Mode::CodecModeForBits(0x01, Some(Type::Micro)).unwrap()
); );
assert_eq!( assert_eq!(
Mode::BYTE, Mode::BYTE,
Mode::CodecModeForBits(0x02, Some(true)).unwrap() Mode::CodecModeForBits(0x02, Some(Type::Micro)).unwrap()
); );
assert_eq!( assert_eq!(
Mode::KANJI, 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] #[test]
@@ -133,3 +145,87 @@ fn MicroCharacterCount() {
Mode::CharacterCountBits(&Mode::KANJI, Version::Micro(4).unwrap()) 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")
)
);
}

View File

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

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

View File

@@ -12,3 +12,5 @@ mod QRDataMaskTest;
mod QRBitMatrixParserTest; mod QRBitMatrixParserTest;
mod MQRDecoderTest; mod MQRDecoderTest;
mod RMQRDecoderTest;

View File

@@ -78,6 +78,7 @@ pub struct FormatInformation {
pub mask: u32, // = 0 pub mask: u32, // = 0
pub data: u32, // = 255 pub data: u32, // = 255
pub bitsIndex: u8, // = 255; pub bitsIndex: u8, // = 255;
pub rMQRVersion: u8, //= 0;
} }
impl Default for FormatInformation { impl Default for FormatInformation {
@@ -91,6 +92,7 @@ impl Default for FormatInformation {
mask: 0, mask: 0,
data: 255, data: 255,
bitsIndex: 255, bitsIndex: 255,
rMQRVersion: 0,
} }
} }
} }
@@ -110,6 +112,7 @@ impl FormatInformation {
mask: 0, mask: 0,
bitsIndex: 255, bitsIndex: 255,
data: 255, data: 255,
rMQRVersion: 0,
}) })
} }

View File

@@ -8,6 +8,7 @@ mod mode;
mod qr_code_decoder_meta_data; mod qr_code_decoder_meta_data;
pub mod qrcode_decoder; pub mod qrcode_decoder;
mod version; mod version;
mod version_build_versions_arrays;
#[cfg(test)] #[cfg(test)]
mod DecodedBitStreamParserTestCase; mod DecodedBitStreamParserTestCase;

View File

@@ -15,6 +15,7 @@
*/ */
use crate::common::Result; use crate::common::Result;
use crate::qrcode::cpp_port::Type;
use crate::Exceptions; use crate::Exceptions;
use super::Version; use super::Version;
@@ -82,6 +83,7 @@ impl Mode {
*/ */
pub fn getCharacterCountBits(&self, version: &Version) -> u8 { pub fn getCharacterCountBits(&self, version: &Version) -> u8 {
let number = version.getVersionNumber(); let number = version.getVersionNumber();
let offset = if number <= 9 { let offset = if number <= 9 {
0 0
} else if number <= 26 { } 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() { (if version.isMicro() {
version.getVersionNumber() * 2 + 1 version.getVersionNumber() * 2 + 1
} else { } else {
4 4 - u32::from(version.isRMQR())
}) as u8 }) 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() { (if version.isMicro() {
version.getVersionNumber() - 1 version.getVersionNumber() - 1
} else { } else {
4 4 - u32::from(version.isRMQR())
}) as u8 }) as u8
} }
/** /**
@@ -142,19 +145,33 @@ impl Mode {
* @return Mode encoded by these bits * @return Mode encoded by these bits
* @throws FormatError if bits do not correspond to a known mode * @throws FormatError if bits do not correspond to a known mode
*/ */
pub fn CodecModeForBits(bits: u32, isMicro: Option<bool>) -> Result<Self> { pub fn CodecModeForBits(bits: u32, qr_type: Option<Type>) -> Result<Self> {
let isMicro = isMicro.unwrap_or(false); let qr_type = qr_type.unwrap_or(Type::Model2);
const BITS_2_MODE_LEN: usize = 4; let bits = bits as usize;
if !isMicro { if (qr_type == Type::Micro) {
if (0x00..=0x05).contains(&bits) || (0x07..=0x09).contains(&bits) || bits == 0x0d { const Bits2Mode: [Mode; 4] =
return Mode::try_from(bits); [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 { } else {
const BITS_2_MODE: [Mode; BITS_2_MODE_LEN] = if ((bits >= 0x00 && bits <= 0x05) || (bits >= 0x07 && bits <= 0x09) || bits == 0x0d) {
[Mode::NUMERIC, Mode::ALPHANUMERIC, Mode::BYTE, Mode::KANJI]; return Mode::try_from(bits as u32);
if (bits as usize) < BITS_2_MODE_LEN {
return Ok(BITS_2_MODE[bits as usize]);
} }
} }
@@ -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 { let i = if number <= 9 {
0 0
} else if number <= 26 { } else if number <= 26 {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1 @@
3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081

View File

@@ -0,0 +1 @@
symbologyIdentifier=]Q1

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 B

View File

@@ -0,0 +1 @@
,,

View File

@@ -0,0 +1 @@
isInverted=true

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 B

View File

@@ -0,0 +1 @@
,,

View File

@@ -311,3 +311,18 @@ fn cpp_qrcode_black_box7_test_case() {
tester.test_black_box(); tester.test_black_box();
} }
#[test]
fn cpp_rmqr_blackbox_test_case() {
let mut tester = common::AbstractBlackBoxTestCase::new(
"test_resources/blackbox/cpp/rmqrcode-1",
QrReader,
BarcodeFormat::RECTANGULAR_MICRO_QR_CODE,
);
tester.add_test(1, 1, 0.0);
tester.add_test(1, 1, 90.0);
tester.add_test(1, 1, 180.0);
tester.add_test(1, 1, 270.0);
tester.test_black_box();
}