From c7422198d6151dd4e28e25d939bb3a0d2a5ab343 Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Sat, 13 Jan 2024 15:20:15 -0600 Subject: [PATCH] port: QRCode: restructure Format and Version code after rRMQ addition https://github.com/zxing-cpp/zxing-cpp/commit/f27106c78c78287bf74d22958c83a09da6986e92 --- .../base_extentions/qr_formatinformation.rs | 12 +- .../base_extentions/qrcode_version.rs | 129 +++++++++------ src/qrcode/cpp_port/bitmatrix_parser.rs | 4 +- src/qrcode/cpp_port/detector.rs | 152 ++++++------------ src/qrcode/cpp_port/test/QRVersionTest.rs | 4 +- src/qrcode/decoder/format_information.rs | 3 - src/qrcode/decoder/version.rs | 32 ++-- src/rxing_result_point.rs | 14 +- 8 files changed, 170 insertions(+), 180 deletions(-) diff --git a/src/common/cpp_essentials/base_extentions/qr_formatinformation.rs b/src/common/cpp_essentials/base_extentions/qr_formatinformation.rs index bee5c63..2dd548d 100644 --- a/src/common/cpp_essentials/base_extentions/qr_formatinformation.rs +++ b/src/common/cpp_essentials/base_extentions/qr_formatinformation.rs @@ -102,23 +102,23 @@ impl FormatInformation { */ 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)], + &[formatInfoBits1], + &[formatInfoBits2], ) } else { // TODO probably remove if `sampleRMQR()` done properly - Self::FindBestFormatInfoRMQR(&[formatInfoBits1, mirror18Bits(formatInfoBits1)], &[]) + Self::FindBestFormatInfoRMQR(&[formatInfoBits1], &[]) }; // Bit 6 is error correction (M/H), and bits 0-5 version. fi.error_correction_level = ErrorCorrectionLevel::ECLevelFromBits(((fi.data >> 5) as u8 & 1) << 1, false); // Shift to match QRCode M/H fi.data_mask = 4; // ((y / 2) + (x / 3)) % 2 == 0 - fi.rMQRVersion = fi.data as u8 & 0x1F; - fi.isMirrored = fi.bitsIndex > 1; + fi.microVersion = (fi.data & 0x1F) + 1; + fi.isMirrored = false; // TODO: implement mirrored format bit reading fi } diff --git a/src/common/cpp_essentials/base_extentions/qrcode_version.rs b/src/common/cpp_essentials/base_extentions/qrcode_version.rs index 0bdb1a2..b6b7084 100644 --- a/src/common/cpp_essentials/base_extentions/qrcode_version.rs +++ b/src/common/cpp_essentials/base_extentions/qrcode_version.rs @@ -15,7 +15,7 @@ use crate::qrcode::decoder::{ }; use crate::{point, Exceptions, PointI}; -const dimsVersionRMQR: [PointI; 32] = [ +const RMQR_SIZES: [PointI; 32] = [ point(43, 7), point(59, 7), point(77, 7), @@ -144,59 +144,96 @@ impl Version { Type::const_eq(self.qr_type, Type::RectMicro) } - pub fn HasMicroSize(bitMatrix: &BitMatrix) -> bool { - let size = bitMatrix.height(); - size == bitMatrix.width() && (11..=17).contains(&size) && (size % 2) == 1 + pub fn SymbolSize(version: u32, qr_type: Type) -> PointI { + let version = version as i32; + + let square = |s: i32| point(s, s); + let valid = |v: i32, max: i32| v >= 1 && v <= max; + + match (qr_type) { + Type::Model1 => { + if valid(version, 32) { + square(17 + 4 * version) + } else { + PointI::default() + } + } + Type::Model2 => { + if valid(version, 40) { + square(17 + 4 * version) + } else { + PointI::default() + } + } + Type::Micro => { + if valid(version, 4) { + square(9 + 2 * version) + } else { + PointI::default() + } + } + Type::RectMicro => { + if valid(version, 32) { + RMQR_SIZES[(version - 1) as usize] + } else { + PointI::default() + } + } + } } - pub fn HasRMQRSize(bitMatrix: &BitMatrix) -> bool { - Self::getVersionRMQR(bitMatrix) != -1 + pub fn IsValidSize(size: PointI, qr_type: Type) -> bool { + match (qr_type) { + Type::Model1 => size.x == size.y && size.x >= 21 && size.x <= 145 && (size.x % 4 == 1), + Type::Model2 => size.x == size.y && size.x >= 21 && size.x <= 177 && (size.x % 4 == 1), + Type::Micro => size.x == size.y && size.x >= 11 && size.x <= 17 && (size.x % 2 == 1), + Type::RectMicro => { + size.x != size.y + && size.x.is_odd() + && size.y.is_odd() + && size.x >= 27 + && size.x <= 139 + && size.y >= 7 + && size.y <= 17 + && Self::IndexOf(&RMQR_SIZES, size) != -1 + } + } + } + pub fn HasValidSizeType(bitMatrix: &BitMatrix, qr_type: Type) -> bool { + return Self::IsValidSize( + point(bitMatrix.width() as i32, bitMatrix.height() as i32), + qr_type, + ); } - pub fn HasValidSize(bitMatrix: &BitMatrix) -> bool { - let size = bitMatrix.height(); - if bitMatrix.width() != size { - Self::HasRMQRSize(bitMatrix) + pub fn HasValidSize(matrix: &BitMatrix) -> bool { + return Self::HasValidSizeType(matrix, Type::Model1) + || Self::HasValidSizeType(matrix, Type::Model2) + || Self::HasValidSizeType(matrix, Type::Micro) + || Self::HasValidSizeType(matrix, Type::RectMicro); + } + + fn IndexOf(points: &[PointI], search: PointI) -> i32 { + RMQR_SIZES + .iter() + .position(|p| *p == search) + .and_then(|x| Some(x as i32)) + .unwrap_or(-1) + } + + pub fn NumberPoint(size: PointI) -> u32 { + if (size.x != size.y) { + return (Self::IndexOf(&RMQR_SIZES, size) + 1) as u32; + } else if (Self::IsValidSize(size, Type::Model2)) { + return ((size.x as i32 - 17) / 4) as u32; + } else if (Self::IsValidSize(size, Type::Micro)) { + return ((size.x as i32 - 9) / 2) as u32; } else { - Self::HasMicroSize(bitMatrix) || ((21..=177).contains(&size) && (size % 4) == 1) + return 0; } } pub fn Number(bitMatrix: &BitMatrix) -> u32 { - 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.is_odd() - && height.is_odd() - && (27..=139).contains(&width) - && (7..=17).contains(&height) - { - 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; - } - } - } - -1 + return Self::NumberPoint(point(bitMatrix.width() as i32, bitMatrix.height() as i32)); } } diff --git a/src/qrcode/cpp_port/bitmatrix_parser.rs b/src/qrcode/cpp_port/bitmatrix_parser.rs index 9417f4b..b625a02 100644 --- a/src/qrcode/cpp_port/bitmatrix_parser.rs +++ b/src/qrcode/cpp_port/bitmatrix_parser.rs @@ -37,7 +37,7 @@ pub fn ReadVersion(bitMatrix: &BitMatrix, qr_type: Type) -> Result { } pub fn ReadFormatInformation(bitMatrix: &BitMatrix) -> Result { - if Version::HasMicroSize(bitMatrix) { + if Version::HasValidSizeType(bitMatrix, Type::Micro) { // Read top-left format info bits let mut formatInfoBits = 0; for x in 1..9 { @@ -52,7 +52,7 @@ pub fn ReadFormatInformation(bitMatrix: &BitMatrix) -> Result return Ok(FormatInformation::DecodeMQR(formatInfoBits as u32)); } - if Version::HasRMQRSize(bitMatrix) { + if Version::HasValidSizeType(bitMatrix, Type::RectMicro) { // Read top-left format info bits let mut formatInfoBits1 = 0; for y in (1..=3).rev() { diff --git a/src/qrcode/cpp_port/detector.rs b/src/qrcode/cpp_port/detector.rs index 91c4f26..e330e52 100644 --- a/src/qrcode/cpp_port/detector.rs +++ b/src/qrcode/cpp_port/detector.rs @@ -10,7 +10,7 @@ use crate::{ decoder::{FormatInformation, Version, VersionRef}, detector::QRCodeDetectorResult, }, - Exceptions, + Exceptions, point, }; use multimap::MultiMap; use num::Integer; @@ -27,6 +27,8 @@ use crate::{ point_f, Point, }; +use super::Type; + #[derive(Copy, Clone, Default, Debug, PartialEq, Eq)] pub struct FinderPatternSet { pub bl: ConcentricPattern, @@ -40,8 +42,6 @@ pub type FinderPatternSets = Vec; const LEN: usize = 5; const SUM: usize = 7; const PATTERN: FixedPattern = 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> { @@ -570,7 +570,7 @@ pub fn SampleQR(image: &BitMatrix, fp: &FinderPatternSet) -> Result= Version::DimensionOfVersion(7, false) as i32 { + if dimension >= Version::SymbolSize(7, Type::Model2).x as i32 { let version = ReadVersion(image, dimension as u32, mod2Pix); // if the version bits are garbage -> discard the detection @@ -799,10 +799,9 @@ pub fn DetectPureQR(image: &BitMatrix) -> Result { // SaveAsPBM(image, "weg.pbm"); // #endif - let MIN_MODULES: u32 = Version::DimensionOfVersion(1, false); - let MAX_MODULES: u32 = Version::DimensionOfVersion(40, false); + let MIN_MODULES : i32 = Version::SymbolSize(1, Type::Model2).x; - 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 as u32); if !found || (width as i32 - height as i32).abs() > 1 { return Err(Exceptions::NOT_FOUND); @@ -846,8 +845,7 @@ pub fn DetectPureQR(image: &BitMatrix) -> Result { .dim; let moduleSize: f32 = ((width) as f32) / dimension as f32; - if dimension < MIN_MODULES as i32 - || dimension > MAX_MODULES as i32 + if !Version::IsValidSize(point(dimension, dimension), Type::Model2) || !image.is_in(point_f( left as f32 + moduleSize / 2.0 + (dimension - 1) as f32 * moduleSize, top as f32 + moduleSize / 2.0 + (dimension - 1) as f32 * moduleSize, @@ -888,10 +886,9 @@ pub fn DetectPureQR(image: &BitMatrix) -> Result { pub fn DetectPureMQR(image: &BitMatrix) -> Result { type Pattern = [PatternType; 5]; - const MIN_MODULES: u32 = Version::DimensionOfVersion(1, true); - const MAX_MODULES: u32 = Version::DimensionOfVersion(4, true); + let MIN_MODULES : i32= Version::SymbolSize(1, Type::Micro).x; - 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 as u32); // int left, top, width, height; if !found || (width as i32 - height as i32).abs() > 1 { @@ -914,7 +911,7 @@ pub fn DetectPureMQR(image: &BitMatrix) -> Result { let moduleSize: f32 = (fpWidth as f32) / 7.0; let dimension = (width as f32 / moduleSize).floor() as u32; - if !(MIN_MODULES..=MAX_MODULES).contains(&dimension) + if !Version::IsValidSize(point(dimension as i32, dimension as i32), Type::Micro) || !image.is_in(point_f( left as f32 + moduleSize / 2.0 + (dimension - 1) as f32 * moduleSize, top as f32 + moduleSize / 2.0 + (dimension - 1) as f32 * moduleSize, @@ -952,23 +949,25 @@ pub fn DetectPureMQR(image: &BitMatrix) -> Result { } pub fn DetectPureRMQR(image: &BitMatrix) -> Result { + const SUBPATTERN : FixedPattern<4,4> = FixedPattern::new([1, 1, 1, 1]); + const TIMINGPATTERN : FixedPattern<10,10>= FixedPattern::new([1, 1, 1, 1, 1, 1, 1, 1, 1, 1]); + type Pattern = [PatternType; 5]; //std::array; - type SubPattern = [PatternType; 5]; //std::array; - type CornerEdgePattern = [PatternType; 2]; //std::array; + // type SubPattern = [PatternType; 5]; //std::array; + // type CornerEdgePattern = [PatternType; 2]; //std::array; + + type SubPattern = [PatternType;4]; + type TimingPattern = [PatternType;10]; // #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 MIN_MODULES : i32 = Version::SymbolSize(1, Type::RectMicro).y; - 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 as u32); - if !found { + if !found || height >= width{ return Err(Exceptions::NOT_FOUND); } let right = left + width - 1; @@ -989,76 +988,37 @@ pub fn DetectPureRMQR(image: &BitMatrix) -> Result { 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::(&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::(&view, &CORNER_EDGE_RMQR, None, 0.0, 0.0) != 0.0) { - { - return Err(Exceptions::NOT_FOUND); - } - } - } + let fpWidth = (diagonal).into_iter().sum::(); 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() - || !(MIN_MODULES_W..=MAX_MODULES_W).contains(&dimW) - || !(MIN_MODULES_H..=MAX_MODULES_H).contains(&dimH) - || !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); - } + if (!Version::IsValidSize(point(dimW as i32, dimH as i32), Type::RectMicro)) + {return Err(Exceptions::NOT_FOUND);} + + // Finder sub pattern + let subdiagonal : SubPattern = EdgeTracer::new(image, br, point_i(-1, -1)).readPatternFromBlack(1,None).ok_or(Exceptions::ILLEGAL_STATE)?; + let subdiagonal_hld = diagonal.to_vec().into(); + let view = PatternView::new(&subdiagonal_hld); + if IsPattern::(&view, &SUBPATTERN, None, 0.0, 0.0) != 0.0 + {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::(&view, &CORNER_EDGE_RMQR, None, 0.0, 0.0) != 0.0) { - return Err(Exceptions::NOT_FOUND); + // Horizontal timing patterns + for (p, d) in [(tr, point(-1, 0)), (bl, point(1, 0)), (tl, point(1, 0)), (br, point(-1, 0))] { + let mut cur = EdgeTracer::new(image, p, d.into()); + // skip corner / finder / sub pattern edge + cur.stepToEdge(Some(2 + i32::from(cur.isWhite())), None, None); + let timing : TimingPattern = cur.readPattern(None).ok_or(Exceptions::ILLEGAL_STATE)?; + let timing_hld = diagonal.to_vec().into(); + let view = PatternView::new(&timing_hld); + if !(IsPattern::(&view, &TIMINGPATTERN, 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::(&view, &CORNER_EDGE_RMQR, None, 0.0, 0.0) != 0.0) { - return Err(Exceptions::NOT_FOUND); - } - } - } + + // #ifdef PRINT_DEBUG // LogMatrix log; @@ -1159,7 +1119,7 @@ pub fn SampleMQR(image: &BitMatrix, fp: ConcentricPattern) -> Result Result Result Result { if self.isRMQR() { - let dimension = Version::DimensionOfVersionRMQR(self.versionNumber); - let mut bitMatrix = BitMatrix::new(dimension.x as u32, dimension.y as u32)?; + let size = Version::SymbolSize(self.versionNumber, Type::RectMicro); + let mut bitMatrix = BitMatrix::new(size.x as u32, size.y as u32)?; // Set edge timing patterns - bitMatrix.setRegion(0, 0, dimension.x as u32, 1)?; // Top - bitMatrix.setRegion(0, (dimension.y - 1) as u32, dimension.x as u32, 1)?; // Bottom - bitMatrix.setRegion(0, 1, 1, (dimension.y - 2) as u32)?; // Left - bitMatrix.setRegion((dimension.x - 1) as u32, 1, 1, (dimension.y - 2) as u32)?; // Right + bitMatrix.setRegion(0, 0, size.x as u32, 1)?; // Top + bitMatrix.setRegion(0, (size.y - 1) as u32, size.x as u32, 1)?; // Bottom + bitMatrix.setRegion(0, 1, 1, (size.y - 2) as u32)?; // Left + bitMatrix.setRegion((size.x - 1) as u32, 1, 1, (size.y - 2) as u32)?; // Right // Set vertical timing and alignment patterns let max = self.alignmentPatternCenters.len(); // Same as vertical timing column @@ -217,32 +217,32 @@ impl Version { // for (size_t x = 0; x < max; ++x) { let cx = self.alignmentPatternCenters[x]; bitMatrix.setRegion(cx - 1, 1, 3, 2)?; // Top alignment pattern - bitMatrix.setRegion(cx - 1, (dimension.y - 3) as u32, 3, 2)?; // Bottom alignment pattern - bitMatrix.setRegion(cx, 3, 1, (dimension.y - 6) as u32)?; // Vertical timing pattern + bitMatrix.setRegion(cx - 1, (size.y - 3) as u32, 3, 2)?; // Bottom alignment pattern + bitMatrix.setRegion(cx, 3, 1, (size.y - 6) as u32)?; // Vertical timing pattern } // Top left finder pattern + separator - bitMatrix.setRegion(1, 1, 8 - 1, 8 - 1 - u32::from(dimension.y == 7))?; // R7 finder bottom flush with edge + bitMatrix.setRegion(1, 1, 8 - 1, 8 - 1 - u32::from(size.y == 7))?; // R7 finder bottom flush with edge // Top left format bitMatrix.setRegion(8, 1, 3, 5)?; bitMatrix.setRegion(11, 1, 1, 3)?; // Bottom right finder subpattern bitMatrix.setRegion( - (dimension.x - 5) as u32, - (dimension.y - 5) as u32, + (size.x - 5) as u32, + (size.y - 5) as u32, 5 - 1, 5 - 1, )?; // Bottom right format - bitMatrix.setRegion((dimension.x - 8) as u32, (dimension.y - 6) as u32, 3, 5)?; - bitMatrix.setRegion((dimension.x - 5) as u32, (dimension.y - 6) as u32, 3, 1)?; + bitMatrix.setRegion((size.x - 8) as u32, (size.y - 6) as u32, 3, 5)?; + bitMatrix.setRegion((size.x - 5) as u32, (size.y - 6) as u32, 3, 1)?; // Top right corner finder - bitMatrix.set((dimension.x - 2) as u32, 1); - if dimension.y > 9 { + bitMatrix.set((size.x - 2) as u32, 1); + if size.y > 9 { // Bottom left corner finder - bitMatrix.set(1, (dimension.y - 2) as u32); + bitMatrix.set(1, (size.y - 2) as u32); } return Ok(bitMatrix); diff --git a/src/rxing_result_point.rs b/src/rxing_result_point.rs index 205dfa1..c0d4450 100644 --- a/src/rxing_result_point.rs +++ b/src/rxing_result_point.rs @@ -90,12 +90,22 @@ impl Hash for Point { } } -impl PartialEq for Point { +// impl PartialEq for Point { +// fn eq(&self, other: &Self) -> bool { +// self.x == other.x && self.y == other.y +// } +// } +// impl Eq for Point {} + +impl PartialEq for PointT +where + T: PartialEq, +{ fn eq(&self, other: &Self) -> bool { self.x == other.x && self.y == other.y } } -impl Eq for Point {} +impl Eq for PointT where T: PartialEq {} impl PointT where